llm-cli-gateway 2.6.3 → 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/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/doctor.d.ts CHANGED
@@ -2,6 +2,7 @@ import { type EndpointExposureReport } from "./endpoint-exposure.js";
2
2
  import { type ProviderLoginStatus } from "./provider-status.js";
3
3
  import type { FlightRecorderQuery } from "./flight-recorder.js";
4
4
  import { type CacheAwarenessConfig } from "./config.js";
5
+ import { type ProviderCapabilityId, type ProviderKind } from "./provider-tool-capabilities.js";
5
6
  export type CliType = "claude" | "codex" | "gemini" | "grok" | "mistral";
6
7
  export interface CacheAwarenessReport {
7
8
  enabled_features: Array<"anthropic_cache_control" | "ttl_warnings">;
@@ -17,6 +18,26 @@ export interface CacheAwarenessReport {
17
18
  total_cache_read_tokens: number;
18
19
  }>>;
19
20
  }
21
+ export interface ProviderCapabilitySummaryReport {
22
+ schema_version: "provider-tool-capabilities.v2";
23
+ tool: "provider_tool_capabilities";
24
+ resources: {
25
+ catalog: "provider-tools://catalog";
26
+ providers: Record<ProviderCapabilityId, string>;
27
+ };
28
+ cache_ttl_ms: number;
29
+ providers: Record<ProviderCapabilityId, {
30
+ provider_kind: ProviderKind;
31
+ cli_available: boolean;
32
+ gateway_request_tools: string[];
33
+ supported_features: string[];
34
+ unsupported_inputs: string[];
35
+ config_surface_count: number;
36
+ discovered_skill_count: number;
37
+ discovered_provider_tool_count: number;
38
+ warnings: string[];
39
+ }>;
40
+ }
20
41
  export interface VibeSessionLoggingStatus {
21
42
  config_path: string;
22
43
  config_present: boolean;
@@ -116,6 +137,7 @@ export interface DoctorReport {
116
137
  vibe_session_logging: VibeSessionLoggingStatus;
117
138
  };
118
139
  cache_awareness: CacheAwarenessReport;
140
+ provider_capabilities: ProviderCapabilitySummaryReport;
119
141
  upstream: {
120
142
  note: string;
121
143
  recommendation: string;
package/dist/doctor.js CHANGED
@@ -11,6 +11,7 @@ import { loadWorkspaceRegistry } from "./workspace-registry.js";
11
11
  import { computeGlobalCacheStats } from "./cache-stats.js";
12
12
  import { FlightRecorder, resolveFlightRecorderDbPath } from "./flight-recorder.js";
13
13
  import { buildUpstreamContractReport } from "./upstream-contracts.js";
14
+ import { getProviderToolCapabilities, providerCapabilityIds, } from "./provider-tool-capabilities.js";
14
15
  export function checkVibeSessionLogging(home = homedir()) {
15
16
  const configPath = join(home, ".vibe", "config.toml");
16
17
  if (!existsSync(configPath)) {
@@ -226,6 +227,49 @@ function buildCacheAwarenessReport(opts) {
226
227
  per_cli: perCli,
227
228
  };
228
229
  }
230
+ function buildProviderCapabilitySummary(providerStatuses) {
231
+ const capabilities = getProviderToolCapabilities({
232
+ includeSkills: true,
233
+ includeProviderTools: true,
234
+ includeUnsupported: true,
235
+ includePaths: false,
236
+ });
237
+ const providers = Object.fromEntries(providerCapabilityIds().map(provider => {
238
+ const capability = capabilities[provider];
239
+ if (!capability) {
240
+ throw new Error(`Missing provider capability record for ${provider}`);
241
+ }
242
+ const cliAvailable = provider === "grok_api"
243
+ ? capability.gatewayRequestTools.includes("grok_api_request")
244
+ : providerStatuses[provider].installed;
245
+ return [
246
+ provider,
247
+ {
248
+ provider_kind: capability.providerKind,
249
+ cli_available: cliAvailable,
250
+ gateway_request_tools: capability.gatewayRequestTools,
251
+ supported_features: Object.entries(capability.features)
252
+ .filter(([, feature]) => feature.supported)
253
+ .map(([name]) => name),
254
+ unsupported_inputs: capability.unsupportedInputs.map(input => input.input),
255
+ config_surface_count: capability.configSurfaces.length,
256
+ discovered_skill_count: capability.discoveredSkills.length,
257
+ discovered_provider_tool_count: capability.discoveredProviderTools.length,
258
+ warnings: capability.warnings,
259
+ },
260
+ ];
261
+ }));
262
+ return {
263
+ schema_version: "provider-tool-capabilities.v2",
264
+ tool: "provider_tool_capabilities",
265
+ resources: {
266
+ catalog: "provider-tools://catalog",
267
+ providers: Object.fromEntries(providerCapabilityIds().map(provider => [provider, `provider-tools://${provider}`])),
268
+ },
269
+ cache_ttl_ms: 60_000,
270
+ providers,
271
+ };
272
+ }
229
273
  export function createDoctorReport(envOrOptions = process.env) {
230
274
  const opts = isCreateDoctorReportOptions(envOrOptions)
231
275
  ? envOrOptions
@@ -316,6 +360,7 @@ export function createDoctorReport(envOrOptions = process.env) {
316
360
  endpoint_exposure: endpointExposure,
317
361
  client_config: clientConfigStatus(),
318
362
  cache_awareness: buildCacheAwarenessReport(opts),
363
+ provider_capabilities: buildProviderCapabilitySummary(providerStatuses),
319
364
  upstream,
320
365
  next_actions: [],
321
366
  };
@@ -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";
@@ -22,6 +22,7 @@ import { loadConfig, loadPersistenceConfig, loadCacheAwarenessConfig, loadProvid
22
22
  import { createXaiResponse, XaiApiError, } from "./xai-api-provider.js";
23
23
  import { checkHealth } from "./health.js";
24
24
  import { clearModelRegistryCache, getAvailableCliInfo, getCliInfo, resolveModelAlias, } from "./model-registry.js";
25
+ import { getProviderToolCapabilities } from "./provider-tool-capabilities.js";
25
26
  import { AsyncJobManager, } from "./async-job-manager.js";
26
27
  import { createJobStore } from "./job-store.js";
27
28
  import { ApprovalManager } from "./approval-manager.js";
@@ -488,13 +489,10 @@ async function resolveWorkspaceAndWorktreeForRequest(args) {
488
489
  else if (isGatewayAppDirCwd()) {
489
490
  throw new Error("No workspace selected. Configure [workspaces].default or pass a registered workspace alias.");
490
491
  }
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.");
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.");
498
496
  }
499
497
  if (workspace) {
500
498
  if (args.workingDir) {
@@ -719,7 +717,7 @@ export function extractUsageAndCost(cli, output, outputFormat, ctx) {
719
717
  costUsd: parsed.costUsd ?? undefined,
720
718
  };
721
719
  }
722
- if (cli === "codex" && outputFormat === "json") {
720
+ if (cli === "codex") {
723
721
  const parsed = parseCodexJsonStream(output);
724
722
  if (!parsed.usage) {
725
723
  return {};
@@ -1049,6 +1047,27 @@ function registerBaseResources(server, runtime) {
1049
1047
  const contents = await runtime.resourceProvider.readResource(uri.href);
1050
1048
  return { contents: contents ? [contents] : [] };
1051
1049
  });
1050
+ server.registerResource("provider-tools-catalog", "provider-tools://catalog", {
1051
+ title: "Provider Tool Capabilities Catalog",
1052
+ description: "Read-only catalog of gateway tool controls and discovered provider skills",
1053
+ mimeType: "application/json",
1054
+ }, async (uri) => {
1055
+ runtime.logger.debug("Reading provider-tools://catalog resource");
1056
+ const contents = await runtime.resourceProvider.readResource(uri.href);
1057
+ return { contents: contents ? [contents] : [] };
1058
+ });
1059
+ server.registerResource("provider-tools", new ResourceTemplate("provider-tools://{provider}", { list: undefined }), {
1060
+ title: "Provider Tool Capabilities",
1061
+ description: "Read-only gateway tool controls and discovered local skills for one provider CLI",
1062
+ mimeType: "application/json",
1063
+ }, async (uri, variables) => {
1064
+ const provider = Array.isArray(variables.provider)
1065
+ ? variables.provider[0]
1066
+ : variables.provider;
1067
+ runtime.logger.debug(`Reading provider-tools://${provider}`);
1068
+ const contents = await runtime.resourceProvider.readResource(uri.href);
1069
+ return { contents: contents ? [contents] : [] };
1070
+ });
1052
1071
  }
1053
1072
  function resolvePromptOrPartsForPrep(args) {
1054
1073
  const hasPrompt = typeof args.prompt === "string" && args.prompt.length > 0;
@@ -1398,9 +1417,7 @@ export function prepareCodexRequest(params, runtime = resolveGatewayServerRuntim
1398
1417
  if (params.dangerouslyBypassApprovalsAndSandbox) {
1399
1418
  args.push("--dangerously-bypass-approvals-and-sandbox");
1400
1419
  }
1401
- if (params.outputFormat === "json") {
1402
- args.push("--json");
1403
- }
1420
+ args.push("--json");
1404
1421
  args.push("--skip-git-repo-check");
1405
1422
  let highImpactCleanup;
1406
1423
  if (sessionPlan.mode === "new") {
@@ -1808,6 +1825,9 @@ export function buildMistralRetryPrep(params, recoveryModel) {
1808
1825
  }
1809
1826
  function buildCliResponse(cli, stdout, optimizeResponse, corrId, sessionId, prep, durationMs, resumable, outputFormat, warnings) {
1810
1827
  let finalStdout = stdout;
1828
+ if (cli === "codex" && outputFormat !== "json") {
1829
+ finalStdout = codexDisplayText(stdout);
1830
+ }
1811
1831
  if (optimizeResponse && outputFormat !== "json") {
1812
1832
  const optimized = optimizeResponseText(finalStdout);
1813
1833
  logOptimizationTokens("response", corrId, finalStdout, optimized);
@@ -3597,7 +3617,7 @@ export function createGatewayServer(deps = {}) {
3597
3617
  outputFormat: z
3598
3618
  .enum(["text", "json"])
3599
3619
  .default("text")
3600
- .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."),
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."),
3601
3621
  outputSchema: z
3602
3622
  .union([z.string(), z.record(z.string(), z.unknown())])
3603
3623
  .optional()
@@ -3757,7 +3777,7 @@ export function createGatewayServer(deps = {}) {
3757
3777
  logger.info(`[${corrId}] codex_request completed successfully in ${durationMs}ms`);
3758
3778
  const codexUsage = extractUsageAndCost("codex", stdout, outputFormat);
3759
3779
  safeFlightComplete(corrId, {
3760
- response: stdout,
3780
+ response: codexFrResponse(outputFormat, stdout),
3761
3781
  durationMs,
3762
3782
  retryCount: 0,
3763
3783
  circuitBreakerState: "closed",
@@ -4600,7 +4620,7 @@ export function createGatewayServer(deps = {}) {
4600
4620
  outputFormat: z
4601
4621
  .enum(["text", "json"])
4602
4622
  .default("text")
4603
- .describe("Codex output format. `json` emits --json (JSONL events) for token usage extraction."),
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."),
4604
4624
  outputSchema: z
4605
4625
  .union([z.string(), z.record(z.string(), z.unknown())])
4606
4626
  .optional()
@@ -5202,6 +5222,11 @@ export function createGatewayServer(deps = {}) {
5202
5222
  if (outputFormat === "stream-json" && result.stdout) {
5203
5223
  parsed = parseStreamJson(result.stdout);
5204
5224
  }
5225
+ if (asyncJobManager.getJobCli(jobId) === "codex" &&
5226
+ outputFormat !== "json" &&
5227
+ result.stdout) {
5228
+ result.stdout = codexDisplayText(result.stdout);
5229
+ }
5205
5230
  return {
5206
5231
  content: [
5207
5232
  {
@@ -5415,6 +5440,44 @@ export function createGatewayServer(deps = {}) {
5415
5440
  const result = cli ? { [cli]: cliInfo[cli] } : cliInfo;
5416
5441
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
5417
5442
  });
5443
+ server.tool("provider_tool_capabilities", "Report provider tool/feature capabilities and discovered local skill/tool integrations for claude|codex|gemini|grok|grok_api|mistral.", {
5444
+ cli: z
5445
+ .preprocess(value => (value === "" || value === null ? undefined : value), z.enum(["claude", "codex", "gemini", "grok", "grok_api", "mistral"]).optional())
5446
+ .describe("Provider filter (claude|codex|gemini|grok|grok_api|mistral)"),
5447
+ includeSkills: z
5448
+ .boolean()
5449
+ .default(true)
5450
+ .describe("Include bounded local skill discovery results"),
5451
+ includeProviderTools: z
5452
+ .boolean()
5453
+ .default(true)
5454
+ .describe("Include provider-native tools extracted from local skills"),
5455
+ includeUnsupported: z
5456
+ .boolean()
5457
+ .default(true)
5458
+ .describe("Include explicit unsupported/degraded input records"),
5459
+ includePaths: z
5460
+ .boolean()
5461
+ .default(false)
5462
+ .describe("Include raw local filesystem paths in discovery output"),
5463
+ refresh: z.boolean().default(false).describe("Bypass the short-lived capability cache"),
5464
+ }, {
5465
+ title: "Provider tool capabilities",
5466
+ readOnlyHint: true,
5467
+ destructiveHint: false,
5468
+ idempotentHint: true,
5469
+ openWorldHint: false,
5470
+ }, async ({ cli, includeSkills, includeProviderTools, includeUnsupported, includePaths, refresh, }) => {
5471
+ const capabilities = getProviderToolCapabilities({
5472
+ cli,
5473
+ includeSkills,
5474
+ includeProviderTools,
5475
+ includeUnsupported,
5476
+ includePaths,
5477
+ refresh,
5478
+ });
5479
+ return { content: [{ type: "text", text: JSON.stringify(capabilities, null, 2) }] };
5480
+ });
5418
5481
  server.tool("cli_versions", "Report installed provider CLI versions, availability, and login status for all five providers or one.", {
5419
5482
  cli: z
5420
5483
  .preprocess(value => (value === "" || value === null ? undefined : value), z.enum(["claude", "codex", "gemini", "grok", "mistral"]).optional())
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-05-26";
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-05-26";
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 ZERO;
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) {
@@ -0,0 +1,135 @@
1
+ import { type CliInfo } from "./model-registry.js";
2
+ import { type CliType } from "./session-manager.js";
3
+ export interface ProviderToolControl {
4
+ name?: string;
5
+ supported: boolean;
6
+ requestField?: string;
7
+ cliFlag?: string;
8
+ behavior: string;
9
+ }
10
+ export type ProviderCapabilityId = CliType | "grok_api";
11
+ export type ProviderKind = "cli" | "api";
12
+ export type UnsupportedInputBehavior = "reject" | "ignored" | "not_supported" | "approval_tracking_only" | "deprecated";
13
+ export type ProviderToolConfidence = "high" | "medium" | "low";
14
+ export type ProviderToolExtractionReason = "exact-tool-section" | "known-tool-name" | "backtick-heuristic" | "low-confidence";
15
+ export interface ProviderSkillCapability {
16
+ name: string;
17
+ source: "user" | "bundled";
18
+ path?: string;
19
+ description?: string;
20
+ declaredTools: string[];
21
+ declaredToolReasons?: Partial<Record<string, ProviderToolExtractionReason>>;
22
+ }
23
+ export interface ProviderNativeToolCapability {
24
+ name: string;
25
+ source: string;
26
+ skillName?: string;
27
+ path?: string;
28
+ confidence: ProviderToolConfidence;
29
+ reason: ProviderToolExtractionReason;
30
+ }
31
+ export interface ProviderConfigSurface {
32
+ name: string;
33
+ kind: "file" | "directory" | "env" | "gateway" | "provider";
34
+ present: boolean;
35
+ path?: string;
36
+ entries?: string[];
37
+ details?: string;
38
+ }
39
+ export interface ProviderUnsupportedInput {
40
+ input: string;
41
+ behavior: UnsupportedInputBehavior;
42
+ details: string;
43
+ }
44
+ export interface ProviderFeatureCapability {
45
+ supported: boolean;
46
+ details?: string;
47
+ values?: string[];
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
+ }
66
+ export type ProviderFeatureMap = Record<string, ProviderFeatureCapability>;
67
+ export interface ProviderCapabilityControls {
68
+ allowlist: ProviderToolControl;
69
+ denylist: ProviderToolControl;
70
+ mcpServers: ProviderToolControl;
71
+ nativeSkills: ProviderToolControl;
72
+ [name: string]: ProviderToolControl;
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;
93
+ export interface ProviderToolCapabilities {
94
+ schemaVersion: "provider-tool-capabilities.v2";
95
+ generatedAt: string;
96
+ cli: ProviderCapabilityId;
97
+ providerKind: ProviderKind;
98
+ gatewayRequestTools: string[];
99
+ modelInfo: CliInfo | GrokApiModelInfo;
100
+ summary: string;
101
+ acpContract: AcpProviderContract;
102
+ acp: ProviderAcpCapability;
103
+ controls: ProviderCapabilityControls;
104
+ features: ProviderFeatureMap;
105
+ discoveredSkills: ProviderSkillCapability[];
106
+ discoveredProviderTools: ProviderNativeToolCapability[];
107
+ configSurfaces: ProviderConfigSurface[];
108
+ unsupportedInputs: ProviderUnsupportedInput[];
109
+ warnings: string[];
110
+ metadata: {
111
+ deprecatedFields?: Record<string, string>;
112
+ cacheTtlMs: number;
113
+ };
114
+ gatewayRequestTool: string;
115
+ }
116
+ export interface GrokApiModelInfo {
117
+ description: string;
118
+ models: Record<string, string>;
119
+ defaultModel?: string;
120
+ defaultModelSource?: string;
121
+ warnings?: string[];
122
+ }
123
+ export interface ProviderCapabilityQuery {
124
+ cli?: ProviderCapabilityId;
125
+ includeSkills?: boolean;
126
+ includeProviderTools?: boolean;
127
+ includeUnsupported?: boolean;
128
+ includePaths?: boolean;
129
+ refresh?: boolean;
130
+ }
131
+ export type ProviderToolCapabilitiesMap = Partial<Record<ProviderCapabilityId, ProviderToolCapabilities>>;
132
+ export declare function getProviderToolCapabilities(queryOrCli?: ProviderCapabilityQuery | ProviderCapabilityId): ProviderToolCapabilitiesMap;
133
+ export declare function getOneProviderToolCapabilities(cli: ProviderCapabilityId, queryOrCli?: ProviderCapabilityQuery | ProviderCapabilityId): ProviderToolCapabilities;
134
+ export declare function clearProviderToolCapabilitiesCache(): void;
135
+ export declare function providerCapabilityIds(): readonly ProviderCapabilityId[];