llm-cli-gateway 2.13.1 → 2.14.0-rc.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.
Files changed (88) hide show
  1. package/CHANGELOG.md +140 -0
  2. package/README.md +53 -26
  3. package/dist/acp/client.d.ts +29 -1
  4. package/dist/acp/client.js +78 -4
  5. package/dist/acp/errors.d.ts +9 -1
  6. package/dist/acp/errors.js +19 -0
  7. package/dist/acp/event-normalizer.d.ts +12 -0
  8. package/dist/acp/event-normalizer.js +16 -0
  9. package/dist/acp/flight-redaction.d.ts +3 -0
  10. package/dist/acp/flight-redaction.js +3 -0
  11. package/dist/acp/permission-bridge.js +11 -5
  12. package/dist/acp/process-manager.d.ts +2 -1
  13. package/dist/acp/process-manager.js +43 -4
  14. package/dist/acp/provider-registry.js +19 -11
  15. package/dist/acp/runtime.d.ts +8 -0
  16. package/dist/acp/runtime.js +47 -4
  17. package/dist/acp/types.d.ts +3083 -55
  18. package/dist/acp/types.js +242 -5
  19. package/dist/async-job-manager.d.ts +2 -0
  20. package/dist/async-job-manager.js +11 -0
  21. package/dist/codex-json-parser.d.ts +1 -0
  22. package/dist/codex-json-parser.js +6 -0
  23. package/dist/config.d.ts +16 -0
  24. package/dist/config.js +105 -19
  25. package/dist/connector-setup.d.ts +39 -0
  26. package/dist/connector-setup.js +86 -0
  27. package/dist/doctor.d.ts +39 -1
  28. package/dist/doctor.js +182 -3
  29. package/dist/flight-recorder.d.ts +2 -0
  30. package/dist/flight-recorder.js +20 -0
  31. package/dist/gemini-json-parser.d.ts +2 -0
  32. package/dist/gemini-json-parser.js +45 -8
  33. package/dist/grok-json-parser.d.ts +14 -0
  34. package/dist/grok-json-parser.js +156 -0
  35. package/dist/http-transport.js +27 -3
  36. package/dist/index.d.ts +48 -2
  37. package/dist/index.js +633 -144
  38. package/dist/job-store.d.ts +45 -7
  39. package/dist/job-store.js +138 -17
  40. package/dist/model-registry.d.ts +1 -0
  41. package/dist/model-registry.js +46 -0
  42. package/dist/oauth.js +17 -16
  43. package/dist/postgres-job-store-worker.d.ts +1 -0
  44. package/dist/postgres-job-store-worker.js +327 -0
  45. package/dist/pricing.d.ts +3 -2
  46. package/dist/provider-acp-capabilities.d.ts +52 -0
  47. package/dist/provider-acp-capabilities.js +101 -0
  48. package/dist/provider-admin-tools.d.ts +100 -0
  49. package/dist/provider-admin-tools.js +572 -0
  50. package/dist/provider-capability-cache.d.ts +46 -0
  51. package/dist/provider-capability-cache.js +248 -0
  52. package/dist/provider-capability-discovery.d.ts +85 -0
  53. package/dist/provider-capability-discovery.js +461 -0
  54. package/dist/provider-capability-resolver.d.ts +29 -0
  55. package/dist/provider-capability-resolver.js +92 -0
  56. package/dist/provider-definition-assertions.d.ts +9 -0
  57. package/dist/provider-definition-assertions.js +147 -0
  58. package/dist/provider-definitions.d.ts +127 -0
  59. package/dist/provider-definitions.js +758 -0
  60. package/dist/provider-help-parser.d.ts +34 -0
  61. package/dist/provider-help-parser.js +203 -0
  62. package/dist/provider-model-discovery.d.ts +30 -0
  63. package/dist/provider-model-discovery.js +229 -0
  64. package/dist/provider-output-metadata.d.ts +7 -0
  65. package/dist/provider-output-metadata.js +68 -0
  66. package/dist/provider-schema-builder.d.ts +22 -0
  67. package/dist/provider-schema-builder.js +55 -0
  68. package/dist/provider-surface-generator.d.ts +98 -0
  69. package/dist/provider-surface-generator.js +140 -0
  70. package/dist/provider-tool-capabilities.d.ts +3 -2
  71. package/dist/provider-tool-capabilities.js +77 -62
  72. package/dist/remote-url.d.ts +28 -0
  73. package/dist/remote-url.js +61 -0
  74. package/dist/request-helpers.d.ts +37 -4
  75. package/dist/request-helpers.js +134 -0
  76. package/dist/resources.d.ts +6 -1
  77. package/dist/resources.js +67 -193
  78. package/dist/session-manager.d.ts +2 -0
  79. package/dist/session-manager.js +34 -8
  80. package/dist/stream-json-parser.d.ts +1 -0
  81. package/dist/stream-json-parser.js +3 -0
  82. package/dist/upstream-contracts.d.ts +17 -1
  83. package/dist/upstream-contracts.js +209 -36
  84. package/dist/workspace-registry.d.ts +9 -0
  85. package/dist/workspace-registry.js +24 -4
  86. package/npm-shrinkwrap.json +2 -2
  87. package/package.json +8 -3
  88. package/setup/status.schema.json +75 -0
package/dist/index.js CHANGED
@@ -4,21 +4,24 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4
4
  import { randomUUID } from "crypto";
5
5
  import { createRequire } from "module";
6
6
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync, chmodSync, } from "fs";
7
- import { dirname, join } from "path";
7
+ import { basename, dirname, isAbsolute, join, relative } from "path";
8
8
  import { fileURLToPath } from "url";
9
9
  import { z } from "zod/v3";
10
10
  import { executeCli, killAllProcessGroups, providerCommandName } from "./executor.js";
11
11
  import { parseStreamJson } from "./stream-json-parser.js";
12
12
  import { parseCodexJsonStream, codexDisplayText, codexFrResponse } from "./codex-json-parser.js";
13
+ import { grokDisplayText } from "./grok-json-parser.js";
14
+ import { extractProviderOutputMetadata } from "./provider-output-metadata.js";
13
15
  import { parseGeminiJson, parseGeminiStreamJson } from "./gemini-json-parser.js";
14
16
  import { parseVibeMetaJson } from "./mistral-meta-json-parser.js";
15
17
  import { homedir } from "os";
16
- import { CLI_TYPES, PROVIDER_TYPES, createSessionManager, } from "./session-manager.js";
18
+ import { CLI_TYPES, PROVIDER_TYPES, createSessionManager, callerIsRemote, remoteSafeSession, } from "./session-manager.js";
17
19
  import { createWorktree, createWorktreeSessionCleanupHook, } from "./worktree-manager.js";
18
20
  import { ResourceProvider } from "./resources.js";
21
+ import { generateResourceDescriptors, generateProviderAcpDescriptors, } from "./provider-surface-generator.js";
19
22
  import { PerformanceMetrics } from "./metrics.js";
20
23
  import { estimateTokens, optimizePrompt as optimizePromptText, optimizeResponse as optimizeResponseText, } from "./optimizer.js";
21
- import { loadConfig, loadPersistenceConfig, loadCacheAwarenessConfig, loadProvidersConfig, loadAcpConfig, loadLimitsConfig, defaultGatewayConfigPath, isXaiProviderEnabled, enabledApiProviders, minStableTokensForModel, } from "./config.js";
24
+ import { loadConfig, loadPersistenceConfig, loadCacheAwarenessConfig, loadProvidersConfig, loadAcpConfig, loadAdminConfig, loadLimitsConfig, defaultGatewayConfigPath, isXaiProviderEnabled, enabledApiProviders, minStableTokensForModel, } from "./config.js";
22
25
  import { runAcpRequest } from "./acp/runtime.js";
23
26
  import { isAcpError } from "./acp/errors.js";
24
27
  import { redactSecrets } from "./secret-redaction.js";
@@ -27,12 +30,15 @@ import { prepareApiRequest, apiProviderCatalogEntry, ApiModelNotAllowedError, }
27
30
  import { checkHealth } from "./health.js";
28
31
  import { clearModelRegistryCache, getAvailableCliInfo, getCliInfo, resolveModelAlias, } from "./model-registry.js";
29
32
  import { getProviderToolCapabilities } from "./provider-tool-capabilities.js";
33
+ import { getProviderDefinition, DEVIN_ACP_AGENT_TYPES, } from "./provider-definitions.js";
34
+ import { buildProviderDiscoveredView, peekProviderCapabilitySet, warmProviderCapabilities, } from "./provider-capability-resolver.js";
35
+ import { registerProviderAdminTools } from "./provider-admin-tools.js";
30
36
  import { AsyncJobManager, JobSaturationError, isAsyncJobInProgress, extractApiHttpStatus, extractApiErrorBody, } from "./async-job-manager.js";
31
37
  import { createJobStore } from "./job-store.js";
32
38
  import { ApprovalManager, bypassAllowedByOperator, } from "./approval-manager.js";
33
39
  import { checkReviewIntegrity } from "./review-integrity.js";
34
40
  import { buildClaudeMcpConfig, CLAUDE_MCP_SERVER_NAMES, } from "./claude-mcp-config.js";
35
- import { resolveGrokSessionArgs, resolveMistralSessionArgs, resolveCodexSessionArgs, sanitizeCliArgValues, prepareMistralRequest as buildMistralCliInvocation, GATEWAY_SESSION_PREFIX, resolveClaudePermissionFlags, resolveCodexSandboxFlags, CLAUDE_PERMISSION_MODES, GEMINI_APPROVAL_MODES, CODEX_SANDBOX_MODES, CODEX_ASK_FOR_APPROVAL_MODES, CLAUDE_EFFORT_LEVELS, prepareClaudeHighImpactFlags, validateClaudeAgentsMap, prepareCodexHighImpactFlags, prepareCodexForkRequest, CODEX_CONFIG_OVERRIDES_SCHEMA, resolveGeminiSessionPlan, GEMINI_HIGH_IMPACT_PARAMS_SCHEMA, } from "./request-helpers.js";
41
+ import { resolveGrokSessionArgs, resolveMistralSessionArgs, resolveCodexSessionArgs, sanitizeCliArgValues, prepareMistralRequest as buildMistralCliInvocation, GATEWAY_SESSION_PREFIX, resolveClaudePermissionFlags, resolveCodexSandboxFlags, CLAUDE_PERMISSION_MODES, GEMINI_APPROVAL_MODES, CODEX_SANDBOX_MODES, CODEX_ASK_FOR_APPROVAL_MODES, CODEX_LOCAL_PROVIDERS, CODEX_COLOR_MODES, CLAUDE_EFFORT_LEVELS, prepareClaudeHighImpactFlags, validateClaudeAgentsMap, prepareCodexHighImpactFlags, prepareCodexForkRequest, CODEX_CONFIG_OVERRIDES_SCHEMA, resolveGeminiSessionPlan, GEMINI_HIGH_IMPACT_PARAMS_SCHEMA, } from "./request-helpers.js";
36
42
  import { createFlightRecorder } from "./flight-recorder.js";
37
43
  import { resolvePromptInput, PromptPartsSchema, assembleClaudeCacheBlocks, } from "./prompt-parts.js";
38
44
  import { computeSessionCacheStats, computeTtlRemaining, readPersistedRequest, PERSISTED_REQUEST_DEFAULT_MAX_CHARS, } from "./cache-stats.js";
@@ -41,7 +47,9 @@ import { startHttpGateway } from "./http-transport.js";
41
47
  import { getRequestContext, resolveOwnerPrincipal, principalCanAccess } from "./request-context.js";
42
48
  import { printDoctorJson } from "./doctor.js";
