Sign In

@ask-llm/codex-mcp

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ask-llm/codex-mcp - npm Package Compare versions

Comparing version
0.7.1
to
0.7.2
+1437
dist/codexExecutor-DBI0E31B.js
import { ZodError, z } from "zod";
import { createHash, randomUUID } from "node:crypto";
import { existsSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
import * as os from "node:os";
import { homedir, tmpdir } from "node:os";
import * as path from "node:path";
import { delimiter, isAbsolute, join } from "node:path";
import { execFile, execFileSync, spawn } from "node:child_process";
import { promisify } from "node:util";
//#region ../shared/dist/providers.js
/**
* Single source of truth for the provider list (ADR-128).
*
* Every provider-name enum, type union, or user-facing provider list in the
* monorepo must derive from this tuple — hand-maintained copies drifted when
* antigravity was added (see BUGS.md 2026-07-02 audit entry).
*/
const PROVIDERS = [
"gemini",
"codex",
"claude",
"ollama",
"antigravity"
];
//#endregion
//#region ../shared/dist/askResponse.js
const usageStatsSchema = z.object({
provider: z.enum(PROVIDERS),
model: z.string(),
inputTokens: z.number().optional(),
outputTokens: z.number().optional(),
cachedTokens: z.number().optional(),
thinkingTokens: z.number().optional(),
durationMs: z.number(),
fellBack: z.boolean()
});
const askResponseSchema = z.object({
provider: z.enum(PROVIDERS),
response: z.string(),
model: z.string(),
sessionId: z.string().optional(),
usage: usageStatsSchema.optional()
});
//#endregion
//#region ../shared/dist/constants.js
const LOG_PREFIX = "[GMCPT]";
const LOG_LEVEL_ENV_VAR = "GMCPT_LOG_LEVEL";
const PROTOCOL = {
ROLES: {
USER: "user",
ASSISTANT: "assistant"
},
CONTENT_TYPES: { TEXT: "text" },
STATUS: {
SUCCESS: "success",
ERROR: "error",
FAILED: "failed",
REPORT: "report"
},
NOTIFICATIONS: { PROGRESS: "notifications/progress" },
KEEPALIVE_INTERVAL: 25e3
};
const EXECUTION = {
DEFAULT_TIMEOUT_MS: 21e4,
DEFAULT_CODEX_TIMEOUT_MS: 8e5,
DEFAULT_CLAUDE_TIMEOUT_MS: 6e5,
DEFAULT_OLLAMA_TIMEOUT_MS: 6e5,
TIMEOUT_ENV_VAR: "GMCPT_TIMEOUT_MS",
CODEX_TIMEOUT_ENV_VAR: "ASK_CODEX_TIMEOUT_MS",
CLAUDE_TIMEOUT_ENV_VAR: "ASK_CLAUDE_TIMEOUT_MS",
GEMINI_TIMEOUT_ENV_VAR: "ASK_GEMINI_TIMEOUT_MS",
OLLAMA_TIMEOUT_ENV_VAR: "ASK_OLLAMA_TIMEOUT_MS",
ERROR_TRUNCATE_LENGTH: 2e3,
STDIN_THRESHOLD_BYTES: 16384
};
//#endregion
//#region ../shared/dist/logger.js
const LOG_LEVEL_PRIORITY = {
debug: 0,
info: 1,
warn: 2,
error: 3
};
function getLogLevel() {
const env = process.env[LOG_LEVEL_ENV_VAR]?.toLowerCase();
if (env && env in LOG_LEVEL_PRIORITY) return env;
return "warn";
}
function shouldLog(level) {
return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[getLogLevel()];
}
var Logger = class Logger {
static _nextCommandId = 0;
static _commandStartTimes = /* @__PURE__ */ new Map();
static formatMessage(message) {
return `${LOG_PREFIX} ${message}`;
}
static warn(message, ...args) {
if (!shouldLog("warn")) return;
console.warn(Logger.formatMessage(message), ...args);
}
static error(message, ...args) {
if (!shouldLog("error")) return;
console.error(Logger.formatMessage(message), ...args);
}
static debug(message, ...args) {
if (!shouldLog("debug")) return;
console.warn(Logger.formatMessage(message), ...args);
}
static info(message, ...args) {
if (!shouldLog("info")) return;
console.warn(Logger.formatMessage(message), ...args);
}
static toolInvocation(toolName, args) {
Logger.warn(`Tool "${toolName}" raw args:`, JSON.stringify(args, null, 2));
}
static toolParsedArgs(prompt, model, sandbox, changeMode) {
Logger.warn(`Parsed prompt: "${prompt}"\nmodel: ${model ?? "default"}, sandbox: ${sandbox ?? false}, changeMode: ${changeMode ?? false}`);
}
static commandExecution(command, args) {
const commandId = Logger._nextCommandId++;
Logger._commandStartTimes.set(commandId, Date.now());
Logger.warn(`[cmd:${commandId}] Starting: ${command} ${args.map((arg) => `"${arg}"`).join(" ")}`);
return commandId;
}
static commandComplete(commandId, exitCode, outputLength) {
const startTime = Logger._commandStartTimes.get(commandId);
const elapsed = startTime ? ((Date.now() - startTime) / 1e3).toFixed(1) : "?";
Logger.warn(`[cmd:${commandId}] [${elapsed}s] Process finished with exit code: ${exitCode}`);
if (outputLength !== void 0) Logger.warn(`[cmd:${commandId}] Response: ${outputLength} chars`);
Logger._commandStartTimes.delete(commandId);
}
static checkNodeVersion(minMajor = 20) {
if (parseInt(process.versions.node.split(".")[0], 10) < minMajor) Logger.error(`Node.js v${process.versions.node} detected — v${minMajor}+ required. Some providers (e.g., gemini-cli) use ES2024 features that will crash on older runtimes.`);
}
};
//#endregion
//#region ../shared/dist/changeMode/changeModeParser.js
function validateChangeModeEdits(edits) {
const errors = [];
for (const edit of edits) {
if (!edit.filename) errors.push("Edit missing filename");
if (edit.oldStartLine > edit.oldEndLine) errors.push(`Invalid line range for ${edit.filename}: ${edit.oldStartLine} > ${edit.oldEndLine}`);
if (edit.newStartLine > edit.newEndLine) errors.push(`Invalid new line range for ${edit.filename}: ${edit.newStartLine} > ${edit.newEndLine}`);
if (!edit.oldCode && !edit.newCode) errors.push(`Empty edit for ${edit.filename}`);
}
return {
valid: errors.length === 0,
errors
};
}
//#endregion
//#region ../shared/dist/changeMode/changeModeTranslator.js
function formatChangeModeResponse(edits, chunkInfo) {
const header = chunkInfo && chunkInfo.total > 1 ? `[CHANGEMODE OUTPUT - Chunk ${chunkInfo.current} of ${chunkInfo.total}]
Gemini has analyzed your codebase and generated edits across ${chunkInfo.total} chunks.
This chunk contains ${edits.length} complete edit${edits.length === 1 ? "" : "s"} that can be applied independently.
Each chunk contains self-contained edits grouped by file. You can safely apply these edits
before fetching the next chunk.
` : `[CHANGEMODE OUTPUT - Gemini has analyzed the files and provided these edits]
I have prepared ${edits.length} modification${edits.length === 1 ? "" : "s"} for your codebase.
IMPORTANT: Apply these edits directly WITHOUT reading the files first. The edits below contain exact text matches from the current file contents.
`;
const instructions = edits.map((edit, index) => {
return `### Edit ${index + 1}: ${edit.filename}
Replace this exact text:
\`\`\`
${edit.oldCode}
\`\`\`
With this text:
\`\`\`
${edit.newCode}
\`\`\`
`;
}).join("\n");
let footer = `
---
Apply these edits in order. Each edit uses exact string matching, so the old_str must match exactly what appears between the code blocks.`;
if (chunkInfo && chunkInfo.current < chunkInfo.total && chunkInfo.cacheKey) footer += `
---
**Next Step**: After applying the edits above, retrieve the next chunk (${chunkInfo.current + 1} of ${chunkInfo.total}) by calling the **fetch-chunk** MCP tool with:
- **cacheKey**: \`${chunkInfo.cacheKey}\`
- **chunkIndex**: \`${chunkInfo.current + 1}\`
There ${chunkInfo.total - chunkInfo.current === 1 ? "is" : "are"} ${chunkInfo.total - chunkInfo.current} more chunk${chunkInfo.total - chunkInfo.current === 1 ? "" : "s"} containing additional edits.
**CONTINUE**: You are working on a multi-chunk changeMode response. After applying these edits, fetch the next chunk to continue with the remaining modifications.`;
return header + instructions + footer;
}
function summarizeChangeModeEdits(edits, isPartialView) {
const fileGroups = /* @__PURE__ */ new Map();
for (const edit of edits) fileGroups.set(edit.filename, (fileGroups.get(edit.filename) || 0) + 1);
const summary = Array.from(fileGroups.entries()).map(([file, count]) => `- ${file}: ${count} edit${count === 1 ? "" : "s"}`).join("\n");
return `${isPartialView ? `ChangeMode Summary (Complete analysis across all chunks):` : `ChangeMode Summary:`}
Total edits: ${edits.length}${isPartialView ? " (across all chunks)" : ""}
Files affected: ${fileGroups.size}
${summary}`;
}
path.join(os.tmpdir(), "gemini-mcp-chunks");
//#endregion
//#region ../shared/dist/shellPath.js
const IS_WINDOWS$1 = process.platform === "win32";
const SHELL_PATH_ENV_VAR = "ASK_LLM_PATH";
let cachedPath = null;
function extractShellPath() {
if (IS_WINDOWS$1) return null;
try {
const match = execFileSync(process.env.SHELL || "/bin/zsh", ["-ilc", "echo \"___PATH___$PATH___END___\""], {
encoding: "utf8",
stdio: [
"ignore",
"pipe",
"ignore"
],
timeout: 5e3
}).match(/___PATH___(.*)___END___/);
if (match?.[1]) return match[1].trim();
} catch {
Logger.debug("Failed to extract PATH from login shell");
}
return null;
}
function findNvmNodePath() {
const nvmDir = join(homedir(), ".nvm", "versions", "node");
if (!existsSync(nvmDir)) return null;
try {
const versions = readdirSync(nvmDir).filter((v) => {
return parseInt(v.replace("v", "").split(".")[0], 10) >= 20;
}).sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
if (versions.length > 0) {
const binDir = join(nvmDir, versions[0], "bin");
if (existsSync(binDir)) return binDir;
}
} catch {
Logger.debug("Failed to scan nvm versions");
}
return null;
}
function buildAugmentedPath() {
const currentPath = process.env.PATH || "";
const home = homedir();
const candidates = [];
const nvmBin = findNvmNodePath();
if (nvmBin) candidates.push(nvmBin);
for (const dir of [
join(home, ".volta", "bin"),
join(home, ".local", "share", "fnm"),
"/opt/homebrew/bin",
"/usr/local/bin"
]) if (existsSync(dir)) candidates.push(dir);
if (candidates.length === 0) return currentPath;
return [...candidates, ...currentPath.split(delimiter)].join(delimiter);
}
function resolveShellPath() {
if (cachedPath !== null) return cachedPath;
const envOverride = process.env[SHELL_PATH_ENV_VAR];
if (envOverride) {
Logger.debug(`Using ${SHELL_PATH_ENV_VAR} override`);
cachedPath = envOverride;
return cachedPath;
}
if (IS_WINDOWS$1) {
cachedPath = process.env.PATH || "";
return cachedPath;
}
const shellPath = extractShellPath();
if (shellPath) {
Logger.debug("Using PATH from login shell");
cachedPath = shellPath;
return cachedPath;
}
Logger.debug("Login shell PATH extraction failed, using heuristic fallback");
cachedPath = buildAugmentedPath();
return cachedPath;
}
function getSpawnEnv() {
return {
...process.env,
PATH: resolveShellPath()
};
}
//#endregion
//#region ../shared/dist/commandExecutor.js
const IS_WINDOWS = process.platform === "win32";
const REDACTED_COMMAND_ARGUMENT = "<redacted>";
function isCommandNotFoundError(stderr, command) {
const lower = stderr.toLowerCase();
return lower.includes("command not found") || lower.includes("not found on path") || lower.includes("is not recognized as an internal or external command") || lower.includes(`spawn ${command.toLowerCase()} enoent`);
}
const QUOTA_PASSTHROUGH_PATTERNS = [
"RESOURCE_EXHAUSTED",
"TerminalQuotaError",
"exhausted your capacity",
"rate_limit_exceeded",
"quota_exceeded",
"insufficient_quota",
"usage limit"
];
function sanitizeErrorForLLM(stderr, command, platform = process.platform) {
if (stderr.includes("Invalid regular expression flags") && stderr.includes("Node.js v")) return `${command} CLI requires Node.js v20+ but is running on ${stderr.match(/Node\.js (v[\d.]+)/)?.[1] ?? "unknown"}. The user should update their Node version or set ASK_LLM_PATH in their MCP config to point to a Node v20+ installation.`;
if (isCommandNotFoundError(stderr, command)) return `${command} CLI not found on PATH. Ensure it is installed and accessible. Run "${platform === "win32" ? `where.exe ${command}` : `which ${command}`}" in a terminal to verify.`;
if (stderr.includes("EACCES") || stderr.includes("Permission denied")) return `Permission denied when running ${command} CLI. Check file permissions and try running with appropriate access.`;
const lower = stderr.toLowerCase();
const matchedQuotaPattern = QUOTA_PASSTHROUGH_PATTERNS.find((p) => lower.includes(p.toLowerCase()));
if (matchedQuotaPattern) {
if (stderr.length <= 500) return stderr;
const idx = lower.indexOf(matchedQuotaPattern.toLowerCase());
const start = Math.max(0, idx - 100);
const end = Math.min(start + 500, stderr.length);
const head = start > 0 ? "... (truncated) " : "";
const tail = end < stderr.length ? "... (truncated)" : "";
return `${head}${stderr.slice(start, end)}${tail}`;
}
const preview = stderr.split("\n").filter((l) => l.trim().length > 0).slice(0, 3).join("\n");
if (preview.length > 0 && preview.length < 500) return preview;
return stderr.length > 500 ? `${stderr.slice(0, 500)}... (truncated)` : stderr;
}
function parseTimeoutEnv(envVal) {
if (!envVal) return void 0;
const parsed = Number(envVal);
return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
}
function getTimeoutMs() {
return parseTimeoutEnv(process.env[EXECUTION.TIMEOUT_ENV_VAR]) ?? EXECUTION.DEFAULT_TIMEOUT_MS;
}
function resolveTimeoutMs(providerEnvVar, fallbackDefault) {
const providerVal = parseTimeoutEnv(process.env[providerEnvVar]);
if (providerVal !== void 0) return providerVal;
const globalVal = parseTimeoutEnv(process.env[EXECUTION.TIMEOUT_ENV_VAR]);
if (globalVal !== void 0) return globalVal;
return fallbackDefault;
}
function quoteArgsForWindows(args) {
return args.map((a) => {
if (a.includes(" ") || a.includes("\"") || a.includes("&") || a.includes("|") || a.includes("^")) return `"${a.replace(/"/g, "\\\"")}"`;
return a;
});
}
function argsForLogging(args, options) {
if (!options) return args;
const sensitiveValues = new Set(options.sensitiveValues);
return args.map((arg) => sensitiveValues.has(arg) ? REDACTED_COMMAND_ARGUMENT : arg);
}
async function executeCommand(command, args, onProgress, onStderr, stdinPayload, timeoutMs, commandLogging) {
return new Promise((resolve, reject) => {
const commandId = Logger.commandExecution(command, argsForLogging(args, commandLogging));
const childProcess = spawn(command, IS_WINDOWS ? quoteArgsForWindows(args) : args, {
env: getSpawnEnv(),
shell: IS_WINDOWS,
stdio: [
"pipe",
"pipe",
"pipe"
]
});
childProcess.stdin.on("error", () => {});
if (stdinPayload !== void 0 && stdinPayload.length > 0) childProcess.stdin.write(stdinPayload);
childProcess.stdin.end();
const stdoutChunks = [];
const stderrChunks = [];
let isResolved = false;
let killTimer;
const effectiveTimeoutMs = timeoutMs ?? getTimeoutMs();
const timer = setTimeout(() => {
if (isResolved) return;
isResolved = true;
Logger.warn(`[cmd:${commandId}] Timeout after ${effectiveTimeoutMs}ms, sending SIGTERM`);
childProcess.kill("SIGTERM");
killTimer = setTimeout(() => {
try {
childProcess.kill("SIGKILL");
} catch {}
}, 5e3);
killTimer.unref?.();
const timeoutSec = Math.round(effectiveTimeoutMs / 1e3);
reject(/* @__PURE__ */ new Error(`Command timed out after ${timeoutSec}s. The LLM provider took too long to respond. Try a shorter prompt or increase the timeout via the provider env var (ASK_CODEX_TIMEOUT_MS / ASK_CLAUDE_TIMEOUT_MS / ASK_GEMINI_TIMEOUT_MS) or the global ${EXECUTION.TIMEOUT_ENV_VAR} (current: ${effectiveTimeoutMs}ms).`));
}, effectiveTimeoutMs);
childProcess.stdout.on("data", (data) => {
stdoutChunks.push(data);
if (onProgress) onProgress(data.toString());
});
childProcess.stderr.on("data", (data) => {
stderrChunks.push(data);
if (onStderr) onStderr(data.toString());
});
childProcess.on("error", (error) => {
if (killTimer) clearTimeout(killTimer);
if (!isResolved) {
isResolved = true;
clearTimeout(timer);
Logger.error(`Process error:`, error);
reject(/* @__PURE__ */ new Error(`Failed to spawn command: ${error.message}`));
}
});
childProcess.on("close", (code) => {
if (killTimer) clearTimeout(killTimer);
if (!isResolved) {
isResolved = true;
clearTimeout(timer);
const stdout = Buffer.concat(stdoutChunks).toString();
if (code === 0) {
Logger.commandComplete(commandId, code, stdout.length);
resolve(stdout.trim());
} else {
Logger.commandComplete(commandId, code);
Logger.error(`Failed with exit code ${code}`);
const userMessage = sanitizeErrorForLLM([Buffer.concat(stderrChunks).toString().trim(), stdout.trim()].filter(Boolean).join("\n") || "Unknown error", command);
reject(new Error(userMessage));
}
}
});
});
}
promisify(execFile);
//#endregion
//#region ../shared/dist/pathValidation.js
/**
* Element schema for includeDirs-style tool parameters. Directories handed to
* provider CLIs (`--include-directories`, `--add-dir`) must stay inside the
* workspace: traversal, absolute, and home-relative paths would widen what the
* external CLI can read far beyond the repo the user pointed it at.
*/
const relativeDirSchema = z.string().refine((dir) => !dir.includes("..") && !isAbsolute(dir) && !dir.startsWith("~"), { message: "Directory paths must be relative without '..' or '~'" });
//#endregion
//#region ../shared/dist/machine.js
const providerFailureKindSchema = z.enum([
"rate_limited",
"auth_failed",
"unavailable",
"timeout",
"schema_invalid",
"tool_unavailable"
]);
const machineRoleSchema = z.enum([
"brainstorm",
"review",
"verify"
]);
const machineProviderSchema = z.enum([
"codex",
"claude",
"antigravity"
]);
const actorProviderSchema = z.enum(PROVIDERS);
const requestIdSchema = z.string().regex(/^[A-Za-z0-9._:-]{8,160}$/);
const nonBlankStringSchema = z.string().regex(/\S/, "Value must contain a non-whitespace character");
const machineRelativeDirSchema = relativeDirSchema.max(1024).regex(/^(?!.*\.\.)(?!~)(?!\/)(?![A-Za-z]:[\\/])(?!\\).*$/, "Directory paths must be relative without '..' or '~'");
z.object({
schemaVersion: z.literal(1),
requestId: requestIdSchema,
role: machineRoleSchema,
provider: machineProviderSchema,
prompt: z.string().min(1).max(15e4),
model: nonBlankStringSchema.max(256).optional(),
readOnly: z.literal(true),
writerProvider: actorProviderSchema.optional(),
includeDirs: z.array(machineRelativeDirSchema).max(16).default([])
}).strict().superRefine((value, ctx) => {
if (value.role === "review" && value.writerProvider === value.provider) ctx.addIssue({
code: "custom",
message: "review provider must differ from writer"
});
});
const reviewFindingSchema = z.object({
id: z.string().min(1),
severity: z.enum([
"critical",
"high",
"medium",
"low"
]),
confidence: z.number().int().min(0).max(100),
title: z.string().min(1),
evidence: z.string().min(1),
recommendation: z.string().min(1),
file: z.string().min(1).nullable(),
line: z.number().int().positive().nullable()
}).strict();
const reviewPayloadSchema = z.object({
summary: z.string(),
findings: z.array(reviewFindingSchema)
}).strict();
const brainstormPayloadSchema = z.object({
recommendation: z.string().min(1),
ideas: z.array(z.object({
title: z.string().min(1),
rationale: z.string().min(1),
risks: z.array(z.string())
}).strict()).min(1)
}).strict();
const verificationPayloadSchema = z.object({
verdict: z.enum([
"verified",
"partial",
"failed"
]),
claims: z.array(z.object({
claim: z.string().min(1),
status: z.enum([
"proven",
"disproven",
"unverifiable"
]),
evidence: z.string().min(1)
}).strict())
}).strict();
const normalizedTokenUsageSchema = z.object({
inputTokens: z.number().int().nonnegative(),
outputTokens: z.number().int().nonnegative(),
totalTokens: z.number().int().nonnegative()
}).strict();
const fallbackSchema = z.discriminatedUnion("occurred", [z.object({
occurred: z.literal(false),
requestedModel: nonBlankStringSchema.nullable(),
actualModel: nonBlankStringSchema.nullable()
}).strict(), z.object({
occurred: z.literal(true),
requestedModel: nonBlankStringSchema,
actualModel: nonBlankStringSchema
}).strict()]).refine((value) => value.occurred || value.requestedModel === value.actualModel, {
message: "requested and actual models must match when fallback did not occur",
path: ["actualModel"]
});
const sessionLocatorSchema = z.union([z.object({
sessionId: nonBlankStringSchema,
transcriptPath: nonBlankStringSchema.nullable()
}).strict(), z.object({
sessionId: nonBlankStringSchema.nullable(),
transcriptPath: nonBlankStringSchema
}).strict()]);
const quotaSignalSchema = z.discriminatedUnion("kind", [z.object({
kind: z.literal("reported"),
usedPercent: z.number().min(0).max(100),
windowHours: z.number().positive()
}).strict(), z.object({ kind: z.literal("runtime_proxy_required") }).strict()]);
const normalizedProviderFailureSchema = z.object({
kind: providerFailureKindSchema,
message: z.string().min(1)
}).strict();
const resultEnvelopeShape = {
schemaVersion: z.literal(1),
requestId: requestIdSchema,
provider: machineProviderSchema,
actualModel: nonBlankStringSchema.nullable(),
rawResponseSha256: z.string().regex(/^[a-f0-9]{64}$/).nullable(),
durationMs: z.number().int().nonnegative(),
usage: normalizedTokenUsageSchema.nullable(),
fallback: fallbackSchema,
session: sessionLocatorSchema.nullable(),
quotaSignal: quotaSignalSchema
};
const brainstormSuccessResultSchema = z.object({
...resultEnvelopeShape,
status: z.literal("success"),
role: z.literal("brainstorm"),
payload: brainstormPayloadSchema,
failure: z.null()
}).strict();
const reviewSuccessResultSchema = z.object({
...resultEnvelopeShape,
status: z.literal("success"),
role: z.literal("review"),
payload: reviewPayloadSchema,
failure: z.null()
}).strict();
const verificationSuccessResultSchema = z.object({
...resultEnvelopeShape,
status: z.literal("success"),
role: z.literal("verify"),
payload: verificationPayloadSchema,
failure: z.null()
}).strict();
const machineSuccessResultSchema = z.discriminatedUnion("role", [
brainstormSuccessResultSchema,
reviewSuccessResultSchema,
verificationSuccessResultSchema
]).refine((value) => value.actualModel === value.fallback.actualModel, {
message: "result and fallback actual models must match",
path: ["fallback", "actualModel"]
});
const machineFailureResultSchema = z.object({
...resultEnvelopeShape,
status: z.literal("failed"),
role: machineRoleSchema,
payload: z.null(),
failure: normalizedProviderFailureSchema
}).strict().refine((value) => value.actualModel === value.fallback.actualModel, {
message: "result and fallback actual models must match",
path: ["fallback", "actualModel"]
});
z.discriminatedUnion("status", [machineSuccessResultSchema, machineFailureResultSchema]);
//#endregion
//#region ../shared/dist/progressTracker.js
async function sendProgressNotification(extra, progress, total, message) {
const progressToken = extra._meta?.progressToken;
if (!progressToken) return;
try {
const params = {
progressToken,
progress
};
if (total !== void 0) params.total = total;
if (message) params.message = message;
await extra.sendNotification({
method: PROTOCOL.NOTIFICATIONS.PROGRESS,
params
});
} catch (error) {
Logger.error("Failed to send progress notification:", error);
}
}
function createProgressTracker(operationName, extra, messages) {
let active = true;
let latestOutput = "";
let messageIndex = 0;
let progress = 0;
sendProgressNotification(extra, 0, void 0, `Starting ${operationName}`);
const interval = setInterval(async () => {
if (active) {
progress += 1;
const baseMessage = messages[messageIndex % messages.length];
const outputPreview = latestOutput.slice(-150).trim();
const msg = outputPreview ? `${baseMessage}\nOutput: ...${outputPreview}` : baseMessage;
await sendProgressNotification(extra, progress, void 0, msg);
messageIndex++;
} else clearInterval(interval);
}, PROTOCOL.KEEPALIVE_INTERVAL);
return {
interval,
async stop(success) {
active = false;
clearInterval(interval);
await sendProgressNotification(extra, 100, 100, success ? `${operationName} completed` : `${operationName} failed`);
},
updateOutput(output) {
latestOutput = output;
}
};
}
//#endregion
//#region ../shared/dist/registry.js
const toolRegistry = [];
async function executeTool(toolName, args, onProgress, onUsage) {
const tool = toolRegistry.find((t) => t.name === toolName);
if (!tool) throw new Error(`Unknown tool: ${toolName}`);
try {
const validatedArgs = tool.zodSchema.parse(args);
return tool.execute(validatedArgs, onProgress, onUsage);
} catch (error) {
if (error instanceof ZodError) {
const issues = error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(", ");
throw new Error(`Invalid arguments for ${toolName}: ${issues}`);
}
throw error;
}
}
function getPromptMessage(toolName, args) {
if (!toolRegistry.find((t) => t.name === toolName)?.prompt) throw new Error(`No prompt defined for tool: ${toolName}`);
const paramStrings = [];
if (args.prompt) paramStrings.push(args.prompt);
Object.entries(args).forEach(([key, value]) => {
if (key !== "prompt" && value !== void 0 && value !== null && value !== "false") if (value === "true") paramStrings.push(`[${key}]`);
else paramStrings.push(`(${key}: ${value})`);
});
return `Use the ${toolName} tool${paramStrings.length > 0 ? `: ${paramStrings.join(" ")}` : ""}`;
}
//#endregion
//#region ../shared/dist/responseCache.js
const DEFAULT_TTL_MS = 1800 * 1e3;
const DEFAULT_MAX_SIZE_BYTES = 10 * 1024 * 1024;
const DEFAULT_MAX_ENTRIES = 100;
var ResponseCache = class {
cache = /* @__PURE__ */ new Map();
ttlMs;
maxSizeBytes;
maxEntries;
totalSizeBytes = 0;
constructor(options) {
this.ttlMs = options?.ttlMs ?? DEFAULT_TTL_MS;
this.maxSizeBytes = options?.maxSizeBytes ?? DEFAULT_MAX_SIZE_BYTES;
this.maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
}
static buildKey(provider, prompt, model, extra) {
const raw = `${provider}:${model ?? "default"}:${extra ?? ""}:${prompt}`;
return createHash("sha256").update(raw).digest("hex").slice(0, 16);
}
get(key) {
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() - entry.createdAt > this.ttlMs) {
this.delete(key);
Logger.debug(`Response cache expired: ${key}`);
return null;
}
entry.lastAccessedAt = Date.now();
return entry.response;
}
set(key, response) {
if (this.cache.has(key)) this.delete(key);
const sizeBytes = Buffer.byteLength(response, "utf-8");
if (sizeBytes > this.maxSizeBytes) {
Logger.debug(`Response too large to cache: ${sizeBytes} bytes`);
return;
}
while (this.totalSizeBytes + sizeBytes > this.maxSizeBytes || this.cache.size >= this.maxEntries) {
const lruKey = this.findLRU();
if (!lruKey) break;
this.delete(lruKey);
}
this.cache.set(key, {
response,
createdAt: Date.now(),
lastAccessedAt: Date.now(),
sizeBytes
});
this.totalSizeBytes += sizeBytes;
Logger.debug(`Response cached: ${key} (${sizeBytes} bytes, ${this.cache.size} entries, ${this.totalSizeBytes} total bytes)`);
}
get size() {
return this.cache.size;
}
get byteSize() {
return this.totalSizeBytes;
}
clear() {
this.cache.clear();
this.totalSizeBytes = 0;
}
delete(key) {
const entry = this.cache.get(key);
if (entry) {
this.totalSizeBytes -= entry.sizeBytes;
this.cache.delete(key);
}
}
findLRU() {
let oldestKey = null;
let oldestTime = Infinity;
for (const [key, entry] of this.cache) if (entry.lastAccessedAt < oldestTime) {
oldestTime = entry.lastAccessedAt;
oldestKey = key;
}
return oldestKey;
}
};
const responseCache = new ResponseCache();
//#endregion
//#region ../shared/dist/usage.js
function emptyProviderSnapshot() {
return {
calls: 0,
inputTokens: 0,
outputTokens: 0,
cachedTokens: 0,
thinkingTokens: 0,
durationMs: 0,
fellBack: 0
};
}
function addToBucket(bucket, stats) {
bucket.calls += 1;
bucket.inputTokens += stats.inputTokens ?? 0;
bucket.outputTokens += stats.outputTokens ?? 0;
bucket.cachedTokens += stats.cachedTokens ?? 0;
bucket.thinkingTokens += stats.thinkingTokens ?? 0;
bucket.durationMs += stats.durationMs;
if (stats.fellBack) bucket.fellBack += 1;
}
function createSessionUsage() {
let totalCalls = 0;
let totalInputTokens = 0;
let totalOutputTokens = 0;
let totalCachedTokens = 0;
let totalThinkingTokens = 0;
let totalDurationMs = 0;
let fallbackCount = 0;
const byProvider = {};
const byModel = {};
return {
record(stats) {
totalCalls += 1;
totalInputTokens += stats.inputTokens ?? 0;
totalOutputTokens += stats.outputTokens ?? 0;
totalCachedTokens += stats.cachedTokens ?? 0;
totalThinkingTokens += stats.thinkingTokens ?? 0;
totalDurationMs += stats.durationMs;
if (stats.fellBack) fallbackCount += 1;
if (!byProvider[stats.provider]) byProvider[stats.provider] = emptyProviderSnapshot();
addToBucket(byProvider[stats.provider], stats);
if (!byModel[stats.model]) byModel[stats.model] = emptyProviderSnapshot();
addToBucket(byModel[stats.model], stats);
},
snapshot() {
return {
totalCalls,
totalInputTokens,
totalOutputTokens,
totalCachedTokens,
totalThinkingTokens,
totalDurationMs,
fallbackCount,
byProvider: structuredClone(byProvider),
byModel: structuredClone(byModel)
};
},
reset() {
totalCalls = 0;
totalInputTokens = 0;
totalOutputTokens = 0;
totalCachedTokens = 0;
totalThinkingTokens = 0;
totalDurationMs = 0;
fallbackCount = 0;
for (const key of Object.keys(byProvider)) delete byProvider[key];
for (const key of Object.keys(byModel)) delete byModel[key];
}
};
}
function formatSessionUsage(snapshot) {
if (snapshot.totalCalls === 0) return "No LLM calls recorded in this session yet.";
const lines = [
"## Session Usage Summary",
"",
`Total calls: ${snapshot.totalCalls.toLocaleString()}`,
`Total input tokens: ${snapshot.totalInputTokens.toLocaleString()}`,
`Total output tokens: ${snapshot.totalOutputTokens.toLocaleString()}`
];
if (snapshot.totalThinkingTokens > 0) lines.push(`Total thinking tokens: ${snapshot.totalThinkingTokens.toLocaleString()}`);
if (snapshot.totalCachedTokens > 0) lines.push(`Total cached tokens: ${snapshot.totalCachedTokens.toLocaleString()}`);
lines.push(`Total wall time: ${(snapshot.totalDurationMs / 1e3).toFixed(1)}s`);
if (snapshot.fallbackCount > 0) lines.push(`Quota fallbacks triggered: ${snapshot.fallbackCount}`);
const providerEntries = Object.entries(snapshot.byProvider);
if (providerEntries.length > 0) {
lines.push("", "### By provider", "");
for (const [provider, bucket] of providerEntries) lines.push(`- **${provider}** — ${bucket.calls} calls, ${bucket.inputTokens.toLocaleString()} in / ${bucket.outputTokens.toLocaleString()} out tokens, ${(bucket.durationMs / 1e3).toFixed(1)}s` + (bucket.fellBack > 0 ? `, ${bucket.fellBack} fallbacks` : ""));
}
return lines.join("\n");
}
//#endregion
//#region ../shared/dist/serverFactory.js
const diagnosticCheckSchema = z.object({
name: z.string(),
status: z.enum([
"pass",
"warn",
"fail",
"skip"
]),
message: z.string(),
fix: z.string().optional()
});
const diagnosticProviderSchema = z.object({
name: z.string(),
command: z.string(),
available: z.boolean(),
cliPath: z.string().optional(),
cliVersion: z.string().optional(),
error: z.string().optional()
});
z.object({
status: z.enum([
"ok",
"warning",
"error"
]),
generatedAt: z.string(),
environment: z.object({
nodeVersion: z.string(),
nodeOk: z.boolean(),
platform: z.string(),
arch: z.string(),
resolvedPath: z.string(),
askLlmPath: z.string().optional(),
timeoutMs: z.number(),
codexTimeoutMs: z.number(),
claudeTimeoutMs: z.number(),
geminiTimeoutMs: z.number()
}),
providers: z.array(diagnosticProviderSchema),
checks: z.array(diagnosticCheckSchema)
});
const providerUsageBucketSchema = z.object({
calls: z.number(),
inputTokens: z.number(),
outputTokens: z.number(),
cachedTokens: z.number(),
thinkingTokens: z.number(),
durationMs: z.number(),
fellBack: z.number()
});
const sessionUsageSnapshotSchema = z.object({
totalCalls: z.number(),
totalInputTokens: z.number(),
totalOutputTokens: z.number(),
totalCachedTokens: z.number(),
totalThinkingTokens: z.number(),
totalDurationMs: z.number(),
fallbackCount: z.number(),
byProvider: z.record(z.string(), providerUsageBucketSchema),
byModel: z.record(z.string(), providerUsageBucketSchema)
});
function createUsageStatsTool(sessionUsage) {
return {
name: "get-usage-stats",
description: "Get the current MCP server's session usage stats: total LLM calls, token totals (input/output/thinking/cached), wall time, and breakdowns per provider and per model. No data leaves your machine — counts are tracked in-memory for the lifetime of the server process. Returns both human-readable markdown and a structured JSON snapshot via outputSchema.",
zodSchema: z.object({}),
outputSchema: sessionUsageSnapshotSchema,
annotations: {
title: "Get Usage Stats",
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
},
category: "utility",
execute: async () => {
const snapshot = sessionUsage.snapshot();
return {
text: formatSessionUsage(snapshot),
structuredContent: snapshot
};
}
};
}
function registerSessionUsageResource(server, sessionUsage) {
server.registerResource("session-usage", "usage://current-session", {
title: "Current Session Usage",
description: "Live JSON snapshot of token usage and call statistics for this MCP server session. Re-read at any time for the current totals.",
mimeType: "application/json"
}, async (uri) => ({ contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(sessionUsage.snapshot(), null, 2)
}] }));
}
function registerTools({ server, tools, executeTool, getPromptMessage, progressMessages, sessionUsage }) {
const seen = /* @__PURE__ */ new Set();
for (const tool of tools) {
if (seen.has(tool.name)) throw new Error(`Duplicate tool name "${tool.name}" — tool names must be unique within a server`);
seen.add(tool.name);
const shape = tool.zodSchema.shape;
const outputShape = tool.outputSchema ? tool.outputSchema.shape : void 0;
server.registerTool(tool.name, {
description: tool.description,
inputSchema: shape,
...outputShape ? { outputSchema: outputShape } : {},
annotations: tool.annotations
}, async (args, extra) => {
const toolName = tool.name;
const handle = createProgressTracker(toolName, extra, progressMessages(toolName));
try {
const toolArgs = args;
Logger.toolInvocation(toolName, args);
const result = await executeTool(toolName, toolArgs, (newOutput) => {
handle.updateOutput(newOutput);
}, sessionUsage ? (stats) => sessionUsage.record(stats) : void 0);
await handle.stop(true);
if (typeof result === "string") return {
content: [{
type: "text",
text: result
}],
isError: false
};
return {
content: [{
type: "text",
text: result.text
}],
structuredContent: result.structuredContent,
isError: false
};
} catch (error) {
await handle.stop(false);
Logger.error(`Error in tool '${toolName}':`, error);
return {
content: [{
type: "text",
text: `Error executing ${toolName}: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
});
}
for (const tool of tools) {
if (!tool.prompt) continue;
server.registerPrompt(tool.name, { description: tool.prompt.description }, async (args) => {
return { messages: [{
role: "user",
content: {
type: "text",
text: getPromptMessage(tool.name, args)
}
}] };
});
}
}
path.join(os.tmpdir(), "ask-llm-sessions");
//#endregion
//#region src/constants.ts
const ERROR_MESSAGES = {
QUOTA_SIGNALS: [
"rate_limit_exceeded",
"quota_exceeded",
"429",
"insufficient_quota",
"out of credits",
"spend cap",
"usage limit"
],
ARCHIVED_SESSION_SIGNALS: [
"archived_sessions",
"archived session",
"session is archived"
],
MODEL_UNAVAILABLE_SIGNALS: ["is not supported when using codex with a chatgpt"],
NO_PROMPT_PROVIDED: "Please provide a prompt for analysis. Ask general questions or describe the code you want reviewed.",
TOOL_NOT_FOUND: "not found in registry"
};
const STATUS_MESSAGES = {
QUOTA_SWITCHING: "Codex quota exceeded, switching to fallback model...",
FALLBACK_RETRY: "Retrying with fallback model...",
FALLBACK_SUCCESS: "Fallback model completed successfully",
CODEX_RESPONSE: "Codex response:"
};
const FACTORY_DEFAULT_MODEL = "gpt-5.6-sol";
const CODEX_REASONING_EFFORTS = [
"low",
"medium",
"high",
"xhigh",
"max"
];
const FACTORY_DEFAULT_REASONING_EFFORT = "medium";
function isCodexReasoningEffort(value) {
return value !== void 0 && CODEX_REASONING_EFFORTS.includes(value);
}
const configuredReasoningEffort = process.env.ASK_CODEX_REASONING_EFFORT;
const DEFAULT_REASONING_EFFORT = isCodexReasoningEffort(configuredReasoningEffort) ? configuredReasoningEffort : FACTORY_DEFAULT_REASONING_EFFORT;
const MODELS = {
DEFAULT: process.env.ASK_CODEX_MODEL || "gpt-5.6-sol",
PREFERRED: process.env.ASK_CODEX_PREFERRED_MODEL || "gpt-5.6-sol",
FALLBACK: process.env.ASK_CODEX_FALLBACK_MODEL || "gpt-5.6-terra"
};
const CLI = {
COMMANDS: {
CODEX: "codex",
EXEC: "exec",
RESUME: "resume"
},
FLAGS: {
MODEL: "-m",
CONFIG: "-c",
SKIP_GIT: "--skip-git-repo-check",
EPHEMERAL: "--ephemeral",
JSON: "--json",
SANDBOX: "--sandbox",
SANDBOX_WORKSPACE_WRITE: "workspace-write",
IGNORE_USER_CONFIG: "--ignore-user-config",
IGNORE_RULES: "--ignore-rules",
ADD_DIR: "--add-dir",
OUTPUT_SCHEMA: "--output-schema",
SANDBOX_READ_ONLY: "read-only"
}
};
const CODEX_EDIT_SCHEMA = {
type: "object",
additionalProperties: false,
properties: { edits: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
file: {
type: "string",
description: "Repo-relative path to an existing file"
},
startLine: {
type: ["integer", "null"],
description: "1-based line where oldCode begins (or null)"
},
oldCode: {
type: "string",
description: "Exact existing text to replace (must match the file verbatim)"
},
newCode: {
type: "string",
description: "Replacement text"
},
description: {
type: ["string", "null"],
description: "One-line rationale (or null)"
}
},
required: [
"file",
"startLine",
"oldCode",
"newCode",
"description"
]
}
} },
required: ["edits"]
};
//#endregion
//#region src/utils/codexDoctor.ts
const execFileAsync = promisify(execFile);
const DOCTOR_PROBE_TIMEOUT_MS = 5e3;
const DOCTOR_MAX_BUFFER = 1024 * 1024 * 4;
function mapCheckStatus(status) {
switch (String(status).toLowerCase()) {
case "ok":
case "pass": return "pass";
case "error":
case "fail":
case "failed": return "fail";
case "skip":
case "skipped": return "skip";
default: return "warn";
}
}
function mapOverallStatus(status) {
switch (String(status).toLowerCase()) {
case "ok":
case "pass": return "ok";
case "error":
case "fail": return "error";
default: return "warning";
}
}
/**
* Parse `codex doctor --json` stdout into a provider-agnostic enrichment.
* Returns `undefined` for anything that is not a recognizable doctor report
* (non-JSON, missing `overallStatus`/`checks`) so callers degrade silently.
*/
function parseCodexDoctorJson(stdout) {
let raw;
try {
raw = JSON.parse(stdout);
} catch {
return;
}
if (!raw || typeof raw !== "object") return void 0;
const report = raw;
if (typeof report.overallStatus !== "string") return void 0;
if (!report.checks || typeof report.checks !== "object") return void 0;
const checks = [];
for (const [key, value] of Object.entries(report.checks)) {
if (!value || typeof value !== "object") continue;
const check = value;
const remediation = typeof check.remediation === "string" && check.remediation.length > 0 ? check.remediation : void 0;
checks.push({
name: typeof check.id === "string" ? check.id : key,
status: mapCheckStatus(check.status),
summary: typeof check.summary === "string" ? check.summary : "",
...remediation ? { remediation } : {}
});
}
return {
heading: "codex doctor",
overall: mapOverallStatus(report.overallStatus),
checks
};
}
async function runCodexDoctorJson(command, pathEnv) {
const { stdout } = await execFileAsync(command, ["doctor", "--json"], {
env: {
...process.env,
PATH: pathEnv
},
timeout: DOCTOR_PROBE_TIMEOUT_MS,
maxBuffer: DOCTOR_MAX_BUFFER
});
return stdout;
}
/**
* Enrich the codex provider with a compact `codex doctor` health summary.
* Capability-probed: any failure (codex too old to know `--json`, timeout,
* unparseable output) yields `undefined`, leaving the doctor report unchanged.
* The `run` parameter is injectable for testing.
*/
async function enrichCodexDoctor(ctx, run = runCodexDoctorJson) {
try {
return parseCodexDoctorJson(await run(ctx.command, ctx.pathEnv));
} catch (err) {
const salvaged = err.stdout;
return typeof salvaged === "string" ? parseCodexDoctorJson(salvaged) : void 0;
}
}
//#endregion
//#region src/utils/codexExecutor.ts
function buildUsageStats(usage, model, durationMs, fellBack) {
return {
provider: "codex",
model,
inputTokens: usage?.input_tokens,
outputTokens: usage?.output_tokens,
cachedTokens: usage?.cached_input_tokens,
thinkingTokens: usage?.reasoning_output_tokens,
durationMs,
fellBack
};
}
function formatStats(usage) {
if (!usage) return "";
const parts = [];
if (usage.input_tokens != null) parts.push(`${usage.input_tokens.toLocaleString()} input tokens`);
if (usage.output_tokens != null) parts.push(`${usage.output_tokens.toLocaleString()} output tokens`);
if (usage.reasoning_output_tokens != null && usage.reasoning_output_tokens > 0) parts.push(`${usage.reasoning_output_tokens.toLocaleString()} thinking tokens`);
if (usage.cached_input_tokens != null && usage.cached_input_tokens > 0) parts.push(`${usage.cached_input_tokens.toLocaleString()} cached`);
return parts.length > 0 ? `\n\n[Codex stats: ${parts.join(", ")}]` : "";
}
function parseCodexJsonlOutput(raw, model, durationMs, fellBack) {
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
let lastAgentMessage;
let threadId;
let usage;
let lastError;
let sawJsonlEvent = false;
for (const line of lines) {
let parsed;
try {
parsed = JSON.parse(line);
} catch {
continue;
}
if (parsed && typeof parsed === "object" && typeof parsed.type === "string") sawJsonlEvent = true;
if (parsed.type === "thread.started") {
const thread = parsed;
if (thread.thread_id) threadId = thread.thread_id;
}
if (parsed.type === "item.completed") {
const item = parsed.item;
if (item?.type === "agent_message" && typeof item.text === "string" && item.text.length > 0) lastAgentMessage = item.text;
}
if (parsed.type === "turn.completed") usage = parsed.usage;
if (parsed.type === "turn.failed") lastError = parsed.error?.message ?? JSON.stringify(parsed);
if (parsed.type === "error") lastError = parsed.message ?? JSON.stringify(parsed);
}
if (lastError && !lastAgentMessage) throw new Error(`Codex error event: ${lastError}`);
if (!lastAgentMessage) {
if (sawJsonlEvent) {
const truncated = raw.length > EXECUTION.ERROR_TRUNCATE_LENGTH ? `${raw.slice(0, EXECUTION.ERROR_TRUNCATE_LENGTH)}…` : raw;
throw new Error(`Codex completed without an agent message${threadId ? ` (thread ${threadId})` : ""}. The run ended before producing a response — retry, or resume the thread via sessionId. Raw JSONL (truncated):\n${truncated}`);
}
Logger.debug("No parseable Codex JSONL events found, using raw text as the response");
return {
response: raw,
threadId,
usage: buildUsageStats(usage, model, durationMs, fellBack)
};
}
return {
response: lastAgentMessage + formatStats(usage),
threadId,
usage: buildUsageStats(usage, model, durationMs, fellBack)
};
}
function isQuotaError(error) {
const msg = (error instanceof Error ? error.message : String(error)).toLowerCase();
return ERROR_MESSAGES.QUOTA_SIGNALS.some((signal) => msg.includes(signal));
}
function isArchivedSessionError(error) {
const msg = (error instanceof Error ? error.message : String(error)).toLowerCase();
return ERROR_MESSAGES.ARCHIVED_SESSION_SIGNALS.some((signal) => msg.includes(signal));
}
function isModelUnavailableError(error) {
const msg = (error instanceof Error ? error.message : String(error)).toLowerCase();
return ERROR_MESSAGES.MODEL_UNAVAILABLE_SIGNALS.some((signal) => msg.includes(signal));
}
function extractFirstJsonObject(text) {
const start = text.indexOf("{");
if (start === -1) return null;
let depth = 0;
let inStr = false;
let esc = false;
for (let i = start; i < text.length; i++) {
const ch = text[i];
if (inStr) {
if (esc) esc = false;
else if (ch === "\\") esc = true;
else if (ch === "\"") inStr = false;
} else if (ch === "\"") inStr = true;
else if (ch === "{") depth++;
else if (ch === "}") {
depth--;
if (depth === 0) return text.slice(start, i + 1);
}
}
return null;
}
function parseCodexEdits(rawJson) {
let parsed;
try {
parsed = JSON.parse(extractFirstJsonObject(rawJson) ?? rawJson);
} catch {
return [];
}
if (!parsed || !Array.isArray(parsed.edits)) return [];
const edits = [];
for (const item of parsed.edits) {
if (!item || typeof item.file !== "string" || typeof item.oldCode !== "string" || typeof item.newCode !== "string") continue;
const oldCode = item.oldCode;
const newCode = item.newCode;
const startLine = typeof item.startLine === "number" && item.startLine > 0 ? item.startLine : 1;
const oldLineCount = oldCode === "" ? 0 : oldCode.split("\n").length;
const newLineCount = newCode === "" ? 0 : newCode.split("\n").length;
edits.push({
filename: item.file,
oldStartLine: startLine,
oldEndLine: startLine + (oldLineCount > 0 ? oldLineCount - 1 : 0),
oldCode,
newStartLine: startLine,
newEndLine: startLine + (newLineCount > 0 ? newLineCount - 1 : 0),
newCode
});
}
return edits;
}
function processCodexEditOutput(rawJson) {
const edits = parseCodexEdits(rawJson);
if (edits.length === 0) return "Codex proposed no edits for this request.";
const validation = validateChangeModeEdits(edits);
if (!validation.valid) return `Edit validation failed:\n${validation.errors.join("\n")}`;
let result = formatChangeModeResponse(edits);
if (edits.length > 5) result = `${summarizeChangeModeEdits(edits)}\n\n${result}`;
return result;
}
function buildArgs(prompt, model, sessionId, useStdin, includeDirs, sandboxMode = CLI.FLAGS.SANDBOX_READ_ONLY, schemaPath, reasoningEffort = DEFAULT_REASONING_EFFORT) {
const base = [CLI.COMMANDS.EXEC];
if (sessionId) base.push(CLI.COMMANDS.RESUME);
base.push(CLI.FLAGS.SKIP_GIT);
if (!sessionId) base.push(CLI.FLAGS.EPHEMERAL);
if (process.env.ASK_CODEX_LOAD_USER_CONFIG !== "1") base.push(CLI.FLAGS.IGNORE_USER_CONFIG, CLI.FLAGS.IGNORE_RULES);
base.push(CLI.FLAGS.SANDBOX, sandboxMode, CLI.FLAGS.CONFIG, `model_reasoning_effort="${reasoningEffort}"`, CLI.FLAGS.JSON, CLI.FLAGS.MODEL, model);
if (schemaPath) base.push(CLI.FLAGS.OUTPUT_SCHEMA, schemaPath);
if (includeDirs?.length) for (const dir of includeDirs) base.push(CLI.FLAGS.ADD_DIR, dir);
if (sessionId) base.push(sessionId);
if (!useStdin) base.push(prompt);
return base;
}
async function executeCodexCLI(options) {
const model = options.model || MODELS.DEFAULT;
const reasoningEffort = options.reasoningEffort || DEFAULT_REASONING_EFFORT;
const sessionId = options.sessionId;
const editMode = options.editMode === true;
const outputSchema = options.outputSchema ?? (editMode ? CODEX_EDIT_SCHEMA : void 0);
const sandboxMode = options.sandbox === "workspace-write" ? CLI.FLAGS.SANDBOX_WORKSPACE_WRITE : CLI.FLAGS.SANDBOX_READ_ONLY;
const wantsSession = sessionId !== void 0;
const preferredEligible = options.preferred === true && !options.model && !wantsSession && !editMode && MODELS.PREFERRED !== MODELS.DEFAULT;
const dirsPart = options.includeDirs?.length ? [...options.includeDirs].sort().join(":") : "";
const extraContext = `effort=${reasoningEffort};edit=${editMode ? 1 : 0};sandbox=${sandboxMode};dirs=${dirsPart}`;
const cacheKey = wantsSession || preferredEligible || outputSchema ? null : ResponseCache.buildKey("codex", options.prompt, model, extraContext);
if (cacheKey) {
const cached = responseCache.get(cacheKey);
if (cached) {
Logger.debug("Response cache hit for codex");
return {
response: cached,
threadId: void 0,
usage: void 0
};
}
}
let schemaPath;
try {
if (outputSchema) {
schemaPath = join(tmpdir(), `codex-output-schema-${process.pid}-${randomUUID()}.json`);
writeFileSync(schemaPath, JSON.stringify(outputSchema), { mode: 384 });
}
const useStdin = outputSchema !== void 0 || options.prompt.length > EXECUTION.STDIN_THRESHOLD_BYTES;
const stdinPayload = useStdin ? options.prompt : void 0;
const args = buildArgs(options.prompt, model, sessionId, useStdin, options.includeDirs, sandboxMode, schemaPath, reasoningEffort);
const timeoutMs = resolveTimeoutMs(EXECUTION.CODEX_TIMEOUT_ENV_VAR, EXECUTION.DEFAULT_CODEX_TIMEOUT_MS);
let downgradedFromPreferred = false;
if (preferredEligible) {
const preferredArgs = buildArgs(options.prompt, MODELS.PREFERRED, void 0, useStdin, options.includeDirs, sandboxMode, schemaPath, reasoningEffort);
const preferredStartedAt = Date.now();
try {
return parseCodexJsonlOutput(await executeCommand(CLI.COMMANDS.CODEX, preferredArgs, options.onProgress, void 0, stdinPayload, timeoutMs), MODELS.PREFERRED, Date.now() - preferredStartedAt, false);
} catch (preferredError) {
const reason = preferredError instanceof Error ? preferredError.message : String(preferredError);
Logger.warn(`Preferred Codex model ${MODELS.PREFERRED} unavailable (${reason}); falling back to ${MODELS.DEFAULT}.`);
downgradedFromPreferred = true;
}
}
const startedAt = Date.now();
try {
const result = parseCodexJsonlOutput(await executeCommand(CLI.COMMANDS.CODEX, args, options.onProgress, void 0, stdinPayload, timeoutMs), model, Date.now() - startedAt, downgradedFromPreferred);
if (cacheKey) responseCache.set(cacheKey, result.response);
return result;
} catch (error) {
if (sessionId && isArchivedSessionError(error)) throw new Error(`Codex session ${sessionId} is archived. Run \`codex unarchive ${sessionId}\` to resume it, or omit sessionId to start a new thread.`);
if (isQuotaError(error) && model !== MODELS.FALLBACK) {
Logger.warn(`${STATUS_MESSAGES.QUOTA_SWITCHING} Falling back to ${MODELS.FALLBACK}.`);
Logger.debug(`Status: ${STATUS_MESSAGES.FALLBACK_RETRY}`);
const fallbackArgs = buildArgs(options.prompt, MODELS.FALLBACK, sessionId, useStdin, options.includeDirs, sandboxMode, schemaPath, reasoningEffort);
const fallbackStartedAt = Date.now();
try {
const raw = await executeCommand(CLI.COMMANDS.CODEX, fallbackArgs, options.onProgress, void 0, stdinPayload, timeoutMs);
Logger.warn(`Successfully executed with ${MODELS.FALLBACK} fallback.`);
Logger.debug(`Status: ${STATUS_MESSAGES.FALLBACK_SUCCESS}`);
return parseCodexJsonlOutput(raw, MODELS.FALLBACK, Date.now() - fallbackStartedAt, true);
} catch (fallbackError) {
const fallbackMsg = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
if (isModelUnavailableError(fallbackError)) {
const remediation = process.env.ASK_CODEX_FALLBACK_MODEL ? "Set ASK_CODEX_FALLBACK_MODEL to a model your account supports, or unset it to use the default (gpt-5.6-terra)." : "Set ASK_CODEX_FALLBACK_MODEL to a model your account supports.";
throw new Error(`${MODELS.DEFAULT} quota exceeded and the fallback model "${MODELS.FALLBACK}" is not available for this Codex account type (${fallbackMsg}). ${remediation}`);
}
throw new Error(`${MODELS.DEFAULT} quota exceeded, ${MODELS.FALLBACK} fallback also failed: ${fallbackMsg}. Run \`codex doctor\` to inspect your Codex CLI installation.`);
}
}
throw error;
}
} finally {
if (schemaPath) try {
unlinkSync(schemaPath);
} catch {}
}
}
//#endregion
export { Logger as C, executeCommand as S, createSessionUsage as _, parseCodexJsonlOutput as a, toolRegistry as b, CODEX_REASONING_EFFORTS as c, FACTORY_DEFAULT_REASONING_EFFORT as d, MODELS as f, registerTools as g, registerSessionUsageResource as h, parseCodexEdits as i, ERROR_MESSAGES as l, createUsageStatsTool as m, isModelUnavailableError as n, processCodexEditOutput as o, STATUS_MESSAGES as p, isQuotaError as r, enrichCodexDoctor as s, executeCodexCLI as t, FACTORY_DEFAULT_MODEL as u, executeTool as v, askResponseSchema as w, relativeDirSchema as x, getPromptMessage as y };
//# sourceMappingURL=codexExecutor-DBI0E31B.js.map

Sorry, the diff of this file is too big to display

import { S as executeCommand, b as toolRegistry, c as CODEX_REASONING_EFFORTS, d as FACTORY_DEFAULT_REASONING_EFFORT, f as MODELS, l as ERROR_MESSAGES, o as processCodexEditOutput, p as STATUS_MESSAGES, t as executeCodexCLI, u as FACTORY_DEFAULT_MODEL, w as askResponseSchema, x as relativeDirSchema } from "./codexExecutor-DBI0E31B.js";
import { z } from "zod";
//#region src/tools/ask-codex.tool.ts
const askCodexArgsSchema = z.object({
prompt: z.string().min(1).max(1e5).describe("The question, code review request, or analysis task to send to Codex CLI"),
model: z.string().optional().describe(`DO NOT set this parameter. The tool automatically uses ${MODELS.DEFAULT} and falls back to ${MODELS.FALLBACK} on quota errors. Only set this if the user explicitly requests a specific model.`),
reasoningEffort: z.enum(CODEX_REASONING_EFFORTS).optional().describe(`Codex reasoning effort for this call. Defaults to ${FACTORY_DEFAULT_REASONING_EFFORT}; /codex-review and /brainstorm use high for quality-first work.`),
sessionId: z.string().optional().describe("Optional Codex thread ID to resume a prior conversation. Use the [Thread ID: ...] value from a previous response to continue the same chat with full prior context."),
includeDirs: z.array(relativeDirSchema).optional().describe("Additional directories Codex may access alongside the working directory (maps to codex `--add-dir`, repeatable). Must be relative paths (e.g., 'packages/api'). Useful in monorepos where relevant context spans sibling packages."),
preferred: z.boolean().optional().describe(`Opt into ASK_CODEX_PREFERRED_MODEL when it is configured to differ from the ${MODELS.DEFAULT} default. The built-in preferred value is also ${MODELS.PREFERRED}, so normal calls and review skills should leave this unset.`),
sandbox: z.enum(["read-only", "workspace-write"]).optional().default("read-only").describe("Codex sandbox mode for this call. Defaults to 'read-only', which enforces the core review contract (Codex reads and proposes, the MCP client edits). Set 'workspace-write' ONLY as an explicit opt-out for flows that need Codex to write files itself, e.g. image generation. Review, second-opinion, and analysis flows must never set this.")
});
const askCodexTool = {
name: "ask-codex",
description: `Send a prompt to OpenAI Codex CLI (defaults to ${FACTORY_DEFAULT_MODEL} with automatic fallback on quota errors). Use for code review, second opinions, analysis, and AI-to-AI collaboration. Do not override the model parameter unless the user explicitly asks. Returns both human-readable text and a structured response (provider, model, sessionId, usage) via outputSchema. The returned sessionId field maps to Codex's thread_id and can be passed back as sessionId to continue the conversation.`,
zodSchema: askCodexArgsSchema,
outputSchema: askResponseSchema,
annotations: {
title: "Ask Codex",
readOnlyHint: true,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true
},
prompt: { description: "Execute Codex CLI to get OpenAI Codex's response for code review and analysis." },
category: "codex",
execute: async (args, onProgress, onUsage) => {
const { prompt, model, reasoningEffort, sessionId, includeDirs, preferred, sandbox } = args;
if (!prompt?.trim()) throw new Error(ERROR_MESSAGES.NO_PROMPT_PROVIDED);
const result = await executeCodexCLI({
prompt,
model,
reasoningEffort,
sessionId,
includeDirs,
preferred,
sandbox,
onProgress
});
if (result.usage) onUsage?.(result.usage);
const threadLine = result.threadId ? `\n\n[Thread ID: ${result.threadId}]` : "";
return {
text: `${STATUS_MESSAGES.CODEX_RESPONSE}\n${result.response}${threadLine}`,
structuredContent: {
provider: "codex",
response: result.response,
model: result.usage?.model ?? model ?? MODELS.DEFAULT,
sessionId: result.threadId,
usage: result.usage
}
};
}
};
const askCodexEditTool = {
name: "ask-codex-edit",
description: "Send a code edit request to OpenAI Codex CLI and get structured search/replace edit blocks back (via codex --output-schema). Codex reads the existing files (read-only) and proposes precise, applyable changes for Claude to apply. Use this when you want Codex to suggest specific code modifications to existing files rather than just analysis. Mirrors ask-gemini-edit.",
zodSchema: z.object({
prompt: z.string().min(1).max(1e5).describe("Describe the code changes you want against existing files. Codex returns structured search/replace edit blocks (via --output-schema) that can be applied directly."),
model: z.string().optional().describe(`DO NOT set this parameter. The tool automatically uses ${MODELS.DEFAULT} and falls back to ${MODELS.FALLBACK} on quota errors.`),
sessionId: z.string().optional().describe("Optional Codex thread ID to resume a prior conversation. Use the [Thread ID: ...] value from a previous response to refine the same edit session."),
includeDirs: z.array(relativeDirSchema).optional().describe("Additional directories Codex may read alongside the working directory (maps to codex `--add-dir`). Must be relative paths (e.g., 'packages/api'). Useful in monorepos where relevant context spans sibling packages.")
}),
annotations: {
title: "Ask Codex (Edit Mode)",
readOnlyHint: true,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true
},
prompt: { description: "Execute Codex CLI with --output-schema to get structured edit suggestions for existing files." },
category: "codex",
execute: async (args, onProgress, onUsage) => {
const { prompt, model, sessionId, includeDirs } = args;
if (!prompt?.trim()) throw new Error(ERROR_MESSAGES.NO_PROMPT_PROVIDED);
const result = await executeCodexCLI({
prompt,
model,
sessionId,
includeDirs,
editMode: true,
onProgress
});
if (result.usage) onUsage?.(result.usage);
const threadLine = result.threadId ? `\n\n[Thread ID: ${result.threadId}]` : "";
return `${processCodexEditOutput(result.response)}${threadLine}`;
}
};
const pingTool = {
name: "ping",
description: "Test connectivity with the MCP server",
zodSchema: z.object({ message: z.string().optional().describe("A message to echo back to test the connection") }),
annotations: {
title: "Ping",
readOnlyHint: true,
idempotentHint: true,
openWorldHint: false
},
prompt: { description: "Echo test message to verify MCP server is working" },
category: "simple",
execute: async (args, onProgress) => {
return executeCommand("echo", [args.message || "Pong from Codex MCP Server!"], onProgress);
}
};
//#endregion
//#region src/tools/index.ts
toolRegistry.push(askCodexTool, askCodexEditTool, pingTool);
//#endregion
export {};
//# sourceMappingURL=tools-CVF7RWA7.js.map
{"version":3,"file":"tools-CVF7RWA7.js","names":[],"sources":["../src/tools/ask-codex.tool.ts","../src/tools/ask-codex-edit.tool.ts","../src/tools/simple-tools.ts","../src/tools/index.ts"],"sourcesContent":["import { type AskResponse, askResponseSchema, relativeDirSchema, type UnifiedTool } from \"@ask-llm/shared\";\nimport { z } from \"zod\";\nimport {\n CODEX_REASONING_EFFORTS,\n type CodexReasoningEffort,\n ERROR_MESSAGES,\n FACTORY_DEFAULT_MODEL,\n FACTORY_DEFAULT_REASONING_EFFORT,\n MODELS,\n STATUS_MESSAGES,\n} from \"../constants.js\";\nimport { executeCodexCLI } from \"../utils/codexExecutor.js\";\n\nconst askCodexArgsSchema = z.object({\n prompt: z\n .string()\n .min(1)\n .max(100000)\n .describe(\"The question, code review request, or analysis task to send to Codex CLI\"),\n model: z\n .string()\n .optional()\n .describe(\n `DO NOT set this parameter. The tool automatically uses ${MODELS.DEFAULT} and falls back to ${MODELS.FALLBACK} on quota errors. Only set this if the user explicitly requests a specific model.`,\n ),\n reasoningEffort: z\n .enum(CODEX_REASONING_EFFORTS)\n .optional()\n .describe(\n `Codex reasoning effort for this call. Defaults to ${FACTORY_DEFAULT_REASONING_EFFORT}; /codex-review and /brainstorm use high for quality-first work.`,\n ),\n sessionId: z\n .string()\n .optional()\n .describe(\n \"Optional Codex thread ID to resume a prior conversation. Use the [Thread ID: ...] value from a previous response to continue the same chat with full prior context.\",\n ),\n includeDirs: z\n .array(relativeDirSchema)\n .optional()\n .describe(\n \"Additional directories Codex may access alongside the working directory (maps to codex `--add-dir`, repeatable). Must be relative paths (e.g., 'packages/api'). Useful in monorepos where relevant context spans sibling packages.\",\n ),\n preferred: z\n .boolean()\n .optional()\n .describe(\n `Opt into ASK_CODEX_PREFERRED_MODEL when it is configured to differ from the ${MODELS.DEFAULT} default. The built-in preferred value is also ${MODELS.PREFERRED}, so normal calls and review skills should leave this unset.`,\n ),\n sandbox: z\n .enum([\"read-only\", \"workspace-write\"])\n .optional()\n .default(\"read-only\")\n .describe(\n \"Codex sandbox mode for this call. Defaults to 'read-only', which enforces the core review contract (Codex reads and proposes, the MCP client edits). Set 'workspace-write' ONLY as an explicit opt-out for flows that need Codex to write files itself, e.g. image generation. Review, second-opinion, and analysis flows must never set this.\",\n ),\n});\n\nexport const askCodexTool: UnifiedTool = {\n name: \"ask-codex\",\n description: `Send a prompt to OpenAI Codex CLI (defaults to ${FACTORY_DEFAULT_MODEL} with automatic fallback on quota errors). Use for code review, second opinions, analysis, and AI-to-AI collaboration. Do not override the model parameter unless the user explicitly asks. Returns both human-readable text and a structured response (provider, model, sessionId, usage) via outputSchema. The returned sessionId field maps to Codex's thread_id and can be passed back as sessionId to continue the conversation.`,\n zodSchema: askCodexArgsSchema,\n outputSchema: askResponseSchema,\n annotations: {\n title: \"Ask Codex\",\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n prompt: {\n description: \"Execute Codex CLI to get OpenAI Codex's response for code review and analysis.\",\n },\n category: \"codex\",\n execute: async (args, onProgress, onUsage) => {\n const { prompt, model, reasoningEffort, sessionId, includeDirs, preferred, sandbox } = args;\n if (!prompt?.trim()) {\n throw new Error(ERROR_MESSAGES.NO_PROMPT_PROVIDED);\n }\n\n const result = await executeCodexCLI({\n prompt: prompt as string,\n model: model as string | undefined,\n reasoningEffort: reasoningEffort as CodexReasoningEffort | undefined,\n sessionId: sessionId as string | undefined,\n includeDirs: includeDirs as string[] | undefined,\n preferred: preferred as boolean | undefined,\n sandbox: sandbox as \"read-only\" | \"workspace-write\" | undefined,\n onProgress,\n });\n\n if (result.usage) onUsage?.(result.usage);\n\n const threadLine = result.threadId ? `\\n\\n[Thread ID: ${result.threadId}]` : \"\";\n const text = `${STATUS_MESSAGES.CODEX_RESPONSE}\\n${result.response}${threadLine}`;\n const structured: AskResponse = {\n provider: \"codex\",\n response: result.response,\n model: result.usage?.model ?? (model as string | undefined) ?? MODELS.DEFAULT,\n sessionId: result.threadId,\n usage: result.usage,\n };\n return { text, structuredContent: structured as unknown as Record<string, unknown> };\n },\n};\n","import { relativeDirSchema, type UnifiedTool } from \"@ask-llm/shared\";\nimport { z } from \"zod\";\nimport { ERROR_MESSAGES, MODELS } from \"../constants.js\";\nimport { executeCodexCLI, processCodexEditOutput } from \"../utils/codexExecutor.js\";\n\nconst askCodexEditArgsSchema = z.object({\n prompt: z\n .string()\n .min(1)\n .max(100000)\n .describe(\n \"Describe the code changes you want against existing files. Codex returns structured search/replace edit blocks (via --output-schema) that can be applied directly.\",\n ),\n model: z\n .string()\n .optional()\n .describe(\n `DO NOT set this parameter. The tool automatically uses ${MODELS.DEFAULT} and falls back to ${MODELS.FALLBACK} on quota errors.`,\n ),\n sessionId: z\n .string()\n .optional()\n .describe(\n \"Optional Codex thread ID to resume a prior conversation. Use the [Thread ID: ...] value from a previous response to refine the same edit session.\",\n ),\n includeDirs: z\n .array(relativeDirSchema)\n .optional()\n .describe(\n \"Additional directories Codex may read alongside the working directory (maps to codex `--add-dir`). Must be relative paths (e.g., 'packages/api'). Useful in monorepos where relevant context spans sibling packages.\",\n ),\n});\n\nexport const askCodexEditTool: UnifiedTool = {\n name: \"ask-codex-edit\",\n description:\n \"Send a code edit request to OpenAI Codex CLI and get structured search/replace edit blocks back (via codex --output-schema). Codex reads the existing files (read-only) and proposes precise, applyable changes for Claude to apply. Use this when you want Codex to suggest specific code modifications to existing files rather than just analysis. Mirrors ask-gemini-edit.\",\n zodSchema: askCodexEditArgsSchema,\n annotations: {\n title: \"Ask Codex (Edit Mode)\",\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n prompt: {\n description: \"Execute Codex CLI with --output-schema to get structured edit suggestions for existing files.\",\n },\n category: \"codex\",\n execute: async (args, onProgress, onUsage) => {\n const { prompt, model, sessionId, includeDirs } = args;\n if (!prompt?.trim()) {\n throw new Error(ERROR_MESSAGES.NO_PROMPT_PROVIDED);\n }\n\n const result = await executeCodexCLI({\n prompt: prompt as string,\n model: model as string | undefined,\n sessionId: sessionId as string | undefined,\n includeDirs: includeDirs as string[] | undefined,\n editMode: true,\n onProgress,\n });\n\n if (result.usage) onUsage?.(result.usage);\n\n const threadLine = result.threadId ? `\\n\\n[Thread ID: ${result.threadId}]` : \"\";\n return `${processCodexEditOutput(result.response)}${threadLine}`;\n },\n};\n","import type { UnifiedTool } from \"@ask-llm/shared\";\nimport { executeCommand } from \"@ask-llm/shared\";\nimport { z } from \"zod\";\n\nconst pingArgsSchema = z.object({\n message: z.string().optional().describe(\"A message to echo back to test the connection\"),\n});\n\nexport const pingTool: UnifiedTool = {\n name: \"ping\",\n description: \"Test connectivity with the MCP server\",\n zodSchema: pingArgsSchema,\n annotations: {\n title: \"Ping\",\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n prompt: {\n description: \"Echo test message to verify MCP server is working\",\n },\n category: \"simple\",\n execute: async (args, onProgress) => {\n const message = args.message || \"Pong from Codex MCP Server!\";\n return executeCommand(\"echo\", [message as string], onProgress);\n },\n};\n","import { toolRegistry } from \"@ask-llm/shared\";\nimport { askCodexTool } from \"./ask-codex.tool.js\";\nimport { askCodexEditTool } from \"./ask-codex-edit.tool.js\";\nimport { pingTool } from \"./simple-tools.js\";\n\ntoolRegistry.push(askCodexTool, askCodexEditTool, pingTool);\n\nexport { executeTool, getPromptMessage, toolRegistry } from \"@ask-llm/shared\";\n"],"mappings":";;;AAaA,MAAM,qBAAqB,EAAE,OAAO;CAClC,QAAQ,EACL,OAAO,CAAC,CACR,IAAI,CAAC,CAAC,CACN,IAAI,GAAM,CAAC,CACX,SAAS,0EAA0E;CACtF,OAAO,EACJ,OAAO,CAAC,CACR,SAAS,CAAC,CACV,SACC,0DAA0D,OAAO,QAAQ,qBAAqB,OAAO,SAAS,kFAChH;CACF,iBAAiB,EACd,KAAK,uBAAuB,CAAC,CAC7B,SAAS,CAAC,CACV,SACC,qDAAqD,iCAAiC,iEACxF;CACF,WAAW,EACR,OAAO,CAAC,CACR,SAAS,CAAC,CACV,SACC,qKACF;CACF,aAAa,EACV,MAAM,iBAAiB,CAAC,CACxB,SAAS,CAAC,CACV,SACC,oOACF;CACF,WAAW,EACR,QAAQ,CAAC,CACT,SAAS,CAAC,CACV,SACC,+EAA+E,OAAO,QAAQ,iDAAiD,OAAO,UAAU,6DAClK;CACF,SAAS,EACN,KAAK,CAAC,aAAa,iBAAiB,CAAC,CAAC,CACtC,SAAS,CAAC,CACV,QAAQ,WAAW,CAAC,CACpB,SACC,gVACF;AACJ,CAAC;AAED,MAAa,eAA4B;CACvC,MAAM;CACN,aAAa,kDAAkD,sBAAsB;CACrF,WAAW;CACX,cAAc;CACd,aAAa;EACX,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;CACjB;CACA,QAAQ,EACN,aAAa,iFACf;CACA,UAAU;CACV,SAAS,OAAO,MAAM,YAAY,YAAY;EAC5C,MAAM,EAAE,QAAQ,OAAO,iBAAiB,WAAW,aAAa,WAAW,YAAY;EACvF,IAAI,CAAC,QAAQ,KAAK,GAChB,MAAM,IAAI,MAAM,eAAe,kBAAkB;EAGnD,MAAM,SAAS,MAAM,gBAAgB;GAC3B;GACD;GACU;GACN;GACE;GACF;GACF;GACT;EACF,CAAC;EAED,IAAI,OAAO,OAAO,UAAU,OAAO,KAAK;EAExC,MAAM,aAAa,OAAO,WAAW,mBAAmB,OAAO,SAAS,KAAK;EAS7E,OAAO;GAAE,MAAA,GARO,gBAAgB,eAAe,IAAI,OAAO,WAAW;GAQtD,mBAAmB;IANhC,UAAU;IACV,UAAU,OAAO;IACjB,OAAO,OAAO,OAAO,SAAU,SAAgC,OAAO;IACtE,WAAW,OAAO;IAClB,OAAO,OAAO;GAE2B;EAAwC;CACrF;AACF;ACvEA,MAAa,mBAAgC;CAC3C,MAAM;CACN,aACE;CACF,WAhC6B,EAAE,OAAO;EACtC,QAAQ,EACL,OAAO,CAAC,CACR,IAAI,CAAC,CAAC,CACN,IAAI,GAAM,CAAC,CACX,SACC,oKACF;EACF,OAAO,EACJ,OAAO,CAAC,CACR,SAAS,CAAC,CACV,SACC,0DAA0D,OAAO,QAAQ,qBAAqB,OAAO,SAAS,kBAChH;EACF,WAAW,EACR,OAAO,CAAC,CACR,SAAS,CAAC,CACV,SACC,mJACF;EACF,aAAa,EACV,MAAM,iBAAiB,CAAC,CACxB,SAAS,CAAC,CACV,SACC,sNACF;CACJ,CAMa;CACX,aAAa;EACX,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;CACjB;CACA,QAAQ,EACN,aAAa,gGACf;CACA,UAAU;CACV,SAAS,OAAO,MAAM,YAAY,YAAY;EAC5C,MAAM,EAAE,QAAQ,OAAO,WAAW,gBAAgB;EAClD,IAAI,CAAC,QAAQ,KAAK,GAChB,MAAM,IAAI,MAAM,eAAe,kBAAkB;EAGnD,MAAM,SAAS,MAAM,gBAAgB;GAC3B;GACD;GACI;GACE;GACb,UAAU;GACV;EACF,CAAC;EAED,IAAI,OAAO,OAAO,UAAU,OAAO,KAAK;EAExC,MAAM,aAAa,OAAO,WAAW,mBAAmB,OAAO,SAAS,KAAK;EAC7E,OAAO,GAAG,uBAAuB,OAAO,QAAQ,IAAI;CACtD;AACF;AC7DA,MAAa,WAAwB;CACnC,MAAM;CACN,aAAa;CACb,WAPqB,EAAE,OAAO,EAC9B,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,+CAA+C,EACzF,CAKa;CACX,aAAa;EACX,OAAO;EACP,cAAc;EACd,gBAAgB;EAChB,eAAe;CACjB;CACA,QAAQ,EACN,aAAa,oDACf;CACA,UAAU;CACV,SAAS,OAAO,MAAM,eAAe;EAEnC,OAAO,eAAe,QAAQ,CADd,KAAK,WAAW,6BACgB,GAAG,UAAU;CAC/D;AACF;;;ACrBA,aAAa,KAAK,cAAc,kBAAkB,QAAQ"}
+1
-1
#!/usr/bin/env node
import { C as Logger } from "./codexExecutor-CQOWlbPt.js";
import { C as Logger } from "./codexExecutor-DBI0E31B.js";
import { startServer } from "./index.js";

@@ -4,0 +4,0 @@ //#region src/cli.ts

@@ -1,2 +0,2 @@

import { a as parseCodexJsonlOutput, i as parseCodexEdits, n as isModelUnavailableError, o as processCodexEditOutput, r as isQuotaError, s as enrichCodexDoctor, t as executeCodexCLI } from "./codexExecutor-CQOWlbPt.js";
import { a as parseCodexJsonlOutput, i as parseCodexEdits, n as isModelUnavailableError, o as processCodexEditOutput, r as isQuotaError, s as enrichCodexDoctor, t as executeCodexCLI } from "./codexExecutor-DBI0E31B.js";
export { enrichCodexDoctor, executeCodexCLI, isModelUnavailableError, isQuotaError, parseCodexEdits, parseCodexJsonlOutput, processCodexEditOutput };

@@ -1,1 +0,1 @@

{"version":3,"file":"index-BRK854Ug.d.ts","names":["ROLES","USER","ASSISTANT","CONTENT_TYPES","TEXT","STATUS","SUCCESS","ERROR","FAILED","REPORT","NOTIFICATIONS","PROGRESS","KEEPALIVE_INTERVAL","DEFAULT_TIMEOUT_MS","DEFAULT_CODEX_TIMEOUT_MS","DEFAULT_CLAUDE_TIMEOUT_MS","DEFAULT_OLLAMA_TIMEOUT_MS","TIMEOUT_ENV_VAR","CODEX_TIMEOUT_ENV_VAR","CLAUDE_TIMEOUT_ENV_VAR","GEMINI_TIMEOUT_ENV_VAR","OLLAMA_TIMEOUT_ENV_VAR","ERROR_TRUNCATE_LENGTH","STDIN_THRESHOLD_BYTES","prompt","message","key","name","status","CheckStatus","message","fix","summary","remediation","heading","overall","OverallStatus","checks","ProviderEnrichmentCheck","command","available","cliPath","cliVersion","error","enrichment","ProviderEnrichment","generatedAt","environment","nodeVersion","nodeOk","platform","arch","resolvedPath","askLlmPath","timeoutMs","codexTimeoutMs","claudeTimeoutMs","geminiTimeoutMs","providers","ProviderProbe","DiagnosticCheck","key","versionArgs","installHint","probeAvailability","Promise","enrich","pathEnv","ctx","ProviderSpec","DiagnosticReport","report","PROVIDERS","provider","ProviderName","model","inputTokens","outputTokens","cachedTokens","thinkingTokens","durationMs","fellBack","totalCalls","totalInputTokens","totalOutputTokens","totalCachedTokens","totalThinkingTokens","totalDurationMs","fallbackCount","byProvider","Record","ProviderUsageSnapshot","byModel","calls","record","UsageStats","stats","snapshot","SessionUsageSnapshot","reset","SessionUsage","text","structuredContent","Record","StructuredToolResult","name","description","zodSchema","ZodTypeAny","outputSchema","annotations","ToolAnnotations","prompt","arguments","Array","required","execute","BaseToolArguments","args","newOutput","onProgress","UsageStats","stats","onUsage","Promise","ToolResult","category","ProviderName","UnifiedTool","toolName"],"sources":["../../shared/dist/constants.d.ts","../../shared/dist/doctor.d.ts","../../shared/dist/providers.d.ts","../../shared/dist/usage.d.ts","../../shared/dist/registry.d.ts"],"mappings":";;;;;UAkCiB,iBAAA;EACbwB,MAAAA;EACAC,OAAAA;EAAAA,CACCC,GAAAA;AAAAA;;;KCrCO,WAAA;AAAA,KACA,aAAA;AAAA,UAOK,uBAAA;EACbC,IAAAA;EACAC,MAAAA,EAAQ,WAAW;EACnBI,OAAAA;EACAC,WAAAA;AAAAA;AAAAA,UAEa,kBAAA;EACbC,OAAAA;EACAC,OAAAA,EAAS,aAAA;EACTE,MAAAA,EAAQ,uBAAuB;AAAA;;;;;;;;;ADiBnC;cE3BqB,SAAA;AAAA,KACT,YAAA,WAAuB,SAAS;;;UCP3B,UAAA;EACboC,QAAAA,EAAU,YAAY;EACtBE,KAAAA;EACAC,WAAAA;EACAC,YAAAA;EACAC,YAAAA;EACAC,cAAAA;EACAC,UAAAA;EACAC,QAAAA;AAAAA;;;UCJa,oBAAA;EACboB,IAAAA;EACAC,iBAAAA,EAAmB,MAAM;AAAA;AAAA,KAEjB,UAAA,YAAsB,oBAAoB;AAAA,UACrC,WAAA;EACbG,IAAAA;EACAC,WAAAA;EACAC,SAAAA,EAAW,UAAA;EACXE,YAAAA,GAAe,UAAA;EACfC,WAAAA,GAAc,eAAA;EACdE,MAAAA;IACIN,WAAAA;IACAO,SAAAA,GAAY,KAAA;MACRR,IAAAA;MACAC,WAAAA;MACAS,QAAAA;IAAAA;EAAAA;EAGRC,OAAAA,GAAUE,IAAAA,EAAM,iBAAA,EAAmBE,UAAAA,IAAcD,SAAAA,mBAA4BI,OAAAA,IAAWD,KAAAA,EAAO,UAAA,cAAwB,OAAA,CAAQ,UAAA;EAC/HI,QAAAA,cAAsB,YAAA;AAAA;AAAA,cAEL,YAAA,EAAc,WAAW;AAAA,iBACtB,aAAA,CAAYG,QAAAA,UAAkBX,IAAAA,EAAM,iBAAA,EAAmBE,UAAAA,IAAcD,SAAAA,mBAA4BI,OAAAA,IAAWD,KAAAA,EAAO,UAAA,YAAsB,OAAA,CAAQ,UAAA;AAAA,iBACjJ,kBAAA,CAAiBO,QAAAA,UAAkBX,IAAAA,EAAM,MAAM"}
{"version":3,"file":"index-BRK854Ug.d.ts","names":["ROLES","USER","ASSISTANT","CONTENT_TYPES","TEXT","STATUS","SUCCESS","ERROR","FAILED","REPORT","NOTIFICATIONS","PROGRESS","KEEPALIVE_INTERVAL","DEFAULT_TIMEOUT_MS","DEFAULT_CODEX_TIMEOUT_MS","DEFAULT_CLAUDE_TIMEOUT_MS","DEFAULT_OLLAMA_TIMEOUT_MS","TIMEOUT_ENV_VAR","CODEX_TIMEOUT_ENV_VAR","CLAUDE_TIMEOUT_ENV_VAR","GEMINI_TIMEOUT_ENV_VAR","OLLAMA_TIMEOUT_ENV_VAR","ERROR_TRUNCATE_LENGTH","STDIN_THRESHOLD_BYTES","prompt","message","key","name","status","CheckStatus","message","fix","summary","remediation","heading","overall","OverallStatus","checks","ProviderEnrichmentCheck","available","error","command","cliPath","cliVersion","enrichment","ProviderEnrichment","generatedAt","environment","nodeVersion","nodeOk","platform","arch","resolvedPath","askLlmPath","timeoutMs","codexTimeoutMs","claudeTimeoutMs","geminiTimeoutMs","providers","ProviderProbe","DiagnosticCheck","key","versionArgs","installHint","probeAvailability","Promise","assessVersion","version","probeError","ProviderVersionAssessment","enrich","pathEnv","ctx","ProviderSpec","DiagnosticReport","report","PROVIDERS","provider","ProviderName","model","inputTokens","outputTokens","cachedTokens","thinkingTokens","durationMs","fellBack","totalCalls","totalInputTokens","totalOutputTokens","totalCachedTokens","totalThinkingTokens","totalDurationMs","fallbackCount","byProvider","Record","ProviderUsageSnapshot","byModel","calls","record","UsageStats","stats","snapshot","SessionUsageSnapshot","reset","SessionUsage","text","structuredContent","Record","StructuredToolResult","name","description","zodSchema","ZodTypeAny","outputSchema","annotations","ToolAnnotations","prompt","arguments","Array","required","execute","BaseToolArguments","args","newOutput","onProgress","UsageStats","stats","onUsage","Promise","ToolResult","category","ProviderName","UnifiedTool","toolName"],"sources":["../../shared/dist/constants.d.ts","../../shared/dist/doctor.d.ts","../../shared/dist/providers.d.ts","../../shared/dist/usage.d.ts","../../shared/dist/registry.d.ts"],"mappings":";;;;;UAkCiB,iBAAA;EACbwB,MAAAA;EACAC,OAAAA;EAAAA,CACCC,GAAAA;AAAAA;;;KCrCO,WAAA;AAAA,KACA,aAAA;AAAA,UAOK,uBAAA;EACbC,IAAAA;EACAC,MAAAA,EAAQ,WAAW;EACnBI,OAAAA;EACAC,WAAAA;AAAAA;AAAAA,UAEa,kBAAA;EACbC,OAAAA;EACAC,OAAAA,EAAS,aAAA;EACTE,MAAAA,EAAQ,uBAAuB;AAAA;;;;;;;;;ADiBnC;cE3BqB,SAAA;AAAA,KACT,YAAA,WAAuB,SAAS;;;UCP3B,UAAA;EACbwC,QAAAA,EAAU,YAAY;EACtBE,KAAAA;EACAC,WAAAA;EACAC,YAAAA;EACAC,YAAAA;EACAC,cAAAA;EACAC,UAAAA;EACAC,QAAAA;AAAAA;;;UCJa,oBAAA;EACboB,IAAAA;EACAC,iBAAAA,EAAmB,MAAM;AAAA;AAAA,KAEjB,UAAA,YAAsB,oBAAoB;AAAA,UACrC,WAAA;EACbG,IAAAA;EACAC,WAAAA;EACAC,SAAAA,EAAW,UAAA;EACXE,YAAAA,GAAe,UAAA;EACfC,WAAAA,GAAc,eAAA;EACdE,MAAAA;IACIN,WAAAA;IACAO,SAAAA,GAAY,KAAA;MACRR,IAAAA;MACAC,WAAAA;MACAS,QAAAA;IAAAA;EAAAA;EAGRC,OAAAA,GAAUE,IAAAA,EAAM,iBAAA,EAAmBE,UAAAA,IAAcD,SAAAA,mBAA4BI,OAAAA,IAAWD,KAAAA,EAAO,UAAA,cAAwB,OAAA,CAAQ,UAAA;EAC/HI,QAAAA,cAAsB,YAAA;AAAA;AAAA,cAEL,YAAA,EAAc,WAAW;AAAA,iBACtB,aAAA,CAAYG,QAAAA,UAAkBX,IAAAA,EAAM,iBAAA,EAAmBE,UAAAA,IAAcD,SAAAA,mBAA4BI,OAAAA,IAAWD,KAAAA,EAAO,UAAA,YAAsB,OAAA,CAAQ,UAAA;AAAA,iBACjJ,kBAAA,CAAiBO,QAAAA,UAAkBX,IAAAA,EAAM,MAAM"}

@@ -1,3 +0,3 @@

import { C as Logger, _ as createSessionUsage, b as toolRegistry, g as registerTools, h as registerSessionUsageResource, m as createUsageStatsTool, v as executeTool, y as getPromptMessage } from "./codexExecutor-CQOWlbPt.js";
import "./tools-CfSNYpIz.js";
import { C as Logger, _ as createSessionUsage, b as toolRegistry, g as registerTools, h as registerSessionUsageResource, m as createUsageStatsTool, v as executeTool, y as getPromptMessage } from "./codexExecutor-DBI0E31B.js";
import "./tools-CVF7RWA7.js";
import { createRequire } from "node:module";

@@ -4,0 +4,0 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

@@ -1,3 +0,3 @@

import { b as toolRegistry, v as executeTool, y as getPromptMessage } from "./codexExecutor-CQOWlbPt.js";
import "./tools-CfSNYpIz.js";
import { b as toolRegistry, v as executeTool, y as getPromptMessage } from "./codexExecutor-DBI0E31B.js";
import "./tools-CVF7RWA7.js";
export { executeTool, getPromptMessage, toolRegistry };
{
"name": "@ask-llm/codex-mcp",
"version": "0.7.1",
"version": "0.7.2",
"mcpName": "io.github.Lykhoyda/ask-codex",

@@ -5,0 +5,0 @@ "description": "MCP server for OpenAI Codex CLI integration - for Claude IDE and other IDEs",

import { ZodError, z } from "zod";
import { createHash, randomUUID } from "node:crypto";
import { existsSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
import * as os from "node:os";
import { homedir, tmpdir } from "node:os";
import * as path from "node:path";
import { delimiter, isAbsolute, join } from "node:path";
import { execFile, execFileSync, spawn } from "node:child_process";
import { promisify } from "node:util";
//#region ../shared/dist/providers.js
/**
* Single source of truth for the provider list (ADR-128).
*
* Every provider-name enum, type union, or user-facing provider list in the
* monorepo must derive from this tuple — hand-maintained copies drifted when
* antigravity was added (see BUGS.md 2026-07-02 audit entry).
*/
const PROVIDERS = [
"gemini",
"codex",
"claude",
"ollama",
"antigravity"
];
//#endregion
//#region ../shared/dist/askResponse.js
const usageStatsSchema = z.object({
provider: z.enum(PROVIDERS),
model: z.string(),
inputTokens: z.number().optional(),
outputTokens: z.number().optional(),
cachedTokens: z.number().optional(),
thinkingTokens: z.number().optional(),
durationMs: z.number(),
fellBack: z.boolean()
});
const askResponseSchema = z.object({
provider: z.enum(PROVIDERS),
response: z.string(),
model: z.string(),
sessionId: z.string().optional(),
usage: usageStatsSchema.optional()
});
//#endregion
//#region ../shared/dist/constants.js
const LOG_PREFIX = "[GMCPT]";
const LOG_LEVEL_ENV_VAR = "GMCPT_LOG_LEVEL";
const PROTOCOL = {
ROLES: {
USER: "user",
ASSISTANT: "assistant"
},
CONTENT_TYPES: { TEXT: "text" },
STATUS: {
SUCCESS: "success",
ERROR: "error",
FAILED: "failed",
REPORT: "report"
},
NOTIFICATIONS: { PROGRESS: "notifications/progress" },
KEEPALIVE_INTERVAL: 25e3
};
const EXECUTION = {
DEFAULT_TIMEOUT_MS: 21e4,
DEFAULT_CODEX_TIMEOUT_MS: 8e5,
DEFAULT_CLAUDE_TIMEOUT_MS: 6e5,
DEFAULT_OLLAMA_TIMEOUT_MS: 6e5,
TIMEOUT_ENV_VAR: "GMCPT_TIMEOUT_MS",
CODEX_TIMEOUT_ENV_VAR: "ASK_CODEX_TIMEOUT_MS",
CLAUDE_TIMEOUT_ENV_VAR: "ASK_CLAUDE_TIMEOUT_MS",
GEMINI_TIMEOUT_ENV_VAR: "ASK_GEMINI_TIMEOUT_MS",
OLLAMA_TIMEOUT_ENV_VAR: "ASK_OLLAMA_TIMEOUT_MS",
ERROR_TRUNCATE_LENGTH: 2e3,
STDIN_THRESHOLD_BYTES: 16384
};
//#endregion
//#region ../shared/dist/logger.js
const LOG_LEVEL_PRIORITY = {
debug: 0,
info: 1,
warn: 2,
error: 3
};
function getLogLevel() {
const env = process.env[LOG_LEVEL_ENV_VAR]?.toLowerCase();
if (env && env in LOG_LEVEL_PRIORITY) return env;
return "warn";
}
function shouldLog(level) {
return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[getLogLevel()];
}
var Logger = class Logger {
static _nextCommandId = 0;
static _commandStartTimes = /* @__PURE__ */ new Map();
static formatMessage(message) {
return `${LOG_PREFIX} ${message}`;
}
static warn(message, ...args) {
if (!shouldLog("warn")) return;
console.warn(Logger.formatMessage(message), ...args);
}
static error(message, ...args) {
if (!shouldLog("error")) return;
console.error(Logger.formatMessage(message), ...args);
}
static debug(message, ...args) {
if (!shouldLog("debug")) return;
console.warn(Logger.formatMessage(message), ...args);
}
static info(message, ...args) {
if (!shouldLog("info")) return;
console.warn(Logger.formatMessage(message), ...args);
}
static toolInvocation(toolName, args) {
Logger.warn(`Tool "${toolName}" raw args:`, JSON.stringify(args, null, 2));
}
static toolParsedArgs(prompt, model, sandbox, changeMode) {
Logger.warn(`Parsed prompt: "${prompt}"\nmodel: ${model ?? "default"}, sandbox: ${sandbox ?? false}, changeMode: ${changeMode ?? false}`);
}
static commandExecution(command, args) {
const commandId = Logger._nextCommandId++;
Logger._commandStartTimes.set(commandId, Date.now());
Logger.warn(`[cmd:${commandId}] Starting: ${command} ${args.map((arg) => `"${arg}"`).join(" ")}`);
return commandId;
}
static commandComplete(commandId, exitCode, outputLength) {
const startTime = Logger._commandStartTimes.get(commandId);
const elapsed = startTime ? ((Date.now() - startTime) / 1e3).toFixed(1) : "?";
Logger.warn(`[cmd:${commandId}] [${elapsed}s] Process finished with exit code: ${exitCode}`);
if (outputLength !== void 0) Logger.warn(`[cmd:${commandId}] Response: ${outputLength} chars`);
Logger._commandStartTimes.delete(commandId);
}
static checkNodeVersion(minMajor = 20) {
if (parseInt(process.versions.node.split(".")[0], 10) < minMajor) Logger.error(`Node.js v${process.versions.node} detected — v${minMajor}+ required. Some providers (e.g., gemini-cli) use ES2024 features that will crash on older runtimes.`);
}
};
//#endregion
//#region ../shared/dist/changeMode/changeModeParser.js
function validateChangeModeEdits(edits) {
const errors = [];
for (const edit of edits) {
if (!edit.filename) errors.push("Edit missing filename");
if (edit.oldStartLine > edit.oldEndLine) errors.push(`Invalid line range for ${edit.filename}: ${edit.oldStartLine} > ${edit.oldEndLine}`);
if (edit.newStartLine > edit.newEndLine) errors.push(`Invalid new line range for ${edit.filename}: ${edit.newStartLine} > ${edit.newEndLine}`);
if (!edit.oldCode && !edit.newCode) errors.push(`Empty edit for ${edit.filename}`);
}
return {
valid: errors.length === 0,
errors
};
}
//#endregion
//#region ../shared/dist/changeMode/changeModeTranslator.js
function formatChangeModeResponse(edits, chunkInfo) {
const header = chunkInfo && chunkInfo.total > 1 ? `[CHANGEMODE OUTPUT - Chunk ${chunkInfo.current} of ${chunkInfo.total}]
Gemini has analyzed your codebase and generated edits across ${chunkInfo.total} chunks.
This chunk contains ${edits.length} complete edit${edits.length === 1 ? "" : "s"} that can be applied independently.
Each chunk contains self-contained edits grouped by file. You can safely apply these edits
before fetching the next chunk.
` : `[CHANGEMODE OUTPUT - Gemini has analyzed the files and provided these edits]
I have prepared ${edits.length} modification${edits.length === 1 ? "" : "s"} for your codebase.
IMPORTANT: Apply these edits directly WITHOUT reading the files first. The edits below contain exact text matches from the current file contents.
`;
const instructions = edits.map((edit, index) => {
return `### Edit ${index + 1}: ${edit.filename}
Replace this exact text:
\`\`\`
${edit.oldCode}
\`\`\`
With this text:
\`\`\`
${edit.newCode}
\`\`\`
`;
}).join("\n");
let footer = `
---
Apply these edits in order. Each edit uses exact string matching, so the old_str must match exactly what appears between the code blocks.`;
if (chunkInfo && chunkInfo.current < chunkInfo.total && chunkInfo.cacheKey) footer += `
---
**Next Step**: After applying the edits above, retrieve the next chunk (${chunkInfo.current + 1} of ${chunkInfo.total}) by calling the **fetch-chunk** MCP tool with:
- **cacheKey**: \`${chunkInfo.cacheKey}\`
- **chunkIndex**: \`${chunkInfo.current + 1}\`
There ${chunkInfo.total - chunkInfo.current === 1 ? "is" : "are"} ${chunkInfo.total - chunkInfo.current} more chunk${chunkInfo.total - chunkInfo.current === 1 ? "" : "s"} containing additional edits.
**CONTINUE**: You are working on a multi-chunk changeMode response. After applying these edits, fetch the next chunk to continue with the remaining modifications.`;
return header + instructions + footer;
}
function summarizeChangeModeEdits(edits, isPartialView) {
const fileGroups = /* @__PURE__ */ new Map();
for (const edit of edits) fileGroups.set(edit.filename, (fileGroups.get(edit.filename) || 0) + 1);
const summary = Array.from(fileGroups.entries()).map(([file, count]) => `- ${file}: ${count} edit${count === 1 ? "" : "s"}`).join("\n");
return `${isPartialView ? `ChangeMode Summary (Complete analysis across all chunks):` : `ChangeMode Summary:`}
Total edits: ${edits.length}${isPartialView ? " (across all chunks)" : ""}
Files affected: ${fileGroups.size}
${summary}`;
}
path.join(os.tmpdir(), "gemini-mcp-chunks");
//#endregion
//#region ../shared/dist/shellPath.js
const IS_WINDOWS$1 = process.platform === "win32";
const SHELL_PATH_ENV_VAR = "ASK_LLM_PATH";
let cachedPath = null;
function extractShellPath() {
if (IS_WINDOWS$1) return null;
try {
const match = execFileSync(process.env.SHELL || "/bin/zsh", ["-ilc", "echo \"___PATH___$PATH___END___\""], {
encoding: "utf8",
stdio: [
"ignore",
"pipe",
"ignore"
],
timeout: 5e3
}).match(/___PATH___(.*)___END___/);
if (match?.[1]) return match[1].trim();
} catch {
Logger.debug("Failed to extract PATH from login shell");
}
return null;
}
function findNvmNodePath() {
const nvmDir = join(homedir(), ".nvm", "versions", "node");
if (!existsSync(nvmDir)) return null;
try {
const versions = readdirSync(nvmDir).filter((v) => {
return parseInt(v.replace("v", "").split(".")[0], 10) >= 20;
}).sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
if (versions.length > 0) {
const binDir = join(nvmDir, versions[0], "bin");
if (existsSync(binDir)) return binDir;
}
} catch {
Logger.debug("Failed to scan nvm versions");
}
return null;
}
function buildAugmentedPath() {
const currentPath = process.env.PATH || "";
const home = homedir();
const candidates = [];
const nvmBin = findNvmNodePath();
if (nvmBin) candidates.push(nvmBin);
for (const dir of [
join(home, ".volta", "bin"),
join(home, ".local", "share", "fnm"),
"/opt/homebrew/bin",
"/usr/local/bin"
]) if (existsSync(dir)) candidates.push(dir);
if (candidates.length === 0) return currentPath;
return [...candidates, ...currentPath.split(delimiter)].join(delimiter);
}
function resolveShellPath() {
if (cachedPath !== null) return cachedPath;
const envOverride = process.env[SHELL_PATH_ENV_VAR];
if (envOverride) {
Logger.debug(`Using ${SHELL_PATH_ENV_VAR} override`);
cachedPath = envOverride;
return cachedPath;
}
if (IS_WINDOWS$1) {
cachedPath = process.env.PATH || "";
return cachedPath;
}
const shellPath = extractShellPath();
if (shellPath) {
Logger.debug("Using PATH from login shell");
cachedPath = shellPath;
return cachedPath;
}
Logger.debug("Login shell PATH extraction failed, using heuristic fallback");
cachedPath = buildAugmentedPath();
return cachedPath;
}
function getSpawnEnv() {
return {
...process.env,
PATH: resolveShellPath()
};
}
//#endregion
//#region ../shared/dist/commandExecutor.js
const IS_WINDOWS = process.platform === "win32";
const REDACTED_COMMAND_ARGUMENT = "<redacted>";
const QUOTA_PASSTHROUGH_PATTERNS = [
"RESOURCE_EXHAUSTED",
"TerminalQuotaError",
"exhausted your capacity",
"rate_limit_exceeded",
"quota_exceeded",
"insufficient_quota",
"usage limit"
];
function sanitizeErrorForLLM(stderr, command) {
if (stderr.includes("Invalid regular expression flags") && stderr.includes("Node.js v")) return `${command} CLI requires Node.js v20+ but is running on ${stderr.match(/Node\.js (v[\d.]+)/)?.[1] ?? "unknown"}. The user should update their Node version or set ASK_LLM_PATH in their MCP config to point to a Node v20+ installation.`;
if (stderr.includes("command not found") || stderr.includes(`spawn ${command} ENOENT`)) return `${command} CLI not found on PATH. Ensure it is installed and accessible. Run "which ${command}" in a terminal to verify.`;
if (stderr.includes("EACCES") || stderr.includes("Permission denied")) return `Permission denied when running ${command} CLI. Check file permissions and try running with appropriate access.`;
const lower = stderr.toLowerCase();
const matchedQuotaPattern = QUOTA_PASSTHROUGH_PATTERNS.find((p) => lower.includes(p.toLowerCase()));
if (matchedQuotaPattern) {
if (stderr.length <= 500) return stderr;
const idx = lower.indexOf(matchedQuotaPattern.toLowerCase());
const start = Math.max(0, idx - 100);
const end = Math.min(start + 500, stderr.length);
const head = start > 0 ? "... (truncated) " : "";
const tail = end < stderr.length ? "... (truncated)" : "";
return `${head}${stderr.slice(start, end)}${tail}`;
}
const preview = stderr.split("\n").filter((l) => l.trim().length > 0).slice(0, 3).join("\n");
if (preview.length > 0 && preview.length < 500) return preview;
return stderr.length > 500 ? `${stderr.slice(0, 500)}... (truncated)` : stderr;
}
function parseTimeoutEnv(envVal) {
if (!envVal) return void 0;
const parsed = Number(envVal);
return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
}
function getTimeoutMs() {
return parseTimeoutEnv(process.env[EXECUTION.TIMEOUT_ENV_VAR]) ?? EXECUTION.DEFAULT_TIMEOUT_MS;
}
function resolveTimeoutMs(providerEnvVar, fallbackDefault) {
const providerVal = parseTimeoutEnv(process.env[providerEnvVar]);
if (providerVal !== void 0) return providerVal;
const globalVal = parseTimeoutEnv(process.env[EXECUTION.TIMEOUT_ENV_VAR]);
if (globalVal !== void 0) return globalVal;
return fallbackDefault;
}
function quoteArgsForWindows(args) {
return args.map((a) => {
if (a.includes(" ") || a.includes("\"") || a.includes("&") || a.includes("|") || a.includes("^")) return `"${a.replace(/"/g, "\\\"")}"`;
return a;
});
}
function argsForLogging(args, options) {
if (!options) return args;
const sensitiveValues = new Set(options.sensitiveValues);
return args.map((arg) => sensitiveValues.has(arg) ? REDACTED_COMMAND_ARGUMENT : arg);
}
async function executeCommand(command, args, onProgress, onStderr, stdinPayload, timeoutMs, commandLogging) {
return new Promise((resolve, reject) => {
const commandId = Logger.commandExecution(command, argsForLogging(args, commandLogging));
const childProcess = spawn(command, IS_WINDOWS ? quoteArgsForWindows(args) : args, {
env: getSpawnEnv(),
shell: IS_WINDOWS,
stdio: [
"pipe",
"pipe",
"pipe"
]
});
childProcess.stdin.on("error", () => {});
if (stdinPayload !== void 0 && stdinPayload.length > 0) childProcess.stdin.write(stdinPayload);
childProcess.stdin.end();
const stdoutChunks = [];
const stderrChunks = [];
let isResolved = false;
let killTimer;
const effectiveTimeoutMs = timeoutMs ?? getTimeoutMs();
const timer = setTimeout(() => {
if (isResolved) return;
isResolved = true;
Logger.warn(`[cmd:${commandId}] Timeout after ${effectiveTimeoutMs}ms, sending SIGTERM`);
childProcess.kill("SIGTERM");
killTimer = setTimeout(() => {
try {
childProcess.kill("SIGKILL");
} catch {}
}, 5e3);
killTimer.unref?.();
const timeoutSec = Math.round(effectiveTimeoutMs / 1e3);
reject(/* @__PURE__ */ new Error(`Command timed out after ${timeoutSec}s. The LLM provider took too long to respond. Try a shorter prompt or increase the timeout via the provider env var (ASK_CODEX_TIMEOUT_MS / ASK_CLAUDE_TIMEOUT_MS / ASK_GEMINI_TIMEOUT_MS) or the global ${EXECUTION.TIMEOUT_ENV_VAR} (current: ${effectiveTimeoutMs}ms).`));
}, effectiveTimeoutMs);
childProcess.stdout.on("data", (data) => {
stdoutChunks.push(data);
if (onProgress) onProgress(data.toString());
});
childProcess.stderr.on("data", (data) => {
stderrChunks.push(data);
if (onStderr) onStderr(data.toString());
});
childProcess.on("error", (error) => {
if (killTimer) clearTimeout(killTimer);
if (!isResolved) {
isResolved = true;
clearTimeout(timer);
Logger.error(`Process error:`, error);
reject(/* @__PURE__ */ new Error(`Failed to spawn command: ${error.message}`));
}
});
childProcess.on("close", (code) => {
if (killTimer) clearTimeout(killTimer);
if (!isResolved) {
isResolved = true;
clearTimeout(timer);
const stdout = Buffer.concat(stdoutChunks).toString();
if (code === 0) {
Logger.commandComplete(commandId, code, stdout.length);
resolve(stdout.trim());
} else {
Logger.commandComplete(commandId, code);
Logger.error(`Failed with exit code ${code}`);
const userMessage = sanitizeErrorForLLM([Buffer.concat(stderrChunks).toString().trim(), stdout.trim()].filter(Boolean).join("\n") || "Unknown error", command);
reject(new Error(userMessage));
}
}
});
});
}
promisify(execFile);
//#endregion
//#region ../shared/dist/pathValidation.js
/**
* Element schema for includeDirs-style tool parameters. Directories handed to
* provider CLIs (`--include-directories`, `--add-dir`) must stay inside the
* workspace: traversal, absolute, and home-relative paths would widen what the
* external CLI can read far beyond the repo the user pointed it at.
*/
const relativeDirSchema = z.string().refine((dir) => !dir.includes("..") && !isAbsolute(dir) && !dir.startsWith("~"), { message: "Directory paths must be relative without '..' or '~'" });
//#endregion
//#region ../shared/dist/machine.js
const providerFailureKindSchema = z.enum([
"rate_limited",
"auth_failed",
"unavailable",
"timeout",
"schema_invalid",
"tool_unavailable"
]);
const machineRoleSchema = z.enum([
"brainstorm",
"review",
"verify"
]);
const machineProviderSchema = z.enum([
"codex",
"claude",
"antigravity"
]);
const actorProviderSchema = z.enum(PROVIDERS);
const requestIdSchema = z.string().regex(/^[A-Za-z0-9._:-]{8,160}$/);
const nonBlankStringSchema = z.string().regex(/\S/, "Value must contain a non-whitespace character");
const machineRelativeDirSchema = relativeDirSchema.max(1024).regex(/^(?!.*\.\.)(?!~)(?!\/)(?![A-Za-z]:[\\/])(?!\\).*$/, "Directory paths must be relative without '..' or '~'");
z.object({
schemaVersion: z.literal(1),
requestId: requestIdSchema,
role: machineRoleSchema,
provider: machineProviderSchema,
prompt: z.string().min(1).max(15e4),
model: nonBlankStringSchema.max(256).optional(),
readOnly: z.literal(true),
writerProvider: actorProviderSchema.optional(),
includeDirs: z.array(machineRelativeDirSchema).max(16).default([])
}).strict().superRefine((value, ctx) => {
if (value.role === "review" && value.writerProvider === value.provider) ctx.addIssue({
code: "custom",
message: "review provider must differ from writer"
});
});
const reviewFindingSchema = z.object({
id: z.string().min(1),
severity: z.enum([
"critical",
"high",
"medium",
"low"
]),
confidence: z.number().int().min(0).max(100),
title: z.string().min(1),
evidence: z.string().min(1),
recommendation: z.string().min(1),
file: z.string().min(1).nullable(),
line: z.number().int().positive().nullable()
}).strict();
const reviewPayloadSchema = z.object({
summary: z.string(),
findings: z.array(reviewFindingSchema)
}).strict();
const brainstormPayloadSchema = z.object({
recommendation: z.string().min(1),
ideas: z.array(z.object({
title: z.string().min(1),
rationale: z.string().min(1),
risks: z.array(z.string())
}).strict()).min(1)
}).strict();
const verificationPayloadSchema = z.object({
verdict: z.enum([
"verified",
"partial",
"failed"
]),
claims: z.array(z.object({
claim: z.string().min(1),
status: z.enum([
"proven",
"disproven",
"unverifiable"
]),
evidence: z.string().min(1)
}).strict())
}).strict();
const normalizedTokenUsageSchema = z.object({
inputTokens: z.number().int().nonnegative(),
outputTokens: z.number().int().nonnegative(),
totalTokens: z.number().int().nonnegative()
}).strict();
const fallbackSchema = z.discriminatedUnion("occurred", [z.object({
occurred: z.literal(false),
requestedModel: nonBlankStringSchema.nullable(),
actualModel: nonBlankStringSchema.nullable()
}).strict(), z.object({
occurred: z.literal(true),
requestedModel: nonBlankStringSchema,
actualModel: nonBlankStringSchema
}).strict()]).refine((value) => value.occurred || value.requestedModel === value.actualModel, {
message: "requested and actual models must match when fallback did not occur",
path: ["actualModel"]
});
const sessionLocatorSchema = z.union([z.object({
sessionId: nonBlankStringSchema,
transcriptPath: nonBlankStringSchema.nullable()
}).strict(), z.object({
sessionId: nonBlankStringSchema.nullable(),
transcriptPath: nonBlankStringSchema
}).strict()]);
const quotaSignalSchema = z.discriminatedUnion("kind", [z.object({
kind: z.literal("reported"),
usedPercent: z.number().min(0).max(100),
windowHours: z.number().positive()
}).strict(), z.object({ kind: z.literal("runtime_proxy_required") }).strict()]);
const normalizedProviderFailureSchema = z.object({
kind: providerFailureKindSchema,
message: z.string().min(1)
}).strict();
const resultEnvelopeShape = {
schemaVersion: z.literal(1),
requestId: requestIdSchema,
provider: machineProviderSchema,
actualModel: nonBlankStringSchema.nullable(),
rawResponseSha256: z.string().regex(/^[a-f0-9]{64}$/).nullable(),
durationMs: z.number().int().nonnegative(),
usage: normalizedTokenUsageSchema.nullable(),
fallback: fallbackSchema,
session: sessionLocatorSchema.nullable(),
quotaSignal: quotaSignalSchema
};
const brainstormSuccessResultSchema = z.object({
...resultEnvelopeShape,
status: z.literal("success"),
role: z.literal("brainstorm"),
payload: brainstormPayloadSchema,
failure: z.null()
}).strict();
const reviewSuccessResultSchema = z.object({
...resultEnvelopeShape,
status: z.literal("success"),
role: z.literal("review"),
payload: reviewPayloadSchema,
failure: z.null()
}).strict();
const verificationSuccessResultSchema = z.object({
...resultEnvelopeShape,
status: z.literal("success"),
role: z.literal("verify"),
payload: verificationPayloadSchema,
failure: z.null()
}).strict();
const machineSuccessResultSchema = z.discriminatedUnion("role", [
brainstormSuccessResultSchema,
reviewSuccessResultSchema,
verificationSuccessResultSchema
]).refine((value) => value.actualModel === value.fallback.actualModel, {
message: "result and fallback actual models must match",
path: ["fallback", "actualModel"]
});
const machineFailureResultSchema = z.object({
...resultEnvelopeShape,
status: z.literal("failed"),
role: machineRoleSchema,
payload: z.null(),
failure: normalizedProviderFailureSchema
}).strict().refine((value) => value.actualModel === value.fallback.actualModel, {
message: "result and fallback actual models must match",
path: ["fallback", "actualModel"]
});
z.discriminatedUnion("status", [machineSuccessResultSchema, machineFailureResultSchema]);
//#endregion
//#region ../shared/dist/progressTracker.js
async function sendProgressNotification(extra, progress, total, message) {
const progressToken = extra._meta?.progressToken;
if (!progressToken) return;
try {
const params = {
progressToken,
progress
};
if (total !== void 0) params.total = total;
if (message) params.message = message;
await extra.sendNotification({
method: PROTOCOL.NOTIFICATIONS.PROGRESS,
params
});
} catch (error) {
Logger.error("Failed to send progress notification:", error);
}
}
function createProgressTracker(operationName, extra, messages) {
let active = true;
let latestOutput = "";
let messageIndex = 0;
let progress = 0;
sendProgressNotification(extra, 0, void 0, `Starting ${operationName}`);
const interval = setInterval(async () => {
if (active) {
progress += 1;
const baseMessage = messages[messageIndex % messages.length];
const outputPreview = latestOutput.slice(-150).trim();
const msg = outputPreview ? `${baseMessage}\nOutput: ...${outputPreview}` : baseMessage;
await sendProgressNotification(extra, progress, void 0, msg);
messageIndex++;
} else clearInterval(interval);
}, PROTOCOL.KEEPALIVE_INTERVAL);
return {
interval,
async stop(success) {
active = false;
clearInterval(interval);
await sendProgressNotification(extra, 100, 100, success ? `${operationName} completed` : `${operationName} failed`);
},
updateOutput(output) {
latestOutput = output;
}
};
}
//#endregion
//#region ../shared/dist/registry.js
const toolRegistry = [];
async function executeTool(toolName, args, onProgress, onUsage) {
const tool = toolRegistry.find((t) => t.name === toolName);
if (!tool) throw new Error(`Unknown tool: ${toolName}`);
try {
const validatedArgs = tool.zodSchema.parse(args);
return tool.execute(validatedArgs, onProgress, onUsage);
} catch (error) {
if (error instanceof ZodError) {
const issues = error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(", ");
throw new Error(`Invalid arguments for ${toolName}: ${issues}`);
}
throw error;
}
}
function getPromptMessage(toolName, args) {
if (!toolRegistry.find((t) => t.name === toolName)?.prompt) throw new Error(`No prompt defined for tool: ${toolName}`);
const paramStrings = [];
if (args.prompt) paramStrings.push(args.prompt);
Object.entries(args).forEach(([key, value]) => {
if (key !== "prompt" && value !== void 0 && value !== null && value !== "false") if (value === "true") paramStrings.push(`[${key}]`);
else paramStrings.push(`(${key}: ${value})`);
});
return `Use the ${toolName} tool${paramStrings.length > 0 ? `: ${paramStrings.join(" ")}` : ""}`;
}
//#endregion
//#region ../shared/dist/responseCache.js
const DEFAULT_TTL_MS = 1800 * 1e3;
const DEFAULT_MAX_SIZE_BYTES = 10 * 1024 * 1024;
const DEFAULT_MAX_ENTRIES = 100;
var ResponseCache = class {
cache = /* @__PURE__ */ new Map();
ttlMs;
maxSizeBytes;
maxEntries;
totalSizeBytes = 0;
constructor(options) {
this.ttlMs = options?.ttlMs ?? DEFAULT_TTL_MS;
this.maxSizeBytes = options?.maxSizeBytes ?? DEFAULT_MAX_SIZE_BYTES;
this.maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
}
static buildKey(provider, prompt, model, extra) {
const raw = `${provider}:${model ?? "default"}:${extra ?? ""}:${prompt}`;
return createHash("sha256").update(raw).digest("hex").slice(0, 16);
}
get(key) {
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() - entry.createdAt > this.ttlMs) {
this.delete(key);
Logger.debug(`Response cache expired: ${key}`);
return null;
}
entry.lastAccessedAt = Date.now();
return entry.response;
}
set(key, response) {
if (this.cache.has(key)) this.delete(key);
const sizeBytes = Buffer.byteLength(response, "utf-8");
if (sizeBytes > this.maxSizeBytes) {
Logger.debug(`Response too large to cache: ${sizeBytes} bytes`);
return;
}
while (this.totalSizeBytes + sizeBytes > this.maxSizeBytes || this.cache.size >= this.maxEntries) {
const lruKey = this.findLRU();
if (!lruKey) break;
this.delete(lruKey);
}
this.cache.set(key, {
response,
createdAt: Date.now(),
lastAccessedAt: Date.now(),
sizeBytes
});
this.totalSizeBytes += sizeBytes;
Logger.debug(`Response cached: ${key} (${sizeBytes} bytes, ${this.cache.size} entries, ${this.totalSizeBytes} total bytes)`);
}
get size() {
return this.cache.size;
}
get byteSize() {
return this.totalSizeBytes;
}
clear() {
this.cache.clear();
this.totalSizeBytes = 0;
}
delete(key) {
const entry = this.cache.get(key);
if (entry) {
this.totalSizeBytes -= entry.sizeBytes;
this.cache.delete(key);
}
}
findLRU() {
let oldestKey = null;
let oldestTime = Infinity;
for (const [key, entry] of this.cache) if (entry.lastAccessedAt < oldestTime) {
oldestTime = entry.lastAccessedAt;
oldestKey = key;
}
return oldestKey;
}
};
const responseCache = new ResponseCache();
//#endregion
//#region ../shared/dist/usage.js
function emptyProviderSnapshot() {
return {
calls: 0,
inputTokens: 0,
outputTokens: 0,
cachedTokens: 0,
thinkingTokens: 0,
durationMs: 0,
fellBack: 0
};
}
function addToBucket(bucket, stats) {
bucket.calls += 1;
bucket.inputTokens += stats.inputTokens ?? 0;
bucket.outputTokens += stats.outputTokens ?? 0;
bucket.cachedTokens += stats.cachedTokens ?? 0;
bucket.thinkingTokens += stats.thinkingTokens ?? 0;
bucket.durationMs += stats.durationMs;
if (stats.fellBack) bucket.fellBack += 1;
}
function createSessionUsage() {
let totalCalls = 0;
let totalInputTokens = 0;
let totalOutputTokens = 0;
let totalCachedTokens = 0;
let totalThinkingTokens = 0;
let totalDurationMs = 0;
let fallbackCount = 0;
const byProvider = {};
const byModel = {};
return {
record(stats) {
totalCalls += 1;
totalInputTokens += stats.inputTokens ?? 0;
totalOutputTokens += stats.outputTokens ?? 0;
totalCachedTokens += stats.cachedTokens ?? 0;
totalThinkingTokens += stats.thinkingTokens ?? 0;
totalDurationMs += stats.durationMs;
if (stats.fellBack) fallbackCount += 1;
if (!byProvider[stats.provider]) byProvider[stats.provider] = emptyProviderSnapshot();
addToBucket(byProvider[stats.provider], stats);
if (!byModel[stats.model]) byModel[stats.model] = emptyProviderSnapshot();
addToBucket(byModel[stats.model], stats);
},
snapshot() {
return {
totalCalls,
totalInputTokens,
totalOutputTokens,
totalCachedTokens,
totalThinkingTokens,
totalDurationMs,
fallbackCount,
byProvider: structuredClone(byProvider),
byModel: structuredClone(byModel)
};
},
reset() {
totalCalls = 0;
totalInputTokens = 0;
totalOutputTokens = 0;
totalCachedTokens = 0;
totalThinkingTokens = 0;
totalDurationMs = 0;
fallbackCount = 0;
for (const key of Object.keys(byProvider)) delete byProvider[key];
for (const key of Object.keys(byModel)) delete byModel[key];
}
};
}
function formatSessionUsage(snapshot) {
if (snapshot.totalCalls === 0) return "No LLM calls recorded in this session yet.";
const lines = [
"## Session Usage Summary",
"",
`Total calls: ${snapshot.totalCalls.toLocaleString()}`,
`Total input tokens: ${snapshot.totalInputTokens.toLocaleString()}`,
`Total output tokens: ${snapshot.totalOutputTokens.toLocaleString()}`
];
if (snapshot.totalThinkingTokens > 0) lines.push(`Total thinking tokens: ${snapshot.totalThinkingTokens.toLocaleString()}`);
if (snapshot.totalCachedTokens > 0) lines.push(`Total cached tokens: ${snapshot.totalCachedTokens.toLocaleString()}`);
lines.push(`Total wall time: ${(snapshot.totalDurationMs / 1e3).toFixed(1)}s`);
if (snapshot.fallbackCount > 0) lines.push(`Quota fallbacks triggered: ${snapshot.fallbackCount}`);
const providerEntries = Object.entries(snapshot.byProvider);
if (providerEntries.length > 0) {
lines.push("", "### By provider", "");
for (const [provider, bucket] of providerEntries) lines.push(`- **${provider}** — ${bucket.calls} calls, ${bucket.inputTokens.toLocaleString()} in / ${bucket.outputTokens.toLocaleString()} out tokens, ${(bucket.durationMs / 1e3).toFixed(1)}s` + (bucket.fellBack > 0 ? `, ${bucket.fellBack} fallbacks` : ""));
}
return lines.join("\n");
}
//#endregion
//#region ../shared/dist/serverFactory.js
const diagnosticCheckSchema = z.object({
name: z.string(),
status: z.enum([
"pass",
"warn",
"fail",
"skip"
]),
message: z.string(),
fix: z.string().optional()
});
const diagnosticProviderSchema = z.object({
name: z.string(),
command: z.string(),
available: z.boolean(),
cliPath: z.string().optional(),
cliVersion: z.string().optional(),
error: z.string().optional()
});
z.object({
status: z.enum([
"ok",
"warning",
"error"
]),
generatedAt: z.string(),
environment: z.object({
nodeVersion: z.string(),
nodeOk: z.boolean(),
platform: z.string(),
arch: z.string(),
resolvedPath: z.string(),
askLlmPath: z.string().optional(),
timeoutMs: z.number(),
codexTimeoutMs: z.number(),
claudeTimeoutMs: z.number(),
geminiTimeoutMs: z.number()
}),
providers: z.array(diagnosticProviderSchema),
checks: z.array(diagnosticCheckSchema)
});
const providerUsageBucketSchema = z.object({
calls: z.number(),
inputTokens: z.number(),
outputTokens: z.number(),
cachedTokens: z.number(),
thinkingTokens: z.number(),
durationMs: z.number(),
fellBack: z.number()
});
const sessionUsageSnapshotSchema = z.object({
totalCalls: z.number(),
totalInputTokens: z.number(),
totalOutputTokens: z.number(),
totalCachedTokens: z.number(),
totalThinkingTokens: z.number(),
totalDurationMs: z.number(),
fallbackCount: z.number(),
byProvider: z.record(z.string(), providerUsageBucketSchema),
byModel: z.record(z.string(), providerUsageBucketSchema)
});
function createUsageStatsTool(sessionUsage) {
return {
name: "get-usage-stats",
description: "Get the current MCP server's session usage stats: total LLM calls, token totals (input/output/thinking/cached), wall time, and breakdowns per provider and per model. No data leaves your machine — counts are tracked in-memory for the lifetime of the server process. Returns both human-readable markdown and a structured JSON snapshot via outputSchema.",
zodSchema: z.object({}),
outputSchema: sessionUsageSnapshotSchema,
annotations: {
title: "Get Usage Stats",
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false
},
category: "utility",
execute: async () => {
const snapshot = sessionUsage.snapshot();
return {
text: formatSessionUsage(snapshot),
structuredContent: snapshot
};
}
};
}
function registerSessionUsageResource(server, sessionUsage) {
server.registerResource("session-usage", "usage://current-session", {
title: "Current Session Usage",
description: "Live JSON snapshot of token usage and call statistics for this MCP server session. Re-read at any time for the current totals.",
mimeType: "application/json"
}, async (uri) => ({ contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(sessionUsage.snapshot(), null, 2)
}] }));
}
function registerTools({ server, tools, executeTool, getPromptMessage, progressMessages, sessionUsage }) {
const seen = /* @__PURE__ */ new Set();
for (const tool of tools) {
if (seen.has(tool.name)) throw new Error(`Duplicate tool name "${tool.name}" — tool names must be unique within a server`);
seen.add(tool.name);
const shape = tool.zodSchema.shape;
const outputShape = tool.outputSchema ? tool.outputSchema.shape : void 0;
server.registerTool(tool.name, {
description: tool.description,
inputSchema: shape,
...outputShape ? { outputSchema: outputShape } : {},
annotations: tool.annotations
}, async (args, extra) => {
const toolName = tool.name;
const handle = createProgressTracker(toolName, extra, progressMessages(toolName));
try {
const toolArgs = args;
Logger.toolInvocation(toolName, args);
const result = await executeTool(toolName, toolArgs, (newOutput) => {
handle.updateOutput(newOutput);
}, sessionUsage ? (stats) => sessionUsage.record(stats) : void 0);
await handle.stop(true);
if (typeof result === "string") return {
content: [{
type: "text",
text: result
}],
isError: false
};
return {
content: [{
type: "text",
text: result.text
}],
structuredContent: result.structuredContent,
isError: false
};
} catch (error) {
await handle.stop(false);
Logger.error(`Error in tool '${toolName}':`, error);
return {
content: [{
type: "text",
text: `Error executing ${toolName}: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
});
}
for (const tool of tools) {
if (!tool.prompt) continue;
server.registerPrompt(tool.name, { description: tool.prompt.description }, async (args) => {
return { messages: [{
role: "user",
content: {
type: "text",
text: getPromptMessage(tool.name, args)
}
}] };
});
}
}
path.join(os.tmpdir(), "ask-llm-sessions");
//#endregion
//#region src/constants.ts
const ERROR_MESSAGES = {
QUOTA_SIGNALS: [
"rate_limit_exceeded",
"quota_exceeded",
"429",
"insufficient_quota",
"out of credits",
"spend cap",
"usage limit"
],
ARCHIVED_SESSION_SIGNALS: [
"archived_sessions",
"archived session",
"session is archived"
],
MODEL_UNAVAILABLE_SIGNALS: ["is not supported when using codex with a chatgpt"],
NO_PROMPT_PROVIDED: "Please provide a prompt for analysis. Ask general questions or describe the code you want reviewed.",
TOOL_NOT_FOUND: "not found in registry"
};
const STATUS_MESSAGES = {
QUOTA_SWITCHING: "Codex quota exceeded, switching to fallback model...",
FALLBACK_RETRY: "Retrying with fallback model...",
FALLBACK_SUCCESS: "Fallback model completed successfully",
CODEX_RESPONSE: "Codex response:"
};
const FACTORY_DEFAULT_MODEL = "gpt-5.6-sol";
const CODEX_REASONING_EFFORTS = [
"low",
"medium",
"high",
"xhigh",
"max"
];
const FACTORY_DEFAULT_REASONING_EFFORT = "medium";
function isCodexReasoningEffort(value) {
return value !== void 0 && CODEX_REASONING_EFFORTS.includes(value);
}
const configuredReasoningEffort = process.env.ASK_CODEX_REASONING_EFFORT;
const DEFAULT_REASONING_EFFORT = isCodexReasoningEffort(configuredReasoningEffort) ? configuredReasoningEffort : FACTORY_DEFAULT_REASONING_EFFORT;
const MODELS = {
DEFAULT: process.env.ASK_CODEX_MODEL || "gpt-5.6-sol",
PREFERRED: process.env.ASK_CODEX_PREFERRED_MODEL || "gpt-5.6-sol",
FALLBACK: process.env.ASK_CODEX_FALLBACK_MODEL || "gpt-5.6-terra"
};
const CLI = {
COMMANDS: {
CODEX: "codex",
EXEC: "exec",
RESUME: "resume"
},
FLAGS: {
MODEL: "-m",
CONFIG: "-c",
SKIP_GIT: "--skip-git-repo-check",
EPHEMERAL: "--ephemeral",
JSON: "--json",
SANDBOX: "--sandbox",
SANDBOX_WORKSPACE_WRITE: "workspace-write",
IGNORE_USER_CONFIG: "--ignore-user-config",
IGNORE_RULES: "--ignore-rules",
ADD_DIR: "--add-dir",
OUTPUT_SCHEMA: "--output-schema",
SANDBOX_READ_ONLY: "read-only"
}
};
const CODEX_EDIT_SCHEMA = {
type: "object",
additionalProperties: false,
properties: { edits: {
type: "array",
items: {
type: "object",
additionalProperties: false,
properties: {
file: {
type: "string",
description: "Repo-relative path to an existing file"
},
startLine: {
type: ["integer", "null"],
description: "1-based line where oldCode begins (or null)"
},
oldCode: {
type: "string",
description: "Exact existing text to replace (must match the file verbatim)"
},
newCode: {
type: "string",
description: "Replacement text"
},
description: {
type: ["string", "null"],
description: "One-line rationale (or null)"
}
},
required: [
"file",
"startLine",
"oldCode",
"newCode",
"description"
]
}
} },
required: ["edits"]
};
//#endregion
//#region src/utils/codexDoctor.ts
const execFileAsync = promisify(execFile);
const DOCTOR_PROBE_TIMEOUT_MS = 5e3;
const DOCTOR_MAX_BUFFER = 1024 * 1024 * 4;
function mapCheckStatus(status) {
switch (String(status).toLowerCase()) {
case "ok":
case "pass": return "pass";
case "error":
case "fail":
case "failed": return "fail";
case "skip":
case "skipped": return "skip";
default: return "warn";
}
}
function mapOverallStatus(status) {
switch (String(status).toLowerCase()) {
case "ok":
case "pass": return "ok";
case "error":
case "fail": return "error";
default: return "warning";
}
}
/**
* Parse `codex doctor --json` stdout into a provider-agnostic enrichment.
* Returns `undefined` for anything that is not a recognizable doctor report
* (non-JSON, missing `overallStatus`/`checks`) so callers degrade silently.
*/
function parseCodexDoctorJson(stdout) {
let raw;
try {
raw = JSON.parse(stdout);
} catch {
return;
}
if (!raw || typeof raw !== "object") return void 0;
const report = raw;
if (typeof report.overallStatus !== "string") return void 0;
if (!report.checks || typeof report.checks !== "object") return void 0;
const checks = [];
for (const [key, value] of Object.entries(report.checks)) {
if (!value || typeof value !== "object") continue;
const check = value;
const remediation = typeof check.remediation === "string" && check.remediation.length > 0 ? check.remediation : void 0;
checks.push({
name: typeof check.id === "string" ? check.id : key,
status: mapCheckStatus(check.status),
summary: typeof check.summary === "string" ? check.summary : "",
...remediation ? { remediation } : {}
});
}
return {
heading: "codex doctor",
overall: mapOverallStatus(report.overallStatus),
checks
};
}
async function runCodexDoctorJson(command, pathEnv) {
const { stdout } = await execFileAsync(command, ["doctor", "--json"], {
env: {
...process.env,
PATH: pathEnv
},
timeout: DOCTOR_PROBE_TIMEOUT_MS,
maxBuffer: DOCTOR_MAX_BUFFER
});
return stdout;
}
/**
* Enrich the codex provider with a compact `codex doctor` health summary.
* Capability-probed: any failure (codex too old to know `--json`, timeout,
* unparseable output) yields `undefined`, leaving the doctor report unchanged.
* The `run` parameter is injectable for testing.
*/
async function enrichCodexDoctor(ctx, run = runCodexDoctorJson) {
try {
return parseCodexDoctorJson(await run(ctx.command, ctx.pathEnv));
} catch (err) {
const salvaged = err.stdout;
return typeof salvaged === "string" ? parseCodexDoctorJson(salvaged) : void 0;
}
}
//#endregion
//#region src/utils/codexExecutor.ts
function buildUsageStats(usage, model, durationMs, fellBack) {
return {
provider: "codex",
model,
inputTokens: usage?.input_tokens,
outputTokens: usage?.output_tokens,
cachedTokens: usage?.cached_input_tokens,
thinkingTokens: usage?.reasoning_output_tokens,
durationMs,
fellBack
};
}
function formatStats(usage) {
if (!usage) return "";
const parts = [];
if (usage.input_tokens != null) parts.push(`${usage.input_tokens.toLocaleString()} input tokens`);
if (usage.output_tokens != null) parts.push(`${usage.output_tokens.toLocaleString()} output tokens`);
if (usage.reasoning_output_tokens != null && usage.reasoning_output_tokens > 0) parts.push(`${usage.reasoning_output_tokens.toLocaleString()} thinking tokens`);
if (usage.cached_input_tokens != null && usage.cached_input_tokens > 0) parts.push(`${usage.cached_input_tokens.toLocaleString()} cached`);
return parts.length > 0 ? `\n\n[Codex stats: ${parts.join(", ")}]` : "";
}
function parseCodexJsonlOutput(raw, model, durationMs, fellBack) {
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
let lastAgentMessage;
let threadId;
let usage;
let lastError;
let sawJsonlEvent = false;
for (const line of lines) {
let parsed;
try {
parsed = JSON.parse(line);
} catch {
continue;
}
if (parsed && typeof parsed === "object" && typeof parsed.type === "string") sawJsonlEvent = true;
if (parsed.type === "thread.started") {
const thread = parsed;
if (thread.thread_id) threadId = thread.thread_id;
}
if (parsed.type === "item.completed") {
const item = parsed.item;
if (item?.type === "agent_message" && typeof item.text === "string" && item.text.length > 0) lastAgentMessage = item.text;
}
if (parsed.type === "turn.completed") usage = parsed.usage;
if (parsed.type === "turn.failed") lastError = parsed.error?.message ?? JSON.stringify(parsed);
if (parsed.type === "error") lastError = parsed.message ?? JSON.stringify(parsed);
}
if (lastError && !lastAgentMessage) throw new Error(`Codex error event: ${lastError}`);
if (!lastAgentMessage) {
if (sawJsonlEvent) {
const truncated = raw.length > EXECUTION.ERROR_TRUNCATE_LENGTH ? `${raw.slice(0, EXECUTION.ERROR_TRUNCATE_LENGTH)}…` : raw;
throw new Error(`Codex completed without an agent message${threadId ? ` (thread ${threadId})` : ""}. The run ended before producing a response — retry, or resume the thread via sessionId. Raw JSONL (truncated):\n${truncated}`);
}
Logger.debug("No parseable Codex JSONL events found, using raw text as the response");
return {
response: raw,
threadId,
usage: buildUsageStats(usage, model, durationMs, fellBack)
};
}
return {
response: lastAgentMessage + formatStats(usage),
threadId,
usage: buildUsageStats(usage, model, durationMs, fellBack)
};
}
function isQuotaError(error) {
const msg = (error instanceof Error ? error.message : String(error)).toLowerCase();
return ERROR_MESSAGES.QUOTA_SIGNALS.some((signal) => msg.includes(signal));
}
function isArchivedSessionError(error) {
const msg = (error instanceof Error ? error.message : String(error)).toLowerCase();
return ERROR_MESSAGES.ARCHIVED_SESSION_SIGNALS.some((signal) => msg.includes(signal));
}
function isModelUnavailableError(error) {
const msg = (error instanceof Error ? error.message : String(error)).toLowerCase();
return ERROR_MESSAGES.MODEL_UNAVAILABLE_SIGNALS.some((signal) => msg.includes(signal));
}
function extractFirstJsonObject(text) {
const start = text.indexOf("{");
if (start === -1) return null;
let depth = 0;
let inStr = false;
let esc = false;
for (let i = start; i < text.length; i++) {
const ch = text[i];
if (inStr) {
if (esc) esc = false;
else if (ch === "\\") esc = true;
else if (ch === "\"") inStr = false;
} else if (ch === "\"") inStr = true;
else if (ch === "{") depth++;
else if (ch === "}") {
depth--;
if (depth === 0) return text.slice(start, i + 1);
}
}
return null;
}
function parseCodexEdits(rawJson) {
let parsed;
try {
parsed = JSON.parse(extractFirstJsonObject(rawJson) ?? rawJson);
} catch {
return [];
}
if (!parsed || !Array.isArray(parsed.edits)) return [];
const edits = [];
for (const item of parsed.edits) {
if (!item || typeof item.file !== "string" || typeof item.oldCode !== "string" || typeof item.newCode !== "string") continue;
const oldCode = item.oldCode;
const newCode = item.newCode;
const startLine = typeof item.startLine === "number" && item.startLine > 0 ? item.startLine : 1;
const oldLineCount = oldCode === "" ? 0 : oldCode.split("\n").length;
const newLineCount = newCode === "" ? 0 : newCode.split("\n").length;
edits.push({
filename: item.file,
oldStartLine: startLine,
oldEndLine: startLine + (oldLineCount > 0 ? oldLineCount - 1 : 0),
oldCode,
newStartLine: startLine,
newEndLine: startLine + (newLineCount > 0 ? newLineCount - 1 : 0),
newCode
});
}
return edits;
}
function processCodexEditOutput(rawJson) {
const edits = parseCodexEdits(rawJson);
if (edits.length === 0) return "Codex proposed no edits for this request.";
const validation = validateChangeModeEdits(edits);
if (!validation.valid) return `Edit validation failed:\n${validation.errors.join("\n")}`;
let result = formatChangeModeResponse(edits);
if (edits.length > 5) result = `${summarizeChangeModeEdits(edits)}\n\n${result}`;
return result;
}
function buildArgs(prompt, model, sessionId, useStdin, includeDirs, sandboxMode = CLI.FLAGS.SANDBOX_READ_ONLY, schemaPath, reasoningEffort = DEFAULT_REASONING_EFFORT) {
const base = [CLI.COMMANDS.EXEC];
if (sessionId) base.push(CLI.COMMANDS.RESUME);
base.push(CLI.FLAGS.SKIP_GIT);
if (!sessionId) base.push(CLI.FLAGS.EPHEMERAL);
if (process.env.ASK_CODEX_LOAD_USER_CONFIG !== "1") base.push(CLI.FLAGS.IGNORE_USER_CONFIG, CLI.FLAGS.IGNORE_RULES);
base.push(CLI.FLAGS.SANDBOX, sandboxMode, CLI.FLAGS.CONFIG, `model_reasoning_effort="${reasoningEffort}"`, CLI.FLAGS.JSON, CLI.FLAGS.MODEL, model);
if (schemaPath) base.push(CLI.FLAGS.OUTPUT_SCHEMA, schemaPath);
if (includeDirs?.length) for (const dir of includeDirs) base.push(CLI.FLAGS.ADD_DIR, dir);
if (sessionId) base.push(sessionId);
if (!useStdin) base.push(prompt);
return base;
}
async function executeCodexCLI(options) {
const model = options.model || MODELS.DEFAULT;
const reasoningEffort = options.reasoningEffort || DEFAULT_REASONING_EFFORT;
const sessionId = options.sessionId;
const editMode = options.editMode === true;
const outputSchema = options.outputSchema ?? (editMode ? CODEX_EDIT_SCHEMA : void 0);
const sandboxMode = options.sandbox === "workspace-write" ? CLI.FLAGS.SANDBOX_WORKSPACE_WRITE : CLI.FLAGS.SANDBOX_READ_ONLY;
const wantsSession = sessionId !== void 0;
const preferredEligible = options.preferred === true && !options.model && !wantsSession && !editMode && MODELS.PREFERRED !== MODELS.DEFAULT;
const dirsPart = options.includeDirs?.length ? [...options.includeDirs].sort().join(":") : "";
const extraContext = `effort=${reasoningEffort};edit=${editMode ? 1 : 0};sandbox=${sandboxMode};dirs=${dirsPart}`;
const cacheKey = wantsSession || preferredEligible || outputSchema ? null : ResponseCache.buildKey("codex", options.prompt, model, extraContext);
if (cacheKey) {
const cached = responseCache.get(cacheKey);
if (cached) {
Logger.debug("Response cache hit for codex");
return {
response: cached,
threadId: void 0,
usage: void 0
};
}
}
let schemaPath;
try {
if (outputSchema) {
schemaPath = join(tmpdir(), `codex-output-schema-${process.pid}-${randomUUID()}.json`);
writeFileSync(schemaPath, JSON.stringify(outputSchema), { mode: 384 });
}
const useStdin = outputSchema !== void 0 || options.prompt.length > EXECUTION.STDIN_THRESHOLD_BYTES;
const stdinPayload = useStdin ? options.prompt : void 0;
const args = buildArgs(options.prompt, model, sessionId, useStdin, options.includeDirs, sandboxMode, schemaPath, reasoningEffort);
const timeoutMs = resolveTimeoutMs(EXECUTION.CODEX_TIMEOUT_ENV_VAR, EXECUTION.DEFAULT_CODEX_TIMEOUT_MS);
let downgradedFromPreferred = false;
if (preferredEligible) {
const preferredArgs = buildArgs(options.prompt, MODELS.PREFERRED, void 0, useStdin, options.includeDirs, sandboxMode, schemaPath, reasoningEffort);
const preferredStartedAt = Date.now();
try {
return parseCodexJsonlOutput(await executeCommand(CLI.COMMANDS.CODEX, preferredArgs, options.onProgress, void 0, stdinPayload, timeoutMs), MODELS.PREFERRED, Date.now() - preferredStartedAt, false);
} catch (preferredError) {
const reason = preferredError instanceof Error ? preferredError.message : String(preferredError);
Logger.warn(`Preferred Codex model ${MODELS.PREFERRED} unavailable (${reason}); falling back to ${MODELS.DEFAULT}.`);
downgradedFromPreferred = true;
}
}
const startedAt = Date.now();
try {
const result = parseCodexJsonlOutput(await executeCommand(CLI.COMMANDS.CODEX, args, options.onProgress, void 0, stdinPayload, timeoutMs), model, Date.now() - startedAt, downgradedFromPreferred);
if (cacheKey) responseCache.set(cacheKey, result.response);
return result;
} catch (error) {
if (sessionId && isArchivedSessionError(error)) throw new Error(`Codex session ${sessionId} is archived. Run \`codex unarchive ${sessionId}\` to resume it, or omit sessionId to start a new thread.`);
if (isQuotaError(error) && model !== MODELS.FALLBACK) {
Logger.warn(`${STATUS_MESSAGES.QUOTA_SWITCHING} Falling back to ${MODELS.FALLBACK}.`);
Logger.debug(`Status: ${STATUS_MESSAGES.FALLBACK_RETRY}`);
const fallbackArgs = buildArgs(options.prompt, MODELS.FALLBACK, sessionId, useStdin, options.includeDirs, sandboxMode, schemaPath, reasoningEffort);
const fallbackStartedAt = Date.now();
try {
const raw = await executeCommand(CLI.COMMANDS.CODEX, fallbackArgs, options.onProgress, void 0, stdinPayload, timeoutMs);
Logger.warn(`Successfully executed with ${MODELS.FALLBACK} fallback.`);
Logger.debug(`Status: ${STATUS_MESSAGES.FALLBACK_SUCCESS}`);
return parseCodexJsonlOutput(raw, MODELS.FALLBACK, Date.now() - fallbackStartedAt, true);
} catch (fallbackError) {
const fallbackMsg = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
if (isModelUnavailableError(fallbackError)) {
const remediation = process.env.ASK_CODEX_FALLBACK_MODEL ? "Set ASK_CODEX_FALLBACK_MODEL to a model your account supports, or unset it to use the default (gpt-5.6-terra)." : "Set ASK_CODEX_FALLBACK_MODEL to a model your account supports.";
throw new Error(`${MODELS.DEFAULT} quota exceeded and the fallback model "${MODELS.FALLBACK}" is not available for this Codex account type (${fallbackMsg}). ${remediation}`);
}
throw new Error(`${MODELS.DEFAULT} quota exceeded, ${MODELS.FALLBACK} fallback also failed: ${fallbackMsg}. Run \`codex doctor\` to inspect your Codex CLI installation.`);
}
}
throw error;
}
} finally {
if (schemaPath) try {
unlinkSync(schemaPath);
} catch {}
}
}
//#endregion
export { Logger as C, executeCommand as S, createSessionUsage as _, parseCodexJsonlOutput as a, toolRegistry as b, CODEX_REASONING_EFFORTS as c, FACTORY_DEFAULT_REASONING_EFFORT as d, MODELS as f, registerTools as g, registerSessionUsageResource as h, parseCodexEdits as i, ERROR_MESSAGES as l, createUsageStatsTool as m, isModelUnavailableError as n, processCodexEditOutput as o, STATUS_MESSAGES as p, isQuotaError as r, enrichCodexDoctor as s, executeCodexCLI as t, FACTORY_DEFAULT_MODEL as u, executeTool as v, askResponseSchema as w, relativeDirSchema as x, getPromptMessage as y };
//# sourceMappingURL=codexExecutor-CQOWlbPt.js.map

Sorry, the diff of this file is too big to display

import { S as executeCommand, b as toolRegistry, c as CODEX_REASONING_EFFORTS, d as FACTORY_DEFAULT_REASONING_EFFORT, f as MODELS, l as ERROR_MESSAGES, o as processCodexEditOutput, p as STATUS_MESSAGES, t as executeCodexCLI, u as FACTORY_DEFAULT_MODEL, w as askResponseSchema, x as relativeDirSchema } from "./codexExecutor-CQOWlbPt.js";
import { z } from "zod";
//#region src/tools/ask-codex.tool.ts
const askCodexArgsSchema = z.object({
prompt: z.string().min(1).max(1e5).describe("The question, code review request, or analysis task to send to Codex CLI"),
model: z.string().optional().describe(`DO NOT set this parameter. The tool automatically uses ${MODELS.DEFAULT} and falls back to ${MODELS.FALLBACK} on quota errors. Only set this if the user explicitly requests a specific model.`),
reasoningEffort: z.enum(CODEX_REASONING_EFFORTS).optional().describe(`Codex reasoning effort for this call. Defaults to ${FACTORY_DEFAULT_REASONING_EFFORT}; /codex-review and /brainstorm use high for quality-first work.`),
sessionId: z.string().optional().describe("Optional Codex thread ID to resume a prior conversation. Use the [Thread ID: ...] value from a previous response to continue the same chat with full prior context."),
includeDirs: z.array(relativeDirSchema).optional().describe("Additional directories Codex may access alongside the working directory (maps to codex `--add-dir`, repeatable). Must be relative paths (e.g., 'packages/api'). Useful in monorepos where relevant context spans sibling packages."),
preferred: z.boolean().optional().describe(`Opt into ASK_CODEX_PREFERRED_MODEL when it is configured to differ from the ${MODELS.DEFAULT} default. The built-in preferred value is also ${MODELS.PREFERRED}, so normal calls and review skills should leave this unset.`),
sandbox: z.enum(["read-only", "workspace-write"]).optional().default("read-only").describe("Codex sandbox mode for this call. Defaults to 'read-only', which enforces the core review contract (Codex reads and proposes, the MCP client edits). Set 'workspace-write' ONLY as an explicit opt-out for flows that need Codex to write files itself, e.g. image generation. Review, second-opinion, and analysis flows must never set this.")
});
const askCodexTool = {
name: "ask-codex",
description: `Send a prompt to OpenAI Codex CLI (defaults to ${FACTORY_DEFAULT_MODEL} with automatic fallback on quota errors). Use for code review, second opinions, analysis, and AI-to-AI collaboration. Do not override the model parameter unless the user explicitly asks. Returns both human-readable text and a structured response (provider, model, sessionId, usage) via outputSchema. The returned sessionId field maps to Codex's thread_id and can be passed back as sessionId to continue the conversation.`,
zodSchema: askCodexArgsSchema,
outputSchema: askResponseSchema,
annotations: {
title: "Ask Codex",
readOnlyHint: true,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true
},
prompt: { description: "Execute Codex CLI to get OpenAI Codex's response for code review and analysis." },
category: "codex",
execute: async (args, onProgress, onUsage) => {
const { prompt, model, reasoningEffort, sessionId, includeDirs, preferred, sandbox } = args;
if (!prompt?.trim()) throw new Error(ERROR_MESSAGES.NO_PROMPT_PROVIDED);
const result = await executeCodexCLI({
prompt,
model,
reasoningEffort,
sessionId,
includeDirs,
preferred,
sandbox,
onProgress
});
if (result.usage) onUsage?.(result.usage);
const threadLine = result.threadId ? `\n\n[Thread ID: ${result.threadId}]` : "";
return {
text: `${STATUS_MESSAGES.CODEX_RESPONSE}\n${result.response}${threadLine}`,
structuredContent: {
provider: "codex",
response: result.response,
model: result.usage?.model ?? model ?? MODELS.DEFAULT,
sessionId: result.threadId,
usage: result.usage
}
};
}
};
const askCodexEditTool = {
name: "ask-codex-edit",
description: "Send a code edit request to OpenAI Codex CLI and get structured search/replace edit blocks back (via codex --output-schema). Codex reads the existing files (read-only) and proposes precise, applyable changes for Claude to apply. Use this when you want Codex to suggest specific code modifications to existing files rather than just analysis. Mirrors ask-gemini-edit.",
zodSchema: z.object({
prompt: z.string().min(1).max(1e5).describe("Describe the code changes you want against existing files. Codex returns structured search/replace edit blocks (via --output-schema) that can be applied directly."),
model: z.string().optional().describe(`DO NOT set this parameter. The tool automatically uses ${MODELS.DEFAULT} and falls back to ${MODELS.FALLBACK} on quota errors.`),
sessionId: z.string().optional().describe("Optional Codex thread ID to resume a prior conversation. Use the [Thread ID: ...] value from a previous response to refine the same edit session."),
includeDirs: z.array(relativeDirSchema).optional().describe("Additional directories Codex may read alongside the working directory (maps to codex `--add-dir`). Must be relative paths (e.g., 'packages/api'). Useful in monorepos where relevant context spans sibling packages.")
}),
annotations: {
title: "Ask Codex (Edit Mode)",
readOnlyHint: true,
destructiveHint: false,
idempotentHint: false,
openWorldHint: true
},
prompt: { description: "Execute Codex CLI with --output-schema to get structured edit suggestions for existing files." },
category: "codex",
execute: async (args, onProgress, onUsage) => {
const { prompt, model, sessionId, includeDirs } = args;
if (!prompt?.trim()) throw new Error(ERROR_MESSAGES.NO_PROMPT_PROVIDED);
const result = await executeCodexCLI({
prompt,
model,
sessionId,
includeDirs,
editMode: true,
onProgress
});
if (result.usage) onUsage?.(result.usage);
const threadLine = result.threadId ? `\n\n[Thread ID: ${result.threadId}]` : "";
return `${processCodexEditOutput(result.response)}${threadLine}`;
}
};
const pingTool = {
name: "ping",
description: "Test connectivity with the MCP server",
zodSchema: z.object({ message: z.string().optional().describe("A message to echo back to test the connection") }),
annotations: {
title: "Ping",
readOnlyHint: true,
idempotentHint: true,
openWorldHint: false
},
prompt: { description: "Echo test message to verify MCP server is working" },
category: "simple",
execute: async (args, onProgress) => {
return executeCommand("echo", [args.message || "Pong from Codex MCP Server!"], onProgress);
}
};
//#endregion
//#region src/tools/index.ts
toolRegistry.push(askCodexTool, askCodexEditTool, pingTool);
//#endregion
export {};
//# sourceMappingURL=tools-CfSNYpIz.js.map
{"version":3,"file":"tools-CfSNYpIz.js","names":[],"sources":["../src/tools/ask-codex.tool.ts","../src/tools/ask-codex-edit.tool.ts","../src/tools/simple-tools.ts","../src/tools/index.ts"],"sourcesContent":["import { type AskResponse, askResponseSchema, relativeDirSchema, type UnifiedTool } from \"@ask-llm/shared\";\nimport { z } from \"zod\";\nimport {\n CODEX_REASONING_EFFORTS,\n type CodexReasoningEffort,\n ERROR_MESSAGES,\n FACTORY_DEFAULT_MODEL,\n FACTORY_DEFAULT_REASONING_EFFORT,\n MODELS,\n STATUS_MESSAGES,\n} from \"../constants.js\";\nimport { executeCodexCLI } from \"../utils/codexExecutor.js\";\n\nconst askCodexArgsSchema = z.object({\n prompt: z\n .string()\n .min(1)\n .max(100000)\n .describe(\"The question, code review request, or analysis task to send to Codex CLI\"),\n model: z\n .string()\n .optional()\n .describe(\n `DO NOT set this parameter. The tool automatically uses ${MODELS.DEFAULT} and falls back to ${MODELS.FALLBACK} on quota errors. Only set this if the user explicitly requests a specific model.`,\n ),\n reasoningEffort: z\n .enum(CODEX_REASONING_EFFORTS)\n .optional()\n .describe(\n `Codex reasoning effort for this call. Defaults to ${FACTORY_DEFAULT_REASONING_EFFORT}; /codex-review and /brainstorm use high for quality-first work.`,\n ),\n sessionId: z\n .string()\n .optional()\n .describe(\n \"Optional Codex thread ID to resume a prior conversation. Use the [Thread ID: ...] value from a previous response to continue the same chat with full prior context.\",\n ),\n includeDirs: z\n .array(relativeDirSchema)\n .optional()\n .describe(\n \"Additional directories Codex may access alongside the working directory (maps to codex `--add-dir`, repeatable). Must be relative paths (e.g., 'packages/api'). Useful in monorepos where relevant context spans sibling packages.\",\n ),\n preferred: z\n .boolean()\n .optional()\n .describe(\n `Opt into ASK_CODEX_PREFERRED_MODEL when it is configured to differ from the ${MODELS.DEFAULT} default. The built-in preferred value is also ${MODELS.PREFERRED}, so normal calls and review skills should leave this unset.`,\n ),\n sandbox: z\n .enum([\"read-only\", \"workspace-write\"])\n .optional()\n .default(\"read-only\")\n .describe(\n \"Codex sandbox mode for this call. Defaults to 'read-only', which enforces the core review contract (Codex reads and proposes, the MCP client edits). Set 'workspace-write' ONLY as an explicit opt-out for flows that need Codex to write files itself, e.g. image generation. Review, second-opinion, and analysis flows must never set this.\",\n ),\n});\n\nexport const askCodexTool: UnifiedTool = {\n name: \"ask-codex\",\n description: `Send a prompt to OpenAI Codex CLI (defaults to ${FACTORY_DEFAULT_MODEL} with automatic fallback on quota errors). Use for code review, second opinions, analysis, and AI-to-AI collaboration. Do not override the model parameter unless the user explicitly asks. Returns both human-readable text and a structured response (provider, model, sessionId, usage) via outputSchema. The returned sessionId field maps to Codex's thread_id and can be passed back as sessionId to continue the conversation.`,\n zodSchema: askCodexArgsSchema,\n outputSchema: askResponseSchema,\n annotations: {\n title: \"Ask Codex\",\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n prompt: {\n description: \"Execute Codex CLI to get OpenAI Codex's response for code review and analysis.\",\n },\n category: \"codex\",\n execute: async (args, onProgress, onUsage) => {\n const { prompt, model, reasoningEffort, sessionId, includeDirs, preferred, sandbox } = args;\n if (!prompt?.trim()) {\n throw new Error(ERROR_MESSAGES.NO_PROMPT_PROVIDED);\n }\n\n const result = await executeCodexCLI({\n prompt: prompt as string,\n model: model as string | undefined,\n reasoningEffort: reasoningEffort as CodexReasoningEffort | undefined,\n sessionId: sessionId as string | undefined,\n includeDirs: includeDirs as string[] | undefined,\n preferred: preferred as boolean | undefined,\n sandbox: sandbox as \"read-only\" | \"workspace-write\" | undefined,\n onProgress,\n });\n\n if (result.usage) onUsage?.(result.usage);\n\n const threadLine = result.threadId ? `\\n\\n[Thread ID: ${result.threadId}]` : \"\";\n const text = `${STATUS_MESSAGES.CODEX_RESPONSE}\\n${result.response}${threadLine}`;\n const structured: AskResponse = {\n provider: \"codex\",\n response: result.response,\n model: result.usage?.model ?? (model as string | undefined) ?? MODELS.DEFAULT,\n sessionId: result.threadId,\n usage: result.usage,\n };\n return { text, structuredContent: structured as unknown as Record<string, unknown> };\n },\n};\n","import { relativeDirSchema, type UnifiedTool } from \"@ask-llm/shared\";\nimport { z } from \"zod\";\nimport { ERROR_MESSAGES, MODELS } from \"../constants.js\";\nimport { executeCodexCLI, processCodexEditOutput } from \"../utils/codexExecutor.js\";\n\nconst askCodexEditArgsSchema = z.object({\n prompt: z\n .string()\n .min(1)\n .max(100000)\n .describe(\n \"Describe the code changes you want against existing files. Codex returns structured search/replace edit blocks (via --output-schema) that can be applied directly.\",\n ),\n model: z\n .string()\n .optional()\n .describe(\n `DO NOT set this parameter. The tool automatically uses ${MODELS.DEFAULT} and falls back to ${MODELS.FALLBACK} on quota errors.`,\n ),\n sessionId: z\n .string()\n .optional()\n .describe(\n \"Optional Codex thread ID to resume a prior conversation. Use the [Thread ID: ...] value from a previous response to refine the same edit session.\",\n ),\n includeDirs: z\n .array(relativeDirSchema)\n .optional()\n .describe(\n \"Additional directories Codex may read alongside the working directory (maps to codex `--add-dir`). Must be relative paths (e.g., 'packages/api'). Useful in monorepos where relevant context spans sibling packages.\",\n ),\n});\n\nexport const askCodexEditTool: UnifiedTool = {\n name: \"ask-codex-edit\",\n description:\n \"Send a code edit request to OpenAI Codex CLI and get structured search/replace edit blocks back (via codex --output-schema). Codex reads the existing files (read-only) and proposes precise, applyable changes for Claude to apply. Use this when you want Codex to suggest specific code modifications to existing files rather than just analysis. Mirrors ask-gemini-edit.\",\n zodSchema: askCodexEditArgsSchema,\n annotations: {\n title: \"Ask Codex (Edit Mode)\",\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n prompt: {\n description: \"Execute Codex CLI with --output-schema to get structured edit suggestions for existing files.\",\n },\n category: \"codex\",\n execute: async (args, onProgress, onUsage) => {\n const { prompt, model, sessionId, includeDirs } = args;\n if (!prompt?.trim()) {\n throw new Error(ERROR_MESSAGES.NO_PROMPT_PROVIDED);\n }\n\n const result = await executeCodexCLI({\n prompt: prompt as string,\n model: model as string | undefined,\n sessionId: sessionId as string | undefined,\n includeDirs: includeDirs as string[] | undefined,\n editMode: true,\n onProgress,\n });\n\n if (result.usage) onUsage?.(result.usage);\n\n const threadLine = result.threadId ? `\\n\\n[Thread ID: ${result.threadId}]` : \"\";\n return `${processCodexEditOutput(result.response)}${threadLine}`;\n },\n};\n","import type { UnifiedTool } from \"@ask-llm/shared\";\nimport { executeCommand } from \"@ask-llm/shared\";\nimport { z } from \"zod\";\n\nconst pingArgsSchema = z.object({\n message: z.string().optional().describe(\"A message to echo back to test the connection\"),\n});\n\nexport const pingTool: UnifiedTool = {\n name: \"ping\",\n description: \"Test connectivity with the MCP server\",\n zodSchema: pingArgsSchema,\n annotations: {\n title: \"Ping\",\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n },\n prompt: {\n description: \"Echo test message to verify MCP server is working\",\n },\n category: \"simple\",\n execute: async (args, onProgress) => {\n const message = args.message || \"Pong from Codex MCP Server!\";\n return executeCommand(\"echo\", [message as string], onProgress);\n },\n};\n","import { toolRegistry } from \"@ask-llm/shared\";\nimport { askCodexTool } from \"./ask-codex.tool.js\";\nimport { askCodexEditTool } from \"./ask-codex-edit.tool.js\";\nimport { pingTool } from \"./simple-tools.js\";\n\ntoolRegistry.push(askCodexTool, askCodexEditTool, pingTool);\n\nexport { executeTool, getPromptMessage, toolRegistry } from \"@ask-llm/shared\";\n"],"mappings":";;;AAaA,MAAM,qBAAqB,EAAE,OAAO;CAClC,QAAQ,EACL,OAAO,CAAC,CACR,IAAI,CAAC,CAAC,CACN,IAAI,GAAM,CAAC,CACX,SAAS,0EAA0E;CACtF,OAAO,EACJ,OAAO,CAAC,CACR,SAAS,CAAC,CACV,SACC,0DAA0D,OAAO,QAAQ,qBAAqB,OAAO,SAAS,kFAChH;CACF,iBAAiB,EACd,KAAK,uBAAuB,CAAC,CAC7B,SAAS,CAAC,CACV,SACC,qDAAqD,iCAAiC,iEACxF;CACF,WAAW,EACR,OAAO,CAAC,CACR,SAAS,CAAC,CACV,SACC,qKACF;CACF,aAAa,EACV,MAAM,iBAAiB,CAAC,CACxB,SAAS,CAAC,CACV,SACC,oOACF;CACF,WAAW,EACR,QAAQ,CAAC,CACT,SAAS,CAAC,CACV,SACC,+EAA+E,OAAO,QAAQ,iDAAiD,OAAO,UAAU,6DAClK;CACF,SAAS,EACN,KAAK,CAAC,aAAa,iBAAiB,CAAC,CAAC,CACtC,SAAS,CAAC,CACV,QAAQ,WAAW,CAAC,CACpB,SACC,gVACF;AACJ,CAAC;AAED,MAAa,eAA4B;CACvC,MAAM;CACN,aAAa,kDAAkD,sBAAsB;CACrF,WAAW;CACX,cAAc;CACd,aAAa;EACX,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;CACjB;CACA,QAAQ,EACN,aAAa,iFACf;CACA,UAAU;CACV,SAAS,OAAO,MAAM,YAAY,YAAY;EAC5C,MAAM,EAAE,QAAQ,OAAO,iBAAiB,WAAW,aAAa,WAAW,YAAY;EACvF,IAAI,CAAC,QAAQ,KAAK,GAChB,MAAM,IAAI,MAAM,eAAe,kBAAkB;EAGnD,MAAM,SAAS,MAAM,gBAAgB;GAC3B;GACD;GACU;GACN;GACE;GACF;GACF;GACT;EACF,CAAC;EAED,IAAI,OAAO,OAAO,UAAU,OAAO,KAAK;EAExC,MAAM,aAAa,OAAO,WAAW,mBAAmB,OAAO,SAAS,KAAK;EAS7E,OAAO;GAAE,MAAA,GARO,gBAAgB,eAAe,IAAI,OAAO,WAAW;GAQtD,mBAAmB;IANhC,UAAU;IACV,UAAU,OAAO;IACjB,OAAO,OAAO,OAAO,SAAU,SAAgC,OAAO;IACtE,WAAW,OAAO;IAClB,OAAO,OAAO;GAE2B;EAAwC;CACrF;AACF;ACvEA,MAAa,mBAAgC;CAC3C,MAAM;CACN,aACE;CACF,WAhC6B,EAAE,OAAO;EACtC,QAAQ,EACL,OAAO,CAAC,CACR,IAAI,CAAC,CAAC,CACN,IAAI,GAAM,CAAC,CACX,SACC,oKACF;EACF,OAAO,EACJ,OAAO,CAAC,CACR,SAAS,CAAC,CACV,SACC,0DAA0D,OAAO,QAAQ,qBAAqB,OAAO,SAAS,kBAChH;EACF,WAAW,EACR,OAAO,CAAC,CACR,SAAS,CAAC,CACV,SACC,mJACF;EACF,aAAa,EACV,MAAM,iBAAiB,CAAC,CACxB,SAAS,CAAC,CACV,SACC,sNACF;CACJ,CAMa;CACX,aAAa;EACX,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;CACjB;CACA,QAAQ,EACN,aAAa,gGACf;CACA,UAAU;CACV,SAAS,OAAO,MAAM,YAAY,YAAY;EAC5C,MAAM,EAAE,QAAQ,OAAO,WAAW,gBAAgB;EAClD,IAAI,CAAC,QAAQ,KAAK,GAChB,MAAM,IAAI,MAAM,eAAe,kBAAkB;EAGnD,MAAM,SAAS,MAAM,gBAAgB;GAC3B;GACD;GACI;GACE;GACb,UAAU;GACV;EACF,CAAC;EAED,IAAI,OAAO,OAAO,UAAU,OAAO,KAAK;EAExC,MAAM,aAAa,OAAO,WAAW,mBAAmB,OAAO,SAAS,KAAK;EAC7E,OAAO,GAAG,uBAAuB,OAAO,QAAQ,IAAI;CACtD;AACF;AC7DA,MAAa,WAAwB;CACnC,MAAM;CACN,aAAa;CACb,WAPqB,EAAE,OAAO,EAC9B,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,+CAA+C,EACzF,CAKa;CACX,aAAa;EACX,OAAO;EACP,cAAc;EACd,gBAAgB;EAChB,eAAe;CACjB;CACA,QAAQ,EACN,aAAa,oDACf;CACA,UAAU;CACV,SAAS,OAAO,MAAM,eAAe;EAEnC,OAAO,eAAe,QAAQ,CADd,KAAK,WAAW,6BACgB,GAAG,UAAU;CAC/D;AACF;;;ACrBA,aAAa,KAAK,cAAc,kBAAkB,QAAQ"}