llm-cli-gateway 2.11.1 → 2.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -23,11 +23,10 @@ import { runAcpRequest } from "./acp/runtime.js";
23
23
  import { isAcpError } from "./acp/errors.js";
24
24
  import { createApiProvider, runApiRequest, apiProviderBreakerState, } from "./api-provider.js";
25
25
  import { prepareApiRequest, apiProviderCatalogEntry, ApiModelNotAllowedError, } from "./api-request.js";
26
- import { createXaiResponse, XaiApiError, } from "./xai-api-provider.js";
27
26
  import { checkHealth } from "./health.js";
28
27
  import { clearModelRegistryCache, getAvailableCliInfo, getCliInfo, resolveModelAlias, } from "./model-registry.js";
29
28
  import { getProviderToolCapabilities } from "./provider-tool-capabilities.js";
30
- import { AsyncJobManager, } from "./async-job-manager.js";
29
+ import { AsyncJobManager, extractApiHttpStatus, extractApiErrorBody, } from "./async-job-manager.js";
31
30
  import { createJobStore } from "./job-store.js";
32
31
  import { ApprovalManager, bypassAllowedByOperator, } from "./approval-manager.js";
33
32
  import { checkReviewIntegrity } from "./review-integrity.js";
@@ -43,6 +42,7 @@ import { printDoctorJson } from "./doctor.js";
43
42
  import { createWorkspace, describeWorkspace, getWorkspace, loadWorkspaceRegistry, registerExistingWorkspace, resolveWorkspaceForProvider, validatePathInsideWorkspace, } from "./workspace-registry.js";
44
43
  import { generateSecret, hashSecret } from "./oauth.js";
45
44
  import { registerValidationTools } from "./validation-tools.js";
45
+ import { currentCaller, resolveValidationReceipt } from "./validation-receipt.js";
46
46
  import { assertUpstreamCliArgs, assertUpstreamCliEnv, buildProviderSubcommandsCompactCatalog, buildUpstreamContractReport, getCliSubcommandContract, probeInstalledCliContract, serializeCliSubcommandContract, UPSTREAM_CLI_CONTRACTS, } from "./upstream-contracts.js";
47
47
  import { buildArgvFromGeneration, deriveZodShapeFromGeneration, GROK_FLAG_GENERATION, GROK_GEN_OUTPUT_FORMAT, GROK_GEN_MAIN, GROK_GEN_PROMPT_FILE, GROK_GEN_SINGLE, GROK_GEN_TAIL, } from "./provider-codegen.js";
48
48
  import { entrypointFileURL } from "./entrypoint-url.js";
@@ -161,14 +161,15 @@ export function buildServerInstructions(asyncJobsEnabled, grokApiToolsEnabled =
161
161
  : '- Async jobs are DISABLED (persistence.backend = "none"): *_request_async and llm_job_* tools are not registered, and sync requests run to completion (no auto-deferral).';
162
162
  return `llm-cli-gateway: Multi-LLM orchestration via MCP.
163
163
 
164
- Tools: claude_request, codex_request, gemini_request, grok_request, mistral_request${apiToolsNote} (sync)${asyncToolsNote} | codex_fork_session (fork a Codex session into a new branch)
164
+ Tools: claude_request, codex_request, gemini_request, grok_request, mistral_request, devin_request${apiToolsNote} (sync)${asyncToolsNote} | codex_fork_session (fork a Codex session into a new branch)
165
165
  Validation: validate_with_models, second_opinion, compare_answers, red_team_review, consensus_check, ask_model, synthesize_validation, list_available_models | job_status/job_result (validation jobs)
166
166
  ${jobsLine}Sessions: session_create, session_list, session_set_active, session_get, session_delete, session_clear_all
167
- Other: list_models, cli_versions, upstream_contracts, provider_subcommands_* (read-only subcommand contract/drift introspection), cli_upgrade, approval_list, llm_process_health, llm_request_result (read back any persisted request sync or async by correlationId)
167
+ Other: list_models, provider_tool_capabilities, cli_versions, upstream_contracts, provider_subcommands_* (read-only subcommand contract/drift introspection), cli_upgrade, approval_list, llm_process_health, llm_request_result (read back any persisted request, sync or async, by correlationId)
168
+ Workspaces: workspace_create, workspace_list, workspace_get, workspace_register_existing_repo
168
169
 
169
170
  Key behaviors:
170
171
  ${deferralLine}
171
- - Sessions: Claude --continue, Gemini (Antigravity) --conversation <id>/--continue, Grok --resume/--continue, Mistral --resume/--continue (current Vibe defaults session logging on; doctor flags explicit session_logging.enabled=false), Codex \`exec resume <ID>\` / \`exec resume --last\` (all real CLI continuity). For Codex, sessionId must be a real Codex UUID (from ~/.codex/sessions/); gateway-generated gw-* IDs are rejected.
172
+ - Sessions: Claude --continue, Gemini (Antigravity) --conversation <id>/--continue, Grok --resume/--continue, Mistral --resume/--continue (current Vibe defaults session logging on; doctor flags explicit session_logging.enabled=false), Codex \`exec resume <ID>\` / \`exec resume --last\`, Devin --resume <id>/--continue (all real CLI continuity). For Codex, sessionId must be a real Codex UUID (from ~/.codex/sessions/); gateway-generated gw-* IDs are rejected.
172
173
  - Approval gates: opt-in via approvalStrategy:"mcp_managed".
173
174
  - Upstream drift detection: After upgrading any provider CLI (especially grok), use upstream_contracts with probeInstalled:true and provider_subcommand_drift for declared subcommand help surfaces. Probes are safe, read-only --help checks.
174
175
  - Idle timeout kills stuck processes (default 10min, configurable via idleTimeoutMs).
@@ -280,6 +281,11 @@ export const WORKSPACE_ALIAS_SCHEMA = z
280
281
  .describe("Registered workspace alias. Remote clients use aliases, not absolute paths.");
281
282
  export const SESSION_PROVIDER_VALUES = PROVIDER_TYPES;
282
283
  export const SESSION_PROVIDER_ENUM = z.enum(SESSION_PROVIDER_VALUES);
284
+ export function sessionProviderValuesFor(providers) {
285
+ return [
286
+ ...new Set([...SESSION_PROVIDER_VALUES, ...enabledApiProviders(providers).map(p => p.name)]),
287
+ ];
288
+ }
283
289
  let activeServer = null;
284
290
  let activeHttpGateway = null;
285
291
  export function resolveGatewayServerRuntime(deps = {}, options = {}) {
@@ -458,7 +464,7 @@ async function awaitJobOrDefer(cli, args, corrId, idleTimeoutMs, outputFormat, f
458
464
  message: `Execution exceeded sync deadline (${SYNC_DEADLINE_MS}ms). Poll with llm_job_status, collect with llm_job_result.`,
459
465
  };
460
466
  }
461
- async function awaitApiJobOrDefer(provider, apiRequest, corrId, runtime = resolveGatewayServerRuntime(), onComplete, flightRecorderEntry, extractUsage) {
467
+ async function awaitApiJobOrDefer(provider, apiRequest, corrId, runtime = resolveGatewayServerRuntime(), onComplete, flightRecorderEntry, extractUsage, forceRefresh, forceInline) {
462
468
  let onCompleteOwnedByCaller = onComplete !== undefined;
463
469
  const consumeOnComplete = () => {
464
470
  if (!onCompleteOwnedByCaller || !onComplete)
@@ -474,13 +480,28 @@ async function awaitApiJobOrDefer(provider, apiRequest, corrId, runtime = resolv
474
480
  const deferralAvailable = runtime.persistence.backend !== "none" &&
475
481
  runtime.persistence.asyncJobsEnabled &&
476
482
  runtime.asyncJobManager.hasStore();
477
- if (SYNC_DEADLINE_MS === 0 || !deferralAvailable) {
483
+ if (SYNC_DEADLINE_MS === 0 || !deferralAvailable || forceInline) {
478
484
  try {
479
485
  const result = await runApiRequest(provider, apiRequest, runtime.logger);
480
- return { stdout: result.text, stderr: "", code: 0 };
486
+ return {
487
+ stdout: result.text,
488
+ stderr: "",
489
+ code: 0,
490
+ usage: result.usage,
491
+ httpStatus: result.httpStatus,
492
+ responseId: result.responseId ?? null,
493
+ model: result.model,
494
+ status: result.status ?? null,
495
+ };
481
496
  }
482
497
  catch (err) {
483
- return { stdout: "", stderr: err.message, code: 1 };
498
+ return {
499
+ stdout: "",
500
+ stderr: err.message,
501
+ code: 1,
502
+ httpStatus: extractApiHttpStatus(err) ?? undefined,
503
+ errorBody: extractApiErrorBody(err),
504
+ };
484
505
  }
485
506
  finally {
486
507
  consumeOnComplete();
@@ -495,6 +516,7 @@ async function awaitApiJobOrDefer(provider, apiRequest, corrId, runtime = resolv
495
516
  onComplete,
496
517
  flightRecorderEntry,
497
518
  extractUsage,
519
+ forceRefresh,
498
520
  });
499
521
  onCompleteOwnedByCaller = false;
500
522
  }
@@ -517,6 +539,11 @@ async function awaitApiJobOrDefer(provider, apiRequest, corrId, runtime = resolv
517
539
  stdout: result.stdout,
518
540
  stderr: result.stderr || result.error || "",
519
541
  code: result.exitCode ?? 1,
542
+ usage: result.apiUsage,
543
+ httpStatus: result.httpStatus,
544
+ responseId: result.responseId,
545
+ model: result.model,
546
+ errorBody: result.errorBody,
520
547
  };
521
548
  }
522
549
  await new Promise(resolve => setTimeout(resolve, SYNC_POLL_INTERVAL_MS));
@@ -780,22 +807,41 @@ function registerWorkspaceTools(server, runtime) {
780
807
  }
781
808
  });
782
809
  }
783
- function createErrorResponse(cli, code, stderr, correlationId, error) {
810
+ export function createErrorResponse(cli, code, stderr, correlationId, error, apiError) {
784
811
  let errorMessage = `Error executing ${cli} CLI`;
785
812
  const isLaunchExit = code === 127 || code === -4058;
813
+ const isOutputOverflow = Boolean(error) && /Output exceeded maximum size/i.test(error.message);
814
+ const authProbe = `${error?.message ?? ""}\n${stderr ?? ""}`;
815
+ const isAuthError = /not logged in|please run .*login|unauthorized|\b401\b|authentication failed|invalid api key|no api key|expired token|re-?authenticate/i.test(authProbe);
816
+ const authRemediation = {
817
+ claude: "Run `claude login` (or set ANTHROPIC_API_KEY) and retry.",
818
+ codex: "Run `codex login` and retry.",
819
+ gemini: "Re-authenticate the Antigravity `agy` CLI and retry.",
820
+ grok: "Run `grok login` (or re-authenticate) and retry.",
821
+ mistral: "Run `vibe --setup` (or re-authenticate) and retry.",
822
+ devin: "Run `devin auth login` (or set WINDSURF_API_KEY) and retry.",
823
+ };
786
824
  if (error) {
787
825
  errorMessage += `:\n${error.message}`;
788
826
  if (error.message.includes("ENOENT")) {
789
827
  errorMessage += `\n\nThe '${cli}' command was not found. Please ensure ${cli} CLI is installed and in your PATH.`;
790
828
  }
829
+ else if (/EACCES|EPERM/.test(error.message)) {
830
+ errorMessage += `\n\nThe '${cli}' binary is not executable or permission was denied (EACCES/EPERM). Check its file permissions and PATH.`;
831
+ }
832
+ else if (isOutputOverflow) {
833
+ errorMessage += `\n\nThe ${cli} output exceeded the 50MB cap. Narrow the request, ask for structured/JSON output, or split the work into smaller calls.`;
834
+ }
791
835
  logger.error(`[${correlationId || "unknown"}] ${cli} CLI execution failed:`, error.message);
792
836
  }
793
837
  else if (code === 124) {
794
838
  errorMessage += `: Command timed out\n${stderr}`;
839
+ errorMessage += `\n\nIncrease timeoutMs, or use the *_request_async variant and poll with llm_job_status / llm_job_result.`;
795
840
  logger.error(`[${correlationId || "unknown"}] ${cli} CLI timed out`);
796
841
  }
797
842
  else if (code === 125) {
798
843
  errorMessage += `: Process killed due to inactivity\n${stderr}`;
844
+ errorMessage += `\n\nThe CLI may have been awaiting interactive approval. Pass a non-interactive permission/approval option, or raise idleTimeoutMs.`;
799
845
  logger.error(`[${correlationId || "unknown"}] ${cli} CLI killed due to inactivity`);
800
846
  }
801
847
  else if (isLaunchExit) {
@@ -804,8 +850,14 @@ function createErrorResponse(cli, code, stderr, correlationId, error) {
804
850
  }
805
851
  else if (code !== 0) {
806
852
  errorMessage += ` (exit code ${code}):\n${stderr}`;
853
+ if (!stderr || stderr.trim().length === 0) {
854
+ errorMessage += `\n\nNo error output was captured. The CLI may not be authenticated, may be awaiting interactive input, or may have rejected an unsupported flag. Re-run with DEBUG=1 for more detail.`;
855
+ }
807
856
  logger.error(`[${correlationId || "unknown"}] ${cli} CLI failed with exit code ${code}`);
808
857
  }
858
+ if (isAuthError) {
859
+ errorMessage += `\n\n${authRemediation[cli] ?? `Re-authenticate the ${cli} CLI and retry.`}`;
860
+ }
809
861
  return {
810
862
  content: [{ type: "text", text: errorMessage }],
811
863
  isError: true,
@@ -814,20 +866,30 @@ function createErrorResponse(cli, code, stderr, correlationId, error) {
814
866
  correlationId: correlationId || null,
815
867
  cli,
816
868
  exitCode: code,
817
- errorCategory: code === 124
818
- ? "timeout"
819
- : code === 125
820
- ? "idle_timeout"
821
- : error
822
- ? "spawn_error"
823
- : isLaunchExit
824
- ? "spawn_error"
825
- : "cli_error",
869
+ errorCategory: isAuthError
870
+ ? "auth_error"
871
+ : code === 124
872
+ ? "timeout"
873
+ : code === 125
874
+ ? "idle_timeout"
875
+ : isOutputOverflow
876
+ ? "output_overflow"
877
+ : error
878
+ ? "spawn_error"
879
+ : isLaunchExit
880
+ ? "spawn_error"
881
+ : "cli_error",
882
+ ...(apiError
883
+ ? {
884
+ httpStatus: apiError.httpStatus ?? undefined,
885
+ responseBody: apiError.responseBody,
886
+ }
887
+ : {}),
826
888
  },
827
889
  };
828
890
  }
829
891
  export function extractUsageAndCost(cli, output, outputFormat, ctx) {
830
- if (cli === "claude" && outputFormat === "stream-json") {
892
+ if (cli === "claude" && (outputFormat === "stream-json" || outputFormat === "json")) {
831
893
  const parsed = parseStreamJson(output);
832
894
  if (!parsed.usage) {
833
895
  return { costUsd: parsed.costUsd ?? undefined };
@@ -1191,9 +1253,30 @@ function registerBaseResources(server, runtime) {
1191
1253
  const contents = await runtime.resourceProvider.readResource(uri.href);
1192
1254
  return { contents: contents ? [contents] : [] };
1193
1255
  });
1256
+ const validationReceiptStore = runtime.persistence.backend === "sqlite"
1257
+ ? runtime.asyncJobManager.getValidationRunStore()
1258
+ : null;
1259
+ if (validationReceiptStore) {
1260
+ server.registerResource("validation-receipt", new ResourceTemplate("validation-receipt://{validationId}", { list: undefined }), {
1261
+ title: "Validation Receipt",
1262
+ description: "Immutable receipt of a terminal cross-LLM validation run (own-or-not-found).",
1263
+ mimeType: "application/json",
1264
+ }, async (uri, variables) => {
1265
+ const validationId = Array.isArray(variables.validationId)
1266
+ ? variables.validationId[0]
1267
+ : variables.validationId;
1268
+ runtime.logger.debug(`Reading validation-receipt://${validationId}`);
1269
+ const result = resolveValidationReceipt({ asyncJobManager: runtime.asyncJobManager, validationRunStore: validationReceiptStore }, String(validationId), { caller: currentCaller() });
1270
+ return {
1271
+ contents: [
1272
+ { uri: uri.href, mimeType: "application/json", text: JSON.stringify(result, null, 2) },
1273
+ ],
1274
+ };
1275
+ });
1276
+ }
1194
1277
  }
