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.
- package/CHANGELOG.md +140 -0
- package/README.md +53 -26
- package/dist/acp/client.d.ts +29 -1
- package/dist/acp/client.js +78 -4
- package/dist/acp/errors.d.ts +9 -1
- package/dist/acp/errors.js +19 -0
- package/dist/acp/event-normalizer.d.ts +12 -0
- package/dist/acp/event-normalizer.js +16 -0
- package/dist/acp/flight-redaction.d.ts +3 -0
- package/dist/acp/flight-redaction.js +3 -0
- package/dist/acp/permission-bridge.js +11 -5
- package/dist/acp/process-manager.d.ts +2 -1
- package/dist/acp/process-manager.js +43 -4
- package/dist/acp/provider-registry.js +19 -11
- package/dist/acp/runtime.d.ts +8 -0
- package/dist/acp/runtime.js +47 -4
- package/dist/acp/types.d.ts +3083 -55
- package/dist/acp/types.js +242 -5
- package/dist/async-job-manager.d.ts +2 -0
- package/dist/async-job-manager.js +11 -0
- package/dist/codex-json-parser.d.ts +1 -0
- package/dist/codex-json-parser.js +6 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.js +105 -19
- package/dist/connector-setup.d.ts +39 -0
- package/dist/connector-setup.js +86 -0
- package/dist/doctor.d.ts +39 -1
- package/dist/doctor.js +182 -3
- package/dist/flight-recorder.d.ts +2 -0
- package/dist/flight-recorder.js +20 -0
- package/dist/gemini-json-parser.d.ts +2 -0
- package/dist/gemini-json-parser.js +45 -8
- package/dist/grok-json-parser.d.ts +14 -0
- package/dist/grok-json-parser.js +156 -0
- package/dist/http-transport.js +27 -3
- package/dist/index.d.ts +48 -2
- package/dist/index.js +633 -144
- package/dist/job-store.d.ts +45 -7
- package/dist/job-store.js +138 -17
- package/dist/model-registry.d.ts +1 -0
- package/dist/model-registry.js +46 -0
- package/dist/oauth.js +17 -16
- package/dist/postgres-job-store-worker.d.ts +1 -0
- package/dist/postgres-job-store-worker.js +327 -0
- package/dist/pricing.d.ts +3 -2
- package/dist/provider-acp-capabilities.d.ts +52 -0
- package/dist/provider-acp-capabilities.js +101 -0
- package/dist/provider-admin-tools.d.ts +100 -0
- package/dist/provider-admin-tools.js +572 -0
- package/dist/provider-capability-cache.d.ts +46 -0
- package/dist/provider-capability-cache.js +248 -0
- package/dist/provider-capability-discovery.d.ts +85 -0
- package/dist/provider-capability-discovery.js +461 -0
- package/dist/provider-capability-resolver.d.ts +29 -0
- package/dist/provider-capability-resolver.js +92 -0
- package/dist/provider-definition-assertions.d.ts +9 -0
- package/dist/provider-definition-assertions.js +147 -0
- package/dist/provider-definitions.d.ts +127 -0
- package/dist/provider-definitions.js +758 -0
- package/dist/provider-help-parser.d.ts +34 -0
- package/dist/provider-help-parser.js +203 -0
- package/dist/provider-model-discovery.d.ts +30 -0
- package/dist/provider-model-discovery.js +229 -0
- package/dist/provider-output-metadata.d.ts +7 -0
- package/dist/provider-output-metadata.js +68 -0
- package/dist/provider-schema-builder.d.ts +22 -0
- package/dist/provider-schema-builder.js +55 -0
- package/dist/provider-surface-generator.d.ts +98 -0
- package/dist/provider-surface-generator.js +140 -0
- package/dist/provider-tool-capabilities.d.ts +3 -2
- package/dist/provider-tool-capabilities.js +77 -62
- package/dist/remote-url.d.ts +28 -0
- package/dist/remote-url.js +61 -0
- package/dist/request-helpers.d.ts +37 -4
- package/dist/request-helpers.js +134 -0
- package/dist/resources.d.ts +6 -1
- package/dist/resources.js +67 -193
- package/dist/session-manager.d.ts +2 -0
- package/dist/session-manager.js +34 -8
- package/dist/stream-json-parser.d.ts +1 -0
- package/dist/stream-json-parser.js +3 -0
- package/dist/upstream-contracts.d.ts +17 -1
- package/dist/upstream-contracts.js +209 -36
- package/dist/workspace-registry.d.ts +9 -0
- package/dist/workspace-registry.js +24 -4
- package/npm-shrinkwrap.json +2 -2
- package/package.json +8 -3
- 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 {
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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({
|
|
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:
|
|
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:
|
|
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
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
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
|
|
1367
|
-
|
|
1368
|
-
|
|
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
|
|
2365
|
+
let resultError = false;
|
|
2204
2366
|
if (cli === "claude") {
|
|
2205
2367
|
const parsedResult = parseStreamJson(stdout);
|
|
2206
2368
|
if (parsedResult.isError) {
|
|
2207
|
-
|
|
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 (
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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 = {
|
|
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 = {
|
|
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
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
|
|
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
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
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
|
-
...(
|
|
8042
|
-
|
|
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")
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
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,
|