@ask-llm/mcp
Advanced tools
| import { z } from "zod"; | ||
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| //#region ../shared/dist/machine.d.ts | ||
| declare const machineProviderSchema: z.ZodEnum<{ | ||
| codex: "codex"; | ||
| claude: "claude"; | ||
| antigravity: "antigravity"; | ||
| }>; | ||
| declare const machineRequestSchema: z.ZodObject<{ | ||
| schemaVersion: z.ZodLiteral<1>; | ||
| requestId: z.ZodString; | ||
| role: z.ZodEnum<{ | ||
| brainstorm: "brainstorm"; | ||
| review: "review"; | ||
| verify: "verify"; | ||
| }>; | ||
| provider: z.ZodEnum<{ | ||
| codex: "codex"; | ||
| claude: "claude"; | ||
| antigravity: "antigravity"; | ||
| }>; | ||
| prompt: z.ZodString; | ||
| model: z.ZodOptional<z.ZodString>; | ||
| readOnly: z.ZodLiteral<true>; | ||
| writerProvider: z.ZodOptional<z.ZodEnum<{ | ||
| gemini: "gemini"; | ||
| codex: "codex"; | ||
| claude: "claude"; | ||
| ollama: "ollama"; | ||
| antigravity: "antigravity"; | ||
| }>>; | ||
| includeDirs: z.ZodDefault<z.ZodArray<z.ZodString>>; | ||
| }, z.core.$strict>; | ||
| declare const machineResultSchema: z.ZodDiscriminatedUnion<[z.ZodDiscriminatedUnion<[z.ZodObject<{ | ||
| status: z.ZodLiteral<"success">; | ||
| role: z.ZodLiteral<"brainstorm">; | ||
| payload: z.ZodObject<{ | ||
| recommendation: z.ZodString; | ||
| ideas: z.ZodArray<z.ZodObject<{ | ||
| title: z.ZodString; | ||
| rationale: z.ZodString; | ||
| risks: z.ZodArray<z.ZodString>; | ||
| }, z.core.$strict>>; | ||
| }, z.core.$strict>; | ||
| failure: z.ZodNull; | ||
| schemaVersion: z.ZodLiteral<1>; | ||
| requestId: z.ZodString; | ||
| provider: z.ZodEnum<{ | ||
| codex: "codex"; | ||
| claude: "claude"; | ||
| antigravity: "antigravity"; | ||
| }>; | ||
| actualModel: z.ZodNullable<z.ZodString>; | ||
| rawResponseSha256: z.ZodNullable<z.ZodString>; | ||
| durationMs: z.ZodNumber; | ||
| usage: z.ZodNullable<z.ZodObject<{ | ||
| inputTokens: z.ZodNumber; | ||
| outputTokens: z.ZodNumber; | ||
| totalTokens: z.ZodNumber; | ||
| }, z.core.$strict>>; | ||
| fallback: z.ZodDiscriminatedUnion<[z.ZodObject<{ | ||
| occurred: z.ZodLiteral<false>; | ||
| requestedModel: z.ZodNullable<z.ZodString>; | ||
| actualModel: z.ZodNullable<z.ZodString>; | ||
| }, z.core.$strict>, z.ZodObject<{ | ||
| occurred: z.ZodLiteral<true>; | ||
| requestedModel: z.ZodString; | ||
| actualModel: z.ZodString; | ||
| }, z.core.$strict>], "occurred">; | ||
| session: z.ZodNullable<z.ZodUnion<readonly [z.ZodObject<{ | ||
| sessionId: z.ZodString; | ||
| transcriptPath: z.ZodNullable<z.ZodString>; | ||
| }, z.core.$strict>, z.ZodObject<{ | ||
| sessionId: z.ZodNullable<z.ZodString>; | ||
| transcriptPath: z.ZodString; | ||
| }, z.core.$strict>]>>; | ||
| quotaSignal: z.ZodDiscriminatedUnion<[z.ZodObject<{ | ||
| kind: z.ZodLiteral<"reported">; | ||
| usedPercent: z.ZodNumber; | ||
| windowHours: z.ZodNumber; | ||
| }, z.core.$strict>, z.ZodObject<{ | ||
| kind: z.ZodLiteral<"runtime_proxy_required">; | ||
| }, z.core.$strict>], "kind">; | ||
| }, z.core.$strict>, z.ZodObject<{ | ||
| status: z.ZodLiteral<"success">; | ||
| role: z.ZodLiteral<"review">; | ||
| payload: z.ZodObject<{ | ||
| summary: z.ZodString; | ||
| findings: z.ZodArray<z.ZodObject<{ | ||
| id: z.ZodString; | ||
| severity: z.ZodEnum<{ | ||
| critical: "critical"; | ||
| high: "high"; | ||
| medium: "medium"; | ||
| low: "low"; | ||
| }>; | ||
| confidence: z.ZodNumber; | ||
| title: z.ZodString; | ||
| evidence: z.ZodString; | ||
| recommendation: z.ZodString; | ||
| file: z.ZodNullable<z.ZodString>; | ||
| line: z.ZodNullable<z.ZodNumber>; | ||
| }, z.core.$strict>>; | ||
| }, z.core.$strict>; | ||
| failure: z.ZodNull; | ||
| schemaVersion: z.ZodLiteral<1>; | ||
| requestId: z.ZodString; | ||
| provider: z.ZodEnum<{ | ||
| codex: "codex"; | ||
| claude: "claude"; | ||
| antigravity: "antigravity"; | ||
| }>; | ||
| actualModel: z.ZodNullable<z.ZodString>; | ||
| rawResponseSha256: z.ZodNullable<z.ZodString>; | ||
| durationMs: z.ZodNumber; | ||
| usage: z.ZodNullable<z.ZodObject<{ | ||
| inputTokens: z.ZodNumber; | ||
| outputTokens: z.ZodNumber; | ||
| totalTokens: z.ZodNumber; | ||
| }, z.core.$strict>>; | ||
| fallback: z.ZodDiscriminatedUnion<[z.ZodObject<{ | ||
| occurred: z.ZodLiteral<false>; | ||
| requestedModel: z.ZodNullable<z.ZodString>; | ||
| actualModel: z.ZodNullable<z.ZodString>; | ||
| }, z.core.$strict>, z.ZodObject<{ | ||
| occurred: z.ZodLiteral<true>; | ||
| requestedModel: z.ZodString; | ||
| actualModel: z.ZodString; | ||
| }, z.core.$strict>], "occurred">; | ||
| session: z.ZodNullable<z.ZodUnion<readonly [z.ZodObject<{ | ||
| sessionId: z.ZodString; | ||
| transcriptPath: z.ZodNullable<z.ZodString>; | ||
| }, z.core.$strict>, z.ZodObject<{ | ||
| sessionId: z.ZodNullable<z.ZodString>; | ||
| transcriptPath: z.ZodString; | ||
| }, z.core.$strict>]>>; | ||
| quotaSignal: z.ZodDiscriminatedUnion<[z.ZodObject<{ | ||
| kind: z.ZodLiteral<"reported">; | ||
| usedPercent: z.ZodNumber; | ||
| windowHours: z.ZodNumber; | ||
| }, z.core.$strict>, z.ZodObject<{ | ||
| kind: z.ZodLiteral<"runtime_proxy_required">; | ||
| }, z.core.$strict>], "kind">; | ||
| }, z.core.$strict>, z.ZodObject<{ | ||
| status: z.ZodLiteral<"success">; | ||
| role: z.ZodLiteral<"verify">; | ||
| payload: z.ZodObject<{ | ||
| verdict: z.ZodEnum<{ | ||
| failed: "failed"; | ||
| verified: "verified"; | ||
| partial: "partial"; | ||
| }>; | ||
| claims: z.ZodArray<z.ZodObject<{ | ||
| claim: z.ZodString; | ||
| status: z.ZodEnum<{ | ||
| proven: "proven"; | ||
| disproven: "disproven"; | ||
| unverifiable: "unverifiable"; | ||
| }>; | ||
| evidence: z.ZodString; | ||
| }, z.core.$strict>>; | ||
| }, z.core.$strict>; | ||
| failure: z.ZodNull; | ||
| schemaVersion: z.ZodLiteral<1>; | ||
| requestId: z.ZodString; | ||
| provider: z.ZodEnum<{ | ||
| codex: "codex"; | ||
| claude: "claude"; | ||
| antigravity: "antigravity"; | ||
| }>; | ||
| actualModel: z.ZodNullable<z.ZodString>; | ||
| rawResponseSha256: z.ZodNullable<z.ZodString>; | ||
| durationMs: z.ZodNumber; | ||
| usage: z.ZodNullable<z.ZodObject<{ | ||
| inputTokens: z.ZodNumber; | ||
| outputTokens: z.ZodNumber; | ||
| totalTokens: z.ZodNumber; | ||
| }, z.core.$strict>>; | ||
| fallback: z.ZodDiscriminatedUnion<[z.ZodObject<{ | ||
| occurred: z.ZodLiteral<false>; | ||
| requestedModel: z.ZodNullable<z.ZodString>; | ||
| actualModel: z.ZodNullable<z.ZodString>; | ||
| }, z.core.$strict>, z.ZodObject<{ | ||
| occurred: z.ZodLiteral<true>; | ||
| requestedModel: z.ZodString; | ||
| actualModel: z.ZodString; | ||
| }, z.core.$strict>], "occurred">; | ||
| session: z.ZodNullable<z.ZodUnion<readonly [z.ZodObject<{ | ||
| sessionId: z.ZodString; | ||
| transcriptPath: z.ZodNullable<z.ZodString>; | ||
| }, z.core.$strict>, z.ZodObject<{ | ||
| sessionId: z.ZodNullable<z.ZodString>; | ||
| transcriptPath: z.ZodString; | ||
| }, z.core.$strict>]>>; | ||
| quotaSignal: z.ZodDiscriminatedUnion<[z.ZodObject<{ | ||
| kind: z.ZodLiteral<"reported">; | ||
| usedPercent: z.ZodNumber; | ||
| windowHours: z.ZodNumber; | ||
| }, z.core.$strict>, z.ZodObject<{ | ||
| kind: z.ZodLiteral<"runtime_proxy_required">; | ||
| }, z.core.$strict>], "kind">; | ||
| }, z.core.$strict>], "role">, z.ZodObject<{ | ||
| status: z.ZodLiteral<"failed">; | ||
| role: z.ZodEnum<{ | ||
| brainstorm: "brainstorm"; | ||
| review: "review"; | ||
| verify: "verify"; | ||
| }>; | ||
| payload: z.ZodNull; | ||
| failure: z.ZodObject<{ | ||
| kind: z.ZodEnum<{ | ||
| unavailable: "unavailable"; | ||
| rate_limited: "rate_limited"; | ||
| auth_failed: "auth_failed"; | ||
| timeout: "timeout"; | ||
| schema_invalid: "schema_invalid"; | ||
| tool_unavailable: "tool_unavailable"; | ||
| }>; | ||
| message: z.ZodString; | ||
| }, z.core.$strict>; | ||
| schemaVersion: z.ZodLiteral<1>; | ||
| requestId: z.ZodString; | ||
| provider: z.ZodEnum<{ | ||
| codex: "codex"; | ||
| claude: "claude"; | ||
| antigravity: "antigravity"; | ||
| }>; | ||
| actualModel: z.ZodNullable<z.ZodString>; | ||
| rawResponseSha256: z.ZodNullable<z.ZodString>; | ||
| durationMs: z.ZodNumber; | ||
| usage: z.ZodNullable<z.ZodObject<{ | ||
| inputTokens: z.ZodNumber; | ||
| outputTokens: z.ZodNumber; | ||
| totalTokens: z.ZodNumber; | ||
| }, z.core.$strict>>; | ||
| fallback: z.ZodDiscriminatedUnion<[z.ZodObject<{ | ||
| occurred: z.ZodLiteral<false>; | ||
| requestedModel: z.ZodNullable<z.ZodString>; | ||
| actualModel: z.ZodNullable<z.ZodString>; | ||
| }, z.core.$strict>, z.ZodObject<{ | ||
| occurred: z.ZodLiteral<true>; | ||
| requestedModel: z.ZodString; | ||
| actualModel: z.ZodString; | ||
| }, z.core.$strict>], "occurred">; | ||
| session: z.ZodNullable<z.ZodUnion<readonly [z.ZodObject<{ | ||
| sessionId: z.ZodString; | ||
| transcriptPath: z.ZodNullable<z.ZodString>; | ||
| }, z.core.$strict>, z.ZodObject<{ | ||
| sessionId: z.ZodNullable<z.ZodString>; | ||
| transcriptPath: z.ZodString; | ||
| }, z.core.$strict>]>>; | ||
| quotaSignal: z.ZodDiscriminatedUnion<[z.ZodObject<{ | ||
| kind: z.ZodLiteral<"reported">; | ||
| usedPercent: z.ZodNumber; | ||
| windowHours: z.ZodNumber; | ||
| }, z.core.$strict>, z.ZodObject<{ | ||
| kind: z.ZodLiteral<"runtime_proxy_required">; | ||
| }, z.core.$strict>], "kind">; | ||
| }, z.core.$strict>], "status">; | ||
| type MachineProvider = z.infer<typeof machineProviderSchema>; | ||
| type MachineRequest = z.infer<typeof machineRequestSchema>; | ||
| type MachineResult = z.infer<typeof machineResultSchema>; | ||
| //#endregion | ||
| //#region ../shared/dist/providers.d.ts | ||
| /** | ||
| * 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). | ||
| */ | ||
| declare const PROVIDERS: readonly ["gemini", "codex", "claude", "ollama", "antigravity"]; | ||
| type ProviderName = (typeof PROVIDERS)[number]; | ||
| //#endregion | ||
| //#region ../shared/dist/usage.d.ts | ||
| interface UsageStats { | ||
| provider: ProviderName; | ||
| model: string; | ||
| inputTokens: number | undefined; | ||
| outputTokens: number | undefined; | ||
| cachedTokens: number | undefined; | ||
| thinkingTokens: number | undefined; | ||
| durationMs: number; | ||
| fellBack: boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/machine.d.ts | ||
| type JsonSchema = Record<string, unknown>; | ||
| interface MachineDeps { | ||
| loadExecutor: (provider: MachineProvider) => ExecutorFn | undefined | Promise<ExecutorFn | undefined>; | ||
| now: () => number; | ||
| env?: Readonly<Record<string, string | undefined>>; | ||
| } | ||
| type MachineSchemaTarget = "request" | "result" | "failure"; | ||
| interface MachineSchemaRefinement { | ||
| id: string; | ||
| targets: MachineSchemaTarget[]; | ||
| when?: { | ||
| pointer: string; | ||
| equals: string | number | boolean | null; | ||
| }; | ||
| assertion: { | ||
| leftPointer: string; | ||
| operator: "equals" | "notEquals"; | ||
| rightPointer: string; | ||
| }; | ||
| message: string; | ||
| } | ||
| interface MachineSchemaRefinementSet { | ||
| version: 1; | ||
| rules: MachineSchemaRefinement[]; | ||
| } | ||
| interface MachineSchemaRefinementViolation { | ||
| id: string; | ||
| message: string; | ||
| } | ||
| interface MachineJsonSchemaBundle { | ||
| digest: string; | ||
| failure: JsonSchema; | ||
| request: JsonSchema; | ||
| refinements: MachineSchemaRefinementSet; | ||
| result: JsonSchema; | ||
| rolePayloads: { | ||
| brainstorm: JsonSchema; | ||
| review: JsonSchema; | ||
| verify: JsonSchema; | ||
| }; | ||
| } | ||
| declare function validateMachineSchemaRefinements(target: MachineSchemaTarget, document: unknown, refinements: MachineSchemaRefinementSet): MachineSchemaRefinementViolation[]; | ||
| declare function buildRolePrompt(request: MachineRequest): string; | ||
| declare function runMachineRequest(input: unknown, deps: MachineDeps): Promise<MachineResult>; | ||
| declare function machineJsonSchemaBundle(): MachineJsonSchemaBundle; | ||
| //#endregion | ||
| //#region src/index.d.ts | ||
| interface ProviderStatus { | ||
| available: string[]; | ||
| missing: string[]; | ||
| } | ||
| type ExecutorFn = (options: { | ||
| prompt: string; | ||
| model?: string; | ||
| sessionId?: string; | ||
| includeDirs?: string[]; | ||
| sandbox?: "read-only" | "workspace-write"; | ||
| outputSchema?: Record<string, unknown>; | ||
| readOnly?: boolean; | ||
| onProgress?: (output: string) => void; | ||
| }) => Promise<{ | ||
| response: string; | ||
| model?: string; | ||
| usage?: UsageStats; | ||
| sessionId?: string; | ||
| threadId?: string; | ||
| transcriptPath?: string; | ||
| }>; | ||
| declare function getLoadedExecutor(name: string): ExecutorFn | undefined; | ||
| declare function detectProviders(): Promise<ProviderStatus>; | ||
| declare function startServer(): Promise<void>; | ||
| //#endregion | ||
| export { startServer as a, MachineSchemaRefinement as c, MachineSchemaTarget as d, buildRolePrompt as f, validateMachineSchemaRefinements as h, getLoadedExecutor as i, MachineSchemaRefinementSet as l, runMachineRequest as m, ProviderStatus as n, MachineDeps as o, machineJsonSchemaBundle as p, detectProviders as r, MachineJsonSchemaBundle as s, ExecutorFn as t, MachineSchemaRefinementViolation as u }; | ||
| //# sourceMappingURL=index-BreUyilR.d.ts.map |
| {"version":3,"file":"index-BreUyilR.d.ts","names":["z","ZodEnum","unavailable","rate_limited","auth_failed","timeout","schema_invalid","tool_unavailable","brainstorm","review","verify","codex","claude","antigravity","gemini","ollama","ZodObject","schemaVersion","ZodLiteral","requestId","ZodString","role","provider","prompt","model","ZodOptional","readOnly","writerProvider","includeDirs","ZodDefault","ZodArray","core","$strict","id","severity","critical","high","medium","low","confidence","ZodNumber","title","evidence","recommendation","file","ZodNullable","line","summary","findings","ideas","rationale","risks","verdict","failed","verified","partial","claims","claim","status","proven","disproven","unverifiable","inputTokens","outputTokens","totalTokens","ZodDiscriminatedUnion","occurred","requestedModel","actualModel","ZodUnion","sessionId","transcriptPath","kind","usedPercent","windowHours","message","payload","failure","ZodNull","rawResponseSha256","durationMs","usage","fallback","session","quotaSignal","error","ProviderFailureKind","BrainstormPayload","ReviewPayload","VerificationPayload","Role","MachineRole","ok","RolePayloadByRole","raw","ParsedRolePayload","infer","providerFailureKindSchema","machineRoleSchema","machineProviderSchema","actorProviderSchema","machineRequestSchema","reviewFindingSchema","reviewPayloadSchema","brainstormPayloadSchema","verificationPayloadSchema","normalizedTokenUsageSchema","fallbackSchema","sessionLocatorSchema","quotaSignalSchema","normalizedProviderFailureSchema","machineSuccessResultSchema","machineFailureResultSchema","machineResultSchema","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"],"sources":["../../shared/dist/machine.d.ts","../../shared/dist/providers.d.ts","../../shared/dist/usage.d.ts","../src/machine.ts","../src/index.ts"],"mappings":"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"} |
| import { z } from "zod"; | ||
| import { createHash } from "node:crypto"; | ||
| import { existsSync, readdirSync } from "node:fs"; | ||
| import * as os from "node:os"; | ||
| import { homedir } from "node:os"; | ||
| import * as path from "node:path"; | ||
| import { delimiter, isAbsolute, join } from "node:path"; | ||
| import { execFile, execFileSync } 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$1 = [ | ||
| "gemini", | ||
| "codex", | ||
| "claude", | ||
| "ollama", | ||
| "antigravity" | ||
| ]; | ||
| //#endregion | ||
| //#region ../shared/dist/askResponse.js | ||
| const usageStatsSchema = z.object({ | ||
| provider: z.enum(PROVIDERS$1), | ||
| 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$1), | ||
| 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.`); | ||
| } | ||
| }; | ||
| 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() | ||
| }; | ||
| } | ||
| process.platform; | ||
| function parseTimeoutEnv(envVal) { | ||
| if (!envVal) return void 0; | ||
| const parsed = Number(envVal); | ||
| return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0; | ||
| } | ||
| 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; | ||
| } | ||
| //#endregion | ||
| //#region ../shared/dist/doctor.js | ||
| const execFileAsync = promisify(execFile); | ||
| const NODE_MIN_MAJOR = 20; | ||
| const VERSION_PROBE_TIMEOUT_MS = 5e3; | ||
| function parseNodeMajor(version) { | ||
| const match = version.match(/^v?(\d+)/); | ||
| return match ? Number.parseInt(match[1], 10) : 0; | ||
| } | ||
| async function probeCommand(command, versionArgs, pathEnv) { | ||
| const env = { | ||
| ...process.env, | ||
| PATH: pathEnv | ||
| }; | ||
| let cliPath; | ||
| try { | ||
| const { stdout } = await execFileAsync(process.platform === "win32" ? "where" : "which", [command], { | ||
| env, | ||
| timeout: VERSION_PROBE_TIMEOUT_MS | ||
| }); | ||
| cliPath = stdout.split(/\r?\n/)[0]?.trim() || void 0; | ||
| } catch { | ||
| return { | ||
| cliPath: void 0, | ||
| version: void 0, | ||
| error: "not found on PATH" | ||
| }; | ||
| } | ||
| try { | ||
| const { stdout, stderr } = await execFileAsync(command, versionArgs, { | ||
| env, | ||
| timeout: VERSION_PROBE_TIMEOUT_MS | ||
| }); | ||
| const versionLine = (stdout || stderr).split(/\r?\n/)[0]?.trim(); | ||
| return { | ||
| cliPath, | ||
| version: versionLine || void 0, | ||
| error: void 0 | ||
| }; | ||
| } catch (err) { | ||
| const msg = err instanceof Error ? err.message : String(err); | ||
| return { | ||
| cliPath, | ||
| version: void 0, | ||
| error: `version probe failed: ${msg.slice(0, 200)}` | ||
| }; | ||
| } | ||
| } | ||
| async function runDiagnostics(providers) { | ||
| const checks = []; | ||
| const resolvedPath = resolveShellPath(); | ||
| const nodeOk = parseNodeMajor(process.version) >= NODE_MIN_MAJOR; | ||
| checks.push(nodeOk ? { | ||
| name: "Node.js version", | ||
| status: "pass", | ||
| message: `${process.version} (>= v${NODE_MIN_MAJOR} required)` | ||
| } : { | ||
| name: "Node.js version", | ||
| status: "fail", | ||
| message: `${process.version} is too old — provider CLIs use ES2024 features that crash on Node < ${NODE_MIN_MAJOR}`, | ||
| fix: `Upgrade Node to v${NODE_MIN_MAJOR}+ (LTS). nvm: \`nvm install 22 && nvm use 22\`` | ||
| }); | ||
| checks.push(resolvedPath.length > 0 ? { | ||
| name: "PATH resolution", | ||
| status: "pass", | ||
| message: `Resolved PATH has ${resolvedPath.split(":").filter(Boolean).length} entries` | ||
| } : { | ||
| name: "PATH resolution", | ||
| status: "fail", | ||
| message: "Resolved PATH is empty — provider CLIs cannot be located", | ||
| fix: "Set ASK_LLM_PATH env var explicitly, or restart your MCP client from a shell with a working PATH" | ||
| }); | ||
| const providerProbes = []; | ||
| for (const spec of providers) { | ||
| if (spec.probeAvailability) { | ||
| let available = false; | ||
| try { | ||
| available = await spec.probeAvailability(); | ||
| } catch { | ||
| available = false; | ||
| } | ||
| providerProbes.push({ | ||
| name: spec.name, | ||
| command: spec.command, | ||
| available, | ||
| cliPath: void 0, | ||
| cliVersion: void 0, | ||
| error: available ? void 0 : "endpoint unreachable" | ||
| }); | ||
| checks.push(available ? { | ||
| name: `Provider: ${spec.name}`, | ||
| status: "pass", | ||
| message: "endpoint reachable" | ||
| } : { | ||
| name: `Provider: ${spec.name}`, | ||
| status: "warn", | ||
| message: "endpoint unreachable", | ||
| fix: spec.installHint | ||
| }); | ||
| continue; | ||
| } | ||
| const versionArgs = spec.versionArgs ?? ["--version"]; | ||
| const probe = await probeCommand(spec.command, versionArgs, resolvedPath); | ||
| const available = probe.cliPath !== void 0 && probe.error === void 0; | ||
| let enrichment; | ||
| if (available && spec.enrich) try { | ||
| enrichment = await spec.enrich({ | ||
| command: spec.command, | ||
| pathEnv: resolvedPath | ||
| }); | ||
| } catch { | ||
| enrichment = void 0; | ||
| } | ||
| providerProbes.push({ | ||
| name: spec.name, | ||
| command: spec.command, | ||
| available, | ||
| cliPath: probe.cliPath, | ||
| cliVersion: probe.version, | ||
| error: probe.error, | ||
| enrichment | ||
| }); | ||
| if (probe.cliPath === void 0) checks.push({ | ||
| name: `Provider: ${spec.name}`, | ||
| status: "warn", | ||
| message: `\`${spec.command}\` not found on PATH`, | ||
| fix: spec.installHint | ||
| }); | ||
| else if (probe.error !== void 0) checks.push({ | ||
| name: `Provider: ${spec.name}`, | ||
| status: "warn", | ||
| message: `Found at ${probe.cliPath} but ${probe.error}`, | ||
| fix: "Check that the CLI is properly installed and authenticated" | ||
| }); | ||
| else checks.push({ | ||
| name: `Provider: ${spec.name}`, | ||
| status: "pass", | ||
| message: `${probe.version ?? "available"} at ${probe.cliPath}` | ||
| }); | ||
| } | ||
| const askLlmPath = process.env.ASK_LLM_PATH; | ||
| if (askLlmPath) checks.push({ | ||
| name: "ASK_LLM_PATH override", | ||
| status: "warn", | ||
| message: `Custom PATH override is set (${askLlmPath.split(":").length} entries)`, | ||
| fix: "Unset ASK_LLM_PATH if you want shell-resolved PATH instead" | ||
| }); | ||
| const timeoutEnv = process.env.GMCPT_TIMEOUT_MS; | ||
| const timeoutMs = timeoutEnv ? Number.parseInt(timeoutEnv, 10) : 21e4; | ||
| const codexTimeoutMs = resolveTimeoutMs(EXECUTION.CODEX_TIMEOUT_ENV_VAR, EXECUTION.DEFAULT_CODEX_TIMEOUT_MS); | ||
| const claudeTimeoutMs = resolveTimeoutMs(EXECUTION.CLAUDE_TIMEOUT_ENV_VAR, EXECUTION.DEFAULT_CLAUDE_TIMEOUT_MS); | ||
| const geminiTimeoutMs = resolveTimeoutMs(EXECUTION.GEMINI_TIMEOUT_ENV_VAR, EXECUTION.DEFAULT_TIMEOUT_MS); | ||
| const hasFailure = checks.some((c) => c.status === "fail"); | ||
| const hasWarn = checks.some((c) => c.status === "warn"); | ||
| return { | ||
| status: hasFailure ? "error" : hasWarn ? "warning" : "ok", | ||
| generatedAt: (/* @__PURE__ */ new Date()).toISOString(), | ||
| environment: { | ||
| nodeVersion: process.version, | ||
| nodeOk, | ||
| platform: process.platform, | ||
| arch: process.arch, | ||
| resolvedPath, | ||
| askLlmPath, | ||
| timeoutMs, | ||
| codexTimeoutMs, | ||
| claudeTimeoutMs, | ||
| geminiTimeoutMs | ||
| }, | ||
| providers: providerProbes, | ||
| checks | ||
| }; | ||
| } | ||
| const STATUS_GLYPH = { | ||
| pass: "✓", | ||
| warn: "!", | ||
| fail: "✗", | ||
| skip: "-" | ||
| }; | ||
| const OVERALL_GLYPH = { | ||
| ok: "✓", | ||
| warning: "!", | ||
| error: "✗" | ||
| }; | ||
| function formatDiagnosticReport(report) { | ||
| const lines = []; | ||
| lines.push(`${OVERALL_GLYPH[report.status]} ask-llm doctor — ${report.status.toUpperCase()}`); | ||
| lines.push(""); | ||
| lines.push("Environment:"); | ||
| lines.push(` Node: ${report.environment.nodeVersion}${report.environment.nodeOk ? "" : " (TOO OLD)"}`); | ||
| lines.push(` Platform: ${report.environment.platform}/${report.environment.arch}`); | ||
| lines.push(` Timeouts: codex=${report.environment.codexTimeoutMs}ms, claude=${report.environment.claudeTimeoutMs}ms, gemini=${report.environment.geminiTimeoutMs}ms`); | ||
| if (report.environment.askLlmPath) lines.push(` ASK_LLM_PATH: set (${report.environment.askLlmPath.split(":").length} entries)`); | ||
| lines.push(""); | ||
| lines.push("Checks:"); | ||
| for (const check of report.checks) { | ||
| lines.push(` ${STATUS_GLYPH[check.status]} ${check.name}: ${check.message}`); | ||
| if (check.fix) lines.push(` → ${check.fix}`); | ||
| } | ||
| if (report.providers.length > 0) { | ||
| lines.push(""); | ||
| lines.push("Providers:"); | ||
| for (const provider of report.providers) { | ||
| const status = provider.available ? "available" : "unavailable"; | ||
| const detail = provider.cliPath ? ` (${provider.cliVersion ?? "version unknown"})` : ""; | ||
| lines.push(` - ${provider.name}: ${status}${detail}`); | ||
| if (provider.cliPath) lines.push(` path: ${provider.cliPath}`); | ||
| if (provider.enrichment) { | ||
| const { heading, overall, checks } = provider.enrichment; | ||
| lines.push(` ${heading}: ${overall.toUpperCase()}`); | ||
| for (const check of checks) { | ||
| if (check.status === "pass" || check.status === "skip") continue; | ||
| lines.push(` ${STATUS_GLYPH[check.status]} ${check.name}: ${check.summary}`); | ||
| if (check.remediation) lines.push(` → ${check.remediation}`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| lines.push(""); | ||
| return lines.join("\n"); | ||
| } | ||
| //#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$1); | ||
| 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 '~'"); | ||
| const machineRequestSchema = 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"] | ||
| }); | ||
| const machineResultSchema = z.discriminatedUnion("status", [machineSuccessResultSchema, machineFailureResultSchema]); | ||
| const rolePayloadSchemas$1 = { | ||
| brainstorm: brainstormPayloadSchema, | ||
| review: reviewPayloadSchema, | ||
| verify: verificationPayloadSchema | ||
| }; | ||
| const AUTH_FAILURE_SIGNALS = [ | ||
| /\b401\b/, | ||
| /\b403\b/, | ||
| /auth(?:entication)? (?:failed|required)/, | ||
| /unauthori[sz]ed/, | ||
| /forbidden/, | ||
| /invalid api key/, | ||
| /not logged in/, | ||
| /login required/ | ||
| ]; | ||
| const QUOTA_FAILURE_SIGNALS = [ | ||
| /\b429\b/, | ||
| /usage limit/, | ||
| /rate[_ -]?limit/, | ||
| /quota/, | ||
| /resource[_ -]?exhausted/, | ||
| /exhausted (?:your )?capacity/, | ||
| /out of credits/, | ||
| /spend cap/, | ||
| /too many requests/ | ||
| ]; | ||
| const TIMEOUT_FAILURE_SIGNALS = [ | ||
| /timed out/, | ||
| /timeout/, | ||
| /etimedout/, | ||
| /deadline exceeded/ | ||
| ]; | ||
| const TOOL_FAILURE_SIGNALS = [ | ||
| /enoent/, | ||
| /command not found/, | ||
| /executable not found/, | ||
| /no such file or directory/, | ||
| /cannot find (?:the )?command/ | ||
| ]; | ||
| function matchesAny(message, signals) { | ||
| return signals.some((signal) => signal.test(message)); | ||
| } | ||
| function classifyProviderFailure(error) { | ||
| const message = (error instanceof Error ? error.message : String(error)).toLowerCase(); | ||
| if (matchesAny(message, AUTH_FAILURE_SIGNALS)) return "auth_failed"; | ||
| if (matchesAny(message, QUOTA_FAILURE_SIGNALS)) return "rate_limited"; | ||
| if (matchesAny(message, TIMEOUT_FAILURE_SIGNALS)) return "timeout"; | ||
| if (matchesAny(message, TOOL_FAILURE_SIGNALS)) return "tool_unavailable"; | ||
| return "unavailable"; | ||
| } | ||
| function* extractJsonObjects(raw) { | ||
| let start = -1; | ||
| let depth = 0; | ||
| let inString = false; | ||
| let escaped = false; | ||
| for (let index = 0; index < raw.length; index += 1) { | ||
| const character = raw[index]; | ||
| if (start === -1) { | ||
| if (inString) { | ||
| if (escaped) escaped = false; | ||
| else if (character === "\\") escaped = true; | ||
| else if (character === "\"") inString = false; | ||
| } else if (character === "\"") inString = true; | ||
| else if (character === "{") { | ||
| start = index; | ||
| depth = 1; | ||
| } | ||
| continue; | ||
| } | ||
| if (inString) { | ||
| if (escaped) escaped = false; | ||
| else if (character === "\\") escaped = true; | ||
| else if (character === "\"") inString = false; | ||
| continue; | ||
| } | ||
| if (character === "\"") inString = true; | ||
| else if (character === "{") depth += 1; | ||
| else if (character === "}") { | ||
| depth -= 1; | ||
| if (depth === 0) { | ||
| yield raw.slice(start, index + 1); | ||
| start = -1; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| function parseRolePayload(role, raw) { | ||
| for (const jsonObject of extractJsonObjects(raw)) try { | ||
| const result = rolePayloadSchemas$1[role].safeParse(JSON.parse(jsonObject)); | ||
| if (result.success) return { | ||
| ok: true, | ||
| payload: result.data | ||
| }; | ||
| } catch {} | ||
| return { | ||
| ok: false, | ||
| failure: { | ||
| kind: "schema_invalid", | ||
| message: `Provider output did not contain a valid ${role} payload` | ||
| } | ||
| }; | ||
| } | ||
| //#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/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; | ||
| } | ||
| }; | ||
| 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() | ||
| }); | ||
| const diagnosticReportSchema = 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) | ||
| }); | ||
| function createDiagnoseTool(providers) { | ||
| return { | ||
| name: "diagnose", | ||
| description: "Run a self-diagnosis of the MCP server's environment: Node version, resolved PATH, provider CLI presence and versions, key env vars. Returns a structured report and a human-readable summary. Read-only; never spawns LLM calls.", | ||
| zodSchema: z.object({}), | ||
| outputSchema: diagnosticReportSchema, | ||
| annotations: { | ||
| title: "Diagnose Environment", | ||
| readOnlyHint: true, | ||
| destructiveHint: false, | ||
| idempotentHint: true, | ||
| openWorldHint: false | ||
| }, | ||
| category: "utility", | ||
| execute: async () => { | ||
| const report = await runDiagnostics(providers); | ||
| return { | ||
| text: formatDiagnosticReport(report), | ||
| structuredContent: report | ||
| }; | ||
| } | ||
| }; | ||
| } | ||
| 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) | ||
| }] })); | ||
| } | ||
| path.join(os.tmpdir(), "ask-llm-sessions"); | ||
| //#endregion | ||
| //#region src/constants.ts | ||
| const PROVIDERS = { | ||
| gemini: { | ||
| name: "Gemini", | ||
| command: "gemini", | ||
| executorModule: "@ask-llm/gemini-mcp/executor", | ||
| executorFn: "executeGeminiCLI", | ||
| defaultModel: "gemini-3.1-pro-preview" | ||
| }, | ||
| codex: { | ||
| name: "Codex", | ||
| command: "codex", | ||
| executorModule: "@ask-llm/codex-mcp/executor", | ||
| executorFn: "executeCodexCLI", | ||
| defaultModel: "gpt-5.6-sol", | ||
| modelEnvVar: "ASK_CODEX_MODEL", | ||
| enrichModule: "@ask-llm/codex-mcp/executor", | ||
| enrichFn: "enrichCodexDoctor" | ||
| }, | ||
| claude: { | ||
| name: "Claude", | ||
| command: "claude", | ||
| executorModule: "@ask-llm/claude-mcp/executor", | ||
| executorFn: "executeClaudeCLI", | ||
| defaultModel: "opus", | ||
| modelEnvVar: "ASK_CLAUDE_MODEL", | ||
| disabledWhenEnvVar: "CLAUDECODE" | ||
| }, | ||
| ollama: { | ||
| name: "Ollama", | ||
| command: "ollama", | ||
| executorModule: "@ask-llm/ollama-mcp/executor", | ||
| executorFn: "executeOllamaCLI", | ||
| defaultModel: "qwen3.6:27b", | ||
| availabilityModule: "@ask-llm/ollama-mcp/executor", | ||
| availabilityFn: "isProviderAvailable" | ||
| }, | ||
| antigravity: { | ||
| name: "Antigravity", | ||
| command: "agy", | ||
| executorModule: "@ask-llm/antigravity-mcp/executor", | ||
| executorFn: "executeAntigravityCLI", | ||
| defaultModel: "Gemini 3.1 Pro (High)", | ||
| modelEnvVar: "ASK_ANTIGRAVITY_MODEL" | ||
| } | ||
| }; | ||
| const INSTALL_HINTS = { | ||
| gemini: "npm install -g @google/gemini-cli", | ||
| codex: "npm install -g @openai/codex", | ||
| claude: "npm install -g @anthropic-ai/claude-code, then run `claude` once to authenticate", | ||
| ollama: "https://ollama.com — then: ollama pull qwen3.6:27b", | ||
| antigravity: "Install Google Antigravity (agy) from https://antigravity.google, then run `agy` once to log in" | ||
| }; | ||
| function isProviderEligible(provider) { | ||
| return !(provider.disabledWhenEnvVar && process.env[provider.disabledWhenEnvVar]); | ||
| } | ||
| function getEligibleProviderKeys() { | ||
| return Object.entries(PROVIDERS).filter(([, provider]) => isProviderEligible(provider)).map(([key]) => key); | ||
| } | ||
| //#endregion | ||
| //#region src/machine.ts | ||
| const rolePayloadSchemas = { | ||
| brainstorm: brainstormPayloadSchema, | ||
| review: reviewPayloadSchema, | ||
| verify: verificationPayloadSchema | ||
| }; | ||
| const failureMessages = { | ||
| rate_limited: "Provider rate limit or quota was reached", | ||
| auth_failed: "Provider authentication failed", | ||
| unavailable: "Provider execution failed", | ||
| timeout: "Provider request timed out", | ||
| schema_invalid: "Provider output did not match the role schema", | ||
| tool_unavailable: "Provider tool is unavailable" | ||
| }; | ||
| const machineSchemaRefinements = { | ||
| version: 1, | ||
| rules: [ | ||
| { | ||
| id: "review-provider-differs-from-writer", | ||
| targets: ["request"], | ||
| when: { | ||
| pointer: "/role", | ||
| equals: "review" | ||
| }, | ||
| assertion: { | ||
| leftPointer: "/provider", | ||
| operator: "notEquals", | ||
| rightPointer: "/writerProvider" | ||
| }, | ||
| message: "review provider must differ from writer" | ||
| }, | ||
| { | ||
| id: "non-fallback-models-match", | ||
| targets: ["failure", "result"], | ||
| when: { | ||
| pointer: "/fallback/occurred", | ||
| equals: false | ||
| }, | ||
| assertion: { | ||
| leftPointer: "/fallback/requestedModel", | ||
| operator: "equals", | ||
| rightPointer: "/fallback/actualModel" | ||
| }, | ||
| message: "requested and actual models must match when fallback did not occur" | ||
| }, | ||
| { | ||
| id: "envelope-and-fallback-models-match", | ||
| targets: ["failure", "result"], | ||
| assertion: { | ||
| leftPointer: "/actualModel", | ||
| operator: "equals", | ||
| rightPointer: "/fallback/actualModel" | ||
| }, | ||
| message: "result and fallback actual models must match" | ||
| } | ||
| ] | ||
| }; | ||
| function canonicalize(value) { | ||
| if (Array.isArray(value)) return value.map(canonicalize); | ||
| if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, entry]) => [key, canonicalize(entry)])); | ||
| return value; | ||
| } | ||
| function jsonPointerValue(document, pointer) { | ||
| if (pointer === "") return document; | ||
| if (!pointer.startsWith("/")) return void 0; | ||
| return pointer.slice(1).split("/").map((segment) => segment.replaceAll("~1", "/").replaceAll("~0", "~")).reduce((value, segment) => { | ||
| if (value === null || typeof value !== "object") return void 0; | ||
| return value[segment]; | ||
| }, document); | ||
| } | ||
| function validateMachineSchemaRefinements(target, document, refinements) { | ||
| const violations = []; | ||
| for (const rule of refinements.rules) { | ||
| if (!rule.targets.includes(target)) continue; | ||
| if (rule.when && !Object.is(jsonPointerValue(document, rule.when.pointer), rule.when.equals)) continue; | ||
| const left = jsonPointerValue(document, rule.assertion.leftPointer); | ||
| const right = jsonPointerValue(document, rule.assertion.rightPointer); | ||
| const equal = Object.is(left, right); | ||
| if (!(rule.assertion.operator === "equals" ? equal : !equal)) violations.push({ | ||
| id: rule.id, | ||
| message: rule.message | ||
| }); | ||
| } | ||
| return violations; | ||
| } | ||
| function roleJsonSchema(role) { | ||
| return canonicalize(z.toJSONSchema(rolePayloadSchemas[role])); | ||
| } | ||
| function buildRolePrompt(request) { | ||
| return `${request.prompt}\n\nReturn only one JSON object matching this JSON Schema: ${JSON.stringify(roleJsonSchema(request.role))}`; | ||
| } | ||
| function nonEmptyString(value) { | ||
| if (typeof value !== "string") return null; | ||
| const normalized = value.trim(); | ||
| return normalized.length > 0 ? normalized : null; | ||
| } | ||
| function actualModel(result) { | ||
| return nonEmptyString(result?.usage?.model) ?? nonEmptyString(result?.model); | ||
| } | ||
| function normalizeUsage(result) { | ||
| const inputTokens = result?.usage?.inputTokens; | ||
| const outputTokens = result?.usage?.outputTokens; | ||
| if (!Number.isInteger(inputTokens) || !Number.isInteger(outputTokens) || inputTokens === void 0 || outputTokens === void 0 || inputTokens < 0 || outputTokens < 0) return null; | ||
| return { | ||
| inputTokens, | ||
| outputTokens, | ||
| totalTokens: inputTokens + outputTokens | ||
| }; | ||
| } | ||
| function resolveEffectiveModel(request, env) { | ||
| const provider = PROVIDERS[request.provider]; | ||
| const configuredModel = provider?.modelEnvVar ? env[provider.modelEnvVar] : void 0; | ||
| return nonEmptyString(request.model) ?? nonEmptyString(configuredModel) ?? provider.defaultModel; | ||
| } | ||
| function fallbackFor(request, result, effectiveRequestedModel) { | ||
| const resolvedModel = actualModel(result); | ||
| if (resolvedModel === null) return { | ||
| occurred: false, | ||
| requestedModel: null, | ||
| actualModel: null | ||
| }; | ||
| if (request.provider === "antigravity") { | ||
| if (effectiveRequestedModel !== resolvedModel) return { | ||
| occurred: true, | ||
| requestedModel: effectiveRequestedModel, | ||
| actualModel: resolvedModel | ||
| }; | ||
| return { | ||
| occurred: false, | ||
| requestedModel: resolvedModel, | ||
| actualModel: resolvedModel | ||
| }; | ||
| } | ||
| if (result?.usage?.fellBack === true) return { | ||
| occurred: true, | ||
| requestedModel: effectiveRequestedModel, | ||
| actualModel: resolvedModel | ||
| }; | ||
| return { | ||
| occurred: false, | ||
| requestedModel: resolvedModel, | ||
| actualModel: resolvedModel | ||
| }; | ||
| } | ||
| function sessionFor(request, result) { | ||
| const sessionId = request.provider === "codex" ? nonEmptyString(result?.threadId) : request.provider === "claude" ? nonEmptyString(result?.sessionId) : null; | ||
| const transcriptPath = request.provider === "antigravity" ? nonEmptyString(result?.transcriptPath) : null; | ||
| if (sessionId !== null) return { | ||
| sessionId, | ||
| transcriptPath | ||
| }; | ||
| if (transcriptPath !== null) return { | ||
| sessionId, | ||
| transcriptPath | ||
| }; | ||
| return null; | ||
| } | ||
| function durationMs(value) { | ||
| return Math.max(0, Math.round(value)); | ||
| } | ||
| function resultEnvelope(request, result, elapsedMs, rawResponseSha256, effectiveRequestedModel) { | ||
| return { | ||
| schemaVersion: 1, | ||
| requestId: request.requestId, | ||
| provider: request.provider, | ||
| actualModel: actualModel(result), | ||
| rawResponseSha256, | ||
| durationMs: durationMs(elapsedMs), | ||
| usage: normalizeUsage(result), | ||
| fallback: fallbackFor(request, result, effectiveRequestedModel), | ||
| session: sessionFor(request, result), | ||
| quotaSignal: { kind: "runtime_proxy_required" } | ||
| }; | ||
| } | ||
| function failureResult(request, kind, elapsedMs, effectiveRequestedModel, result, rawResponseSha256 = null, message = failureMessages[kind]) { | ||
| return machineResultSchema.parse({ | ||
| ...resultEnvelope(request, result, elapsedMs, rawResponseSha256, effectiveRequestedModel), | ||
| status: "failed", | ||
| role: request.role, | ||
| payload: null, | ||
| failure: { | ||
| kind, | ||
| message | ||
| } | ||
| }); | ||
| } | ||
| function successResult(request, result, elapsedMs, effectiveRequestedModel) { | ||
| const rawResponse = typeof result.response === "string" ? result.response : ""; | ||
| const rawResponseSha256 = createHash("sha256").update(rawResponse).digest("hex"); | ||
| const parsed = parseRolePayload(request.role, rawResponse); | ||
| if (!parsed.ok) return failureResult(request, "schema_invalid", elapsedMs, effectiveRequestedModel, result, rawResponseSha256, parsed.failure.message); | ||
| return machineResultSchema.parse({ | ||
| ...resultEnvelope(request, result, elapsedMs, rawResponseSha256, effectiveRequestedModel), | ||
| status: "success", | ||
| role: request.role, | ||
| payload: parsed.payload, | ||
| failure: null | ||
| }); | ||
| } | ||
| async function runMachineRequest(input, deps) { | ||
| const request = machineRequestSchema.parse(input); | ||
| const effectiveRequestedModel = resolveEffectiveModel(request, deps.env ?? process.env); | ||
| const executor = await deps.loadExecutor(request.provider); | ||
| if (!executor) return failureResult(request, "tool_unavailable", 0, effectiveRequestedModel); | ||
| const started = deps.now(); | ||
| try { | ||
| return successResult(request, await executor({ | ||
| prompt: buildRolePrompt(request), | ||
| model: effectiveRequestedModel, | ||
| includeDirs: request.includeDirs, | ||
| sandbox: "read-only", | ||
| readOnly: true, | ||
| outputSchema: roleJsonSchema(request.role) | ||
| }), deps.now() - started, effectiveRequestedModel); | ||
| } catch (error) { | ||
| return failureResult(request, classifyProviderFailure(error), deps.now() - started, effectiveRequestedModel); | ||
| } | ||
| } | ||
| function machineJsonSchemaBundle() { | ||
| const schemas = canonicalize({ | ||
| failure: z.toJSONSchema(machineFailureResultSchema), | ||
| request: z.toJSONSchema(machineRequestSchema, { io: "input" }), | ||
| refinements: machineSchemaRefinements, | ||
| result: z.toJSONSchema(machineResultSchema), | ||
| rolePayloads: { | ||
| brainstorm: z.toJSONSchema(brainstormPayloadSchema), | ||
| review: z.toJSONSchema(reviewPayloadSchema), | ||
| verify: z.toJSONSchema(verificationPayloadSchema) | ||
| } | ||
| }); | ||
| const digest = createHash("sha256").update(JSON.stringify(schemas)).digest("hex"); | ||
| return canonicalize({ | ||
| ...schemas, | ||
| digest | ||
| }); | ||
| } | ||
| //#endregion | ||
| export { runDiagnostics as _, INSTALL_HINTS as a, askResponseSchema as b, isProviderEligible as c, registerSessionUsageResource as d, createSessionUsage as f, formatDiagnosticReport as g, machineRequestSchema as h, validateMachineSchemaRefinements as i, createDiagnoseTool as l, createProgressTracker as m, machineJsonSchemaBundle as n, PROVIDERS as o, formatSessionUsage as p, runMachineRequest as r, getEligibleProviderKeys as s, buildRolePrompt as t, createUsageStatsTool as u, getSpawnEnv as v, PROVIDERS$1 as x, Logger as y }; | ||
| //# sourceMappingURL=machine-D6srHmWi.js.map |
Sorry, the diff of this file is too big to display
| import { c as MachineSchemaRefinement, d as MachineSchemaTarget, f as buildRolePrompt, h as validateMachineSchemaRefinements, l as MachineSchemaRefinementSet, m as runMachineRequest, o as MachineDeps, p as machineJsonSchemaBundle, s as MachineJsonSchemaBundle, u as MachineSchemaRefinementViolation } from "./index-BreUyilR.js"; | ||
| export { MachineDeps, MachineJsonSchemaBundle, MachineSchemaRefinement, MachineSchemaRefinementSet, MachineSchemaRefinementViolation, MachineSchemaTarget, buildRolePrompt, machineJsonSchemaBundle, runMachineRequest, validateMachineSchemaRefinements }; |
| import { i as validateMachineSchemaRefinements, n as machineJsonSchemaBundle, r as runMachineRequest, t as buildRolePrompt } from "./machine-D6srHmWi.js"; | ||
| export { buildRolePrompt, machineJsonSchemaBundle, runMachineRequest, validateMachineSchemaRefinements }; |
| import { a as INSTALL_HINTS, b as askResponseSchema, c as isProviderEligible, d as registerSessionUsageResource, f as createSessionUsage, l as createDiagnoseTool, m as createProgressTracker, o as PROVIDERS$1, s as getEligibleProviderKeys, u as createUsageStatsTool, v as getSpawnEnv, x as PROVIDERS, y as Logger } from "./machine-D6srHmWi.js"; | ||
| import { createRequire } from "node:module"; | ||
| import { z } from "zod"; | ||
| import { execFile } from "node:child_process"; | ||
| import { promisify } from "node:util"; | ||
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; | ||
| //#region src/multiLlm.ts | ||
| 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 multiLlmResultSchema = z.object({ | ||
| provider: z.string(), | ||
| ok: z.boolean(), | ||
| response: z.string().optional(), | ||
| model: z.string().optional(), | ||
| sessionId: z.string().optional(), | ||
| usage: usageStatsSchema.optional(), | ||
| durationMs: z.number(), | ||
| error: z.string().optional() | ||
| }); | ||
| const multiLlmReportSchema = z.object({ | ||
| dispatchedAt: z.string(), | ||
| totalDurationMs: z.number(), | ||
| successCount: z.number(), | ||
| failureCount: z.number(), | ||
| results: z.array(multiLlmResultSchema) | ||
| }); | ||
| async function dispatchMultiLlm(opts) { | ||
| const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString(); | ||
| const startTime = Date.now(); | ||
| const dispatches = opts.providers.map(async (provider) => { | ||
| const callStart = Date.now(); | ||
| const executor = opts.getExecutor(provider); | ||
| if (!executor) return { | ||
| provider, | ||
| ok: false, | ||
| error: `Provider "${provider}" is not loaded`, | ||
| durationMs: Date.now() - callStart | ||
| }; | ||
| try { | ||
| const result = await executor({ prompt: opts.prompt }); | ||
| if (result.usage) opts.recordUsage?.(result.usage); | ||
| return { | ||
| provider, | ||
| ok: true, | ||
| response: result.response, | ||
| model: result.usage?.model ?? result.model, | ||
| sessionId: result.sessionId ?? result.threadId, | ||
| usage: result.usage, | ||
| durationMs: Date.now() - callStart | ||
| }; | ||
| } catch (err) { | ||
| return { | ||
| provider, | ||
| ok: false, | ||
| error: err instanceof Error ? err.message : String(err), | ||
| durationMs: Date.now() - callStart | ||
| }; | ||
| } | ||
| }); | ||
| const results = await Promise.all(dispatches); | ||
| const totalDurationMs = Date.now() - startTime; | ||
| const successCount = results.filter((r) => r.ok).length; | ||
| return { | ||
| dispatchedAt, | ||
| totalDurationMs, | ||
| successCount, | ||
| failureCount: results.length - successCount, | ||
| results | ||
| }; | ||
| } | ||
| function formatMultiLlmReport(report) { | ||
| return [`## Multi-LLM Dispatch — ${report.successCount}/${report.results.length} succeeded (${(report.totalDurationMs / 1e3).toFixed(1)}s total)\n`, ...report.results.map((r) => { | ||
| const status = r.ok ? "✓" : "✗"; | ||
| const durationTag = `${(r.durationMs / 1e3).toFixed(1)}s`; | ||
| const modelTag = r.model ? ` · ${r.model}` : ""; | ||
| if (r.ok) return `\n### ${r.provider} ${status} (${durationTag}${modelTag})\n\n${r.response ?? "(empty response)"}`; | ||
| return `\n### ${r.provider} ${status} (${durationTag})\n\n**Failed:** ${r.error ?? "unknown error"}`; | ||
| })].join("\n"); | ||
| } | ||
| function buildMultiLlmInputSchema(availableProviders) { | ||
| const providerEnum = availableProviders.length > 0 ? availableProviders : [...PROVIDERS]; | ||
| return z.object({ | ||
| prompt: z.string().min(1).max(1e5).describe("The prompt to send to all selected providers in parallel."), | ||
| providers: z.array(z.enum(providerEnum)).min(1).optional().describe(`Which providers to dispatch to. Available: ${providerEnum.join(", ")}. Defaults to all available providers.`) | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/availability.ts | ||
| const execFileAsync = promisify(execFile); | ||
| const IS_WINDOWS = process.platform === "win32"; | ||
| async function isCommandAvailable(command) { | ||
| try { | ||
| await execFileAsync(IS_WINDOWS ? "where" : "which", [command], { | ||
| timeout: 5e3, | ||
| env: getSpawnEnv() | ||
| }); | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/providerSpecs.ts | ||
| async function buildProviderSpecs() { | ||
| const specs = []; | ||
| for (const [key, config] of Object.entries(PROVIDERS$1)) { | ||
| if (!isProviderEligible(config)) continue; | ||
| const installHint = INSTALL_HINTS[key]; | ||
| let probeAvailability; | ||
| if (config.availabilityModule && config.availabilityFn) { | ||
| const moduleName = config.availabilityModule; | ||
| const fnName = config.availabilityFn; | ||
| probeAvailability = async () => { | ||
| try { | ||
| const fn = (await import(moduleName))[fnName]; | ||
| if (typeof fn !== "function") return false; | ||
| return await fn(); | ||
| } catch { | ||
| return false; | ||
| } | ||
| }; | ||
| } | ||
| let enrich; | ||
| if (config.enrichModule && config.enrichFn) { | ||
| const moduleName = config.enrichModule; | ||
| const fnName = config.enrichFn; | ||
| enrich = async (ctx) => { | ||
| try { | ||
| const fn = (await import(moduleName))[fnName]; | ||
| if (typeof fn !== "function") return void 0; | ||
| return await fn(ctx); | ||
| } catch { | ||
| return; | ||
| } | ||
| }; | ||
| } | ||
| specs.push({ | ||
| key, | ||
| name: config.name, | ||
| command: config.command, | ||
| installHint, | ||
| probeAvailability, | ||
| enrich | ||
| }); | ||
| } | ||
| return specs; | ||
| } | ||
| //#endregion | ||
| //#region src/index.ts | ||
| function readPackageJson() { | ||
| try { | ||
| return createRequire(import.meta.url)("../package.json"); | ||
| } catch { | ||
| return { | ||
| name: "@ask-llm/mcp", | ||
| version: "0.0.0" | ||
| }; | ||
| } | ||
| } | ||
| const loadedExecutors = /* @__PURE__ */ new Map(); | ||
| function getLoadedExecutor(name) { | ||
| return loadedExecutors.get(name); | ||
| } | ||
| async function detectProviders() { | ||
| const available = []; | ||
| const missing = []; | ||
| const checks = await Promise.all(Object.entries(PROVIDERS$1).map(async ([key, provider]) => { | ||
| let found; | ||
| const disabled = Boolean(provider.disabledWhenEnvVar && process.env[provider.disabledWhenEnvVar]); | ||
| if (disabled) found = false; | ||
| else if (provider.availabilityModule && provider.availabilityFn) try { | ||
| found = await (await import(provider.availabilityModule))[provider.availabilityFn](); | ||
| } catch { | ||
| found = false; | ||
| } | ||
| else found = await isCommandAvailable(provider.command); | ||
| return { | ||
| key, | ||
| provider, | ||
| found, | ||
| disabled | ||
| }; | ||
| })); | ||
| for (const { key, provider, found, disabled } of checks) if (found) try { | ||
| const mod = await import(provider.executorModule); | ||
| loadedExecutors.set(key, mod[provider.executorFn]); | ||
| available.push(key); | ||
| Logger.warn(`Provider ${provider.name} (${provider.command}) — available`); | ||
| } catch (err) { | ||
| missing.push(key); | ||
| Logger.error(`Provider ${provider.name} — import failed:`, err); | ||
| } | ||
| else { | ||
| missing.push(key); | ||
| if (disabled) { | ||
| Logger.warn(`Provider ${provider.name} (${provider.command}) — disabled because ${provider.disabledWhenEnvVar} identifies the current MCP host`); | ||
| continue; | ||
| } | ||
| const hint = INSTALL_HINTS[key] ?? ""; | ||
| Logger.warn(`Provider ${provider.name} (${provider.command}) — not found${hint ? `. Install: ${hint}` : ""}`); | ||
| } | ||
| if (available.length === 0) { | ||
| Logger.warn("No LLM providers found. Install at least one CLI to enable AI tools."); | ||
| for (const [key, hint] of Object.entries(INSTALL_HINTS)) Logger.warn(` ${PROVIDERS$1[key]?.name ?? key}: ${hint}`); | ||
| } | ||
| return { | ||
| available, | ||
| missing | ||
| }; | ||
| } | ||
| function buildAskLlmSchema(availableProviders) { | ||
| const providerEnum = availableProviders.length > 0 ? availableProviders : getEligibleProviderKeys(); | ||
| const providerDescriptions = providerEnum.map((k) => { | ||
| const p = PROVIDERS$1[k]; | ||
| return p ? `"${k}" (${p.name}, default model: ${p.defaultModel})` : k; | ||
| }).join(", "); | ||
| return z.object({ | ||
| provider: z.enum(providerEnum).describe(`Which LLM provider to use. Available: ${providerDescriptions}`), | ||
| prompt: z.string().min(1).max(1e5).describe("The question, code review request, or analysis task to send"), | ||
| model: z.string().optional().describe("Override the default model. Usually not needed."), | ||
| sessionId: z.string().optional().describe("Optional session ID to resume a prior conversation. Pass the [Session ID: ...] or [Thread ID: ...] value from a previous response. Each provider handles sessions natively (Claude/Gemini --resume, Codex exec resume) or via server-side replay (Ollama).") | ||
| }); | ||
| } | ||
| const PROGRESS_MESSAGES = (op) => [ | ||
| `${op} - Processing your request...`, | ||
| `${op} - Generating insights...`, | ||
| `${op} - Large analysis in progress...`, | ||
| `${op} - Still working...` | ||
| ]; | ||
| async function startServer() { | ||
| Logger.debug("init @ask-llm/mcp"); | ||
| Logger.checkNodeVersion(); | ||
| const { name, version } = readPackageJson(); | ||
| const { available } = await detectProviders(); | ||
| const server = new McpServer({ | ||
| name, | ||
| version | ||
| }); | ||
| const sessionUsage = createSessionUsage(); | ||
| const askLlmSchema = buildAskLlmSchema(available); | ||
| server.registerTool("ask-llm", { | ||
| description: "Send a prompt to an LLM provider (Codex, Claude, Antigravity, Ollama, Gemini). Specify which provider to use. Each provider auto-selects its best model with fallback on errors. Returns both human-readable text and a structured response (provider, model, sessionId, usage) via outputSchema.", | ||
| inputSchema: askLlmSchema.shape, | ||
| outputSchema: askResponseSchema.shape, | ||
| annotations: { | ||
| title: "Ask LLM", | ||
| readOnlyHint: false, | ||
| destructiveHint: false, | ||
| openWorldHint: true | ||
| } | ||
| }, async (args, extra) => { | ||
| const progress = createProgressTracker("ask-llm", extra, PROGRESS_MESSAGES("ask-llm")); | ||
| try { | ||
| const { provider, prompt, model, sessionId } = askLlmSchema.parse(args); | ||
| Logger.toolInvocation("ask-llm", args); | ||
| const executor = loadedExecutors.get(provider); | ||
| if (!executor) { | ||
| const hint = INSTALL_HINTS[provider] ?? ""; | ||
| throw new Error(`Provider "${provider}" is not available. ${hint ? `Install: ${hint}` : "Check that the CLI is on your PATH."}`); | ||
| } | ||
| const result = await executor({ | ||
| prompt, | ||
| model, | ||
| sessionId, | ||
| onProgress: (output) => { | ||
| progress.updateOutput(output); | ||
| } | ||
| }); | ||
| if (result.usage) sessionUsage.record(result.usage); | ||
| await progress.stop(true); | ||
| const providerName = PROVIDERS$1[provider]?.name ?? provider; | ||
| const resolvedSessionId = result.sessionId ?? result.threadId; | ||
| const idLine = result.sessionId ? `\n\n[Session ID: ${result.sessionId}]` : result.threadId ? `\n\n[Thread ID: ${result.threadId}]` : ""; | ||
| const structured = { | ||
| provider, | ||
| response: result.response, | ||
| model: result.usage?.model ?? result.model ?? model ?? PROVIDERS$1[provider]?.defaultModel ?? "unknown", | ||
| sessionId: resolvedSessionId, | ||
| usage: result.usage | ||
| }; | ||
| return { | ||
| content: [{ | ||
| type: "text", | ||
| text: `${providerName} response:\n${result.response}${idLine}` | ||
| }], | ||
| structuredContent: structured, | ||
| isError: false | ||
| }; | ||
| } catch (error) { | ||
| await progress.stop(false); | ||
| const msg = error instanceof Error ? error.message : String(error); | ||
| Logger.error("ask-llm error:", error); | ||
| return { | ||
| content: [{ | ||
| type: "text", | ||
| text: `Error: ${msg}` | ||
| }], | ||
| isError: true | ||
| }; | ||
| } | ||
| }); | ||
| const pingSchema = z.object({ message: z.string().optional().describe("A message to echo back to test the connection") }); | ||
| server.registerTool("ping", { | ||
| description: "Test connectivity with the MCP server", | ||
| inputSchema: pingSchema.shape, | ||
| annotations: { | ||
| title: "Ping", | ||
| readOnlyHint: true, | ||
| idempotentHint: true, | ||
| openWorldHint: false | ||
| } | ||
| }, async (args) => { | ||
| const { message } = pingSchema.parse(args); | ||
| const providers = available.length > 0 ? available.join(", ") : "none"; | ||
| return { | ||
| content: [{ | ||
| type: "text", | ||
| text: message || `Pong from @ask-llm/mcp! Available providers: ${providers}` | ||
| }], | ||
| isError: false | ||
| }; | ||
| }); | ||
| const usageTool = createUsageStatsTool(sessionUsage); | ||
| const usageOutputShape = usageTool.outputSchema ? usageTool.outputSchema.shape : void 0; | ||
| server.registerTool(usageTool.name, { | ||
| description: usageTool.description, | ||
| inputSchema: {}, | ||
| ...usageOutputShape ? { outputSchema: usageOutputShape } : {}, | ||
| annotations: usageTool.annotations | ||
| }, async () => { | ||
| const result = await usageTool.execute({}); | ||
| if (typeof result === "string") return { | ||
| content: [{ | ||
| type: "text", | ||
| text: result | ||
| }], | ||
| isError: false | ||
| }; | ||
| return { | ||
| content: [{ | ||
| type: "text", | ||
| text: result.text | ||
| }], | ||
| structuredContent: result.structuredContent, | ||
| isError: false | ||
| }; | ||
| }); | ||
| registerSessionUsageResource(server, sessionUsage); | ||
| const multiLlmInputSchema = buildMultiLlmInputSchema(available.length > 0 ? available : getEligibleProviderKeys()); | ||
| server.registerTool("multi-llm", { | ||
| description: "Dispatch the same prompt to multiple LLM providers in parallel and return all responses in one structured payload. Use when you want to compare answers across Codex, Claude, Antigravity, Ollama, and Gemini, or when you want a multi-provider sanity check on a question. Returns per-provider success/failure, response text, model, sessionId, and token usage. Each call is fresh — no session continuity (use ask-llm for individual session-bearing calls).", | ||
| inputSchema: multiLlmInputSchema.shape, | ||
| outputSchema: multiLlmReportSchema.shape, | ||
| annotations: { | ||
| title: "Multi-LLM Parallel Dispatch", | ||
| readOnlyHint: false, | ||
| destructiveHint: false, | ||
| idempotentHint: false, | ||
| openWorldHint: true | ||
| } | ||
| }, async (args) => { | ||
| const { prompt, providers: requestedProviders } = multiLlmInputSchema.parse(args); | ||
| const providers = requestedProviders && requestedProviders.length > 0 ? requestedProviders : available; | ||
| Logger.toolInvocation("multi-llm", { | ||
| prompt: prompt.slice(0, 80), | ||
| providers | ||
| }); | ||
| const report = await dispatchMultiLlm({ | ||
| prompt, | ||
| providers, | ||
| getExecutor: (name) => loadedExecutors.get(name), | ||
| recordUsage: (stats) => sessionUsage.record(stats) | ||
| }); | ||
| return { | ||
| content: [{ | ||
| type: "text", | ||
| text: formatMultiLlmReport(report) | ||
| }], | ||
| structuredContent: report, | ||
| isError: false | ||
| }; | ||
| }); | ||
| const diagnoseTool = createDiagnoseTool(await buildProviderSpecs()); | ||
| const diagnoseOutputShape = diagnoseTool.outputSchema ? diagnoseTool.outputSchema.shape : void 0; | ||
| server.registerTool(diagnoseTool.name, { | ||
| description: diagnoseTool.description, | ||
| inputSchema: {}, | ||
| ...diagnoseOutputShape ? { outputSchema: diagnoseOutputShape } : {}, | ||
| annotations: diagnoseTool.annotations | ||
| }, async () => { | ||
| const result = await diagnoseTool.execute({}); | ||
| if (typeof result === "string") return { | ||
| content: [{ | ||
| type: "text", | ||
| text: result | ||
| }], | ||
| isError: false | ||
| }; | ||
| return { | ||
| content: [{ | ||
| type: "text", | ||
| text: result.text | ||
| }], | ||
| structuredContent: result.structuredContent, | ||
| isError: false | ||
| }; | ||
| }); | ||
| Logger.warn(`@ask-llm/mcp v${version} — 5 tools, ${available.length} provider(s): ${available.join(", ") || "none"}`); | ||
| const transport = new StdioServerTransport(); | ||
| await server.connect(transport); | ||
| Logger.debug("@ask-llm/mcp listening on stdio"); | ||
| } | ||
| //#endregion | ||
| export { buildProviderSpecs as i, getLoadedExecutor as n, startServer as r, detectProviders as t }; | ||
| //# sourceMappingURL=src-DUet31oX.js.map |
| {"version":3,"file":"src-DUet31oX.js","names":["PROVIDERS","PROVIDERS"],"sources":["../src/multiLlm.ts","../src/utils/availability.ts","../src/utils/providerSpecs.ts","../src/index.ts"],"sourcesContent":["import { PROVIDERS, type UsageStats } from \"@ask-llm/shared\";\nimport { z } from \"zod\";\nimport type { ExecutorFn } from \"./index.js\";\n\nexport interface MultiLlmResult {\n provider: string;\n ok: boolean;\n response?: string;\n model?: string;\n sessionId?: string;\n usage?: UsageStats;\n durationMs: number;\n error?: string;\n}\n\nexport interface MultiLlmReport {\n dispatchedAt: string;\n totalDurationMs: number;\n successCount: number;\n failureCount: number;\n results: MultiLlmResult[];\n}\n\nconst usageStatsSchema = z.object({\n provider: z.enum(PROVIDERS),\n model: z.string(),\n inputTokens: z.number().optional(),\n outputTokens: z.number().optional(),\n cachedTokens: z.number().optional(),\n thinkingTokens: z.number().optional(),\n durationMs: z.number(),\n fellBack: z.boolean(),\n});\n\nconst multiLlmResultSchema = z.object({\n provider: z.string(),\n ok: z.boolean(),\n response: z.string().optional(),\n model: z.string().optional(),\n sessionId: z.string().optional(),\n usage: usageStatsSchema.optional(),\n durationMs: z.number(),\n error: z.string().optional(),\n});\n\nexport const multiLlmReportSchema = z.object({\n dispatchedAt: z.string(),\n totalDurationMs: z.number(),\n successCount: z.number(),\n failureCount: z.number(),\n results: z.array(multiLlmResultSchema),\n});\n\nexport interface DispatchOptions {\n prompt: string;\n providers: string[];\n getExecutor: (provider: string) => ExecutorFn | undefined;\n recordUsage?: (stats: UsageStats) => void;\n}\n\nexport async function dispatchMultiLlm(opts: DispatchOptions): Promise<MultiLlmReport> {\n const dispatchedAt = new Date().toISOString();\n const startTime = Date.now();\n\n const dispatches = opts.providers.map(async (provider): Promise<MultiLlmResult> => {\n const callStart = Date.now();\n const executor = opts.getExecutor(provider);\n if (!executor) {\n return {\n provider,\n ok: false,\n error: `Provider \"${provider}\" is not loaded`,\n durationMs: Date.now() - callStart,\n };\n }\n try {\n const result = await executor({ prompt: opts.prompt });\n if (result.usage) opts.recordUsage?.(result.usage);\n return {\n provider,\n ok: true,\n response: result.response,\n model: result.usage?.model ?? result.model,\n sessionId: result.sessionId ?? result.threadId,\n usage: result.usage,\n durationMs: Date.now() - callStart,\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n provider,\n ok: false,\n error: msg,\n durationMs: Date.now() - callStart,\n };\n }\n });\n\n const results = await Promise.all(dispatches);\n const totalDurationMs = Date.now() - startTime;\n const successCount = results.filter((r) => r.ok).length;\n const failureCount = results.length - successCount;\n\n return { dispatchedAt, totalDurationMs, successCount, failureCount, results };\n}\n\nexport function formatMultiLlmReport(report: MultiLlmReport): string {\n const header = `## Multi-LLM Dispatch — ${report.successCount}/${report.results.length} succeeded (${(report.totalDurationMs / 1000).toFixed(1)}s total)\\n`;\n const sections = report.results.map((r) => {\n const status = r.ok ? \"✓\" : \"✗\";\n const durationTag = `${(r.durationMs / 1000).toFixed(1)}s`;\n const modelTag = r.model ? ` · ${r.model}` : \"\";\n if (r.ok) {\n return `\\n### ${r.provider} ${status} (${durationTag}${modelTag})\\n\\n${r.response ?? \"(empty response)\"}`;\n }\n return `\\n### ${r.provider} ${status} (${durationTag})\\n\\n**Failed:** ${r.error ?? \"unknown error\"}`;\n });\n return [header, ...sections].join(\"\\n\");\n}\n\nexport function buildMultiLlmInputSchema(availableProviders: string[]): z.ZodObject<z.ZodRawShape> {\n const providerEnum = availableProviders.length > 0 ? availableProviders : [...PROVIDERS];\n return z.object({\n prompt: z.string().min(1).max(100000).describe(\"The prompt to send to all selected providers in parallel.\"),\n providers: z\n .array(z.enum(providerEnum as [string, ...string[]]))\n .min(1)\n .optional()\n .describe(\n `Which providers to dispatch to. Available: ${providerEnum.join(\", \")}. Defaults to all available providers.`,\n ),\n });\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { getSpawnEnv } from \"@ask-llm/shared\";\n\nconst execFileAsync = promisify(execFile);\nconst IS_WINDOWS = process.platform === \"win32\";\n\nexport async function isCommandAvailable(command: string): Promise<boolean> {\n try {\n const which = IS_WINDOWS ? \"where\" : \"which\";\n await execFileAsync(which, [command], { timeout: 5000, env: getSpawnEnv() });\n return true;\n } catch {\n return false;\n }\n}\n","import type { ProviderSpec } from \"@ask-llm/shared\";\nimport { INSTALL_HINTS, isProviderEligible, PROVIDERS } from \"../constants.js\";\n\nexport async function buildProviderSpecs(): Promise<ProviderSpec[]> {\n const specs: ProviderSpec[] = [];\n for (const [key, config] of Object.entries(PROVIDERS)) {\n if (!isProviderEligible(config)) continue;\n\n const installHint = INSTALL_HINTS[key];\n let probeAvailability: ProviderSpec[\"probeAvailability\"];\n if (config.availabilityModule && config.availabilityFn) {\n const moduleName = config.availabilityModule;\n const fnName = config.availabilityFn;\n probeAvailability = async () => {\n try {\n const mod = (await import(moduleName)) as Record<string, unknown>;\n const fn = mod[fnName] as (() => Promise<boolean>) | undefined;\n if (typeof fn !== \"function\") return false;\n return await fn();\n } catch {\n return false;\n }\n };\n }\n let enrich: ProviderSpec[\"enrich\"];\n if (config.enrichModule && config.enrichFn) {\n const moduleName = config.enrichModule;\n const fnName = config.enrichFn;\n enrich = async (ctx) => {\n try {\n const mod = (await import(moduleName)) as Record<string, unknown>;\n const fn = mod[fnName] as ProviderSpec[\"enrich\"] | undefined;\n if (typeof fn !== \"function\") return undefined;\n return await fn(ctx);\n } catch {\n return undefined;\n }\n };\n }\n\n specs.push({\n key,\n name: config.name,\n command: config.command,\n installHint,\n probeAvailability,\n enrich,\n });\n }\n return specs;\n}\n","import { createRequire } from \"node:module\";\nimport {\n type AskResponse,\n askResponseSchema,\n createDiagnoseTool,\n createProgressTracker,\n createSessionUsage,\n createUsageStatsTool,\n Logger,\n type ProviderName,\n registerSessionUsageResource,\n type UsageStats,\n} from \"@ask-llm/shared\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport type { RequestHandlerExtra } from \"@modelcontextprotocol/sdk/shared/protocol.js\";\nimport type { CallToolResult, ServerNotification, ServerRequest } from \"@modelcontextprotocol/sdk/types.js\";\nimport { z } from \"zod\";\nimport { getEligibleProviderKeys, INSTALL_HINTS, PROVIDERS } from \"./constants.js\";\nimport { buildMultiLlmInputSchema, dispatchMultiLlm, formatMultiLlmReport, multiLlmReportSchema } from \"./multiLlm.js\";\nimport { isCommandAvailable } from \"./utils/availability.js\";\nimport { buildProviderSpecs } from \"./utils/providerSpecs.js\";\n\nfunction readPackageJson(): { name: string; version: string } {\n try {\n const require = createRequire(import.meta.url);\n return require(\"../package.json\") as { name: string; version: string };\n } catch {\n return { name: \"@ask-llm/mcp\", version: \"0.0.0\" };\n }\n}\n\nexport interface ProviderStatus {\n available: string[];\n missing: string[];\n}\n\nexport type ExecutorFn = (options: {\n prompt: string;\n model?: string;\n sessionId?: string;\n includeDirs?: string[];\n sandbox?: \"read-only\" | \"workspace-write\";\n outputSchema?: Record<string, unknown>;\n readOnly?: boolean;\n onProgress?: (output: string) => void;\n}) => Promise<{\n response: string;\n // Actual model that produced the answer. Gemini/Codex report it via\n // usage.model; Ollama and Antigravity surface it here (their usage may be\n // undefined), so the unified layer reads result.model to stay accurate after\n // a provider-internal fallback (e.g. Antigravity Pro → Flash).\n model?: string;\n usage?: UsageStats;\n sessionId?: string;\n threadId?: string;\n transcriptPath?: string;\n}>;\n\nexport type {\n MachineDeps,\n MachineJsonSchemaBundle,\n MachineSchemaRefinement,\n MachineSchemaRefinementSet,\n MachineSchemaRefinementViolation,\n MachineSchemaTarget,\n} from \"./machine.js\";\nexport {\n buildRolePrompt,\n machineJsonSchemaBundle,\n runMachineRequest,\n validateMachineSchemaRefinements,\n} from \"./machine.js\";\n\nconst loadedExecutors = new Map<string, ExecutorFn>();\n\nexport function getLoadedExecutor(name: string): ExecutorFn | undefined {\n return loadedExecutors.get(name);\n}\n\nexport async function detectProviders(): Promise<ProviderStatus> {\n const available: string[] = [];\n const missing: string[] = [];\n\n const checks = await Promise.all(\n Object.entries(PROVIDERS).map(async ([key, provider]) => {\n let found: boolean;\n const disabled = Boolean(provider.disabledWhenEnvVar && process.env[provider.disabledWhenEnvVar]);\n if (disabled) {\n found = false;\n } else if (provider.availabilityModule && provider.availabilityFn) {\n try {\n const mod = await import(provider.availabilityModule);\n found = await (mod[provider.availabilityFn] as () => Promise<boolean>)();\n } catch {\n found = false;\n }\n } else {\n found = await isCommandAvailable(provider.command);\n }\n return { key, provider, found, disabled };\n }),\n );\n\n for (const { key, provider, found, disabled } of checks) {\n if (found) {\n try {\n const mod = await import(provider.executorModule);\n loadedExecutors.set(key, mod[provider.executorFn] as ExecutorFn);\n available.push(key);\n Logger.warn(`Provider ${provider.name} (${provider.command}) — available`);\n } catch (err) {\n missing.push(key);\n Logger.error(`Provider ${provider.name} — import failed:`, err);\n }\n } else {\n missing.push(key);\n if (disabled) {\n Logger.warn(\n `Provider ${provider.name} (${provider.command}) — disabled because ${provider.disabledWhenEnvVar} identifies the current MCP host`,\n );\n continue;\n }\n const hint = INSTALL_HINTS[key] ?? \"\";\n Logger.warn(`Provider ${provider.name} (${provider.command}) — not found${hint ? `. Install: ${hint}` : \"\"}`);\n }\n }\n\n if (available.length === 0) {\n Logger.warn(\"No LLM providers found. Install at least one CLI to enable AI tools.\");\n for (const [key, hint] of Object.entries(INSTALL_HINTS)) {\n Logger.warn(` ${PROVIDERS[key]?.name ?? key}: ${hint}`);\n }\n }\n\n return { available, missing };\n}\n\nfunction buildAskLlmSchema(availableProviders: string[]) {\n const providerEnum = availableProviders.length > 0 ? availableProviders : getEligibleProviderKeys();\n const providerDescriptions = providerEnum\n .map((k) => {\n const p = PROVIDERS[k];\n return p ? `\"${k}\" (${p.name}, default model: ${p.defaultModel})` : k;\n })\n .join(\", \");\n\n return z.object({\n provider: z\n .enum(providerEnum as [string, ...string[]])\n .describe(`Which LLM provider to use. Available: ${providerDescriptions}`),\n prompt: z.string().min(1).max(100000).describe(\"The question, code review request, or analysis task to send\"),\n model: z.string().optional().describe(\"Override the default model. Usually not needed.\"),\n sessionId: z\n .string()\n .optional()\n .describe(\n \"Optional session ID to resume a prior conversation. Pass the [Session ID: ...] or [Thread ID: ...] value from a previous response. Each provider handles sessions natively (Claude/Gemini --resume, Codex exec resume) or via server-side replay (Ollama).\",\n ),\n });\n}\n\ntype ToolExtra = RequestHandlerExtra<ServerRequest, ServerNotification>;\n\nconst PROGRESS_MESSAGES = (op: string) => [\n `${op} - Processing your request...`,\n `${op} - Generating insights...`,\n `${op} - Large analysis in progress...`,\n `${op} - Still working...`,\n];\n\nexport async function startServer() {\n Logger.debug(\"init @ask-llm/mcp\");\n Logger.checkNodeVersion();\n const { name, version } = readPackageJson();\n const { available } = await detectProviders();\n\n const server = new McpServer({ name, version });\n const sessionUsage = createSessionUsage();\n\n const askLlmSchema = buildAskLlmSchema(available);\n\n server.registerTool(\n \"ask-llm\",\n {\n description:\n \"Send a prompt to an LLM provider (Codex, Claude, Antigravity, Ollama, Gemini). Specify which provider to use. Each provider auto-selects its best model with fallback on errors. Returns both human-readable text and a structured response (provider, model, sessionId, usage) via outputSchema.\",\n inputSchema: askLlmSchema.shape,\n outputSchema: (askResponseSchema as z.ZodObject<z.ZodRawShape>).shape,\n annotations: { title: \"Ask LLM\", readOnlyHint: false, destructiveHint: false, openWorldHint: true },\n },\n async (args: Record<string, unknown>, extra: ToolExtra): Promise<CallToolResult> => {\n const progress = createProgressTracker(\"ask-llm\", extra, PROGRESS_MESSAGES(\"ask-llm\"));\n try {\n const { provider, prompt, model, sessionId } = askLlmSchema.parse(args);\n Logger.toolInvocation(\"ask-llm\", args);\n\n const executor = loadedExecutors.get(provider);\n if (!executor) {\n const hint = INSTALL_HINTS[provider] ?? \"\";\n throw new Error(\n `Provider \"${provider}\" is not available. ${hint ? `Install: ${hint}` : \"Check that the CLI is on your PATH.\"}`,\n );\n }\n\n const result = await executor({\n prompt,\n model,\n sessionId,\n onProgress: (output) => {\n progress.updateOutput(output);\n },\n });\n\n if (result.usage) sessionUsage.record(result.usage);\n\n await progress.stop(true);\n const providerName = PROVIDERS[provider]?.name ?? provider;\n const resolvedSessionId = result.sessionId ?? result.threadId;\n const idLine = result.sessionId\n ? `\\n\\n[Session ID: ${result.sessionId}]`\n : result.threadId\n ? `\\n\\n[Thread ID: ${result.threadId}]`\n : \"\";\n const structured: AskResponse = {\n provider: provider as ProviderName,\n response: result.response,\n model: result.usage?.model ?? result.model ?? model ?? PROVIDERS[provider]?.defaultModel ?? \"unknown\",\n sessionId: resolvedSessionId,\n usage: result.usage,\n };\n return {\n content: [{ type: \"text\", text: `${providerName} response:\\n${result.response}${idLine}` }],\n structuredContent: structured as unknown as Record<string, unknown>,\n isError: false,\n };\n } catch (error) {\n await progress.stop(false);\n const msg = error instanceof Error ? error.message : String(error);\n Logger.error(\"ask-llm error:\", error);\n return { content: [{ type: \"text\", text: `Error: ${msg}` }], isError: true };\n }\n },\n );\n\n const pingSchema = z.object({\n message: z.string().optional().describe(\"A message to echo back to test the connection\"),\n });\n\n server.registerTool(\n \"ping\",\n {\n description: \"Test connectivity with the MCP server\",\n inputSchema: pingSchema.shape,\n annotations: { title: \"Ping\", readOnlyHint: true, idempotentHint: true, openWorldHint: false },\n },\n async (args: Record<string, unknown>): Promise<CallToolResult> => {\n const { message } = pingSchema.parse(args);\n const providers = available.length > 0 ? available.join(\", \") : \"none\";\n const text = message || `Pong from @ask-llm/mcp! Available providers: ${providers}`;\n return { content: [{ type: \"text\", text }], isError: false };\n },\n );\n\n const usageTool = createUsageStatsTool(sessionUsage);\n const usageOutputShape = usageTool.outputSchema\n ? (usageTool.outputSchema as z.ZodObject<z.ZodRawShape>).shape\n : undefined;\n server.registerTool(\n usageTool.name,\n {\n description: usageTool.description,\n inputSchema: {},\n ...(usageOutputShape ? { outputSchema: usageOutputShape } : {}),\n annotations: usageTool.annotations,\n },\n async (): Promise<CallToolResult> => {\n const result = await usageTool.execute({});\n if (typeof result === \"string\") {\n return { content: [{ type: \"text\", text: result }], isError: false };\n }\n return {\n content: [{ type: \"text\", text: result.text }],\n structuredContent: result.structuredContent,\n isError: false,\n };\n },\n );\n registerSessionUsageResource(server, sessionUsage);\n\n const multiLlmInputSchema = buildMultiLlmInputSchema(available.length > 0 ? available : getEligibleProviderKeys());\n server.registerTool(\n \"multi-llm\",\n {\n description:\n \"Dispatch the same prompt to multiple LLM providers in parallel and return all responses in one structured payload. Use when you want to compare answers across Codex, Claude, Antigravity, Ollama, and Gemini, or when you want a multi-provider sanity check on a question. Returns per-provider success/failure, response text, model, sessionId, and token usage. Each call is fresh — no session continuity (use ask-llm for individual session-bearing calls).\",\n inputSchema: multiLlmInputSchema.shape,\n outputSchema: (multiLlmReportSchema as z.ZodObject<z.ZodRawShape>).shape,\n annotations: {\n title: \"Multi-LLM Parallel Dispatch\",\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: true,\n },\n },\n async (args: Record<string, unknown>): Promise<CallToolResult> => {\n const { prompt, providers: requestedProviders } = multiLlmInputSchema.parse(args) as {\n prompt: string;\n providers?: string[];\n };\n const providers = requestedProviders && requestedProviders.length > 0 ? requestedProviders : available;\n Logger.toolInvocation(\"multi-llm\", { prompt: prompt.slice(0, 80), providers });\n\n const report = await dispatchMultiLlm({\n prompt,\n providers,\n getExecutor: (name) => loadedExecutors.get(name),\n recordUsage: (stats) => sessionUsage.record(stats),\n });\n\n return {\n content: [{ type: \"text\", text: formatMultiLlmReport(report) }],\n structuredContent: report as unknown as Record<string, unknown>,\n isError: false,\n };\n },\n );\n\n const diagnoseSpecs = await buildProviderSpecs();\n const diagnoseTool = createDiagnoseTool(diagnoseSpecs);\n const diagnoseOutputShape = diagnoseTool.outputSchema\n ? (diagnoseTool.outputSchema as z.ZodObject<z.ZodRawShape>).shape\n : undefined;\n server.registerTool(\n diagnoseTool.name,\n {\n description: diagnoseTool.description,\n inputSchema: {},\n ...(diagnoseOutputShape ? { outputSchema: diagnoseOutputShape } : {}),\n annotations: diagnoseTool.annotations,\n },\n async (): Promise<CallToolResult> => {\n const result = await diagnoseTool.execute({});\n if (typeof result === \"string\") {\n return { content: [{ type: \"text\", text: result }], isError: false };\n }\n return {\n content: [{ type: \"text\", text: result.text }],\n structuredContent: result.structuredContent,\n isError: false,\n };\n },\n );\n\n Logger.warn(`@ask-llm/mcp v${version} — 5 tools, ${available.length} provider(s): ${available.join(\", \") || \"none\"}`);\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n Logger.debug(\"@ask-llm/mcp listening on stdio\");\n}\n"],"mappings":";;;;;;;;AAuBA,MAAM,mBAAmB,EAAE,OAAO;CAChC,UAAU,EAAE,KAAK,SAAS;CAC1B,OAAO,EAAE,OAAO;CAChB,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;CACjC,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS;CAClC,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS;CAClC,gBAAgB,EAAE,OAAO,CAAC,CAAC,SAAS;CACpC,YAAY,EAAE,OAAO;CACrB,UAAU,EAAE,QAAQ;AACtB,CAAC;AAED,MAAM,uBAAuB,EAAE,OAAO;CACpC,UAAU,EAAE,OAAO;CACnB,IAAI,EAAE,QAAQ;CACd,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;CAC9B,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;CAC3B,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;CAC/B,OAAO,iBAAiB,SAAS;CACjC,YAAY,EAAE,OAAO;CACrB,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;AAC7B,CAAC;AAED,MAAa,uBAAuB,EAAE,OAAO;CAC3C,cAAc,EAAE,OAAO;CACvB,iBAAiB,EAAE,OAAO;CAC1B,cAAc,EAAE,OAAO;CACvB,cAAc,EAAE,OAAO;CACvB,SAAS,EAAE,MAAM,oBAAoB;AACvC,CAAC;AASD,eAAsB,iBAAiB,MAAgD;CACrF,MAAM,gCAAe,IAAI,KAAK,EAAA,CAAE,YAAY;CAC5C,MAAM,YAAY,KAAK,IAAI;CAE3B,MAAM,aAAa,KAAK,UAAU,IAAI,OAAO,aAAsC;EACjF,MAAM,YAAY,KAAK,IAAI;EAC3B,MAAM,WAAW,KAAK,YAAY,QAAQ;EAC1C,IAAI,CAAC,UACH,OAAO;GACL;GACA,IAAI;GACJ,OAAO,aAAa,SAAS;GAC7B,YAAY,KAAK,IAAI,IAAI;EAC3B;EAEF,IAAI;GACF,MAAM,SAAS,MAAM,SAAS,EAAE,QAAQ,KAAK,OAAO,CAAC;GACrD,IAAI,OAAO,OAAO,KAAK,cAAc,OAAO,KAAK;GACjD,OAAO;IACL;IACA,IAAI;IACJ,UAAU,OAAO;IACjB,OAAO,OAAO,OAAO,SAAS,OAAO;IACrC,WAAW,OAAO,aAAa,OAAO;IACtC,OAAO,OAAO;IACd,YAAY,KAAK,IAAI,IAAI;GAC3B;EACF,SAAS,KAAK;GAEZ,OAAO;IACL;IACA,IAAI;IACJ,OAJU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;IAKzD,YAAY,KAAK,IAAI,IAAI;GAC3B;EACF;CACF,CAAC;CAED,MAAM,UAAU,MAAM,QAAQ,IAAI,UAAU;CAC5C,MAAM,kBAAkB,KAAK,IAAI,IAAI;CACrC,MAAM,eAAe,QAAQ,QAAQ,MAAM,EAAE,EAAE,CAAC,CAAC;CAGjD,OAAO;EAAE;EAAc;EAAiB;EAAc,cAFjC,QAAQ,SAAS;EAE8B;CAAQ;AAC9E;AAEA,SAAgB,qBAAqB,QAAgC;CAWnE,OAAO,CAAC,2BAVkC,OAAO,aAAa,GAAG,OAAO,QAAQ,OAAO,eAAe,OAAO,kBAAkB,IAAA,CAAM,QAAQ,CAAC,EAAE,aAUhI,GATC,OAAO,QAAQ,KAAK,MAAM;EACzC,MAAM,SAAS,EAAE,KAAK,MAAM;EAC5B,MAAM,cAAc,IAAI,EAAE,aAAa,IAAA,CAAM,QAAQ,CAAC,EAAE;EACxD,MAAM,WAAW,EAAE,QAAQ,MAAM,EAAE,UAAU;EAC7C,IAAI,EAAE,IACJ,OAAO,SAAS,EAAE,SAAS,GAAG,OAAO,IAAI,cAAc,SAAS,OAAO,EAAE,YAAY;EAEvF,OAAO,SAAS,EAAE,SAAS,GAAG,OAAO,IAAI,YAAY,mBAAmB,EAAE,SAAS;CACrF,CAC0B,CAAC,CAAC,CAAC,KAAK,IAAI;AACxC;AAEA,SAAgB,yBAAyB,oBAA0D;CACjG,MAAM,eAAe,mBAAmB,SAAS,IAAI,qBAAqB,CAAC,GAAG,SAAS;CACvF,OAAO,EAAE,OAAO;EACd,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAM,CAAC,CAAC,SAAS,2DAA2D;EAC1G,WAAW,EACR,MAAM,EAAE,KAAK,YAAqC,CAAC,CAAC,CACpD,IAAI,CAAC,CAAC,CACN,SAAS,CAAC,CACV,SACC,8CAA8C,aAAa,KAAK,IAAI,EAAE,uCACxE;CACJ,CAAC;AACH;;;AChIA,MAAM,gBAAgB,UAAU,QAAQ;AACxC,MAAM,aAAa,QAAQ,aAAa;AAExC,eAAsB,mBAAmB,SAAmC;CAC1E,IAAI;EAEF,MAAM,cADQ,aAAa,UAAU,SACV,CAAC,OAAO,GAAG;GAAE,SAAS;GAAM,KAAK,YAAY;EAAE,CAAC;EAC3E,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;ACZA,eAAsB,qBAA8C;CAClE,MAAM,QAAwB,CAAC;CAC/B,KAAK,MAAM,CAAC,KAAK,WAAW,OAAO,QAAQA,WAAS,GAAG;EACrD,IAAI,CAAC,mBAAmB,MAAM,GAAG;EAEjC,MAAM,cAAc,cAAc;EAClC,IAAI;EACJ,IAAI,OAAO,sBAAsB,OAAO,gBAAgB;GACtD,MAAM,aAAa,OAAO;GAC1B,MAAM,SAAS,OAAO;GACtB,oBAAoB,YAAY;IAC9B,IAAI;KAEF,MAAM,MAAK,MADQ,OAAO,YAAA,CACX;KACf,IAAI,OAAO,OAAO,YAAY,OAAO;KACrC,OAAO,MAAM,GAAG;IAClB,QAAQ;KACN,OAAO;IACT;GACF;EACF;EACA,IAAI;EACJ,IAAI,OAAO,gBAAgB,OAAO,UAAU;GAC1C,MAAM,aAAa,OAAO;GAC1B,MAAM,SAAS,OAAO;GACtB,SAAS,OAAO,QAAQ;IACtB,IAAI;KAEF,MAAM,MAAK,MADQ,OAAO,YAAA,CACX;KACf,IAAI,OAAO,OAAO,YAAY,OAAO,KAAA;KACrC,OAAO,MAAM,GAAG,GAAG;IACrB,QAAQ;KACN;IACF;GACF;EACF;EAEA,MAAM,KAAK;GACT;GACA,MAAM,OAAO;GACb,SAAS,OAAO;GAChB;GACA;GACA;EACF,CAAC;CACH;CACA,OAAO;AACT;;;AC3BA,SAAS,kBAAqD;CAC5D,IAAI;EAEF,OADgB,cAAc,OAAO,KAAK,GAC7B,CAAC,CAAC,iBAAiB;CAClC,QAAQ;EACN,OAAO;GAAE,MAAM;GAAgB,SAAS;EAAQ;CAClD;AACF;AA4CA,MAAM,kCAAkB,IAAI,IAAwB;AAEpD,SAAgB,kBAAkB,MAAsC;CACtE,OAAO,gBAAgB,IAAI,IAAI;AACjC;AAEA,eAAsB,kBAA2C;CAC/D,MAAM,YAAsB,CAAC;CAC7B,MAAM,UAAoB,CAAC;CAE3B,MAAM,SAAS,MAAM,QAAQ,IAC3B,OAAO,QAAQC,WAAS,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,cAAc;EACvD,IAAI;EACJ,MAAM,WAAW,QAAQ,SAAS,sBAAsB,QAAQ,IAAI,SAAS,mBAAmB;EAChG,IAAI,UACF,QAAQ;OACH,IAAI,SAAS,sBAAsB,SAAS,gBACjD,IAAI;GAEF,QAAQ,OAAO,MADG,OAAO,SAAS,oBAAA,CACf,SAAS,eAAe,CAA4B;EACzE,QAAQ;GACN,QAAQ;EACV;OAEA,QAAQ,MAAM,mBAAmB,SAAS,OAAO;EAEnD,OAAO;GAAE;GAAK;GAAU;GAAO;EAAS;CAC1C,CAAC,CACH;CAEA,KAAK,MAAM,EAAE,KAAK,UAAU,OAAO,cAAc,QAC/C,IAAI,OACF,IAAI;EACF,MAAM,MAAM,MAAM,OAAO,SAAS;EAClC,gBAAgB,IAAI,KAAK,IAAI,SAAS,WAAyB;EAC/D,UAAU,KAAK,GAAG;EAClB,OAAO,KAAK,YAAY,SAAS,KAAK,IAAI,SAAS,QAAQ,cAAc;CAC3E,SAAS,KAAK;EACZ,QAAQ,KAAK,GAAG;EAChB,OAAO,MAAM,YAAY,SAAS,KAAK,oBAAoB,GAAG;CAChE;MACK;EACL,QAAQ,KAAK,GAAG;EAChB,IAAI,UAAU;GACZ,OAAO,KACL,YAAY,SAAS,KAAK,IAAI,SAAS,QAAQ,uBAAuB,SAAS,mBAAmB,iCACpG;GACA;EACF;EACA,MAAM,OAAO,cAAc,QAAQ;EACnC,OAAO,KAAK,YAAY,SAAS,KAAK,IAAI,SAAS,QAAQ,eAAe,OAAO,cAAc,SAAS,IAAI;CAC9G;CAGF,IAAI,UAAU,WAAW,GAAG;EAC1B,OAAO,KAAK,sEAAsE;EAClF,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,aAAa,GACpD,OAAO,KAAK,KAAKA,YAAU,IAAI,EAAE,QAAQ,IAAI,IAAI,MAAM;CAE3D;CAEA,OAAO;EAAE;EAAW;CAAQ;AAC9B;AAEA,SAAS,kBAAkB,oBAA8B;CACvD,MAAM,eAAe,mBAAmB,SAAS,IAAI,qBAAqB,wBAAwB;CAClG,MAAM,uBAAuB,aAC1B,KAAK,MAAM;EACV,MAAM,IAAIA,YAAU;EACpB,OAAO,IAAI,IAAI,EAAE,KAAK,EAAE,KAAK,mBAAmB,EAAE,aAAa,KAAK;CACtE,CAAC,CAAC,CACD,KAAK,IAAI;CAEZ,OAAO,EAAE,OAAO;EACd,UAAU,EACP,KAAK,YAAqC,CAAC,CAC3C,SAAS,yCAAyC,sBAAsB;EAC3E,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAM,CAAC,CAAC,SAAS,6DAA6D;EAC5G,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,iDAAiD;EACvF,WAAW,EACR,OAAO,CAAC,CACR,SAAS,CAAC,CACV,SACC,4PACF;CACJ,CAAC;AACH;AAIA,MAAM,qBAAqB,OAAe;CACxC,GAAG,GAAG;CACN,GAAG,GAAG;CACN,GAAG,GAAG;CACN,GAAG,GAAG;AACR;AAEA,eAAsB,cAAc;CAClC,OAAO,MAAM,mBAAmB;CAChC,OAAO,iBAAiB;CACxB,MAAM,EAAE,MAAM,YAAY,gBAAgB;CAC1C,MAAM,EAAE,cAAc,MAAM,gBAAgB;CAE5C,MAAM,SAAS,IAAI,UAAU;EAAE;EAAM;CAAQ,CAAC;CAC9C,MAAM,eAAe,mBAAmB;CAExC,MAAM,eAAe,kBAAkB,SAAS;CAEhD,OAAO,aACL,WACA;EACE,aACE;EACF,aAAa,aAAa;EAC1B,cAAe,kBAAiD;EAChE,aAAa;GAAE,OAAO;GAAW,cAAc;GAAO,iBAAiB;GAAO,eAAe;EAAK;CACpG,GACA,OAAO,MAA+B,UAA8C;EAClF,MAAM,WAAW,sBAAsB,WAAW,OAAO,kBAAkB,SAAS,CAAC;EACrF,IAAI;GACF,MAAM,EAAE,UAAU,QAAQ,OAAO,cAAc,aAAa,MAAM,IAAI;GACtE,OAAO,eAAe,WAAW,IAAI;GAErC,MAAM,WAAW,gBAAgB,IAAI,QAAQ;GAC7C,IAAI,CAAC,UAAU;IACb,MAAM,OAAO,cAAc,aAAa;IACxC,MAAM,IAAI,MACR,aAAa,SAAS,sBAAsB,OAAO,YAAY,SAAS,uCAC1E;GACF;GAEA,MAAM,SAAS,MAAM,SAAS;IAC5B;IACA;IACA;IACA,aAAa,WAAW;KACtB,SAAS,aAAa,MAAM;IAC9B;GACF,CAAC;GAED,IAAI,OAAO,OAAO,aAAa,OAAO,OAAO,KAAK;GAElD,MAAM,SAAS,KAAK,IAAI;GACxB,MAAM,eAAeA,YAAU,SAAS,EAAE,QAAQ;GAClD,MAAM,oBAAoB,OAAO,aAAa,OAAO;GACrD,MAAM,SAAS,OAAO,YAClB,oBAAoB,OAAO,UAAU,KACrC,OAAO,WACL,mBAAmB,OAAO,SAAS,KACnC;GACN,MAAM,aAA0B;IACpB;IACV,UAAU,OAAO;IACjB,OAAO,OAAO,OAAO,SAAS,OAAO,SAAS,SAASA,YAAU,SAAS,EAAE,gBAAgB;IAC5F,WAAW;IACX,OAAO,OAAO;GAChB;GACA,OAAO;IACL,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM,GAAG,aAAa,cAAc,OAAO,WAAW;IAAS,CAAC;IAC1F,mBAAmB;IACnB,SAAS;GACX;EACF,SAAS,OAAO;GACd,MAAM,SAAS,KAAK,KAAK;GACzB,MAAM,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GACjE,OAAO,MAAM,kBAAkB,KAAK;GACpC,OAAO;IAAE,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM,UAAU;IAAM,CAAC;IAAG,SAAS;GAAK;EAC7E;CACF,CACF;CAEA,MAAM,aAAa,EAAE,OAAO,EAC1B,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,+CAA+C,EACzF,CAAC;CAED,OAAO,aACL,QACA;EACE,aAAa;EACb,aAAa,WAAW;EACxB,aAAa;GAAE,OAAO;GAAQ,cAAc;GAAM,gBAAgB;GAAM,eAAe;EAAM;CAC/F,GACA,OAAO,SAA2D;EAChE,MAAM,EAAE,YAAY,WAAW,MAAM,IAAI;EACzC,MAAM,YAAY,UAAU,SAAS,IAAI,UAAU,KAAK,IAAI,IAAI;EAEhE,OAAO;GAAE,SAAS,CAAC;IAAE,MAAM;IAAQ,MADtB,WAAW,gDAAgD;GAChC,CAAC;GAAG,SAAS;EAAM;CAC7D,CACF;CAEA,MAAM,YAAY,qBAAqB,YAAY;CACnD,MAAM,mBAAmB,UAAU,eAC9B,UAAU,aAA4C,QACvD,KAAA;CACJ,OAAO,aACL,UAAU,MACV;EACE,aAAa,UAAU;EACvB,aAAa,CAAC;EACd,GAAI,mBAAmB,EAAE,cAAc,iBAAiB,IAAI,CAAC;EAC7D,aAAa,UAAU;CACzB,GACA,YAAqC;EACnC,MAAM,SAAS,MAAM,UAAU,QAAQ,CAAC,CAAC;EACzC,IAAI,OAAO,WAAW,UACpB,OAAO;GAAE,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM;GAAO,CAAC;GAAG,SAAS;EAAM;EAErE,OAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,OAAO;GAAK,CAAC;GAC7C,mBAAmB,OAAO;GAC1B,SAAS;EACX;CACF,CACF;CACA,6BAA6B,QAAQ,YAAY;CAEjD,MAAM,sBAAsB,yBAAyB,UAAU,SAAS,IAAI,YAAY,wBAAwB,CAAC;CACjH,OAAO,aACL,aACA;EACE,aACE;EACF,aAAa,oBAAoB;EACjC,cAAe,qBAAoD;EACnE,aAAa;GACX,OAAO;GACP,cAAc;GACd,iBAAiB;GACjB,gBAAgB;GAChB,eAAe;EACjB;CACF,GACA,OAAO,SAA2D;EAChE,MAAM,EAAE,QAAQ,WAAW,uBAAuB,oBAAoB,MAAM,IAAI;EAIhF,MAAM,YAAY,sBAAsB,mBAAmB,SAAS,IAAI,qBAAqB;EAC7F,OAAO,eAAe,aAAa;GAAE,QAAQ,OAAO,MAAM,GAAG,EAAE;GAAG;EAAU,CAAC;EAE7E,MAAM,SAAS,MAAM,iBAAiB;GACpC;GACA;GACA,cAAc,SAAS,gBAAgB,IAAI,IAAI;GAC/C,cAAc,UAAU,aAAa,OAAO,KAAK;EACnD,CAAC;EAED,OAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,qBAAqB,MAAM;GAAE,CAAC;GAC9D,mBAAmB;GACnB,SAAS;EACX;CACF,CACF;CAGA,MAAM,eAAe,mBAAmB,MADZ,mBAAmB,CACM;CACrD,MAAM,sBAAsB,aAAa,eACpC,aAAa,aAA4C,QAC1D,KAAA;CACJ,OAAO,aACL,aAAa,MACb;EACE,aAAa,aAAa;EAC1B,aAAa,CAAC;EACd,GAAI,sBAAsB,EAAE,cAAc,oBAAoB,IAAI,CAAC;EACnE,aAAa,aAAa;CAC5B,GACA,YAAqC;EACnC,MAAM,SAAS,MAAM,aAAa,QAAQ,CAAC,CAAC;EAC5C,IAAI,OAAO,WAAW,UACpB,OAAO;GAAE,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM;GAAO,CAAC;GAAG,SAAS;EAAM;EAErE,OAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,OAAO;GAAK,CAAC;GAC7C,mBAAmB,OAAO;GAC1B,SAAS;EACX;CACF,CACF;CAEA,OAAO,KAAK,iBAAiB,QAAQ,cAAc,UAAU,OAAO,gBAAgB,UAAU,KAAK,IAAI,KAAK,QAAQ;CAEpH,MAAM,YAAY,IAAI,qBAAqB;CAC3C,MAAM,OAAO,QAAQ,SAAS;CAC9B,OAAO,MAAM,iCAAiC;AAChD"} |
+104
-2
| #!/usr/bin/env node | ||
| import { a as PROVIDERS, c as formatDiagnosticReport, i as buildProviderSpecs, l as runDiagnostics, n as getLoadedExecutor, o as createSessionUsage, r as startServer, s as formatSessionUsage, t as detectProviders, u as Logger } from "./src-DYUWMdDM.js"; | ||
| import { _ as runDiagnostics, f as createSessionUsage, g as formatDiagnosticReport, h as machineRequestSchema, n as machineJsonSchemaBundle, o as PROVIDERS, p as formatSessionUsage, r as runMachineRequest, y as Logger } from "./machine-D6srHmWi.js"; | ||
| import { i as buildProviderSpecs, n as getLoadedExecutor, r as startServer, t as detectProviders } from "./src-DUet31oX.js"; | ||
| import * as readline from "node:readline/promises"; | ||
@@ -167,2 +168,95 @@ //#region src/repl.ts | ||
| //#region src/cli.ts | ||
| const MAX_MACHINE_STDIN_BYTES = 2 * 1024 * 1024; | ||
| var MachineInputError = class extends Error {}; | ||
| async function readStdin() { | ||
| const chunks = []; | ||
| let size = 0; | ||
| let oversized = false; | ||
| for await (const chunk of process.stdin) { | ||
| const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); | ||
| size += buffer.byteLength; | ||
| if (size > MAX_MACHINE_STDIN_BYTES) oversized = true; | ||
| if (!oversized) chunks.push(buffer); | ||
| } | ||
| if (size === 0 || oversized) throw new MachineInputError(); | ||
| return Buffer.concat(chunks).toString("utf8"); | ||
| } | ||
| function parseMachineInput(raw) { | ||
| try { | ||
| return JSON.parse(raw); | ||
| } catch { | ||
| throw new MachineInputError(); | ||
| } | ||
| } | ||
| async function loadMachineExecutor(provider) { | ||
| const spec = PROVIDERS[provider]; | ||
| if (!spec) return void 0; | ||
| try { | ||
| const configuredOverride = process.env.ASK_LLM_MACHINE_EXECUTOR_MODULE; | ||
| const executor = (await (process.env.ASK_LLM_MACHINE_ALLOW_EXECUTOR_OVERRIDE === "1" && configuredOverride ? import(configuredOverride) : import(spec.executorModule)))[spec.executorFn]; | ||
| return typeof executor === "function" ? executor : void 0; | ||
| } catch { | ||
| Logger.warn(`Machine provider ${provider} could not be loaded`); | ||
| return; | ||
| } | ||
| } | ||
| async function withDiagnosticsOnStderr(run) { | ||
| const log = console.log; | ||
| const info = console.info; | ||
| console.log = console.error; | ||
| console.info = console.error; | ||
| try { | ||
| return await run(); | ||
| } finally { | ||
| console.log = log; | ||
| console.info = info; | ||
| } | ||
| } | ||
| function writeMachineDocument(document) { | ||
| return new Promise((resolve, reject) => { | ||
| const output = process.stdout; | ||
| let serialized; | ||
| let settled = false; | ||
| let callbackCheck; | ||
| function finish(error) { | ||
| if (settled) return; | ||
| settled = true; | ||
| if (callbackCheck) clearImmediate(callbackCheck); | ||
| output.off("error", handleError); | ||
| if (error === void 0 || error === null) resolve(); | ||
| else reject(error); | ||
| } | ||
| function handleError(error) { | ||
| finish(error); | ||
| } | ||
| function handleWrite(error) { | ||
| if (callbackCheck) return; | ||
| callbackCheck = setImmediate(() => finish(error)); | ||
| } | ||
| try { | ||
| serialized = `${JSON.stringify(document)}\n`; | ||
| output.once("error", handleError); | ||
| output.write(serialized, handleWrite); | ||
| } catch (error) { | ||
| finish(error); | ||
| } | ||
| }); | ||
| } | ||
| async function runMachineCli() { | ||
| try { | ||
| if (process.argv.length > 3) throw new MachineInputError(); | ||
| const input = parseMachineInput(await readStdin()); | ||
| const request = machineRequestSchema.safeParse(input); | ||
| if (!request.success) throw new MachineInputError(); | ||
| await writeMachineDocument(await withDiagnosticsOnStderr(() => runMachineRequest(request.data, { | ||
| loadExecutor: loadMachineExecutor, | ||
| now: Date.now | ||
| }))); | ||
| return 0; | ||
| } catch (error) { | ||
| if (!(error instanceof MachineInputError)) throw error; | ||
| process.stderr.write("machine input rejected: expected one valid JSON request on stdin\n"); | ||
| return 2; | ||
| } | ||
| } | ||
| async function runDoctor(jsonOutput) { | ||
@@ -175,3 +269,11 @@ const report = await runDiagnostics(await buildProviderSpecs()); | ||
| const subcommand = process.argv[2]; | ||
| if (subcommand === "doctor") runDoctor(process.argv.includes("--json")).then((code) => process.exit(code), (error) => { | ||
| if (subcommand === "machine-schema") writeMachineDocument(machineJsonSchemaBundle()).then(() => process.exit(0), () => { | ||
| process.stderr.write("machine schema failed\n"); | ||
| process.exit(3); | ||
| }); | ||
| else if (subcommand === "machine") runMachineCli().then((code) => process.exit(code), () => { | ||
| process.stderr.write("machine dispatcher failed\n"); | ||
| process.exit(3); | ||
| }); | ||
| else if (subcommand === "doctor") runDoctor(process.argv.includes("--json")).then((code) => process.exit(code), (error) => { | ||
| Logger.error("doctor failed:", error); | ||
@@ -178,0 +280,0 @@ process.exit(1); |
+1
-1
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"cli.js","names":[],"sources":["../src/repl.ts","../src/cli.ts"],"sourcesContent":["import * as readline from \"node:readline/promises\";\nimport { createSessionUsage, formatSessionUsage, Logger, type SessionUsage, type UsageStats } from \"@ask-llm/shared\";\nimport { PROVIDERS } from \"./constants.js\";\nimport { detectProviders, type ExecutorFn, getLoadedExecutor } from \"./index.js\";\n\nexport interface ReplState {\n currentProvider: string;\n sessions: Map<string, string>;\n available: string[];\n sessionUsage: SessionUsage;\n}\n\nexport interface SlashResult {\n exit?: boolean;\n message?: string;\n cleared?: boolean;\n}\n\nconst SLASH_COMMANDS: Array<[string, string]> = [\n [\"/help\", \"Show this help\"],\n [\"/provider <name>\", `Switch active provider (e.g. ${Object.keys(PROVIDERS).join(\", \")})`],\n [\"/providers\", \"List available providers and which one is active\"],\n [\"/new\", \"Drop the current provider's session — start fresh\"],\n [\"/session <id>\", \"Resume a specific session id for the current provider\"],\n [\"/sessions\", \"Show the session id currently held for each provider\"],\n [\"/usage\", \"Show in-memory session usage stats (tokens, calls, fallbacks)\"],\n [\"/clear\", \"Clear the screen\"],\n [\"/quit\", \"Exit (also: /exit)\"],\n];\n\nexport function formatHelp(): string {\n const lines = [\"Slash commands:\"];\n for (const [cmd, desc] of SLASH_COMMANDS) {\n lines.push(` ${cmd.padEnd(22)} ${desc}`);\n }\n return lines.join(\"\\n\");\n}\n\nexport function formatBanner(state: ReplState): string {\n const sid = state.sessions.get(state.currentProvider);\n const sessionTag = sid ? ` (session ${sid.slice(0, 8)}…)` : \" (no session)\";\n return [\n \"ask-llm REPL\",\n ` available: ${state.available.join(\", \")}`,\n ` active: ${state.currentProvider}${sessionTag}`,\n \" type /help for commands, /quit to exit\",\n \"\",\n ].join(\"\\n\");\n}\n\nfunction describeSessions(state: ReplState): string {\n if (state.sessions.size === 0) return \"No active sessions.\";\n const lines = [\"Active sessions:\"];\n for (const provider of state.available) {\n const sid = state.sessions.get(provider);\n lines.push(` ${provider.padEnd(8)} ${sid ?? \"—\"}`);\n }\n return lines.join(\"\\n\");\n}\n\nexport function handleSlash(line: string, state: ReplState): SlashResult {\n const parts = line.trim().split(/\\s+/);\n const cmd = parts[0];\n const arg = parts.slice(1).join(\" \").trim();\n\n switch (cmd) {\n case \"/help\":\n return { message: formatHelp() };\n case \"/provider\": {\n if (!arg) return { message: `Usage: /provider <name>. Available: ${state.available.join(\", \")}` };\n if (!state.available.includes(arg)) {\n return { message: `Provider \"${arg}\" not available. Available: ${state.available.join(\", \")}` };\n }\n state.currentProvider = arg;\n const sid = state.sessions.get(arg);\n return { message: `Switched to ${arg}${sid ? ` (resuming session ${sid.slice(0, 8)}…)` : \" (no session)\"}` };\n }\n case \"/providers\":\n return {\n message: state.available\n .map(\n (p) =>\n ` ${p === state.currentProvider ? \"*\" : \" \"} ${p} ${PROVIDERS[p]?.name ? `(${PROVIDERS[p]?.name})` : \"\"}`,\n )\n .join(\"\\n\"),\n };\n case \"/new\": {\n const had = state.sessions.delete(state.currentProvider);\n return {\n message: had\n ? `Cleared session for ${state.currentProvider}`\n : `${state.currentProvider} already had no session`,\n };\n }\n case \"/session\": {\n if (!arg) return { message: \"Usage: /session <id>\" };\n state.sessions.set(state.currentProvider, arg);\n return { message: `Resuming session ${arg.slice(0, 8)}… for ${state.currentProvider}` };\n }\n case \"/sessions\":\n return { message: describeSessions(state) };\n case \"/usage\":\n return { message: formatSessionUsage(state.sessionUsage.snapshot()) };\n case \"/clear\":\n return { cleared: true };\n case \"/quit\":\n case \"/exit\":\n return { exit: true };\n default:\n return { message: `Unknown command: ${cmd}. Type /help.` };\n }\n}\n\nexport async function dispatchPrompt(\n prompt: string,\n state: ReplState,\n out: NodeJS.WritableStream,\n executorOverride?: ExecutorFn,\n): Promise<{ ok: boolean; error?: string; usage?: UsageStats }> {\n const executor = executorOverride ?? getLoadedExecutor(state.currentProvider);\n if (!executor) {\n return { ok: false, error: `Provider ${state.currentProvider} is not loaded` };\n }\n\n const sessionId = state.sessions.get(state.currentProvider);\n out.write(`\\n[${state.currentProvider}]\\n`);\n\n let sawAnyChunk = false;\n try {\n const result = await executor({\n prompt,\n sessionId,\n onProgress: (chunk) => {\n sawAnyChunk = true;\n out.write(chunk);\n },\n });\n\n if (!sawAnyChunk) {\n out.write(result.response);\n }\n out.write(\"\\n\");\n\n const newSession = result.sessionId ?? result.threadId;\n if (newSession) state.sessions.set(state.currentProvider, newSession);\n\n if (result.usage) state.sessionUsage.record(result.usage);\n\n return { ok: true, usage: result.usage };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n out.write(`\\n[error] ${msg}\\n`);\n return { ok: false, error: msg };\n }\n}\n\n// Resolves to the user's line, or null on EOF / stream close. node:readline's\n// promises API leaves question() pending forever when stdin closes (Ctrl-D or a\n// piped EOF), so we race it against the interface's \"close\" event — otherwise\n// the REPL hangs instead of exiting. The \"close\" listener is removed when the\n// question resolves normally, so repeated prompts don't leak listeners.\nfunction questionOrEof(rl: readline.Interface, prompt: string): Promise<string | null> {\n return new Promise((resolve) => {\n const onClose = () => resolve(null);\n rl.once(\"close\", onClose);\n rl.question(prompt).then(\n (answer) => {\n rl.off(\"close\", onClose);\n resolve(answer);\n },\n () => {\n rl.off(\"close\", onClose);\n resolve(null);\n },\n );\n });\n}\n\nexport async function runReplLoop(rl: readline.Interface, state: ReplState, out: NodeJS.WritableStream): Promise<void> {\n // `closed` is NOT redundant with questionOrEof's null-on-close return: if the\n // stream closes while we're inside dispatchPrompt (between prompts),\n // questionOrEof's own \"close\" listener isn't registered yet, so without this\n // flag the loop would re-enter questionOrEof on a closed interface and hang.\n let closed = false;\n rl.once(\"close\", () => {\n closed = true;\n });\n\n while (!closed) {\n const line = await questionOrEof(rl, `${state.currentProvider}> `);\n if (line === null) break;\n\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n if (trimmed.startsWith(\"/\")) {\n const res = handleSlash(trimmed, state);\n if (res.cleared) out.write(\"\\x1b[2J\\x1b[H\");\n if (res.message) out.write(`${res.message}\\n`);\n if (res.exit) break;\n continue;\n }\n\n await dispatchPrompt(trimmed, state, out);\n }\n}\n\nexport async function startRepl(): Promise<number> {\n Logger.checkNodeVersion();\n process.stdout.write(\"Detecting providers...\\n\");\n const { available } = await detectProviders();\n\n if (available.length === 0) {\n process.stdout.write(\"\\nNo providers available. Run `npx @ask-llm/mcp doctor` for diagnostics.\\n\");\n return 1;\n }\n\n const state: ReplState = {\n currentProvider: available[0],\n sessions: new Map(),\n available,\n sessionUsage: createSessionUsage(),\n };\n\n process.stdout.write(`\\n${formatBanner(state)}`);\n\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });\n\n try {\n await runReplLoop(rl, state, process.stdout);\n } finally {\n rl.close();\n }\n process.stdout.write(\"\\nbye.\\n\");\n return 0;\n}\n","#!/usr/bin/env node\n\nimport { formatDiagnosticReport, Logger, type ProviderSpec, runDiagnostics } from \"@ask-llm/shared\";\nimport { startServer } from \"./index.js\";\nimport { startRepl } from \"./repl.js\";\nimport { buildProviderSpecs } from \"./utils/providerSpecs.js\";\n\nasync function runDoctor(jsonOutput: boolean): Promise<number> {\n const specs: ProviderSpec[] = await buildProviderSpecs();\n const report = await runDiagnostics(specs);\n\n if (jsonOutput) {\n process.stdout.write(`${JSON.stringify(report, null, 2)}\\n`);\n } else {\n process.stdout.write(formatDiagnosticReport(report));\n }\n\n return report.status === \"error\" ? 1 : 0;\n}\n\nconst subcommand = process.argv[2];\n\nif (subcommand === \"doctor\") {\n const jsonOutput = process.argv.includes(\"--json\");\n runDoctor(jsonOutput).then(\n (code) => process.exit(code),\n (error) => {\n Logger.error(\"doctor failed:\", error);\n process.exit(1);\n },\n );\n} else if (subcommand === \"repl\") {\n startRepl().then(\n (code) => process.exit(code),\n (error) => {\n Logger.error(\"repl failed:\", error);\n process.exit(1);\n },\n );\n} else {\n startServer().catch((error) => {\n Logger.error(\"Fatal error:\", error);\n process.exit(1);\n });\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"cli.js","names":[],"sources":["../src/repl.ts","../src/cli.ts"],"sourcesContent":["import * as readline from \"node:readline/promises\";\nimport { createSessionUsage, formatSessionUsage, Logger, type SessionUsage, type UsageStats } from \"@ask-llm/shared\";\nimport { PROVIDERS } from \"./constants.js\";\nimport { detectProviders, type ExecutorFn, getLoadedExecutor } from \"./index.js\";\n\nexport interface ReplState {\n currentProvider: string;\n sessions: Map<string, string>;\n available: string[];\n sessionUsage: SessionUsage;\n}\n\nexport interface SlashResult {\n exit?: boolean;\n message?: string;\n cleared?: boolean;\n}\n\nconst SLASH_COMMANDS: Array<[string, string]> = [\n [\"/help\", \"Show this help\"],\n [\"/provider <name>\", `Switch active provider (e.g. ${Object.keys(PROVIDERS).join(\", \")})`],\n [\"/providers\", \"List available providers and which one is active\"],\n [\"/new\", \"Drop the current provider's session — start fresh\"],\n [\"/session <id>\", \"Resume a specific session id for the current provider\"],\n [\"/sessions\", \"Show the session id currently held for each provider\"],\n [\"/usage\", \"Show in-memory session usage stats (tokens, calls, fallbacks)\"],\n [\"/clear\", \"Clear the screen\"],\n [\"/quit\", \"Exit (also: /exit)\"],\n];\n\nexport function formatHelp(): string {\n const lines = [\"Slash commands:\"];\n for (const [cmd, desc] of SLASH_COMMANDS) {\n lines.push(` ${cmd.padEnd(22)} ${desc}`);\n }\n return lines.join(\"\\n\");\n}\n\nexport function formatBanner(state: ReplState): string {\n const sid = state.sessions.get(state.currentProvider);\n const sessionTag = sid ? ` (session ${sid.slice(0, 8)}…)` : \" (no session)\";\n return [\n \"ask-llm REPL\",\n ` available: ${state.available.join(\", \")}`,\n ` active: ${state.currentProvider}${sessionTag}`,\n \" type /help for commands, /quit to exit\",\n \"\",\n ].join(\"\\n\");\n}\n\nfunction describeSessions(state: ReplState): string {\n if (state.sessions.size === 0) return \"No active sessions.\";\n const lines = [\"Active sessions:\"];\n for (const provider of state.available) {\n const sid = state.sessions.get(provider);\n lines.push(` ${provider.padEnd(8)} ${sid ?? \"—\"}`);\n }\n return lines.join(\"\\n\");\n}\n\nexport function handleSlash(line: string, state: ReplState): SlashResult {\n const parts = line.trim().split(/\\s+/);\n const cmd = parts[0];\n const arg = parts.slice(1).join(\" \").trim();\n\n switch (cmd) {\n case \"/help\":\n return { message: formatHelp() };\n case \"/provider\": {\n if (!arg) return { message: `Usage: /provider <name>. Available: ${state.available.join(\", \")}` };\n if (!state.available.includes(arg)) {\n return { message: `Provider \"${arg}\" not available. Available: ${state.available.join(\", \")}` };\n }\n state.currentProvider = arg;\n const sid = state.sessions.get(arg);\n return { message: `Switched to ${arg}${sid ? ` (resuming session ${sid.slice(0, 8)}…)` : \" (no session)\"}` };\n }\n case \"/providers\":\n return {\n message: state.available\n .map(\n (p) =>\n ` ${p === state.currentProvider ? \"*\" : \" \"} ${p} ${PROVIDERS[p]?.name ? `(${PROVIDERS[p]?.name})` : \"\"}`,\n )\n .join(\"\\n\"),\n };\n case \"/new\": {\n const had = state.sessions.delete(state.currentProvider);\n return {\n message: had\n ? `Cleared session for ${state.currentProvider}`\n : `${state.currentProvider} already had no session`,\n };\n }\n case \"/session\": {\n if (!arg) return { message: \"Usage: /session <id>\" };\n state.sessions.set(state.currentProvider, arg);\n return { message: `Resuming session ${arg.slice(0, 8)}… for ${state.currentProvider}` };\n }\n case \"/sessions\":\n return { message: describeSessions(state) };\n case \"/usage\":\n return { message: formatSessionUsage(state.sessionUsage.snapshot()) };\n case \"/clear\":\n return { cleared: true };\n case \"/quit\":\n case \"/exit\":\n return { exit: true };\n default:\n return { message: `Unknown command: ${cmd}. Type /help.` };\n }\n}\n\nexport async function dispatchPrompt(\n prompt: string,\n state: ReplState,\n out: NodeJS.WritableStream,\n executorOverride?: ExecutorFn,\n): Promise<{ ok: boolean; error?: string; usage?: UsageStats }> {\n const executor = executorOverride ?? getLoadedExecutor(state.currentProvider);\n if (!executor) {\n return { ok: false, error: `Provider ${state.currentProvider} is not loaded` };\n }\n\n const sessionId = state.sessions.get(state.currentProvider);\n out.write(`\\n[${state.currentProvider}]\\n`);\n\n let sawAnyChunk = false;\n try {\n const result = await executor({\n prompt,\n sessionId,\n onProgress: (chunk) => {\n sawAnyChunk = true;\n out.write(chunk);\n },\n });\n\n if (!sawAnyChunk) {\n out.write(result.response);\n }\n out.write(\"\\n\");\n\n const newSession = result.sessionId ?? result.threadId;\n if (newSession) state.sessions.set(state.currentProvider, newSession);\n\n if (result.usage) state.sessionUsage.record(result.usage);\n\n return { ok: true, usage: result.usage };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n out.write(`\\n[error] ${msg}\\n`);\n return { ok: false, error: msg };\n }\n}\n\n// Resolves to the user's line, or null on EOF / stream close. node:readline's\n// promises API leaves question() pending forever when stdin closes (Ctrl-D or a\n// piped EOF), so we race it against the interface's \"close\" event — otherwise\n// the REPL hangs instead of exiting. The \"close\" listener is removed when the\n// question resolves normally, so repeated prompts don't leak listeners.\nfunction questionOrEof(rl: readline.Interface, prompt: string): Promise<string | null> {\n return new Promise((resolve) => {\n const onClose = () => resolve(null);\n rl.once(\"close\", onClose);\n rl.question(prompt).then(\n (answer) => {\n rl.off(\"close\", onClose);\n resolve(answer);\n },\n () => {\n rl.off(\"close\", onClose);\n resolve(null);\n },\n );\n });\n}\n\nexport async function runReplLoop(rl: readline.Interface, state: ReplState, out: NodeJS.WritableStream): Promise<void> {\n // `closed` is NOT redundant with questionOrEof's null-on-close return: if the\n // stream closes while we're inside dispatchPrompt (between prompts),\n // questionOrEof's own \"close\" listener isn't registered yet, so without this\n // flag the loop would re-enter questionOrEof on a closed interface and hang.\n let closed = false;\n rl.once(\"close\", () => {\n closed = true;\n });\n\n while (!closed) {\n const line = await questionOrEof(rl, `${state.currentProvider}> `);\n if (line === null) break;\n\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n if (trimmed.startsWith(\"/\")) {\n const res = handleSlash(trimmed, state);\n if (res.cleared) out.write(\"\\x1b[2J\\x1b[H\");\n if (res.message) out.write(`${res.message}\\n`);\n if (res.exit) break;\n continue;\n }\n\n await dispatchPrompt(trimmed, state, out);\n }\n}\n\nexport async function startRepl(): Promise<number> {\n Logger.checkNodeVersion();\n process.stdout.write(\"Detecting providers...\\n\");\n const { available } = await detectProviders();\n\n if (available.length === 0) {\n process.stdout.write(\"\\nNo providers available. Run `npx @ask-llm/mcp doctor` for diagnostics.\\n\");\n return 1;\n }\n\n const state: ReplState = {\n currentProvider: available[0],\n sessions: new Map(),\n available,\n sessionUsage: createSessionUsage(),\n };\n\n process.stdout.write(`\\n${formatBanner(state)}`);\n\n const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });\n\n try {\n await runReplLoop(rl, state, process.stdout);\n } finally {\n rl.close();\n }\n process.stdout.write(\"\\nbye.\\n\");\n return 0;\n}\n","#!/usr/bin/env node\n\nimport {\n formatDiagnosticReport,\n Logger,\n type MachineProvider,\n machineRequestSchema,\n type ProviderSpec,\n runDiagnostics,\n} from \"@ask-llm/shared\";\nimport { PROVIDERS } from \"./constants.js\";\nimport { type ExecutorFn, startServer } from \"./index.js\";\nimport { machineJsonSchemaBundle, runMachineRequest } from \"./machine.js\";\nimport { startRepl } from \"./repl.js\";\nimport { buildProviderSpecs } from \"./utils/providerSpecs.js\";\n\nconst MAX_MACHINE_STDIN_BYTES = 2 * 1024 * 1024;\n\nclass MachineInputError extends Error {}\n\nasync function readStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n let size = 0;\n let oversized = false;\n\n for await (const chunk of process.stdin) {\n const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n size += buffer.byteLength;\n if (size > MAX_MACHINE_STDIN_BYTES) oversized = true;\n if (!oversized) chunks.push(buffer);\n }\n\n if (size === 0 || oversized) throw new MachineInputError();\n return Buffer.concat(chunks).toString(\"utf8\");\n}\n\nfunction parseMachineInput(raw: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n throw new MachineInputError();\n }\n}\n\nasync function loadMachineExecutor(provider: MachineProvider): Promise<ExecutorFn | undefined> {\n const spec = PROVIDERS[provider];\n if (!spec) return undefined;\n\n try {\n const configuredOverride = process.env.ASK_LLM_MACHINE_EXECUTOR_MODULE;\n const allowOverride = process.env.ASK_LLM_MACHINE_ALLOW_EXECUTOR_OVERRIDE === \"1\";\n const moduleName = allowOverride && configuredOverride ? configuredOverride : spec.executorModule;\n const module = await import(moduleName);\n const executor = module[spec.executorFn];\n return typeof executor === \"function\" ? (executor as ExecutorFn) : undefined;\n } catch {\n Logger.warn(`Machine provider ${provider} could not be loaded`);\n return undefined;\n }\n}\n\nasync function withDiagnosticsOnStderr<T>(run: () => Promise<T>): Promise<T> {\n const log = console.log;\n const info = console.info;\n console.log = console.error;\n console.info = console.error;\n try {\n return await run();\n } finally {\n console.log = log;\n console.info = info;\n }\n}\n\nfunction writeMachineDocument(document: unknown): Promise<void> {\n return new Promise((resolve, reject) => {\n const output = process.stdout;\n let serialized: string;\n let settled = false;\n let callbackCheck: NodeJS.Immediate | undefined;\n\n function finish(error?: unknown): void {\n if (settled) return;\n settled = true;\n if (callbackCheck) clearImmediate(callbackCheck);\n output.off(\"error\", handleError);\n if (error === undefined || error === null) resolve();\n else reject(error);\n }\n\n function handleError(error: Error): void {\n finish(error);\n }\n\n function handleWrite(error?: Error | null): void {\n if (callbackCheck) return;\n callbackCheck = setImmediate(() => finish(error));\n }\n\n try {\n serialized = `${JSON.stringify(document)}\\n`;\n output.once(\"error\", handleError);\n output.write(serialized, handleWrite);\n } catch (error) {\n finish(error);\n }\n });\n}\n\nasync function runMachineCli(): Promise<number> {\n try {\n if (process.argv.length > 3) throw new MachineInputError();\n const input = parseMachineInput(await readStdin());\n const request = machineRequestSchema.safeParse(input);\n if (!request.success) throw new MachineInputError();\n\n const result = await withDiagnosticsOnStderr(() =>\n runMachineRequest(request.data, {\n loadExecutor: loadMachineExecutor,\n now: Date.now,\n }),\n );\n await writeMachineDocument(result);\n return 0;\n } catch (error) {\n if (!(error instanceof MachineInputError)) throw error;\n process.stderr.write(\"machine input rejected: expected one valid JSON request on stdin\\n\");\n return 2;\n }\n}\n\nasync function runDoctor(jsonOutput: boolean): Promise<number> {\n const specs: ProviderSpec[] = await buildProviderSpecs();\n const report = await runDiagnostics(specs);\n\n if (jsonOutput) {\n process.stdout.write(`${JSON.stringify(report, null, 2)}\\n`);\n } else {\n process.stdout.write(formatDiagnosticReport(report));\n }\n\n return report.status === \"error\" ? 1 : 0;\n}\n\nconst subcommand = process.argv[2];\n\nif (subcommand === \"machine-schema\") {\n writeMachineDocument(machineJsonSchemaBundle()).then(\n () => process.exit(0),\n () => {\n process.stderr.write(\"machine schema failed\\n\");\n process.exit(3);\n },\n );\n} else if (subcommand === \"machine\") {\n runMachineCli().then(\n (code) => process.exit(code),\n () => {\n process.stderr.write(\"machine dispatcher failed\\n\");\n process.exit(3);\n },\n );\n} else if (subcommand === \"doctor\") {\n const jsonOutput = process.argv.includes(\"--json\");\n runDoctor(jsonOutput).then(\n (code) => process.exit(code),\n (error) => {\n Logger.error(\"doctor failed:\", error);\n process.exit(1);\n },\n );\n} else if (subcommand === \"repl\") {\n startRepl().then(\n (code) => process.exit(code),\n (error) => {\n Logger.error(\"repl failed:\", error);\n process.exit(1);\n },\n );\n} else {\n startServer().catch((error) => {\n Logger.error(\"Fatal error:\", error);\n process.exit(1);\n });\n}\n"],"mappings":"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"} |
+2
-48
@@ -1,48 +0,2 @@ | ||
| import { z } from "zod"; | ||
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| //#region ../shared/dist/providers.d.ts | ||
| /** | ||
| * 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). | ||
| */ | ||
| declare const PROVIDERS: readonly ["gemini", "codex", "claude", "ollama", "antigravity"]; | ||
| type ProviderName = (typeof PROVIDERS)[number]; | ||
| //#endregion | ||
| //#region ../shared/dist/usage.d.ts | ||
| interface UsageStats { | ||
| provider: ProviderName; | ||
| model: string; | ||
| inputTokens: number | undefined; | ||
| outputTokens: number | undefined; | ||
| cachedTokens: number | undefined; | ||
| thinkingTokens: number | undefined; | ||
| durationMs: number; | ||
| fellBack: boolean; | ||
| } | ||
| //#endregion | ||
| //#region src/index.d.ts | ||
| interface ProviderStatus { | ||
| available: string[]; | ||
| missing: string[]; | ||
| } | ||
| type ExecutorFn = (options: { | ||
| prompt: string; | ||
| model?: string; | ||
| sessionId?: string; | ||
| onProgress?: (output: string) => void; | ||
| }) => Promise<{ | ||
| response: string; | ||
| model?: string; | ||
| usage?: UsageStats; | ||
| sessionId?: string; | ||
| threadId?: string; | ||
| }>; | ||
| declare function getLoadedExecutor(name: string): ExecutorFn | undefined; | ||
| declare function detectProviders(): Promise<ProviderStatus>; | ||
| declare function startServer(): Promise<void>; | ||
| //#endregion | ||
| export { ExecutorFn, ProviderStatus, detectProviders, getLoadedExecutor, startServer }; | ||
| //# sourceMappingURL=index.d.ts.map | ||
| import { a as startServer, c as MachineSchemaRefinement, d as MachineSchemaTarget, f as buildRolePrompt, h as validateMachineSchemaRefinements, i as getLoadedExecutor, l as MachineSchemaRefinementSet, m as runMachineRequest, n as ProviderStatus, o as MachineDeps, p as machineJsonSchemaBundle, r as detectProviders, s as MachineJsonSchemaBundle, t as ExecutorFn, u as MachineSchemaRefinementViolation } from "./index-BreUyilR.js"; | ||
| export { ExecutorFn, type MachineDeps, type MachineJsonSchemaBundle, type MachineSchemaRefinement, type MachineSchemaRefinementSet, type MachineSchemaRefinementViolation, type MachineSchemaTarget, ProviderStatus, buildRolePrompt, detectProviders, getLoadedExecutor, machineJsonSchemaBundle, runMachineRequest, startServer, validateMachineSchemaRefinements }; |
+3
-2
@@ -1,2 +0,3 @@ | ||
| import { n as getLoadedExecutor, r as startServer, t as detectProviders } from "./src-DYUWMdDM.js"; | ||
| export { detectProviders, getLoadedExecutor, startServer }; | ||
| import { i as validateMachineSchemaRefinements, n as machineJsonSchemaBundle, r as runMachineRequest, t as buildRolePrompt } from "./machine-D6srHmWi.js"; | ||
| import { n as getLoadedExecutor, r as startServer, t as detectProviders } from "./src-DUet31oX.js"; | ||
| export { buildRolePrompt, detectProviders, getLoadedExecutor, machineJsonSchemaBundle, runMachineRequest, startServer, validateMachineSchemaRefinements }; |
+11
-6
| { | ||
| "name": "@ask-llm/mcp", | ||
| "version": "0.5.2", | ||
| "version": "0.6.0", | ||
| "mcpName": "io.github.Lykhoyda/ask-llm", | ||
@@ -13,2 +13,6 @@ "description": "Unified MCP server for multi-LLM consultation — registers tools from all available providers (Gemini, Codex, Claude, Ollama, Antigravity) behind runtime availability checks", | ||
| "default": "./dist/index.js" | ||
| }, | ||
| "./machine": { | ||
| "types": "./dist/machine.d.ts", | ||
| "default": "./dist/machine.js" | ||
| } | ||
@@ -66,7 +70,7 @@ }, | ||
| "dependencies": { | ||
| "@ask-llm/antigravity-mcp": "^0.4.2", | ||
| "@ask-llm/claude-mcp": "^0.1.2", | ||
| "@ask-llm/codex-mcp": "^0.6.2", | ||
| "@ask-llm/gemini-mcp": "^1.6.15", | ||
| "@ask-llm/ollama-mcp": "^0.5.2", | ||
| "@ask-llm/antigravity-mcp": "^0.5.0", | ||
| "@ask-llm/claude-mcp": "^0.1.3", | ||
| "@ask-llm/codex-mcp": "^0.7.0", | ||
| "@ask-llm/gemini-mcp": "^1.6.16", | ||
| "@ask-llm/ollama-mcp": "^0.5.3", | ||
| "@modelcontextprotocol/sdk": "^1.27.1", | ||
@@ -79,2 +83,3 @@ "zod": "^4.3.6" | ||
| "@types/node": "^22.19.13", | ||
| "ajv": "8.18.0", | ||
| "tsdown": "^0.22.2", | ||
@@ -81,0 +86,0 @@ "typescript": "^5.0.0", |
+81
-0
@@ -72,2 +72,83 @@ # Ask LLM MCP (Unified) | ||
| ## Machine Protocol | ||
| `machine` exposes a stdin-only JSON interface for factory controllers. It accepts one request of at most 2 MiB, with the prompt bounded to 150,000 characters by the schema, validates it before loading a provider, and writes exactly one typed result document to stdout. Prompts and issue content are never accepted through argv, and diagnostics go only to stderr. | ||
| Create a request file without putting its content in the command line: | ||
| ```json | ||
| { | ||
| "schemaVersion": 1, | ||
| "requestId": "factory-review-0001", | ||
| "role": "review", | ||
| "provider": "codex", | ||
| "prompt": "<redacted review input>", | ||
| "readOnly": true, | ||
| "writerProvider": "claude", | ||
| "includeDirs": ["packages/example"] | ||
| } | ||
| ``` | ||
| Then dispatch it through stdin: | ||
| ```bash | ||
| npx -y ask-llm-mcp machine < request.json | ||
| ``` | ||
| A valid dispatch returns a typed success or provider-failure envelope: | ||
| ```json | ||
| { | ||
| "schemaVersion": 1, | ||
| "requestId": "factory-review-0001", | ||
| "status": "success", | ||
| "role": "review", | ||
| "provider": "codex", | ||
| "actualModel": "<redacted model>", | ||
| "rawResponseSha256": "<redacted sha256>", | ||
| "durationMs": 1200, | ||
| "usage": { "inputTokens": 100, "outputTokens": 20, "totalTokens": 120 }, | ||
| "fallback": { | ||
| "occurred": false, | ||
| "requestedModel": "<redacted model>", | ||
| "actualModel": "<redacted model>" | ||
| }, | ||
| "session": { "sessionId": "<redacted session>", "transcriptPath": null }, | ||
| "payload": { "summary": "<redacted>", "findings": [] }, | ||
| "quotaSignal": { "kind": "runtime_proxy_required" }, | ||
| "failure": null | ||
| } | ||
| ``` | ||
| The complete envelope also records model, fallback, duration, token, response-hash, and session provenance. Provider-level failures use the same strict result contract and still exit successfully so controllers can parse and classify them. | ||
| All machine dispatches force `readOnly: true` and pass read-only sandbox options to the provider adapter. The interface supports Codex, Claude, and Antigravity; it does not provide a write path. Subscription usage percentages remain unknown unless the provider exposes them, and the dispatcher never infers a percentage from token counts. | ||
| | Exit code | Meaning | Stdout | | ||
| |-----------|---------|--------| | ||
| | `0` | Valid result envelope, including provider-level failure | One JSON document | | ||
| | `2` | Missing, oversized, malformed, or schema-invalid stdin request | Empty | | ||
| | `3` | Dispatcher infrastructure failure | Empty | | ||
| Use `machine-schema` to retrieve the stable canonical request/result schema bundle and its digest: | ||
| ```bash | ||
| npx -y ask-llm-mcp machine-schema > machine-schema.json | ||
| ``` | ||
| ```json | ||
| { | ||
| "digest": "<redacted sha256>", | ||
| "failure": { "<redacted>": true }, | ||
| "request": { "<redacted>": true }, | ||
| "refinements": { "version": 1, "rules": [] }, | ||
| "result": { "<redacted>": true }, | ||
| "rolePayloads": { "brainstorm": {}, "review": {}, "verify": {} } | ||
| } | ||
| ``` | ||
| The whole bundle is authoritative. Validate a document against its Draft 2020-12 `request`, `result`, or `failure` schema first, then run `validateMachineSchemaRefinements(target, document, bundle.refinements)`. The portable refinement descriptors cover sibling-field equality rules that standard JSON Schema cannot express, including self-review and fallback provenance checks. The digest covers every base schema, role payload schema, and refinement descriptor. | ||
| `machine-schema` is provider-independent: it neither detects nor loads a provider and does not require a provider CLI to be installed. The refinement interpreter and its `MachineSchemaRefinement`, `MachineSchemaRefinementSet`, `MachineSchemaRefinementViolation`, and `MachineSchemaTarget` types are exported from `ask-llm-mcp/machine` and the package root. | ||
| ## Documentation | ||
@@ -74,0 +155,0 @@ |
| {"version":3,"file":"index.d.ts","names":["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"],"sources":["../../shared/dist/providers.d.ts","../../shared/dist/usage.d.ts","../src/index.ts"],"mappings":";;;;;;;;;AAOA;cAAqB,SAAA;AAAA,KACT,YAAA,WAAuB,SAAS;;;UCP3B,UAAA;EACbC,QAAAA,EAAU,YAAY;EACtBE,KAAAA;EACAC,WAAAA;EACAC,YAAAA;EACAC,YAAAA;EACAC,cAAAA;EACAC,UAAAA;EACAC,QAAAA;AAAAA;;;UCuBa,cAAA;EACf,SAAA;EACA,OAAO;AAAA;AAAA,KAGG,UAAA,IAAc,OAAA;EACxB,MAAA;EACA,KAAA;EACA,SAAA;EACA,UAAA,IAAc,MAAA;AAAA,MACV,OAAO;EACX,QAAA;EAKA,KAAA;EACA,KAAA,GAAQ,UAAA;EACR,SAAA;EACA,QAAA;AAAA;AAAA,iBAKc,iBAAA,CAAkB,IAAA,WAAe,UAAU;AAAA,iBAIrC,eAAA,IAAmB,OAAO,CAAC,cAAA;AAAA,iBA2F3B,WAAA,IAAW,OAAA"} |
-1274
| import { createRequire } from "node:module"; | ||
| import { z } from "zod"; | ||
| import { createHash } from "node:crypto"; | ||
| import { existsSync, readdirSync } from "node:fs"; | ||
| import * as os from "node:os"; | ||
| import { homedir } from "node:os"; | ||
| import * as path from "node:path"; | ||
| import { delimiter, isAbsolute, join } from "node:path"; | ||
| import { execFile, execFileSync } from "node:child_process"; | ||
| import { promisify } from "node:util"; | ||
| import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
| import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; | ||
| //#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$1 = [ | ||
| "gemini", | ||
| "codex", | ||
| "claude", | ||
| "ollama", | ||
| "antigravity" | ||
| ]; | ||
| //#endregion | ||
| //#region ../shared/dist/askResponse.js | ||
| const usageStatsSchema$1 = z.object({ | ||
| provider: z.enum(PROVIDERS$1), | ||
| 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$1), | ||
| response: z.string(), | ||
| model: z.string(), | ||
| sessionId: z.string().optional(), | ||
| usage: usageStatsSchema$1.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.`); | ||
| } | ||
| }; | ||
| path.join(os.tmpdir(), "gemini-mcp-chunks"); | ||
| //#endregion | ||
| //#region ../shared/dist/shellPath.js | ||
| const IS_WINDOWS$2 = process.platform === "win32"; | ||
| const SHELL_PATH_ENV_VAR = "ASK_LLM_PATH"; | ||
| let cachedPath = null; | ||
| function extractShellPath() { | ||
| if (IS_WINDOWS$2) 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$2) { | ||
| 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() | ||
| }; | ||
| } | ||
| process.platform; | ||
| function parseTimeoutEnv(envVal) { | ||
| if (!envVal) return void 0; | ||
| const parsed = Number(envVal); | ||
| return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0; | ||
| } | ||
| 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; | ||
| } | ||
| //#endregion | ||
| //#region ../shared/dist/doctor.js | ||
| const execFileAsync$1 = promisify(execFile); | ||
| const NODE_MIN_MAJOR = 20; | ||
| const VERSION_PROBE_TIMEOUT_MS = 5e3; | ||
| function parseNodeMajor(version) { | ||
| const match = version.match(/^v?(\d+)/); | ||
| return match ? Number.parseInt(match[1], 10) : 0; | ||
| } | ||
| async function probeCommand(command, versionArgs, pathEnv) { | ||
| const env = { | ||
| ...process.env, | ||
| PATH: pathEnv | ||
| }; | ||
| let cliPath; | ||
| try { | ||
| const { stdout } = await execFileAsync$1(process.platform === "win32" ? "where" : "which", [command], { | ||
| env, | ||
| timeout: VERSION_PROBE_TIMEOUT_MS | ||
| }); | ||
| cliPath = stdout.split(/\r?\n/)[0]?.trim() || void 0; | ||
| } catch { | ||
| return { | ||
| cliPath: void 0, | ||
| version: void 0, | ||
| error: "not found on PATH" | ||
| }; | ||
| } | ||
| try { | ||
| const { stdout, stderr } = await execFileAsync$1(command, versionArgs, { | ||
| env, | ||
| timeout: VERSION_PROBE_TIMEOUT_MS | ||
| }); | ||
| const versionLine = (stdout || stderr).split(/\r?\n/)[0]?.trim(); | ||
| return { | ||
| cliPath, | ||
| version: versionLine || void 0, | ||
| error: void 0 | ||
| }; | ||
| } catch (err) { | ||
| const msg = err instanceof Error ? err.message : String(err); | ||
| return { | ||
| cliPath, | ||
| version: void 0, | ||
| error: `version probe failed: ${msg.slice(0, 200)}` | ||
| }; | ||
| } | ||
| } | ||
| async function runDiagnostics(providers) { | ||
| const checks = []; | ||
| const resolvedPath = resolveShellPath(); | ||
| const nodeOk = parseNodeMajor(process.version) >= NODE_MIN_MAJOR; | ||
| checks.push(nodeOk ? { | ||
| name: "Node.js version", | ||
| status: "pass", | ||
| message: `${process.version} (>= v${NODE_MIN_MAJOR} required)` | ||
| } : { | ||
| name: "Node.js version", | ||
| status: "fail", | ||
| message: `${process.version} is too old — provider CLIs use ES2024 features that crash on Node < ${NODE_MIN_MAJOR}`, | ||
| fix: `Upgrade Node to v${NODE_MIN_MAJOR}+ (LTS). nvm: \`nvm install 22 && nvm use 22\`` | ||
| }); | ||
| checks.push(resolvedPath.length > 0 ? { | ||
| name: "PATH resolution", | ||
| status: "pass", | ||
| message: `Resolved PATH has ${resolvedPath.split(":").filter(Boolean).length} entries` | ||
| } : { | ||
| name: "PATH resolution", | ||
| status: "fail", | ||
| message: "Resolved PATH is empty — provider CLIs cannot be located", | ||
| fix: "Set ASK_LLM_PATH env var explicitly, or restart your MCP client from a shell with a working PATH" | ||
| }); | ||
| const providerProbes = []; | ||
| for (const spec of providers) { | ||
| if (spec.probeAvailability) { | ||
| let available = false; | ||
| try { | ||
| available = await spec.probeAvailability(); | ||
| } catch { | ||
| available = false; | ||
| } | ||
| providerProbes.push({ | ||
| name: spec.name, | ||
| command: spec.command, | ||
| available, | ||
| cliPath: void 0, | ||
| cliVersion: void 0, | ||
| error: available ? void 0 : "endpoint unreachable" | ||
| }); | ||
| checks.push(available ? { | ||
| name: `Provider: ${spec.name}`, | ||
| status: "pass", | ||
| message: "endpoint reachable" | ||
| } : { | ||
| name: `Provider: ${spec.name}`, | ||
| status: "warn", | ||
| message: "endpoint unreachable", | ||
| fix: spec.installHint | ||
| }); | ||
| continue; | ||
| } | ||
| const versionArgs = spec.versionArgs ?? ["--version"]; | ||
| const probe = await probeCommand(spec.command, versionArgs, resolvedPath); | ||
| const available = probe.cliPath !== void 0 && probe.error === void 0; | ||
| let enrichment; | ||
| if (available && spec.enrich) try { | ||
| enrichment = await spec.enrich({ | ||
| command: spec.command, | ||
| pathEnv: resolvedPath | ||
| }); | ||
| } catch { | ||
| enrichment = void 0; | ||
| } | ||
| providerProbes.push({ | ||
| name: spec.name, | ||
| command: spec.command, | ||
| available, | ||
| cliPath: probe.cliPath, | ||
| cliVersion: probe.version, | ||
| error: probe.error, | ||
| enrichment | ||
| }); | ||
| if (probe.cliPath === void 0) checks.push({ | ||
| name: `Provider: ${spec.name}`, | ||
| status: "warn", | ||
| message: `\`${spec.command}\` not found on PATH`, | ||
| fix: spec.installHint | ||
| }); | ||
| else if (probe.error !== void 0) checks.push({ | ||
| name: `Provider: ${spec.name}`, | ||
| status: "warn", | ||
| message: `Found at ${probe.cliPath} but ${probe.error}`, | ||
| fix: "Check that the CLI is properly installed and authenticated" | ||
| }); | ||
| else checks.push({ | ||
| name: `Provider: ${spec.name}`, | ||
| status: "pass", | ||
| message: `${probe.version ?? "available"} at ${probe.cliPath}` | ||
| }); | ||
| } | ||
| const askLlmPath = process.env.ASK_LLM_PATH; | ||
| if (askLlmPath) checks.push({ | ||
| name: "ASK_LLM_PATH override", | ||
| status: "warn", | ||
| message: `Custom PATH override is set (${askLlmPath.split(":").length} entries)`, | ||
| fix: "Unset ASK_LLM_PATH if you want shell-resolved PATH instead" | ||
| }); | ||
| const timeoutEnv = process.env.GMCPT_TIMEOUT_MS; | ||
| const timeoutMs = timeoutEnv ? Number.parseInt(timeoutEnv, 10) : 21e4; | ||
| const codexTimeoutMs = resolveTimeoutMs(EXECUTION.CODEX_TIMEOUT_ENV_VAR, EXECUTION.DEFAULT_CODEX_TIMEOUT_MS); | ||
| const claudeTimeoutMs = resolveTimeoutMs(EXECUTION.CLAUDE_TIMEOUT_ENV_VAR, EXECUTION.DEFAULT_CLAUDE_TIMEOUT_MS); | ||
| const geminiTimeoutMs = resolveTimeoutMs(EXECUTION.GEMINI_TIMEOUT_ENV_VAR, EXECUTION.DEFAULT_TIMEOUT_MS); | ||
| const hasFailure = checks.some((c) => c.status === "fail"); | ||
| const hasWarn = checks.some((c) => c.status === "warn"); | ||
| return { | ||
| status: hasFailure ? "error" : hasWarn ? "warning" : "ok", | ||
| generatedAt: (/* @__PURE__ */ new Date()).toISOString(), | ||
| environment: { | ||
| nodeVersion: process.version, | ||
| nodeOk, | ||
| platform: process.platform, | ||
| arch: process.arch, | ||
| resolvedPath, | ||
| askLlmPath, | ||
| timeoutMs, | ||
| codexTimeoutMs, | ||
| claudeTimeoutMs, | ||
| geminiTimeoutMs | ||
| }, | ||
| providers: providerProbes, | ||
| checks | ||
| }; | ||
| } | ||
| const STATUS_GLYPH = { | ||
| pass: "✓", | ||
| warn: "!", | ||
| fail: "✗", | ||
| skip: "-" | ||
| }; | ||
| const OVERALL_GLYPH = { | ||
| ok: "✓", | ||
| warning: "!", | ||
| error: "✗" | ||
| }; | ||
| function formatDiagnosticReport(report) { | ||
| const lines = []; | ||
| lines.push(`${OVERALL_GLYPH[report.status]} ask-llm doctor — ${report.status.toUpperCase()}`); | ||
| lines.push(""); | ||
| lines.push("Environment:"); | ||
| lines.push(` Node: ${report.environment.nodeVersion}${report.environment.nodeOk ? "" : " (TOO OLD)"}`); | ||
| lines.push(` Platform: ${report.environment.platform}/${report.environment.arch}`); | ||
| lines.push(` Timeouts: codex=${report.environment.codexTimeoutMs}ms, claude=${report.environment.claudeTimeoutMs}ms, gemini=${report.environment.geminiTimeoutMs}ms`); | ||
| if (report.environment.askLlmPath) lines.push(` ASK_LLM_PATH: set (${report.environment.askLlmPath.split(":").length} entries)`); | ||
| lines.push(""); | ||
| lines.push("Checks:"); | ||
| for (const check of report.checks) { | ||
| lines.push(` ${STATUS_GLYPH[check.status]} ${check.name}: ${check.message}`); | ||
| if (check.fix) lines.push(` → ${check.fix}`); | ||
| } | ||
| if (report.providers.length > 0) { | ||
| lines.push(""); | ||
| lines.push("Providers:"); | ||
| for (const provider of report.providers) { | ||
| const status = provider.available ? "available" : "unavailable"; | ||
| const detail = provider.cliPath ? ` (${provider.cliVersion ?? "version unknown"})` : ""; | ||
| lines.push(` - ${provider.name}: ${status}${detail}`); | ||
| if (provider.cliPath) lines.push(` path: ${provider.cliPath}`); | ||
| if (provider.enrichment) { | ||
| const { heading, overall, checks } = provider.enrichment; | ||
| lines.push(` ${heading}: ${overall.toUpperCase()}`); | ||
| for (const check of checks) { | ||
| if (check.status === "pass" || check.status === "skip") continue; | ||
| lines.push(` ${STATUS_GLYPH[check.status]} ${check.name}: ${check.summary}`); | ||
| if (check.remediation) lines.push(` → ${check.remediation}`); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| lines.push(""); | ||
| return lines.join("\n"); | ||
| } | ||
| z.string().refine((dir) => !dir.includes("..") && !isAbsolute(dir) && !dir.startsWith("~"), { message: "Directory paths must be relative without '..' or '~'" }); | ||
| //#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/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; | ||
| } | ||
| }; | ||
| 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() | ||
| }); | ||
| const diagnosticReportSchema = 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) | ||
| }); | ||
| function createDiagnoseTool(providers) { | ||
| return { | ||
| name: "diagnose", | ||
| description: "Run a self-diagnosis of the MCP server's environment: Node version, resolved PATH, provider CLI presence and versions, key env vars. Returns a structured report and a human-readable summary. Read-only; never spawns LLM calls.", | ||
| zodSchema: z.object({}), | ||
| outputSchema: diagnosticReportSchema, | ||
| annotations: { | ||
| title: "Diagnose Environment", | ||
| readOnlyHint: true, | ||
| destructiveHint: false, | ||
| idempotentHint: true, | ||
| openWorldHint: false | ||
| }, | ||
| category: "utility", | ||
| execute: async () => { | ||
| const report = await runDiagnostics(providers); | ||
| return { | ||
| text: formatDiagnosticReport(report), | ||
| structuredContent: report | ||
| }; | ||
| } | ||
| }; | ||
| } | ||
| 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) | ||
| }] })); | ||
| } | ||
| path.join(os.tmpdir(), "ask-llm-sessions"); | ||
| //#endregion | ||
| //#region src/constants.ts | ||
| const PROVIDERS = { | ||
| gemini: { | ||
| name: "Gemini", | ||
| command: "gemini", | ||
| executorModule: "@ask-llm/gemini-mcp/executor", | ||
| executorFn: "executeGeminiCLI", | ||
| defaultModel: "gemini-3.1-pro-preview" | ||
| }, | ||
| codex: { | ||
| name: "Codex", | ||
| command: "codex", | ||
| executorModule: "@ask-llm/codex-mcp/executor", | ||
| executorFn: "executeCodexCLI", | ||
| defaultModel: "gpt-5.6-sol", | ||
| enrichModule: "@ask-llm/codex-mcp/executor", | ||
| enrichFn: "enrichCodexDoctor" | ||
| }, | ||
| claude: { | ||
| name: "Claude", | ||
| command: "claude", | ||
| executorModule: "@ask-llm/claude-mcp/executor", | ||
| executorFn: "executeClaudeCLI", | ||
| defaultModel: "opus", | ||
| disabledWhenEnvVar: "CLAUDECODE" | ||
| }, | ||
| ollama: { | ||
| name: "Ollama", | ||
| command: "ollama", | ||
| executorModule: "@ask-llm/ollama-mcp/executor", | ||
| executorFn: "executeOllamaCLI", | ||
| defaultModel: "qwen3.6:27b", | ||
| availabilityModule: "@ask-llm/ollama-mcp/executor", | ||
| availabilityFn: "isProviderAvailable" | ||
| }, | ||
| antigravity: { | ||
| name: "Antigravity", | ||
| command: "agy", | ||
| executorModule: "@ask-llm/antigravity-mcp/executor", | ||
| executorFn: "executeAntigravityCLI", | ||
| defaultModel: "Gemini 3.1 Pro (High)" | ||
| } | ||
| }; | ||
| const INSTALL_HINTS = { | ||
| gemini: "npm install -g @google/gemini-cli", | ||
| codex: "npm install -g @openai/codex", | ||
| claude: "npm install -g @anthropic-ai/claude-code, then run `claude` once to authenticate", | ||
| ollama: "https://ollama.com — then: ollama pull qwen3.6:27b", | ||
| antigravity: "Install Google Antigravity (agy) from https://antigravity.google, then run `agy` once to log in" | ||
| }; | ||
| function isProviderEligible(provider) { | ||
| return !(provider.disabledWhenEnvVar && process.env[provider.disabledWhenEnvVar]); | ||
| } | ||
| function getEligibleProviderKeys() { | ||
| return Object.entries(PROVIDERS).filter(([, provider]) => isProviderEligible(provider)).map(([key]) => key); | ||
| } | ||
| //#endregion | ||
| //#region src/multiLlm.ts | ||
| const usageStatsSchema = z.object({ | ||
| provider: z.enum(PROVIDERS$1), | ||
| 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 multiLlmResultSchema = z.object({ | ||
| provider: z.string(), | ||
| ok: z.boolean(), | ||
| response: z.string().optional(), | ||
| model: z.string().optional(), | ||
| sessionId: z.string().optional(), | ||
| usage: usageStatsSchema.optional(), | ||
| durationMs: z.number(), | ||
| error: z.string().optional() | ||
| }); | ||
| const multiLlmReportSchema = z.object({ | ||
| dispatchedAt: z.string(), | ||
| totalDurationMs: z.number(), | ||
| successCount: z.number(), | ||
| failureCount: z.number(), | ||
| results: z.array(multiLlmResultSchema) | ||
| }); | ||
| async function dispatchMultiLlm(opts) { | ||
| const dispatchedAt = (/* @__PURE__ */ new Date()).toISOString(); | ||
| const startTime = Date.now(); | ||
| const dispatches = opts.providers.map(async (provider) => { | ||
| const callStart = Date.now(); | ||
| const executor = opts.getExecutor(provider); | ||
| if (!executor) return { | ||
| provider, | ||
| ok: false, | ||
| error: `Provider "${provider}" is not loaded`, | ||
| durationMs: Date.now() - callStart | ||
| }; | ||
| try { | ||
| const result = await executor({ prompt: opts.prompt }); | ||
| if (result.usage) opts.recordUsage?.(result.usage); | ||
| return { | ||
| provider, | ||
| ok: true, | ||
| response: result.response, | ||
| model: result.usage?.model ?? result.model, | ||
| sessionId: result.sessionId ?? result.threadId, | ||
| usage: result.usage, | ||
| durationMs: Date.now() - callStart | ||
| }; | ||
| } catch (err) { | ||
| return { | ||
| provider, | ||
| ok: false, | ||
| error: err instanceof Error ? err.message : String(err), | ||
| durationMs: Date.now() - callStart | ||
| }; | ||
| } | ||
| }); | ||
| const results = await Promise.all(dispatches); | ||
| const totalDurationMs = Date.now() - startTime; | ||
| const successCount = results.filter((r) => r.ok).length; | ||
| return { | ||
| dispatchedAt, | ||
| totalDurationMs, | ||
| successCount, | ||
| failureCount: results.length - successCount, | ||
| results | ||
| }; | ||
| } | ||
| function formatMultiLlmReport(report) { | ||
| return [`## Multi-LLM Dispatch — ${report.successCount}/${report.results.length} succeeded (${(report.totalDurationMs / 1e3).toFixed(1)}s total)\n`, ...report.results.map((r) => { | ||
| const status = r.ok ? "✓" : "✗"; | ||
| const durationTag = `${(r.durationMs / 1e3).toFixed(1)}s`; | ||
| const modelTag = r.model ? ` · ${r.model}` : ""; | ||
| if (r.ok) return `\n### ${r.provider} ${status} (${durationTag}${modelTag})\n\n${r.response ?? "(empty response)"}`; | ||
| return `\n### ${r.provider} ${status} (${durationTag})\n\n**Failed:** ${r.error ?? "unknown error"}`; | ||
| })].join("\n"); | ||
| } | ||
| function buildMultiLlmInputSchema(availableProviders) { | ||
| const providerEnum = availableProviders.length > 0 ? availableProviders : [...PROVIDERS$1]; | ||
| return z.object({ | ||
| prompt: z.string().min(1).max(1e5).describe("The prompt to send to all selected providers in parallel."), | ||
| providers: z.array(z.enum(providerEnum)).min(1).optional().describe(`Which providers to dispatch to. Available: ${providerEnum.join(", ")}. Defaults to all available providers.`) | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/utils/availability.ts | ||
| const execFileAsync = promisify(execFile); | ||
| const IS_WINDOWS = process.platform === "win32"; | ||
| async function isCommandAvailable(command) { | ||
| try { | ||
| await execFileAsync(IS_WINDOWS ? "where" : "which", [command], { | ||
| timeout: 5e3, | ||
| env: getSpawnEnv() | ||
| }); | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/utils/providerSpecs.ts | ||
| async function buildProviderSpecs() { | ||
| const specs = []; | ||
| for (const [key, config] of Object.entries(PROVIDERS)) { | ||
| if (!isProviderEligible(config)) continue; | ||
| const installHint = INSTALL_HINTS[key]; | ||
| let probeAvailability; | ||
| if (config.availabilityModule && config.availabilityFn) { | ||
| const moduleName = config.availabilityModule; | ||
| const fnName = config.availabilityFn; | ||
| probeAvailability = async () => { | ||
| try { | ||
| const fn = (await import(moduleName))[fnName]; | ||
| if (typeof fn !== "function") return false; | ||
| return await fn(); | ||
| } catch { | ||
| return false; | ||
| } | ||
| }; | ||
| } | ||
| let enrich; | ||
| if (config.enrichModule && config.enrichFn) { | ||
| const moduleName = config.enrichModule; | ||
| const fnName = config.enrichFn; | ||
| enrich = async (ctx) => { | ||
| try { | ||
| const fn = (await import(moduleName))[fnName]; | ||
| if (typeof fn !== "function") return void 0; | ||
| return await fn(ctx); | ||
| } catch { | ||
| return; | ||
| } | ||
| }; | ||
| } | ||
| specs.push({ | ||
| key, | ||
| name: config.name, | ||
| command: config.command, | ||
| installHint, | ||
| probeAvailability, | ||
| enrich | ||
| }); | ||
| } | ||
| return specs; | ||
| } | ||
| //#endregion | ||
| //#region src/index.ts | ||
| function readPackageJson() { | ||
| try { | ||
| return createRequire(import.meta.url)("../package.json"); | ||
| } catch { | ||
| return { | ||
| name: "@ask-llm/mcp", | ||
| version: "0.0.0" | ||
| }; | ||
| } | ||
| } | ||
| const loadedExecutors = /* @__PURE__ */ new Map(); | ||
| function getLoadedExecutor(name) { | ||
| return loadedExecutors.get(name); | ||
| } | ||
| async function detectProviders() { | ||
| const available = []; | ||
| const missing = []; | ||
| const checks = await Promise.all(Object.entries(PROVIDERS).map(async ([key, provider]) => { | ||
| let found; | ||
| const disabled = Boolean(provider.disabledWhenEnvVar && process.env[provider.disabledWhenEnvVar]); | ||
| if (disabled) found = false; | ||
| else if (provider.availabilityModule && provider.availabilityFn) try { | ||
| found = await (await import(provider.availabilityModule))[provider.availabilityFn](); | ||
| } catch { | ||
| found = false; | ||
| } | ||
| else found = await isCommandAvailable(provider.command); | ||
| return { | ||
| key, | ||
| provider, | ||
| found, | ||
| disabled | ||
| }; | ||
| })); | ||
| for (const { key, provider, found, disabled } of checks) if (found) try { | ||
| const mod = await import(provider.executorModule); | ||
| loadedExecutors.set(key, mod[provider.executorFn]); | ||
| available.push(key); | ||
| Logger.warn(`Provider ${provider.name} (${provider.command}) — available`); | ||
| } catch (err) { | ||
| missing.push(key); | ||
| Logger.error(`Provider ${provider.name} — import failed:`, err); | ||
| } | ||
| else { | ||
| missing.push(key); | ||
| if (disabled) { | ||
| Logger.warn(`Provider ${provider.name} (${provider.command}) — disabled because ${provider.disabledWhenEnvVar} identifies the current MCP host`); | ||
| continue; | ||
| } | ||
| const hint = INSTALL_HINTS[key] ?? ""; | ||
| Logger.warn(`Provider ${provider.name} (${provider.command}) — not found${hint ? `. Install: ${hint}` : ""}`); | ||
| } | ||
| if (available.length === 0) { | ||
| Logger.warn("No LLM providers found. Install at least one CLI to enable AI tools."); | ||
| for (const [key, hint] of Object.entries(INSTALL_HINTS)) Logger.warn(` ${PROVIDERS[key]?.name ?? key}: ${hint}`); | ||
| } | ||
| return { | ||
| available, | ||
| missing | ||
| }; | ||
| } | ||
| function buildAskLlmSchema(availableProviders) { | ||
| const providerEnum = availableProviders.length > 0 ? availableProviders : getEligibleProviderKeys(); | ||
| const providerDescriptions = providerEnum.map((k) => { | ||
| const p = PROVIDERS[k]; | ||
| return p ? `"${k}" (${p.name}, default model: ${p.defaultModel})` : k; | ||
| }).join(", "); | ||
| return z.object({ | ||
| provider: z.enum(providerEnum).describe(`Which LLM provider to use. Available: ${providerDescriptions}`), | ||
| prompt: z.string().min(1).max(1e5).describe("The question, code review request, or analysis task to send"), | ||
| model: z.string().optional().describe("Override the default model. Usually not needed."), | ||
| sessionId: z.string().optional().describe("Optional session ID to resume a prior conversation. Pass the [Session ID: ...] or [Thread ID: ...] value from a previous response. Each provider handles sessions natively (Claude/Gemini --resume, Codex exec resume) or via server-side replay (Ollama).") | ||
| }); | ||
| } | ||
| const PROGRESS_MESSAGES = (op) => [ | ||
| `${op} - Processing your request...`, | ||
| `${op} - Generating insights...`, | ||
| `${op} - Large analysis in progress...`, | ||
| `${op} - Still working...` | ||
| ]; | ||
| async function startServer() { | ||
| Logger.debug("init @ask-llm/mcp"); | ||
| Logger.checkNodeVersion(); | ||
| const { name, version } = readPackageJson(); | ||
| const { available } = await detectProviders(); | ||
| const server = new McpServer({ | ||
| name, | ||
| version | ||
| }); | ||
| const sessionUsage = createSessionUsage(); | ||
| const askLlmSchema = buildAskLlmSchema(available); | ||
| server.registerTool("ask-llm", { | ||
| description: "Send a prompt to an LLM provider (Codex, Claude, Antigravity, Ollama, Gemini). Specify which provider to use. Each provider auto-selects its best model with fallback on errors. Returns both human-readable text and a structured response (provider, model, sessionId, usage) via outputSchema.", | ||
| inputSchema: askLlmSchema.shape, | ||
| outputSchema: askResponseSchema.shape, | ||
| annotations: { | ||
| title: "Ask LLM", | ||
| readOnlyHint: false, | ||
| destructiveHint: false, | ||
| openWorldHint: true | ||
| } | ||
| }, async (args, extra) => { | ||
| const progress = createProgressTracker("ask-llm", extra, PROGRESS_MESSAGES("ask-llm")); | ||
| try { | ||
| const { provider, prompt, model, sessionId } = askLlmSchema.parse(args); | ||
| Logger.toolInvocation("ask-llm", args); | ||
| const executor = loadedExecutors.get(provider); | ||
| if (!executor) { | ||
| const hint = INSTALL_HINTS[provider] ?? ""; | ||
| throw new Error(`Provider "${provider}" is not available. ${hint ? `Install: ${hint}` : "Check that the CLI is on your PATH."}`); | ||
| } | ||
| const result = await executor({ | ||
| prompt, | ||
| model, | ||
| sessionId, | ||
| onProgress: (output) => { | ||
| progress.updateOutput(output); | ||
| } | ||
| }); | ||
| if (result.usage) sessionUsage.record(result.usage); | ||
| await progress.stop(true); | ||
| const providerName = PROVIDERS[provider]?.name ?? provider; | ||
| const resolvedSessionId = result.sessionId ?? result.threadId; | ||
| const idLine = result.sessionId ? `\n\n[Session ID: ${result.sessionId}]` : result.threadId ? `\n\n[Thread ID: ${result.threadId}]` : ""; | ||
| const structured = { | ||
| provider, | ||
| response: result.response, | ||
| model: result.usage?.model ?? result.model ?? model ?? PROVIDERS[provider]?.defaultModel ?? "unknown", | ||
| sessionId: resolvedSessionId, | ||
| usage: result.usage | ||
| }; | ||
| return { | ||
| content: [{ | ||
| type: "text", | ||
| text: `${providerName} response:\n${result.response}${idLine}` | ||
| }], | ||
| structuredContent: structured, | ||
| isError: false | ||
| }; | ||
| } catch (error) { | ||
| await progress.stop(false); | ||
| const msg = error instanceof Error ? error.message : String(error); | ||
| Logger.error("ask-llm error:", error); | ||
| return { | ||
| content: [{ | ||
| type: "text", | ||
| text: `Error: ${msg}` | ||
| }], | ||
| isError: true | ||
| }; | ||
| } | ||
| }); | ||
| const pingSchema = z.object({ message: z.string().optional().describe("A message to echo back to test the connection") }); | ||
| server.registerTool("ping", { | ||
| description: "Test connectivity with the MCP server", | ||
| inputSchema: pingSchema.shape, | ||
| annotations: { | ||
| title: "Ping", | ||
| readOnlyHint: true, | ||
| idempotentHint: true, | ||
| openWorldHint: false | ||
| } | ||
| }, async (args) => { | ||
| const { message } = pingSchema.parse(args); | ||
| const providers = available.length > 0 ? available.join(", ") : "none"; | ||
| return { | ||
| content: [{ | ||
| type: "text", | ||
| text: message || `Pong from @ask-llm/mcp! Available providers: ${providers}` | ||
| }], | ||
| isError: false | ||
| }; | ||
| }); | ||
| const usageTool = createUsageStatsTool(sessionUsage); | ||
| const usageOutputShape = usageTool.outputSchema ? usageTool.outputSchema.shape : void 0; | ||
| server.registerTool(usageTool.name, { | ||
| description: usageTool.description, | ||
| inputSchema: {}, | ||
| ...usageOutputShape ? { outputSchema: usageOutputShape } : {}, | ||
| annotations: usageTool.annotations | ||
| }, async () => { | ||
| const result = await usageTool.execute({}); | ||
| if (typeof result === "string") return { | ||
| content: [{ | ||
| type: "text", | ||
| text: result | ||
| }], | ||
| isError: false | ||
| }; | ||
| return { | ||
| content: [{ | ||
| type: "text", | ||
| text: result.text | ||
| }], | ||
| structuredContent: result.structuredContent, | ||
| isError: false | ||
| }; | ||
| }); | ||
| registerSessionUsageResource(server, sessionUsage); | ||
| const multiLlmInputSchema = buildMultiLlmInputSchema(available.length > 0 ? available : getEligibleProviderKeys()); | ||
| server.registerTool("multi-llm", { | ||
| description: "Dispatch the same prompt to multiple LLM providers in parallel and return all responses in one structured payload. Use when you want to compare answers across Codex, Claude, Antigravity, Ollama, and Gemini, or when you want a multi-provider sanity check on a question. Returns per-provider success/failure, response text, model, sessionId, and token usage. Each call is fresh — no session continuity (use ask-llm for individual session-bearing calls).", | ||
| inputSchema: multiLlmInputSchema.shape, | ||
| outputSchema: multiLlmReportSchema.shape, | ||
| annotations: { | ||
| title: "Multi-LLM Parallel Dispatch", | ||
| readOnlyHint: false, | ||
| destructiveHint: false, | ||
| idempotentHint: false, | ||
| openWorldHint: true | ||
| } | ||
| }, async (args) => { | ||
| const { prompt, providers: requestedProviders } = multiLlmInputSchema.parse(args); | ||
| const providers = requestedProviders && requestedProviders.length > 0 ? requestedProviders : available; | ||
| Logger.toolInvocation("multi-llm", { | ||
| prompt: prompt.slice(0, 80), | ||
| providers | ||
| }); | ||
| const report = await dispatchMultiLlm({ | ||
| prompt, | ||
| providers, | ||
| getExecutor: (name) => loadedExecutors.get(name), | ||
| recordUsage: (stats) => sessionUsage.record(stats) | ||
| }); | ||
| return { | ||
| content: [{ | ||
| type: "text", | ||
| text: formatMultiLlmReport(report) | ||
| }], | ||
| structuredContent: report, | ||
| isError: false | ||
| }; | ||
| }); | ||
| const diagnoseTool = createDiagnoseTool(await buildProviderSpecs()); | ||
| const diagnoseOutputShape = diagnoseTool.outputSchema ? diagnoseTool.outputSchema.shape : void 0; | ||
| server.registerTool(diagnoseTool.name, { | ||
| description: diagnoseTool.description, | ||
| inputSchema: {}, | ||
| ...diagnoseOutputShape ? { outputSchema: diagnoseOutputShape } : {}, | ||
| annotations: diagnoseTool.annotations | ||
| }, async () => { | ||
| const result = await diagnoseTool.execute({}); | ||
| if (typeof result === "string") return { | ||
| content: [{ | ||
| type: "text", | ||
| text: result | ||
| }], | ||
| isError: false | ||
| }; | ||
| return { | ||
| content: [{ | ||
| type: "text", | ||
| text: result.text | ||
| }], | ||
| structuredContent: result.structuredContent, | ||
| isError: false | ||
| }; | ||
| }); | ||
| Logger.warn(`@ask-llm/mcp v${version} — 5 tools, ${available.length} provider(s): ${available.join(", ") || "none"}`); | ||
| const transport = new StdioServerTransport(); | ||
| await server.connect(transport); | ||
| Logger.debug("@ask-llm/mcp listening on stdio"); | ||
| } | ||
| //#endregion | ||
| export { PROVIDERS as a, formatDiagnosticReport as c, buildProviderSpecs as i, runDiagnostics as l, getLoadedExecutor as n, createSessionUsage as o, startServer as r, formatSessionUsage as s, detectProviders as t, Logger as u }; | ||
| //# sourceMappingURL=src-DYUWMdDM.js.map |
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
269210
50%15
50%2461
62.66%160
102.53%7
16.67%19
18.75%3
200%+ Added
+ Added
- Removed
- Removed
Updated
Updated
Updated
Updated