43
49
  import { redactDiagnosticUrl } from "./endpoint-exposure.js";
44
- import { createWorkspace, describeWorkspace, getWorkspace, loadWorkspaceRegistry, registerExistingWorkspace, resolveWorkspaceForProvider, validatePathInsideWorkspace, } from "./workspace-registry.js";
50
+ import { buildRemoteConnectorUrls } from "./remote-url.js";
51
+ import { gatherConnectorSetupPacket, renderConnectorSetupSummary, } from "./connector-setup.js";
52
+ import { createWorkspace, describeWorkspace, describeWorkspaceRemote, getWorkspace, loadWorkspaceRegistry, registerExistingWorkspace, resolveWorkspaceForProvider, validatePathInsideWorkspace, } from "./workspace-registry.js";
45
53
  import { generateSecret, hashSecret } from "./oauth.js";
46
54
  import { registerValidationTools } from "./validation-tools.js";
47
55
  import { currentCaller, resolveValidationReceipt } from "./validation-receipt.js";
@@ -235,6 +243,7 @@ let persistenceConfig = null;
235
243
  let cacheAwarenessConfig = null;
236
244
  let providersConfig = null;
237
245
  let acpConfig = null;
246
+ let adminConfig = null;
238
247
  let limitsConfig = null;
239
248
  let jobStore = null;
240
249
  let jobStoreInitialized = false;
@@ -256,6 +265,10 @@ function getAcpConfig(runtimeLogger = logger) {
256
265
  acpConfig ??= loadAcpConfig(runtimeLogger);
257
266
  return acpConfig;
258
267
  }
268
+ function getAdminConfig(runtimeLogger = logger) {
269
+ adminConfig ??= loadAdminConfig(runtimeLogger);
270
+ return adminConfig;
271
+ }
259
272
  function getCacheAwarenessConfig(runtimeLogger = logger) {
260
273
  cacheAwarenessConfig ??= loadCacheAwarenessConfig(runtimeLogger);
261
274
  return cacheAwarenessConfig;
@@ -297,6 +310,91 @@ function mcpServerEnum() {
297
310
  }
298
311
  const CLI_TYPE_ENUM = z.enum(CLI_TYPES);
299
312
  export const MAX_TURNS_SCHEMA = z.number().int().positive().safe().max(10_000);
313
+ const CLAUDE_PART_A_FIELDS = {
314
+ includeHookEvents: z
315
+ .boolean()
316
+ .optional()
317
+ .describe("Claude --include-hook-events: include all hook lifecycle events in the output stream. Only takes effect with outputFormat=stream-json (the default)."),
318
+ replayUserMessages: z
319
+ .boolean()
320
+ .optional()
321
+ .describe("Claude --replay-user-messages: re-emit user messages from stdin back on stdout for acknowledgment. Only works with input-format=stream-json and outputFormat=stream-json (the cacheControl path)."),
322
+ systemPromptFile: z
323
+ .string()
324
+ .min(1)
325
+ .optional()
326
+ .describe("Claude --system-prompt-file: replace the system prompt from a file path (path variant of systemPrompt)."),
327
+ appendSystemPromptFile: z
328
+ .string()
329
+ .min(1)
330
+ .optional()
331
+ .describe("Claude --append-system-prompt-file: append a system prompt from a file path (path variant of appendSystemPrompt)."),
332
+ name: z
333
+ .string()
334
+ .min(1)
335
+ .optional()
336
+ .describe("Claude --name: display name for this session (shown in pickers/titles)."),
337
+ pluginDir: z
338
+ .array(z.string())
339
+ .optional()
340
+ .describe("Claude --plugin-dir: load a plugin from a directory or .zip for this session only. One --plugin-dir instance per entry."),
341
+ pluginUrl: z
342
+ .array(z.string())
343
+ .optional()
344
+ .describe("Claude --plugin-url: load a plugin .zip from a URL for this session only. One --plugin-url instance per entry."),
345
+ safeMode: z
346
+ .boolean()
347
+ .optional()
348
+ .describe("Claude --safe-mode: start with all customizations (CLAUDE.md, skills, plugins, hooks, MCP, commands, agents) disabled for troubleshooting. Explicit opt-in."),
349
+ bare: z
350
+ .boolean()
351
+ .optional()
352
+ .describe("Claude --bare: minimal mode (skip hooks, LSP, plugin sync, attribution, auto-memory, keychain reads, CLAUDE.md auto-discovery). Explicit opt-in."),
353
+ debug: z
354
+ .union([z.string(), z.boolean()])
355
+ .optional()
356
+ .describe('Claude -d/--debug: enable debug mode. true emits a bare --debug; a string emits --debug <filter> (e.g. "api,hooks"). Debug output goes to stderr only.'),
357
+ debugFile: z
358
+ .string()
359
+ .min(1)
360
+ .optional()
361
+ .describe("Claude --debug-file: write debug logs to a specific file path (enables debug mode)."),
362
+ };
363
+ const CODEX_PART_A_FIELDS = {
364
+ enable: z
365
+ .array(z.string())
366
+ .optional()
367
+ .describe("Codex --enable <FEATURE> (repeatable): enable a feature for this run (equivalent to -c features.<name>=true). Accepted on resume."),
368
+ disable: z
369
+ .array(z.string())
370
+ .optional()
371
+ .describe("Codex --disable <FEATURE> (repeatable): disable a feature for this run (equivalent to -c features.<name>=false). Accepted on resume."),
372
+ strictConfig: z
373
+ .boolean()
374
+ .optional()
375
+ .describe("Codex --strict-config: error out when config.toml contains fields not recognized by this Codex version. New sessions only."),
376
+ oss: z
377
+ .boolean()
378
+ .optional()
379
+ .describe("Codex --oss: use the open-source provider. New sessions only."),
380
+ localProvider: z
381
+ .enum(CODEX_LOCAL_PROVIDERS)
382
+ .optional()
383
+ .describe("Codex --local-provider: local OSS provider (lmstudio|ollama), used with oss. New sessions only."),
384
+ color: z
385
+ .enum(CODEX_COLOR_MODES)
386
+ .optional()
387
+ .describe("Codex --color: output color mode (always|never|auto). New sessions only."),
388
+ outputLastMessage: z
389
+ .string()
390
+ .min(1)
391
+ .optional()
392
+ .describe("Codex -o/--output-last-message <FILE>: write the agent's last message to a file. New sessions only."),
393
+ dangerouslyBypassHookTrust: z
394
+ .boolean()
395
+ .optional()
396
+ .describe("Codex --dangerously-bypass-hook-trust: run enabled hooks without persisted hook trust for this invocation. DANGEROUS. Explicit opt-in; never defaulted on."),
397
+ };
300
398
  const GROK_GENERATED_SHAPE = deriveZodShapeFromGeneration(UPSTREAM_CLI_CONTRACTS.grok, GROK_FLAG_GENERATION);
301
399
  export const MAX_TOKENS_SCHEMA = z.number().int().positive().safe().max(100_000_000);
302
400
  export const MAX_PRICE_SCHEMA = z.number().positive().finite().min(1e-6).max(10_000);
@@ -365,7 +463,7 @@ export function resolveGatewayServerRuntime(deps = {}, options = {}) {
365
463
  sessionManager: runtimeSessionManager,
366
464
  resourceProvider: deps.resourceProvider ??
367
465
  (options.isolateState
368
- ? new ResourceProvider(runtimeSessionManager, runtimePerformanceMetrics, runtimeFlightRecorder, deps.cacheAwareness ?? getCacheAwarenessConfig(runtimeLogger), deps.providers ?? getProvidersConfig(runtimeLogger))
466
+ ? new ResourceProvider(runtimeSessionManager, runtimePerformanceMetrics, runtimeFlightRecorder, deps.cacheAwareness ?? getCacheAwarenessConfig(runtimeLogger), deps.providers ?? getProvidersConfig(runtimeLogger), undefined, deps.acpConfig ?? getAcpConfig(runtimeLogger))
369
467
  : resourceProvider),
370
468
  db: "db" in deps ? (deps.db ?? null) : db,
371
469
  performanceMetrics: runtimePerformanceMetrics,
@@ -377,6 +475,7 @@ export function resolveGatewayServerRuntime(deps = {}, options = {}) {
377
475
  cacheAwareness: deps.cacheAwareness ?? getCacheAwarenessConfig(runtimeLogger),
378
476
  providers: deps.providers ?? getProvidersConfig(runtimeLogger),
379
477
  acpConfig: deps.acpConfig ?? getAcpConfig(runtimeLogger),
478
+ adminConfig: deps.adminConfig ?? getAdminConfig(runtimeLogger),
380
479
  workspaces: deps.workspaces ?? loadWorkspaceRegistry(runtimeLogger),
381
480
  };
382
481
  }
@@ -417,12 +516,28 @@ export async function runAcpTransport(deps, params) {
417
516
  model: params.model,
418
517
  sessionId: params.sessionId,
419
518
  correlationId: corrId,
519
+ agentType: params.agentType,
420
520
  });
521
+ const stop = result.stopReason;
522
+ const normalStop = stop === null || stop === "end_turn";
523
+ let warning = null;
524
+ if (!normalStop) {
525
+ warning =
526
+ `provider ended the turn with stop_reason=${stop}` +
527
+ (result.text.trim().length === 0 ? " and produced no output" : "") +
528
+ "; the response may be partial or refused.";
529
+ }
530
+ else if (result.text.trim().length === 0) {
531
+ warning =
532
+ "provider produced no assistant output (possible no-op, guardrail, or awaited input).";
533
+ }
534
+ const banner = `[gateway] transport=acp session=${result.gatewaySessionId}` +
535
+ (warning ? `\n[gateway] warning: ${warning}` : "");
421
536
  return {
422
537
  content: [
423
538
  {
424
539
  type: "text",
425
- text: `[gateway] transport=acp session=${result.gatewaySessionId}\n${result.text}`,
540
+ text: `${banner}\n${result.text}`,
426
541
  },
427
542
  ],
428
543
  };
@@ -738,7 +853,10 @@ async function resolveWorkspaceAndWorktreeForRequest(args) {
738
853
  workspaceAlias: workspace?.alias,
739
854
  workspaceRoot: workspace?.root,
740
855
  });
741
- return { cwd: resolved.cwd, worktreePath: resolved.worktreePath, workspace };
856
+ const displayWorktreePath = isRemoteTransport
857
+ ? remoteSafeWorktreePath(resolved.worktreePath, workspace?.root)
858
+ : resolved.worktreePath;
859
+ return { cwd: resolved.cwd, worktreePath: displayWorktreePath, workspace };
742
860
  }
