llm-cli-gateway 2.4.0 → 2.6.0
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 +45 -0
- package/README.md +18 -18
- package/dist/async-job-manager.d.ts +2 -0
- package/dist/async-job-manager.js +43 -3
- package/dist/auth.d.ts +44 -1
- package/dist/auth.js +60 -13
- package/dist/cli-updater.js +22 -13
- package/dist/config.d.ts +2 -0
- package/dist/config.js +151 -0
- package/dist/doctor.d.ts +15 -0
- package/dist/doctor.js +22 -11
- package/dist/executor.d.ts +1 -0
- package/dist/executor.js +7 -0
- package/dist/http-transport.js +74 -12
- package/dist/index.d.ts +16 -1
- package/dist/index.js +643 -306
- package/dist/oauth.d.ts +38 -0
- package/dist/oauth.js +441 -0
- package/dist/provider-codegen.d.ts +27 -0
- package/dist/provider-codegen.js +335 -0
- package/dist/provider-login-guidance.js +9 -9
- package/dist/provider-status.js +5 -5
- package/dist/request-context.d.ts +7 -0
- package/dist/request-context.js +8 -0
- package/dist/request-helpers.js +2 -2
- package/dist/upstream-contracts.js +95 -116
- package/dist/workspace-registry.d.ts +63 -0
- package/dist/workspace-registry.js +417 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/setup/status.schema.json +42 -1
package/dist/index.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { randomUUID } from "crypto";
|
|
5
|
-
import {
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync, chmodSync, } from "fs";
|
|
6
7
|
import { dirname, join } from "path";
|
|
7
8
|
import { fileURLToPath } from "url";
|
|
8
9
|
import { z } from "zod/v3";
|
|
9
|
-
import { executeCli, killAllProcessGroups } from "./executor.js";
|
|
10
|
+
import { executeCli, killAllProcessGroups, providerCommandName } from "./executor.js";
|
|
10
11
|
import { parseStreamJson } from "./stream-json-parser.js";
|
|
11
12
|
import { parseCodexJsonStream } from "./codex-json-parser.js";
|
|
12
13
|
import { parseGeminiJson, parseGeminiStreamJson } from "./gemini-json-parser.js";
|
|
@@ -17,7 +18,7 @@ import { createWorktree, createWorktreeSessionCleanupHook, } from "./worktree-ma
|
|
|
17
18
|
import { ResourceProvider } from "./resources.js";
|
|
18
19
|
import { PerformanceMetrics } from "./metrics.js";
|
|
19
20
|
import { estimateTokens, optimizePrompt as optimizePromptText, optimizeResponse as optimizeResponseText, } from "./optimizer.js";
|
|
20
|
-
import { loadConfig, loadPersistenceConfig, loadCacheAwarenessConfig, loadProvidersConfig, isXaiProviderEnabled, minStableTokensForModel, } from "./config.js";
|
|
21
|
+
import { loadConfig, loadPersistenceConfig, loadCacheAwarenessConfig, loadProvidersConfig, defaultGatewayConfigPath, isXaiProviderEnabled, minStableTokensForModel, } from "./config.js";
|
|
21
22
|
import { createXaiResponse, XaiApiError, } from "./xai-api-provider.js";
|
|
22
23
|
import { checkHealth } from "./health.js";
|
|
23
24
|
import { clearModelRegistryCache, getAvailableCliInfo, getCliInfo, resolveModelAlias, } from "./model-registry.js";
|
|
@@ -26,15 +27,19 @@ import { createJobStore } from "./job-store.js";
|
|
|
26
27
|
import { ApprovalManager } from "./approval-manager.js";
|
|
27
28
|
import { checkReviewIntegrity } from "./review-integrity.js";
|
|
28
29
|
import { buildClaudeMcpConfig, CLAUDE_MCP_SERVER_NAMES, } from "./claude-mcp-config.js";
|
|
29
|
-
import { resolveGrokSessionArgs, resolveMistralSessionArgs, resolveCodexSessionArgs, sanitizeCliArgValues, prepareMistralRequest as buildMistralCliInvocation, MISTRAL_AGENT_MODES, 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,
|
|
30
|
+
import { resolveGrokSessionArgs, resolveMistralSessionArgs, resolveCodexSessionArgs, sanitizeCliArgValues, prepareMistralRequest as buildMistralCliInvocation, MISTRAL_AGENT_MODES, 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";
|
|
30
31
|
import { createFlightRecorder } from "./flight-recorder.js";
|
|
31
32
|
import { resolvePromptInput, PromptPartsSchema, assembleClaudeCacheBlocks, } from "./prompt-parts.js";
|
|
32
33
|
import { computeSessionCacheStats, computeTtlRemaining, readPersistedRequest, PERSISTED_REQUEST_DEFAULT_MAX_CHARS, } from "./cache-stats.js";
|
|
33
34
|
import { getCliVersions, runCliUpgrade } from "./cli-updater.js";
|
|
34
35
|
import { startHttpGateway } from "./http-transport.js";
|
|
36
|
+
import { getRequestContext } from "./request-context.js";
|
|
35
37
|
import { printDoctorJson } from "./doctor.js";
|
|
38
|
+
import { createWorkspace, describeWorkspace, getWorkspace, loadWorkspaceRegistry, registerExistingWorkspace, resolveWorkspaceForProvider, validatePathInsideWorkspace, } from "./workspace-registry.js";
|
|
39
|
+
import { generateSecret, hashSecret } from "./oauth.js";
|
|
36
40
|
import { registerValidationTools } from "./validation-tools.js";
|
|
37
|
-
import { assertUpstreamCliArgs, assertUpstreamCliEnv, buildProviderSubcommandsCompactCatalog, buildUpstreamContractReport, getCliSubcommandContract, probeInstalledCliContract, serializeCliSubcommandContract, } from "./upstream-contracts.js";
|
|
41
|
+
import { assertUpstreamCliArgs, assertUpstreamCliEnv, buildProviderSubcommandsCompactCatalog, buildUpstreamContractReport, getCliSubcommandContract, probeInstalledCliContract, serializeCliSubcommandContract, UPSTREAM_CLI_CONTRACTS, } from "./upstream-contracts.js";
|
|
42
|
+
import { buildArgvFromGeneration, deriveZodShapeFromGeneration, GROK_FLAG_GENERATION, GROK_GEN_OUTPUT_FORMAT, GROK_GEN_MAIN, GROK_GEN_PROMPT_FILE, GROK_GEN_SINGLE, GROK_GEN_TAIL, } from "./provider-codegen.js";
|
|
38
43
|
import { entrypointFileURL } from "./entrypoint-url.js";
|
|
39
44
|
const logger = {
|
|
40
45
|
info: (message, ...args) => {
|
|
@@ -158,7 +163,7 @@ Other: list_models, cli_versions, upstream_contracts, provider_subcommands_* (re
|
|
|
158
163
|
|
|
159
164
|
Key behaviors:
|
|
160
165
|
${deferralLine}
|
|
161
|
-
- Sessions: Claude --continue, Gemini --
|
|
166
|
+
- Sessions: Claude --continue, Gemini (Antigravity) --conversation <id>/--continue, Grok --resume/--continue, Mistral --resume/--continue (current Vibe defaults session logging on; doctor flags explicit session_logging.enabled=false), Codex \`exec resume <ID>\` / \`exec resume --last\` (all real CLI continuity). For Codex, sessionId must be a real Codex UUID (from ~/.codex/sessions/); gateway-generated gw-* IDs are rejected.
|
|
162
167
|
- Approval gates: opt-in via approvalStrategy:"mcp_managed".
|
|
163
168
|
- Upstream drift detection: After upgrading any provider CLI (especially grok), use upstream_contracts with probeInstalled:true and provider_subcommand_drift for declared subcommand help surfaces. Probes are safe, read-only --help checks.
|
|
164
169
|
- Idle timeout kills stuck processes (default 10min, configurable via idleTimeoutMs).
|
|
@@ -226,6 +231,7 @@ function getApprovalManager(runtimeLogger = logger) {
|
|
|
226
231
|
const MCP_SERVER_ENUM = z.enum(CLAUDE_MCP_SERVER_NAMES);
|
|
227
232
|
const CLI_TYPE_ENUM = z.enum(CLI_TYPES);
|
|
228
233
|
export const MAX_TURNS_SCHEMA = z.number().int().positive().safe().max(10_000);
|
|
234
|
+
const GROK_GENERATED_SHAPE = deriveZodShapeFromGeneration(UPSTREAM_CLI_CONTRACTS.grok, GROK_FLAG_GENERATION);
|
|
229
235
|
export const MAX_TOKENS_SCHEMA = z.number().int().positive().safe().max(100_000_000);
|
|
230
236
|
export const MAX_PRICE_SCHEMA = z.number().positive().finite().min(1e-6).max(10_000);
|
|
231
237
|
export const WORKTREE_SCHEMA = z
|
|
@@ -252,6 +258,12 @@ export const WORKTREE_SCHEMA = z
|
|
|
252
258
|
"path. NOTE: callers should `.gitignore` the `.worktrees/` " +
|
|
253
259
|
"directory in their repo (the gateway does NOT auto-gitignore — " +
|
|
254
260
|
"see slice λ spec Q4).");
|
|
261
|
+
export const WORKSPACE_ALIAS_SCHEMA = z
|
|
262
|
+
.string()
|
|
263
|
+
.min(1)
|
|
264
|
+
.max(64)
|
|
265
|
+
.regex(/^[A-Za-z][A-Za-z0-9._-]{0,63}$/)
|
|
266
|
+
.describe("Registered workspace alias. Remote clients use aliases, not absolute paths.");
|
|
255
267
|
export const SESSION_PROVIDER_VALUES = PROVIDER_TYPES;
|
|
256
268
|
export const SESSION_PROVIDER_ENUM = z.enum(SESSION_PROVIDER_VALUES);
|
|
257
269
|
let activeServer = null;
|
|
@@ -286,6 +298,7 @@ export function resolveGatewayServerRuntime(deps = {}, options = {}) {
|
|
|
286
298
|
persistence: deps.persistence ?? getPersistenceConfig(runtimeLogger),
|
|
287
299
|
cacheAwareness: deps.cacheAwareness ?? getCacheAwarenessConfig(runtimeLogger),
|
|
288
300
|
providers: deps.providers ?? getProvidersConfig(runtimeLogger),
|
|
301
|
+
workspaces: deps.workspaces ?? loadWorkspaceRegistry(runtimeLogger),
|
|
289
302
|
};
|
|
290
303
|
}
|
|
291
304
|
export function shouldRegisterGrokApiTools(providers) {
|
|
@@ -329,7 +342,7 @@ async function awaitJobOrDefer(cli, args, corrId, idleTimeoutMs, outputFormat, f
|
|
|
329
342
|
runtime.persistence.asyncJobsEnabled &&
|
|
330
343
|
runtime.asyncJobManager.hasStore();
|
|
331
344
|
if (SYNC_DEADLINE_MS === 0 || !deferralAvailable) {
|
|
332
|
-
const command = cli
|
|
345
|
+
const command = providerCommandName(cli);
|
|
333
346
|
try {
|
|
334
347
|
return await executeCli(command, args, {
|
|
335
348
|
idleTimeout: idleTimeoutMs,
|
|
@@ -415,7 +428,7 @@ function buildDeferredToolResponse(deferred, sessionId) {
|
|
|
415
428
|
],
|
|
416
429
|
};
|
|
417
430
|
}
|
|
418
|
-
export async function resolveWorktreeForRequest(worktreeOpt, sessionId, runtime) {
|
|
431
|
+
export async function resolveWorktreeForRequest(worktreeOpt, sessionId, runtime, options = {}) {
|
|
419
432
|
if (!worktreeOpt)
|
|
420
433
|
return {};
|
|
421
434
|
const sessionManager = runtime.sessionManager;
|
|
@@ -423,12 +436,21 @@ export async function resolveWorktreeForRequest(worktreeOpt, sessionId, runtime)
|
|
|
423
436
|
const session = await Promise.resolve(sessionManager.getSession(sessionId));
|
|
424
437
|
const existingPath = session?.metadata?.worktreePath;
|
|
425
438
|
if (typeof existingPath === "string" && existingPath.length > 0) {
|
|
426
|
-
return {
|
|
439
|
+
return {
|
|
440
|
+
cwd: existingPath,
|
|
441
|
+
worktreePath: existingPath,
|
|
442
|
+
workspaceAlias: typeof session?.metadata?.workspaceAlias === "string"
|
|
443
|
+
? session.metadata.workspaceAlias
|
|
444
|
+
: options.workspaceAlias,
|
|
445
|
+
workspaceRoot: typeof session?.metadata?.workspaceRoot === "string"
|
|
446
|
+
? session.metadata.workspaceRoot
|
|
447
|
+
: options.workspaceRoot,
|
|
448
|
+
};
|
|
427
449
|
}
|
|
428
450
|
}
|
|
429
451
|
const name = worktreeOpt === true ? undefined : worktreeOpt.name;
|
|
430
452
|
const ref = worktreeOpt === true ? undefined : worktreeOpt.ref;
|
|
431
|
-
const repoRoot = process.cwd();
|
|
453
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
432
454
|
const handle = await createWorktree({
|
|
433
455
|
repoRoot,
|
|
434
456
|
name,
|
|
@@ -439,13 +461,204 @@ export async function resolveWorktreeForRequest(worktreeOpt, sessionId, runtime)
|
|
|
439
461
|
await Promise.resolve(sessionManager.updateSessionMetadata(sessionId, {
|
|
440
462
|
worktreePath: handle.path,
|
|
441
463
|
worktreeName: handle.name,
|
|
464
|
+
...(options.workspaceAlias ? { workspaceAlias: options.workspaceAlias } : {}),
|
|
465
|
+
...(options.workspaceRoot ? { workspaceRoot: options.workspaceRoot } : {}),
|
|
466
|
+
}));
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
cwd: handle.path,
|
|
470
|
+
worktreePath: handle.path,
|
|
471
|
+
workspaceAlias: options.workspaceAlias,
|
|
472
|
+
workspaceRoot: options.workspaceRoot,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function isGatewayAppDirCwd() {
|
|
476
|
+
return process.cwd() === join(homedir(), ".llm-cli-gateway");
|
|
477
|
+
}
|
|
478
|
+
async function resolveWorkspaceAndWorktreeForRequest(args) {
|
|
479
|
+
const session = args.sessionId
|
|
480
|
+
? await Promise.resolve(args.runtime.sessionManager.getSession(args.sessionId))
|
|
481
|
+
: null;
|
|
482
|
+
let workspace;
|
|
483
|
+
if (args.workspace ||
|
|
484
|
+
args.runtime.workspaces.defaultAlias ||
|
|
485
|
+
typeof session?.metadata?.workspaceAlias === "string") {
|
|
486
|
+
workspace = resolveWorkspaceForProvider(args.runtime.workspaces, args.provider, args.workspace, session?.metadata);
|
|
487
|
+
}
|
|
488
|
+
else if (isGatewayAppDirCwd()) {
|
|
489
|
+
throw new Error("No workspace selected. Configure [workspaces].default or pass a registered workspace alias.");
|
|
490
|
+
}
|
|
491
|
+
if (!workspace && getRequestContext()?.authKind === "oauth") {
|
|
492
|
+
throw new Error("Remote OAuth provider requests require a registered workspace alias or [workspaces].default.");
|
|
493
|
+
}
|
|
494
|
+
if (!workspace &&
|
|
495
|
+
(args.workingDir || (args.addDir?.length ?? 0) > 0) &&
|
|
496
|
+
!args.runtime.workspaces.allowUnregisteredWorkingDir) {
|
|
497
|
+
throw new Error("workingDir/addDir require a registered workspace alias unless [workspaces].allow_unregistered_working_dir is explicitly enabled.");
|
|
498
|
+
}
|
|
499
|
+
if (workspace) {
|
|
500
|
+
if (args.workingDir) {
|
|
501
|
+
validatePathInsideWorkspace(workspace, args.workingDir, "workingDir");
|
|
502
|
+
}
|
|
503
|
+
for (const dir of args.addDir ?? []) {
|
|
504
|
+
validatePathInsideWorkspace(workspace, dir, "addDir");
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (args.worktree) {
|
|
508
|
+
if (workspace && !workspace.repo.allowWorktree) {
|
|
509
|
+
throw new Error(`Workspace "${workspace.alias}" does not allow worktree requests`);
|
|
510
|
+
}
|
|
511
|
+
const resolved = await resolveWorktreeForRequest(args.worktree, args.sessionId, args.runtime, {
|
|
512
|
+
repoRoot: workspace?.root,
|
|
513
|
+
workspaceAlias: workspace?.alias,
|
|
514
|
+
workspaceRoot: workspace?.root,
|
|
515
|
+
});
|
|
516
|
+
return { cwd: resolved.cwd, worktreePath: resolved.worktreePath, workspace };
|
|
517
|
+
}
|
|
518
|
+
if (workspace && args.sessionId) {
|
|
519
|
+
await Promise.resolve(args.runtime.sessionManager.updateSessionMetadata(args.sessionId, {
|
|
520
|
+
workspaceAlias: workspace.alias,
|
|
521
|
+
workspaceRoot: workspace.root,
|
|
442
522
|
}));
|
|
443
523
|
}
|
|
444
|
-
return { cwd:
|
|
524
|
+
return { cwd: workspace?.cwd, workspace };
|
|
445
525
|
}
|
|
446
526
|
export function formatWorktreePrefix(worktreePath) {
|
|
447
527
|
return worktreePath ? `[gateway] worktree=${worktreePath}\n` : "";
|
|
448
528
|
}
|
|
529
|
+
function workspaceAdminEnabled() {
|
|
530
|
+
const scopes = getRequestContext()?.authScopes ?? [];
|
|
531
|
+
return process.env.LLM_GATEWAY_WORKSPACE_ADMIN === "1" && scopes.includes("workspace:admin");
|
|
532
|
+
}
|
|
533
|
+
function registerWorkspaceTools(server, runtime) {
|
|
534
|
+
server.tool("workspace_list", "List registered workspace aliases and summary metadata. Does not browse files.", {}, {
|
|
535
|
+
title: "List workspaces",
|
|
536
|
+
readOnlyHint: true,
|
|
537
|
+
destructiveHint: false,
|
|
538
|
+
idempotentHint: true,
|
|
539
|
+
openWorldHint: false,
|
|
540
|
+
}, async () => {
|
|
541
|
+
const registry = loadWorkspaceRegistry(runtime.logger);
|
|
542
|
+
return {
|
|
543
|
+
content: [
|
|
544
|
+
{
|
|
545
|
+
type: "text",
|
|
546
|
+
text: JSON.stringify({
|
|
547
|
+
success: true,
|
|
548
|
+
enabled: registry.enabled,
|
|
549
|
+
default: registry.defaultAlias,
|
|
550
|
+
workspaces: registry.repos.map(describeWorkspace),
|
|
551
|
+
allowed_roots: registry.allowedRoots.map(root => ({
|
|
552
|
+
alias: root.alias,
|
|
553
|
+
path: root.path,
|
|
554
|
+
allow_register_existing_git_repos: root.allowRegisterExistingGitRepos,
|
|
555
|
+
allow_create_directories: root.allowCreateDirectories,
|
|
556
|
+
allow_init_git_repos: root.allowInitGitRepos,
|
|
557
|
+
max_create_depth: root.maxCreateDepth,
|
|
558
|
+
})),
|
|
559
|
+
}, null, 2),
|
|
560
|
+
},
|
|
561
|
+
],
|
|
562
|
+
};
|
|
563
|
+
});
|
|
564
|
+
server.tool("workspace_get", "Inspect a registered workspace alias. Does not list files.", { alias: WORKSPACE_ALIAS_SCHEMA }, {
|
|
565
|
+
title: "Get workspace",
|
|
566
|
+
readOnlyHint: true,
|
|
567
|
+
destructiveHint: false,
|
|
568
|
+
idempotentHint: true,
|
|
569
|
+
openWorldHint: false,
|
|
570
|
+
}, async ({ alias }) => {
|
|
571
|
+
try {
|
|
572
|
+
const registry = loadWorkspaceRegistry(runtime.logger);
|
|
573
|
+
return {
|
|
574
|
+
content: [
|
|
575
|
+
{
|
|
576
|
+
type: "text",
|
|
577
|
+
text: JSON.stringify({ success: true, workspace: describeWorkspace(getWorkspace(registry, alias)) }, null, 2),
|
|
578
|
+
},
|
|
579
|
+
],
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
return createErrorResponse("workspace_get", 1, "", undefined, error);
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
server.tool("workspace_create", "Create a new local folder or git repo under a configured allowed root. Requires LLM_GATEWAY_WORKSPACE_ADMIN=1 and OAuth scope workspace:admin.", {
|
|
587
|
+
alias: WORKSPACE_ALIAS_SCHEMA,
|
|
588
|
+
root: WORKSPACE_ALIAS_SCHEMA.describe("Allowed-root alias from workspace_list."),
|
|
589
|
+
slug: z.string().min(1).max(255).describe("Safe relative path under the allowed root."),
|
|
590
|
+
kind: z.enum(["folder", "git"]).default("git"),
|
|
591
|
+
setDefault: z.boolean().default(false),
|
|
592
|
+
}, {
|
|
593
|
+
title: "Create workspace",
|
|
594
|
+
readOnlyHint: false,
|
|
595
|
+
destructiveHint: true,
|
|
596
|
+
idempotentHint: false,
|
|
597
|
+
openWorldHint: false,
|
|
598
|
+
}, async ({ alias, root, slug, kind, setDefault }) => {
|
|
599
|
+
try {
|
|
600
|
+
if (!workspaceAdminEnabled()) {
|
|
601
|
+
throw new Error("workspace_create requires LLM_GATEWAY_WORKSPACE_ADMIN=1 and OAuth scope workspace:admin");
|
|
602
|
+
}
|
|
603
|
+
const repo = createWorkspace({
|
|
604
|
+
alias,
|
|
605
|
+
rootAlias: root,
|
|
606
|
+
slug,
|
|
607
|
+
kind,
|
|
608
|
+
setDefault,
|
|
609
|
+
logger: runtime.logger,
|
|
610
|
+
});
|
|
611
|
+
return {
|
|
612
|
+
content: [
|
|
613
|
+
{
|
|
614
|
+
type: "text",
|
|
615
|
+
text: JSON.stringify({ success: true, workspace: describeWorkspace(repo) }, null, 2),
|
|
616
|
+
},
|
|
617
|
+
],
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
return createErrorResponse("workspace_create", 1, "", undefined, error);
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
server.tool("workspace_register_existing_repo", "Register an existing local Git repo under an allowed root. Requires LLM_GATEWAY_WORKSPACE_ADMIN=1 and OAuth scope workspace:admin.", {
|
|
625
|
+
alias: WORKSPACE_ALIAS_SCHEMA,
|
|
626
|
+
path: z
|
|
627
|
+
.string()
|
|
628
|
+
.min(1)
|
|
629
|
+
.describe("Absolute path to an existing Git repo under an allowed root."),
|
|
630
|
+
setDefault: z.boolean().default(false),
|
|
631
|
+
}, {
|
|
632
|
+
title: "Register workspace",
|
|
633
|
+
readOnlyHint: false,
|
|
634
|
+
destructiveHint: false,
|
|
635
|
+
idempotentHint: false,
|
|
636
|
+
openWorldHint: false,
|
|
637
|
+
}, async ({ alias, path, setDefault }) => {
|
|
638
|
+
try {
|
|
639
|
+
if (!workspaceAdminEnabled()) {
|
|
640
|
+
throw new Error("workspace_register_existing_repo requires LLM_GATEWAY_WORKSPACE_ADMIN=1 and OAuth scope workspace:admin");
|
|
641
|
+
}
|
|
642
|
+
const repo = registerExistingWorkspace({
|
|
643
|
+
alias,
|
|
644
|
+
repoPath: path,
|
|
645
|
+
setDefault,
|
|
646
|
+
logger: runtime.logger,
|
|
647
|
+
});
|
|
648
|
+
return {
|
|
649
|
+
content: [
|
|
650
|
+
{
|
|
651
|
+
type: "text",
|
|
652
|
+
text: JSON.stringify({ success: true, workspace: describeWorkspace(repo) }, null, 2),
|
|
653
|
+
},
|
|
654
|
+
],
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
return createErrorResponse("workspace_register_existing_repo", 1, "", undefined, error);
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
}
|
|
449
662
|
function createErrorResponse(cli, code, stderr, correlationId, error) {
|
|
450
663
|
let errorMessage = `Error executing ${cli} CLI`;
|
|
451
664
|
const isLaunchExit = code === 127 || code === -4058;
|
|
@@ -476,6 +689,7 @@ function createErrorResponse(cli, code, stderr, correlationId, error) {
|
|
|
476
689
|
content: [{ type: "text", text: errorMessage }],
|
|
477
690
|
isError: true,
|
|
478
691
|
structuredContent: {
|
|
692
|
+
response: errorMessage,
|
|
479
693
|
correlationId: correlationId || null,
|
|
480
694
|
cli,
|
|
481
695
|
exitCode: code,
|
|
@@ -900,7 +1114,7 @@ export function prepareClaudeRequest(params, runtime = resolveGatewayServerRunti
|
|
|
900
1114
|
logOptimizationTokens("prompt", corrId, effectivePrompt, optimized);
|
|
901
1115
|
effectivePrompt = optimized;
|
|
902
1116
|
}
|
|
903
|
-
const requestedMcpServers =
|
|
1117
|
+
const requestedMcpServers = params.mcpServers ? [...new Set(params.mcpServers)] : [];
|
|
904
1118
|
const mcpConfigResolution = resolveClaudeMcpConfig(params.operation, corrId, requestedMcpServers, params.strictMcpConfig);
|
|
905
1119
|
if ("errorResponse" in mcpConfigResolution) {
|
|
906
1120
|
return mcpConfigResolution.errorResponse;
|
|
@@ -1109,7 +1323,7 @@ export function prepareCodexRequest(params, runtime = resolveGatewayServerRuntim
|
|
|
1109
1323
|
logOptimizationTokens("prompt", corrId, effectivePrompt, optimized);
|
|
1110
1324
|
effectivePrompt = optimized;
|
|
1111
1325
|
}
|
|
1112
|
-
const requestedMcpServers =
|
|
1326
|
+
const requestedMcpServers = params.mcpServers ? [...new Set(params.mcpServers)] : [];
|
|
1113
1327
|
let approvalDecision = null;
|
|
1114
1328
|
if (params.approvalStrategy === "mcp_managed") {
|
|
1115
1329
|
approvalDecision = runtime.approvalManager.decide({
|
|
@@ -1267,7 +1481,7 @@ export function prepareGeminiRequest(params, runtime = resolveGatewayServerRunti
|
|
|
1267
1481
|
logOptimizationTokens("prompt", corrId, effectivePrompt, optimized);
|
|
1268
1482
|
effectivePrompt = optimized;
|
|
1269
1483
|
}
|
|
1270
|
-
const requestedMcpServers =
|
|
1484
|
+
const requestedMcpServers = params.mcpServers ? [...new Set(params.mcpServers)] : [];
|
|
1271
1485
|
let approvalDecision = null;
|
|
1272
1486
|
if (params.approvalStrategy === "mcp_managed") {
|
|
1273
1487
|
approvalDecision = runtime.approvalManager.decide({
|
|
@@ -1287,51 +1501,45 @@ export function prepareGeminiRequest(params, runtime = resolveGatewayServerRunti
|
|
|
1287
1501
|
}
|
|
1288
1502
|
}
|
|
1289
1503
|
const effectiveApprovalMode = params.approvalStrategy === "mcp_managed" ? "yolo" : params.approvalMode;
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
if (highImpact.missingPolicyPath) {
|
|
1296
|
-
return createErrorResponse(params.operation, 1, "", corrId, new Error(`${highImpact.missingPolicyField}: path does not exist: ${highImpact.missingPolicyPath}`));
|
|
1297
|
-
}
|
|
1298
|
-
if (params.attachments && params.attachments.length > 0) {
|
|
1299
|
-
try {
|
|
1300
|
-
effectivePrompt = prependGeminiAttachments(effectivePrompt, params.attachments);
|
|
1301
|
-
}
|
|
1302
|
-
catch (err) {
|
|
1303
|
-
return createErrorResponse(params.operation, 1, "", corrId, err instanceof Error ? err : new Error(String(err)));
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
const args = ["-p", effectivePrompt];
|
|
1307
|
-
if (resolvedModel)
|
|
1308
|
-
args.push("--model", resolvedModel);
|
|
1309
|
-
if (effectiveApprovalMode)
|
|
1310
|
-
args.push("--approval-mode", effectiveApprovalMode);
|
|
1311
|
-
if (params.yolo && effectiveApprovalMode !== "yolo") {
|
|
1312
|
-
args.push("--yolo");
|
|
1504
|
+
const unsupported = (field, detail) => createErrorResponse(params.operation, 1, "", corrId, new Error(`${field} is not supported by Antigravity CLI (agy): ${detail}`));
|
|
1505
|
+
if (effectiveApprovalMode &&
|
|
1506
|
+
effectiveApprovalMode !== "default" &&
|
|
1507
|
+
effectiveApprovalMode !== "yolo") {
|
|
1508
|
+
return unsupported("approvalMode", "use 'default' for prompted execution or 'yolo'/yolo=true for --dangerously-skip-permissions");
|
|
1313
1509
|
}
|
|
1314
1510
|
if (params.allowedTools && params.allowedTools.length > 0) {
|
|
1315
|
-
|
|
1316
|
-
params.allowedTools.forEach(tool => args.push("--allowed-tools", tool));
|
|
1511
|
+
return unsupported("allowedTools", "agy has no non-interactive allowed-tools flag");
|
|
1317
1512
|
}
|
|
1318
1513
|
if (requestedMcpServers.length > 0) {
|
|
1319
|
-
|
|
1320
|
-
requestedMcpServers.forEach(serverName => args.push("--allowed-mcp-server-names", serverName));
|
|
1514
|
+
return unsupported("mcpServers", "agy has no non-interactive allowed MCP server allowlist flag");
|
|
1321
1515
|
}
|
|
1322
|
-
if (params.
|
|
1323
|
-
|
|
1324
|
-
params.includeDirs.forEach(dir => args.push("--include-directories", dir));
|
|
1516
|
+
if (params.outputFormat && params.outputFormat !== "text") {
|
|
1517
|
+
return unsupported("outputFormat", "agy print mode currently emits text only");
|
|
1325
1518
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
args.push("-o", "json");
|
|
1519
|
+
if (params.policyFiles && params.policyFiles.length > 0) {
|
|
1520
|
+
return unsupported("policyFiles", "agy has no --policy flag");
|
|
1329
1521
|
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1522
|
+
if (params.adminPolicyFiles && params.adminPolicyFiles.length > 0) {
|
|
1523
|
+
return unsupported("adminPolicyFiles", "agy has no --admin-policy flag");
|
|
1524
|
+
}
|
|
1525
|
+
if (params.attachments && params.attachments.length > 0) {
|
|
1526
|
+
return unsupported("attachments", "agy has no documented @path attachment-token contract");
|
|
1332
1527
|
}
|
|
1333
1528
|
if (params.skipTrust) {
|
|
1334
|
-
|
|
1529
|
+
return unsupported("skipTrust", "agy has no --skip-trust flag");
|
|
1530
|
+
}
|
|
1531
|
+
const args = ["--print", effectivePrompt];
|
|
1532
|
+
if (resolvedModel)
|
|
1533
|
+
args.push("--model", resolvedModel);
|
|
1534
|
+
if (params.includeDirs && params.includeDirs.length > 0) {
|
|
1535
|
+
sanitizeCliArgValues(params.includeDirs, "includeDirs");
|
|
1536
|
+
params.includeDirs.forEach(dir => args.push("--add-dir", dir));
|
|
1537
|
+
}
|
|
1538
|
+
if (params.sandbox) {
|
|
1539
|
+
args.push("--sandbox");
|
|
1540
|
+
}
|
|
1541
|
+
if (params.yolo || effectiveApprovalMode === "yolo") {
|
|
1542
|
+
args.push("--dangerously-skip-permissions");
|
|
1335
1543
|
}
|
|
1336
1544
|
return {
|
|
1337
1545
|
corrId,
|
|
@@ -1400,76 +1608,19 @@ export function prepareGrokRequest(params, runtime = resolveGatewayServerRuntime
|
|
|
1400
1608
|
}
|
|
1401
1609
|
}
|
|
1402
1610
|
const effectiveAlwaysApprove = params.approvalStrategy === "mcp_managed" ? true : Boolean(params.alwaysApprove);
|
|
1611
|
+
const grokContract = UPSTREAM_CLI_CONTRACTS.grok;
|
|
1612
|
+
const genParams = params;
|
|
1403
1613
|
const args = ["-p", effectivePrompt];
|
|
1404
1614
|
if (resolvedModel)
|
|
1405
1615
|
args.push("--model", resolvedModel);
|
|
1406
|
-
|
|
1407
|
-
args.push("--output-format", params.outputFormat);
|
|
1616
|
+
args.push(...buildArgvFromGeneration(grokContract, GROK_GEN_OUTPUT_FORMAT, genParams));
|
|
1408
1617
|
if (effectiveAlwaysApprove) {
|
|
1409
1618
|
args.push("--always-approve");
|
|
1410
1619
|
}
|
|
1411
1620
|
else if (params.permissionMode) {
|
|
1412
1621
|
args.push("--permission-mode", params.permissionMode);
|
|
1413
1622
|
}
|
|
1414
|
-
|
|
1415
|
-
args.push("--effort", params.effort);
|
|
1416
|
-
if (params.reasoningEffort)
|
|
1417
|
-
args.push("--reasoning-effort", params.reasoningEffort);
|
|
1418
|
-
if (params.allowedTools && params.allowedTools.length > 0) {
|
|
1419
|
-
args.push("--tools", params.allowedTools.join(","));
|
|
1420
|
-
}
|
|
1421
|
-
if (params.disallowedTools && params.disallowedTools.length > 0) {
|
|
1422
|
-
args.push("--disallowed-tools", params.disallowedTools.join(","));
|
|
1423
|
-
}
|
|
1424
|
-
if (params.maxTurns !== undefined) {
|
|
1425
|
-
args.push("--max-turns", String(params.maxTurns));
|
|
1426
|
-
}
|
|
1427
|
-
if (params.workingDir) {
|
|
1428
|
-
args.push("--cwd", params.workingDir);
|
|
1429
|
-
}
|
|
1430
|
-
if (params.sandbox) {
|
|
1431
|
-
args.push("--sandbox", params.sandbox);
|
|
1432
|
-
}
|
|
1433
|
-
if (params.rules) {
|
|
1434
|
-
args.push("--rules", params.rules);
|
|
1435
|
-
}
|
|
1436
|
-
if (params.systemPromptOverride) {
|
|
1437
|
-
args.push("--system-prompt-override", params.systemPromptOverride);
|
|
1438
|
-
}
|
|
1439
|
-
if (params.allow && params.allow.length > 0) {
|
|
1440
|
-
for (const rule of params.allow) {
|
|
1441
|
-
args.push("--allow", rule);
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
if (params.deny && params.deny.length > 0) {
|
|
1445
|
-
for (const rule of params.deny) {
|
|
1446
|
-
args.push("--deny", rule);
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
if (params.compactionMode) {
|
|
1450
|
-
args.push("--compaction-mode", params.compactionMode);
|
|
1451
|
-
}
|
|
1452
|
-
if (params.compactionDetail) {
|
|
1453
|
-
args.push("--compaction-detail", params.compactionDetail);
|
|
1454
|
-
}
|
|
1455
|
-
if (params.agent) {
|
|
1456
|
-
args.push("--agent", params.agent);
|
|
1457
|
-
}
|
|
1458
|
-
if (params.bestOfN !== undefined) {
|
|
1459
|
-
args.push("--best-of-n", String(params.bestOfN));
|
|
1460
|
-
}
|
|
1461
|
-
if (params.check) {
|
|
1462
|
-
args.push("--check");
|
|
1463
|
-
}
|
|
1464
|
-
if (params.disableWebSearch) {
|
|
1465
|
-
args.push("--disable-web-search");
|
|
1466
|
-
}
|
|
1467
|
-
if (params.todoGate) {
|
|
1468
|
-
args.push("--todo-gate");
|
|
1469
|
-
}
|
|
1470
|
-
if (params.verbatim) {
|
|
1471
|
-
args.push("--verbatim");
|
|
1472
|
-
}
|
|
1623
|
+
args.push(...buildArgvFromGeneration(grokContract, GROK_GEN_MAIN, genParams));
|
|
1473
1624
|
if (params.agents !== undefined) {
|
|
1474
1625
|
if (typeof params.agents === "string") {
|
|
1475
1626
|
if (!params.agents.trim()) {
|
|
@@ -1485,9 +1636,7 @@ export function prepareGrokRequest(params, runtime = resolveGatewayServerRuntime
|
|
|
1485
1636
|
args.push("--agents", JSON.stringify(agentsResult.value));
|
|
1486
1637
|
}
|
|
1487
1638
|
}
|
|
1488
|
-
|
|
1489
|
-
args.push("--prompt-file", params.promptFile);
|
|
1490
|
-
}
|
|
1639
|
+
args.push(...buildArgvFromGeneration(grokContract, GROK_GEN_PROMPT_FILE, genParams));
|
|
1491
1640
|
if (params.promptJson !== undefined) {
|
|
1492
1641
|
const promptJsonValue = typeof params.promptJson === "string" ? params.promptJson : JSON.stringify(params.promptJson);
|
|
1493
1642
|
if (!promptJsonValue.trim()) {
|
|
@@ -1495,33 +1644,8 @@ export function prepareGrokRequest(params, runtime = resolveGatewayServerRuntime
|
|
|
1495
1644
|
}
|
|
1496
1645
|
args.push("--prompt-json", promptJsonValue);
|
|
1497
1646
|
}
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
}
|
|
1501
|
-
if (params.experimentalMemory) {
|
|
1502
|
-
args.push("--experimental-memory");
|
|
1503
|
-
}
|
|
1504
|
-
if (params.noAltScreen) {
|
|
1505
|
-
args.push("--no-alt-screen");
|
|
1506
|
-
}
|
|
1507
|
-
if (params.noMemory) {
|
|
1508
|
-
args.push("--no-memory");
|
|
1509
|
-
}
|
|
1510
|
-
if (params.noPlan) {
|
|
1511
|
-
args.push("--no-plan");
|
|
1512
|
-
}
|
|
1513
|
-
if (params.noSubagents) {
|
|
1514
|
-
args.push("--no-subagents");
|
|
1515
|
-
}
|
|
1516
|
-
if (params.oauth) {
|
|
1517
|
-
args.push("--oauth");
|
|
1518
|
-
}
|
|
1519
|
-
if (params.restoreCode) {
|
|
1520
|
-
args.push("--restore-code");
|
|
1521
|
-
}
|
|
1522
|
-
if (params.leaderSocket) {
|
|
1523
|
-
args.push("--leader-socket", params.leaderSocket);
|
|
1524
|
-
}
|
|
1647
|
+
args.push(...buildArgvFromGeneration(grokContract, GROK_GEN_SINGLE, genParams));
|
|
1648
|
+
args.push(...buildArgvFromGeneration(grokContract, GROK_GEN_TAIL, genParams));
|
|
1525
1649
|
if (params.nativeWorktree === true) {
|
|
1526
1650
|
args.push("--worktree");
|
|
1527
1651
|
}
|
|
@@ -1678,6 +1802,7 @@ function buildCliResponse(cli, stdout, optimizeResponse, corrId, sessionId, prep
|
|
|
1678
1802
|
const response = {
|
|
1679
1803
|
content: [{ type: "text", text: finalStdout }],
|
|
1680
1804
|
structuredContent: {
|
|
1805
|
+
response: finalStdout,
|
|
1681
1806
|
model: prep.resolvedModel || "default",
|
|
1682
1807
|
cli,
|
|
1683
1808
|
correlationId: corrId,
|
|
@@ -1813,6 +1938,7 @@ function buildGrokApiToolResponse(args) {
|
|
|
1813
1938
|
const response = {
|
|
1814
1939
|
content: [{ type: "text", text }],
|
|
1815
1940
|
structuredContent: {
|
|
1941
|
+
response: text,
|
|
1816
1942
|
provider: "grok-api",
|
|
1817
1943
|
cli: "grok-api",
|
|
1818
1944
|
model: args.result.model || args.prep.resolvedModel,
|
|
@@ -2016,6 +2142,7 @@ function resolveHandlerRuntime(deps) {
|
|
|
2016
2142
|
sessionManager: deps.sessionManager,
|
|
2017
2143
|
logger: normalizedLogger,
|
|
2018
2144
|
asyncJobManager: asyncDeps.asyncJobManager,
|
|
2145
|
+
workspaces: deps.workspaces,
|
|
2019
2146
|
});
|
|
2020
2147
|
}
|
|
2021
2148
|
export async function handleGeminiRequest(deps, params) {
|
|
@@ -2071,13 +2198,20 @@ export async function handleGeminiRequest(deps, params) {
|
|
|
2071
2198
|
args.push(...sessionPlan.args);
|
|
2072
2199
|
let worktreeResolution = {};
|
|
2073
2200
|
try {
|
|
2074
|
-
worktreeResolution = await
|
|
2201
|
+
worktreeResolution = await resolveWorkspaceAndWorktreeForRequest({
|
|
2202
|
+
provider: "gemini",
|
|
2203
|
+
workspace: params.workspace,
|
|
2204
|
+
worktree: params.worktree,
|
|
2205
|
+
sessionId: effectiveSessionIdHint,
|
|
2206
|
+
runtime,
|
|
2207
|
+
addDir: params.includeDirs,
|
|
2208
|
+
});
|
|
2075
2209
|
}
|
|
2076
2210
|
catch (err) {
|
|
2077
2211
|
return createErrorResponse("gemini_request", 1, "", corrId, err);
|
|
2078
2212
|
}
|
|
2079
2213
|
const geminiFrHandoff = buildAsyncFlightRecorderHandoff("gemini", prep, params.sessionId, params.outputFormat);
|
|
2080
|
-
const result = await awaitJobOrDefer("gemini", args, corrId, resolveIdleTimeout("gemini", params.idleTimeoutMs), params.outputFormat, params.forceRefresh, runtime, undefined, undefined, geminiFrHandoff.flightRecorderEntry, geminiFrHandoff.extractUsage, worktreeResolution.cwd);
|
|
2214
|
+
const result = await awaitJobOrDefer("gemini", args, corrId, resolveIdleTimeout("gemini", params.idleTimeoutMs), params.outputFormat, params.forceRefresh, runtime, undefined, undefined, geminiFrHandoff.flightRecorderEntry, geminiFrHandoff.extractUsage, undefined, worktreeResolution.cwd);
|
|
2081
2215
|
if (isDeferredResponse(result)) {
|
|
2082
2216
|
return buildDeferredToolResponse(result, effectiveSessionIdHint);
|
|
2083
2217
|
}
|
|
@@ -2209,7 +2343,14 @@ export async function handleGeminiRequestAsync(deps, params) {
|
|
|
2209
2343
|
}
|
|
2210
2344
|
let worktreeResolution = {};
|
|
2211
2345
|
try {
|
|
2212
|
-
worktreeResolution = await
|
|
2346
|
+
worktreeResolution = await resolveWorkspaceAndWorktreeForRequest({
|
|
2347
|
+
provider: "gemini",
|
|
2348
|
+
workspace: params.workspace,
|
|
2349
|
+
worktree: params.worktree,
|
|
2350
|
+
sessionId: effectiveSessionId,
|
|
2351
|
+
runtime,
|
|
2352
|
+
addDir: params.includeDirs,
|
|
2353
|
+
});
|
|
2213
2354
|
}
|
|
2214
2355
|
catch (err) {
|
|
2215
2356
|
return createErrorResponse("gemini_request_async", 1, "", corrId, err);
|
|
@@ -2322,7 +2463,14 @@ export async function handleGrokRequest(deps, params) {
|
|
|
2322
2463
|
args.push(...sessionResult.resumeArgs);
|
|
2323
2464
|
let worktreeResolution = {};
|
|
2324
2465
|
try {
|
|
2325
|
-
worktreeResolution = await
|
|
2466
|
+
worktreeResolution = await resolveWorkspaceAndWorktreeForRequest({
|
|
2467
|
+
provider: "grok",
|
|
2468
|
+
workspace: params.workspace,
|
|
2469
|
+
worktree: params.worktree,
|
|
2470
|
+
sessionId: sessionResult.effectiveSessionId,
|
|
2471
|
+
runtime,
|
|
2472
|
+
workingDir: params.workingDir,
|
|
2473
|
+
});
|
|
2326
2474
|
}
|
|
2327
2475
|
catch (err) {
|
|
2328
2476
|
return createErrorResponse("grok_request", 1, "", corrId, err);
|
|
@@ -2490,7 +2638,14 @@ export async function handleGrokRequestAsync(deps, params) {
|
|
|
2490
2638
|
}
|
|
2491
2639
|
let worktreeResolution = {};
|
|
2492
2640
|
try {
|
|
2493
|
-
worktreeResolution = await
|
|
2641
|
+
worktreeResolution = await resolveWorkspaceAndWorktreeForRequest({
|
|
2642
|
+
provider: "grok",
|
|
2643
|
+
workspace: params.workspace,
|
|
2644
|
+
worktree: params.worktree,
|
|
2645
|
+
sessionId: effectiveSessionId,
|
|
2646
|
+
runtime,
|
|
2647
|
+
workingDir: params.workingDir,
|
|
2648
|
+
});
|
|
2494
2649
|
}
|
|
2495
2650
|
catch (err) {
|
|
2496
2651
|
return createErrorResponse("grok_request_async", 1, "", corrId, err);
|
|
@@ -2578,7 +2733,15 @@ export async function handleMistralRequest(deps, params) {
|
|
|
2578
2733
|
args.push(...sessionResult.resumeArgs);
|
|
2579
2734
|
let worktreeResolution = {};
|
|
2580
2735
|
try {
|
|
2581
|
-
worktreeResolution = await
|
|
2736
|
+
worktreeResolution = await resolveWorkspaceAndWorktreeForRequest({
|
|
2737
|
+
provider: "mistral",
|
|
2738
|
+
workspace: params.workspace,
|
|
2739
|
+
worktree: params.worktree,
|
|
2740
|
+
sessionId: sessionResult.effectiveSessionId,
|
|
2741
|
+
runtime,
|
|
2742
|
+
workingDir: params.workingDir,
|
|
2743
|
+
addDir: params.addDir,
|
|
2744
|
+
});
|
|
2582
2745
|
}
|
|
2583
2746
|
catch (err) {
|
|
2584
2747
|
return createErrorResponse("mistral_request", 1, "", corrId, err);
|
|
@@ -2732,7 +2895,15 @@ export async function handleMistralRequestAsync(deps, params) {
|
|
|
2732
2895
|
}
|
|
2733
2896
|
let worktreeResolution = {};
|
|
2734
2897
|
try {
|
|
2735
|
-
worktreeResolution = await
|
|
2898
|
+
worktreeResolution = await resolveWorkspaceAndWorktreeForRequest({
|
|
2899
|
+
provider: "mistral",
|
|
2900
|
+
workspace: params.workspace,
|
|
2901
|
+
worktree: params.worktree,
|
|
2902
|
+
sessionId: effectiveSessionId,
|
|
2903
|
+
runtime,
|
|
2904
|
+
workingDir: params.workingDir,
|
|
2905
|
+
addDir: params.addDir,
|
|
2906
|
+
});
|
|
2736
2907
|
}
|
|
2737
2908
|
catch (err) {
|
|
2738
2909
|
return createErrorResponse("mistral_request_async", 1, "", corrId, err);
|
|
@@ -2844,7 +3015,15 @@ export async function handleCodexRequestAsync(deps, params) {
|
|
|
2844
3015
|
}
|
|
2845
3016
|
let worktreeResolution = {};
|
|
2846
3017
|
try {
|
|
2847
|
-
worktreeResolution = await
|
|
3018
|
+
worktreeResolution = await resolveWorkspaceAndWorktreeForRequest({
|
|
3019
|
+
provider: "codex",
|
|
3020
|
+
workspace: params.workspace,
|
|
3021
|
+
worktree: params.worktree,
|
|
3022
|
+
sessionId: effectiveSessionId,
|
|
3023
|
+
runtime,
|
|
3024
|
+
workingDir: params.workingDir,
|
|
3025
|
+
addDir: params.addDir,
|
|
3026
|
+
});
|
|
2848
3027
|
}
|
|
2849
3028
|
catch (err) {
|
|
2850
3029
|
runPrepCleanupLocally();
|
|
@@ -2899,6 +3078,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
2899
3078
|
const server = newGatewayMcpServer(asyncJobsEnabled, grokApiToolsEnabled);
|
|
2900
3079
|
registerBaseResources(server, runtime);
|
|
2901
3080
|
registerValidationTools(server, { asyncJobManager });
|
|
3081
|
+
registerWorkspaceTools(server, runtime);
|
|
2902
3082
|
if (grokApiToolsEnabled) {
|
|
2903
3083
|
server.tool("grok_api_request", "Run an xAI Grok API request synchronously through the Responses API. Requires exactly one of prompt or promptParts. Registered only when [providers.xai] is configured and its API-key env var is present.", {
|
|
2904
3084
|
prompt: z
|
|
@@ -3082,6 +3262,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
3082
3262
|
.array(z.string())
|
|
3083
3263
|
.optional()
|
|
3084
3264
|
.describe('Claude --tools: restrict the available built-in tool set (distinct from allowedTools permission gating). Pass [""] to disable all tools.'),
|
|
3265
|
+
workspace: WORKSPACE_ALIAS_SCHEMA.optional(),
|
|
3085
3266
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
3086
3267
|
approvalStrategy: z
|
|
3087
3268
|
.enum(["legacy", "mcp_managed"])
|
|
@@ -3119,7 +3300,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
3119
3300
|
destructiveHint: true,
|
|
3120
3301
|
idempotentHint: false,
|
|
3121
3302
|
openWorldHint: true,
|
|
3122
|
-
}, 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, worktree, approvalStrategy, approvalPolicy, mcpServers, strictMcpConfig, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, }) => {
|
|
3303
|
+
}, 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, }) => {
|
|
3123
3304
|
const startTime = Date.now();
|
|
3124
3305
|
if (systemPrompt !== undefined && appendSystemPrompt !== undefined) {
|
|
3125
3306
|
return createErrorResponse("claude", 1, "", correlationId, new Error("systemPrompt and appendSystemPrompt are mutually exclusive; use one or the other (not both)."));
|
|
@@ -3214,7 +3395,14 @@ export function createGatewayServer(deps = {}) {
|
|
|
3214
3395
|
}
|
|
3215
3396
|
let worktreeResolution = {};
|
|
3216
3397
|
try {
|
|
3217
|
-
worktreeResolution = await
|
|
3398
|
+
worktreeResolution = await resolveWorkspaceAndWorktreeForRequest({
|
|
3399
|
+
provider: "claude",
|
|
3400
|
+
workspace,
|
|
3401
|
+
worktree,
|
|
3402
|
+
sessionId: effectiveSessionId,
|
|
3403
|
+
runtime,
|
|
3404
|
+
addDir,
|
|
3405
|
+
});
|
|
3218
3406
|
}
|
|
3219
3407
|
catch (err) {
|
|
3220
3408
|
return createErrorResponse("claude_request", 1, "", corrId, err);
|
|
@@ -3425,6 +3613,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
3425
3613
|
.array(z.string())
|
|
3426
3614
|
.optional()
|
|
3427
3615
|
.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."),
|
|
3616
|
+
workspace: WORKSPACE_ALIAS_SCHEMA.optional(),
|
|
3428
3617
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
3429
3618
|
}, {
|
|
3430
3619
|
title: "Codex request",
|
|
@@ -3432,7 +3621,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
3432
3621
|
destructiveHint: true,
|
|
3433
3622
|
idempotentHint: false,
|
|
3434
3623
|
openWorldHint: true,
|
|
3435
|
-
}, 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, worktree, }) => {
|
|
3624
|
+
}, 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, }) => {
|
|
3436
3625
|
const startTime = Date.now();
|
|
3437
3626
|
const prep = prepareCodexRequest({
|
|
3438
3627
|
prompt,
|
|
@@ -3488,7 +3677,15 @@ export function createGatewayServer(deps = {}) {
|
|
|
3488
3677
|
const prepCleanup = "cleanup" in prep && typeof prep.cleanup === "function" ? prep.cleanup : undefined;
|
|
3489
3678
|
let worktreeResolution = {};
|
|
3490
3679
|
try {
|
|
3491
|
-
worktreeResolution = await
|
|
3680
|
+
worktreeResolution = await resolveWorkspaceAndWorktreeForRequest({
|
|
3681
|
+
provider: "codex",
|
|
3682
|
+
workspace,
|
|
3683
|
+
worktree,
|
|
3684
|
+
sessionId,
|
|
3685
|
+
runtime,
|
|
3686
|
+
workingDir,
|
|
3687
|
+
addDir,
|
|
3688
|
+
});
|
|
3492
3689
|
}
|
|
3493
3690
|
catch (err) {
|
|
3494
3691
|
return createErrorResponse("codex_request", 1, "", corrId, err);
|
|
@@ -3610,13 +3807,14 @@ export function createGatewayServer(deps = {}) {
|
|
|
3610
3807
|
.max(3_600_000)
|
|
3611
3808
|
.optional()
|
|
3612
3809
|
.describe("Idle timeout in ms (min 30s, max 1h, omit=CLI default)"),
|
|
3810
|
+
workspace: WORKSPACE_ALIAS_SCHEMA.optional(),
|
|
3613
3811
|
}, {
|
|
3614
3812
|
title: "Fork Codex session",
|
|
3615
3813
|
readOnlyHint: false,
|
|
3616
3814
|
destructiveHint: true,
|
|
3617
3815
|
idempotentHint: false,
|
|
3618
3816
|
openWorldHint: true,
|
|
3619
|
-
}, async ({ prompt, sessionId, forkLast, model, sandboxMode, askForApproval, correlationId, idleTimeoutMs, }) => {
|
|
3817
|
+
}, async ({ prompt, sessionId, forkLast, model, sandboxMode, askForApproval, correlationId, idleTimeoutMs, workspace, }) => {
|
|
3620
3818
|
const corrId = correlationId || randomUUID();
|
|
3621
3819
|
const startTime = Date.now();
|
|
3622
3820
|
let durationMs = 0;
|
|
@@ -3656,7 +3854,13 @@ export function createGatewayServer(deps = {}) {
|
|
|
3656
3854
|
const finalArgs = [forkArgs[0], ...flagSegment, ...forkArgs.slice(1)];
|
|
3657
3855
|
logger.info(`[${corrId}] codex_fork_session invoked (forkLast=${Boolean(forkLast)}, sessionId=${sessionId ? "set" : "unset"})`);
|
|
3658
3856
|
try {
|
|
3659
|
-
const
|
|
3857
|
+
const worktreeResolution = await resolveWorkspaceAndWorktreeForRequest({
|
|
3858
|
+
provider: "codex",
|
|
3859
|
+
workspace,
|
|
3860
|
+
sessionId,
|
|
3861
|
+
runtime,
|
|
3862
|
+
});
|
|
3863
|
+
const result = await awaitJobOrDefer("codex", finalArgs, corrId, resolveIdleTimeout("codex", idleTimeoutMs), undefined, false, runtime, undefined, undefined, undefined, undefined, undefined, worktreeResolution.cwd);
|
|
3660
3864
|
if (isDeferredResponse(result)) {
|
|
3661
3865
|
return buildDeferredToolResponse(result, sessionId);
|
|
3662
3866
|
}
|
|
@@ -3678,23 +3882,23 @@ export function createGatewayServer(deps = {}) {
|
|
|
3678
3882
|
performanceMetrics.recordRequest("codex", finalizedDurationMs, wasSuccessful);
|
|
3679
3883
|
}
|
|
3680
3884
|
});
|
|
3681
|
-
server.tool("gemini_request", "Run a Google
|
|
3885
|
+
server.tool("gemini_request", "Run a Google Antigravity CLI (`agy`) request through the Gemini-compatible gateway tool synchronously (when async jobs are enabled, auto-defers to a pollable job past the sync deadline; otherwise runs to completion). Requires exactly one of prompt or promptParts.", {
|
|
3682
3886
|
prompt: z
|
|
3683
3887
|
.string()
|
|
3684
3888
|
.min(1, "Prompt cannot be empty")
|
|
3685
3889
|
.max(100000, "Prompt too long (max 100k chars)")
|
|
3686
3890
|
.optional()
|
|
3687
|
-
.describe("Prompt text for
|
|
3891
|
+
.describe("Prompt text for Antigravity CLI (mutually exclusive with promptParts)"),
|
|
3688
3892
|
promptParts: PromptPartsSchema.optional().describe("Cache-aware structured prompt: { system?, tools?, context?, task }. Mutually exclusive with prompt. Stable parts hash into cache_state for prefix-discipline tracking."),
|
|
3689
3893
|
model: z
|
|
3690
3894
|
.string()
|
|
3691
3895
|
.optional()
|
|
3692
|
-
.describe("Model name or alias (e.g. gemini-3-pro-preview, gemini-2.5-flash, pro, flash, latest)"),
|
|
3896
|
+
.describe("Model name or alias passed to agy --model (e.g. gemini-3-pro-preview, gemini-2.5-flash, pro, flash, latest)"),
|
|
3693
3897
|
sessionId: z
|
|
3694
3898
|
.string()
|
|
3695
3899
|
.optional()
|
|
3696
|
-
.describe("
|
|
3697
|
-
resumeLatest: z.boolean().default(false).describe("
|
|
3900
|
+
.describe("Antigravity conversation ID to resume (emits --conversation <id>)"),
|
|
3901
|
+
resumeLatest: z.boolean().default(false).describe("Continue the most recent conversation"),
|
|
3698
3902
|
createNewSession: z.boolean().default(false).describe("Force new session"),
|
|
3699
3903
|
approvalMode: z
|
|
3700
3904
|
.enum(GEMINI_APPROVAL_MODES)
|
|
@@ -3710,13 +3914,16 @@ export function createGatewayServer(deps = {}) {
|
|
|
3710
3914
|
.describe("Approval policy override"),
|
|
3711
3915
|
mcpServers: z
|
|
3712
3916
|
.array(MCP_SERVER_ENUM)
|
|
3713
|
-
.default([
|
|
3714
|
-
.describe("
|
|
3917
|
+
.default([])
|
|
3918
|
+
.describe("Unsupported for Antigravity CLI; non-empty values are rejected"),
|
|
3715
3919
|
allowedTools: z
|
|
3716
3920
|
.array(z.string())
|
|
3717
3921
|
.optional()
|
|
3718
|
-
.describe("
|
|
3719
|
-
includeDirs: z
|
|
3922
|
+
.describe("Unsupported for Antigravity CLI; non-empty values are rejected"),
|
|
3923
|
+
includeDirs: z
|
|
3924
|
+
.array(z.string())
|
|
3925
|
+
.optional()
|
|
3926
|
+
.describe("Additional workspace directories passed as --add-dir"),
|
|
3720
3927
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
3721
3928
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
3722
3929
|
optimizeResponse: z.boolean().default(false).describe("Optimize response output"),
|
|
@@ -3734,19 +3941,20 @@ export function createGatewayServer(deps = {}) {
|
|
|
3734
3941
|
outputFormat: z
|
|
3735
3942
|
.enum(["text", "json", "stream-json"])
|
|
3736
3943
|
.default("text")
|
|
3737
|
-
.describe("
|
|
3738
|
-
sandbox: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.sandbox.describe("Run
|
|
3739
|
-
policyFiles: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.policyFiles.describe("
|
|
3740
|
-
adminPolicyFiles: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.adminPolicyFiles.describe("
|
|
3741
|
-
attachments: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.attachments.describe("
|
|
3944
|
+
.describe("Antigravity CLI currently supports text output only through the gateway; json and stream-json are rejected."),
|
|
3945
|
+
sandbox: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.sandbox.describe("Run Antigravity in sandbox mode (--sandbox)"),
|
|
3946
|
+
policyFiles: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.policyFiles.describe("Unsupported for Antigravity CLI; non-empty values are rejected."),
|
|
3947
|
+
adminPolicyFiles: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.adminPolicyFiles.describe("Unsupported for Antigravity CLI; non-empty values are rejected."),
|
|
3948
|
+
attachments: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.attachments.describe("Unsupported for Antigravity CLI; non-empty values are rejected."),
|
|
3742
3949
|
skipTrust: z
|
|
3743
3950
|
.boolean()
|
|
3744
3951
|
.default(false)
|
|
3745
|
-
.describe("
|
|
3952
|
+
.describe("Unsupported for Antigravity CLI; true is rejected."),
|
|
3746
3953
|
yolo: z
|
|
3747
3954
|
.boolean()
|
|
3748
3955
|
.optional()
|
|
3749
|
-
.describe("Emit `--
|
|
3956
|
+
.describe("Emit `--dangerously-skip-permissions` to auto-approve all actions. Routed through the same approval gate. Under mcp_managed the gate still decides."),
|
|
3957
|
+
workspace: WORKSPACE_ALIAS_SCHEMA.optional(),
|
|
3750
3958
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
3751
3959
|
}, {
|
|
3752
3960
|
title: "Gemini request",
|
|
@@ -3754,7 +3962,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
3754
3962
|
destructiveHint: true,
|
|
3755
3963
|
idempotentHint: false,
|
|
3756
3964
|
openWorldHint: true,
|
|
3757
|
-
}, 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, worktree, }) => {
|
|
3965
|
+
}, async ({ prompt, promptParts, model, sessionId, resumeLatest, createNewSession, approvalMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, includeDirs, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, outputFormat, sandbox, policyFiles, adminPolicyFiles, attachments, skipTrust, yolo, workspace, worktree, }) => {
|
|
3758
3966
|
return handleGeminiRequest({ sessionManager, logger, runtime }, {
|
|
3759
3967
|
prompt,
|
|
3760
3968
|
promptParts,
|
|
@@ -3780,6 +3988,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
3780
3988
|
attachments,
|
|
3781
3989
|
skipTrust,
|
|
3782
3990
|
yolo,
|
|
3991
|
+
workspace,
|
|
3783
3992
|
worktree,
|
|
3784
3993
|
});
|
|
3785
3994
|
});
|
|
@@ -3792,10 +4001,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
3792
4001
|
.describe("Prompt text for Grok (mutually exclusive with promptParts)"),
|
|
3793
4002
|
promptParts: PromptPartsSchema.optional().describe("Cache-aware structured prompt: { system?, tools?, context?, task }. Mutually exclusive with prompt. Stable parts hash into cache_state for prefix-discipline tracking."),
|
|
3794
4003
|
model: z.string().optional().describe("Model name or alias (e.g. grok-build, latest)"),
|
|
3795
|
-
|
|
3796
|
-
.enum(["plain", "json", "streaming-json"])
|
|
3797
|
-
.optional()
|
|
3798
|
-
.describe("Output format (plain|json|streaming-json). Grok default is plain."),
|
|
4004
|
+
...GROK_GENERATED_SHAPE,
|
|
3799
4005
|
sessionId: z
|
|
3800
4006
|
.string()
|
|
3801
4007
|
.optional()
|
|
@@ -3813,11 +4019,6 @@ export function createGatewayServer(deps = {}) {
|
|
|
3813
4019
|
.enum(["default", "acceptEdits", "auto", "dontAsk", "bypassPermissions", "plan"])
|
|
3814
4020
|
.optional()
|
|
3815
4021
|
.describe("Grok permission mode"),
|
|
3816
|
-
effort: z
|
|
3817
|
-
.enum(["low", "medium", "high", "xhigh", "max"])
|
|
3818
|
-
.optional()
|
|
3819
|
-
.describe("Grok effort level"),
|
|
3820
|
-
reasoningEffort: z.string().optional().describe("Reasoning effort for reasoning models"),
|
|
3821
4022
|
approvalStrategy: z
|
|
3822
4023
|
.enum(["legacy", "mcp_managed"])
|
|
3823
4024
|
.default("legacy")
|
|
@@ -3830,14 +4031,6 @@ export function createGatewayServer(deps = {}) {
|
|
|
3830
4031
|
.array(MCP_SERVER_ENUM)
|
|
3831
4032
|
.default(["sqry"])
|
|
3832
4033
|
.describe("MCP server names for approval tracking (Grok manages its own MCP config via `grok mcp`)"),
|
|
3833
|
-
allowedTools: z
|
|
3834
|
-
.array(z.string())
|
|
3835
|
-
.optional()
|
|
3836
|
-
.describe("Allowed built-in tools (passed as --tools comma list)"),
|
|
3837
|
-
disallowedTools: z
|
|
3838
|
-
.array(z.string())
|
|
3839
|
-
.optional()
|
|
3840
|
-
.describe("Disallowed built-in tools (passed as --disallowed-tools comma list)"),
|
|
3841
4034
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
3842
4035
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
3843
4036
|
optimizeResponse: z.boolean().default(false).describe("Optimize response output"),
|
|
@@ -3852,111 +4045,19 @@ export function createGatewayServer(deps = {}) {
|
|
|
3852
4045
|
.boolean()
|
|
3853
4046
|
.default(false)
|
|
3854
4047
|
.describe("Bypass dedup and force a fresh CLI run even if a recent identical request exists"),
|
|
3855
|
-
maxTurns: MAX_TURNS_SCHEMA.optional().describe("Grok `--max-turns N`: cap on agent-loop iterations for cost / latency control (Phase 4 slice δ). Bounded to safe integers ≤ 10000."),
|
|
3856
|
-
workingDir: z
|
|
3857
|
-
.string()
|
|
3858
|
-
.min(1)
|
|
3859
|
-
.optional()
|
|
3860
|
-
.describe("Grok --cwd <DIR>: working directory for this invocation. Lets headless callers run Grok against a directory other than the gateway process's cwd."),
|
|
3861
|
-
sandbox: z
|
|
3862
|
-
.string()
|
|
3863
|
-
.min(1)
|
|
3864
|
-
.optional()
|
|
3865
|
-
.describe("Grok --sandbox <PROFILE>: sandbox profile for filesystem and network access. Freeform per `grok --help` (no enum constraint on Grok 0.1.210); also settable via GROK_SANDBOX env var. Caller responsibility to pass a valid profile name."),
|
|
3866
|
-
rules: z
|
|
3867
|
-
.string()
|
|
3868
|
-
.min(1)
|
|
3869
|
-
.optional()
|
|
3870
|
-
.describe("Grok --rules <RULES>: extra rules to append to the system prompt. Supports `@file` prefix per `grok --help` to load from a file; gateway passes the value verbatim and lets Grok parse the prefix."),
|
|
3871
|
-
systemPromptOverride: z
|
|
3872
|
-
.string()
|
|
3873
|
-
.min(1)
|
|
3874
|
-
.optional()
|
|
3875
|
-
.describe("Grok --system-prompt-override <PROMPT>: replace the agent's system prompt entirely. Distinct from Claude's --system-prompt / --append-system-prompt (Grok has only one override flag, not a pair)."),
|
|
3876
|
-
allow: z
|
|
3877
|
-
.array(z.string())
|
|
3878
|
-
.optional()
|
|
3879
|
-
.describe('Grok --allow <RULE>: permission allow rules. Each entry is emitted as its own --allow instance (per `grok --help`: "Repeat to add multiple rules").'),
|
|
3880
|
-
deny: z
|
|
3881
|
-
.array(z.string())
|
|
3882
|
-
.optional()
|
|
3883
|
-
.describe('Grok --deny <RULE>: permission deny rules. Each entry is emitted as its own --deny instance (per `grok --help`: "Repeat to add multiple rules").'),
|
|
3884
|
-
compactionMode: z
|
|
3885
|
-
.enum(["summary", "transcript", "segments"])
|
|
3886
|
-
.optional()
|
|
3887
|
-
.describe("Grok --compaction-mode: summary (default; no pointer) | transcript (points at the raw transcript) | segments (persists per-segment markdown to grep). Sets GROK_COMPACTION_MODE."),
|
|
3888
|
-
compactionDetail: z
|
|
3889
|
-
.enum(["none", "minimal", "balanced", "verbose"])
|
|
3890
|
-
.optional()
|
|
3891
|
-
.describe("Grok --compaction-detail: verbatim segment detail (none|minimal|balanced|verbose, default verbose). Only affects `--compaction-mode segments`. Sets GROK_COMPACTION_DETAIL."),
|
|
3892
|
-
agent: z
|
|
3893
|
-
.string()
|
|
3894
|
-
.min(1)
|
|
3895
|
-
.optional()
|
|
3896
|
-
.describe("Grok --agent <NAME>: agent name or definition file path."),
|
|
3897
|
-
bestOfN: MAX_TURNS_SCHEMA.optional().describe("Grok --best-of-n <N>: run the task N ways in parallel and pick the best (headless only)."),
|
|
3898
|
-
check: z
|
|
3899
|
-
.boolean()
|
|
3900
|
-
.optional()
|
|
3901
|
-
.describe("Grok --check: append a self-verification loop to the prompt (headless only)."),
|
|
3902
|
-
disableWebSearch: z
|
|
3903
|
-
.boolean()
|
|
3904
|
-
.optional()
|
|
3905
|
-
.describe("Grok --disable-web-search: disable web search and remote retrieval tools."),
|
|
3906
|
-
todoGate: z
|
|
3907
|
-
.boolean()
|
|
3908
|
-
.optional()
|
|
3909
|
-
.describe("Grok --todo-gate: enable runtime turn-end TodoGate for this session (session-scoped, not persisted)."),
|
|
3910
|
-
verbatim: z
|
|
3911
|
-
.boolean()
|
|
3912
|
-
.optional()
|
|
3913
|
-
.describe("Grok --verbatim: send the prompt exactly as given. Also skips gateway optimizePrompt when true."),
|
|
3914
4048
|
agents: z
|
|
3915
4049
|
.union([z.string().min(1), z.record(z.string(), z.record(z.string(), z.unknown()))])
|
|
3916
4050
|
.optional()
|
|
3917
4051
|
.describe("Grok --agents <JSON>: inline subagent definitions (JSON string or name → { description, prompt, … } map)."),
|
|
3918
|
-
promptFile: z
|
|
3919
|
-
.string()
|
|
3920
|
-
.min(1)
|
|
3921
|
-
.optional()
|
|
3922
|
-
.describe("Grok --prompt-file <PATH>: single-turn prompt loaded from a file."),
|
|
3923
4052
|
promptJson: z
|
|
3924
4053
|
.union([z.string(), z.array(z.unknown()), z.record(z.string(), z.unknown())])
|
|
3925
4054
|
.optional()
|
|
3926
4055
|
.describe("Grok --prompt-json <JSON>: single-turn prompt JSON blocks (string or serializable value)."),
|
|
3927
|
-
single: z
|
|
3928
|
-
.string()
|
|
3929
|
-
.min(1)
|
|
3930
|
-
.optional()
|
|
3931
|
-
.describe("Grok --single <PROMPT>: single-turn prompt (in addition to gateway -p)."),
|
|
3932
|
-
experimentalMemory: z
|
|
3933
|
-
.boolean()
|
|
3934
|
-
.optional()
|
|
3935
|
-
.describe("Grok --experimental-memory: enable cross-session memory."),
|
|
3936
|
-
noAltScreen: z
|
|
3937
|
-
.boolean()
|
|
3938
|
-
.optional()
|
|
3939
|
-
.describe("Grok --no-alt-screen: run inline without alt screen."),
|
|
3940
|
-
noMemory: z.boolean().optional().describe("Grok --no-memory: disable cross-session memory."),
|
|
3941
|
-
noPlan: z.boolean().optional().describe("Grok --no-plan: disable plan mode."),
|
|
3942
|
-
noSubagents: z
|
|
3943
|
-
.boolean()
|
|
3944
|
-
.optional()
|
|
3945
|
-
.describe("Grok --no-subagents: disable subagent spawning."),
|
|
3946
|
-
oauth: z.boolean().optional().describe("Grok --oauth: use OAuth during authentication."),
|
|
3947
|
-
restoreCode: z
|
|
3948
|
-
.boolean()
|
|
3949
|
-
.optional()
|
|
3950
|
-
.describe("Grok --restore-code: check out the original session commit when resuming."),
|
|
3951
|
-
leaderSocket: z
|
|
3952
|
-
.string()
|
|
3953
|
-
.min(1)
|
|
3954
|
-
.optional()
|
|
3955
|
-
.describe("Grok 0.2.32+ --leader-socket <PATH>: custom leader socket path (default ~/.grok/leader.sock). Targets an isolated leader process, e.g. a local/branch Grok build; name it ~/.grok/leader-*.sock to keep `grok leader list/kill` discovery working."),
|
|
3956
4056
|
nativeWorktree: z
|
|
3957
4057
|
.union([z.boolean(), z.string().min(1)])
|
|
3958
4058
|
.optional()
|
|
3959
4059
|
.describe("Grok -w/--worktree: native CLI worktree flag (`true` → bare `--worktree`, string → named). NOT gateway slice λ `worktree`."),
|
|
4060
|
+
workspace: WORKSPACE_ALIAS_SCHEMA.optional(),
|
|
3960
4061
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
3961
4062
|
}, {
|
|
3962
4063
|
title: "Grok request",
|
|
@@ -3964,7 +4065,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
3964
4065
|
destructiveHint: true,
|
|
3965
4066
|
idempotentHint: false,
|
|
3966
4067
|
openWorldHint: true,
|
|
3967
|
-
}, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, alwaysApprove, permissionMode, effort, reasoningEffort, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, maxTurns, workingDir, sandbox, rules, systemPromptOverride, allow, deny, compactionMode, compactionDetail, agent, bestOfN, check, disableWebSearch, todoGate, verbatim, agents, promptFile, promptJson, single, experimentalMemory, noAltScreen, noMemory, noPlan, noSubagents, oauth, restoreCode, leaderSocket, nativeWorktree, worktree, }) => {
|
|
4068
|
+
}, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, alwaysApprove, permissionMode, effort, reasoningEffort, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, maxTurns, workingDir, sandbox, rules, systemPromptOverride, allow, deny, compactionMode, compactionDetail, agent, bestOfN, check, disableWebSearch, todoGate, verbatim, agents, promptFile, promptJson, single, experimentalMemory, noAltScreen, noMemory, noPlan, noSubagents, oauth, restoreCode, leaderSocket, nativeWorktree, workspace, worktree, }) => {
|
|
3968
4069
|
return handleGrokRequest({ sessionManager, logger, runtime }, {
|
|
3969
4070
|
prompt,
|
|
3970
4071
|
promptParts,
|
|
@@ -4015,6 +4116,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4015
4116
|
restoreCode,
|
|
4016
4117
|
leaderSocket,
|
|
4017
4118
|
nativeWorktree,
|
|
4119
|
+
workspace,
|
|
4018
4120
|
worktree,
|
|
4019
4121
|
});
|
|
4020
4122
|
});
|
|
@@ -4097,6 +4199,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4097
4199
|
.array(z.string())
|
|
4098
4200
|
.optional()
|
|
4099
4201
|
.describe("Vibe --add-dir <DIR>: additional writable workspace directories. Each entry is emitted as its own --add-dir instance (Vibe states this flag may be specified multiple times)."),
|
|
4202
|
+
workspace: WORKSPACE_ALIAS_SCHEMA.optional(),
|
|
4100
4203
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
4101
4204
|
}, {
|
|
4102
4205
|
title: "Mistral Vibe request",
|
|
@@ -4104,7 +4207,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4104
4207
|
destructiveHint: true,
|
|
4105
4208
|
idempotentHint: false,
|
|
4106
4209
|
openWorldHint: true,
|
|
4107
|
-
}, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, permissionMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, trust, maxTurns, maxPrice, maxTokens, workingDir, addDir, worktree, }) => {
|
|
4210
|
+
}, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, permissionMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, trust, maxTurns, maxPrice, maxTokens, workingDir, addDir, workspace, worktree, }) => {
|
|
4108
4211
|
return handleMistralRequest({ sessionManager, logger, runtime }, {
|
|
4109
4212
|
prompt,
|
|
4110
4213
|
promptParts,
|
|
@@ -4130,6 +4233,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4130
4233
|
maxTokens,
|
|
4131
4234
|
workingDir,
|
|
4132
4235
|
addDir,
|
|
4236
|
+
workspace,
|
|
4133
4237
|
worktree,
|
|
4134
4238
|
});
|
|
4135
4239
|
});
|
|
@@ -4242,6 +4346,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4242
4346
|
.array(z.string())
|
|
4243
4347
|
.optional()
|
|
4244
4348
|
.describe('Claude --tools: restrict the available built-in tool set (distinct from allowedTools permission gating). Pass [""] to disable all tools.'),
|
|
4349
|
+
workspace: WORKSPACE_ALIAS_SCHEMA.optional(),
|
|
4245
4350
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
4246
4351
|
approvalStrategy: z
|
|
4247
4352
|
.enum(["legacy", "mcp_managed"])
|
|
@@ -4278,7 +4383,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4278
4383
|
destructiveHint: true,
|
|
4279
4384
|
idempotentHint: false,
|
|
4280
4385
|
openWorldHint: true,
|
|
4281
|
-
}, 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, worktree, approvalStrategy, approvalPolicy, mcpServers, strictMcpConfig, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, }) => {
|
|
4386
|
+
}, 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, }) => {
|
|
4282
4387
|
if (systemPrompt !== undefined && appendSystemPrompt !== undefined) {
|
|
4283
4388
|
return createErrorResponse("claude", 1, "", correlationId, new Error("systemPrompt and appendSystemPrompt are mutually exclusive; use one or the other (not both)."));
|
|
4284
4389
|
}
|
|
@@ -4349,7 +4454,14 @@ export function createGatewayServer(deps = {}) {
|
|
|
4349
4454
|
});
|
|
4350
4455
|
let worktreeResolution = {};
|
|
4351
4456
|
try {
|
|
4352
|
-
worktreeResolution = await
|
|
4457
|
+
worktreeResolution = await resolveWorkspaceAndWorktreeForRequest({
|
|
4458
|
+
provider: "claude",
|
|
4459
|
+
workspace,
|
|
4460
|
+
worktree,
|
|
4461
|
+
sessionId: effectiveSessionId,
|
|
4462
|
+
runtime,
|
|
4463
|
+
addDir,
|
|
4464
|
+
});
|
|
4353
4465
|
}
|
|
4354
4466
|
catch (err) {
|
|
4355
4467
|
return createErrorResponse("claude_request_async", 1, "", corrId, err);
|
|
@@ -4489,6 +4601,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4489
4601
|
.array(z.string())
|
|
4490
4602
|
.optional()
|
|
4491
4603
|
.describe("Codex --add-dir <DIR>: additional writable workspace directories (repeat per entry). New sessions only."),
|
|
4604
|
+
workspace: WORKSPACE_ALIAS_SCHEMA.optional(),
|
|
4492
4605
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
4493
4606
|
}, {
|
|
4494
4607
|
title: "Codex request (async job)",
|
|
@@ -4496,7 +4609,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4496
4609
|
destructiveHint: true,
|
|
4497
4610
|
idempotentHint: false,
|
|
4498
4611
|
openWorldHint: true,
|
|
4499
|
-
}, 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, worktree, }) => {
|
|
4612
|
+
}, 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, }) => {
|
|
4500
4613
|
return handleCodexRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
|
|
4501
4614
|
prompt,
|
|
4502
4615
|
promptParts,
|
|
@@ -4527,26 +4640,27 @@ export function createGatewayServer(deps = {}) {
|
|
|
4527
4640
|
ignoreRules,
|
|
4528
4641
|
workingDir,
|
|
4529
4642
|
addDir,
|
|
4643
|
+
workspace,
|
|
4530
4644
|
worktree,
|
|
4531
4645
|
});
|
|
4532
4646
|
});
|
|
4533
|
-
server.tool("gemini_request_async", "Start a Google
|
|
4647
|
+
server.tool("gemini_request_async", "Start a Google Antigravity CLI (`agy`) request as a durable background job through the Gemini-compatible gateway tool. Poll with llm_job_status, collect with llm_job_result.", {
|
|
4534
4648
|
prompt: z
|
|
4535
4649
|
.string()
|
|
4536
4650
|
.min(1, "Prompt cannot be empty")
|
|
4537
4651
|
.max(100000, "Prompt too long (max 100k chars)")
|
|
4538
4652
|
.optional()
|
|
4539
|
-
.describe("Prompt text for
|
|
4653
|
+
.describe("Prompt text for Antigravity CLI (mutually exclusive with promptParts)"),
|
|
4540
4654
|
promptParts: PromptPartsSchema.optional().describe("Cache-aware structured prompt: { system?, tools?, context?, task }. Mutually exclusive with prompt. Stable parts hash into cache_state for prefix-discipline tracking."),
|
|
4541
4655
|
model: z
|
|
4542
4656
|
.string()
|
|
4543
4657
|
.optional()
|
|
4544
|
-
.describe("Model name or alias (e.g. gemini-3-pro-preview, gemini-2.5-flash, pro, flash, latest)"),
|
|
4658
|
+
.describe("Model name or alias passed to agy --model (e.g. gemini-3-pro-preview, gemini-2.5-flash, pro, flash, latest)"),
|
|
4545
4659
|
sessionId: z
|
|
4546
4660
|
.string()
|
|
4547
4661
|
.optional()
|
|
4548
|
-
.describe("
|
|
4549
|
-
resumeLatest: z.boolean().default(false).describe("
|
|
4662
|
+
.describe("Antigravity conversation ID to resume (emits --conversation <id>)"),
|
|
4663
|
+
resumeLatest: z.boolean().default(false).describe("Continue the most recent conversation"),
|
|
4550
4664
|
createNewSession: z.boolean().default(false).describe("Force new session"),
|
|
4551
4665
|
approvalMode: z
|
|
4552
4666
|
.enum(GEMINI_APPROVAL_MODES)
|
|
@@ -4562,13 +4676,16 @@ export function createGatewayServer(deps = {}) {
|
|
|
4562
4676
|
.describe("Approval policy override"),
|
|
4563
4677
|
mcpServers: z
|
|
4564
4678
|
.array(MCP_SERVER_ENUM)
|
|
4565
|
-
.default([
|
|
4566
|
-
.describe("
|
|
4679
|
+
.default([])
|
|
4680
|
+
.describe("Unsupported for Antigravity CLI; non-empty values are rejected"),
|
|
4567
4681
|
allowedTools: z
|
|
4568
4682
|
.array(z.string())
|
|
4569
4683
|
.optional()
|
|
4570
|
-
.describe("
|
|
4571
|
-
includeDirs: z
|
|
4684
|
+
.describe("Unsupported for Antigravity CLI; non-empty values are rejected"),
|
|
4685
|
+
includeDirs: z
|
|
4686
|
+
.array(z.string())
|
|
4687
|
+
.optional()
|
|
4688
|
+
.describe("Additional workspace directories passed as --add-dir"),
|
|
4572
4689
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
4573
4690
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
4574
4691
|
idleTimeoutMs: z
|
|
@@ -4585,19 +4702,20 @@ export function createGatewayServer(deps = {}) {
|
|
|
4585
4702
|
outputFormat: z
|
|
4586
4703
|
.enum(["text", "json", "stream-json"])
|
|
4587
4704
|
.default("text")
|
|
4588
|
-
.describe("
|
|
4589
|
-
sandbox: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.sandbox.describe("Run
|
|
4590
|
-
policyFiles: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.policyFiles.describe("
|
|
4591
|
-
adminPolicyFiles: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.adminPolicyFiles.describe("
|
|
4592
|
-
attachments: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.attachments.describe("
|
|
4705
|
+
.describe("Antigravity CLI currently supports text output only through the gateway; json and stream-json are rejected."),
|
|
4706
|
+
sandbox: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.sandbox.describe("Run Antigravity in sandbox mode (--sandbox)"),
|
|
4707
|
+
policyFiles: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.policyFiles.describe("Unsupported for Antigravity CLI; non-empty values are rejected."),
|
|
4708
|
+
adminPolicyFiles: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.adminPolicyFiles.describe("Unsupported for Antigravity CLI; non-empty values are rejected."),
|
|
4709
|
+
attachments: GEMINI_HIGH_IMPACT_PARAMS_SCHEMA.shape.attachments.describe("Unsupported for Antigravity CLI; non-empty values are rejected."),
|
|
4593
4710
|
skipTrust: z
|
|
4594
4711
|
.boolean()
|
|
4595
4712
|
.default(false)
|
|
4596
|
-
.describe("
|
|
4713
|
+
.describe("Unsupported for Antigravity CLI; true is rejected."),
|
|
4597
4714
|
yolo: z
|
|
4598
4715
|
.boolean()
|
|
4599
4716
|
.optional()
|
|
4600
|
-
.describe("Emit `--
|
|
4717
|
+
.describe("Emit `--dangerously-skip-permissions` to auto-approve all actions. Routed through the same approval gate. Under mcp_managed the gate still decides."),
|
|
4718
|
+
workspace: WORKSPACE_ALIAS_SCHEMA.optional(),
|
|
4601
4719
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
4602
4720
|
}, {
|
|
4603
4721
|
title: "Gemini request (async job)",
|
|
@@ -4605,7 +4723,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4605
4723
|
destructiveHint: true,
|
|
4606
4724
|
idempotentHint: false,
|
|
4607
4725
|
openWorldHint: true,
|
|
4608
|
-
}, async ({ prompt, promptParts, model, sessionId, resumeLatest, createNewSession, approvalMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, includeDirs, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, outputFormat, sandbox, policyFiles, adminPolicyFiles, attachments, skipTrust, yolo, worktree, }) => {
|
|
4726
|
+
}, async ({ prompt, promptParts, model, sessionId, resumeLatest, createNewSession, approvalMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, includeDirs, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, outputFormat, sandbox, policyFiles, adminPolicyFiles, attachments, skipTrust, yolo, workspace, worktree, }) => {
|
|
4609
4727
|
return handleGeminiRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
|
|
4610
4728
|
prompt,
|
|
4611
4729
|
promptParts,
|
|
@@ -4630,6 +4748,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4630
4748
|
attachments,
|
|
4631
4749
|
skipTrust,
|
|
4632
4750
|
yolo,
|
|
4751
|
+
workspace,
|
|
4633
4752
|
worktree,
|
|
4634
4753
|
});
|
|
4635
4754
|
});
|
|
@@ -4809,6 +4928,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4809
4928
|
.union([z.boolean(), z.string().min(1)])
|
|
4810
4929
|
.optional()
|
|
4811
4930
|
.describe("Grok -w/--worktree: native CLI worktree flag (`true` → bare `--worktree`, string → named). NOT gateway slice λ `worktree`."),
|
|
4931
|
+
workspace: WORKSPACE_ALIAS_SCHEMA.optional(),
|
|
4812
4932
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
4813
4933
|
}, {
|
|
4814
4934
|
title: "Grok request (async job)",
|
|
@@ -4816,7 +4936,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4816
4936
|
destructiveHint: true,
|
|
4817
4937
|
idempotentHint: false,
|
|
4818
4938
|
openWorldHint: true,
|
|
4819
|
-
}, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, alwaysApprove, permissionMode, effort, reasoningEffort, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, maxTurns, workingDir, sandbox, rules, systemPromptOverride, allow, deny, compactionMode, compactionDetail, agent, bestOfN, check, disableWebSearch, todoGate, verbatim, agents, promptFile, promptJson, single, experimentalMemory, noAltScreen, noMemory, noPlan, noSubagents, oauth, restoreCode, leaderSocket, nativeWorktree, worktree, }) => {
|
|
4939
|
+
}, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, alwaysApprove, permissionMode, effort, reasoningEffort, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, maxTurns, workingDir, sandbox, rules, systemPromptOverride, allow, deny, compactionMode, compactionDetail, agent, bestOfN, check, disableWebSearch, todoGate, verbatim, agents, promptFile, promptJson, single, experimentalMemory, noAltScreen, noMemory, noPlan, noSubagents, oauth, restoreCode, leaderSocket, nativeWorktree, workspace, worktree, }) => {
|
|
4820
4940
|
return handleGrokRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
|
|
4821
4941
|
prompt,
|
|
4822
4942
|
promptParts,
|
|
@@ -4866,6 +4986,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4866
4986
|
restoreCode,
|
|
4867
4987
|
leaderSocket,
|
|
4868
4988
|
nativeWorktree,
|
|
4989
|
+
workspace,
|
|
4869
4990
|
worktree,
|
|
4870
4991
|
});
|
|
4871
4992
|
});
|
|
@@ -4947,6 +5068,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4947
5068
|
.array(z.string())
|
|
4948
5069
|
.optional()
|
|
4949
5070
|
.describe("Vibe --add-dir <DIR>: additional writable workspace directories. Each entry is emitted as its own --add-dir instance."),
|
|
5071
|
+
workspace: WORKSPACE_ALIAS_SCHEMA.optional(),
|
|
4950
5072
|
worktree: WORKTREE_SCHEMA.optional(),
|
|
4951
5073
|
}, {
|
|
4952
5074
|
title: "Mistral Vibe request (async job)",
|
|
@@ -4954,7 +5076,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4954
5076
|
destructiveHint: true,
|
|
4955
5077
|
idempotentHint: false,
|
|
4956
5078
|
openWorldHint: true,
|
|
4957
|
-
}, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, permissionMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, trust, maxTurns, maxPrice, maxTokens, workingDir, addDir, worktree, }) => {
|
|
5079
|
+
}, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, permissionMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, trust, maxTurns, maxPrice, maxTokens, workingDir, addDir, workspace, worktree, }) => {
|
|
4958
5080
|
return handleMistralRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
|
|
4959
5081
|
prompt,
|
|
4960
5082
|
promptParts,
|
|
@@ -4979,6 +5101,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4979
5101
|
maxTokens,
|
|
4980
5102
|
workingDir,
|
|
4981
5103
|
addDir,
|
|
5104
|
+
workspace,
|
|
4982
5105
|
worktree,
|
|
4983
5106
|
});
|
|
4984
5107
|
});
|
|
@@ -5845,6 +5968,210 @@ async function shutdown(signal) {
|
|
|
5845
5968
|
}
|
|
5846
5969
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
5847
5970
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
5971
|
+
function readMutableGatewayConfig(configPath = defaultGatewayConfigPath()) {
|
|
5972
|
+
if (!existsSync(configPath))
|
|
5973
|
+
return {};
|
|
5974
|
+
const require = createRequire(import.meta.url);
|
|
5975
|
+
const TOML = require("smol-toml");
|
|
5976
|
+
return TOML.parse(readFileSync(configPath, "utf8"));
|
|
5977
|
+
}
|
|
5978
|
+
function writeMutableGatewayConfig(data, configPath = defaultGatewayConfigPath()) {
|
|
5979
|
+
const require = createRequire(import.meta.url);
|
|
5980
|
+
const TOML = require("smol-toml");
|
|
5981
|
+
mkdirSync(dirname(configPath), { recursive: true, mode: 0o700 });
|
|
5982
|
+
writeFileSync(configPath, TOML.stringify(data), { mode: 0o600 });
|
|
5983
|
+
chmodSync(configPath, 0o600);
|
|
5984
|
+
}
|
|
5985
|
+
function ensureOAuthTable(config) {
|
|
5986
|
+
config.http ??= {};
|
|
5987
|
+
config.http.oauth ??= {};
|
|
5988
|
+
const oauth = config.http.oauth;
|
|
5989
|
+
oauth.enabled ??= true;
|
|
5990
|
+
oauth.issuer ??= "auto";
|
|
5991
|
+
oauth.require_pkce ??= true;
|
|
5992
|
+
oauth.registration_policy ??= "static_clients";
|
|
5993
|
+
oauth.allow_public_clients ??= false;
|
|
5994
|
+
oauth.token_ttl_seconds ??= 3600;
|
|
5995
|
+
oauth.clients ??= [];
|
|
5996
|
+
return oauth;
|
|
5997
|
+
}
|
|
5998
|
+
function argValue(args, name) {
|
|
5999
|
+
const idx = args.indexOf(name);
|
|
6000
|
+
return idx >= 0 ? args[idx + 1] : undefined;
|
|
6001
|
+
}
|
|
6002
|
+
function requireArg(args, name) {
|
|
6003
|
+
const value = argValue(args, name);
|
|
6004
|
+
if (!value)
|
|
6005
|
+
throw new Error(`Missing ${name}`);
|
|
6006
|
+
return value;
|
|
6007
|
+
}
|
|
6008
|
+
function localBaseUrlForPrint() {
|
|
6009
|
+
const publicUrl = process.env.LLM_GATEWAY_PUBLIC_URL;
|
|
6010
|
+
if (publicUrl) {
|
|
6011
|
+
try {
|
|
6012
|
+
return new URL(publicUrl).origin;
|
|
6013
|
+
}
|
|
6014
|
+
catch {
|
|
6015
|
+
}
|
|
6016
|
+
}
|
|
6017
|
+
return `http://${process.env.LLM_GATEWAY_HTTP_HOST ?? "127.0.0.1"}:${process.env.LLM_GATEWAY_HTTP_PORT ?? "3333"}`;
|
|
6018
|
+
}
|
|
6019
|
+
function printJsonLine(value) {
|
|
6020
|
+
process.stdout.write(JSON.stringify(value, null, 2) + "\n");
|
|
6021
|
+
}
|
|
6022
|
+
function runOAuthCommand(args) {
|
|
6023
|
+
const [scope, action] = args;
|
|
6024
|
+
const config = readMutableGatewayConfig();
|
|
6025
|
+
const oauth = ensureOAuthTable(config);
|
|
6026
|
+
if (scope === "client") {
|
|
6027
|
+
const clients = (Array.isArray(oauth.clients) ? oauth.clients : []);
|
|
6028
|
+
oauth.clients = clients;
|
|
6029
|
+
if (action === "add") {
|
|
6030
|
+
const clientId = args[2];
|
|
6031
|
+
if (!clientId)
|
|
6032
|
+
throw new Error("Usage: llm-cli-gateway oauth client add <client-id> --redirect-uri <uri> [--print-once]");
|
|
6033
|
+
const redirectUri = requireArg(args, "--redirect-uri");
|
|
6034
|
+
const secret = generateSecret();
|
|
6035
|
+
clients.push({
|
|
6036
|
+
client_id: clientId,
|
|
6037
|
+
client_secret_hash: hashSecret(secret),
|
|
6038
|
+
allowed_redirect_uris: [redirectUri],
|
|
6039
|
+
scopes: ["mcp"],
|
|
6040
|
+
});
|
|
6041
|
+
writeMutableGatewayConfig(config);
|
|
6042
|
+
printJsonLine({
|
|
6043
|
+
ok: true,
|
|
6044
|
+
client_id: clientId,
|
|
6045
|
+
...(args.includes("--print-once") ? { client_secret: secret } : {}),
|
|
6046
|
+
oauth: {
|
|
6047
|
+
issuer: localBaseUrlForPrint(),
|
|
6048
|
+
authorization_url: `${localBaseUrlForPrint()}/oauth/authorize`,
|
|
6049
|
+
token_url: `${localBaseUrlForPrint()}/oauth/token`,
|
|
6050
|
+
},
|
|
6051
|
+
note: args.includes("--print-once")
|
|
6052
|
+
? "client_secret is shown once; it is stored only as a hash."
|
|
6053
|
+
: "client secret generated and stored only as a hash; rerun rotate --print-once if needed.",
|
|
6054
|
+
});
|
|
6055
|
+
return;
|
|
6056
|
+
}
|
|
6057
|
+
if (action === "list") {
|
|
6058
|
+
printJsonLine({
|
|
6059
|
+
ok: true,
|
|
6060
|
+
clients: clients.map(client => ({
|
|
6061
|
+
client_id: client.client_id,
|
|
6062
|
+
redirect_uris: client.allowed_redirect_uris ?? [],
|
|
6063
|
+
secret_configured: Boolean(client.client_secret_hash),
|
|
6064
|
+
})),
|
|
6065
|
+
});
|
|
6066
|
+
return;
|
|
6067
|
+
}
|
|
6068
|
+
if (action === "rotate") {
|
|
6069
|
+
const clientId = args[2];
|
|
6070
|
+
const client = clients.find(candidate => candidate.client_id === clientId);
|
|
6071
|
+
if (!client)
|
|
6072
|
+
throw new Error(`Unknown OAuth client ${clientId}`);
|
|
6073
|
+
const secret = generateSecret();
|
|
6074
|
+
client.client_secret_hash = hashSecret(secret);
|
|
6075
|
+
writeMutableGatewayConfig(config);
|
|
6076
|
+
printJsonLine({
|
|
6077
|
+
ok: true,
|
|
6078
|
+
client_id: clientId,
|
|
6079
|
+
...(args.includes("--print-once") ? { client_secret: secret } : {}),
|
|
6080
|
+
note: "Future OAuth exchanges use the rotated secret; already-issued opaque access tokens expire by token TTL or server restart.",
|
|
6081
|
+
});
|
|
6082
|
+
return;
|
|
6083
|
+
}
|
|
6084
|
+
if (action === "revoke") {
|
|
6085
|
+
const clientId = args[2];
|
|
6086
|
+
oauth.clients = clients.filter(client => client.client_id !== clientId);
|
|
6087
|
+
writeMutableGatewayConfig(config);
|
|
6088
|
+
printJsonLine({
|
|
6089
|
+
ok: true,
|
|
6090
|
+
client_id: clientId,
|
|
6091
|
+
note: "Future OAuth exchanges are revoked; already-issued opaque access tokens expire by token TTL or server restart.",
|
|
6092
|
+
});
|
|
6093
|
+
return;
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
if (scope === "shared-secret") {
|
|
6097
|
+
if (action === "set" || action === "rotate") {
|
|
6098
|
+
const secret = generateSecret();
|
|
6099
|
+
oauth.registration_policy = "shared_secret";
|
|
6100
|
+
oauth.shared_secret = {
|
|
6101
|
+
enabled: true,
|
|
6102
|
+
secret_hash: hashSecret(secret),
|
|
6103
|
+
prompt_label: "Gateway access code",
|
|
6104
|
+
};
|
|
6105
|
+
writeMutableGatewayConfig(config);
|
|
6106
|
+
printJsonLine({
|
|
6107
|
+
ok: true,
|
|
6108
|
+
shared_secret_enabled: true,
|
|
6109
|
+
...(args.includes("--print-once") ? { shared_secret: secret } : {}),
|
|
6110
|
+
note: args.includes("--print-once")
|
|
6111
|
+
? "shared_secret is shown once; it is stored only as a hash."
|
|
6112
|
+
: "shared secret generated and stored only as a hash.",
|
|
6113
|
+
});
|
|
6114
|
+
return;
|
|
6115
|
+
}
|
|
6116
|
+
if (action === "disable") {
|
|
6117
|
+
oauth.shared_secret = { enabled: false, prompt_label: "Gateway access code" };
|
|
6118
|
+
if (oauth.registration_policy === "shared_secret")
|
|
6119
|
+
oauth.registration_policy = "static_clients";
|
|
6120
|
+
writeMutableGatewayConfig(config);
|
|
6121
|
+
printJsonLine({ ok: true, shared_secret_enabled: false });
|
|
6122
|
+
return;
|
|
6123
|
+
}
|
|
6124
|
+
}
|
|
6125
|
+
throw new Error("Usage: llm-cli-gateway oauth client|shared-secret ...");
|
|
6126
|
+
}
|
|
6127
|
+
function runWorkspaceCommand(args) {
|
|
6128
|
+
const [action] = args;
|
|
6129
|
+
if (action === "list") {
|
|
6130
|
+
const registry = loadWorkspaceRegistry(logger);
|
|
6131
|
+
printJsonLine({
|
|
6132
|
+
ok: true,
|
|
6133
|
+
default: registry.defaultAlias,
|
|
6134
|
+
workspaces: registry.repos.map(describeWorkspace),
|
|
6135
|
+
allowed_roots: registry.allowedRoots.map(root => ({
|
|
6136
|
+
alias: root.alias,
|
|
6137
|
+
path: root.path,
|
|
6138
|
+
allow_create_directories: root.allowCreateDirectories,
|
|
6139
|
+
allow_init_git_repos: root.allowInitGitRepos,
|
|
6140
|
+
})),
|
|
6141
|
+
});
|
|
6142
|
+
return;
|
|
6143
|
+
}
|
|
6144
|
+
if (action === "create") {
|
|
6145
|
+
const alias = args[1];
|
|
6146
|
+
if (!alias)
|
|
6147
|
+
throw new Error("Usage: llm-cli-gateway workspace create <alias> --root <root> --slug <slug> --kind folder|git [--default]");
|
|
6148
|
+
const repo = createWorkspace({
|
|
6149
|
+
alias,
|
|
6150
|
+
rootAlias: requireArg(args, "--root"),
|
|
6151
|
+
slug: requireArg(args, "--slug"),
|
|
6152
|
+
kind: (argValue(args, "--kind") ?? "git"),
|
|
6153
|
+
setDefault: args.includes("--default"),
|
|
6154
|
+
logger,
|
|
6155
|
+
});
|
|
6156
|
+
printJsonLine({ ok: true, workspace: describeWorkspace(repo) });
|
|
6157
|
+
return;
|
|
6158
|
+
}
|
|
6159
|
+
if (action === "add") {
|
|
6160
|
+
const alias = args[1];
|
|
6161
|
+
const repoPath = args[2];
|
|
6162
|
+
if (!alias || !repoPath)
|
|
6163
|
+
throw new Error("Usage: llm-cli-gateway workspace add <alias> <path> [--default]");
|
|
6164
|
+
const repo = registerExistingWorkspace({
|
|
6165
|
+
alias,
|
|
6166
|
+
repoPath,
|
|
6167
|
+
setDefault: args.includes("--default"),
|
|
6168
|
+
logger,
|
|
6169
|
+
});
|
|
6170
|
+
printJsonLine({ ok: true, workspace: describeWorkspace(repo) });
|
|
6171
|
+
return;
|
|
6172
|
+
}
|
|
6173
|
+
throw new Error("Usage: llm-cli-gateway workspace list|add|create ...");
|
|
6174
|
+
}
|
|
5848
6175
|
async function main() {
|
|
5849
6176
|
startWindowsBootstrapperSelfHeal();
|
|
5850
6177
|
const args = process.argv.slice(2);
|
|
@@ -5858,6 +6185,8 @@ async function main() {
|
|
|
5858
6185
|
"",
|
|
5859
6186
|
"Usage:",
|
|
5860
6187
|
" llm-cli-gateway [doctor --json|contracts --json|--transport=http|--version]",
|
|
6188
|
+
" llm-cli-gateway oauth client add <id> --redirect-uri <uri> [--print-once]",
|
|
6189
|
+
" llm-cli-gateway workspace list|add|create",
|
|
5861
6190
|
"",
|
|
5862
6191
|
"Doctor:",
|
|
5863
6192
|
" doctor --json # environment, providers, declared contracts",
|
|
@@ -5879,6 +6208,14 @@ async function main() {
|
|
|
5879
6208
|
process.stderr.write("Only doctor --json is supported in this layer.\n");
|
|
5880
6209
|
process.exit(2);
|
|
5881
6210
|
}
|
|
6211
|
+
if (args[0] === "oauth") {
|
|
6212
|
+
runOAuthCommand(args.slice(1));
|
|
6213
|
+
return;
|
|
6214
|
+
}
|
|
6215
|
+
if (args[0] === "workspace") {
|
|
6216
|
+
runWorkspaceCommand(args.slice(1));
|
|
6217
|
+
return;
|
|
6218
|
+
}
|
|
5882
6219
|
if (args[0] === "contracts") {
|
|
5883
6220
|
if (args.includes("--json")) {
|
|
5884
6221
|
const cliArg = args.find(arg => arg.startsWith("--cli="))?.split("=")[1];
|