llm-cli-gateway 1.17.1 → 1.17.3
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 +28 -0
- package/README.md +16 -19
- package/dist/cache-stats.d.ts +47 -0
- package/dist/cache-stats.js +85 -2
- package/dist/config.js +1 -1
- package/dist/doctor.d.ts +22 -1
- package/dist/doctor.js +35 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +123 -39
- package/dist/process-monitor.d.ts +1 -2
- package/dist/process-monitor.js +7 -7
- package/dist/prompt-parts.d.ts +1 -1
- package/dist/prompt-parts.js +1 -1
- package/dist/provider-login-guidance.js +5 -5
- package/dist/provider-status.js +0 -4
- package/dist/request-helpers.d.ts +28 -26
- package/dist/request-helpers.js +50 -43
- package/dist/session-manager.js +1 -1
- package/dist/stream-json-parser.js +30 -15
- package/dist/upstream-contracts.d.ts +25 -1
- package/dist/upstream-contracts.js +213 -18
- package/dist/validation-tools.js +1 -1
- package/package.json +11 -8
- package/setup/status.schema.json +31 -0
- package/socket.yml +9 -3
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { randomUUID } from "crypto";
|
|
|
5
5
|
import { existsSync, readFileSync, readdirSync, renameSync, unlinkSync } from "fs";
|
|
6
6
|
import { dirname, join } from "path";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
|
-
import { z } from "zod";
|
|
8
|
+
import { z } from "zod/v3";
|
|
9
9
|
import { executeCli, killAllProcessGroups } from "./executor.js";
|
|
10
10
|
import { parseStreamJson } from "./stream-json-parser.js";
|
|
11
11
|
import { parseCodexJsonStream } from "./codex-json-parser.js";
|
|
@@ -28,7 +28,7 @@ import { buildClaudeMcpConfig, CLAUDE_MCP_SERVER_NAMES, } from "./claude-mcp-con
|
|
|
28
28
|
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, prepareGeminiHighImpactFlags, prependGeminiAttachments, resolveGeminiSessionPlan, GEMINI_HIGH_IMPACT_PARAMS_SCHEMA, } from "./request-helpers.js";
|
|
29
29
|
import { createFlightRecorder } from "./flight-recorder.js";
|
|
30
30
|
import { resolvePromptInput, PromptPartsSchema, assembleClaudeCacheBlocks, } from "./prompt-parts.js";
|
|
31
|
-
import { computeSessionCacheStats, computeTtlRemaining } from "./cache-stats.js";
|
|
31
|
+
import { computeSessionCacheStats, computeTtlRemaining, readPersistedRequest, PERSISTED_REQUEST_DEFAULT_MAX_CHARS, } from "./cache-stats.js";
|
|
32
32
|
import { getCliVersions, runCliUpgrade } from "./cli-updater.js";
|
|
33
33
|
import { startHttpGateway } from "./http-transport.js";
|
|
34
34
|
import { printDoctorJson } from "./doctor.js";
|
|
@@ -161,12 +161,13 @@ Tools: claude_request, codex_request, gemini_request, grok_request, mistral_requ
|
|
|
161
161
|
Validation: validate_with_models, second_opinion, compare_answers, red_team_review, consensus_check, ask_model, synthesize_validation
|
|
162
162
|
Jobs: llm_job_status, llm_job_result, llm_job_cancel
|
|
163
163
|
Sessions: session_create, session_list, session_set_active, session_get, session_delete, session_clear_all
|
|
164
|
-
Other: list_models, cli_versions, upstream_contracts, cli_upgrade, approval_list, llm_process_health
|
|
164
|
+
Other: list_models, cli_versions, upstream_contracts (use --probe-installed after CLI upgrades to detect drift), cli_upgrade, approval_list, llm_process_health, llm_request_result (read back any persisted request — sync or async — by correlationId)
|
|
165
165
|
|
|
166
166
|
Key behaviors:
|
|
167
167
|
- Sync auto-defers at ${SYNC_DEADLINE_MS}ms. Poll deferred jobs via llm_job_status/llm_job_result.
|
|
168
168
|
- Sessions: Claude --continue, Gemini --resume, 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.
|
|
169
169
|
- Approval gates: opt-in via approvalStrategy:"mcp_managed".
|
|
170
|
+
- Upstream drift detection: After upgrading any provider CLI (especially grok), use the upstream_contracts tool with probeInstalled: true (or the CLI command "llm-cli-gateway contracts --json --probe-installed"). This is the primary reliable way to detect when an installed binary has gained or lost flags compared to the gateway's declared contract. The probe is safe and read-only.
|
|
170
171
|
- Idle timeout kills stuck processes (default 10min, configurable via idleTimeoutMs).
|
|
171
172
|
|
|
172
173
|
Skills (full docs via MCP resources):
|
|
@@ -1338,7 +1339,7 @@ export function prepareCodexRequest(params, runtime = resolveGatewayServerRuntim
|
|
|
1338
1339
|
}
|
|
1339
1340
|
}
|
|
1340
1341
|
// Resume mode: codex exec resume <SESSION_ID|--last> [flags] PROMPT
|
|
1341
|
-
// Note: `codex exec resume` does NOT accept
|
|
1342
|
+
// Note: `codex exec resume` does NOT accept sandbox policy flags; the original
|
|
1342
1343
|
// session's approval policy is inherited. We silently drop fullAuto on resume.
|
|
1343
1344
|
let sessionPlan;
|
|
1344
1345
|
try {
|
|
@@ -1363,16 +1364,16 @@ export function prepareCodexRequest(params, runtime = resolveGatewayServerRuntim
|
|
|
1363
1364
|
// Codex sandbox / approval: resolve modern flags + legacy fullAuto shorthand.
|
|
1364
1365
|
// `codex exec resume` rejects all of these (the original session's policy is
|
|
1365
1366
|
// inherited), so we only emit them when starting a NEW session.
|
|
1367
|
+
const sandboxFlags = resolveCodexSandboxFlags({
|
|
1368
|
+
sandboxMode: params.sandboxMode,
|
|
1369
|
+
askForApproval: params.askForApproval,
|
|
1370
|
+
fullAuto: params.fullAuto,
|
|
1371
|
+
useLegacyFullAutoFlag: params.useLegacyFullAutoFlag,
|
|
1372
|
+
});
|
|
1373
|
+
if (sandboxFlags.warning) {
|
|
1374
|
+
runtime.logger.warn(`[${corrId}] ${sandboxFlags.warning}`);
|
|
1375
|
+
}
|
|
1366
1376
|
if (sessionPlan.mode === "new") {
|
|
1367
|
-
const sandboxFlags = resolveCodexSandboxFlags({
|
|
1368
|
-
sandboxMode: params.sandboxMode,
|
|
1369
|
-
askForApproval: params.askForApproval,
|
|
1370
|
-
fullAuto: params.fullAuto,
|
|
1371
|
-
useLegacyFullAutoFlag: params.useLegacyFullAutoFlag,
|
|
1372
|
-
});
|
|
1373
|
-
if (sandboxFlags.warning) {
|
|
1374
|
-
runtime.logger.warn(`[${corrId}] ${sandboxFlags.warning}`);
|
|
1375
|
-
}
|
|
1376
1377
|
args.push(...sandboxFlags.args);
|
|
1377
1378
|
}
|
|
1378
1379
|
if (params.dangerouslyBypassApprovalsAndSandbox) {
|
|
@@ -1386,19 +1387,18 @@ export function prepareCodexRequest(params, runtime = resolveGatewayServerRuntim
|
|
|
1386
1387
|
args.push("--json");
|
|
1387
1388
|
}
|
|
1388
1389
|
args.push("--skip-git-repo-check");
|
|
1389
|
-
// U26: High-impact feature flags. `--search` is
|
|
1390
|
-
// `codex exec
|
|
1391
|
-
//
|
|
1392
|
-
//
|
|
1393
|
-
//
|
|
1394
|
-
// and are emitted in both branches.
|
|
1390
|
+
// U26: High-impact feature flags. `--search` is retained as a compatibility
|
|
1391
|
+
// input but current `codex exec` no longer accepts it, so the helper warns
|
|
1392
|
+
// and emits no argv. `--profile` is accepted for new sessions only. The other
|
|
1393
|
+
// flags here are accepted on resume per `codex exec resume --help` and are
|
|
1394
|
+
// emitted in both branches.
|
|
1395
1395
|
let highImpactCleanup;
|
|
1396
1396
|
if (sessionPlan.mode === "new") {
|
|
1397
1397
|
// Phase 4 slice ζ: emit working-dir and add-dir on new sessions only.
|
|
1398
1398
|
// Both flags are listed in CODEX_RESUME_FILTERED_FLAGS — resume inherits
|
|
1399
1399
|
// the original session's cwd and writable-dir policy, so emitting them
|
|
1400
1400
|
// on resume would be silently stripped (wasteful + misleading on argv
|
|
1401
|
-
// logs). Gating here mirrors `--search` / `--sandbox
|
|
1401
|
+
// logs). Gating here mirrors `--search` / `--sandbox`.
|
|
1402
1402
|
if (params.workingDir) {
|
|
1403
1403
|
args.push("-C", params.workingDir);
|
|
1404
1404
|
}
|
|
@@ -1420,13 +1420,20 @@ export function prepareCodexRequest(params, runtime = resolveGatewayServerRuntim
|
|
|
1420
1420
|
if (high.missingImagePath) {
|
|
1421
1421
|
return createErrorResponse(params.operation, 1, "", corrId, new Error(`images: path does not exist: ${high.missingImagePath}`));
|
|
1422
1422
|
}
|
|
1423
|
+
if (high.warning) {
|
|
1424
|
+
runtime.logger.warn(`[${corrId}] ${high.warning}`);
|
|
1425
|
+
}
|
|
1423
1426
|
args.push(...high.args);
|
|
1424
1427
|
highImpactCleanup = high.cleanup;
|
|
1425
1428
|
}
|
|
1426
1429
|
else {
|
|
1430
|
+
if (params.profile) {
|
|
1431
|
+
runtime.logger.warn(`[${corrId}] profile is ignored on Codex resume because current codex exec resume does not accept --profile.`);
|
|
1432
|
+
}
|
|
1427
1433
|
const high = prepareCodexHighImpactFlags({
|
|
1428
1434
|
outputSchema: params.outputSchema,
|
|
1429
|
-
|
|
1435
|
+
search: params.search,
|
|
1436
|
+
profile: undefined,
|
|
1430
1437
|
configOverrides: params.configOverrides,
|
|
1431
1438
|
ephemeral: params.ephemeral,
|
|
1432
1439
|
images: params.images,
|
|
@@ -1436,6 +1443,9 @@ export function prepareCodexRequest(params, runtime = resolveGatewayServerRuntim
|
|
|
1436
1443
|
if (high.missingImagePath) {
|
|
1437
1444
|
return createErrorResponse(params.operation, 1, "", corrId, new Error(`images: path does not exist: ${high.missingImagePath}`));
|
|
1438
1445
|
}
|
|
1446
|
+
if (high.warning) {
|
|
1447
|
+
runtime.logger.warn(`[${corrId}] ${high.warning}`);
|
|
1448
|
+
}
|
|
1439
1449
|
args.push(...high.args);
|
|
1440
1450
|
highImpactCleanup = high.cleanup;
|
|
1441
1451
|
}
|
|
@@ -2861,7 +2871,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
2861
2871
|
.optional()
|
|
2862
2872
|
.describe("Claude --agent: dispatch to a named single sub-agent."),
|
|
2863
2873
|
agents: z
|
|
2864
|
-
.record(z.record(z.unknown()))
|
|
2874
|
+
.record(z.string(), z.record(z.string(), z.unknown()))
|
|
2865
2875
|
.optional()
|
|
2866
2876
|
.describe("Claude --agents: inline JSON map of agent name → { description, prompt, tools?, model? }."),
|
|
2867
2877
|
forkSession: z
|
|
@@ -2902,7 +2912,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
2902
2912
|
.optional()
|
|
2903
2913
|
.describe("Claude --fallback-model: model name to auto-fallback to when the default model is overloaded (effective only with --print, which the gateway always uses)."),
|
|
2904
2914
|
jsonSchema: z
|
|
2905
|
-
.union([z.string(), z.record(z.unknown())])
|
|
2915
|
+
.union([z.string(), z.record(z.string(), z.unknown())])
|
|
2906
2916
|
.optional()
|
|
2907
2917
|
.describe("Claude --json-schema: JSON Schema literal (NOT a path) constraining structured output. Object values are JSON.stringify-d; string values are passed verbatim. Use with outputFormat='json'."),
|
|
2908
2918
|
// Phase 4 slice ζ — Claude additional-workspace-dirs parity
|
|
@@ -3170,7 +3180,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
3170
3180
|
fullAuto: z
|
|
3171
3181
|
.boolean()
|
|
3172
3182
|
.default(false)
|
|
3173
|
-
.describe("DEPRECATED: prefer `sandboxMode
|
|
3183
|
+
.describe("DEPRECATED: prefer `sandboxMode`. Expands to `--sandbox workspace-write`; current Codex no longer accepts approval-policy flags."),
|
|
3174
3184
|
sandboxMode: z
|
|
3175
3185
|
.enum(CODEX_SANDBOX_MODES)
|
|
3176
3186
|
.optional()
|
|
@@ -3178,11 +3188,11 @@ export function createGatewayServer(deps = {}) {
|
|
|
3178
3188
|
askForApproval: z
|
|
3179
3189
|
.enum(CODEX_ASK_FOR_APPROVAL_MODES)
|
|
3180
3190
|
.optional()
|
|
3181
|
-
.describe("Codex --ask-for-approval
|
|
3191
|
+
.describe("DEPRECATED compatibility input: accepted but ignored because current Codex no longer accepts --ask-for-approval."),
|
|
3182
3192
|
useLegacyFullAutoFlag: z
|
|
3183
3193
|
.boolean()
|
|
3184
3194
|
.default(false)
|
|
3185
|
-
.describe("
|
|
3195
|
+
.describe("DEPRECATED compatibility input: accepted but ignored because current Codex no longer accepts --full-auto."),
|
|
3186
3196
|
dangerouslyBypassApprovalsAndSandbox: z
|
|
3187
3197
|
.boolean()
|
|
3188
3198
|
.default(false)
|
|
@@ -3231,10 +3241,13 @@ export function createGatewayServer(deps = {}) {
|
|
|
3231
3241
|
.describe("Codex output format. `json` emits --json (JSONL events) so token usage and cost are parsed and reported in the flight recorder. `text` is the default."),
|
|
3232
3242
|
// U26: high-impact feature flags. All optional.
|
|
3233
3243
|
outputSchema: z
|
|
3234
|
-
.union([z.string(), z.record(z.unknown())])
|
|
3244
|
+
.union([z.string(), z.record(z.string(), z.unknown())])
|
|
3235
3245
|
.optional()
|
|
3236
3246
|
.describe("Codex --output-schema. Pass a path (string) or an inline JSON Schema object; object is materialised to a 0o600 temp file under os.tmpdir() and deleted after the run."),
|
|
3237
|
-
search: z
|
|
3247
|
+
search: z
|
|
3248
|
+
.boolean()
|
|
3249
|
+
.optional()
|
|
3250
|
+
.describe("DEPRECATED compatibility input: accepted but ignored because current Codex exec no longer accepts --search."),
|
|
3238
3251
|
profile: z
|
|
3239
3252
|
.string()
|
|
3240
3253
|
.optional()
|
|
@@ -3445,7 +3458,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
3445
3458
|
askForApproval: z
|
|
3446
3459
|
.enum(CODEX_ASK_FOR_APPROVAL_MODES)
|
|
3447
3460
|
.optional()
|
|
3448
|
-
.describe("Codex --ask-for-approval
|
|
3461
|
+
.describe("DEPRECATED compatibility input: accepted but ignored because current Codex no longer accepts --ask-for-approval."),
|
|
3449
3462
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
3450
3463
|
idleTimeoutMs: z
|
|
3451
3464
|
.number()
|
|
@@ -3922,7 +3935,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
3922
3935
|
.optional()
|
|
3923
3936
|
.describe("Claude --agent: dispatch to a named single sub-agent."),
|
|
3924
3937
|
agents: z
|
|
3925
|
-
.record(z.record(z.unknown()))
|
|
3938
|
+
.record(z.string(), z.record(z.string(), z.unknown()))
|
|
3926
3939
|
.optional()
|
|
3927
3940
|
.describe("Claude --agents: inline JSON map of agent name → { description, prompt, tools?, model? }."),
|
|
3928
3941
|
forkSession: z
|
|
@@ -3963,7 +3976,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
3963
3976
|
.optional()
|
|
3964
3977
|
.describe("Claude --fallback-model: model name to auto-fallback to when the default model is overloaded (effective only with --print, which the gateway always uses)."),
|
|
3965
3978
|
jsonSchema: z
|
|
3966
|
-
.union([z.string(), z.record(z.unknown())])
|
|
3979
|
+
.union([z.string(), z.record(z.string(), z.unknown())])
|
|
3967
3980
|
.optional()
|
|
3968
3981
|
.describe("Claude --json-schema: JSON Schema literal (NOT a path) constraining structured output. Object values are JSON.stringify-d; string values are passed verbatim. Use with outputFormat='json'."),
|
|
3969
3982
|
// Phase 4 slice ζ — Claude additional-workspace-dirs parity
|
|
@@ -4137,7 +4150,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4137
4150
|
fullAuto: z
|
|
4138
4151
|
.boolean()
|
|
4139
4152
|
.default(false)
|
|
4140
|
-
.describe("DEPRECATED: prefer `sandboxMode
|
|
4153
|
+
.describe("DEPRECATED: prefer `sandboxMode`. Expands to `--sandbox workspace-write`; current Codex no longer accepts approval-policy flags."),
|
|
4141
4154
|
sandboxMode: z
|
|
4142
4155
|
.enum(CODEX_SANDBOX_MODES)
|
|
4143
4156
|
.optional()
|
|
@@ -4145,11 +4158,11 @@ export function createGatewayServer(deps = {}) {
|
|
|
4145
4158
|
askForApproval: z
|
|
4146
4159
|
.enum(CODEX_ASK_FOR_APPROVAL_MODES)
|
|
4147
4160
|
.optional()
|
|
4148
|
-
.describe("Codex --ask-for-approval
|
|
4161
|
+
.describe("DEPRECATED compatibility input: accepted but ignored because current Codex no longer accepts --ask-for-approval."),
|
|
4149
4162
|
useLegacyFullAutoFlag: z
|
|
4150
4163
|
.boolean()
|
|
4151
4164
|
.default(false)
|
|
4152
|
-
.describe("
|
|
4165
|
+
.describe("DEPRECATED compatibility input: accepted but ignored because current Codex no longer accepts --full-auto."),
|
|
4153
4166
|
dangerouslyBypassApprovalsAndSandbox: z
|
|
4154
4167
|
.boolean()
|
|
4155
4168
|
.default(false)
|
|
@@ -4195,10 +4208,13 @@ export function createGatewayServer(deps = {}) {
|
|
|
4195
4208
|
.describe("Codex output format. `json` emits --json (JSONL events) for token usage extraction."),
|
|
4196
4209
|
// U26: high-impact feature flags. All optional.
|
|
4197
4210
|
outputSchema: z
|
|
4198
|
-
.union([z.string(), z.record(z.unknown())])
|
|
4211
|
+
.union([z.string(), z.record(z.string(), z.unknown())])
|
|
4199
4212
|
.optional()
|
|
4200
4213
|
.describe("Codex --output-schema. Pass a path (string) or an inline JSON Schema object."),
|
|
4201
|
-
search: z
|
|
4214
|
+
search: z
|
|
4215
|
+
.boolean()
|
|
4216
|
+
.optional()
|
|
4217
|
+
.describe("DEPRECATED compatibility input: accepted but ignored because current Codex exec no longer accepts --search."),
|
|
4202
4218
|
profile: z.string().optional().describe("Codex --profile <name>."),
|
|
4203
4219
|
configOverrides: CODEX_CONFIG_OVERRIDES_SCHEMA.describe("Codex -c key=value overrides. Keys: /^[a-zA-Z0-9._]+$/. Values: no CR/LF."),
|
|
4204
4220
|
ephemeral: z.boolean().optional().describe("Codex --ephemeral."),
|
|
@@ -4712,6 +4728,59 @@ export function createGatewayServer(deps = {}) {
|
|
|
4712
4728
|
};
|
|
4713
4729
|
});
|
|
4714
4730
|
} // end if (asyncJobsEnabled)
|
|
4731
|
+
// Read back any persisted request (sync OR async) by its correlation id.
|
|
4732
|
+
// Registered unconditionally — it reads the flight recorder, which is
|
|
4733
|
+
// independent of async-job persistence. Every sync/async response echoes
|
|
4734
|
+
// its id in `structuredContent.correlationId`; pass that id here to recover
|
|
4735
|
+
// the persisted prompt/response after the inline result is gone. With flight
|
|
4736
|
+
// recording disabled (LLM_GATEWAY_LOGS_DB=none → NoopFlightRecorder) the
|
|
4737
|
+
// query yields no rows and this returns the "not found" shape.
|
|
4738
|
+
server.tool("llm_request_result", {
|
|
4739
|
+
correlationId: z
|
|
4740
|
+
.string()
|
|
4741
|
+
.min(1)
|
|
4742
|
+
.describe("Correlation id from a prior request's structuredContent.correlationId (sync or async)"),
|
|
4743
|
+
maxChars: z
|
|
4744
|
+
.number()
|
|
4745
|
+
.int()
|
|
4746
|
+
.min(1000)
|
|
4747
|
+
.max(2000000)
|
|
4748
|
+
.default(PERSISTED_REQUEST_DEFAULT_MAX_CHARS)
|
|
4749
|
+
.describe("Max chars of the persisted response to return"),
|
|
4750
|
+
includePrompt: z
|
|
4751
|
+
.boolean()
|
|
4752
|
+
.default(false)
|
|
4753
|
+
.describe("Include the full persisted prompt text in the result"),
|
|
4754
|
+
}, async ({ correlationId, maxChars, includePrompt }) => {
|
|
4755
|
+
const record = readPersistedRequest(flightRecorder, correlationId, {
|
|
4756
|
+
maxChars,
|
|
4757
|
+
includePrompt,
|
|
4758
|
+
});
|
|
4759
|
+
if (!record) {
|
|
4760
|
+
return {
|
|
4761
|
+
content: [
|
|
4762
|
+
{
|
|
4763
|
+
type: "text",
|
|
4764
|
+
text: JSON.stringify({
|
|
4765
|
+
success: false,
|
|
4766
|
+
error: "No persisted request found for this correlation id",
|
|
4767
|
+
correlationId,
|
|
4768
|
+
hint: "The id may be wrong, the row may have aged out of the flight recorder, or flight recording is disabled (LLM_GATEWAY_LOGS_DB=none).",
|
|
4769
|
+
}, null, 2),
|
|
4770
|
+
},
|
|
4771
|
+
],
|
|
4772
|
+
isError: true,
|
|
4773
|
+
};
|
|
4774
|
+
}
|
|
4775
|
+
return {
|
|
4776
|
+
content: [
|
|
4777
|
+
{
|
|
4778
|
+
type: "text",
|
|
4779
|
+
text: JSON.stringify({ success: true, request: record }, null, 2),
|
|
4780
|
+
},
|
|
4781
|
+
],
|
|
4782
|
+
};
|
|
4783
|
+
});
|
|
4715
4784
|
server.tool("llm_process_health", {}, async () => {
|
|
4716
4785
|
const health = asyncJobManager.getJobHealth();
|
|
4717
4786
|
const persistenceBlock = {
|
|
@@ -4793,7 +4862,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4793
4862
|
probeInstalled: z
|
|
4794
4863
|
.boolean()
|
|
4795
4864
|
.default(false)
|
|
4796
|
-
.describe("When true, run local --help probes and compare advertised flags"),
|
|
4865
|
+
.describe("When true, run local --help probes and compare advertised flags against the declared contract. Strongly recommended after any provider CLI upgrade to detect drift."),
|
|
4797
4866
|
}, async ({ cli, probeInstalled }) => {
|
|
4798
4867
|
const report = buildUpstreamContractReport({ cli, probeInstalled });
|
|
4799
4868
|
return { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
|
|
@@ -5224,12 +5293,21 @@ async function main() {
|
|
|
5224
5293
|
"Usage:",
|
|
5225
5294
|
" llm-cli-gateway [doctor --json|contracts --json|--transport=http|--version]",
|
|
5226
5295
|
"",
|
|
5296
|
+
"Doctor:",
|
|
5297
|
+
" doctor --json # environment, providers, declared contracts",
|
|
5298
|
+
" doctor --json --probe-upstream # + expensive installed --help probe for drift",
|
|
5299
|
+
"",
|
|
5300
|
+
"After upgrading provider CLIs (grok/claude/etc), use --probe-upstream or",
|
|
5301
|
+
" llm-cli-gateway contracts --json --probe-installed",
|
|
5302
|
+
"to detect when installed binaries have drifted from the gateway contracts.",
|
|
5303
|
+
"",
|
|
5227
5304
|
].join("\n"));
|
|
5228
5305
|
return;
|
|
5229
5306
|
}
|
|
5230
5307
|
if (args[0] === "doctor") {
|
|
5231
5308
|
if (args.includes("--json")) {
|
|
5232
|
-
|
|
5309
|
+
const probeUpstream = args.includes("--probe-upstream") || args.includes("--probe-installed");
|
|
5310
|
+
printDoctorJson({ probeUpstream });
|
|
5233
5311
|
return;
|
|
5234
5312
|
}
|
|
5235
5313
|
process.stderr.write("Only doctor --json is supported in this layer.\n");
|
|
@@ -5249,7 +5327,13 @@ async function main() {
|
|
|
5249
5327
|
process.stdout.write(JSON.stringify(buildUpstreamContractReport({ cli, probeInstalled }), null, 2) + "\n");
|
|
5250
5328
|
return;
|
|
5251
5329
|
}
|
|
5252
|
-
process.stderr.write(
|
|
5330
|
+
process.stderr.write([
|
|
5331
|
+
"Usage: llm-cli-gateway contracts --json [--cli=claude|codex|gemini|grok|mistral] [--probe-installed]",
|
|
5332
|
+
"",
|
|
5333
|
+
"After upgrading any provider CLI, use --probe-installed to detect drift between",
|
|
5334
|
+
"the installed binary's advertised flags and the gateway's declared contract.",
|
|
5335
|
+
"Example: llm-cli-gateway contracts --json --probe-installed --cli=grok",
|
|
5336
|
+
].join("\n") + "\n");
|
|
5253
5337
|
process.exit(2);
|
|
5254
5338
|
}
|
|
5255
5339
|
const transportArg = args.find(arg => arg.startsWith("--transport="));
|
|
@@ -36,9 +36,8 @@ export declare function parseProcStat(content: string): {
|
|
|
36
36
|
*/
|
|
37
37
|
export declare function parseVmRss(content: string): number | null;
|
|
38
38
|
export declare class ProcessMonitor {
|
|
39
|
-
private logger;
|
|
40
39
|
private prevSamples;
|
|
41
|
-
constructor(
|
|
40
|
+
constructor(_logger?: Logger);
|
|
42
41
|
/** Clear all cached CPU samples */
|
|
43
42
|
reset(): void;
|
|
44
43
|
sampleProcess(pid: number): ProcessHealth;
|
package/dist/process-monitor.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* Gracefully degrades on non-Linux platforms.
|
|
4
4
|
*/
|
|
5
5
|
import { readFileSync } from "fs";
|
|
6
|
-
import { noopLogger } from "./logger.js";
|
|
7
6
|
/**
|
|
8
7
|
* Parse /proc/[pid]/stat safely.
|
|
9
8
|
* The `comm` field (field 2) is in parentheses and may contain spaces,
|
|
@@ -18,10 +17,14 @@ export function parseProcStat(content) {
|
|
|
18
17
|
// fields[0] = state, fields[11] = utime (14-3), fields[12] = stime (15-3)
|
|
19
18
|
if (fields.length < 13)
|
|
20
19
|
return null;
|
|
20
|
+
const utime = parseInt(fields[11], 10);
|
|
21
|
+
const stime = parseInt(fields[12], 10);
|
|
22
|
+
if (!Number.isFinite(utime) || !Number.isFinite(stime))
|
|
23
|
+
return null;
|
|
21
24
|
return {
|
|
22
25
|
state: fields[0],
|
|
23
|
-
utime
|
|
24
|
-
stime
|
|
26
|
+
utime,
|
|
27
|
+
stime,
|
|
25
28
|
};
|
|
26
29
|
}
|
|
27
30
|
/**
|
|
@@ -48,12 +51,9 @@ function getTotalCpuJiffies() {
|
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
export class ProcessMonitor {
|
|
51
|
-
logger;
|
|
52
54
|
// Previous samples for CPU delta calculation
|
|
53
55
|
prevSamples = new Map();
|
|
54
|
-
constructor(
|
|
55
|
-
this.logger = logger;
|
|
56
|
-
}
|
|
56
|
+
constructor(_logger) { }
|
|
57
57
|
/** Clear all cached CPU samples */
|
|
58
58
|
reset() {
|
|
59
59
|
this.prevSamples.clear();
|
package/dist/prompt-parts.d.ts
CHANGED
package/dist/prompt-parts.js
CHANGED
|
@@ -55,16 +55,16 @@ const GUIDANCE = {
|
|
|
55
55
|
},
|
|
56
56
|
grok: {
|
|
57
57
|
provider: "grok",
|
|
58
|
-
displayName: "Grok
|
|
58
|
+
displayName: "Grok Build",
|
|
59
59
|
install: {
|
|
60
|
-
summary: "Install Grok
|
|
61
|
-
commands: ["
|
|
62
|
-
documentationUrl: "https://docs.x.ai/build/
|
|
60
|
+
summary: "Install Grok Build using xAI's current official installer or managed update flow.",
|
|
61
|
+
commands: ["curl -fsSL https://x.ai/cli/install.sh | bash"],
|
|
62
|
+
documentationUrl: "https://docs.x.ai/build/overview",
|
|
63
63
|
},
|
|
64
64
|
login: {
|
|
65
65
|
summary: "Sign in through Grok's official OAuth or device-code flow.",
|
|
66
66
|
commands: ["grok login --oauth", "grok login --device-auth"],
|
|
67
|
-
credentialHandling: "Do not paste xAI API keys, OAuth tokens, or Grok auth files into the gateway or a remote chat.",
|
|
67
|
+
credentialHandling: "For headless environments, set XAI_API_KEY in the local shell. Do not paste xAI API keys, OAuth tokens, or Grok auth files into the gateway or a remote chat.",
|
|
68
68
|
},
|
|
69
69
|
verification: {
|
|
70
70
|
command: "grok inspect --json",
|
package/dist/provider-status.js
CHANGED
|
@@ -182,10 +182,6 @@ export function geminiAuthStatus(env = process.env, home = homedir()) {
|
|
|
182
182
|
const status = oauth || geminiApiKey || googleApiKey || vertexAi ? "present" : "not_found";
|
|
183
183
|
return { status, methods };
|
|
184
184
|
}
|
|
185
|
-
/** Back-compat shim retained for callers that only need the binary store status. */
|
|
186
|
-
function geminiCredentialStoreStatus() {
|
|
187
|
-
return geminiAuthStatus().status;
|
|
188
|
-
}
|
|
189
185
|
function grokCredentialStoreStatus() {
|
|
190
186
|
const home = homedir();
|
|
191
187
|
const candidates = [join(home, ".grok", "auth.json"), join(home, ".config", "grok", "auth.json")];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
1
|
+
import { z } from "zod/v3";
|
|
2
2
|
/** Prefix for gateway-generated session IDs. Enforces provenance structurally. */
|
|
3
3
|
export declare const GATEWAY_SESSION_PREFIX = "gw-";
|
|
4
4
|
export interface SessionResumeResult {
|
|
@@ -38,8 +38,8 @@ export declare function resolveSessionResumeArgs(opts: {
|
|
|
38
38
|
* - "resume-by-id" → `codex exec resume [...resume-safe flags] <SESSION_ID> PROMPT`
|
|
39
39
|
* - "resume-latest" → `codex exec resume --last [...resume-safe flags] PROMPT`
|
|
40
40
|
*
|
|
41
|
-
* `codex exec resume` rejects
|
|
42
|
-
* policy is inherited. Callers MUST filter
|
|
41
|
+
* `codex exec resume` rejects sandbox/working-directory policy flags; the original session's approval
|
|
42
|
+
* policy is inherited. Callers MUST filter those flags out of the flag set
|
|
43
43
|
* when mode is one of the resume forms (see `prepareCodexRequest`).
|
|
44
44
|
*
|
|
45
45
|
* `sessionId` MUST be a real Codex session UUID (as recorded under
|
|
@@ -198,41 +198,40 @@ export type GeminiApprovalMode = (typeof GEMINI_APPROVAL_MODES)[number];
|
|
|
198
198
|
export declare const CODEX_SANDBOX_MODES: readonly ["read-only", "workspace-write", "danger-full-access"];
|
|
199
199
|
export type CodexSandboxMode = (typeof CODEX_SANDBOX_MODES)[number];
|
|
200
200
|
/**
|
|
201
|
-
* Codex approval modes
|
|
201
|
+
* Deprecated Codex approval modes. Current Codex no longer exposes an
|
|
202
|
+
* `--ask-for-approval` flag; the MCP input is temporarily retained so older
|
|
203
|
+
* callers do not fail schema validation, but it emits no CLI argv.
|
|
202
204
|
*/
|
|
203
205
|
export declare const CODEX_ASK_FOR_APPROVAL_MODES: readonly ["untrusted", "on-request", "never"];
|
|
204
206
|
export type CodexAskForApproval = (typeof CODEX_ASK_FOR_APPROVAL_MODES)[number];
|
|
205
207
|
export interface CodexSandboxFlagsInput {
|
|
206
208
|
/** Modern: explicit sandbox mode. */
|
|
207
209
|
sandboxMode?: CodexSandboxMode;
|
|
208
|
-
/**
|
|
210
|
+
/** Deprecated compatibility input; current Codex exposes no approval-policy flag. */
|
|
209
211
|
askForApproval?: CodexAskForApproval;
|
|
210
|
-
/** Legacy: shorthand for sandbox=workspace-write
|
|
212
|
+
/** Legacy: shorthand for sandbox=workspace-write. */
|
|
211
213
|
fullAuto?: boolean;
|
|
212
214
|
/**
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* Mistral GA.
|
|
215
|
+
* Deprecated compatibility input. Current Codex rejects `--full-auto`, so
|
|
216
|
+
* this no longer changes argv emission.
|
|
216
217
|
*/
|
|
217
218
|
useLegacyFullAutoFlag?: boolean;
|
|
218
219
|
}
|
|
219
220
|
export interface CodexSandboxFlagsResult {
|
|
220
221
|
args: string[];
|
|
221
|
-
/** Set when
|
|
222
|
+
/** Set when deprecated/no-op compatibility inputs are supplied. */
|
|
222
223
|
warning?: string;
|
|
223
224
|
}
|
|
224
225
|
/**
|
|
225
|
-
* Resolve Codex
|
|
226
|
-
*
|
|
226
|
+
* Resolve current Codex sandbox args from the modern params + legacy
|
|
227
|
+
* `fullAuto` shorthand. Current Codex exposes `--sandbox`, but no longer
|
|
228
|
+
* exposes `--ask-for-approval` or `--full-auto`.
|
|
227
229
|
*
|
|
228
230
|
* Precedence:
|
|
229
|
-
* 1.
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
* and the explicit values win.
|
|
234
|
-
* 3. Else if `fullAuto: true`, expand to
|
|
235
|
-
* `--sandbox workspace-write --ask-for-approval never`.
|
|
231
|
+
* 1. Explicit `sandboxMode` emits `--sandbox <mode>`.
|
|
232
|
+
* 2. Else if `fullAuto: true`, expand to `--sandbox workspace-write`.
|
|
233
|
+
* 3. Deprecated `askForApproval` and `useLegacyFullAutoFlag` emit no argv
|
|
234
|
+
* and return warnings for callers to surface/log.
|
|
236
235
|
* 4. Else emit nothing.
|
|
237
236
|
*/
|
|
238
237
|
export declare function resolveCodexSandboxFlags(input: CodexSandboxFlagsInput): CodexSandboxFlagsResult;
|
|
@@ -240,19 +239,20 @@ export declare function resolveCodexSandboxFlags(input: CodexSandboxFlagsInput):
|
|
|
240
239
|
* Flags that `codex exec resume` rejects (the original session's policy is
|
|
241
240
|
* inherited). Callers must drop these when building resume argv.
|
|
242
241
|
*
|
|
243
|
-
* Verified against `codex exec resume --help` (codex-cli 0.
|
|
244
|
-
* `--
|
|
245
|
-
* `--
|
|
246
|
-
*
|
|
247
|
-
*
|
|
242
|
+
* Verified against `codex exec resume --help` (codex-cli 0.135.0):
|
|
243
|
+
* `--sandbox`, `--add-dir`, `-C`, `--cd`, `--profile`, and `--search` are rejected.
|
|
244
|
+
* Deprecated `--full-auto` / `--ask-for-approval` are kept here defensively so
|
|
245
|
+
* legacy pre-filtered segments are stripped instead of reaching spawn.
|
|
246
|
+
* `--output-schema` and `-c key=value` ARE accepted on resume and therefore are
|
|
247
|
+
* NOT in this filter (Phase 4 slice α restored the previously-silent drop of those two).
|
|
248
248
|
*/
|
|
249
249
|
export declare const CODEX_RESUME_FILTERED_FLAGS: ReadonlySet<string>;
|
|
250
250
|
/**
|
|
251
251
|
* Strip resume-incompatible flag/value pairs from a Codex argv segment.
|
|
252
252
|
*
|
|
253
253
|
* Bare flags (`--full-auto`, `--search`) drop without consuming a value.
|
|
254
|
-
* Value-taking flags (`--sandbox`, `--ask-for-approval`, `--add-dir`, `-C`,
|
|
255
|
-
* `--
|
|
254
|
+
* Value-taking flags (`--sandbox`, `--ask-for-approval`, `--add-dir`, `-C`, `--cd`,
|
|
255
|
+
* `--profile`) drop together with their immediately-following value.
|
|
256
256
|
*/
|
|
257
257
|
export declare function filterCodexResumeFlags(args: string[]): string[];
|
|
258
258
|
/**
|
|
@@ -492,6 +492,8 @@ export interface CodexHighImpactFlagsResult {
|
|
|
492
492
|
cleanup: () => void;
|
|
493
493
|
/** First missing image path, if any. When set, the caller should bail before spawning. */
|
|
494
494
|
missingImagePath: string | null;
|
|
495
|
+
/** Set when deprecated/no-op compatibility inputs are supplied. */
|
|
496
|
+
warning?: string;
|
|
495
497
|
}
|
|
496
498
|
/**
|
|
497
499
|
* Build the U26 argv segment AND any required side-effect handles.
|