llm-cli-gateway 2.12.1 → 2.13.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +177 -19
  3. package/dist/acp/provider-registry.js +13 -0
  4. package/dist/api-provider.d.ts +1 -0
  5. package/dist/api-provider.js +13 -0
  6. package/dist/approval-manager.d.ts +1 -1
  7. package/dist/async-job-manager.d.ts +35 -3
  8. package/dist/async-job-manager.js +344 -46
  9. package/dist/cli-updater.d.ts +1 -1
  10. package/dist/cli-updater.js +17 -9
  11. package/dist/config.d.ts +34 -0
  12. package/dist/config.js +93 -8
  13. package/dist/doctor.d.ts +32 -4
  14. package/dist/doctor.js +80 -13
  15. package/dist/endpoint-exposure.js +15 -3
  16. package/dist/executor.js +2 -0
  17. package/dist/http-transport.d.ts +3 -0
  18. package/dist/http-transport.js +138 -8
  19. package/dist/index.d.ts +62 -5
  20. package/dist/index.js +774 -83
  21. package/dist/model-registry.d.ts +1 -1
  22. package/dist/model-registry.js +12 -16
  23. package/dist/provider-login-guidance.d.ts +12 -0
  24. package/dist/provider-login-guidance.js +46 -0
  25. package/dist/provider-status.d.ts +13 -1
  26. package/dist/provider-status.js +22 -13
  27. package/dist/provider-tool-capabilities.d.ts +4 -1
  28. package/dist/provider-tool-capabilities.js +310 -11
  29. package/dist/provider-types.d.ts +4 -0
  30. package/dist/provider-types.js +10 -0
  31. package/dist/resources.d.ts +6 -2
  32. package/dist/resources.js +72 -8
  33. package/dist/session-manager.d.ts +3 -5
  34. package/dist/session-manager.js +4 -2
  35. package/dist/upstream-contracts.js +169 -0
  36. package/dist/validation-normalizer.js +5 -4
  37. package/dist/validation-orchestrator.js +4 -0
  38. package/npm-shrinkwrap.json +2 -2
  39. package/package.json +1 -1
  40. package/setup/status.schema.json +68 -3
@@ -0,0 +1,4 @@
1
+ export declare const CLI_TYPES: readonly ["claude", "codex", "gemini", "grok", "mistral", "devin", "cursor"];
2
+ export type CliType = (typeof CLI_TYPES)[number];
3
+ export declare const API_PROVIDER_TYPES: readonly ["grok-api"];
4
+ export type KnownApiProviderType = (typeof API_PROVIDER_TYPES)[number];
@@ -0,0 +1,10 @@
1
+ export const CLI_TYPES = [
2
+ "claude",
3
+ "codex",
4
+ "gemini",
5
+ "grok",
6
+ "mistral",
7
+ "devin",
8
+ "cursor",
9
+ ];
10
+ export const API_PROVIDER_TYPES = ["grok-api"];
@@ -2,7 +2,7 @@ import { ISessionManager } from "./session-manager.js";
2
2
  import { PerformanceMetrics } from "./metrics.js";
3
3
  import { FlightRecorderQuery } from "./flight-recorder.js";
4
4
  import { type GlobalCacheStats, type PrefixCacheStats, type SessionCacheStats } from "./cache-stats.js";
