llm-cli-gateway 2.7.0 → 2.8.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 +37 -0
- package/README.md +5 -1
- package/dist/acp/client.d.ts +78 -0
- package/dist/acp/client.js +201 -0
- package/dist/acp/errors.d.ts +63 -0
- package/dist/acp/errors.js +139 -0
- package/dist/acp/json-rpc-stdio.d.ts +71 -0
- package/dist/acp/json-rpc-stdio.js +375 -0
- package/dist/acp/process-manager.d.ts +66 -0
- package/dist/acp/process-manager.js +364 -0
- package/dist/acp/provider-registry.d.ts +24 -0
- package/dist/acp/provider-registry.js +82 -0
- package/dist/acp/types.d.ts +557 -0
- package/dist/acp/types.js +335 -0
- package/dist/async-job-manager.d.ts +2 -0
- package/dist/async-job-manager.js +45 -16
- package/dist/cache-stats.js +17 -10
- package/dist/codex-json-parser.d.ts +3 -0
- package/dist/codex-json-parser.js +17 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.js +119 -0
- package/dist/http-transport.js +2 -1
- package/dist/index.js +18 -15
- package/dist/pricing.d.ts +1 -1
- package/dist/pricing.js +67 -2
- package/dist/provider-tool-capabilities.d.ts +38 -0
- package/dist/provider-tool-capabilities.js +142 -0
- package/dist/request-context.d.ts +1 -0
- package/dist/request-helpers.d.ts +4 -4
- package/dist/upstream-contracts.d.ts +27 -0
- package/dist/upstream-contracts.js +131 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/config.js
CHANGED
|
@@ -348,6 +348,125 @@ export function isXaiProviderEnabled(config, env = process.env) {
|
|
|
348
348
|
return false;
|
|
349
349
|
return typeof env[keyEnv] === "string" && env[keyEnv].trim().length > 0;
|
|
350
350
|
}
|
|
351
|
+
export const ACP_TRANSPORTS = ["cli", "acp"];
|
|
352
|
+
export const DEFAULT_ACP_PROCESS_IDLE_TIMEOUT_MS = 600000;
|
|
353
|
+
export const DEFAULT_ACP_INITIALIZE_TIMEOUT_MS = 10000;
|
|
354
|
+
export const DEFAULT_ACP_SESSION_NEW_TIMEOUT_MS = 10000;
|
|
355
|
+
export const DEFAULT_ACP_PROMPT_TIMEOUT_MS = 600000;
|
|
356
|
+
const SHELL_METACHARACTERS = /[\s|&;<>(){}$`"'\\*?[\]~#!]/;
|
|
357
|
+
function isSafeExecutable(value) {
|
|
358
|
+
if (value.length === 0)
|
|
359
|
+
return false;
|
|
360
|
+
return !SHELL_METACHARACTERS.test(value);
|
|
361
|
+
}
|
|
362
|
+
const SafeExecutableSchema = z
|
|
363
|
+
.string()
|
|
364
|
+
.min(1)
|
|
365
|
+
.refine(isSafeExecutable, {
|
|
366
|
+
message: "ACP provider command must be a bare executable name or path with no shell metacharacters " +
|
|
367
|
+
"(no spaces, quotes, pipes, redirects, globs, or command substitution); pass arguments via 'args'",
|
|
368
|
+
});
|
|
369
|
+
const SafeArgSchema = z.string();
|
|
370
|
+
const AcpProviderSchema = z
|
|
371
|
+
.object({
|
|
372
|
+
enabled: z.boolean().default(false),
|
|
373
|
+
command: SafeExecutableSchema,
|
|
374
|
+
args: z.array(SafeArgSchema).default([]),
|
|
375
|
+
runtime_enabled: z.boolean().default(false),
|
|
376
|
+
isolated_leader_socket: z.boolean().default(false),
|
|
377
|
+
})
|
|
378
|
+
.strict();
|
|
379
|
+
const AcpConfigSchema = z
|
|
380
|
+
.object({
|
|
381
|
+
enabled: z.boolean().default(false),
|
|
382
|
+
default_transport: z.enum(ACP_TRANSPORTS).default("cli"),
|
|
383
|
+
smoke_on_startup: z.boolean().default(false),
|
|
384
|
+
process_idle_timeout_ms: z
|
|
385
|
+
.number()
|
|
386
|
+
.int()
|
|
387
|
+
.positive()
|
|
388
|
+
.default(DEFAULT_ACP_PROCESS_IDLE_TIMEOUT_MS),
|
|
389
|
+
initialize_timeout_ms: z.number().int().positive().default(DEFAULT_ACP_INITIALIZE_TIMEOUT_MS),
|
|
390
|
+
session_new_timeout_ms: z.number().int().positive().default(DEFAULT_ACP_SESSION_NEW_TIMEOUT_MS),
|
|
391
|
+
prompt_timeout_ms: z.number().int().positive().default(DEFAULT_ACP_PROMPT_TIMEOUT_MS),
|
|
392
|
+
allow_write_host_services: z.boolean().default(false),
|
|
393
|
+
allow_terminal_host_services: z.boolean().default(false),
|
|
394
|
+
fallback_to_cli_when_unhealthy: z.boolean().default(true),
|
|
395
|
+
providers: z.record(z.string(), AcpProviderSchema).default({}),
|
|
396
|
+
})
|
|
397
|
+
.strict();
|
|
398
|
+
function defaultAcpConfig(sourcePath) {
|
|
399
|
+
return {
|
|
400
|
+
enabled: false,
|
|
401
|
+
defaultTransport: "cli",
|
|
402
|
+
smokeOnStartup: false,
|
|
403
|
+
processIdleTimeoutMs: DEFAULT_ACP_PROCESS_IDLE_TIMEOUT_MS,
|
|
404
|
+
initializeTimeoutMs: DEFAULT_ACP_INITIALIZE_TIMEOUT_MS,
|
|
405
|
+
sessionNewTimeoutMs: DEFAULT_ACP_SESSION_NEW_TIMEOUT_MS,
|
|
406
|
+
promptTimeoutMs: DEFAULT_ACP_PROMPT_TIMEOUT_MS,
|
|
407
|
+
allowWriteHostServices: false,
|
|
408
|
+
allowTerminalHostServices: false,
|
|
409
|
+
fallbackToCliWhenUnhealthy: true,
|
|
410
|
+
providers: {},
|
|
411
|
+
sources: { configFile: sourcePath },
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
function readAcpFile(configPath, logger) {
|
|
415
|
+
if (!existsSync(configPath)) {
|
|
416
|
+
return { raw: undefined, sourcePath: null };
|
|
417
|
+
}
|
|
418
|
+
try {
|
|
419
|
+
const require = createRequire(import.meta.url);
|
|
420
|
+
const TOML = require("smol-toml");
|
|
421
|
+
const text = readFileSync(configPath, "utf-8");
|
|
422
|
+
const parsed = TOML.parse(text);
|
|
423
|
+
return { raw: parsed?.acp, sourcePath: configPath };
|
|
424
|
+
}
|
|
425
|
+
catch (err) {
|
|
426
|
+
logger.error(`Failed to parse gateway config at ${configPath}; using acp defaults`, err);
|
|
427
|
+
return { raw: undefined, sourcePath: null };
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
export function loadAcpConfig(logger = noopLogger) {
|
|
431
|
+
const configPath = defaultGatewayConfigPath();
|
|
432
|
+
const { raw, sourcePath } = readAcpFile(configPath, logger);
|
|
433
|
+
if (raw === undefined) {
|
|
434
|
+
return defaultAcpConfig(sourcePath);
|
|
435
|
+
}
|
|
436
|
+
let parsed;
|
|
437
|
+
try {
|
|
438
|
+
parsed = AcpConfigSchema.parse(raw);
|
|
439
|
+
}
|
|
440
|
+
catch (err) {
|
|
441
|
+
throw new Error(`Invalid [acp] config: ${err instanceof Error ? err.message : String(err)}`, {
|
|
442
|
+
cause: err,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
const providers = {};
|
|
446
|
+
for (const [name, p] of Object.entries(parsed.providers)) {
|
|
447
|
+
providers[name] = {
|
|
448
|
+
enabled: p.enabled,
|
|
449
|
+
command: p.command,
|
|
450
|
+
args: p.args,
|
|
451
|
+
runtimeEnabled: p.runtime_enabled,
|
|
452
|
+
isolatedLeaderSocket: p.isolated_leader_socket,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
return {
|
|
456
|
+
enabled: parsed.enabled,
|
|
457
|
+
defaultTransport: parsed.default_transport,
|
|
458
|
+
smokeOnStartup: parsed.smoke_on_startup,
|
|
459
|
+
processIdleTimeoutMs: parsed.process_idle_timeout_ms,
|
|
460
|
+
initializeTimeoutMs: parsed.initialize_timeout_ms,
|
|
461
|
+
sessionNewTimeoutMs: parsed.session_new_timeout_ms,
|
|
462
|
+
promptTimeoutMs: parsed.prompt_timeout_ms,
|
|
463
|
+
allowWriteHostServices: parsed.allow_write_host_services,
|
|
464
|
+
allowTerminalHostServices: parsed.allow_terminal_host_services,
|
|
465
|
+
fallbackToCliWhenUnhealthy: parsed.fallback_to_cli_when_unhealthy,
|
|
466
|
+
providers,
|
|
467
|
+
sources: { configFile: sourcePath },
|
|
468
|
+
};
|
|
469
|
+
}
|
|
351
470
|
const OAuthRegistrationPolicySchema = z.enum(["static_clients", "shared_secret", "open_dev"]);
|
|
352
471
|
const OAuthClientSchema = z
|
|
353
472
|
.object({
|
package/dist/http-transport.js
CHANGED
|
@@ -155,7 +155,7 @@ export async function startHttpGateway(options) {
|
|
|
155
155
|
jsonError(res, 404, "Not found");
|
|
156
156
|
return;
|
|
157
157
|
}
|
|
158
|
-
let requestContext = { authScopes: [] };
|
|
158
|
+
let requestContext = { authScopes: [], transport: "http" };
|
|
159
159
|
if (!noAuthPath) {
|
|
160
160
|
const auth = authorizeBearerRequest(req, token);
|
|
161
161
|
if (!auth.ok) {
|
|
@@ -163,6 +163,7 @@ export async function startHttpGateway(options) {
|
|
|
163
163
|
return;
|
|
164
164
|
}
|
|
165
165
|
requestContext = {
|
|
166
|
+
transport: "http",
|
|
166
167
|
authKind: auth.kind,
|
|
167
168
|
authScopes: auth.scopes ?? [],
|
|
168
169
|
authClientId: auth.clientId,
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { fileURLToPath } from "url";
|
|
|
9
9
|
import { z } from "zod/v3";
|
|
10
10
|
import { executeCli, killAllProcessGroups, providerCommandName } from "./executor.js";
|
|
11
11
|
import { parseStreamJson } from "./stream-json-parser.js";
|
|
12
|
-
import { parseCodexJsonStream } from "./codex-json-parser.js";
|
|
12
|
+
import { parseCodexJsonStream, codexDisplayText, codexFrResponse } from "./codex-json-parser.js";
|
|
13
13
|
import { parseGeminiJson, parseGeminiStreamJson } from "./gemini-json-parser.js";
|
|
14
14
|
import { parseVibeMetaJson } from "./mistral-meta-json-parser.js";
|
|
15
15
|
import { homedir } from "os";
|
|
@@ -489,13 +489,10 @@ async function resolveWorkspaceAndWorktreeForRequest(args) {
|
|
|
489
489
|
else if (isGatewayAppDirCwd()) {
|
|
490
490
|
throw new Error("No workspace selected. Configure [workspaces].default or pass a registered workspace alias.");
|
|
491
491
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
(args.workingDir || (args.addDir?.length ?? 0) > 0) &&
|
|
497
|
-
!args.runtime.workspaces.allowUnregisteredWorkingDir) {
|
|
498
|
-
throw new Error("workingDir/addDir require a registered workspace alias unless [workspaces].allow_unregistered_working_dir is explicitly enabled.");
|
|
492
|
+
const requestContext = getRequestContext();
|
|
493
|
+
const isRemoteTransport = requestContext?.transport === "http" || requestContext?.authKind === "oauth";
|
|
494
|
+
if (!workspace && isRemoteTransport) {
|
|
495
|
+
throw new Error("Remote HTTP provider requests require a registered workspace alias, session workspace, or [workspaces].default.");
|
|
499
496
|
}
|
|
500
497
|
if (workspace) {
|
|
501
498
|
if (args.workingDir) {
|
|
@@ -720,7 +717,7 @@ export function extractUsageAndCost(cli, output, outputFormat, ctx) {
|
|
|
720
717
|
costUsd: parsed.costUsd ?? undefined,
|
|
721
718
|
};
|
|
722
719
|
}
|
|
723
|
-
if (cli === "codex"
|
|
720
|
+
if (cli === "codex") {
|
|
724
721
|
const parsed = parseCodexJsonStream(output);
|
|
725
722
|
if (!parsed.usage) {
|
|
726
723
|
return {};
|
|
@@ -1420,9 +1417,7 @@ export function prepareCodexRequest(params, runtime = resolveGatewayServerRuntim
|
|
|
1420
1417
|
if (params.dangerouslyBypassApprovalsAndSandbox) {
|
|
1421
1418
|
args.push("--dangerously-bypass-approvals-and-sandbox");
|
|
1422
1419
|
}
|
|
1423
|
-
|
|
1424
|
-
args.push("--json");
|
|
1425
|
-
}
|
|
1420
|
+
args.push("--json");
|
|
1426
1421
|
args.push("--skip-git-repo-check");
|
|
1427
1422
|
let highImpactCleanup;
|
|
1428
1423
|
if (sessionPlan.mode === "new") {
|
|
@@ -1830,6 +1825,9 @@ export function buildMistralRetryPrep(params, recoveryModel) {
|
|
|
1830
1825
|
}
|
|
1831
1826
|
function buildCliResponse(cli, stdout, optimizeResponse, corrId, sessionId, prep, durationMs, resumable, outputFormat, warnings) {
|
|
1832
1827
|
let finalStdout = stdout;
|
|
1828
|
+
if (cli === "codex" && outputFormat !== "json") {
|
|
1829
|
+
finalStdout = codexDisplayText(stdout);
|
|
1830
|
+
}
|
|
1833
1831
|
if (optimizeResponse && outputFormat !== "json") {
|
|
1834
1832
|
const optimized = optimizeResponseText(finalStdout);
|
|
1835
1833
|
logOptimizationTokens("response", corrId, finalStdout, optimized);
|
|
@@ -3619,7 +3617,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
3619
3617
|
outputFormat: z
|
|
3620
3618
|
.enum(["text", "json"])
|
|
3621
3619
|
.default("text")
|
|
3622
|
-
.describe("Codex output format.
|
|
3620
|
+
.describe("Codex caller-facing output format. Token/cache usage is recorded in the flight recorder regardless. `text` (default) returns the plain reply; `json` returns the raw `--json` JSONL event stream."),
|
|
3623
3621
|
outputSchema: z
|
|
3624
3622
|
.union([z.string(), z.record(z.string(), z.unknown())])
|
|
3625
3623
|
.optional()
|
|
@@ -3779,7 +3777,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
3779
3777
|
logger.info(`[${corrId}] codex_request completed successfully in ${durationMs}ms`);
|
|
3780
3778
|
const codexUsage = extractUsageAndCost("codex", stdout, outputFormat);
|
|
3781
3779
|
safeFlightComplete(corrId, {
|
|
3782
|
-
response: stdout,
|
|
3780
|
+
response: codexFrResponse(outputFormat, stdout),
|
|
3783
3781
|
durationMs,
|
|
3784
3782
|
retryCount: 0,
|
|
3785
3783
|
circuitBreakerState: "closed",
|
|
@@ -4622,7 +4620,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4622
4620
|
outputFormat: z
|
|
4623
4621
|
.enum(["text", "json"])
|
|
4624
4622
|
.default("text")
|
|
4625
|
-
.describe("Codex output format. `
|
|
4623
|
+
.describe("Codex caller-facing output format. Token/cache usage is recorded in the flight recorder regardless. `text` (default) returns the plain reply; `json` returns the raw `--json` JSONL event stream."),
|
|
4626
4624
|
outputSchema: z
|
|
4627
4625
|
.union([z.string(), z.record(z.string(), z.unknown())])
|
|
4628
4626
|
.optional()
|
|
@@ -5224,6 +5222,11 @@ export function createGatewayServer(deps = {}) {
|
|
|
5224
5222
|
if (outputFormat === "stream-json" && result.stdout) {
|
|
5225
5223
|
parsed = parseStreamJson(result.stdout);
|
|
5226
5224
|
}
|
|
5225
|
+
if (asyncJobManager.getJobCli(jobId) === "codex" &&
|
|
5226
|
+
outputFormat !== "json" &&
|
|
5227
|
+
result.stdout) {
|
|
5228
|
+
result.stdout = codexDisplayText(result.stdout);
|
|
5229
|
+
}
|
|
5227
5230
|
return {
|
|
5228
5231
|
content: [
|
|
5229
5232
|
{
|
package/dist/pricing.d.ts
CHANGED
|
@@ -3,6 +3,6 @@ export interface PricePerMillion {
|
|
|
3
3
|
outputUsd: number;
|
|
4
4
|
cacheReadMultiplier: number;
|
|
5
5
|
}
|
|
6
|
-
export declare const PRICING_AS_OF = "2026-
|
|
6
|
+
export declare const PRICING_AS_OF = "2026-06-13";
|
|
7
7
|
export declare function getPricing(cli: "claude" | "codex" | "gemini" | "grok" | "mistral", model: string): PricePerMillion;
|
|
8
8
|
export declare function estimateCacheSavingsUsd(cli: "claude" | "codex" | "gemini" | "grok" | "mistral", model: string, cacheReadTokens: number): number;
|
package/dist/pricing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const PRICING_AS_OF = "2026-
|
|
1
|
+
export const PRICING_AS_OF = "2026-06-13";
|
|
2
2
|
const ANTHROPIC_SONNET = {
|
|
3
3
|
inputUsd: 3,
|
|
4
4
|
outputUsd: 15,
|
|
@@ -19,6 +19,41 @@ const OPENAI_GPT5 = {
|
|
|
19
19
|
outputUsd: 10,
|
|
20
20
|
cacheReadMultiplier: 0.5,
|
|
21
21
|
};
|
|
22
|
+
const GEMINI_25_PRO = {
|
|
23
|
+
inputUsd: 1.25,
|
|
24
|
+
outputUsd: 10,
|
|
25
|
+
cacheReadMultiplier: 0.1,
|
|
26
|
+
};
|
|
27
|
+
const GEMINI_FLASH = {
|
|
28
|
+
inputUsd: 0.3,
|
|
29
|
+
outputUsd: 2.5,
|
|
30
|
+
cacheReadMultiplier: 0.1,
|
|
31
|
+
};
|
|
32
|
+
const GEMINI_3_PRO = {
|
|
33
|
+
inputUsd: 2,
|
|
34
|
+
outputUsd: 12,
|
|
35
|
+
cacheReadMultiplier: 0.1,
|
|
36
|
+
};
|
|
37
|
+
const GROK_4 = {
|
|
38
|
+
inputUsd: 1.25,
|
|
39
|
+
outputUsd: 2.5,
|
|
40
|
+
cacheReadMultiplier: 0.16,
|
|
41
|
+
};
|
|
42
|
+
const GROK_BUILD = {
|
|
43
|
+
inputUsd: 1,
|
|
44
|
+
outputUsd: 2,
|
|
45
|
+
cacheReadMultiplier: 0.2,
|
|
46
|
+
};
|
|
47
|
+
const MISTRAL_MEDIUM = {
|
|
48
|
+
inputUsd: 1.5,
|
|
49
|
+
outputUsd: 7.5,
|
|
50
|
+
cacheReadMultiplier: 0.1,
|
|
51
|
+
};
|
|
52
|
+
const MISTRAL_DEVSTRAL = {
|
|
53
|
+
inputUsd: 0.1,
|
|
54
|
+
outputUsd: 0.3,
|
|
55
|
+
cacheReadMultiplier: 0.1,
|
|
56
|
+
};
|
|
22
57
|
const ZERO = {
|
|
23
58
|
inputUsd: 0,
|
|
24
59
|
outputUsd: 0,
|
|
@@ -33,13 +68,43 @@ export function getPricing(cli, model) {
|
|
|
33
68
|
return ANTHROPIC_OPUS;
|
|
34
69
|
if (lower.includes("haiku"))
|
|
35
70
|
return ANTHROPIC_HAIKU;
|
|
36
|
-
return
|
|
71
|
+
return ANTHROPIC_SONNET;
|
|
37
72
|
}
|
|
38
73
|
if (cli === "codex") {
|
|
39
74
|
if (lower.includes("gpt-5") || lower.includes("o3"))
|
|
40
75
|
return OPENAI_GPT5;
|
|
41
76
|
return ZERO;
|
|
42
77
|
}
|
|
78
|
+
if (cli === "gemini") {
|
|
79
|
+
const specialtyVariant = lower.includes("lite") ||
|
|
80
|
+
lower.includes("image") ||
|
|
81
|
+
lower.includes("audio") ||
|
|
82
|
+
lower.includes("tts");
|
|
83
|
+
if (!specialtyVariant) {
|
|
84
|
+
if (lower.includes("2.5-flash"))
|
|
85
|
+
return GEMINI_FLASH;
|
|
86
|
+
if (lower.includes("2.5-pro"))
|
|
87
|
+
return GEMINI_25_PRO;
|
|
88
|
+
if (lower.includes("gemini-3") && lower.includes("pro"))
|
|
89
|
+
return GEMINI_3_PRO;
|
|
90
|
+
}
|
|
91
|
+
return ZERO;
|
|
92
|
+
}
|
|
93
|
+
if (cli === "grok") {
|
|
94
|
+
if (lower.includes("grok-build") || lower.includes("grok-code"))
|
|
95
|
+
return GROK_BUILD;
|
|
96
|
+
if (lower.includes("grok-4") || lower.includes("grok-3") || lower.includes("grok-latest")) {
|
|
97
|
+
return GROK_4;
|
|
98
|
+
}
|
|
99
|
+
return ZERO;
|
|
100
|
+
}
|
|
101
|
+
if (cli === "mistral") {
|
|
102
|
+
if (lower.includes("devstral-small"))
|
|
103
|
+
return MISTRAL_DEVSTRAL;
|
|
104
|
+
if (lower.includes("mistral-medium-3.5"))
|
|
105
|
+
return MISTRAL_MEDIUM;
|
|
106
|
+
return ZERO;
|
|
107
|
+
}
|
|
43
108
|
return ZERO;
|
|
44
109
|
}
|
|
45
110
|
export function estimateCacheSavingsUsd(cli, model, cacheReadTokens) {
|
|
@@ -46,6 +46,23 @@ export interface ProviderFeatureCapability {
|
|
|
46
46
|
details?: string;
|
|
47
47
|
values?: string[];
|
|
48
48
|
}
|
|
49
|
+
export type ProviderAcpStatus = "native_smoke_passed" | "native_candidate" | "adapter_mediated_deferred" | "absent_watchlist" | "not_applicable";
|
|
50
|
+
export type ProviderAcpMediation = "native" | "adapter_mediated" | "none";
|
|
51
|
+
export type ProviderAcpSmokeStatus = "passed" | "not_run" | "unsupported";
|
|
52
|
+
export interface ProviderAcpCapability {
|
|
53
|
+
status: ProviderAcpStatus;
|
|
54
|
+
mediation: ProviderAcpMediation;
|
|
55
|
+
targetVersion: string;
|
|
56
|
+
entrypoint: {
|
|
57
|
+
command: string;
|
|
58
|
+
args: string[];
|
|
59
|
+
} | null;
|
|
60
|
+
runtimeEnabled: boolean;
|
|
61
|
+
smokeSupported: boolean;
|
|
62
|
+
smokeStatus: ProviderAcpSmokeStatus;
|
|
63
|
+
caveats: string[];
|
|
64
|
+
docs: string;
|
|
65
|
+
}
|
|
49
66
|
export type ProviderFeatureMap = Record<string, ProviderFeatureCapability>;
|
|
50
67
|
export interface ProviderCapabilityControls {
|
|
51
68
|
allowlist: ProviderToolControl;
|
|
@@ -54,6 +71,25 @@ export interface ProviderCapabilityControls {
|
|
|
54
71
|
nativeSkills: ProviderToolControl;
|
|
55
72
|
[name: string]: ProviderToolControl;
|
|
56
73
|
}
|
|
74
|
+
export type AcpFrozenClassification = "native_candidate" | "adapter_mediated_deferred" | "absent_watchlist";
|
|
75
|
+
export interface AcpProviderContract {
|
|
76
|
+
classification: AcpFrozenClassification;
|
|
77
|
+
summary: string;
|
|
78
|
+
}
|
|
79
|
+
export interface AcpContractMetadata {
|
|
80
|
+
protocol: "Agent Client Protocol";
|
|
81
|
+
outOfScope: "Agent Communication Protocol";
|
|
82
|
+
mcpFrontendRemains: true;
|
|
83
|
+
acpIsInternalProviderTransport: true;
|
|
84
|
+
defaultTransport: "cli";
|
|
85
|
+
hostServicesDenyByDefault: true;
|
|
86
|
+
noRawAcpJsonRpcTool: true;
|
|
87
|
+
adapterSupportIsNotNative: true;
|
|
88
|
+
contractDoc: "docs/acp-contract.md";
|
|
89
|
+
nonGoals: readonly string[];
|
|
90
|
+
providers: Readonly<Record<ProviderCapabilityId, AcpProviderContract>>;
|
|
91
|
+
}
|
|
92
|
+
export declare const ACP_CONTRACT: AcpContractMetadata;
|
|
57
93
|
export interface ProviderToolCapabilities {
|
|
58
94
|
schemaVersion: "provider-tool-capabilities.v2";
|
|
59
95
|
generatedAt: string;
|
|
@@ -62,6 +98,8 @@ export interface ProviderToolCapabilities {
|
|
|
62
98
|
gatewayRequestTools: string[];
|
|
63
99
|
modelInfo: CliInfo | GrokApiModelInfo;
|
|
64
100
|
summary: string;
|
|
101
|
+
acpContract: AcpProviderContract;
|
|
102
|
+
acp: ProviderAcpCapability;
|
|
65
103
|
controls: ProviderCapabilityControls;
|
|
66
104
|
features: ProviderFeatureMap;
|
|
67
105
|
discoveredSkills: ProviderSkillCapability[];
|
|
@@ -11,6 +11,146 @@ const MAX_SKILL_BYTES = 64 * 1024;
|
|
|
11
11
|
const MAX_CONFIG_BYTES = 128 * 1024;
|
|
12
12
|
const MAX_PROVIDER_TOOLS_PER_SKILL = 50;
|
|
13
13
|
const CAPABILITY_CACHE_TTL_MS = 60 * 1000;
|
|
14
|
+
export const ACP_CONTRACT = {
|
|
15
|
+
protocol: "Agent Client Protocol",
|
|
16
|
+
outOfScope: "Agent Communication Protocol",
|
|
17
|
+
mcpFrontendRemains: true,
|
|
18
|
+
acpIsInternalProviderTransport: true,
|
|
19
|
+
defaultTransport: "cli",
|
|
20
|
+
hostServicesDenyByDefault: true,
|
|
21
|
+
noRawAcpJsonRpcTool: true,
|
|
22
|
+
adapterSupportIsNotNative: true,
|
|
23
|
+
contractDoc: "docs/acp-contract.md",
|
|
24
|
+
nonGoals: [
|
|
25
|
+
"Replace the MCP server.",
|
|
26
|
+
"Ship an outbound ACP server or frontend in this slice.",
|
|
27
|
+
"Wrap every provider immediately.",
|
|
28
|
+
"Run adapter-mediated providers by default.",
|
|
29
|
+
"Grant write or terminal HostServices by default.",
|
|
30
|
+
"Expose raw ACP JSON-RPC to agents.",
|
|
31
|
+
"Implement any agent-to-agent Agent Communication Protocol layer.",
|
|
32
|
+
],
|
|
33
|
+
providers: {
|
|
34
|
+
mistral: {
|
|
35
|
+
classification: "native_candidate",
|
|
36
|
+
summary: "Mistral Vibe exposes native ACP via vibe-acp; first runtime pilot candidate.",
|
|
37
|
+
},
|
|
38
|
+
grok: {
|
|
39
|
+
classification: "native_candidate",
|
|
40
|
+
summary: "xAI Grok CLI exposes native ACP via grok agent stdio; second runtime pilot candidate.",
|
|
41
|
+
},
|
|
42
|
+
codex: {
|
|
43
|
+
classification: "adapter_mediated_deferred",
|
|
44
|
+
summary: "OpenAI Codex CLI has no native ACP entrypoint at the target version; adapter-mediated and deferred.",
|
|
45
|
+
},
|
|
46
|
+
claude: {
|
|
47
|
+
classification: "adapter_mediated_deferred",
|
|
48
|
+
summary: "Anthropic Claude Code has no native ACP entrypoint at the target version; adapter-mediated and deferred.",
|
|
49
|
+
},
|
|
50
|
+
gemini: {
|
|
51
|
+
classification: "absent_watchlist",
|
|
52
|
+
summary: "Google Antigravity agy 1.0.7 has no ACP surface; watchlist item only.",
|
|
53
|
+
},
|
|
54
|
+
grok_api: {
|
|
55
|
+
classification: "absent_watchlist",
|
|
56
|
+
summary: "Grok API is an HTTP provider with no ACP process transport; watchlist item only.",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
const ACP_DOCS_REFERENCE = "docs/plans/first-class-acp-gateway-extension.dag.toml";
|
|
61
|
+
const ACP_CAPABILITIES = {
|
|
62
|
+
mistral: {
|
|
63
|
+
status: "native_smoke_passed",
|
|
64
|
+
mediation: "native",
|
|
65
|
+
targetVersion: "vibe 2.14.1",
|
|
66
|
+
entrypoint: { command: "vibe-acp", args: [] },
|
|
67
|
+
runtimeEnabled: false,
|
|
68
|
+
smokeSupported: true,
|
|
69
|
+
smokeStatus: "passed",
|
|
70
|
+
caveats: [
|
|
71
|
+
"Native ACP via the provider-scoped vibe-acp executable; first runtime pilot.",
|
|
72
|
+
"Runtime routing stays disabled until ACP is enabled in gateway config.",
|
|
73
|
+
],
|
|
74
|
+
docs: ACP_DOCS_REFERENCE,
|
|
75
|
+
},
|
|
76
|
+
grok: {
|
|
77
|
+
status: "native_smoke_passed",
|
|
78
|
+
mediation: "native",
|
|
79
|
+
targetVersion: "grok 0.2.50 (cadf94855)",
|
|
80
|
+
entrypoint: { command: "grok", args: ["agent", "stdio"] },
|
|
81
|
+
runtimeEnabled: false,
|
|
82
|
+
smokeSupported: true,
|
|
83
|
+
smokeStatus: "passed",
|
|
84
|
+
caveats: [
|
|
85
|
+
"Native ACP via grok agent stdio; second runtime pilot.",
|
|
86
|
+
"Credential lookup is owned by the installed CLI; empty-env smoke is not expected to pass.",
|
|
87
|
+
"Runtime routing stays disabled until ACP is enabled in gateway config.",
|
|
88
|
+
],
|
|
89
|
+
docs: ACP_DOCS_REFERENCE,
|
|
90
|
+
},
|
|
91
|
+
codex: {
|
|
92
|
+
status: "adapter_mediated_deferred",
|
|
93
|
+
mediation: "adapter_mediated",
|
|
94
|
+
targetVersion: "codex-cli 0.139.0",
|
|
95
|
+
entrypoint: null,
|
|
96
|
+
runtimeEnabled: false,
|
|
97
|
+
smokeSupported: false,
|
|
98
|
+
smokeStatus: "unsupported",
|
|
99
|
+
caveats: [
|
|
100
|
+
"No native ACP entrypoint at the target version; ACP would be adapter-mediated.",
|
|
101
|
+
"Adapter support requires a separate threat model and is never labelled native gateway ACP support.",
|
|
102
|
+
],
|
|
103
|
+
docs: ACP_DOCS_REFERENCE,
|
|
104
|
+
},
|
|
105
|
+
claude: {
|
|
106
|
+
status: "adapter_mediated_deferred",
|
|
107
|
+
mediation: "adapter_mediated",
|
|
108
|
+
targetVersion: "claude 2.1.175",
|
|
109
|
+
entrypoint: null,
|
|
110
|
+
runtimeEnabled: false,
|
|
111
|
+
smokeSupported: false,
|
|
112
|
+
smokeStatus: "unsupported",
|
|
113
|
+
caveats: [
|
|
114
|
+
"No native Claude Code CLI ACP entrypoint at the target version; ACP would be adapter-mediated.",
|
|
115
|
+
"Adapter ownership, permission bridging, and install story must be specified before runtime support.",
|
|
116
|
+
],
|
|
117
|
+
docs: ACP_DOCS_REFERENCE,
|
|
118
|
+
},
|
|
119
|
+
gemini: {
|
|
120
|
+
status: "absent_watchlist",
|
|
121
|
+
mediation: "none",
|
|
122
|
+
targetVersion: "agy 1.0.7",
|
|
123
|
+
entrypoint: null,
|
|
124
|
+
runtimeEnabled: false,
|
|
125
|
+
smokeSupported: false,
|
|
126
|
+
smokeStatus: "unsupported",
|
|
127
|
+
caveats: [
|
|
128
|
+
"Antigravity agy 1.0.7 has no ACP flag or subcommand.",
|
|
129
|
+
"Legacy Gemini CLI ACP evidence does not transfer to agy; kept on the upstream drift watchlist.",
|
|
130
|
+
],
|
|
131
|
+
docs: ACP_DOCS_REFERENCE,
|
|
132
|
+
},
|
|
133
|
+
grok_api: {
|
|
134
|
+
status: "not_applicable",
|
|
135
|
+
mediation: "none",
|
|
136
|
+
targetVersion: "xAI Responses API",
|
|
137
|
+
entrypoint: null,
|
|
138
|
+
runtimeEnabled: false,
|
|
139
|
+
smokeSupported: false,
|
|
140
|
+
smokeStatus: "unsupported",
|
|
141
|
+
caveats: ["ACP is a CLI-stdio transport; the HTTP API provider has no ACP surface."],
|
|
142
|
+
docs: ACP_DOCS_REFERENCE,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
function cloneAcpCapability(acp) {
|
|
146
|
+
return {
|
|
147
|
+
...acp,
|
|
148
|
+
entrypoint: acp.entrypoint
|
|
149
|
+
? { command: acp.entrypoint.command, args: [...acp.entrypoint.args] }
|
|
150
|
+
: null,
|
|
151
|
+
caveats: [...acp.caveats],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
14
154
|
const PROVIDER_CAPABILITY_IDS = [...CLI_TYPES, "grok_api"];
|
|
15
155
|
const KNOWN_PROVIDER_TOOLS = {
|
|
16
156
|
grok: [
|
|
@@ -640,6 +780,8 @@ function buildOneProviderToolCapabilities(cli, query) {
|
|
|
640
780
|
gatewayRequestTool: gatewayRequestTools[0] ?? definition.gatewayRequestTools[0],
|
|
641
781
|
modelInfo: getModelInfo(cli, query.refresh),
|
|
642
782
|
summary: definition.summary,
|
|
783
|
+
acpContract: { ...ACP_CONTRACT.providers[cli] },
|
|
784
|
+
acp: cloneAcpCapability(ACP_CAPABILITIES[cli]),
|
|
643
785
|
controls: cloneControls(definition.controls),
|
|
644
786
|
features,
|
|
645
787
|
discoveredSkills,
|
|
@@ -98,8 +98,8 @@ export declare const CLAUDE_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodEffects<z.ZodObject<
|
|
|
98
98
|
effort: z.ZodOptional<z.ZodEnum<["low", "medium", "high", "xhigh", "max"]>>;
|
|
99
99
|
excludeDynamicSystemPromptSections: z.ZodOptional<z.ZodBoolean>;
|
|
100
100
|
}, "strip", z.ZodTypeAny, {
|
|
101
|
-
agents?: Record<string, Record<string, unknown>> | undefined;
|
|
102
101
|
agent?: string | undefined;
|
|
102
|
+
agents?: Record<string, Record<string, unknown>> | undefined;
|
|
103
103
|
forkSession?: boolean | undefined;
|
|
104
104
|
systemPrompt?: string | undefined;
|
|
105
105
|
appendSystemPrompt?: string | undefined;
|
|
@@ -108,8 +108,8 @@ export declare const CLAUDE_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodEffects<z.ZodObject<
|
|
|
108
108
|
effort?: "medium" | "low" | "high" | "xhigh" | "max" | undefined;
|
|
109
109
|
excludeDynamicSystemPromptSections?: boolean | undefined;
|
|
110
110
|
}, {
|
|
111
|
-
agents?: Record<string, Record<string, unknown>> | undefined;
|
|
112
111
|
agent?: string | undefined;
|
|
112
|
+
agents?: Record<string, Record<string, unknown>> | undefined;
|
|
113
113
|
forkSession?: boolean | undefined;
|
|
114
114
|
systemPrompt?: string | undefined;
|
|
115
115
|
appendSystemPrompt?: string | undefined;
|
|
@@ -118,8 +118,8 @@ export declare const CLAUDE_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodEffects<z.ZodObject<
|
|
|
118
118
|
effort?: "medium" | "low" | "high" | "xhigh" | "max" | undefined;
|
|
119
119
|
excludeDynamicSystemPromptSections?: boolean | undefined;
|
|
120
120
|
}>, {
|
|
121
|
-
agents?: Record<string, Record<string, unknown>> | undefined;
|
|
122
121
|
agent?: string | undefined;
|
|
122
|
+
agents?: Record<string, Record<string, unknown>> | undefined;
|
|
123
123
|
forkSession?: boolean | undefined;
|
|
124
124
|
systemPrompt?: string | undefined;
|
|
125
125
|
appendSystemPrompt?: string | undefined;
|
|
@@ -128,8 +128,8 @@ export declare const CLAUDE_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodEffects<z.ZodObject<
|
|
|
128
128
|
effort?: "medium" | "low" | "high" | "xhigh" | "max" | undefined;
|
|
129
129
|
excludeDynamicSystemPromptSections?: boolean | undefined;
|
|
130
130
|
}, {
|
|
131
|
-
agents?: Record<string, Record<string, unknown>> | undefined;
|
|
132
131
|
agent?: string | undefined;
|
|
132
|
+
agents?: Record<string, Record<string, unknown>> | undefined;
|
|
133
133
|
forkSession?: boolean | undefined;
|
|
134
134
|
systemPrompt?: string | undefined;
|
|
135
135
|
appendSystemPrompt?: string | undefined;
|
|
@@ -107,6 +107,20 @@ export interface ProviderSubcommandCompactCatalog {
|
|
|
107
107
|
];
|
|
108
108
|
rows: readonly (readonly string[])[];
|
|
109
109
|
}
|
|
110
|
+
export type AcpEntrypointStatus = "native" | "adapter_mediated_deferred" | "absent_watchlist";
|
|
111
|
+
export interface AcpEntrypointContract {
|
|
112
|
+
cli: CliType;
|
|
113
|
+
displayName: string;
|
|
114
|
+
status: AcpEntrypointStatus;
|
|
115
|
+
executable: string;
|
|
116
|
+
entrypointArgs: readonly string[];
|
|
117
|
+
targetVersion: string;
|
|
118
|
+
probeArgs: readonly (readonly string[])[];
|
|
119
|
+
adapterCandidates?: readonly string[];
|
|
120
|
+
evidence: string;
|
|
121
|
+
docsRef: string;
|
|
122
|
+
}
|
|
123
|
+
export declare const ACP_ENTRYPOINT_CONTRACTS: Record<CliType, AcpEntrypointContract>;
|
|
110
124
|
export declare const UPSTREAM_CLI_CONTRACTS: Record<CliType, CliContract>;
|
|
111
125
|
export declare function validateUpstreamCliArgs(cli: CliType, args: readonly string[]): ContractValidationResult;
|
|
112
126
|
export declare function assertUpstreamCliArgs(cli: CliType, args: readonly string[]): void;
|
|
@@ -167,6 +181,19 @@ export interface InstalledCliContractProbe {
|
|
|
167
181
|
warnings: string[];
|
|
168
182
|
}
|
|
169
183
|
export declare function probeInstalledCliContract(cli: CliType, timeoutMs?: number): InstalledCliContractProbe;
|
|
184
|
+
export interface InstalledAcpEntrypointProbe {
|
|
185
|
+
cli: CliType;
|
|
186
|
+
status: AcpEntrypointStatus;
|
|
187
|
+
executable: string;
|
|
188
|
+
entrypointArgs: readonly string[];
|
|
189
|
+
targetVersion: string;
|
|
190
|
+
checkedProbeCommands: readonly (readonly string[])[];
|
|
191
|
+
available: boolean | null;
|
|
192
|
+
entrypointDrift: boolean;
|
|
193
|
+
warnings: string[];
|
|
194
|
+
probedAt: string;
|
|
195
|
+
}
|
|
196
|
+
export declare function probeInstalledAcpEntrypoint(cli: CliType, timeoutMs?: number): InstalledAcpEntrypointProbe;
|
|
170
197
|
export declare function buildUpstreamContractReport(options?: {
|
|
171
198
|
cli?: CliType;
|
|
172
199
|
probeInstalled?: boolean;
|