743
861
  if (workspace && args.sessionId) {
744
862
  await Promise.resolve(args.runtime.sessionManager.updateSessionMetadata(args.sessionId, {
@@ -751,6 +869,16 @@ async function resolveWorkspaceAndWorktreeForRequest(args) {
751
869
  export function formatWorktreePrefix(worktreePath) {
752
870
  return worktreePath ? `[gateway] worktree=${worktreePath}\n` : "";
753
871
  }
872
+ export function remoteSafeWorktreePath(worktreePath, workspaceRoot) {
873
+ if (!worktreePath)
874
+ return worktreePath;
875
+ if (workspaceRoot) {
876
+ const rel = relative(workspaceRoot, worktreePath);
877
+ if (rel && !rel.startsWith("..") && !isAbsolute(rel))
878
+ return rel;
879
+ }
880
+ return basename(worktreePath);
881
+ }
754
882
  function workspaceAdminEnabled() {
755
883
  const scopes = getRequestContext()?.authScopes ?? [];
756
884
  return process.env.LLM_GATEWAY_WORKSPACE_ADMIN === "1" && scopes.includes("workspace:admin");
@@ -780,10 +908,9 @@ function registerWorkspaceTools(server, runtime) {
780
908
  success: true,
781
909
  enabled: registry.enabled,
782
910
  default: registry.defaultAlias,
783
- workspaces: registry.repos.map(describeWorkspace),
911
+ workspaces: registry.repos.map(describeWorkspaceRemote),
784
912
  allowed_roots: registry.allowedRoots.map(root => ({
785
913
  alias: root.alias,
786
- path: root.path,
787
914
  allow_register_existing_git_repos: root.allowRegisterExistingGitRepos,
788
915
  allow_create_directories: root.allowCreateDirectories,
789
916
  allow_init_git_repos: root.allowInitGitRepos,
@@ -812,7 +939,10 @@ function registerWorkspaceTools(server, runtime) {
812
939
  content: [
813
940
  {
814
941
  type: "text",
815
- text: JSON.stringify({ success: true, workspace: describeWorkspace(getWorkspace(registry, alias)) }, null, 2),
942
+ text: JSON.stringify({
943
+ success: true,
944
+ workspace: describeWorkspaceRemote(getWorkspace(registry, alias)),
945
+ }, null, 2),
816
946
  },
817
947
  ],
818
948
  };
@@ -851,7 +981,7 @@ function registerWorkspaceTools(server, runtime) {
851
981
  content: [
852
982
  {
853
983
  type: "text",
854
- text: JSON.stringify({ success: true, workspace: describeWorkspace(repo) }, null, 2),
984
+ text: JSON.stringify({ success: true, workspace: describeWorkspaceRemote(repo) }, null, 2),
855
985
  },
856
986
  ],
857
987
  };
@@ -889,7 +1019,7 @@ function registerWorkspaceTools(server, runtime) {
889
1019
  content: [
890
1020
  {
891
1021
  type: "text",
892
- text: JSON.stringify({ success: true, workspace: describeWorkspace(repo) }, null, 2),
1022
+ text: JSON.stringify({ success: true, workspace: describeWorkspaceRemote(repo) }, null, 2),
893
1023
  },
894
1024
  ],
895
1025
  };
@@ -1136,7 +1266,7 @@ function resolveClaudeMcpConfig(operation, correlationId, requestedMcpServers, s
1136
1266
  }
1137
1267
  return { config: mcpConfig };
1138
1268
  }
1139
- function registerBaseResources(server, runtime) {
1269
+ export function registerBaseResources(server, runtime) {
1140
1270
  for (const skill of loadedSkills) {
1141
1271
  server.registerResource(`skill-${skill.name}`, `skills://${skill.name}`, {
1142
1272
  title: skill.name,
@@ -1162,96 +1292,30 @@ function registerBaseResources(server, runtime) {
1162
1292
  const contents = await runtime.resourceProvider.readResource(uri.href);
1163
1293
  return { contents: contents ? [contents] : [] };
1164
1294
  });
1165
- server.registerResource("claude-sessions", "sessions://claude", {
1166
- title: "🤖 Claude Sessions",
1167
- description: "Claude conversation sessions",
1168
- mimeType: "application/json",
1169
- }, async (uri) => {
1170
- runtime.logger.debug("Reading Claude sessions resource");
1171
- const contents = await runtime.resourceProvider.readResource(uri.href);
1172
- return { contents: contents ? [contents] : [] };
1173
- });
1174
- server.registerResource("codex-sessions", "sessions://codex", {
1175
- title: "💻 Codex Sessions",
1176
- description: "Codex conversation sessions",
1177
- mimeType: "application/json",
1178
- }, async (uri) => {
1179
- runtime.logger.debug("Reading Codex sessions resource");
1180
- const contents = await runtime.resourceProvider.readResource(uri.href);
1181
- return { contents: contents ? [contents] : [] };
1182
- });
1183
- server.registerResource("gemini-sessions", "sessions://gemini", {
1184
- title: "✨ Gemini Sessions",
1185
- description: "Gemini conversation sessions",
1186
- mimeType: "application/json",
1187
- }, async (uri) => {
1188
- runtime.logger.debug("Reading Gemini sessions resource");
1189
- const contents = await runtime.resourceProvider.readResource(uri.href);
1190
- return { contents: contents ? [contents] : [] };
1191
- });
1192
- server.registerResource("grok-sessions", "sessions://grok", {
1193
- title: "⚡ Grok Sessions",
1194
- description: "Grok conversation sessions",
1195
- mimeType: "application/json",
1196
- }, async (uri) => {
1197
- runtime.logger.debug("Reading Grok sessions resource");
1198
- const contents = await runtime.resourceProvider.readResource(uri.href);
1199
- return { contents: contents ? [contents] : [] };
1200
- });
1201
- server.registerResource("mistral-sessions", "sessions://mistral", {
1202
- title: "🌬 Mistral Sessions",
1203
- description: "Mistral Vibe conversation sessions",
1204
- mimeType: "application/json",
1205
- }, async (uri) => {
1206
- runtime.logger.debug("Reading Mistral sessions resource");
1207
- const contents = await runtime.resourceProvider.readResource(uri.href);
1208
- return { contents: contents ? [contents] : [] };
1209
- });
1210
- server.registerResource("claude-models", "models://claude", {
1211
- title: "🧠 Claude Models",
1212
- description: "Claude models and capabilities",
1213
- mimeType: "application/json",
1214
- }, async (uri) => {
1215
- runtime.logger.debug("Reading Claude models resource");
1216
- const contents = await runtime.resourceProvider.readResource(uri.href);
1217
- return { contents: contents ? [contents] : [] };
1218
- });
1219
- server.registerResource("codex-models", "models://codex", {
1220
- title: "🔧 Codex Models",
1221
- description: "Codex models and capabilities",
1222
- mimeType: "application/json",
1223
- }, async (uri) => {
1224
- runtime.logger.debug("Reading Codex models resource");
1225
- const contents = await runtime.resourceProvider.readResource(uri.href);
1226
- return { contents: contents ? [contents] : [] };
1227
- });
1228
- server.registerResource("gemini-models", "models://gemini", {
1229
- title: "🌟 Gemini Models",
1230
- description: "Gemini models and capabilities",
1231
- mimeType: "application/json",
1232
- }, async (uri) => {
1233
- runtime.logger.debug("Reading Gemini models resource");
1234
- const contents = await runtime.resourceProvider.readResource(uri.href);
1235
- return { contents: contents ? [contents] : [] };
1236
- });
1237
- server.registerResource("grok-models", "models://grok", {
1238
- title: "⚡ Grok Models",
1239
- description: "Grok models and capabilities",
1240
- mimeType: "application/json",
1241
- }, async (uri) => {
1242
- runtime.logger.debug("Reading Grok models resource");
1243
- const contents = await runtime.resourceProvider.readResource(uri.href);
1244
- return { contents: contents ? [contents] : [] };
1245
- });
1246
- server.registerResource("mistral-models", "models://mistral", {
1247
- title: "🌬 Mistral Models",
1248
- description: "Mistral Vibe models and capabilities",
1249
- mimeType: "application/json",
1250
- }, async (uri) => {
1251
- runtime.logger.debug("Reading Mistral models resource");
1252
- const contents = await runtime.resourceProvider.readResource(uri.href);
1253
- return { contents: contents ? [contents] : [] };
1254
- });
1295
+ for (const descriptor of generateResourceDescriptors()) {
1296
+ if (descriptor.exposesSessionsResource) {
1297
+ server.registerResource(`${descriptor.provider}-sessions`, descriptor.sessionsUri, {
1298
+ title: `${descriptor.icon} ${descriptor.sessionLabel}s`,
1299
+ description: `${descriptor.displayName} conversation sessions`,
1300
+ mimeType: "application/json",
1301
+ }, async (uri) => {
1302
+ runtime.logger.debug(`Reading ${descriptor.sessionsUri} resource`);
1303
+ const contents = await runtime.resourceProvider.readResource(uri.href);
1304
+ return { contents: contents ? [contents] : [] };
1305
+ });
1306
+ }
1307
+ if (descriptor.exposesModelsResource) {
1308
+ server.registerResource(`${descriptor.provider}-models`, descriptor.modelsUri, {
1309
+ title: `${descriptor.icon} ${descriptor.displayName} Models & Capabilities`,
1310
+ description: `${descriptor.displayName} models and capabilities`,
1311
+ mimeType: "application/json",
1312
+ }, async (uri) => {
1313
+ runtime.logger.debug(`Reading ${descriptor.modelsUri} resource`);
1314
+ const contents = await runtime.resourceProvider.readResource(uri.href);
1315
+ return { contents: contents ? [contents] : [] };
1316
+ });
1317
+ }
1318
+ }
1255
1319
  server.registerResource("performance-metrics", "metrics://performance", {
1256
1320
  title: "📈 Performance Metrics",
1257
1321
  description: "Request counts, latency, success/failure rates",
@@ -1363,9 +1427,30 @@ function registerBaseResources(server, runtime) {
1363
1427
  const contents = await runtime.resourceProvider.readResource(uri.href);
1364
1428
  return { contents: contents ? [contents] : [] };
1365
1429
  });
1366
- const validationReceiptStore = runtime.persistence.backend === "sqlite"
1367
- ? runtime.asyncJobManager.getValidationRunStore()
1368
- : null;
1430
+ for (const descriptor of generateProviderAcpDescriptors()) {
1431
+ server.registerResource(`${descriptor.provider}-provider-acp`, descriptor.acpUri, {
1432
+ title: `${descriptor.icon} ${descriptor.displayName} ACP Capabilities`,
1433
+ description: `Native ACP entrypoint, negotiated capabilities, supported session methods, and host-service policy for ${descriptor.displayName}`,
1434
+ mimeType: "application/json",
1435
+ }, async (uri) => {
1436
+ runtime.logger.debug(`Reading ${descriptor.acpUri} resource`);
1437
+ const contents = await runtime.resourceProvider.readResource(uri.href);
1438
+ return { contents: contents ? [contents] : [] };
1439
+ });
1440
+ }
1441
+ server.registerResource("provider-acp", new ResourceTemplate("provider-acp://{provider}", { list: undefined }), {
1442
+ title: "Provider ACP Capabilities",
1443
+ description: "Read-only negotiated ACP capability record for one provider CLI (native: false for providers with no native ACP entrypoint)",
1444
+ mimeType: "application/json",
1445
+ }, async (uri, variables) => {
1446
+ const provider = Array.isArray(variables.provider)
1447
+ ? variables.provider[0]
1448
+ : variables.provider;
1449
+ runtime.logger.debug(`Reading provider-acp://${provider}`);
1450
+ const contents = await runtime.resourceProvider.readResource(uri.href);
1451
+ return { contents: contents ? [contents] : [] };
1452
+ });
1453
+ const validationReceiptStore = runtime.asyncJobManager?.getValidationRunStore();
1369
1454
  if (validationReceiptStore) {
1370
1455
  server.registerResource("validation-receipt", new ResourceTemplate("validation-receipt://{validationId}", { list: undefined }), {
1371
1456
  title: "Validation Receipt",
@@ -1411,10 +1496,33 @@ function resolvePromptOrPartsForPrep(args) {
1411
1496
  stablePrefixTokens: resolved.stablePrefixTokens,
1412
1497
  };
1413
1498
  }
1499
+ function remoteHostPathFieldError(operation, corrId, fields) {
1500
+ const ctx = getRequestContext();
1501
+ const isRemote = ctx?.transport === "http" || ctx?.authKind === "oauth";
1502
+ if (!isRemote)
1503
+ return null;
1504
+ const present = Object.entries(fields)
1505
+ .filter(([, v]) => (Array.isArray(v) ? v.length > 0 : v !== undefined && v !== null))
1506
+ .map(([k]) => k);
1507
+ if (present.length === 0)
1508
+ return null;
1509
+ return createErrorResponse(operation, 1, `Remote HTTP/OAuth requests may not use host-path or plugin fields: ${present.join(", ")}. ` +
1510
+ `These are restricted to local callers; supply content inline or reference a registered workspace.`, corrId);
1511
+ }
1414
1512
  export function prepareClaudeRequest(params, runtime = resolveGatewayServerRuntime()) {
1415
1513
  const corrId = params.correlationId || randomUUID();
1416
1514
  const cliInfo = getCliInfo();
1417
1515
  const resolvedModel = resolveModelAlias("claude", params.model, cliInfo);
1516
+ const remoteFieldErr = remoteHostPathFieldError(params.operation, corrId, {
1517
+ settings: params.settings,
1518
+ systemPromptFile: params.systemPromptFile,
1519
+ appendSystemPromptFile: params.appendSystemPromptFile,
1520
+ pluginDir: params.pluginDir,
1521
+ pluginUrl: params.pluginUrl,
1522
+ debugFile: params.debugFile,
1523
+ });
1524
+ if (remoteFieldErr)
1525
+ return remoteFieldErr;
1418
1526
  const inputResolution = resolvePromptOrPartsForPrep({
1419
1527
  prompt: params.prompt,
1420
1528
  promptParts: params.promptParts,
@@ -1634,6 +1742,17 @@ export function prepareClaudeRequest(params, runtime = resolveGatewayServerRunti
1634
1742
  settingSources: params.settingSources,
1635
1743
  settings: params.settings,
1636
1744
  tools: params.tools,
1745
+ includeHookEvents: params.includeHookEvents,
1746
+ replayUserMessages: params.replayUserMessages,
1747
+ systemPromptFile: params.systemPromptFile,
1748
+ appendSystemPromptFile: params.appendSystemPromptFile,
1749
+ name: params.name,
1750
+ pluginDir: params.pluginDir,
1751
+ pluginUrl: params.pluginUrl,
1752
+ safeMode: params.safeMode,
1753
+ bare: params.bare,
1754
+ debug: params.debug,
1755
+ debugFile: params.debugFile,
1637
1756
  }));
1638
1757
  return {
1639
1758
  corrId,
@@ -1656,6 +1775,13 @@ export function prepareCodexRequest(params, runtime = resolveGatewayServerRuntim
1656
1775
  const corrId = params.correlationId || randomUUID();
1657
1776
  const cliInfo = getCliInfo();
1658
1777
  const resolvedModel = resolveModelAlias("codex", params.model, cliInfo);
1778
+ const remoteFieldErr = remoteHostPathFieldError(params.operation, corrId, {
1779
+ outputSchema: typeof params.outputSchema === "string" ? params.outputSchema : undefined,
1780
+ images: params.images,
1781
+ outputLastMessage: params.outputLastMessage,
1782
+ });
1783
+ if (remoteFieldErr)
1784
+ return remoteFieldErr;
1659
1785
  const inputResolution = resolvePromptOrPartsForPrep({
1660
1786
  prompt: params.prompt,
1661
1787
  promptParts: params.promptParts,
@@ -1734,6 +1860,9 @@ export function prepareCodexRequest(params, runtime = resolveGatewayServerRuntim
1734
1860
  if (params.dangerouslyBypassApprovalsAndSandbox) {
1735
1861
  args.push("--dangerously-bypass-approvals-and-sandbox");
1736
1862
  }
1863
+ if (params.dangerouslyBypassHookTrust) {
1864
+ args.push("--dangerously-bypass-hook-trust");
1865
+ }
1737
1866
  args.push("--json");
1738
1867
  args.push("--skip-git-repo-check");
1739
1868
  let highImpactCleanup;
@@ -1746,6 +1875,21 @@ export function prepareCodexRequest(params, runtime = resolveGatewayServerRuntim
1746
1875
  args.push("--add-dir", dir);
1747
1876
  }
1748
1877
  }
1878
+ if (params.oss) {
1879
+ args.push("--oss");
1880
+ }
1881
+ if (params.localProvider) {
1882
+ args.push("--local-provider", params.localProvider);
1883
+ }
1884
+ if (params.strictConfig) {
1885
+ args.push("--strict-config");
1886
+ }
1887
+ if (params.color) {
1888
+ args.push("--color", params.color);
1889
+ }
1890
+ if (params.outputLastMessage !== undefined) {
1891
+ args.push("--output-last-message", params.outputLastMessage);
1892
+ }
1749
1893
  const high = prepareCodexHighImpactFlags({
1750
1894
  outputSchema: params.outputSchema,
1751
1895
  search: params.search,
@@ -1755,6 +1899,8 @@ export function prepareCodexRequest(params, runtime = resolveGatewayServerRuntim
1755
1899
  images: params.images,
1756
1900
  ignoreUserConfig: params.ignoreUserConfig,
1757
1901
  ignoreRules: params.ignoreRules,
1902
+ enable: params.enable,
1903
+ disable: params.disable,
1758
1904
  });
1759
1905
  if (high.missingImagePath) {
1760
1906
  return createErrorResponse(params.operation, 1, "", corrId, new Error(`images: path does not exist: ${high.missingImagePath}`));
@@ -1778,6 +1924,8 @@ export function prepareCodexRequest(params, runtime = resolveGatewayServerRuntim
1778
1924
  images: params.images,
1779
1925
  ignoreUserConfig: params.ignoreUserConfig,
1780
1926
  ignoreRules: params.ignoreRules,
1927
+ enable: params.enable,
1928
+ disable: params.disable,
1781
1929
  });
1782
1930
  if (high.missingImagePath) {
1783
1931
  return createErrorResponse(params.operation, 1, "", corrId, new Error(`images: path does not exist: ${high.missingImagePath}`));
@@ -1907,6 +2055,9 @@ export function prepareGeminiRequest(params, runtime = resolveGatewayServerRunti
1907
2055
  if (params.newProject) {
1908
2056
  args.push("--new-project");
1909
2057
  }
2058
+ if (params.printTimeout !== undefined && params.printTimeout !== "") {
2059
+ args.push("--print-timeout", params.printTimeout);
2060
+ }
1910
2061
  return {
1911
2062
  corrId,
1912
2063
  effectivePrompt,
@@ -1923,6 +2074,14 @@ export function prepareGrokRequest(params, runtime = resolveGatewayServerRuntime
1923
2074
  const corrId = params.correlationId || randomUUID();
1924
2075
  const cliInfo = getCliInfo();
1925
2076
  const resolvedModel = resolveModelAlias("grok", params.model, cliInfo);
2077
+ const remoteFieldErr = remoteHostPathFieldError(params.operation, corrId, {
2078
+ promptFile: params.promptFile,
2079
+ leaderSocket: params.leaderSocket,
2080
+ rules: params.rules,
2081
+ agent: params.agent,
2082
+ });
2083
+ if (remoteFieldErr)
2084
+ return remoteFieldErr;
1926
2085
  const inputResolution = resolvePromptOrPartsForPrep({
1927
2086
  prompt: params.prompt,
1928
2087
  promptParts: params.promptParts,
@@ -2185,6 +2344,9 @@ export function buildCliResponse(cli, stdout, optimizeResponse, corrId, sessionI
2185
2344
  if (cli === "codex" && outputFormat !== "json") {
2186
2345
  finalStdout = codexDisplayText(stdout);
2187
2346
  }
2347
+ if (cli === "grok") {
2348
+ finalStdout = grokDisplayText(outputFormat, stdout);
2349
+ }
2188
2350
  if (optimizeResponse && outputFormat !== "json") {
2189
2351
  const optimized = optimizeResponseText(finalStdout);
2190
2352
  logOptimizationTokens("response", corrId, finalStdout, optimized);
@@ -2200,11 +2362,11 @@ export function buildCliResponse(cli, stdout, optimizeResponse, corrId, sessionI
2200
2362
  }
2201
2363
  const derivedWarnings = [];
2202
2364
  const extraStructured = {};
2203
- let claudeResultError = false;
2365
+ let resultError = false;
2204
2366
  if (cli === "claude") {
2205
2367
  const parsedResult = parseStreamJson(stdout);
2206
2368
  if (parsedResult.isError) {
2207
- claudeResultError = true;
2369
+ resultError = true;
2208
2370
  extraStructured.resultIsError = true;
2209
2371
  derivedWarnings.push({
2210
2372
  code: "claude_result_error",
@@ -2217,8 +2379,27 @@ export function buildCliResponse(cli, stdout, optimizeResponse, corrId, sessionI
2217
2379
  if (parsedCodex.threadId) {
2218
2380
  extraStructured.codexSessionId = parsedCodex.threadId;
2219
2381
  }
2382
+ if (parsedCodex.error) {
2383
+ resultError = true;
2384
+ extraStructured.resultIsError = true;
2385
+ derivedWarnings.push({
2386
+ code: "codex_result_error",
2387
+ message: `Codex exited 0 but reported a failed turn: ${parsedCodex.error}. The returned text may be partial.`,
2388
+ });
2389
+ }
2220
2390
  }
2221
- if (!claudeResultError && finalStdout.trim().length === 0) {
2391
+ if (cli === "gemini") {
2392
+ const parsedGemini = outputFormat === "stream-json" ? parseGeminiStreamJson(stdout) : parseGeminiJson(stdout);
2393
+ if (parsedGemini?.stopReason === "error") {
2394
+ resultError = true;
2395
+ extraStructured.resultIsError = true;
2396
+ derivedWarnings.push({
2397
+ code: "gemini_result_error",
2398
+ message: "Gemini exited 0 but the result event reported status:error. The returned text may be partial.",
2399
+ });
2400
+ }
2401
+ }
2402
+ if (!resultError && finalStdout.trim().length === 0) {
2222
2403
  extraStructured.emptyOutput = true;
2223
2404
  derivedWarnings.push({
2224
2405
  code: "empty_output",
@@ -2956,6 +3137,7 @@ export async function handleGeminiRequest(deps, params) {
2956
3137
  yolo: params.yolo,
2957
3138
  project: params.project,
2958
3139
  newProject: params.newProject,
3140
+ printTimeout: params.printTimeout,
2959
3141
  }, runtime);
2960
3142
  if (!("args" in prep))
2961
3143
  return prep;
@@ -3044,6 +3226,7 @@ export async function handleGeminiRequest(deps, params) {
3044
3226
  }
3045
3227
  }
3046
3228
  const geminiUsage = extractUsageAndCost("gemini", stdout, params.outputFormat);
3229
+ const geminiMeta = extractProviderOutputMetadata("gemini", stdout, params.outputFormat);
3047
3230
  safeFlightComplete(corrId, {
3048
3231
  response: stdout,
3049
3232
  durationMs,
@@ -3058,6 +3241,8 @@ export async function handleGeminiRequest(deps, params) {
3058
3241
  cacheReadTokens: geminiUsage.cacheReadTokens,
3059
3242
  cacheCreationTokens: geminiUsage.cacheCreationTokens,
3060
3243
  costUsd: geminiUsage.costUsd,
3244
+ providerSessionId: geminiMeta.sessionId,
3245
+ stopReason: geminiMeta.stopReason,
3061
3246
  }, runtime);
3062
3247
  return response;
3063
3248
  }
@@ -3105,6 +3290,7 @@ export async function handleGeminiRequestAsync(deps, params) {
3105
3290
  yolo: params.yolo,
3106
3291
  project: params.project,
3107
3292
  newProject: params.newProject,
3293
+ printTimeout: params.printTimeout,
3108
3294
  }, runtime);
3109
3295
  if (!("args" in prep))
3110
3296
  return prep;
@@ -3326,6 +3512,7 @@ export async function handleGrokRequest(deps, params) {
3326
3512
  first.text = formatWorktreePrefix(worktreeResolution.worktreePath) + first.text;
3327
3513
  }
3328
3514
  }
3515
+ const grokMeta = extractProviderOutputMetadata("grok", stdout, params.outputFormat);
3329
3516
  safeFlightComplete(corrId, {
3330
3517
  response: stdout,
3331
3518
  durationMs,
@@ -3335,6 +3522,8 @@ export async function handleGrokRequest(deps, params) {
3335
3522
  optimizationApplied: params.optimizePrompt || (params.optimizeResponse ?? false),
3336
3523
  exitCode: 0,
3337
3524
  status: "completed",
3525
+ providerSessionId: grokMeta.sessionId,
3526
+ stopReason: grokMeta.stopReason,
3338
3527
  }, runtime);
3339
3528
  return response;
3340
3529
  }
@@ -3489,6 +3678,14 @@ export async function handleGrokRequestAsync(deps, params) {
3489
3678
  }
3490
3679
  export function prepareDevinRequest(params, _runtime) {
3491
3680
  const corrId = params.correlationId ?? randomUUID();
3681
+ const remoteFieldErr = remoteHostPathFieldError(params.operation, corrId, {
3682
+ promptFile: params.promptFile,
3683
+ config: params.config,
3684
+ agentConfig: params.agentConfig,
3685
+ exportSession: typeof params.exportSession === "string" ? params.exportSession : undefined,
3686
+ });
3687
+ if (remoteFieldErr)
3688
+ return remoteFieldErr;
3492
3689
  let prompt = (params.prompt ?? "").trim();
3493
3690
  if (!prompt) {
3494
3691
  return createErrorResponse(params.operation, 1, "prompt is required and cannot be empty", corrId);
@@ -3503,6 +3700,21 @@ export function prepareDevinRequest(params, _runtime) {
3503
3700
  args.push("--permission-mode", params.permissionMode);
3504
3701
  if (params.promptFile)
3505
3702
  args.push("--prompt-file", params.promptFile);
3703
+ if (params.config)
3704
+ args.push("--config", params.config);
3705
+ if (params.sandbox)
3706
+ args.push("--sandbox");
3707
+ if (typeof params.exportSession === "string") {
3708
+ args.push("--export", params.exportSession);
3709
+ }
3710
+ else if (params.exportSession === true) {
3711
+ args.push("--export");
3712
+ }
3713
+ if (params.respectWorkspaceTrust !== undefined) {
3714
+ args.push("--respect-workspace-trust", params.respectWorkspaceTrust ? "true" : "false");
3715
+ }
3716
+ if (params.agentConfig)
3717
+ args.push("--agent-config", params.agentConfig);
3506
3718
  return {
3507
3719
  corrId,
3508
3720
  effectivePrompt: prompt,
@@ -3522,6 +3734,7 @@ export async function handleDevinRequest(deps, params) {
3522
3734
  model: params.model,
3523
3735
  sessionId: params.sessionId,
3524
3736
  correlationId: params.correlationId,
3737
+ agentType: params.agentType,
3525
3738
  });
3526
3739
  }
3527
3740
  const runtime = resolveHandlerRuntime(deps);
@@ -3531,6 +3744,11 @@ export async function handleDevinRequest(deps, params) {
3531
3744
  model: params.model,
3532
3745
  permissionMode: params.permissionMode,
3533
3746
  promptFile: params.promptFile,
3747
+ config: params.config,
3748
+ sandbox: params.sandbox,
3749
+ exportSession: params.exportSession,
3750
+ respectWorkspaceTrust: params.respectWorkspaceTrust,
3751
+ agentConfig: params.agentConfig,
3534
3752
  correlationId: params.correlationId,
3535
3753
  optimizePrompt: params.optimizePrompt,
3536
3754
  operation: "devin_request",
@@ -3634,6 +3852,11 @@ export async function handleDevinRequestAsync(deps, params) {
3634
3852
  model: params.model,
3635
3853
  permissionMode: params.permissionMode,
3636
3854
  promptFile: params.promptFile,
3855
+ config: params.config,
3856
+ sandbox: params.sandbox,
3857
+ exportSession: params.exportSession,
3858
+ respectWorkspaceTrust: params.respectWorkspaceTrust,
3859
+ agentConfig: params.agentConfig,
3637
3860
  correlationId: params.correlationId,
3638
3861
  optimizePrompt: params.optimizePrompt,
3639
3862
  operation: "devin_request_async",
@@ -4338,6 +4561,14 @@ export async function handleCodexRequestAsync(deps, params) {
4338
4561
  ignoreRules: params.ignoreRules,
4339
4562
  workingDir: params.workingDir,
4340
4563
  addDir: params.addDir,
4564
+ enable: params.enable,
4565
+ disable: params.disable,
4566
+ strictConfig: params.strictConfig,
4567
+ oss: params.oss,
4568
+ localProvider: params.localProvider,
4569
+ color: params.color,
4570
+ outputLastMessage: params.outputLastMessage,
4571
+ dangerouslyBypassHookTrust: params.dangerouslyBypassHookTrust,
4341
4572
  }, runtime);
4342
4573
  if (!("args" in prep))
4343
4574
  return prep;
@@ -4442,9 +4673,14 @@ export function createGatewayServer(deps = {}) {
4442
4673
  registerValidationTools(server, {
4443
4674
  asyncJobManager,
4444
4675
  apiProviders: enabledApiProviders(providers),
4445
- validationRunStore: persistence.backend === "sqlite" ? asyncJobManager.getValidationRunStore() : null,
4676
+ validationRunStore: asyncJobManager.getValidationRunStore(),
4446
4677
  });
4447
4678
  registerWorkspaceTools(server, runtime);
4679
+ registerProviderAdminTools(server, {
4680
+ approvalManager: runtime.approvalManager,
4681
+ logger: runtime.logger,
4682
+ allowMutatingCliAdminOps: runtime.adminConfig.allowMutatingCliAdminOps,
4683
+ });
4448
4684
  const apiProviderTools = registerApiProviderTools(server, runtime, providers, asyncJobsEnabled);
4449
4685
  if (apiProviderTools.length > 0) {
4450
4686
  runtime.logger.info(`Registered API provider tools: ${apiProviderTools.join(", ")}`);
@@ -4633,6 +4869,7 @@ export function createGatewayServer(deps = {}) {
4633
4869
  .array(z.string())
4634
4870
  .optional()
4635
4871
  .describe('Claude --tools: restrict the available built-in tool set (distinct from allowedTools permission gating). Pass [""] to disable all tools.'),
4872
+ ...CLAUDE_PART_A_FIELDS,
4636
4873
  workspace: providerWorkspaceAliasSchema(),
4637
4874
  worktree: WORKTREE_SCHEMA.optional(),
4638
4875
  approvalStrategy: z
@@ -4668,7 +4905,7 @@ export function createGatewayServer(deps = {}) {
4668
4905
  destructiveHint: true,
4669
4906
  idempotentHint: false,
4670
4907
  openWorldHint: true,
4671
- }, async ({ prompt, promptParts, model, outputFormat, sessionId, continueSession, createNewSession, allowedTools, disallowedTools, dangerouslySkipPermissions, permissionMode, agent, agents, forkSession, systemPrompt, appendSystemPrompt, maxBudgetUsd, maxTurns, effort, excludeDynamicSystemPromptSections, fallbackModel, jsonSchema, addDir, noSessionPersistence, settingSources, settings, tools, workspace, worktree, approvalStrategy, approvalPolicy, mcpServers, strictMcpConfig, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, }) => {
4908
+ }, async ({ prompt, promptParts, model, outputFormat, sessionId, continueSession, createNewSession, allowedTools, disallowedTools, dangerouslySkipPermissions, permissionMode, agent, agents, forkSession, systemPrompt, appendSystemPrompt, maxBudgetUsd, maxTurns, effort, excludeDynamicSystemPromptSections, fallbackModel, jsonSchema, addDir, noSessionPersistence, settingSources, settings, tools, includeHookEvents, replayUserMessages, systemPromptFile, appendSystemPromptFile, name, pluginDir, pluginUrl, safeMode, bare, debug, debugFile, workspace, worktree, approvalStrategy, approvalPolicy, mcpServers, strictMcpConfig, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, }) => {
4672
4909
  const startTime = Date.now();
4673
4910
  if (systemPrompt !== undefined && appendSystemPrompt !== undefined) {
4674
4911
  return createErrorResponse("claude", 1, "", correlationId, new Error("systemPrompt and appendSystemPrompt are mutually exclusive; use one or the other (not both)."));
@@ -4705,6 +4942,17 @@ export function createGatewayServer(deps = {}) {
4705
4942
  settingSources,
4706
4943
  settings,
4707
4944
  tools,
4945
+ includeHookEvents,
4946
+ replayUserMessages,
4947
+ systemPromptFile,
4948
+ appendSystemPromptFile,
4949
+ name,
4950
+ pluginDir,
4951
+ pluginUrl,
4952
+ safeMode,
4953
+ bare,
4954
+ debug,
4955
+ debugFile,
4708
4956
  }, runtime);
4709
4957
  if (!("args" in prep))
4710
4958
  return prep;
@@ -4832,6 +5080,8 @@ export function createGatewayServer(deps = {}) {
4832
5080
  optimizationApplied: optimizePrompt || optimizeResponse,
4833
5081
  exitCode: 0,
4834
5082
  status: "completed",
5083
+ providerSessionId: parsed.sessionId ?? undefined,
5084
+ stopReason: parsed.stopReason ?? undefined,
4835
5085
  }, runtime);
4836
5086
  const streamResponse = buildCliResponse("claude", parsed.text, optimizeResponse, corrId, effectiveSessionId, prep, durationMs, undefined, outputFormat, warnings);
4837
5087
  if (worktreeResolution.worktreePath) {
@@ -4842,6 +5092,7 @@ export function createGatewayServer(deps = {}) {
4842
5092
  }
4843
5093
  return streamResponse;
4844
5094
  }
5095
+ const claudeMeta = extractProviderOutputMetadata("claude", stdout, outputFormat);
4845
5096
  safeFlightComplete(corrId, {
4846
5097
  response: stdout,
4847
5098
  durationMs,
@@ -4850,6 +5101,8 @@ export function createGatewayServer(deps = {}) {
4850
5101
  optimizationApplied: optimizePrompt || optimizeResponse,
4851
5102
  exitCode: 0,
4852
5103
  status: "completed",
5104
+ providerSessionId: claudeMeta.sessionId,
5105
+ stopReason: claudeMeta.stopReason,
4853
5106
  }, runtime);
4854
5107
  const nonStreamResponse = buildCliResponse("claude", stdout, optimizeResponse, corrId, effectiveSessionId, prep, durationMs, undefined, outputFormat, warnings);
4855
5108
  if (worktreeResolution.worktreePath) {
@@ -4988,6 +5241,7 @@ export function createGatewayServer(deps = {}) {
4988
5241
  .optional()
4989
5242
  .describe("Codex --add-dir <DIR>: additional writable workspace directories. Emitted once per entry on new sessions only; resume inherits the original session's writable-dir policy." +
4990
5243
  LOCAL_ADD_DIR_FIELD_SUFFIX),
5244
+ ...CODEX_PART_A_FIELDS,
4991
5245
  workspace: providerWorkspaceAliasSchema(),
4992
5246
  worktree: WORKTREE_SCHEMA.optional(),
4993
5247
  }, {
@@ -4996,7 +5250,7 @@ export function createGatewayServer(deps = {}) {
4996
5250
  destructiveHint: true,
4997
5251
  idempotentHint: false,
4998
5252
  openWorldHint: true,
4999
- }, async ({ prompt, promptParts, model, fullAuto, sandboxMode, askForApproval, useLegacyFullAutoFlag, dangerouslyBypassApprovalsAndSandbox, approvalStrategy, approvalPolicy, mcpServers, sessionId, resumeLatest, createNewSession, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, outputFormat, outputSchema, search, profile, configOverrides, ephemeral, images, ignoreUserConfig, ignoreRules, workingDir, addDir, workspace, worktree, }) => {
5253
+ }, async ({ prompt, promptParts, model, fullAuto, sandboxMode, askForApproval, useLegacyFullAutoFlag, dangerouslyBypassApprovalsAndSandbox, approvalStrategy, approvalPolicy, mcpServers, sessionId, resumeLatest, createNewSession, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, outputFormat, outputSchema, search, profile, configOverrides, ephemeral, images, ignoreUserConfig, ignoreRules, workingDir, addDir, enable, disable, strictConfig, oss, localProvider, color, outputLastMessage, dangerouslyBypassHookTrust, workspace, worktree, }) => {
5000
5254
  const startTime = Date.now();
5001
5255
  const prep = prepareCodexRequest({
5002
5256
  prompt,
@@ -5027,6 +5281,14 @@ export function createGatewayServer(deps = {}) {
5027
5281
  ignoreRules,
5028
5282
  workingDir,
5029
5283
  addDir,
5284
+ enable,
5285
+ disable,
5286
+ strictConfig,
5287
+ oss,
5288
+ localProvider,
5289
+ color,
5290
+ outputLastMessage,
5291
+ dangerouslyBypassHookTrust,
5030
5292
  }, runtime);
5031
5293
  if (!("args" in prep))
5032
5294
  return prep;
@@ -5085,7 +5347,12 @@ export function createGatewayServer(deps = {}) {
5085
5347
  errorMessage: stderr || `Exit code ${code}`,
5086
5348
  status: "failed",
5087
5349
  }, runtime);
5088
- const codexErrorDetail = stderr && stderr.trim().length > 0 ? stderr : codexDisplayText(stdout);
5350
+ const parsedCodexError = parseCodexJsonStream(stdout).error;
5351
+ const codexErrorDetail = stderr && stderr.trim().length > 0
5352
+ ? stderr
5353
+ : parsedCodexError && parsedCodexError.trim().length > 0
5354
+ ? parsedCodexError
5355
+ : codexDisplayText(stdout);
5089
5356
  const codexResumeHint = sessionId &&
5090
5357
  /not found|no such|unknown session|does not exist|invalid session/i.test(codexErrorDetail)
5091
5358
  ? `\n\nSession ${sessionId} could not be resumed. Codex session IDs are UUIDs under ~/.codex/sessions/; verify the id, omit sessionId, or set createNewSession:true.`
@@ -5113,6 +5380,7 @@ export function createGatewayServer(deps = {}) {
5113
5380
  }
5114
5381
  logger.info(`[${corrId}] codex_request completed successfully in ${durationMs}ms`);
5115
5382
  const codexUsage = extractUsageAndCost("codex", stdout, outputFormat);
5383
+ const codexMeta = extractProviderOutputMetadata("codex", stdout, outputFormat);
5116
5384
  safeFlightComplete(corrId, {
5117
5385
  response: codexFrResponse(outputFormat, stdout),
5118
5386
  durationMs,
@@ -5126,6 +5394,8 @@ export function createGatewayServer(deps = {}) {
5126
5394
  cacheReadTokens: codexUsage.cacheReadTokens,
5127
5395
  cacheCreationTokens: codexUsage.cacheCreationTokens,
5128
5396
  costUsd: codexUsage.costUsd,
5397
+ providerSessionId: codexMeta.sessionId,
5398
+ stopReason: codexMeta.stopReason,
5129
5399
  }, runtime);
5130
5400
  const codexResponse = buildCliResponse("codex", stdout, optimizeResponse, corrId, effectiveSessionId, prep, durationMs, undefined, outputFormat);
5131
5401
  if (worktreeResolution.worktreePath) {
@@ -5247,7 +5517,12 @@ export function createGatewayServer(deps = {}) {
5247
5517
  const { stdout, stderr, code } = result;
5248
5518
  durationMs = Math.max(0, Date.now() - startTime);
5249
5519
  if (code !== 0) {
5250
- const codexErrorDetail = stderr && stderr.trim().length > 0 ? stderr : codexDisplayText(stdout);
5520
+ const parsedCodexError = parseCodexJsonStream(stdout).error;
5521
+ const codexErrorDetail = stderr && stderr.trim().length > 0
5522
+ ? stderr
5523
+ : parsedCodexError && parsedCodexError.trim().length > 0
5524
+ ? parsedCodexError
5525
+ : codexDisplayText(stdout);
5251
5526
  const codexResumeHint = sessionId &&
5252
5527
  /not found|no such|unknown session|does not exist|invalid session/i.test(codexErrorDetail)
5253
5528
  ? `\n\nSession ${sessionId} could not be resumed. Codex session IDs are UUIDs under ~/.codex/sessions/; verify the id, omit sessionId, or set createNewSession:true.`
@@ -5351,6 +5626,11 @@ export function createGatewayServer(deps = {}) {
5351
5626
  .boolean()
5352
5627
  .optional()
5353
5628
  .describe("Antigravity 1.0.13 --new-project: create a new project for this session. Mutually exclusive with project."),
5629
+ printTimeout: z
5630
+ .string()
5631
+ .min(1)
5632
+ .optional()
5633
+ .describe("Antigravity --print-timeout <DURATION>: print-mode wait timeout as a Go duration string (e.g. '5m0s', '30s')."),
5354
5634
  workspace: providerWorkspaceAliasSchema(),
5355
5635
  worktree: WORKTREE_SCHEMA.optional(),
5356
5636
  }, {
@@ -5359,7 +5639,7 @@ export function createGatewayServer(deps = {}) {
5359
5639
  destructiveHint: true,
5360
5640
  idempotentHint: false,
5361
5641
  openWorldHint: true,
5362
- }, 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, }) => {
5642
+ }, 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, printTimeout, workspace, worktree, }) => {
5363
5643
  return handleGeminiRequest({ sessionManager, logger, runtime }, {
5364
5644
  prompt,
5365
5645
  promptParts,
@@ -5387,6 +5667,7 @@ export function createGatewayServer(deps = {}) {
5387
5667
  yolo,
5388
5668
  project,
5389
5669
  newProject,
5670
+ printTimeout,
5390
5671
  workspace,
5391
5672
  worktree,
5392
5673
  });
@@ -5560,6 +5841,27 @@ export function createGatewayServer(deps = {}) {
5560
5841
  .string()
5561
5842
  .optional()
5562
5843
  .describe("Load the initial prompt from a file (--prompt-file)"),
5844
+ config: z.string().optional().describe("Config file path (Devin --config <PATH>)"),
5845
+ sandbox: z
5846
+ .boolean()
5847
+ .optional()
5848
+ .describe("Run the Devin session in a sandbox (Devin --sandbox). Safety control: never defaulted on; pass true to opt in."),
5849
+ exportSession: z
5850
+ .union([z.boolean(), z.string()])
5851
+ .optional()
5852
+ .describe("Export the session (Devin --export [<PATH>]). true emits a bare --export; a string path emits --export <path>."),
5853
+ respectWorkspaceTrust: z
5854
+ .boolean()
5855
+ .optional()
5856
+ .describe("Respect workspace trust (Devin --respect-workspace-trust <bool>). Devin defaults true for interactive and false for print mode; set explicitly to override."),
5857
+ agentConfig: z
5858
+ .string()
5859
+ .optional()
5860
+ .describe("Agent config file path (Devin --agent-config <FILE>)"),
5861
+ agentType: z
5862
+ .enum(DEVIN_ACP_AGENT_TYPES)
5863
+ .optional()
5864
+ .describe("ACP agent variant for transport=acp (`devin acp --agent-type`): 'summarizer' (no tools, text summary) or 'review' (read-only + shell code-review). Ignored for the CLI transport."),
5563
5865
  sessionId: z
5564
5866
  .string()
5565
5867
  .optional()
@@ -5589,13 +5891,19 @@ export function createGatewayServer(deps = {}) {
5589
5891
  destructiveHint: true,
5590
5892
  idempotentHint: false,
5591
5893
  openWorldHint: true,
5592
- }, async ({ prompt, model, transport, permissionMode, promptFile, sessionId, resumeLatest, createNewSession, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, }) => {
5894
+ }, async ({ prompt, model, transport, permissionMode, promptFile, config, sandbox, exportSession, respectWorkspaceTrust, agentConfig, agentType, sessionId, resumeLatest, createNewSession, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, }) => {
5593
5895
  return handleDevinRequest({ sessionManager, logger, runtime }, {
5594
5896
  prompt,
5595
5897
  model,
5596
5898
  transport,
5597
5899
  permissionMode,
5598
5900
  promptFile,
5901
+ config,
5902
+ sandbox,
5903
+ exportSession,
5904
+ respectWorkspaceTrust,
5905
+ agentConfig,
5906
+ agentType,
5599
5907
  sessionId,
5600
5908
  resumeLatest,
5601
5909
  createNewSession,
@@ -5942,6 +6250,7 @@ export function createGatewayServer(deps = {}) {
5942
6250
  .array(z.string())
5943
6251
  .optional()
5944
6252
  .describe('Claude --tools: restrict the available built-in tool set (distinct from allowedTools permission gating). Pass [""] to disable all tools.'),
6253
+ ...CLAUDE_PART_A_FIELDS,
5945
6254
  workspace: providerWorkspaceAliasSchema(),
5946
6255
  worktree: WORKTREE_SCHEMA.optional(),
5947
6256
  approvalStrategy: z
@@ -5976,7 +6285,7 @@ export function createGatewayServer(deps = {}) {
5976
6285
  destructiveHint: true,
5977
6286
  idempotentHint: false,
5978
6287
  openWorldHint: true,
5979
- }, async ({ prompt, promptParts, model, outputFormat, sessionId, continueSession, createNewSession, allowedTools, disallowedTools, dangerouslySkipPermissions, permissionMode, agent, agents, forkSession, systemPrompt, appendSystemPrompt, maxBudgetUsd, maxTurns, effort, excludeDynamicSystemPromptSections, fallbackModel, jsonSchema, addDir, noSessionPersistence, settingSources, settings, tools, workspace, worktree, approvalStrategy, approvalPolicy, mcpServers, strictMcpConfig, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, }) => {
6288
+ }, async ({ prompt, promptParts, model, outputFormat, sessionId, continueSession, createNewSession, allowedTools, disallowedTools, dangerouslySkipPermissions, permissionMode, agent, agents, forkSession, systemPrompt, appendSystemPrompt, maxBudgetUsd, maxTurns, effort, excludeDynamicSystemPromptSections, fallbackModel, jsonSchema, addDir, noSessionPersistence, settingSources, settings, tools, includeHookEvents, replayUserMessages, systemPromptFile, appendSystemPromptFile, name, pluginDir, pluginUrl, safeMode, bare, debug, debugFile, workspace, worktree, approvalStrategy, approvalPolicy, mcpServers, strictMcpConfig, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, }) => {
5980
6289
  if (systemPrompt !== undefined && appendSystemPrompt !== undefined) {
5981
6290
  return createErrorResponse("claude", 1, "", correlationId, new Error("systemPrompt and appendSystemPrompt are mutually exclusive; use one or the other (not both)."));
5982
6291
  }
@@ -6012,6 +6321,17 @@ export function createGatewayServer(deps = {}) {
6012
6321
  settingSources,
6013
6322
  settings,
6014
6323
  tools,
6324
+ includeHookEvents,
6325
+ replayUserMessages,
6326
+ systemPromptFile,
6327
+ appendSystemPromptFile,
6328
+ name,
6329
+ pluginDir,
6330
+ pluginUrl,
6331
+ safeMode,
6332
+ bare,
6333
+ debug,
6334
+ debugFile,
6015
6335
  }, runtime);
6016
6336
  if (!("args" in prep))
6017
6337
  return prep;
@@ -6199,6 +6519,7 @@ export function createGatewayServer(deps = {}) {
6199
6519
  .optional()
6200
6520
  .describe("Codex --add-dir <DIR>: additional writable workspace directories (repeat per entry). New sessions only." +
6201
6521
  LOCAL_ADD_DIR_FIELD_SUFFIX),
6522
+ ...CODEX_PART_A_FIELDS,
6202
6523
  workspace: providerWorkspaceAliasSchema(),
6203
6524
  worktree: WORKTREE_SCHEMA.optional(),
6204
6525
  }, {
@@ -6207,7 +6528,7 @@ export function createGatewayServer(deps = {}) {
6207
6528
  destructiveHint: true,
6208
6529
  idempotentHint: false,
6209
6530
  openWorldHint: true,
6210
- }, async ({ prompt, promptParts, model, fullAuto, sandboxMode, askForApproval, useLegacyFullAutoFlag, dangerouslyBypassApprovalsAndSandbox, approvalStrategy, approvalPolicy, mcpServers, sessionId, resumeLatest, createNewSession, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, outputFormat, outputSchema, search, profile, configOverrides, ephemeral, images, ignoreUserConfig, ignoreRules, workingDir, addDir, workspace, worktree, }) => {
6531
+ }, async ({ prompt, promptParts, model, fullAuto, sandboxMode, askForApproval, useLegacyFullAutoFlag, dangerouslyBypassApprovalsAndSandbox, approvalStrategy, approvalPolicy, mcpServers, sessionId, resumeLatest, createNewSession, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, outputFormat, outputSchema, search, profile, configOverrides, ephemeral, images, ignoreUserConfig, ignoreRules, workingDir, addDir, enable, disable, strictConfig, oss, localProvider, color, outputLastMessage, dangerouslyBypassHookTrust, workspace, worktree, }) => {
6211
6532
  return handleCodexRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
6212
6533
  prompt,
6213
6534
  promptParts,
@@ -6238,6 +6559,14 @@ export function createGatewayServer(deps = {}) {
6238
6559
  ignoreRules,
6239
6560
  workingDir,
6240
6561
  addDir,
6562
+ enable,
6563
+ disable,
6564
+ strictConfig,
6565
+ oss,
6566
+ localProvider,
6567
+ color,
6568
+ outputLastMessage,
6569
+ dangerouslyBypassHookTrust,
6241
6570
  workspace,
6242
6571
  worktree,
6243
6572
  });
@@ -6326,6 +6655,11 @@ export function createGatewayServer(deps = {}) {
6326
6655
  .boolean()
6327
6656
  .optional()
6328
6657
  .describe("Antigravity 1.0.13 --new-project: create a new project for this session. Mutually exclusive with project."),
6658
+ printTimeout: z
6659
+ .string()
6660
+ .min(1)
6661
+ .optional()
6662
+ .describe("Antigravity --print-timeout <DURATION>: print-mode wait timeout as a Go duration string (e.g. '5m0s', '30s')."),
6329
6663
  workspace: providerWorkspaceAliasSchema(),
6330
6664
  worktree: WORKTREE_SCHEMA.optional(),
6331
6665
  }, {
@@ -6334,7 +6668,7 @@ export function createGatewayServer(deps = {}) {
6334
6668
  destructiveHint: true,
6335
6669
  idempotentHint: false,
6336
6670
  openWorldHint: true,
6337
- }, 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, }) => {
6671
+ }, 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, printTimeout, workspace, worktree, }) => {
6338
6672
  return handleGeminiRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
6339
6673
  prompt,
6340
6674
  promptParts,
@@ -6361,6 +6695,7 @@ export function createGatewayServer(deps = {}) {
6361
6695
  yolo,
6362
6696
  project,
6363
6697
  newProject,
6698
+ printTimeout,
6364
6699
  workspace,
6365
6700
  worktree,
6366
6701
  });
@@ -6636,6 +6971,23 @@ export function createGatewayServer(deps = {}) {
6636
6971
  .string()
6637
6972
  .optional()
6638
6973
  .describe("Load the initial prompt from a file (--prompt-file)"),
6974
+ config: z.string().optional().describe("Config file path (Devin --config <PATH>)"),
6975
+ sandbox: z
6976
+ .boolean()
6977
+ .optional()
6978
+ .describe("Run the Devin session in a sandbox (Devin --sandbox). Safety control: never defaulted on; pass true to opt in."),
6979
+ exportSession: z
6980
+ .union([z.boolean(), z.string()])
6981
+ .optional()
6982
+ .describe("Export the session (Devin --export [<PATH>]). true emits a bare --export; a string path emits --export <path>."),
6983
+ respectWorkspaceTrust: z
6984
+ .boolean()
6985
+ .optional()
6986
+ .describe("Respect workspace trust (Devin --respect-workspace-trust <bool>). Devin defaults true for interactive and false for print mode; set explicitly to override."),
6987
+ agentConfig: z
6988
+ .string()
6989
+ .optional()
6990
+ .describe("Agent config file path (Devin --agent-config <FILE>)"),
6639
6991
  sessionId: z
6640
6992
  .string()
6641
6993
  .optional()
@@ -6664,12 +7016,17 @@ export function createGatewayServer(deps = {}) {
6664
7016
  destructiveHint: true,
6665
7017
  idempotentHint: false,
6666
7018
  openWorldHint: true,
6667
- }, async ({ prompt, model, permissionMode, promptFile, sessionId, resumeLatest, createNewSession, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, }) => {
7019
+ }, async ({ prompt, model, permissionMode, promptFile, config, sandbox, exportSession, respectWorkspaceTrust, agentConfig, sessionId, resumeLatest, createNewSession, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, }) => {
6668
7020
  return handleDevinRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
6669
7021
  prompt,
6670
7022
  model,
6671
7023
  permissionMode,
6672
7024
  promptFile,
7025
+ config,
7026
+ sandbox,
7027
+ exportSession,
7028
+ respectWorkspaceTrust,
7029
+ agentConfig,
6673
7030
  sessionId,
6674
7031
  resumeLatest,
6675
7032
  createNewSession,
@@ -6972,6 +7329,18 @@ export function createGatewayServer(deps = {}) {
6972
7329
  result.stdout) {
6973
7330
  result.stdout = codexDisplayText(result.stdout);
6974
7331
  }
7332
+ if (callerIsRemote()) {
7333
+ const leakedId = result.providerSessionId;
7334
+ delete result.providerSessionId;
7335
+ if (leakedId) {
7336
+ if (result.stdout && result.stdout.includes(leakedId)) {
7337
+ result.stdout = result.stdout.split(leakedId).join("[redacted-session-id]");
7338
+ }
7339
+ if (result.stderr && result.stderr.includes(leakedId)) {
7340
+ result.stderr = result.stderr.split(leakedId).join("[redacted-session-id]");
7341
+ }
7342
+ }
7343
+ }
6975
7344
  return {
6976
7345
  content: [
6977
7346
  {
@@ -7215,10 +7584,7 @@ export function createGatewayServer(deps = {}) {
7215
7584
  .max(500)
7216
7585
  .default(50)
7217
7586
  .describe("Max number of approval records"),
7218
- cli: z
7219
- .enum(["claude", "codex", "gemini", "grok", "mistral"])
7220
- .optional()
7221
- .describe("Optional CLI filter"),
7587
+ cli: CLI_TYPE_ENUM.optional().describe("Optional CLI filter (any gateway CLI provider, derived from CLI_TYPES)"),
7222
7588
  }, {
7223
7589
  title: "Approval decisions",
7224
7590
  readOnlyHint: true,
@@ -7256,9 +7622,19 @@ export function createGatewayServer(deps = {}) {
7256
7622
  }, async ({ cli }) => {
7257
7623
  const cliInfo = getAvailableCliInfo();
7258
7624
  const apiRuntimes = enabledApiProviders(providers);
7625
+ const buildDiscoveredMap = (clis) => {
7626
+ const out = {};
7627
+ for (const c of clis) {
7628
+ out[c] = buildProviderDiscoveredView(getProviderDefinition(c), cliInfo[c], peekProviderCapabilitySet);
7629
+ }
7630
+ return out;
7631
+ };
7259
7632
  let result;
7260
7633
  if (cli && CLI_TYPES.includes(cli)) {
7261
- result = { [cli]: cliInfo[cli] };
7634
+ result = {
7635
+ [cli]: cliInfo[cli],
7636
+ discovered: buildDiscoveredMap([cli]),
7637
+ };
7262
7638
  }
7263
7639
  else if (cli) {
7264
7640
  const runtime = apiRuntimes.find(p => p.name === cli);
@@ -7266,7 +7642,11 @@ export function createGatewayServer(deps = {}) {
7266
7642
  }
7267
7643
  else {
7268
7644
  const apiProviders = apiRuntimes.map(apiProviderCatalogEntry);
7269
- result = { ...cliInfo, ...(apiProviders.length > 0 ? { apiProviders } : {}) };
7645
+ result = {
7646
+ ...cliInfo,
7647
+ discovered: buildDiscoveredMap(CLI_TYPES),
7648
+ ...(apiProviders.length > 0 ? { apiProviders } : {}),
7649
+ };
7270
7650
  }
7271
7651
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
7272
7652
  });
@@ -7313,6 +7693,7 @@ export function createGatewayServer(deps = {}) {
7313
7693
  includePaths,
7314
7694
  refresh,
7315
7695
  providersConfig: providers,
7696
+ acpConfig: runtime.acpConfig,
7316
7697
  });
7317
7698
  return { content: [{ type: "text", text: JSON.stringify(capabilities, null, 2) }] };
7318
7699
  });
@@ -7789,7 +8170,7 @@ export function createGatewayServer(deps = {}) {
7789
8170
  text: JSON.stringify({
7790
8171
  success: true,
7791
8172
  session: {
7792
- ...session,
8173
+ ...(callerIsRemote() ? remoteSafeSession(session) : session),
7793
8174
  isActive: activeSession?.id === session.id,
7794
8175
  ...(cacheState ? { cacheState } : {}),
7795
8176
  },
@@ -7858,7 +8239,7 @@ async function initializeSessionManager() {
7858
8239
  });
7859
8240
  logger.info("File-based session manager initialized");
7860
8241
  }
7861
- resourceProvider = new ResourceProvider(sessionManager, performanceMetrics, getFlightRecorder(logger), getCacheAwarenessConfig(logger), getProvidersConfig(logger));
8242
+ resourceProvider = new ResourceProvider(sessionManager, performanceMetrics, getFlightRecorder(logger), getCacheAwarenessConfig(logger), getProvidersConfig(logger), undefined, getAcpConfig(logger));
7862
8243
  }
7863
8244
  function registerHealthResource(server) {
7864
8245
  if (db) {
@@ -7981,6 +8362,56 @@ function localBaseUrlForPrint() {
7981
8362
  function printJsonLine(value) {
7982
8363
  process.stdout.write(JSON.stringify(value, null, 2) + "\n");
7983
8364
  }
8365
+ const OAUTH_RESTART_NOTE = "Restart the gateway HTTP transport for this change to take effect (OAuth config is loaded at startup).";
8366
+ function validateCliRedirectUri(uri) {
8367
+ let parsed;
8368
+ try {
8369
+ parsed = new URL(uri);
8370
+ }
8371
+ catch {
8372
+ throw new Error(`Invalid --redirect-uri "${uri}": it must be an absolute URL with a scheme (e.g. https://chatgpt.com/connector/callback).`);
8373
+ }
8374
+ if (parsed.protocol === "https:")
8375
+ return;
8376
+ if (parsed.protocol === "http:" && hostIsLoopback(parsed.hostname))
8377
+ return;
8378
+ throw new Error(`Invalid --redirect-uri "${uri}": must be https:// (or http:// only for localhost/loopback). The gateway rejects http non-loopback redirect URIs at runtime.`);
8379
+ }
8380
+ function validateCliClientId(clientId) {
8381
+ if (!/^[A-Za-z0-9._-]{1,128}$/.test(clientId)) {
8382
+ throw new Error(`Invalid client-id "${clientId}": use 1-128 chars of letters, digits, dot, underscore, or hyphen.`);
8383
+ }
8384
+ }
8385
+ function hostIsLoopback(hostname) {
8386
+ const h = hostname.toLowerCase();
8387
+ return h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "[::1]";
8388
+ }
8389
+ function redirectLooksLocalhost(uri) {
8390
+ try {
8391
+ return hostIsLoopback(new URL(uri).hostname);
8392
+ }
8393
+ catch {
8394
+ return false;
8395
+ }
8396
+ }
8397
+ function publicUrlIsRemote(env = process.env) {
8398
+ const publicUrl = env.LLM_GATEWAY_PUBLIC_URL;
8399
+ if (!publicUrl)
8400
+ return false;
8401
+ try {
8402
+ return !hostIsLoopback(new URL(publicUrl).hostname);
8403
+ }
8404
+ catch {
8405
+ return false;
8406
+ }
8407
+ }
8408
+ function cliConnectorUrls() {
8409
+ return buildRemoteConnectorUrls({
8410
+ baseOrigin: localBaseUrlForPrint(),
8411
+ mcpPath: process.env.LLM_GATEWAY_HTTP_PATH || "/mcp",
8412
+ oauthEnabled: true,
8413
+ });
8414
+ }
7984
8415
  function runOAuthCommand(args) {
7985
8416
  const [scope, action] = args;
7986
8417
  const config = readMutableGatewayConfig();
@@ -7992,7 +8423,16 @@ function runOAuthCommand(args) {
7992
8423
  const clientId = args[2];
7993
8424
  if (!clientId)
7994
8425
  throw new Error("Usage: llm-cli-gateway oauth client add <client-id> --redirect-uri <uri> [--print-once]");
8426
+ validateCliClientId(clientId);
7995
8427
  const redirectUri = requireArg(args, "--redirect-uri");
8428
+ validateCliRedirectUri(redirectUri);
8429
+ if (clients.some(candidate => candidate.client_id === clientId)) {
8430
+ throw new Error(`OAuth client "${clientId}" already exists. Use \`llm-cli-gateway oauth client rotate ${clientId} --print-once\` to issue a new secret, or revoke it first.`);
8431
+ }
8432
+ const warnings = [];
8433
+ if (redirectLooksLocalhost(redirectUri) && publicUrlIsRemote()) {
8434
+ warnings.push("The redirect URI is localhost but a public URL is configured; remote connectors usually need the provider's public callback URL.");
8435
+ }
7996
8436
  const secret = generateSecret();
7997
8437
  clients.push({
7998
8438
  client_id: clientId,
@@ -8001,18 +8441,26 @@ function runOAuthCommand(args) {
8001
8441
  scopes: ["mcp"],
8002
8442
  });
8003
8443
  writeMutableGatewayConfig(config);
8444
+ const printOnce = args.includes("--print-once");
8445
+ const urls = cliConnectorUrls();
8004
8446
  printJsonLine({
8005
8447
  ok: true,
8006
8448
  client_id: clientId,
8007
- ...(args.includes("--print-once") ? { client_secret: secret } : {}),
8008
- oauth: {
8009
- issuer: localBaseUrlForPrint(),
8010
- authorization_url: `${localBaseUrlForPrint()}/oauth/authorize`,
8011
- token_url: `${localBaseUrlForPrint()}/oauth/token`,
8449
+ redirect_uri: redirectUri,
8450
+ ...(printOnce ? { client_secret: secret, client_secret_copy_once: true } : {}),
8451
+ connector: {
8452
+ mcp_url: urls.mcpUrl,
8453
+ auth_mode: "oauth",
8454
+ authorization_url: urls.authorizationUrl,
8455
+ token_url: urls.tokenUrl,
8456
+ client_id: clientId,
8012
8457
  },
8013
- note: args.includes("--print-once")
8014
- ? "client_secret is shown once; it is stored only as a hash."
8015
- : "client secret generated and stored only as a hash; rerun rotate --print-once if needed.",
8458
+ ...(warnings.length ? { warnings } : {}),
8459
+ note: printOnce
8460
+ ? "client_secret is shown once and stored only as a hash. Copy it into the connector UI now; it cannot be recovered later. " +
8461
+ OAUTH_RESTART_NOTE
8462
+ : "client secret generated and stored only as a hash. Re-run with --print-once (or `oauth client rotate --print-once`) to reveal a secret. " +
8463
+ OAUTH_RESTART_NOTE,
8016
8464
  });
8017
8465
  return;
8018
8466
  }
@@ -8022,6 +8470,7 @@ function runOAuthCommand(args) {
8022
8470
  clients: clients.map(client => ({
8023
8471
  client_id: client.client_id,
8024
8472
  redirect_uris: client.allowed_redirect_uris ?? [],
8473
+ scopes: client.scopes ?? ["mcp"],
8025
8474
  secret_configured: Boolean(client.client_secret_hash),
8026
8475
  })),
8027
8476
  });
@@ -8035,11 +8484,21 @@ function runOAuthCommand(args) {
8035
8484
  const secret = generateSecret();
8036
8485
  client.client_secret_hash = hashSecret(secret);
8037
8486
  writeMutableGatewayConfig(config);
8487
+ const printOnce = args.includes("--print-once");
8038
8488
  printJsonLine({
8039
8489
  ok: true,
8040
8490
  client_id: clientId,
8041
- ...(args.includes("--print-once") ? { client_secret: secret } : {}),
8042
- note: "Future OAuth exchanges use the rotated secret; already-issued opaque access tokens expire by token TTL or server restart.",
8491
+ ...(printOnce ? { client_secret: secret, client_secret_copy_once: true } : {}),
8492
+ client: {
8493
+ client_id: client.client_id,
8494
+ redirect_uris: client.allowed_redirect_uris ?? [],
8495
+ secret_configured: true,
8496
+ },
8497
+ note: (printOnce
8498
+ ? "New client_secret is shown once; copy it into the connector UI now. "
8499
+ : "New secret generated and stored only as a hash; re-run with --print-once to reveal it. ") +
8500
+ "Future OAuth exchanges use the rotated secret; already-issued opaque access tokens expire by token TTL or server restart. " +
8501
+ OAUTH_RESTART_NOTE,
8043
8502
  });
8044
8503
  return;
8045
8504
  }
@@ -8050,7 +8509,8 @@ function runOAuthCommand(args) {
8050
8509
  printJsonLine({
8051
8510
  ok: true,
8052
8511
  client_id: clientId,
8053
- note: "Future OAuth exchanges are revoked; already-issued opaque access tokens expire by token TTL or server restart.",
8512
+ note: "Future OAuth exchanges are revoked; already-issued opaque access tokens expire by token TTL or server restart. " +
8513
+ OAUTH_RESTART_NOTE,
8054
8514
  });
8055
8515
  return;
8056
8516
  }
@@ -8068,10 +8528,13 @@ function runOAuthCommand(args) {
8068
8528
  printJsonLine({
8069
8529
  ok: true,
8070
8530
  shared_secret_enabled: true,
8071
- ...(args.includes("--print-once") ? { shared_secret: secret } : {}),
8072
- note: args.includes("--print-once")
8073
- ? "shared_secret is shown once; it is stored only as a hash."
8074
- : "shared secret generated and stored only as a hash.",
8531
+ ...(args.includes("--print-once")
8532
+ ? { shared_secret: secret, shared_secret_copy_once: true }
8533
+ : {}),
8534
+ note: (args.includes("--print-once")
8535
+ ? "shared_secret is shown once; it is stored only as a hash. "
8536
+ : "shared secret generated and stored only as a hash; re-run with --print-once to reveal it. ") +
8537
+ OAUTH_RESTART_NOTE,
8075
8538
  });
8076
8539
  return;
8077
8540
  }
@@ -8086,6 +8549,19 @@ function runOAuthCommand(args) {
8086
8549
  }
8087
8550
  throw new Error("Usage: llm-cli-gateway oauth client|shared-secret ...");
8088
8551
  }
8552
+ function runConnectorCommand(args) {
8553
+ const [action] = args;
8554
+ if (action !== "setup") {
8555
+ throw new Error("Usage: llm-cli-gateway connector setup [--client-id <id>] [--include-legacy-no-auth]");
8556
+ }
8557
+ const options = {
8558
+ clientId: argValue(args, "--client-id"),
8559
+ includeLegacyNoAuth: args.includes("--include-legacy-no-auth"),
8560
+ };
8561
+ const packet = gatherConnectorSetupPacket(options);
8562
+ process.stderr.write(renderConnectorSetupSummary(packet) + "\n");
8563
+ printJsonLine(packet);
8564
+ }
8089
8565
  function runWorkspaceCommand(args) {
8090
8566
  const [action] = args;
8091
8567
  if (action === "list") {
@@ -8148,8 +8624,16 @@ async function main() {
8148
8624
  "Usage:",
8149
8625
  " llm-cli-gateway [doctor --json|contracts --json|--transport=http|--version]",
8150
8626
  " llm-cli-gateway oauth client add <id> --redirect-uri <uri> [--print-once]",
8627
+ " llm-cli-gateway connector setup [--client-id <id>] [--include-legacy-no-auth]",
8151
8628
  " llm-cli-gateway workspace list|add|create",
8152
8629
  "",
8630
+ "Remote connector (recommended OAuth path):",
8631
+ " 1. Set LLM_GATEWAY_PUBLIC_URL to a public https URL (tunnel/reverse proxy).",
8632
+ " 2. oauth client add <id> --redirect-uri <connector-callback> --print-once",
8633
+ " 3. workspace add <alias> <absolute-repo-path> --default",
8634
+ " 4. connector setup # copy-safe fields to paste into the connector UI",
8635
+ " Inspect readiness first: doctor --json -> remote_http_oauth.stage",
8636
+ "",
8153
8637
  "Doctor:",
8154
8638
  " doctor --json # environment, providers, declared contracts",
8155
8639
  " doctor --json --probe-upstream # + expensive installed --help probe for drift",
@@ -8175,6 +8659,10 @@ async function main() {
8175
8659
  runOAuthCommand(args.slice(1));
8176
8660
  return;
8177
8661
  }
8662
+ if (args[0] === "connector") {
8663
+ runConnectorCommand(args.slice(1));
8664
+ return;
8665
+ }
8178
8666
  if (args[0] === "workspace") {
8179
8667
  runWorkspaceCommand(args.slice(1));
8180
8668
  return;
@@ -8207,6 +8695,7 @@ async function main() {
8207
8695
  "stdio";
8208
8696
  logger.info(`Starting llm-cli-gateway MCP server with ${transportMode} transport`);
8209
8697
  await initializeSessionManager();
8698
+ void warmProviderCapabilities({ logger }).catch(() => undefined);
8210
8699
  const serverDeps = {
8211
8700
  sessionManager,
8212
8701
  resourceProvider,