5
- import type { CacheAwarenessConfig } from "./config.js";
5
+ import { type CacheAwarenessConfig, type ProvidersConfig } from "./config.js";
6
6
  export interface ResourceDefinition {
7
7
  uri: string;
8
8
  name: string;
@@ -25,8 +25,12 @@ export declare class ResourceProvider {
25
25
  private performanceMetrics;
26
26
  private flightRecorder;
27
27
  private cacheAwareness;
28
- constructor(sessionManager: ISessionManager, performanceMetrics: PerformanceMetrics, flightRecorder?: FlightRecorderQuery, cacheAwareness?: CacheAwarenessConfig | null);
28
+ private providers;
29
+ constructor(sessionManager: ISessionManager, performanceMetrics: PerformanceMetrics, flightRecorder?: FlightRecorderQuery, cacheAwareness?: CacheAwarenessConfig | null, providers?: ProvidersConfig | null);
29
30
  getFlightRecorderQuery(): FlightRecorderQuery;
31
+ private apiRuntimes;
32
+ private continuityTrackedApiRuntimes;
33
+ private providerCapabilityIds;
30
34
  readCacheStateGlobal(opts?: {
31
35
  lastNHours?: number;
32
36
  }): GlobalCacheStats;
package/dist/resources.js CHANGED
@@ -2,22 +2,36 @@ import { CLI_TYPES, PROVIDER_TYPES } from "./session-manager.js";
2
2
  import { getRequestContext, principalCanAccess, resolveOwnerPrincipal } from "./request-context.js";
3
3
  import { getAvailableCliInfo } from "./model-registry.js";
4
4
  import { computeGlobalCacheStats, computePrefixCacheStats, computeSessionCacheStats, computeTtlRemaining, } from "./cache-stats.js";
5
+ import { enabledApiProviders, } from "./config.js";
6
+ import { apiContinuityForKind } from "./api-provider.js";
7
+ import { apiProviderCatalogEntry } from "./api-request.js";
5
8
  import { buildProviderSubcommandsCompactCatalog, getCliSubcommandContract, serializeCliSubcommandContract, } from "./upstream-contracts.js";
6
- import { getOneProviderToolCapabilities, getProviderToolCapabilities, providerCapabilityIds, } from "./provider-tool-capabilities.js";
9
+ import { getOneProviderToolCapabilities, getProviderToolCapabilities, knownProviderCapabilityIds, providerCapabilityIds, } from "./provider-tool-capabilities.js";
7
10
  export class ResourceProvider {
8
11
  sessionManager;
9
12
  performanceMetrics;
10
13
  flightRecorder;
11
14
  cacheAwareness;
12
- constructor(sessionManager, performanceMetrics, flightRecorder = { queryRequests: () => [] }, cacheAwareness = null) {
15
+ providers;
16
+ constructor(sessionManager, performanceMetrics, flightRecorder = { queryRequests: () => [] }, cacheAwareness = null, providers = null) {
13
17
  this.sessionManager = sessionManager;
14
18
  this.performanceMetrics = performanceMetrics;
15
19
  this.flightRecorder = flightRecorder;
16
20
  this.cacheAwareness = cacheAwareness;
21
+ this.providers = providers;
17
22
  }
18
23
  getFlightRecorderQuery() {
19
24
  return this.flightRecorder;
20
25
  }
26
+ apiRuntimes() {
27
+ return this.providers ? enabledApiProviders(this.providers) : [];
28
+ }
29
+ continuityTrackedApiRuntimes() {
30
+ return this.apiRuntimes().filter(rt => apiContinuityForKind(rt.kind) !== "none");
31
+ }
32
+ providerCapabilityIds() {
33
+ return this.providers ? providerCapabilityIds(this.providers) : knownProviderCapabilityIds();
34
+ }
21
35
  readCacheStateGlobal(opts = {}) {
22
36
  return computeGlobalCacheStats(this.flightRecorder, opts);
23
37
  }
@@ -101,6 +115,17 @@ export class ResourceProvider {
101
115
  priority: 0.6,
102
116
  },
103
117
  },
118
+ ...this.continuityTrackedApiRuntimes().map(rt => ({
119
+ uri: `sessions://${rt.name}`,
120
+ name: `${rt.name} Sessions`,
121
+ title: `${rt.name} Sessions`,
122
+ description: `List of ${rt.name} API provider conversation sessions`,
123
+ mimeType: "application/json",
124
+ annotations: {
125
+ audience: ["user", "assistant"],
126
+ priority: 0.6,
127
+ },
128
+ })),
104
129
  {
105
130
  uri: "models://claude",
106
131
  name: "Claude Models",
@@ -156,6 +181,17 @@ export class ResourceProvider {
156
181
  priority: 0.8,
157
182
  },
158
183
  },
184
+ ...this.apiRuntimes().map(rt => ({
185
+ uri: `models://${rt.name}`,
186
+ name: `${rt.name} Models`,
187
+ title: `${rt.name} Models & Capabilities`,
188
+ description: `Configured ${rt.name} API provider model catalog`,
189
+ mimeType: "application/json",
190
+ annotations: {
191
+ audience: ["user", "assistant"],
192
+ priority: 0.8,
193
+ },
194
+ })),
159
195
  {
160
196
  uri: "metrics://performance",
161
197
  name: "Performance Metrics",
@@ -189,7 +225,7 @@ export class ResourceProvider {
189
225
  priority: 0.8,
190
226
  },
191
227
  },
192
- ...providerCapabilityIds().map(cli => ({
228
+ ...this.providerCapabilityIds().map(cli => ({
193
229
  uri: `provider-tools://${cli}`,
194
230
  name: `${cli} Tool Capabilities`,
195
231
  title: `${cli} Tool Capabilities`,
@@ -338,6 +374,32 @@ export class ResourceProvider {
338
374
  text: JSON.stringify(cliInfo.mistral, null, 2),
339
375
  };
340
376
  }
377
+ if (uri.startsWith("models://")) {
378
+ const runtime = this.apiRuntimes().find(rt => `models://${rt.name}` === uri);
379
+ if (runtime) {
380
+ return {
381
+ uri,
382
+ mimeType: "application/json",
383
+ text: JSON.stringify(apiProviderCatalogEntry(runtime), null, 2),
384
+ };
385
+ }
386
+ }
387
+ if (uri.startsWith("sessions://")) {
388
+ const runtime = this.continuityTrackedApiRuntimes().find(rt => `sessions://${rt.name}` === uri);
389
+ if (runtime) {
390
+ const sessions = this.ownedSessions(await this.sessionManager.listSessions(runtime.name));
391
+ return {
392
+ uri,
393
+ mimeType: "application/json",
394
+ text: JSON.stringify({
395
+ cli: runtime.name,
396
+ total: sessions.length,
397
+ sessions,
398
+ activeSession: await this.ownedActiveId(runtime.name),
399
+ }, null, 2),
400
+ };
401
+ }
402
+ }
341
403
  if (uri === "metrics://performance") {
342
404
  return {
343
405
  uri,
@@ -356,15 +418,17 @@ export class ResourceProvider {
356
418
  return {
357
419
  uri,
358
420
  mimeType: "application/json",
359
- text: JSON.stringify(getProviderToolCapabilities(), null, 2),
421
+ text: JSON.stringify(getProviderToolCapabilities({ providersConfig: this.providers ?? undefined }), null, 2),
360
422
  };
361
423
  }
362
- const providerToolsResource = parseProviderToolsUri(uri);
424
+ const providerToolsResource = parseProviderToolsUri(uri, this.providerCapabilityIds());
363
425
  if (providerToolsResource) {
364
426
  return {
365
427
  uri,
366
428
  mimeType: "application/json",
367
- text: JSON.stringify(getOneProviderToolCapabilities(providerToolsResource.provider), null, 2),
429
+ text: JSON.stringify(getOneProviderToolCapabilities(providerToolsResource.provider, {
430
+ providersConfig: this.providers ?? undefined,
431
+ }), null, 2),
368
432
  };
369
433
  }
370
434
  const subcommandResource = parseProviderSubcommandUri(uri);
@@ -401,7 +465,7 @@ function parseProviderSubcommandUri(uri) {
401
465
  commandPath: pathParts.map(part => decodeURIComponent(part)).filter(Boolean),
402
466
  };
403
467
  }
404
- function parseProviderToolsUri(uri) {
468
+ function parseProviderToolsUri(uri, providerIds) {
405
469
  const prefix = uri.startsWith("provider-tools://")
406
470
  ? "provider-tools://"
407
471
  : uri.startsWith("provider_tools://")
@@ -410,7 +474,7 @@ function parseProviderToolsUri(uri) {
410
474
  if (!prefix || uri === `${prefix}catalog`)
411
475
  return null;
412
476
  const provider = uri.slice(prefix.length);
413
- if (!providerCapabilityIds().includes(provider))
477
+ if (!providerIds.includes(provider))
414
478
  return null;
415
479
  return { provider: provider };
416
480
  }
@@ -1,14 +1,12 @@
1
1
  import type { Config } from "./config.js";
2
2
  import type { DatabaseConnection } from "./db.js";
3
3
  import type { Logger } from "./logger.js";
4
- export declare const CLI_TYPES: readonly ["claude", "codex", "gemini", "grok", "mistral", "devin"];
5
- export type CliType = (typeof CLI_TYPES)[number];
6
- export declare const API_PROVIDER_TYPES: readonly ["grok-api"];
7
- export type KnownApiProviderType = (typeof API_PROVIDER_TYPES)[number];
4
+ import { API_PROVIDER_TYPES, CLI_TYPES, type CliType, type KnownApiProviderType } from "./provider-types.js";
5
+ export { API_PROVIDER_TYPES, CLI_TYPES, type CliType, type KnownApiProviderType };
8
6
  export type ApiProviderType = KnownApiProviderType | (string & {});
9
7
  export type ProviderType = CliType | ApiProviderType;
10
8
  export type ProviderKind = "cli" | "api";
11
- export declare const PROVIDER_TYPES: readonly ["claude", "codex", "gemini", "grok", "mistral", "devin", "grok-api"];
9
+ export declare const PROVIDER_TYPES: readonly ["claude", "codex", "gemini", "grok", "mistral", "devin", "cursor", "grok-api"];
12
10
  export declare function isCliType(provider: string): provider is CliType;
13
11
  export declare function providerKind(provider: ProviderType): ProviderKind;
14
12
  export declare function defaultSessionDescription(provider: ProviderType): string;
@@ -5,8 +5,8 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, openSyn
5
5
  import { DEFAULT_SESSION_TTL_SECONDS } from "./config.js";
6
6
  import { noopLogger } from "./logger.js";
7
7
  import { getRequestContext, resolveOwnerPrincipal } from "./request-context.js";
8
- export const CLI_TYPES = ["claude", "codex", "gemini", "grok", "mistral", "devin"];
9
- export const API_PROVIDER_TYPES = ["grok-api"];
8
+ import { API_PROVIDER_TYPES, CLI_TYPES, } from "./provider-types.js";
9
+ export { API_PROVIDER_TYPES, CLI_TYPES };
10
10
  export const PROVIDER_TYPES = [...CLI_TYPES, ...API_PROVIDER_TYPES];
11
11
  export function isCliType(provider) {
12
12
  return CLI_TYPES.includes(provider);
@@ -20,6 +20,8 @@ const KNOWN_SESSION_DESCRIPTIONS = {
20
20
  gemini: "Gemini Session",
21
21
  grok: "Grok Session",
22
22
  mistral: "Mistral Session",
23
+ devin: "Devin Session",
24
+ cursor: "Cursor Session",
23
25
  "grok-api": "Grok API Session",
24
26
  };
25
27
  export function defaultSessionDescription(provider) {
@@ -70,6 +70,17 @@ export const ACP_ENTRYPOINT_CONTRACTS = {
70
70
  evidence: 'Native ACP entrypoint `devin acp` (stdio JSON-RPC). Slice D1 manual initialize + session/new smoke passed (protocolVersion 1, agent "Affogato", session created). Third native runtime pilot; routing stays config-gated.',
71
71
  docsRef: "docs/plans/first-class-acp-gateway-extension.dag.toml#provider_matrix.devin",
72
72
  },
73
+ cursor: {
74
+ cli: "cursor",
75
+ displayName: "Cursor Agent CLI",
76
+ status: "native",
77
+ executable: "cursor-agent",
78
+ entrypointArgs: ["acp"],
79
+ targetVersion: "cursor-agent 2026.06.29-2ad2186",
80
+ probeArgs: [["acp", "--help"]],
81
+ evidence: "Native hidden ACP entrypoint `cursor-agent acp` (stdio JSON-RPC). `cursor-agent acp --help` was verified locally; manual initialize + session/new smoke passed locally (protocolVersion 1, session created; no agentInfo returned). Runtime routing stays config-gated.",
82
+ docsRef: "docs/plans/first-class-acp-gateway-extension.dag.toml#provider_matrix.cursor",
83
+ },
73
84
  };
74
85
  const PERMISSION_MODES = [
75
86
  "default",
@@ -1826,6 +1837,164 @@ export const UPSTREAM_CLI_CONTRACTS = {
1826
1837
  },
1827
1838
  ],
1828
1839
  },
1840
+ cursor: {
1841
+ cli: "cursor",
1842
+ executable: "cursor-agent",
1843
+ upstream: "Cursor Agent CLI",
1844
+ upstreamMetadata: {
1845
+ sourceUrls: [
1846
+ "https://cursor.com/cli",
1847
+ "https://cursor.com/docs/cli/acp",
1848
+ "https://cursor.com/docs/cli/reference/parameters",
1849
+ ],
1850
+ packageName: "cursor-agent",
1851
+ installDocsUrl: "https://cursor.com/cli",
1852
+ releaseChannel: "vendor",
1853
+ watchCategories: [
1854
+ "flags",
1855
+ "subcommands",
1856
+ "session-resume",
1857
+ "execution-modes",
1858
+ "acp-entrypoint",
1859
+ ],
1860
+ },
1861
+ helpArgs: [["--help"]],
1862
+ subcommands: {},
1863
+ maxPositionals: 1,
1864
+ mcpTools: ["cursor_request", "cursor_request_async"],
1865
+ mcpParameters: [
1866
+ "prompt",
1867
+ "model",
1868
+ "mode",
1869
+ "outputFormat",
1870
+ "force",
1871
+ "autoReview",
1872
+ "sandbox",
1873
+ "trust",
1874
+ "workspace",
1875
+ "addDir",
1876
+ "sessionId",
1877
+ "resumeLatest",
1878
+ "createNewSession",
1879
+ ],
1880
+ flags: {
1881
+ "--print": {
1882
+ arity: "none",
1883
+ description: "Print responses to console for scripts/non-interactive use",
1884
+ },
1885
+ "--output-format": {
1886
+ arity: "one",
1887
+ values: ["text", "json", "stream-json"],
1888
+ description: "Output format for --print mode",
1889
+ },
1890
+ "--model": { arity: "one", description: "Model to use for the session" },
1891
+ "--mode": {
1892
+ arity: "one",
1893
+ values: ["plan", "ask"],
1894
+ description: "Execution mode (plan or ask)",
1895
+ },
1896
+ "--force": { arity: "none", description: "Force allow commands unless denied" },
1897
+ "--auto-review": { arity: "none", description: "Use Cursor Smart Auto-review" },
1898
+ "--sandbox": {
1899
+ arity: "one",
1900
+ values: ["enabled", "disabled"],
1901
+ description: "Enable or disable Cursor sandbox mode",
1902
+ },
1903
+ "--trust": { arity: "none", description: "Trust workspace in headless mode" },
1904
+ "--workspace": { arity: "one", description: "Workspace directory or saved workspace" },
1905
+ "--add-dir": { arity: "one", description: "Additional workspace root directory" },
1906
+ "--resume": { arity: "one", description: "Resume a specific Cursor chat/session" },
1907
+ "--continue": { arity: "none", description: "Continue the latest Cursor chat" },
1908
+ },
1909
+ acknowledgedUpstreamFlags: [
1910
+ "--api-key",
1911
+ "--header",
1912
+ "-H",
1913
+ "-p",
1914
+ "--plan",
1915
+ "--yolo",
1916
+ "--approve-mcps",
1917
+ "--plugin-dir",
1918
+ "--worktree",
1919
+ "-w",
1920
+ "--worktree-base",
1921
+ "--skip-worktree-setup",
1922
+ "--stream-partial-output",
1923
+ "--list-models",
1924
+ "--version",
1925
+ "-v",
1926
+ "--help",
1927
+ "-h",
1928
+ ],
1929
+ env: {
1930
+ CURSOR_API_KEY: {
1931
+ arity: "one",
1932
+ description: "Cursor Agent API key for headless authentication",
1933
+ },
1934
+ },
1935
+ conformanceFixtures: [
1936
+ {
1937
+ id: "cursor-minimal",
1938
+ description: "Minimal print-mode prompt request",
1939
+ args: ["--print", "hello"],
1940
+ expect: "pass",
1941
+ },
1942
+ {
1943
+ id: "cursor-output-format",
1944
+ description: "--output-format json is accepted",
1945
+ args: ["--print", "--output-format", "json", "hello"],
1946
+ expect: "pass",
1947
+ },
1948
+ {
1949
+ id: "cursor-mode",
1950
+ description: "--mode plan is accepted",
1951
+ args: ["--print", "--mode", "plan", "hello"],
1952
+ expect: "pass",
1953
+ },
1954
+ {
1955
+ id: "cursor-high-impact-controls",
1956
+ description: "Model, force, auto-review, sandbox, trust, and workspace controls are accepted",
1957
+ args: [
1958
+ "--print",
1959
+ "--model",
1960
+ "gpt-5",
1961
+ "--force",
1962
+ "--auto-review",
1963
+ "--sandbox",
1964
+ "enabled",
1965
+ "--trust",
1966
+ "--workspace",
1967
+ "/tmp/workspace",
1968
+ "hello",
1969
+ ],
1970
+ expect: "pass",
1971
+ },
1972
+ {
1973
+ id: "cursor-mode-invalid",
1974
+ description: "Invalid --mode is rejected",
1975
+ args: ["--print", "--mode", "dangerous", "hello"],
1976
+ expect: "fail",
1977
+ },
1978
+ {
1979
+ id: "cursor-resume",
1980
+ description: "Resume by chat id accepted",
1981
+ args: ["--print", "--resume", "chat-123", "hello"],
1982
+ expect: "pass",
1983
+ },
1984
+ {
1985
+ id: "cursor-add-dir",
1986
+ description: "Repeatable --add-dir is accepted",
1987
+ args: ["--print", "--add-dir", "/tmp/extra", "hello"],
1988
+ expect: "pass",
1989
+ },
1990
+ {
1991
+ id: "cursor-unsupported-flag",
1992
+ description: "Unsupported flag is rejected before spawn",
1993
+ args: ["--print", "--not-a-cursor-flag", "hello"],
1994
+ expect: "fail",
1995
+ },
1996
+ ],
1997
+ },
1829
1998
  };
1830
1999
  export function validateUpstreamCliArgs(cli, args) {
1831
2000
  const contract = UPSTREAM_CLI_CONTRACTS[cli];
@@ -1,10 +1,11 @@
1
1
  export function normalizeStartedJob(provider, model, snapshot, warning) {
2
+ const inProgress = snapshot.status === "running" || snapshot.status === "queued";
2
3
  return {
3
4
  provider,
4
5
  model,
5
- status: snapshot.status,
6
- verdict: snapshot.status === "running" ? "pending" : null,
7
- rationale: snapshot.status === "running" ? "Provider job is running asynchronously." : null,
6
+ status: snapshot.status === "queued" ? "running" : snapshot.status,
7
+ verdict: inProgress ? "pending" : null,
8
+ rationale: inProgress ? "Provider job is running asynchronously." : null,
8
9
  risks: [],
9
10
  rawJobReference: {
10
11
  jobId: snapshot.id,
@@ -34,7 +35,7 @@ export function normalizeJobResult(provider, model, result) {
34
35
  return {
35
36
  provider,
36
37
  model,
37
- status: result.status,
38
+ status: result.status === "queued" ? "running" : result.status,
38
39
  verdict: inferVerdict(output, result.status),
39
40
  rationale: output ? excerpt(output, 1800) : error,
40
41
  risks: extractRisks(output, error),
@@ -220,6 +220,10 @@ function buildProviderArgs(provider, prompt) {
220
220
  if (provider === "claude" || provider === "grok" || provider === "mistral") {
221
221
  return ["-p", prompt];
222
222
  }
223
+ if (provider === "devin")
224
+ return ["-p", prompt];
225
+ if (provider === "cursor")
226
+ return ["--print", "--mode", "ask", "--sandbox", "enabled", prompt];
223
227
  if (provider === "codex")
224
228
  return ["exec", "--skip-git-repo-check", prompt];
225
229
  return [prompt];
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "llm-cli-gateway",
3
- "version": "2.12.1",
3
+ "version": "2.13.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "llm-cli-gateway",
9
- "version": "2.12.1",
9
+ "version": "2.13.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@modelcontextprotocol/sdk": "^1.29.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-cli-gateway",
3
- "version": "2.12.1",
3
+ "version": "2.13.0",
4
4
  "mcpName": "io.github.verivus-oss/llm-cli-gateway",
5
5
  "description": "MCP server providing unified access to Claude Code, Codex, Gemini, Grok, Mistral Vibe, and Devin CLIs with session management, retry logic, async job orchestration, durable job results, and cross-LLM validation.",
6
6
  "license": "MIT",
@@ -118,7 +118,7 @@
118
118
  },
119
119
  "providers": {
120
120
  "type": "object",
121
- "required": ["claude", "codex", "gemini", "grok", "mistral"],
121
+ "minProperties": 1,
122
122
  "additionalProperties": {
123
123
  "type": "object",
124
124
  "required": [
@@ -353,7 +353,7 @@
353
353
  "catalog": { "const": "provider-tools://catalog" },
354
354
  "providers": {
355
355
  "type": "object",
356
- "required": ["claude", "codex", "gemini", "grok", "grok_api", "mistral"],
356
+ "minProperties": 1,
357
357
  "additionalProperties": { "type": "string" }
358
358
  }
359
359
  },
@@ -362,7 +362,7 @@
362
362
  "cache_ttl_ms": { "type": "integer", "minimum": 0 },
363
363
  "providers": {
364
364
  "type": "object",
365
- "required": ["claude", "codex", "gemini", "grok", "grok_api", "mistral"],
365
+ "minProperties": 1,
366
366
  "additionalProperties": {
367
367
  "type": "object",
368
368
  "required": [
@@ -405,6 +405,71 @@
405
405
  },
406
406
  "additionalProperties": false
407
407
  },
408
+ "api_providers": {
409
+ "type": "object",
410
+ "description": "Slice 6: health of enabled [providers.<name>] (kind:api) providers. OMITTED entirely when none are enabled (dormant byte-identical).",
411
+ "required": ["enabled_count", "providers"],
412
+ "properties": {
413
+ "enabled_count": { "type": "integer", "minimum": 1 },
414
+ "providers": {
415
+ "type": "object",
416
+ "additionalProperties": {
417
+ "type": "object",
418
+ "required": [
419
+ "name",
420
+ "kind",
421
+ "base_url",
422
+ "default_model",
423
+ "models",
424
+ "api_key_env",
425
+ "api_key_present",
426
+ "reachable",
427
+ "login_guidance"
428
+ ],
429
+ "properties": {
430
+ "name": { "type": "string" },
431
+ "kind": { "enum": ["openai-compatible", "anthropic", "xai-responses"] },
432
+ "base_url": { "type": "string" },
433
+ "default_model": { "type": "string" },
434
+ "models": {
435
+ "type": ["array", "null"],
436
+ "items": { "type": "string" }
437
+ },
438
+ "api_key_env": { "type": ["string", "null"] },
439
+ "api_key_present": { "type": "boolean" },
440
+ "reachable": { "type": ["boolean", "null"] },
441
+ "reachability_error": { "type": "string" },
442
+ "login_guidance": {
443
+ "type": "object",
444
+ "required": [
445
+ "provider",
446
+ "displayName",
447
+ "kind",
448
+ "baseUrl",
449
+ "apiKeyEnv",
450
+ "summary",
451
+ "steps",
452
+ "credentialHandling"
453
+ ],
454
+ "properties": {
455
+ "provider": { "type": "string" },
456
+ "displayName": { "type": "string" },
457
+ "kind": { "enum": ["openai-compatible", "anthropic", "xai-responses"] },
458
+ "baseUrl": { "type": "string" },
459
+ "apiKeyEnv": { "type": ["string", "null"] },
460
+ "summary": { "type": "string" },
461
+ "steps": { "type": "array", "items": { "type": "string" } },
462
+ "credentialHandling": { "type": "string" }
463
+ },
464
+ "additionalProperties": false
465
+ }
466
+ },
467
+ "additionalProperties": false
468
+ }
469
+ }
470
+ },
471
+ "additionalProperties": false
472
+ },
408
473
  "upstream": {
409
474
  "type": "object",
410
475
  "required": [