1195
1278
  function resolvePromptOrPartsForPrep(args) {
1196
- const hasPrompt = typeof args.prompt === "string" && args.prompt.length > 0;
1279
+ const hasPrompt = typeof args.prompt === "string" && args.prompt.trim().length > 0;
1197
1280
  const hasParts = args.promptParts !== undefined;
1198
1281
  if (hasPrompt && hasParts) {
1199
1282
  return {
@@ -1705,6 +1788,15 @@ export function prepareGeminiRequest(params, runtime = resolveGatewayServerRunti
1705
1788
  if (params.yolo || effectiveApprovalMode === "yolo") {
1706
1789
  args.push("--dangerously-skip-permissions");
1707
1790
  }
1791
+ if (params.project !== undefined && params.newProject) {
1792
+ return createErrorResponse(params.operation, 1, "", corrId, new Error("project and newProject are mutually exclusive"));
1793
+ }
1794
+ if (params.project !== undefined && params.project !== "") {
1795
+ args.push("--project", params.project);
1796
+ }
1797
+ if (params.newProject) {
1798
+ args.push("--new-project");
1799
+ }
1708
1800
  return {
1709
1801
  corrId,
1710
1802
  effectivePrompt,
@@ -1818,12 +1910,30 @@ export function prepareGrokRequest(params, runtime = resolveGatewayServerRuntime
1818
1910
  }
1819
1911
  args.push(...buildArgvFromGeneration(grokContract, GROK_GEN_SINGLE, genParams));
1820
1912
  args.push(...buildArgvFromGeneration(grokContract, GROK_GEN_TAIL, genParams));
1913
+ const hasNativeWorktree = params.nativeWorktree === true ||
1914
+ (typeof params.nativeWorktree === "string" && params.nativeWorktree.length > 0);
1821
1915
  if (params.nativeWorktree === true) {
1822
1916
  args.push("--worktree");
1823
1917
  }
1824
1918
  else if (typeof params.nativeWorktree === "string" && params.nativeWorktree.length > 0) {
1825
1919
  args.push("--worktree", params.nativeWorktree);
1826
1920
  }
1921
+ if (params.worktreeRef !== undefined && params.worktreeRef !== "") {
1922
+ if (!hasNativeWorktree) {
1923
+ return createErrorResponse(params.operation, 1, "", corrId, new Error("worktreeRef: requires nativeWorktree (grok --worktree-ref needs --worktree)"));
1924
+ }
1925
+ args.push("--worktree-ref", params.worktreeRef);
1926
+ }
1927
+ if (params.forkSession) {
1928
+ args.push("--fork-session");
1929
+ }
1930
+ if (params.jsonSchema !== undefined) {
1931
+ const schemaArg = typeof params.jsonSchema === "string" ? params.jsonSchema : JSON.stringify(params.jsonSchema);
1932
+ if (!schemaArg.trim()) {
1933
+ return createErrorResponse(params.operation, 1, "", corrId, new Error("jsonSchema: must be a non-empty JSON Schema string or object"));
1934
+ }
1935
+ args.push("--json-schema", schemaArg);
1936
+ }
1827
1937
  return {
1828
1938
  corrId,
1829
1939
  effectivePrompt,
@@ -1960,7 +2070,7 @@ export function buildMistralRetryPrep(params, recoveryModel) {
1960
2070
  addDir: params.addDir,
1961
2071
  });
1962
2072
  }
1963
- function buildCliResponse(cli, stdout, optimizeResponse, corrId, sessionId, prep, durationMs, resumable, outputFormat, warnings) {
2073
+ export function buildCliResponse(cli, stdout, optimizeResponse, corrId, sessionId, prep, durationMs, resumable, outputFormat, warnings) {
1964
2074
  let finalStdout = stdout;
1965
2075
  if (cli === "codex" && outputFormat !== "json") {
1966
2076
  finalStdout = codexDisplayText(stdout);
@@ -1978,9 +2088,37 @@ function buildCliResponse(cli, stdout, optimizeResponse, corrId, sessionId, prep
1978
2088
  .join("\n");
1979
2089
  finalStdout += `\n\n⚠️ Review Integrity Warnings (score: ${prep.reviewIntegrity.totalScore}):\n${warnings}`;
1980
2090
  }
2091
+ const derivedWarnings = [];
2092
+ const extraStructured = {};
2093
+ let claudeResultError = false;
2094
+ if (cli === "claude") {
2095
+ const parsedResult = parseStreamJson(stdout);
2096
+ if (parsedResult.isError) {
2097
+ claudeResultError = true;
2098
+ extraStructured.resultIsError = true;
2099
+ derivedWarnings.push({
2100
+ code: "claude_result_error",
2101
+ message: "Claude exited 0 but the result event reported is_error:true (e.g. max-turns or mid-run error). The returned text may be partial.",
2102
+ });
2103
+ }
2104
+ }
2105
+ if (cli === "codex") {
2106
+ const parsedCodex = parseCodexJsonStream(stdout);
2107
+ if (parsedCodex.threadId) {
2108
+ extraStructured.codexSessionId = parsedCodex.threadId;
2109
+ }
2110
+ }
2111
+ if (!claudeResultError && finalStdout.trim().length === 0) {
2112
+ extraStructured.emptyOutput = true;
2113
+ derivedWarnings.push({
2114
+ code: "empty_output",
2115
+ message: `${cli} exited 0 but produced no assistant output. The request may have been a no-op, hit a guardrail, or awaited input.`,
2116
+ });
2117
+ }
1981
2118
  const response = {
1982
2119
  content: [{ type: "text", text: finalStdout }],
1983
2120
  structuredContent: {
2121
+ ...extraStructured,
1984
2122
  response: finalStdout,
1985
2123
  model: prep.resolvedModel || "default",
1986
2124
  cli,
@@ -2011,8 +2149,9 @@ function buildCliResponse(cli, stdout, optimizeResponse, corrId, sessionId, prep
2011
2149
  if (prep.reviewIntegrity && prep.reviewIntegrity.violations.length > 0) {
2012
2150
  response.reviewIntegrity = prep.reviewIntegrity;
2013
2151
  }
2014
- if (warnings && warnings.length > 0) {
2015
- response.warnings = warnings;
2152
+ const allWarnings = [...(warnings ?? []), ...derivedWarnings];
2153
+ if (allWarnings.length > 0) {
2154
+ response.warnings = allWarnings;
2016
2155
  }
2017
2156
  return response;
2018
2157
  }
@@ -2123,11 +2262,18 @@ async function getExistingSessionForProvider(sessionManager, sessionId, provider
2123
2262
  }
2124
2263
  return existing;
2125
2264
  }
2126
- function asXaiApiError(error) {
2127
- if (error instanceof XaiApiError)
2128
- return error;
2129
- const cause = error?.cause;
2130
- return cause instanceof XaiApiError ? cause : null;
2265
+ function grokInputToMessages(input, instructions) {
2266
+ const messages = [];
2267
+ if (instructions && instructions.length > 0) {
2268
+ messages.push({ role: "system", content: instructions });
2269
+ }
2270
+ if (typeof input === "string") {
2271
+ messages.push({ role: "user", content: input });
2272
+ }
2273
+ else {
2274
+ messages.push(...input);
2275
+ }
2276
+ return messages;
2131
2277
  }
2132
2278
  function buildGrokApiToolResponse(args) {
2133
2279
  let text = args.result.text;
@@ -2221,12 +2367,12 @@ export async function handleGrokApiRequest(deps, params) {
2221
2367
  const session = await resolveGrokApiSession(params, runtime);
2222
2368
  sessionId = session.sessionId;
2223
2369
  previousResponseId = session.previousResponseId;
2224
- const call = (prev) => createXaiResponse({
2370
+ const grokProvider = createApiProvider("grok-api", "xai-responses");
2371
+ const call = (prev) => runApiRequest(grokProvider, {
2225
2372
  baseUrl: xaiConfig.baseUrl,
2226
2373
  apiKey,
2227
2374
  model: prep.resolvedModel,
2228
- input: prep.input,
2229
- instructions: prep.instructions,
2375
+ messages: grokInputToMessages(prep.input, prep.instructions),
2230
2376
  previousResponseId: prev,
2231
2377
  maxOutputTokens: params.maxOutputTokens,
2232
2378
  temperature: params.temperature,
@@ -2239,8 +2385,7 @@ export async function handleGrokApiRequest(deps, params) {
2239
2385
  result = await call(previousResponseId);
2240
2386
  }
2241
2387
  catch (error) {
2242
- const xaiError = asXaiApiError(error);
2243
- if (xaiError?.status === 404 && previousResponseId) {
2388
+ if (extractApiHttpStatus(error) === 404 && previousResponseId) {
2244
2389
  runtime.logger.warn(`[${corrId}] xAI previous_response_id was rejected; clearing stale session metadata and retrying fresh`);
2245
2390
  await runtime.sessionManager.updateSessionMetadata(sessionId, {
2246
2391
  xaiPreviousResponseId: null,
@@ -2286,10 +2431,9 @@ export async function handleGrokApiRequest(deps, params) {
2286
2431
  catch (error) {
2287
2432
  durationMs = Math.max(0, Date.now() - startTime);
2288
2433
  const err = error;
2289
- const xaiError = asXaiApiError(error);
2290
2434
  runtime.logger.error(`[${corrId}] grok_api_request failed`, err.message);
2291
2435
  safeFlightComplete(corrId, {
2292
- response: xaiError?.responseText ?? "",
2436
+ response: extractApiErrorBody(error) ?? "",
2293
2437
  durationMs,
2294
2438
  retryCount: 0,
2295
2439
  circuitBreakerState: "closed",
@@ -2304,10 +2448,63 @@ export async function handleGrokApiRequest(deps, params) {
2304
2448
  runtime.performanceMetrics.recordRequest("grok-api", durationMs || Math.max(0, Date.now() - startTime), wasSuccessful);
2305
2449
  }
2306
2450
  }
2307
- function buildApiProviderCall(providerRuntime, params) {
2451
+ function resolveApiProviderInput(params, toolName, corrId) {
2452
+ const inputResolution = resolvePromptOrPartsForPrep({
2453
+ prompt: params.prompt,
2454
+ promptParts: params.promptParts,
2455
+ operation: toolName,
2456
+ correlationId: corrId,
2457
+ });
2458
+ if (!inputResolution.ok)
2459
+ return { ok: false, error: inputResolution.error };
2460
+ const system = params.promptParts?.system && params.promptParts.system.length > 0
2461
+ ? params.promptParts.system
2462
+ : params.system;
2463
+ let userContent = params.promptParts
2464
+ ? buildXaiPromptPartsUserContent(params.promptParts)
2465
+ : (params.prompt ?? "");
2466
+ let optimizationApplied = false;
2467
+ if (params.optimizePrompt) {
2468
+ const optimized = optimizePromptText(userContent);
2469
+ logOptimizationTokens("prompt", corrId, userContent, optimized);
2470
+ userContent = optimized;
2471
+ optimizationApplied = true;
2472
+ }
2473
+ return {
2474
+ ok: true,
2475
+ userContent,
2476
+ system,
2477
+ effectivePrompt: buildXaiPromptPartsEffectivePrompt(system, userContent),
2478
+ optimizationApplied,
2479
+ stablePrefixHash: inputResolution.stablePrefixHash,
2480
+ stablePrefixTokens: inputResolution.stablePrefixTokens,
2481
+ };
2482
+ }
2483
+ async function resolveApiSession(providerName, continuity, params, runtime) {
2484
+ const label = `${providerName} API Session`;
2485
+ const readPrev = (s) => continuity === "server-side-id" &&
2486
+ !params.createNewSession &&
2487
+ typeof s.metadata?.apiPreviousResponseId === "string"
2488
+ ? s.metadata.apiPreviousResponseId
2489
+ : undefined;
2490
+ if (params.sessionId) {
2491
+ const existing = await getExistingSessionForProvider(runtime.sessionManager, params.sessionId, providerName);
2492
+ const session = existing ??
2493
+ (await runtime.sessionManager.createSession(providerName, label, params.sessionId));
2494
+ return { sessionId: session.id, previousResponseId: readPrev(session) };
2495
+ }
2496
+ if (continuity === "server-side-id" && !params.createNewSession) {
2497
+ const active = await getCallerOwnedActiveSession(runtime.sessionManager, providerName);
2498
+ if (active)
2499
+ return { sessionId: active.id, previousResponseId: readPrev(active) };
2500
+ }
2501
+ const session = await runtime.sessionManager.createSession(providerName, label, `${GATEWAY_SESSION_PREFIX}${randomUUID()}`);
2502
+ return { sessionId: session.id };
2503
+ }
2504
+ function buildApiProviderCall(providerRuntime, params, userContent, system) {
2308
2505
  const apiRequest = prepareApiRequest(providerRuntime, {
2309
- prompt: params.prompt ?? "",
2310
- system: params.system,
2506
+ prompt: userContent,
2507
+ system,
2311
2508
  model: params.model,
2312
2509
  maxOutputTokens: params.maxOutputTokens,
2313
2510
  temperature: params.temperature,
@@ -2318,16 +2515,32 @@ function buildApiProviderCall(providerRuntime, params) {
2318
2515
  const provider = createApiProvider(providerRuntime.name, providerRuntime.kind);
2319
2516
  return { provider, apiRequest };
2320
2517
  }
2321
- function buildApiSuccessResponse(text, corrId, providerName) {
2322
- return {
2518
+ function buildApiSuccessResponse(text, corrId, providerName, telemetry) {
2519
+ const usage = telemetry?.usage;
2520
+ const response = {
2323
2521
  content: [{ type: "text", text }],
2324
2522
  structuredContent: {
2325
2523
  response: text,
2326
- correlationId: corrId,
2524
+ provider: providerName,
2327
2525
  cli: providerName,
2526
+ model: telemetry?.model,
2527
+ correlationId: corrId,
2528
+ sessionId: telemetry?.sessionId ?? null,
2529
+ responseId: telemetry?.responseId ?? null,
2530
+ previousResponseId: telemetry?.previousResponseId ?? null,
2531
+ stalePreviousResponseCleared: telemetry?.stalePreviousResponseCleared ?? false,
2532
+ status: telemetry?.status ?? undefined,
2533
+ httpStatus: telemetry?.httpStatus ?? undefined,
2534
+ inputTokens: usage?.inputTokens,
2535
+ outputTokens: usage?.outputTokens,
2536
+ cacheReadTokens: usage?.cacheReadTokens,
2537
+ costUsd: usage?.costUsd,
2328
2538
  exitCode: 0,
2329
2539
  },
2330
2540
  };
2541
+ if (telemetry?.sessionId)
2542
+ response.sessionId = telemetry.sessionId;
2543
+ return response;
2331
2544
  }
2332
2545
  export async function handleApiProviderRequest(runtimeArg, providerRuntime, params) {
2333
2546
  const toolName = `api_${providerRuntime.name}_request`;
@@ -2335,18 +2548,115 @@ export async function handleApiProviderRequest(runtimeArg, providerRuntime, para
2335
2548
  const startTime = Date.now();
2336
2549
  let wasSuccessful = false;
2337
2550
  try {
2338
- if (!params.prompt || params.prompt.trim().length === 0) {
2339
- return createErrorResponse(toolName, 1, "prompt is required and cannot be empty", corrId);
2551
+ const resolved = resolveApiProviderInput(params, toolName, corrId);
2552
+ if (!resolved.ok)
2553
+ return resolved.error;
2554
+ const { provider, apiRequest } = buildApiProviderCall(providerRuntime, params, resolved.userContent, resolved.system);
2555
+ const serverSide = provider.continuity === "server-side-id";
2556
+ const wantsSession = serverSide || params.sessionId !== undefined || params.createNewSession === true;
2557
+ let sessionId;
2558
+ let previousResponseId;
2559
+ if (wantsSession) {
2560
+ const session = await resolveApiSession(providerRuntime.name, provider.continuity, params, runtimeArg);
2561
+ sessionId = session.sessionId;
2562
+ previousResponseId = session.previousResponseId;
2563
+ await runtimeArg.sessionManager.updateSessionUsage(sessionId);
2564
+ if (previousResponseId)
2565
+ apiRequest.previousResponseId = previousResponseId;
2566
+ }
2567
+ const flightRecorderEntry = {
2568
+ model: apiRequest.model,
2569
+ prompt: resolved.effectivePrompt,
2570
+ sessionId,
2571
+ stablePrefixHash: resolved.stablePrefixHash ?? undefined,
2572
+ stablePrefixTokens: resolved.stablePrefixTokens ?? undefined,
2573
+ };
2574
+ safeFlightStart({
2575
+ correlationId: corrId,
2576
+ cli: providerRuntime.name,
2577
+ model: apiRequest.model,
2578
+ prompt: resolved.effectivePrompt,
2579
+ system: resolved.system,
2580
+ sessionId,
2581
+ stablePrefixHash: resolved.stablePrefixHash ?? undefined,
2582
+ stablePrefixTokens: resolved.stablePrefixTokens ?? undefined,
2583
+ }, runtimeArg);
2584
+ let result = await awaitApiJobOrDefer(provider, apiRequest, corrId, runtimeArg, undefined, flightRecorderEntry, undefined, params.forceRefresh, serverSide);
2585
+ let stalePreviousResponseCleared = false;
2586
+ if (serverSide &&
2587
+ !isDeferredResponse(result) &&
2588
+ result.code !== 0 &&
2589
+ result.httpStatus === 404 &&
2590
+ sessionId &&
2591
+ previousResponseId) {
2592
+ await runtimeArg.sessionManager.updateSessionMetadata(sessionId, {
2593
+ apiPreviousResponseId: null,
2594
+ apiResponseCreatedAt: null,
2595
+ });
2596
+ apiRequest.previousResponseId = undefined;
2597
+ previousResponseId = undefined;
2598
+ stalePreviousResponseCleared = true;
2599
+ const retry = await awaitApiJobOrDefer(provider, apiRequest, corrId, runtimeArg, undefined, flightRecorderEntry, undefined, true, serverSide);
2600
+ if (!isDeferredResponse(retry))
2601
+ result = retry;
2340
2602
  }
2341
- const { provider, apiRequest } = buildApiProviderCall(providerRuntime, params);
2342
- const result = await awaitApiJobOrDefer(provider, apiRequest, corrId, runtimeArg);
2343
2603
  if (isDeferredResponse(result))
2344
- return buildDeferredToolResponse(result);
2604
+ return buildDeferredToolResponse(result, sessionId);
2605
+ const durationMs = Math.max(0, Date.now() - startTime);
2345
2606
  if (result.code !== 0) {
2346
- return createErrorResponse(toolName, result.code, result.stderr, corrId);
2607
+ safeFlightComplete(corrId, {
2608
+ response: result.errorBody ?? "",
2609
+ durationMs,
2610
+ retryCount: 0,
2611
+ circuitBreakerState: "closed",
2612
+ optimizationApplied: resolved.optimizationApplied,
2613
+ exitCode: result.code,
2614
+ httpStatus: result.httpStatus ?? undefined,
2615
+ errorMessage: result.stderr,
2616
+ status: "failed",
2617
+ }, runtimeArg);
2618
+ return createErrorResponse(toolName, result.code, result.stderr, corrId, undefined, {
2619
+ httpStatus: result.httpStatus,
2620
+ responseBody: result.errorBody,
2621
+ });
2347
2622
  }
2348
2623
  wasSuccessful = true;
2349
- return buildApiSuccessResponse(result.stdout, corrId, providerRuntime.name);
2624
+ if (serverSide && sessionId && result.responseId) {
2625
+ await runtimeArg.sessionManager.updateSessionMetadata(sessionId, {
2626
+ apiPreviousResponseId: result.responseId,
2627
+ apiResponseCreatedAt: new Date().toISOString(),
2628
+ });
2629
+ }
2630
+ let text = result.stdout;
2631
+ if (params.optimizeResponse) {
2632
+ const optimized = optimizeResponseText(text);
2633
+ logOptimizationTokens("response", corrId, text, optimized);
2634
+ text = optimized;
2635
+ }
2636
+ safeFlightComplete(corrId, {
2637
+ response: text,
2638
+ durationMs,
2639
+ retryCount: 0,
2640
+ circuitBreakerState: "closed",
2641
+ optimizationApplied: resolved.optimizationApplied || (params.optimizeResponse ?? false),
2642
+ exitCode: 0,
2643
+ httpStatus: result.httpStatus ?? undefined,
2644
+ status: "completed",
2645
+ inputTokens: result.usage?.inputTokens,
2646
+ outputTokens: result.usage?.outputTokens,
2647
+ cacheReadTokens: result.usage?.cacheReadTokens,
2648
+ costUsd: result.usage?.costUsd,
2649
+ }, runtimeArg);
2650
+ return buildApiSuccessResponse(text, corrId, providerRuntime.name, {
2651
+ model: result.model,
2652
+ httpStatus: result.httpStatus,
2653
+ responseId: result.responseId,
2654
+ usage: result.usage,
2655
+ sessionId,
2656
+ previousResponseId,
2657
+ stalePreviousResponseCleared,
2658
+ status: result.status,
2659
+ });
2350
2660
  }
2351
2661
  catch (err) {
2352
2662
  if (err instanceof ApiModelNotAllowedError) {
@@ -2362,15 +2672,22 @@ export function handleApiProviderRequestAsync(runtimeArg, providerRuntime, param
2362
2672
  const toolName = `api_${providerRuntime.name}_request_async`;
2363
2673
  const corrId = params.correlationId ?? randomUUID();
2364
2674
  try {
2365
- if (!params.prompt || params.prompt.trim().length === 0) {
2366
- return createErrorResponse(toolName, 1, "prompt is required and cannot be empty", corrId);
2367
- }
2368
- const { provider, apiRequest } = buildApiProviderCall(providerRuntime, params);
2675
+ const resolved = resolveApiProviderInput(params, toolName, corrId);
2676
+ if (!resolved.ok)
2677
+ return resolved.error;
2678
+ const { provider, apiRequest } = buildApiProviderCall(providerRuntime, params, resolved.userContent, resolved.system);
2369
2679
  const outcome = runtimeArg.asyncJobManager.startHttpJob({
2370
2680
  provider,
2371
2681
  apiRequest,
2372
2682
  correlationId: corrId,
2373
2683
  writeFlightStart: true,
2684
+ forceRefresh: params.forceRefresh,
2685
+ flightRecorderEntry: {
2686
+ model: apiRequest.model,
2687
+ prompt: resolved.effectivePrompt,
2688
+ stablePrefixHash: resolved.stablePrefixHash ?? undefined,
2689
+ stablePrefixTokens: resolved.stablePrefixTokens ?? undefined,
2690
+ },
2374
2691
  });
2375
2692
  return buildDeferredToolResponse({
2376
2693
  deferred: true,
@@ -2393,7 +2710,13 @@ const ApiReasoningEffortSchema = z.enum(["none", "low", "medium", "high"]);
2393
2710
  export function registerApiProviderTools(server, runtime, providers, asyncJobsEnabled) {
2394
2711
  const registered = [];
2395
2712
  const inputSchema = {
2396
- prompt: z.string().min(1).max(100000).optional().describe("Prompt text for the API provider"),
2713
+ prompt: z
2714
+ .string()
2715
+ .min(1)
2716
+ .max(100000)
2717
+ .optional()
2718
+ .describe("Prompt text (mutually exclusive with promptParts)"),
2719
+ promptParts: PromptPartsSchema.optional().describe("Cache-aware structured prompt { system?, tools?, context?, task }; mutually exclusive with prompt"),
2397
2720
  system: z.string().max(100000).optional().describe("Optional system instruction"),
2398
2721
  model: z
2399
2722
  .string()
@@ -2401,18 +2724,56 @@ export function registerApiProviderTools(server, runtime, providers, asyncJobsEn
2401
2724
  .optional()
2402
2725
  .describe("Model id; defaults to the provider default_model"),
2403
2726
  correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
2404
- maxOutputTokens: z.number().int().positive().max(100000000).optional(),
2405
- temperature: z.number().finite().min(0).max(2).optional(),
2406
- topP: z.number().finite().min(0).max(1).optional(),
2407
- reasoningEffort: ApiReasoningEffortSchema.optional(),
2408
- timeoutMs: z.number().int().min(30_000).max(3_600_000).optional(),
2727
+ maxOutputTokens: z
2728
+ .number()
2729
+ .int()
2730
+ .positive()
2731
+ .max(100000000)
2732
+ .optional()
2733
+ .describe("Max output tokens. Bounded to safe integers <= 100000000. Anthropic-kind api providers default max output to 4096 tokens unless maxOutputTokens is set."),
2734
+ temperature: z
2735
+ .number()
2736
+ .finite()
2737
+ .min(0)
2738
+ .max(2)
2739
+ .optional()
2740
+ .describe("Sampling temperature passed to the provider API"),
2741
+ topP: z
2742
+ .number()
2743
+ .finite()
2744
+ .min(0)
2745
+ .max(1)
2746
+ .optional()
2747
+ .describe("Nucleus sampling top_p passed to the provider API"),
2748
+ reasoningEffort: ApiReasoningEffortSchema.optional().describe("Reasoning effort (none|low|medium|high). Only forwarded by the xai adapter; ignored by other provider kinds."),
2749
+ timeoutMs: z
2750
+ .number()
2751
+ .int()
2752
+ .min(30_000)
2753
+ .max(3_600_000)
2754
+ .optional()
2755
+ .describe("HTTP request timeout in ms (min 30s, max 1h, default 10m)"),
2756
+ optimizePrompt: z.boolean().optional().describe("Optimize the prompt before sending"),
2757
+ optimizeResponse: z
2758
+ .boolean()
2759
+ .optional()
2760
+ .describe("Optimize the response text (sync path only)"),
2761
+ forceRefresh: z.boolean().optional().describe("Bypass dedup and force a fresh request"),
2762
+ sessionId: z
2763
+ .string()
2764
+ .optional()
2765
+ .describe("Gateway session to resolve/track (sync tool; reuse keeps it alive). On the async api handler sessionId and createNewSession are currently inert."),
2766
+ createNewSession: z
2767
+ .boolean()
2768
+ .optional()
2769
+ .describe("Force a fresh session instead of reusing the active one. On the async api handler sessionId and createNewSession are currently inert."),
2409
2770
  };
2410
2771
  for (const providerRuntime of enabledApiProviders(providers)) {
2411
2772
  const name = providerRuntime.name;
2412
- server.tool(`api_${name}_request`, `Run a request against the "${name}" API provider (kind: ${providerRuntime.kind}) synchronously. Registered only when [providers.${name}] is configured and enabled.`, inputSchema, { title: `${name} API request`, readOnlyHint: false, openWorldHint: true }, async (params) => handleApiProviderRequest(runtime, providerRuntime, params));
2773
+ server.tool(`api_${name}_request`, `Run a request against the "${name}" API provider (kind: ${providerRuntime.kind}) synchronously. Registered only when [providers.${name}] is configured and enabled. OpenRouter-kind providers require usage_include=true in their provider config for cost reporting.`, inputSchema, { title: `${name} API request`, readOnlyHint: false, openWorldHint: true }, async (params) => handleApiProviderRequest(runtime, providerRuntime, params));
2413
2774
  registered.push(`api_${name}_request`);
2414
2775
  if (asyncJobsEnabled) {
2415
- server.tool(`api_${name}_request_async`, `Start an async request against the "${name}" API provider; returns a jobId to poll with llm_job_status.`, inputSchema, { title: `${name} API request (async)`, readOnlyHint: false, openWorldHint: true }, async (params) => handleApiProviderRequestAsync(runtime, providerRuntime, params));
2776
+ server.tool(`api_${name}_request_async`, `Start an async request against the "${name}" API provider; returns a jobId to poll with llm_job_status. OpenRouter-kind providers require usage_include=true in their provider config for cost reporting.`, inputSchema, { title: `${name} API request (async)`, readOnlyHint: false, openWorldHint: true }, async (params) => handleApiProviderRequestAsync(runtime, providerRuntime, params));
2416
2777
  registered.push(`api_${name}_request_async`);
2417
2778
  }
2418
2779
  }
@@ -2483,6 +2844,8 @@ export async function handleGeminiRequest(deps, params) {
2483
2844
  attachments: params.attachments,
2484
2845
  skipTrust: params.skipTrust,
2485
2846
  yolo: params.yolo,
2847
+ project: params.project,
2848
+ newProject: params.newProject,
2486
2849
  }, runtime);
2487
2850
  if (!("args" in prep))
2488
2851
  return prep;
@@ -2630,6 +2993,8 @@ export async function handleGeminiRequestAsync(deps, params) {
2630
2993
  attachments: params.attachments,
2631
2994
  skipTrust: params.skipTrust,
2632
2995
  yolo: params.yolo,
2996
+ project: params.project,
2997
+ newProject: params.newProject,
2633
2998
  }, runtime);
2634
2999
  if (!("args" in prep))
2635
3000
  return prep;
@@ -2759,6 +3124,9 @@ export async function handleGrokRequest(deps, params) {
2759
3124
  restoreCode: params.restoreCode,
2760
3125
  leaderSocket: params.leaderSocket,
2761
3126
  nativeWorktree: params.nativeWorktree,
3127
+ worktreeRef: params.worktreeRef,
3128
+ forkSession: params.forkSession,
3129
+ jsonSchema: params.jsonSchema,
2762
3130
  }, runtime);
2763
3131
  if (!("args" in prep))
2764
3132
  return prep;
@@ -2927,6 +3295,9 @@ export async function handleGrokRequestAsync(deps, params) {
2927
3295
  restoreCode: params.restoreCode,
2928
3296
  leaderSocket: params.leaderSocket,
2929
3297
  nativeWorktree: params.nativeWorktree,
3298
+ worktreeRef: params.worktreeRef,
3299
+ forkSession: params.forkSession,
3300
+ jsonSchema: params.jsonSchema,
2930
3301
  }, runtime);
2931
3302
  if (!("args" in prep))
2932
3303
  return prep;
@@ -3620,6 +3991,7 @@ export function createGatewayServer(deps = {}) {
3620
3991
  registerValidationTools(server, {
3621
3992
  asyncJobManager,
3622
3993
  apiProviders: enabledApiProviders(providers),
3994
+ validationRunStore: persistence.backend === "sqlite" ? asyncJobManager.getValidationRunStore() : null,
3623
3995
  });
3624
3996
  registerWorkspaceTools(server, runtime);
3625
3997
  const apiProviderTools = registerApiProviderTools(server, runtime, providers, asyncJobsEnabled);
@@ -3720,7 +4092,7 @@ export function createGatewayServer(deps = {}) {
3720
4092
  sessionId: z
3721
4093
  .string()
3722
4094
  .optional()
3723
- .describe("Gateway session record to associate (uses the active session if omitted). Claude continuity itself is via continueSession (--continue); this ID is gateway bookkeeping, not a Claude-native session."),
4095
+ .describe("On a fresh request this id is emitted as Claude --session-id <uuid> (must be a valid UUID that does not already exist). Resume the latest cwd conversation with continueSession:true. gw-* ids are not valid Claude --session-id values."),
3724
4096
  continueSession: z
3725
4097
  .boolean()
3726
4098
  .default(false)
@@ -3754,11 +4126,11 @@ export function createGatewayServer(deps = {}) {
3754
4126
  systemPrompt: z
3755
4127
  .string()
3756
4128
  .optional()
3757
- .describe("Claude --system-prompt: replace the system prompt entirely."),
4129
+ .describe("Claude --system-prompt: replace the system prompt entirely. Mutually exclusive with appendSystemPrompt."),
3758
4130
  appendSystemPrompt: z
3759
4131
  .string()
3760
4132
  .optional()
3761
- .describe("Claude --append-system-prompt: append to the existing system prompt."),
4133
+ .describe("Claude --append-system-prompt: append to the existing system prompt. Mutually exclusive with systemPrompt."),
3762
4134
  maxBudgetUsd: z
3763
4135
  .number()
3764
4136
  .positive()
@@ -3786,7 +4158,7 @@ export function createGatewayServer(deps = {}) {
3786
4158
  jsonSchema: z
3787
4159
  .union([z.string(), z.record(z.string(), z.unknown())])
3788
4160
  .optional()
3789
- .describe("Claude --json-schema: JSON Schema literal (NOT a path) constraining structured output. Object values are JSON.stringify-d; string values are passed verbatim. Use with outputFormat='json'."),
4161
+ .describe("Claude --json-schema: JSON Schema literal (NOT a path) constraining structured output. Object values are JSON.stringify-d; string values are passed verbatim. Use with outputFormat='json'. Set outputFormat:json so the gateway treats the reply as structured output (skips response optimization and warning injection); the default output format is not json, so pass it explicitly."),
3790
4162
  addDir: z
3791
4163
  .array(z.string())
3792
4164
  .optional()
@@ -3814,11 +4186,11 @@ export function createGatewayServer(deps = {}) {
3814
4186
  approvalStrategy: z
3815
4187
  .enum(["legacy", "mcp_managed"])
3816
4188
  .default("legacy")
3817
- .describe("Approval strategy"),
4189
+ .describe("Approval strategy: legacy (default) lets the provider CLI's own flags decide; mcp_managed routes the run through the gateway approval gate (required for approvalPolicy and approval_list). Under mcp_managed the caller's permissionMode/alwaysApprove may be overridden by the gate."),
3818
4190
  approvalPolicy: z
3819
4191
  .enum(["strict", "balanced", "permissive"])
3820
4192
  .optional()
3821
- .describe("Approval policy override"),
4193
+ .describe("Approval policy when approvalStrategy is mcp_managed: strict|balanced|permissive (default balanced). Ignored under legacy strategy."),
3822
4194
  mcpServers: z.array(mcpServerEnum()).default([]).describe("MCP servers exposed to Claude"),
3823
4195
  strictMcpConfig: z
3824
4196
  .boolean()
@@ -3833,7 +4205,7 @@ export function createGatewayServer(deps = {}) {
3833
4205
  .min(30_000)
3834
4206
  .max(3_600_000)
3835
4207
  .optional()
3836
- .describe("Idle timeout in ms (min 30s, max 1h, omit=CLI default)"),
4208
+ .describe("Idle timeout in ms (min 30s, max 1h, omit=CLI default). Idle enforcement applies only when outputFormat is stream-json; it is ignored for text/json."),
3837
4209
  forceRefresh: z
3838
4210
  .boolean()
3839
4211
  .default(false)
@@ -4060,7 +4432,7 @@ export function createGatewayServer(deps = {}) {
4060
4432
  .optional()
4061
4433
  .describe("Prompt text for Codex (mutually exclusive with promptParts)"),
4062
4434
  promptParts: PromptPartsSchema.optional().describe("Cache-aware structured prompt: { system?, tools?, context?, task }. Mutually exclusive with prompt. Stable parts hash into cache_state for prefix-discipline tracking."),
4063
- model: z.string().optional().describe("Model name or alias (e.g. gpt-5.4, latest)"),
4435
+ model: z.string().optional().describe("Model name or alias (e.g. gpt-5.5, gpt-5.4, latest)"),
4064
4436
  fullAuto: z
4065
4437
  .boolean()
4066
4438
  .default(false)
@@ -4068,7 +4440,7 @@ export function createGatewayServer(deps = {}) {
4068
4440
  sandboxMode: z
4069
4441
  .enum(CODEX_SANDBOX_MODES)
4070
4442
  .optional()
4071
- .describe("Codex --sandbox: read-only|workspace-write|danger-full-access."),
4443
+ .describe("Codex --sandbox. Omit = Codex exec built-in default (read-only; cannot write files). Pass workspace-write to let Codex edit files in the working dir, or danger-full-access for unrestricted access."),
4072
4444
  askForApproval: z
4073
4445
  .enum(CODEX_ASK_FOR_APPROVAL_MODES)
4074
4446
  .optional()
@@ -4084,11 +4456,11 @@ export function createGatewayServer(deps = {}) {
4084
4456
  approvalStrategy: z
4085
4457
  .enum(["legacy", "mcp_managed"])
4086
4458
  .default("legacy")
4087
- .describe("Approval strategy"),
4459
+ .describe("Approval strategy: legacy (default) lets the provider CLI's own flags decide; mcp_managed routes the run through the gateway approval gate (required for approvalPolicy and approval_list). Under mcp_managed the caller's permissionMode/alwaysApprove may be overridden by the gate."),
4088
4460
  approvalPolicy: z
4089
4461
  .enum(["strict", "balanced", "permissive"])
4090
4462
  .optional()
4091
- .describe("Approval policy override"),
4463
+ .describe("Approval policy when approvalStrategy is mcp_managed: strict|balanced|permissive (default balanced). Ignored under legacy strategy."),
4092
4464
  mcpServers: z
4093
4465
  .array(mcpServerEnum())
4094
4466
  .default([])
@@ -4096,11 +4468,11 @@ export function createGatewayServer(deps = {}) {
4096
4468
  sessionId: z
4097
4469
  .string()
4098
4470
  .optional()
4099
- .describe("Codex session UUID to resume via `codex exec resume <ID>`. Must be a real Codex session ID (from `~/.codex/sessions/` or the `codex resume` picker). Gateway-generated `gw-*` IDs are rejected."),
4471
+ .describe("Codex session UUID to resume via `codex exec resume <ID>`. Must be a real Codex session ID (from `~/.codex/sessions/` or the `codex resume` picker). Gateway-generated `gw-*` IDs are rejected. For a brand-new session no resumable sessionId is returned; continue with resumeLatest:true or a real Codex UUID."),
4100
4472
  resumeLatest: z
4101
4473
  .boolean()
4102
4474
  .default(false)
4103
- .describe("Resume the most recent Codex session in the current cwd via `codex exec resume --last`. Ignored if sessionId is set."),
4475
+ .describe("Resume the most recent Codex session via `codex exec resume --last`. Ignored if sessionId is set. A brand-new session returns no resumable sessionId; continue with resumeLatest:true or a real Codex UUID."),
4104
4476
  createNewSession: z.boolean().default(false).describe("Force a fresh session (no resume)"),
4105
4477
  correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
4106
4478
  optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
@@ -4255,7 +4627,12 @@ export function createGatewayServer(deps = {}) {
4255
4627
  errorMessage: stderr || `Exit code ${code}`,
4256
4628
  status: "failed",
4257
4629
  }, runtime);
4258
- return createErrorResponse("codex", code, stderr, corrId);
4630
+ const codexErrorDetail = stderr && stderr.trim().length > 0 ? stderr : codexDisplayText(stdout);
4631
+ const codexResumeHint = sessionId &&
4632
+ /not found|no such|unknown session|does not exist|invalid session/i.test(codexErrorDetail)
4633
+ ? `\n\nSession ${sessionId} could not be resumed. Codex session IDs are UUIDs under ~/.codex/sessions/; verify the id, omit sessionId, or set createNewSession:true.`
4634
+ : "";
4635
+ return createErrorResponse("codex", code, `${codexErrorDetail}${codexResumeHint}`, corrId);
4259
4636
  }
4260
4637
  wasSuccessful = true;
4261
4638
  let effectiveSessionId = sessionId;
@@ -4339,7 +4716,7 @@ export function createGatewayServer(deps = {}) {
4339
4716
  sandboxMode: z
4340
4717
  .enum(CODEX_SANDBOX_MODES)
4341
4718
  .optional()
4342
- .describe("Codex --sandbox: read-only|workspace-write|danger-full-access."),
4719
+ .describe("Codex --sandbox. Omit = Codex exec built-in default (read-only; cannot write files). Pass workspace-write to let Codex edit files in the working dir, or danger-full-access for unrestricted access."),
4343
4720
  askForApproval: z
4344
4721
  .enum(CODEX_ASK_FOR_APPROVAL_MODES)
4345
4722
  .optional()
@@ -4412,7 +4789,12 @@ export function createGatewayServer(deps = {}) {
4412
4789
  const { stdout, stderr, code } = result;
4413
4790
  durationMs = Math.max(0, Date.now() - startTime);
4414
4791
  if (code !== 0) {
4415
- return createErrorResponse("codex", code, stderr, corrId);
4792
+ const codexErrorDetail = stderr && stderr.trim().length > 0 ? stderr : codexDisplayText(stdout);
4793
+ const codexResumeHint = sessionId &&
4794
+ /not found|no such|unknown session|does not exist|invalid session/i.test(codexErrorDetail)
4795
+ ? `\n\nSession ${sessionId} could not be resumed. Codex session IDs are UUIDs under ~/.codex/sessions/; verify the id, omit sessionId, or set createNewSession:true.`
4796
+ : "";
4797
+ return createErrorResponse("codex", code, `${codexErrorDetail}${codexResumeHint}`, corrId);
4416
4798
  }
4417
4799
  wasSuccessful = true;
4418
4800
  return {
@@ -4442,21 +4824,24 @@ export function createGatewayServer(deps = {}) {
4442
4824
  sessionId: z
4443
4825
  .string()
4444
4826
  .optional()
4445
- .describe("Antigravity conversation ID to resume (emits --conversation <id>)"),
4446
- resumeLatest: z.boolean().default(false).describe("Continue the most recent conversation"),
4827
+ .describe("Antigravity conversation ID to resume (emits --conversation <id>). agy owns conversation ids; a fresh request returns no resumable sessionId, continue via resumeLatest:true."),
4828
+ resumeLatest: z
4829
+ .boolean()
4830
+ .default(false)
4831
+ .describe("Continue the most recent conversation. agy owns conversation ids; a fresh request returns no resumable sessionId, continue via resumeLatest:true."),
4447
4832
  createNewSession: z.boolean().default(false).describe("Force new session"),
4448
4833
  approvalMode: z
4449
4834
  .enum(GEMINI_APPROVAL_MODES)
4450
4835
  .optional()
4451
- .describe("Approval: default|auto_edit|yolo|plan"),
4836
+ .describe("Approval mode. Only default (prompted) and yolo (auto-approve all, emits --dangerously-skip-permissions) work with the Antigravity agy headless path; auto_edit and plan are rejected at request time (agy has no headless accept-edits/plan mode)."),
4452
4837
  approvalStrategy: z
4453
4838
  .enum(["legacy", "mcp_managed"])
4454
4839
  .default("legacy")
4455
- .describe("Approval strategy"),
4840
+ .describe("Approval strategy: legacy (default) lets the provider CLI's own flags decide; mcp_managed routes the run through the gateway approval gate (required for approvalPolicy and approval_list). Under mcp_managed the caller's permissionMode/alwaysApprove may be overridden by the gate."),
4456
4841
  approvalPolicy: z
4457
4842
  .enum(["strict", "balanced", "permissive"])
4458
4843
  .optional()
4459
- .describe("Approval policy override"),
4844
+ .describe("Approval policy when approvalStrategy is mcp_managed: strict|balanced|permissive (default balanced). Ignored under legacy strategy."),
4460
4845
  mcpServers: z
4461
4846
  .array(mcpServerEnum())
4462
4847
  .default([])
@@ -4486,7 +4871,7 @@ export function createGatewayServer(deps = {}) {
4486
4871
  outputFormat: z
4487
4872
  .enum(["text", "json", "stream-json"])
4488
4873
  .default("text")
4489
- .describe("Antigravity CLI currently supports text output only through the gateway; json and stream-json are rejected."),
4874
+ .describe("Output format. The Antigravity agy headless path emits text only; json and stream-json are rejected at request time. Per-request token usage and cost are therefore not available for gemini."),
4490
4875
  sandbox: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.sandbox.describe("Run Antigravity in sandbox mode (--sandbox)"),
4491
4876
  policyFiles: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.policyFiles.describe("Unsupported for Antigravity CLI; non-empty values are rejected."),
4492
4877
  adminPolicyFiles: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.adminPolicyFiles.describe("Unsupported for Antigravity CLI; non-empty values are rejected."),
@@ -4499,6 +4884,15 @@ export function createGatewayServer(deps = {}) {
4499
4884
  .boolean()
4500
4885
  .optional()
4501
4886
  .describe("Emit `--dangerously-skip-permissions` to auto-approve all actions. Routed through the same approval gate. Under mcp_managed the gate still decides."),
4887
+ project: z
4888
+ .string()
4889
+ .min(1)
4890
+ .optional()
4891
+ .describe("Antigravity 1.0.13 --project <ID>: select the project for this session. Mutually exclusive with newProject."),
4892
+ newProject: z
4893
+ .boolean()
4894
+ .optional()
4895
+ .describe("Antigravity 1.0.13 --new-project: create a new project for this session. Mutually exclusive with project."),
4502
4896
  workspace: WORKSPACE_ALIAS_SCHEMA.optional(),
4503
4897
  worktree: WORKTREE_SCHEMA.optional(),
4504
4898
  }, {
@@ -4507,7 +4901,7 @@ export function createGatewayServer(deps = {}) {
4507
4901
  destructiveHint: true,
4508
4902
  idempotentHint: false,
4509
4903
  openWorldHint: true,
4510
- }, async ({ prompt, promptParts, model, sessionId, resumeLatest, createNewSession, approvalMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, includeDirs, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, outputFormat, sandbox, policyFiles, adminPolicyFiles, attachments, skipTrust, yolo, workspace, worktree, }) => {
4904
+ }, async ({ prompt, promptParts, model, sessionId, resumeLatest, createNewSession, approvalMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, includeDirs, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, outputFormat, sandbox, policyFiles, adminPolicyFiles, attachments, skipTrust, yolo, project, newProject, workspace, worktree, }) => {
4511
4905
  return handleGeminiRequest({ sessionManager, logger, runtime }, {
4512
4906
  prompt,
4513
4907
  promptParts,
@@ -4533,6 +4927,8 @@ export function createGatewayServer(deps = {}) {
4533
4927
  attachments,
4534
4928
  skipTrust,
4535
4929
  yolo,
4930
+ project,
4931
+ newProject,
4536
4932
  workspace,
4537
4933
  worktree,
4538
4934
  });
@@ -4554,11 +4950,11 @@ export function createGatewayServer(deps = {}) {
4554
4950
  sessionId: z
4555
4951
  .string()
4556
4952
  .optional()
4557
- .describe("Provider-native session ID to resume (emits --resume <id>; use resumeLatest for --continue)"),
4953
+ .describe("Provider-native session ID to resume (emits --resume <id>; use resumeLatest for --continue). Note: the gw-* id minted for a brand-new session is not resumable via sessionId; continue with resumeLatest:true."),
4558
4954
  resumeLatest: z
4559
4955
  .boolean()
4560
4956
  .default(false)
4561
- .describe("Resume most recent Grok session in cwd (--continue)"),
4957
+ .describe("Resume most recent Grok session in cwd (--continue). Note: the gw-* id minted for a brand-new session is not resumable via sessionId; continue with resumeLatest:true."),
4562
4958
  createNewSession: z.boolean().default(false).describe("Force new session"),
4563
4959
  alwaysApprove: z
4564
4960
  .boolean()
@@ -4567,15 +4963,15 @@ export function createGatewayServer(deps = {}) {
4567
4963
  permissionMode: z
4568
4964
  .enum(["default", "acceptEdits", "auto", "dontAsk", "bypassPermissions", "plan"])
4569
4965
  .optional()
4570
- .describe("Grok permission mode"),
4966
+ .describe("Grok permission mode: default|acceptEdits|auto|dontAsk|bypassPermissions|plan."),
4571
4967
  approvalStrategy: z
4572
4968
  .enum(["legacy", "mcp_managed"])
4573
4969
  .default("legacy")
4574
- .describe("Approval strategy"),
4970
+ .describe("Approval strategy: legacy (default) lets the provider CLI's own flags decide; mcp_managed routes the run through the gateway approval gate (required for approvalPolicy and approval_list). Under mcp_managed the caller's permissionMode/alwaysApprove may be overridden by the gate."),
4575
4971
  approvalPolicy: z
4576
4972
  .enum(["strict", "balanced", "permissive"])
4577
4973
  .optional()
4578
- .describe("Approval policy override"),
4974
+ .describe("Approval policy when approvalStrategy is mcp_managed: strict|balanced|permissive (default balanced). Ignored under legacy strategy."),
4579
4975
  mcpServers: z
4580
4976
  .array(mcpServerEnum())
4581
4977
  .default([])
@@ -4589,7 +4985,7 @@ export function createGatewayServer(deps = {}) {
4589
4985
  .min(30_000)
4590
4986
  .max(3_600_000)
4591
4987
  .optional()
4592
- .describe("Idle timeout in ms (min 30s, max 1h, omit=CLI default)"),
4988
+ .describe("Idle timeout in ms (min 30s, max 1h). Omit = gateway default of 600000ms (10 min) with no output before the process is killed."),
4593
4989
  forceRefresh: z
4594
4990
  .boolean()
4595
4991
  .default(false)
@@ -4606,6 +5002,19 @@ export function createGatewayServer(deps = {}) {
4606
5002
  .union([z.boolean(), z.string().min(1)])
4607
5003
  .optional()
4608
5004
  .describe("Grok -w/--worktree: native CLI worktree flag (`true` → bare `--worktree`, string → named). NOT gateway slice λ `worktree`."),
5005
+ worktreeRef: z
5006
+ .string()
5007
+ .min(1)
5008
+ .optional()
5009
+ .describe("Grok 0.2.73 --worktree-ref <REF>: git ref (branch/tag/commit) to base the native worktree on. Requires nativeWorktree."),
5010
+ forkSession: z
5011
+ .boolean()
5012
+ .optional()
5013
+ .describe("Grok 0.2.73 --fork-session: when resuming (--resume/--continue), fork into a new session ID instead of reusing the original."),
5014
+ jsonSchema: z
5015
+ .union([z.string().min(1), z.record(z.string(), z.unknown())])
5016
+ .optional()
5017
+ .describe("Grok 0.2.73 --json-schema: constrain output to a JSON Schema (string literal or object; implies json output). Set outputFormat:json so the gateway treats the reply as structured output (skips response optimization and warning injection); the default output format is not json, so pass it explicitly."),
4609
5018
  workspace: WORKSPACE_ALIAS_SCHEMA.optional(),
4610
5019
  worktree: WORKTREE_SCHEMA.optional(),
4611
5020
  }, {
@@ -4614,7 +5023,7 @@ export function createGatewayServer(deps = {}) {
4614
5023
  destructiveHint: true,
4615
5024
  idempotentHint: false,
4616
5025
  openWorldHint: true,
4617
- }, async ({ prompt, promptParts, model, transport, outputFormat, sessionId, resumeLatest, createNewSession, alwaysApprove, permissionMode, effort, reasoningEffort, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, maxTurns, workingDir, sandbox, rules, systemPromptOverride, allow, deny, compactionMode, compactionDetail, agent, bestOfN, check, disableWebSearch, todoGate, verbatim, agents, promptFile, promptJson, single, experimentalMemory, noAltScreen, noMemory, noPlan, noSubagents, oauth, restoreCode, leaderSocket, nativeWorktree, workspace, worktree, }) => {
5026
+ }, async ({ prompt, promptParts, model, transport, outputFormat, sessionId, resumeLatest, createNewSession, alwaysApprove, permissionMode, effort, reasoningEffort, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, maxTurns, workingDir, sandbox, rules, systemPromptOverride, allow, deny, compactionMode, compactionDetail, agent, bestOfN, check, disableWebSearch, todoGate, verbatim, agents, promptFile, promptJson, single, experimentalMemory, noAltScreen, noMemory, noPlan, noSubagents, oauth, restoreCode, leaderSocket, nativeWorktree, worktreeRef, forkSession, jsonSchema, workspace, worktree, }) => {
4618
5027
  return handleGrokRequest({ sessionManager, logger, runtime }, {
4619
5028
  prompt,
4620
5029
  promptParts,
@@ -4666,6 +5075,9 @@ export function createGatewayServer(deps = {}) {
4666
5075
  restoreCode,
4667
5076
  leaderSocket,
4668
5077
  nativeWorktree,
5078
+ worktreeRef,
5079
+ forkSession,
5080
+ jsonSchema,
4669
5081
  workspace,
4670
5082
  worktree,
4671
5083
  });
@@ -4676,7 +5088,7 @@ export function createGatewayServer(deps = {}) {
4676
5088
  .min(1, "Prompt cannot be empty")
4677
5089
  .max(100000, "Prompt too long (max 100k chars)")
4678
5090
  .optional()
4679
- .describe("Prompt text for Devin CLI"),
5091
+ .describe("Prompt text for Devin CLI. Required in practice; promptFile is additive (loads an initial prompt from a file)."),
4680
5092
  model: z.string().optional().describe("Model name or alias (e.g. opus, latest)"),
4681
5093
  transport: z
4682
5094
  .enum(["cli", "acp"])
@@ -4685,7 +5097,7 @@ export function createGatewayServer(deps = {}) {
4685
5097
  permissionMode: z
4686
5098
  .enum(["auto", "smart", "dangerous"])
4687
5099
  .optional()
4688
- .describe("Devin CLI permission mode (--permission-mode). auto auto-approves read-only tools; smart additionally auto-runs actions a fast model judges safe; dangerous auto-approves all."),
5100
+ .describe("Devin CLI permission mode (--permission-mode). auto auto-approves read-only tools; smart additionally auto-runs actions a fast model judges safe; dangerous auto-approves all. When omitted, Devin uses its own headless default; pass auto or smart for unattended runs."),
4689
5101
  promptFile: z
4690
5102
  .string()
4691
5103
  .optional()
@@ -4693,11 +5105,11 @@ export function createGatewayServer(deps = {}) {
4693
5105
  sessionId: z
4694
5106
  .string()
4695
5107
  .optional()
4696
- .describe("Devin session ID to resume (emits --resume <id>; use resumeLatest for --continue)"),
5108
+ .describe("Devin session ID to resume (emits --resume <id>; use resumeLatest for --continue). Note: the gw-* id minted for a brand-new session is not resumable via sessionId; continue with resumeLatest:true."),
4697
5109
  resumeLatest: z
4698
5110
  .boolean()
4699
5111
  .default(false)
4700
- .describe("Resume the most recent Devin session in cwd (--continue)"),
5112
+ .describe("Resume the most recent Devin session in cwd (--continue). Note: the gw-* id minted for a brand-new session is not resumable via sessionId; continue with resumeLatest:true."),
4701
5113
  createNewSession: z.boolean().default(false).describe("Force a new session"),
4702
5114
  correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
4703
5115
  optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
@@ -4736,7 +5148,7 @@ export function createGatewayServer(deps = {}) {
4736
5148
  forceRefresh,
4737
5149
  });
4738
5150
  });
4739
- server.tool("mistral_request", "Run a Mistral Vibe CLI request synchronously (when async jobs are enabled, auto-defers to a pollable job past the sync deadline; otherwise runs to completion). Requires exactly one of prompt or promptParts.", {
5151
+ server.tool("mistral_request", "Run a Mistral Vibe CLI request synchronously (when async jobs are enabled, auto-defers to a pollable job past the sync deadline; otherwise runs to completion). Requires exactly one of prompt or promptParts. Defaults to --agent auto-approve (unattended tool execution); pass permissionMode plan or accept-edits for safer runs.", {
4740
5152
  prompt: z
4741
5153
  .string()
4742
5154
  .min(1, "Prompt cannot be empty")
@@ -4759,11 +5171,11 @@ export function createGatewayServer(deps = {}) {
4759
5171
  sessionId: z
4760
5172
  .string()
4761
5173
  .optional()
4762
- .describe("Session ID (user-provided CLI handle for --resume). Current Vibe defaults session logging on; doctor flags explicit [session_logging] enabled = false."),
5174
+ .describe("Session ID (user-provided CLI handle for --resume). Current Vibe defaults session logging on; doctor flags explicit [session_logging] enabled = false. Note: the gw-* id minted for a brand-new session is not resumable via sessionId; continue with resumeLatest:true."),
4763
5175
  resumeLatest: z
4764
5176
  .boolean()
4765
5177
  .default(false)
4766
- .describe("Resume most recent Vibe session in cwd (--continue)"),
5178
+ .describe("Resume most recent Vibe session in cwd (--continue). Note: the gw-* id minted for a brand-new session is not resumable via sessionId; continue with resumeLatest:true."),
4767
5179
  createNewSession: z.boolean().default(false).describe("Force new session"),
4768
5180
  permissionMode: z
4769
5181
  .string()
@@ -4772,11 +5184,11 @@ export function createGatewayServer(deps = {}) {
4772
5184
  approvalStrategy: z
4773
5185
  .enum(["legacy", "mcp_managed"])
4774
5186
  .default("legacy")
4775
- .describe("Approval strategy"),
5187
+ .describe("Approval strategy: legacy (default) lets the provider CLI's own flags decide; mcp_managed routes the run through the gateway approval gate (required for approvalPolicy and approval_list). Under mcp_managed the caller's permissionMode/alwaysApprove may be overridden by the gate."),
4776
5188
  approvalPolicy: z
4777
5189
  .enum(["strict", "balanced", "permissive"])
4778
5190
  .optional()
4779
- .describe("Approval policy override"),
5191
+ .describe("Approval policy when approvalStrategy is mcp_managed: strict|balanced|permissive (default balanced). Ignored under legacy strategy."),
4780
5192
  mcpServers: z
4781
5193
  .array(mcpServerEnum())
4782
5194
  .default([])
@@ -4878,7 +5290,7 @@ export function createGatewayServer(deps = {}) {
4878
5290
  sessionId: z
4879
5291
  .string()
4880
5292
  .optional()
4881
- .describe("Gateway session record to associate (uses the active session if omitted). Claude continuity itself is via continueSession (--continue); this ID is gateway bookkeeping, not a Claude-native session."),
5293
+ .describe("On a fresh request this id is emitted as Claude --session-id <uuid> (must be a valid UUID that does not already exist). Resume the latest cwd conversation with continueSession:true. gw-* ids are not valid Claude --session-id values."),
4882
5294
  continueSession: z
4883
5295
  .boolean()
4884
5296
  .default(false)
@@ -4912,11 +5324,11 @@ export function createGatewayServer(deps = {}) {
4912
5324
  systemPrompt: z
4913
5325
  .string()
4914
5326
  .optional()
4915
- .describe("Claude --system-prompt: replace the system prompt entirely."),
5327
+ .describe("Claude --system-prompt: replace the system prompt entirely. Mutually exclusive with appendSystemPrompt."),
4916
5328
  appendSystemPrompt: z
4917
5329
  .string()
4918
5330
  .optional()
4919
- .describe("Claude --append-system-prompt: append to the existing system prompt."),
5331
+ .describe("Claude --append-system-prompt: append to the existing system prompt. Mutually exclusive with systemPrompt."),
4920
5332
  maxBudgetUsd: z
4921
5333
  .number()
4922
5334
  .positive()
@@ -4944,7 +5356,7 @@ export function createGatewayServer(deps = {}) {
4944
5356
  jsonSchema: z
4945
5357
  .union([z.string(), z.record(z.string(), z.unknown())])
4946
5358
  .optional()
4947
- .describe("Claude --json-schema: JSON Schema literal (NOT a path) constraining structured output. Object values are JSON.stringify-d; string values are passed verbatim. Use with outputFormat='json'."),
5359
+ .describe("Claude --json-schema: JSON Schema literal (NOT a path) constraining structured output. Object values are JSON.stringify-d; string values are passed verbatim. Use with outputFormat='json'. Set outputFormat:json so the gateway treats the reply as structured output (skips response optimization and warning injection); the default output format is not json, so pass it explicitly."),
4948
5360
  addDir: z
4949
5361
  .array(z.string())
4950
5362
  .optional()
@@ -4972,11 +5384,11 @@ export function createGatewayServer(deps = {}) {
4972
5384
  approvalStrategy: z
4973
5385
  .enum(["legacy", "mcp_managed"])
4974
5386
  .default("legacy")
4975
- .describe("Approval strategy"),
5387
+ .describe("Approval strategy: legacy (default) lets the provider CLI's own flags decide; mcp_managed routes the run through the gateway approval gate (required for approvalPolicy and approval_list). Under mcp_managed the caller's permissionMode/alwaysApprove may be overridden by the gate."),
4976
5388
  approvalPolicy: z
4977
5389
  .enum(["strict", "balanced", "permissive"])
4978
5390
  .optional()
4979
- .describe("Approval policy override"),
5391
+ .describe("Approval policy when approvalStrategy is mcp_managed: strict|balanced|permissive (default balanced). Ignored under legacy strategy."),
4980
5392
  mcpServers: z.array(mcpServerEnum()).default([]).describe("MCP servers exposed to Claude"),
4981
5393
  strictMcpConfig: z
4982
5394
  .boolean()
@@ -4990,7 +5402,7 @@ export function createGatewayServer(deps = {}) {
4990
5402
  .min(30_000)
4991
5403
  .max(3_600_000)
4992
5404
  .optional()
4993
- .describe("Idle timeout in ms (min 30s, max 1h, omit=CLI default)"),
5405
+ .describe("Idle timeout in ms (min 30s, max 1h, omit=CLI default). Idle enforcement applies only when outputFormat is stream-json; it is ignored for text/json."),
4994
5406
  forceRefresh: z
4995
5407
  .boolean()
4996
5408
  .default(false)
@@ -5137,7 +5549,10 @@ export function createGatewayServer(deps = {}) {
5137
5549
  .optional()
5138
5550
  .describe("Prompt text for Codex (mutually exclusive with promptParts)"),
5139
5551
  promptParts: PromptPartsSchema.optional().describe("Cache-aware structured prompt: { system?, tools?, context?, task }. Mutually exclusive with prompt. Stable parts hash into cache_state for prefix-discipline tracking."),
5140
- model: z.string().optional().describe("Model name or alias (e.g. gpt-5.4, latest)"),
5552
+ model: z
5553
+ .string()
5554
+ .optional()
5555
+ .describe("Model name or alias (e.g. gpt-5.5, gpt-5.4, latest)"),
5141
5556
  fullAuto: z
5142
5557
  .boolean()
5143
5558
  .default(false)
@@ -5145,7 +5560,7 @@ export function createGatewayServer(deps = {}) {
5145
5560
  sandboxMode: z
5146
5561
  .enum(CODEX_SANDBOX_MODES)
5147
5562
  .optional()
5148
- .describe("Codex --sandbox: read-only|workspace-write|danger-full-access."),
5563
+ .describe("Codex --sandbox. Omit = Codex exec built-in default (read-only; cannot write files). Pass workspace-write to let Codex edit files in the working dir, or danger-full-access for unrestricted access."),
5149
5564
  askForApproval: z
5150
5565
  .enum(CODEX_ASK_FOR_APPROVAL_MODES)
5151
5566
  .optional()
@@ -5161,11 +5576,11 @@ export function createGatewayServer(deps = {}) {
5161
5576
  approvalStrategy: z
5162
5577
  .enum(["legacy", "mcp_managed"])
5163
5578
  .default("legacy")
5164
- .describe("Approval strategy"),
5579
+ .describe("Approval strategy: legacy (default) lets the provider CLI's own flags decide; mcp_managed routes the run through the gateway approval gate (required for approvalPolicy and approval_list). Under mcp_managed the caller's permissionMode/alwaysApprove may be overridden by the gate."),
5165
5580
  approvalPolicy: z
5166
5581
  .enum(["strict", "balanced", "permissive"])
5167
5582
  .optional()
5168
- .describe("Approval policy override"),
5583
+ .describe("Approval policy when approvalStrategy is mcp_managed: strict|balanced|permissive (default balanced). Ignored under legacy strategy."),
5169
5584
  mcpServers: z
5170
5585
  .array(mcpServerEnum())
5171
5586
  .default([])
@@ -5173,11 +5588,11 @@ export function createGatewayServer(deps = {}) {
5173
5588
  sessionId: z
5174
5589
  .string()
5175
5590
  .optional()
5176
- .describe("Codex session UUID to resume via `codex exec resume <ID>`. Must be a real Codex session ID (from `~/.codex/sessions/` or the `codex resume` picker). Gateway-generated `gw-*` IDs are rejected."),
5591
+ .describe("Codex session UUID to resume via `codex exec resume <ID>`. Must be a real Codex session ID (from `~/.codex/sessions/` or the `codex resume` picker). Gateway-generated `gw-*` IDs are rejected. For a brand-new session no resumable sessionId is returned; continue with resumeLatest:true or a real Codex UUID."),
5177
5592
  resumeLatest: z
5178
5593
  .boolean()
5179
5594
  .default(false)
5180
- .describe("Resume the most recent Codex session in the current cwd via `codex exec resume --last`. Ignored if sessionId is set."),
5595
+ .describe("Resume the most recent Codex session via `codex exec resume --last`. Ignored if sessionId is set. A brand-new session returns no resumable sessionId; continue with resumeLatest:true or a real Codex UUID."),
5181
5596
  createNewSession: z.boolean().default(false).describe("Force a fresh session (no resume)"),
5182
5597
  correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
5183
5598
  optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
@@ -5277,21 +5692,24 @@ export function createGatewayServer(deps = {}) {
5277
5692
  sessionId: z
5278
5693
  .string()
5279
5694
  .optional()
5280
- .describe("Antigravity conversation ID to resume (emits --conversation <id>)"),
5281
- resumeLatest: z.boolean().default(false).describe("Continue the most recent conversation"),
5695
+ .describe("Antigravity conversation ID to resume (emits --conversation <id>). agy owns conversation ids; a fresh request returns no resumable sessionId, continue via resumeLatest:true."),
5696
+ resumeLatest: z
5697
+ .boolean()
5698
+ .default(false)
5699
+ .describe("Continue the most recent conversation. agy owns conversation ids; a fresh request returns no resumable sessionId, continue via resumeLatest:true."),
5282
5700
  createNewSession: z.boolean().default(false).describe("Force new session"),
5283
5701
  approvalMode: z
5284
5702
  .enum(GEMINI_APPROVAL_MODES)
5285
5703
  .optional()
5286
- .describe("Approval: default|auto_edit|yolo|plan"),
5704
+ .describe("Approval mode. Only default (prompted) and yolo (auto-approve all, emits --dangerously-skip-permissions) work with the Antigravity agy headless path; auto_edit and plan are rejected at request time (agy has no headless accept-edits/plan mode)."),
5287
5705
  approvalStrategy: z
5288
5706
  .enum(["legacy", "mcp_managed"])
5289
5707
  .default("legacy")
5290
- .describe("Approval strategy"),
5708
+ .describe("Approval strategy: legacy (default) lets the provider CLI's own flags decide; mcp_managed routes the run through the gateway approval gate (required for approvalPolicy and approval_list). Under mcp_managed the caller's permissionMode/alwaysApprove may be overridden by the gate."),
5291
5709
  approvalPolicy: z
5292
5710
  .enum(["strict", "balanced", "permissive"])
5293
5711
  .optional()
5294
- .describe("Approval policy override"),
5712
+ .describe("Approval policy when approvalStrategy is mcp_managed: strict|balanced|permissive (default balanced). Ignored under legacy strategy."),
5295
5713
  mcpServers: z
5296
5714
  .array(mcpServerEnum())
5297
5715
  .default([])
@@ -5320,7 +5738,7 @@ export function createGatewayServer(deps = {}) {
5320
5738
  outputFormat: z
5321
5739
  .enum(["text", "json", "stream-json"])
5322
5740
  .default("text")
5323
- .describe("Antigravity CLI currently supports text output only through the gateway; json and stream-json are rejected."),
5741
+ .describe("Output format. The Antigravity agy headless path emits text only; json and stream-json are rejected at request time. Per-request token usage and cost are therefore not available for gemini."),
5324
5742
  sandbox: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.sandbox.describe("Run Antigravity in sandbox mode (--sandbox)"),
5325
5743
  policyFiles: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.policyFiles.describe("Unsupported for Antigravity CLI; non-empty values are rejected."),
5326
5744
  adminPolicyFiles: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.adminPolicyFiles.describe("Unsupported for Antigravity CLI; non-empty values are rejected."),
@@ -5333,6 +5751,15 @@ export function createGatewayServer(deps = {}) {
5333
5751
  .boolean()
5334
5752
  .optional()
5335
5753
  .describe("Emit `--dangerously-skip-permissions` to auto-approve all actions. Routed through the same approval gate. Under mcp_managed the gate still decides."),
5754
+ project: z
5755
+ .string()
5756
+ .min(1)
5757
+ .optional()
5758
+ .describe("Antigravity 1.0.13 --project <ID>: select the project for this session. Mutually exclusive with newProject."),
5759
+ newProject: z
5760
+ .boolean()
5761
+ .optional()
5762
+ .describe("Antigravity 1.0.13 --new-project: create a new project for this session. Mutually exclusive with project."),
5336
5763
  workspace: WORKSPACE_ALIAS_SCHEMA.optional(),
5337
5764
  worktree: WORKTREE_SCHEMA.optional(),
5338
5765
  }, {
@@ -5341,7 +5768,7 @@ export function createGatewayServer(deps = {}) {
5341
5768
  destructiveHint: true,
5342
5769
  idempotentHint: false,
5343
5770
  openWorldHint: true,
5344
- }, async ({ prompt, promptParts, model, sessionId, resumeLatest, createNewSession, approvalMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, includeDirs, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, outputFormat, sandbox, policyFiles, adminPolicyFiles, attachments, skipTrust, yolo, workspace, worktree, }) => {
5771
+ }, async ({ prompt, promptParts, model, sessionId, resumeLatest, createNewSession, approvalMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, includeDirs, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, outputFormat, sandbox, policyFiles, adminPolicyFiles, attachments, skipTrust, yolo, project, newProject, workspace, worktree, }) => {
5345
5772
  return handleGeminiRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
5346
5773
  prompt,
5347
5774
  promptParts,
@@ -5366,6 +5793,8 @@ export function createGatewayServer(deps = {}) {
5366
5793
  attachments,
5367
5794
  skipTrust,
5368
5795
  yolo,
5796
+ project,
5797
+ newProject,
5369
5798
  workspace,
5370
5799
  worktree,
5371
5800
  });
@@ -5386,11 +5815,11 @@ export function createGatewayServer(deps = {}) {
5386
5815
  sessionId: z
5387
5816
  .string()
5388
5817
  .optional()
5389
- .describe("Provider-native session ID to resume (emits --resume <id>; use resumeLatest for --continue)"),
5818
+ .describe("Provider-native session ID to resume (emits --resume <id>; use resumeLatest for --continue). Note: the gw-* id minted for a brand-new session is not resumable via sessionId; continue with resumeLatest:true."),
5390
5819
  resumeLatest: z
5391
5820
  .boolean()
5392
5821
  .default(false)
5393
- .describe("Resume most recent Grok session in cwd (--continue)"),
5822
+ .describe("Resume most recent Grok session in cwd (--continue). Note: the gw-* id minted for a brand-new session is not resumable via sessionId; continue with resumeLatest:true."),
5394
5823
  createNewSession: z.boolean().default(false).describe("Force new session"),
5395
5824
  alwaysApprove: z
5396
5825
  .boolean()
@@ -5399,7 +5828,7 @@ export function createGatewayServer(deps = {}) {
5399
5828
  permissionMode: z
5400
5829
  .enum(["default", "acceptEdits", "auto", "dontAsk", "bypassPermissions", "plan"])
5401
5830
  .optional()
5402
- .describe("Grok permission mode"),
5831
+ .describe("Grok permission mode: default|acceptEdits|auto|dontAsk|bypassPermissions|plan."),
5403
5832
  effort: z
5404
5833
  .enum(["low", "medium", "high", "xhigh", "max"])
5405
5834
  .optional()
@@ -5408,11 +5837,11 @@ export function createGatewayServer(deps = {}) {
5408
5837
  approvalStrategy: z
5409
5838
  .enum(["legacy", "mcp_managed"])
5410
5839
  .default("legacy")
5411
- .describe("Approval strategy"),
5840
+ .describe("Approval strategy: legacy (default) lets the provider CLI's own flags decide; mcp_managed routes the run through the gateway approval gate (required for approvalPolicy and approval_list). Under mcp_managed the caller's permissionMode/alwaysApprove may be overridden by the gate."),
5412
5841
  approvalPolicy: z
5413
5842
  .enum(["strict", "balanced", "permissive"])
5414
5843
  .optional()
5415
- .describe("Approval policy override"),
5844
+ .describe("Approval policy when approvalStrategy is mcp_managed: strict|balanced|permissive (default balanced). Ignored under legacy strategy."),
5416
5845
  mcpServers: z
5417
5846
  .array(mcpServerEnum())
5418
5847
  .default([])
@@ -5433,7 +5862,7 @@ export function createGatewayServer(deps = {}) {
5433
5862
  .min(30_000)
5434
5863
  .max(3_600_000)
5435
5864
  .optional()
5436
- .describe("Idle timeout in ms (min 30s, max 1h, omit=CLI default)"),
5865
+ .describe("Idle timeout in ms (min 30s, max 1h). Omit = gateway default of 600000ms (10 min) with no output before the process is killed."),
5437
5866
  forceRefresh: z
5438
5867
  .boolean()
5439
5868
  .default(false)
@@ -5546,6 +5975,19 @@ export function createGatewayServer(deps = {}) {
5546
5975
  .union([z.boolean(), z.string().min(1)])
5547
5976
  .optional()
5548
5977
  .describe("Grok -w/--worktree: native CLI worktree flag (`true` → bare `--worktree`, string → named). NOT gateway slice λ `worktree`."),
5978
+ worktreeRef: z
5979
+ .string()
5980
+ .min(1)
5981
+ .optional()
5982
+ .describe("Grok 0.2.73 --worktree-ref <REF>: git ref (branch/tag/commit) to base the native worktree on. Requires nativeWorktree."),
5983
+ forkSession: z
5984
+ .boolean()
5985
+ .optional()
5986
+ .describe("Grok 0.2.73 --fork-session: when resuming (--resume/--continue), fork into a new session ID instead of reusing the original."),
5987
+ jsonSchema: z
5988
+ .union([z.string().min(1), z.record(z.string(), z.unknown())])
5989
+ .optional()
5990
+ .describe("Grok 0.2.73 --json-schema: constrain output to a JSON Schema (string literal or object; implies json output). Set outputFormat:json so the gateway treats the reply as structured output (skips response optimization and warning injection); the default output format is not json, so pass it explicitly."),
5549
5991
  workspace: WORKSPACE_ALIAS_SCHEMA.optional(),
5550
5992
  worktree: WORKTREE_SCHEMA.optional(),
5551
5993
  }, {
@@ -5554,7 +5996,7 @@ export function createGatewayServer(deps = {}) {
5554
5996
  destructiveHint: true,
5555
5997
  idempotentHint: false,
5556
5998
  openWorldHint: true,
5557
- }, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, alwaysApprove, permissionMode, effort, reasoningEffort, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, maxTurns, workingDir, sandbox, rules, systemPromptOverride, allow, deny, compactionMode, compactionDetail, agent, bestOfN, check, disableWebSearch, todoGate, verbatim, agents, promptFile, promptJson, single, experimentalMemory, noAltScreen, noMemory, noPlan, noSubagents, oauth, restoreCode, leaderSocket, nativeWorktree, workspace, worktree, }) => {
5999
+ }, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, alwaysApprove, permissionMode, effort, reasoningEffort, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, maxTurns, workingDir, sandbox, rules, systemPromptOverride, allow, deny, compactionMode, compactionDetail, agent, bestOfN, check, disableWebSearch, todoGate, verbatim, agents, promptFile, promptJson, single, experimentalMemory, noAltScreen, noMemory, noPlan, noSubagents, oauth, restoreCode, leaderSocket, nativeWorktree, worktreeRef, forkSession, jsonSchema, workspace, worktree, }) => {
5558
6000
  return handleGrokRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
5559
6001
  prompt,
5560
6002
  promptParts,
@@ -5604,6 +6046,9 @@ export function createGatewayServer(deps = {}) {
5604
6046
  restoreCode,
5605
6047
  leaderSocket,
5606
6048
  nativeWorktree,
6049
+ worktreeRef,
6050
+ forkSession,
6051
+ jsonSchema,
5607
6052
  workspace,
5608
6053
  worktree,
5609
6054
  });
@@ -5614,12 +6059,12 @@ export function createGatewayServer(deps = {}) {
5614
6059
  .min(1, "Prompt cannot be empty")
5615
6060
  .max(100000, "Prompt too long (max 100k chars)")
5616
6061
  .optional()
5617
- .describe("Prompt text for Devin CLI"),
6062
+ .describe("Prompt text for Devin CLI. Required in practice; promptFile is additive (loads an initial prompt from a file)."),
5618
6063
  model: z.string().optional().describe("Model name or alias (e.g. opus, latest)"),
5619
6064
  permissionMode: z
5620
6065
  .enum(["auto", "smart", "dangerous"])
5621
6066
  .optional()
5622
- .describe("Devin CLI permission mode (--permission-mode). auto, smart, or dangerous."),
6067
+ .describe("Devin CLI permission mode (--permission-mode). auto, smart, or dangerous. When omitted, Devin uses its own headless default; pass auto or smart for unattended runs."),
5623
6068
  promptFile: z
5624
6069
  .string()
5625
6070
  .optional()
@@ -5627,11 +6072,11 @@ export function createGatewayServer(deps = {}) {
5627
6072
  sessionId: z
5628
6073
  .string()
5629
6074
  .optional()
5630
- .describe("Devin session ID to resume (--resume <id>; use resumeLatest for --continue)"),
6075
+ .describe("Devin session ID to resume (--resume <id>; use resumeLatest for --continue). Note: the gw-* id minted for a brand-new session is not resumable via sessionId; continue with resumeLatest:true."),
5631
6076
  resumeLatest: z
5632
6077
  .boolean()
5633
6078
  .default(false)
5634
- .describe("Resume the most recent Devin session in cwd (--continue)"),
6079
+ .describe("Resume the most recent Devin session in cwd (--continue). Note: the gw-* id minted for a brand-new session is not resumable via sessionId; continue with resumeLatest:true."),
5635
6080
  createNewSession: z.boolean().default(false).describe("Force a new session"),
5636
6081
  correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
5637
6082
  optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
@@ -5686,11 +6131,11 @@ export function createGatewayServer(deps = {}) {
5686
6131
  sessionId: z
5687
6132
  .string()
5688
6133
  .optional()
5689
- .describe("Session ID (user-provided CLI handle for --resume). Current Vibe defaults session logging on; doctor flags explicit [session_logging] enabled = false."),
6134
+ .describe("Session ID (user-provided CLI handle for --resume). Current Vibe defaults session logging on; doctor flags explicit [session_logging] enabled = false. Note: the gw-* id minted for a brand-new session is not resumable via sessionId; continue with resumeLatest:true."),
5690
6135
  resumeLatest: z
5691
6136
  .boolean()
5692
6137
  .default(false)
5693
- .describe("Resume most recent Vibe session in cwd (--continue)"),
6138
+ .describe("Resume most recent Vibe session in cwd (--continue). Note: the gw-* id minted for a brand-new session is not resumable via sessionId; continue with resumeLatest:true."),
5694
6139
  createNewSession: z.boolean().default(false).describe("Force new session"),
5695
6140
  permissionMode: z
5696
6141
  .string()
@@ -5699,11 +6144,11 @@ export function createGatewayServer(deps = {}) {
5699
6144
  approvalStrategy: z
5700
6145
  .enum(["legacy", "mcp_managed"])
5701
6146
  .default("legacy")
5702
- .describe("Approval strategy"),
6147
+ .describe("Approval strategy: legacy (default) lets the provider CLI's own flags decide; mcp_managed routes the run through the gateway approval gate (required for approvalPolicy and approval_list). Under mcp_managed the caller's permissionMode/alwaysApprove may be overridden by the gate."),
5703
6148
  approvalPolicy: z
5704
6149
  .enum(["strict", "balanced", "permissive"])
5705
6150
  .optional()
5706
- .describe("Approval policy override"),
6151
+ .describe("Approval policy when approvalStrategy is mcp_managed: strict|balanced|permissive (default balanced). Ignored under legacy strategy."),
5707
6152
  mcpServers: z
5708
6153
  .array(mcpServerEnum())
5709
6154
  .default([])
@@ -6084,10 +6529,10 @@ export function createGatewayServer(deps = {}) {
6084
6529
  ],
6085
6530
  };
6086
6531
  });
6087
- server.tool("list_models", "List models, aliases, and defaults for one provider CLI (claude|codex|gemini|grok|mistral).", {
6532
+ server.tool("list_models", "List models, aliases, and defaults for one provider CLI (claude|codex|gemini|grok|mistral|devin), or omit cli to list all providers.", {
6088
6533
  cli: z
6089
- .preprocess(value => (value === "" || value === null ? undefined : value), z.enum(["claude", "codex", "gemini", "grok", "mistral"]).optional())
6090
- .describe("CLI filter (claude|codex|gemini|grok|mistral)"),
6534
+ .preprocess(value => (value === "" || value === null ? undefined : value), CLI_TYPE_ENUM.optional())
6535
+ .describe("CLI filter (claude|codex|gemini|grok|mistral|devin)"),
6091
6536
  }, {
6092
6537
  title: "Provider models",
6093
6538
  readOnlyHint: true,
@@ -6099,10 +6544,10 @@ export function createGatewayServer(deps = {}) {
6099
6544
  const result = cli ? { [cli]: cliInfo[cli] } : cliInfo;
6100
6545
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
6101
6546
  });
6102
- server.tool("provider_tool_capabilities", "Report provider tool/feature capabilities and discovered local skill/tool integrations for claude|codex|gemini|grok|grok_api|mistral.", {
6547
+ server.tool("provider_tool_capabilities", "Report provider tool/feature capabilities and discovered local skill/tool integrations for claude|codex|gemini|grok|mistral|devin|grok_api.", {
6103
6548
  cli: z
6104
- .preprocess(value => (value === "" || value === null ? undefined : value), z.enum(["claude", "codex", "gemini", "grok", "grok_api", "mistral"]).optional())
6105
- .describe("Provider filter (claude|codex|gemini|grok|grok_api|mistral)"),
6549
+ .preprocess(value => (value === "" || value === null ? undefined : value), z.enum(["claude", "codex", "gemini", "grok", "mistral", "devin", "grok_api"]).optional())
6550
+ .describe("Provider filter (claude|codex|gemini|grok|mistral|devin|grok_api)"),
6106
6551
  includeSkills: z
6107
6552
  .boolean()
6108
6553
  .default(true)
@@ -6137,10 +6582,10 @@ export function createGatewayServer(deps = {}) {
6137
6582
  });
6138
6583
  return { content: [{ type: "text", text: JSON.stringify(capabilities, null, 2) }] };
6139
6584
  });
6140
- server.tool("cli_versions", "Report installed provider CLI versions, availability, and login status for all five providers or one.", {
6585
+ server.tool("cli_versions", "Report installed provider CLI versions, availability, and login status for all six providers (claude|codex|gemini|grok|mistral|devin) or one.", {
6141
6586
  cli: z
6142
- .preprocess(value => (value === "" || value === null ? undefined : value), z.enum(["claude", "codex", "gemini", "grok", "mistral"]).optional())
6143
- .describe("CLI filter (claude|codex|gemini|grok|mistral)"),
6587
+ .preprocess(value => (value === "" || value === null ? undefined : value), CLI_TYPE_ENUM.optional())
6588
+ .describe("CLI filter (claude|codex|gemini|grok|mistral|devin)"),
6144
6589
  }, {
6145
6590
  title: "Provider CLI versions",
6146
6591
  readOnlyHint: true,
@@ -6154,7 +6599,7 @@ export function createGatewayServer(deps = {}) {
6154
6599
  server.tool("upstream_contracts", "Return the gateway's declared provider CLI contracts; with probeInstalled true, diff against installed --help surfaces to detect flag drift.", {
6155
6600
  cli: z
6156
6601
  .preprocess(value => (value === "" || value === null ? undefined : value), CLI_TYPE_ENUM.optional())
6157
- .describe("CLI filter (claude|codex|gemini|grok|mistral)"),
6602
+ .describe("CLI filter (claude|codex|gemini|grok|mistral|devin)"),
6158
6603
  probeInstalled: z
6159
6604
  .boolean()
6160
6605
  .default(false)
@@ -6172,7 +6617,7 @@ export function createGatewayServer(deps = {}) {
6172
6617
  server.tool("provider_subcommands_list", "Return a compact, filterable read-only catalog of declared provider CLI subcommands without flags or raw help.", {
6173
6618
  provider: z
6174
6619
  .preprocess(value => (value === "" || value === null ? undefined : value), CLI_TYPE_ENUM.optional())
6175
- .describe("Optional provider filter (claude|codex|gemini|grok|mistral)"),
6620
+ .describe("Optional provider filter (claude|codex|gemini|grok|mistral|devin)"),
6176
6621
  tier: z
6177
6622
  .enum(["catalog", "inspect", "execute_candidate", "diagnostic"])
6178
6623
  .optional()
@@ -6222,7 +6667,7 @@ export function createGatewayServer(deps = {}) {
6222
6667
  };
6223
6668
  });
6224
6669
  server.tool("provider_subcommand_contract", "Return the detailed read-only contract for exactly one declared provider CLI subcommand.", {
6225
- provider: CLI_TYPE_ENUM.describe("Provider (claude|codex|gemini|grok|mistral)"),
6670
+ provider: CLI_TYPE_ENUM.describe("Provider (claude|codex|gemini|grok|mistral|devin)"),
6226
6671
  commandPath: z.array(z.string().min(1)).min(1).describe("Command path segments"),
6227
6672
  }, {
6228
6673
  title: "Provider subcommand contract",
@@ -6246,7 +6691,7 @@ export function createGatewayServer(deps = {}) {
6246
6691
  server.tool("provider_subcommand_drift", "Probe declared provider subcommand --help surfaces and return compact drift rows without raw help output.", {
6247
6692
  provider: z
6248
6693
  .preprocess(value => (value === "" || value === null ? undefined : value), CLI_TYPE_ENUM.optional())
6249
- .describe("Optional provider filter (claude|codex|gemini|grok|mistral)"),
6694
+ .describe("Optional provider filter (claude|codex|gemini|grok|mistral|devin)"),
6250
6695
  includeClean: z
6251
6696
  .boolean()
6252
6697
  .default(false)
@@ -6297,7 +6742,7 @@ export function createGatewayServer(deps = {}) {
6297
6742
  };
6298
6743
  });
6299
6744
  server.tool("cli_upgrade", "Plan (dryRun, default true) or execute an upgrade for one provider CLI using its native update mechanism.", {
6300
- cli: z.enum(["claude", "codex", "gemini", "grok", "mistral"]).describe("CLI to upgrade"),
6745
+ cli: CLI_TYPE_ENUM.describe("CLI to upgrade (claude|codex|gemini|grok|mistral|devin)"),
6301
6746
  target: z
6302
6747
  .string()
6303
6748
  .min(1)
@@ -6351,8 +6796,12 @@ export function createGatewayServer(deps = {}) {
6351
6796
  };
6352
6797
  }
6353
6798
  });
6354
- server.tool("session_create", "Create a gateway session record for a provider. NOTE: this is gateway bookkeeping (gw-* ID), not a provider-native session — Codex resume needs a real Codex UUID.", {
6355
- cli: SESSION_PROVIDER_ENUM.describe("Provider type (claude|codex|gemini|grok|mistral|grok-api)"),
6799
+ const sessionProviderValues = sessionProviderValuesFor(providers);
6800
+ const sessionProviderEnum = sessionProviderValues.length > SESSION_PROVIDER_VALUES.length
6801
+ ? z.enum(sessionProviderValues)
6802
+ : SESSION_PROVIDER_ENUM;
6803
+ server.tool("session_create", "Create a gateway session record for a provider. NOTE: this is gateway bookkeeping (a plain UUID), not a provider-native session; Codex resume needs a real Codex UUID.", {
6804
+ cli: sessionProviderEnum.describe("Provider type (claude|codex|gemini|grok|mistral|grok-api)"),
6356
6805
  description: z.string().optional().describe("Session description"),
6357
6806
  setAsActive: z.boolean().default(true).describe("Set as active session"),
6358
6807
  }, {
@@ -6391,7 +6840,9 @@ export function createGatewayServer(deps = {}) {
6391
6840
  }
6392
6841
  });
6393
6842
  server.tool("session_list", "List gateway session records and the active session per provider, optionally filtered by provider.", {
6394
- cli: SESSION_PROVIDER_ENUM.optional().describe("Provider filter (claude|codex|gemini|grok|mistral|grok-api)"),
6843
+ cli: sessionProviderEnum
6844
+ .optional()
6845
+ .describe("Provider filter (claude|codex|gemini|grok|mistral|grok-api)"),
6395
6846
  }, {
6396
6847
  title: "List sessions",
6397
6848
  readOnlyHint: true,
@@ -6402,7 +6853,7 @@ export function createGatewayServer(deps = {}) {
6402
6853
  try {
6403
6854
  const caller = resolveOwnerPrincipal(getRequestContext());
6404
6855
  const sessions = (await sessionManager.listSessions(cli)).filter(s => principalCanAccess(s.ownerPrincipal, caller));
6405
- const activeSessions = Object.fromEntries(await Promise.all(SESSION_PROVIDER_VALUES.map(async (provider) => {
6856
+ const activeSessions = Object.fromEntries(await Promise.all(sessionProviderValues.map(async (provider) => {
6406
6857
  const active = await sessionManager.getActiveSession(provider);
6407
6858
  return [
6408
6859
  provider,
@@ -6424,7 +6875,7 @@ export function createGatewayServer(deps = {}) {
6424
6875
  text: JSON.stringify({
6425
6876
  total: sessionList.length,
6426
6877
  sessions: sessionList,
6427
- activeSessions: Object.fromEntries(SESSION_PROVIDER_VALUES.map(provider => [
6878
+ activeSessions: Object.fromEntries(sessionProviderValues.map(provider => [
6428
6879
  provider,
6429
6880
  activeSessions[provider]?.id || null,
6430
6881
  ])),
@@ -6438,7 +6889,7 @@ export function createGatewayServer(deps = {}) {
6438
6889
  }
6439
6890
  });
6440
6891
  server.tool("session_set_active", "Set or clear the active session for a provider; the active session is used when a request omits sessionId.", {
6441
- cli: SESSION_PROVIDER_ENUM.describe("Provider type (claude|codex|gemini|grok|mistral|grok-api)"),
6892
+ cli: sessionProviderEnum.describe("Provider type (claude|codex|gemini|grok|mistral|grok-api)"),
6442
6893
  sessionId: z.string().nullable().describe("Session ID (null to clear)"),
6443
6894
  }, {
6444
6895
  title: "Set active session",
@@ -6618,7 +7069,9 @@ export function createGatewayServer(deps = {}) {
6618
7069
  }
6619
7070
  });
6620
7071
  server.tool("session_clear_all", "Delete all gateway session records, optionally scoped to one provider.", {
6621
- cli: SESSION_PROVIDER_ENUM.optional().describe("Provider filter (claude|codex|gemini|grok|mistral|grok-api)"),
7072
+ cli: sessionProviderEnum
7073
+ .optional()
7074
+ .describe("Provider filter (claude|codex|gemini|grok|mistral|grok-api)"),
6622
7075
  }, {
6623
7076
  title: "Clear sessions",
6624
7077
  readOnlyHint: false,