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.
- package/CHANGELOG.md +46 -0
- package/README.md +177 -19
- package/dist/acp/provider-registry.js +13 -0
- package/dist/api-provider.d.ts +1 -0
- package/dist/api-provider.js +13 -0
- package/dist/approval-manager.d.ts +1 -1
- package/dist/async-job-manager.d.ts +35 -3
- package/dist/async-job-manager.js +344 -46
- package/dist/cli-updater.d.ts +1 -1
- package/dist/cli-updater.js +17 -9
- package/dist/config.d.ts +34 -0
- package/dist/config.js +93 -8
- package/dist/doctor.d.ts +32 -4
- package/dist/doctor.js +80 -13
- package/dist/endpoint-exposure.js +15 -3
- package/dist/executor.js +2 -0
- package/dist/http-transport.d.ts +3 -0
- package/dist/http-transport.js +138 -8
- package/dist/index.d.ts +62 -5
- package/dist/index.js +774 -83
- package/dist/model-registry.d.ts +1 -1
- package/dist/model-registry.js +12 -16
- package/dist/provider-login-guidance.d.ts +12 -0
- package/dist/provider-login-guidance.js +46 -0
- package/dist/provider-status.d.ts +13 -1
- package/dist/provider-status.js +22 -13
- package/dist/provider-tool-capabilities.d.ts +4 -1
- package/dist/provider-tool-capabilities.js +310 -11
- package/dist/provider-types.d.ts +4 -0
- package/dist/provider-types.js +10 -0
- package/dist/resources.d.ts +6 -2
- package/dist/resources.js +72 -8
- package/dist/session-manager.d.ts +3 -5
- package/dist/session-manager.js +4 -2
- package/dist/upstream-contracts.js +169 -0
- package/dist/validation-normalizer.js +5 -4
- package/dist/validation-orchestrator.js +4 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/setup/status.schema.json +68 -3
package/dist/model-registry.d.ts
CHANGED
package/dist/model-registry.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { parse as parseToml } from "smol-toml";
|
|
5
|
+
import { CLI_TYPES } from "./provider-types.js";
|
|
5
6
|
const FALLBACK_INFO = {
|
|
6
7
|
claude: {
|
|
7
8
|
description: "Anthropic's Claude Code CLI - best for code generation, analysis, and agentic coding tasks",
|
|
@@ -56,6 +57,15 @@ const FALLBACK_INFO = {
|
|
|
56
57
|
},
|
|
57
58
|
modelOrder: ["opus", "gpt-5.5", "swe-1.6"],
|
|
58
59
|
},
|
|
60
|
+
cursor: {
|
|
61
|
+
description: "Cursor Agent CLI - agentic coding and review in Cursor's headless terminal agent",
|
|
62
|
+
models: {
|
|
63
|
+
"gpt-5": "OpenAI GPT-5 model accepted by Cursor Agent examples.",
|
|
64
|
+
"sonnet-4-thinking": "Claude Sonnet thinking model accepted by Cursor Agent examples.",
|
|
65
|
+
"claude-opus-4-8": "Parameterized Claude Opus model family accepted by Cursor Agent examples.",
|
|
66
|
+
},
|
|
67
|
+
modelOrder: ["gpt-5", "sonnet-4-thinking", "claude-opus-4-8"],
|
|
68
|
+
},
|
|
59
69
|
};
|
|
60
70
|
const MODEL_CACHE_TTL_MS = 2 * 60 * 1000;
|
|
61
71
|
const MAX_GEMINI_HISTORY_FILES = 200;
|
|
@@ -77,14 +87,7 @@ export function getCliInfo(forceRefresh = false) {
|
|
|
77
87
|
}
|
|
78
88
|
export function getAvailableCliInfo(forceRefresh = false) {
|
|
79
89
|
const info = getCliInfo(forceRefresh);
|
|
80
|
-
return
|
|
81
|
-
claude: filterUnverifiedModelHints(info.claude),
|
|
82
|
-
codex: filterUnverifiedModelHints(info.codex),
|
|
83
|
-
gemini: filterUnverifiedModelHints(info.gemini),
|
|
84
|
-
grok: filterUnverifiedModelHints(info.grok),
|
|
85
|
-
mistral: filterUnverifiedModelHints(info.mistral),
|
|
86
|
-
devin: filterUnverifiedModelHints(info.devin),
|
|
87
|
-
};
|
|
90
|
+
return Object.fromEntries(CLI_TYPES.map(cli => [cli, filterUnverifiedModelHints(info[cli])]));
|
|
88
91
|
}
|
|
89
92
|
export function clearModelRegistryCache() {
|
|
90
93
|
cachedInfo = null;
|
|
@@ -115,14 +118,7 @@ export function resolveModelAlias(cli, model, info) {
|
|
|
115
118
|
return trimmed;
|
|
116
119
|
}
|
|
117
120
|
function buildCliInfo() {
|
|
118
|
-
const info =
|
|
119
|
-
claude: cloneInfo(FALLBACK_INFO.claude),
|
|
120
|
-
codex: cloneInfo(FALLBACK_INFO.codex),
|
|
121
|
-
gemini: cloneInfo(FALLBACK_INFO.gemini),
|
|
122
|
-
grok: cloneInfo(FALLBACK_INFO.grok),
|
|
123
|
-
mistral: cloneInfo(FALLBACK_INFO.mistral),
|
|
124
|
-
devin: cloneInfo(FALLBACK_INFO.devin),
|
|
125
|
-
};
|
|
121
|
+
const info = Object.fromEntries(CLI_TYPES.map(cli => [cli, cloneInfo(FALLBACK_INFO[cli])]));
|
|
126
122
|
applyClaudeOverrides(info.claude);
|
|
127
123
|
applyCodexOverrides(info.codex);
|
|
128
124
|
applyGeminiOverrides(info.gemini);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CliType } from "./session-manager.js";
|
|
2
|
+
import type { ApiProviderConfig } from "./config.js";
|
|
2
3
|
export interface ProviderLoginGuidance {
|
|
3
4
|
provider: CliType;
|
|
4
5
|
displayName: string;
|
|
@@ -19,3 +20,14 @@ export interface ProviderLoginGuidance {
|
|
|
19
20
|
}
|
|
20
21
|
export declare function getProviderLoginGuidance(provider: CliType): ProviderLoginGuidance;
|
|
21
22
|
export declare function getAllProviderLoginGuidance(): Record<CliType, ProviderLoginGuidance>;
|
|
23
|
+
export interface ApiProviderLoginGuidance {
|
|
24
|
+
provider: string;
|
|
25
|
+
displayName: string;
|
|
26
|
+
kind: ApiProviderConfig["kind"];
|
|
27
|
+
baseUrl: string;
|
|
28
|
+
apiKeyEnv: string | null;
|
|
29
|
+
summary: string;
|
|
30
|
+
steps: string[];
|
|
31
|
+
credentialHandling: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function getApiProviderLoginGuidance(provider: ApiProviderConfig): ApiProviderLoginGuidance;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { redactDiagnosticUrl } from "./endpoint-exposure.js";
|
|
1
2
|
const GUIDANCE = {
|
|
2
3
|
claude: {
|
|
3
4
|
provider: "claude",
|
|
@@ -115,6 +116,24 @@ const GUIDANCE = {
|
|
|
115
116
|
expected: "CLI is installed and `devin auth status` reports an authenticated session",
|
|
116
117
|
},
|
|
117
118
|
},
|
|
119
|
+
cursor: {
|
|
120
|
+
provider: "cursor",
|
|
121
|
+
displayName: "Cursor Agent CLI",
|
|
122
|
+
install: {
|
|
123
|
+
summary: "Install Cursor Agent CLI using Cursor's current official installer.",
|
|
124
|
+
commands: ["cursor-agent update", "cursor-agent --version"],
|
|
125
|
+
documentationUrl: "https://cursor.com/cli",
|
|
126
|
+
},
|
|
127
|
+
login: {
|
|
128
|
+
summary: "Sign in through Cursor Agent's official login flow, or set CURSOR_API_KEY for headless automation.",
|
|
129
|
+
commands: ["cursor-agent login", "cursor-agent status"],
|
|
130
|
+
credentialHandling: "Let Cursor store credentials via `cursor-agent login`, or provide CURSOR_API_KEY in the process environment. Do not paste Cursor API keys into prompts or remote chats.",
|
|
131
|
+
},
|
|
132
|
+
verification: {
|
|
133
|
+
command: "cursor-agent status",
|
|
134
|
+
expected: "CLI is installed and `cursor-agent status` reports an authenticated account",
|
|
135
|
+
},
|
|
136
|
+
},
|
|
118
137
|
};
|
|
119
138
|
export function getProviderLoginGuidance(provider) {
|
|
120
139
|
return GUIDANCE[provider];
|
|
@@ -122,3 +141,30 @@ export function getProviderLoginGuidance(provider) {
|
|
|
122
141
|
export function getAllProviderLoginGuidance() {
|
|
123
142
|
return { ...GUIDANCE };
|
|
124
143
|
}
|
|
144
|
+
export function getApiProviderLoginGuidance(provider) {
|
|
145
|
+
const keyless = provider.apiKeyEnv === null;
|
|
146
|
+
const baseUrl = redactDiagnosticUrl(provider.baseUrl) ?? provider.baseUrl;
|
|
147
|
+
const summary = keyless
|
|
148
|
+
? `Keyless-local API provider "${provider.name}" (kind: ${provider.kind}); no credential is required for its loopback endpoint.`
|
|
149
|
+
: `API provider "${provider.name}" (kind: ${provider.kind}) authenticates with a key read from the ${provider.apiKeyEnv} environment variable.`;
|
|
150
|
+
const steps = keyless
|
|
151
|
+
? [
|
|
152
|
+
`Ensure the local endpoint at ${baseUrl} is running (e.g. Ollama or llama.cpp).`,
|
|
153
|
+
`No API key is needed; the provider is enabled as soon as the loopback endpoint is reachable.`,
|
|
154
|
+
]
|
|
155
|
+
: [
|
|
156
|
+
`Obtain an API key from the provider that serves ${baseUrl}.`,
|
|
157
|
+
`Export it as ${provider.apiKeyEnv} in the gateway's environment (the value is read only at request time).`,
|
|
158
|
+
`Confirm [providers.${provider.name}] in the gateway config points at the intended base_url and default_model.`,
|
|
159
|
+
];
|
|
160
|
+
return {
|
|
161
|
+
provider: provider.name,
|
|
162
|
+
displayName: provider.name,
|
|
163
|
+
kind: provider.kind,
|
|
164
|
+
baseUrl,
|
|
165
|
+
apiKeyEnv: provider.apiKeyEnv,
|
|
166
|
+
summary,
|
|
167
|
+
steps,
|
|
168
|
+
credentialHandling: "Set the API key only via the named environment variable. Do not paste the key into the gateway config, prompts, or a remote chat.",
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CliType } from "./provider-types.js";
|
|
2
2
|
import { type ProviderLoginGuidance } from "./provider-login-guidance.js";
|
|
3
|
+
import { type ApiProviderConfig } from "./config.js";
|
|
3
4
|
export type ProviderLoginStatus = "authenticated" | "not_authenticated" | "unknown" | "not_checked";
|
|
4
5
|
export interface ProviderRuntimeStatus {
|
|
5
6
|
provider: CliType;
|
|
@@ -17,6 +18,17 @@ export interface ProviderRuntimeStatus {
|
|
|
17
18
|
};
|
|
18
19
|
guidance: ProviderLoginGuidance;
|
|
19
20
|
}
|
|
21
|
+
export interface ApiProviderRuntimeStatus {
|
|
22
|
+
provider: string;
|
|
23
|
+
kind: ApiProviderConfig["kind"];
|
|
24
|
+
baseUrl: string;
|
|
25
|
+
defaultModel: string;
|
|
26
|
+
models: string[] | null;
|
|
27
|
+
apiKeyEnv: string | null;
|
|
28
|
+
apiKeyPresent: boolean;
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare function getApiProviderStatus(provider: ApiProviderConfig, env?: NodeJS.ProcessEnv): ApiProviderRuntimeStatus;
|
|
20
32
|
export declare const PROVIDER_COMMANDS: Record<CliType, string>;
|
|
21
33
|
export declare function listProviderRuntimeStatuses(): Record<CliType, ProviderRuntimeStatus>;
|
|
22
34
|
export declare function getProviderRuntimeStatus(provider: CliType): ProviderRuntimeStatus;
|
package/dist/provider-status.js
CHANGED
|
@@ -2,24 +2,32 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { CLI_TYPES } from "./provider-types.js";
|
|
5
6
|
import { getProviderLoginGuidance } from "./provider-login-guidance.js";
|
|
7
|
+
import { apiProviderKeyPresent, isApiProviderEnabled } from "./config.js";
|
|
8
|
+
import { redactDiagnosticUrl } from "./endpoint-exposure.js";
|
|
6
9
|
import { envWithExtendedPath, getExtendedPath, providerCommandName, resolveCommandForSpawn, } from "./executor.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
export function getApiProviderStatus(provider, env = process.env) {
|
|
11
|
+
return {
|
|
12
|
+
provider: provider.name,
|
|
13
|
+
kind: provider.kind,
|
|
14
|
+
baseUrl: redactDiagnosticUrl(provider.baseUrl) ?? provider.baseUrl,
|
|
15
|
+
defaultModel: provider.defaultModel,
|
|
16
|
+
models: provider.models ?? null,
|
|
17
|
+
apiKeyEnv: provider.apiKeyEnv,
|
|
18
|
+
apiKeyPresent: apiProviderKeyPresent(provider, env),
|
|
19
|
+
enabled: isApiProviderEnabled(provider, env),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const VERSION_ARGS = Object.fromEntries(CLI_TYPES.map(provider => [provider, ["--version"]]));
|
|
16
23
|
export const PROVIDER_COMMANDS = {
|
|
17
|
-
claude: "claude",
|
|
18
|
-
codex: "codex",
|
|
24
|
+
claude: providerCommandName("claude"),
|
|
25
|
+
codex: providerCommandName("codex"),
|
|
19
26
|
gemini: providerCommandName("gemini"),
|
|
20
|
-
grok: "grok",
|
|
27
|
+
grok: providerCommandName("grok"),
|
|
21
28
|
mistral: providerCommandName("mistral"),
|
|
22
29
|
devin: providerCommandName("devin"),
|
|
30
|
+
cursor: providerCommandName("cursor"),
|
|
23
31
|
};
|
|
24
32
|
const LOGIN_CHECKS = {
|
|
25
33
|
claude: ["auth", "status", "--json"],
|
|
@@ -27,9 +35,10 @@ const LOGIN_CHECKS = {
|
|
|
27
35
|
grok: ["inspect", "--json"],
|
|
28
36
|
mistral: ["auth", "status"],
|
|
29
37
|
devin: ["auth", "status"],
|
|
38
|
+
cursor: ["status"],
|
|
30
39
|
};
|
|
31
40
|
export function listProviderRuntimeStatuses() {
|
|
32
|
-
return Object.fromEntries(
|
|
41
|
+
return Object.fromEntries(CLI_TYPES.map(provider => [provider, getProviderRuntimeStatus(provider)]));
|
|
33
42
|
}
|
|
34
43
|
export function getProviderRuntimeStatus(provider) {
|
|
35
44
|
const guidance = getProviderLoginGuidance(provider);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type CliInfo } from "./model-registry.js";
|
|
2
2
|
import { type CliType } from "./session-manager.js";
|
|
3
|
+
import { type ProvidersConfig } from "./config.js";
|
|
3
4
|
export interface ProviderToolControl {
|
|
4
5
|
name?: string;
|
|
5
6
|
supported: boolean;
|
|
@@ -128,6 +129,7 @@ export interface ProviderCapabilityQuery {
|
|
|
128
129
|
includeUnsupported?: boolean;
|
|
129
130
|
includePaths?: boolean;
|
|
130
131
|
refresh?: boolean;
|
|
132
|
+
providersConfig?: ProvidersConfig;
|
|
131
133
|
}
|
|
132
134
|
export type ProviderToolCapabilitiesMap = Partial<Record<ProviderCapabilityId, ProviderToolCapabilities>>;
|
|
133
135
|
export declare function getProviderToolCapabilities(queryOrCli?: ProviderCapabilityQuery | ProviderCapabilityId): ProviderToolCapabilitiesMap;
|
|
@@ -137,4 +139,5 @@ export declare function _getCapabilityCacheForTest(): Map<string, {
|
|
|
137
139
|
loadedAt: number;
|
|
138
140
|
value: ProviderToolCapabilities;
|
|
139
141
|
}>;
|
|
140
|
-
export declare function providerCapabilityIds(): readonly
|
|
142
|
+
export declare function providerCapabilityIds(providersConfig?: ProvidersConfig): readonly ProviderCapabilityId[];
|
|
143
|
+
export declare function knownProviderCapabilityIds(): readonly KnownProviderCapabilityId[];
|
|
@@ -5,7 +5,8 @@ import { parse as parseToml } from "smol-toml";
|
|
|
5
5
|
import { CLAUDE_MCP_SERVER_NAMES } from "./claude-mcp-config.js";
|
|
6
6
|
import { getAvailableCliInfo } from "./model-registry.js";
|
|
7
7
|
import { CLI_TYPES } from "./session-manager.js";
|
|
8
|
-
import { isXaiProviderEnabled, loadProvidersConfig } from "./config.js";
|
|
8
|
+
import { enabledApiProviders, isXaiProviderEnabled, loadProvidersConfig, } from "./config.js";
|
|
9
|
+
import { apiContinuityForKind } from "./api-provider.js";
|
|
9
10
|
const MAX_SKILLS_PER_DIR = 100;
|
|
10
11
|
const MAX_SKILL_BYTES = 64 * 1024;
|
|
11
12
|
const MAX_CONFIG_BYTES = 128 * 1024;
|
|
@@ -59,6 +60,10 @@ export const ACP_CONTRACT = {
|
|
|
59
60
|
classification: "native_candidate",
|
|
60
61
|
summary: "Cognition Devin CLI exposes a native ACP server via `devin acp` (stdio); Slice D1 initialize + session/new smoke passed (protocolVersion 1, third runtime pilot). Runtime routing stays config-gated.",
|
|
61
62
|
},
|
|
63
|
+
cursor: {
|
|
64
|
+
classification: "native_candidate",
|
|
65
|
+
summary: "Cursor Agent CLI exposes a hidden native ACP stdio entrypoint via `cursor-agent acp`; initialize + session/new smoke passed locally (protocolVersion 1, session created).",
|
|
66
|
+
},
|
|
62
67
|
},
|
|
63
68
|
};
|
|
64
69
|
const ACP_DOCS_REFERENCE = "docs/plans/first-class-acp-gateway-extension.dag.toml";
|
|
@@ -160,6 +165,20 @@ const ACP_CAPABILITIES = {
|
|
|
160
165
|
],
|
|
161
166
|
docs: ACP_DOCS_REFERENCE,
|
|
162
167
|
},
|
|
168
|
+
cursor: {
|
|
169
|
+
status: "native_smoke_passed",
|
|
170
|
+
mediation: "native",
|
|
171
|
+
targetVersion: "cursor-agent 2026.06.29-2ad2186",
|
|
172
|
+
entrypoint: { command: "cursor-agent", args: ["acp"] },
|
|
173
|
+
runtimeEnabled: false,
|
|
174
|
+
smokeSupported: true,
|
|
175
|
+
smokeStatus: "passed",
|
|
176
|
+
caveats: [
|
|
177
|
+
"Native ACP via hidden `cursor-agent acp` stdio JSON-RPC entrypoint; manual initialize + session/new smoke passed locally (protocolVersion 1, session created; no agentInfo returned).",
|
|
178
|
+
"Runtime routing stays disabled until ACP is enabled in gateway config.",
|
|
179
|
+
],
|
|
180
|
+
docs: ACP_DOCS_REFERENCE,
|
|
181
|
+
},
|
|
163
182
|
};
|
|
164
183
|
function cloneAcpCapability(acp) {
|
|
165
184
|
return {
|
|
@@ -805,11 +824,76 @@ const TOOL_CONTROLS = {
|
|
|
805
824
|
},
|
|
806
825
|
],
|
|
807
826
|
},
|
|
827
|
+
cursor: {
|
|
828
|
+
providerKind: "cli",
|
|
829
|
+
gatewayRequestTools: ["cursor_request", "cursor_request_async"],
|
|
830
|
+
summary: "Cursor Agent CLI runs a headless agentic coding/review session; the gateway passes prompt, model, mode, sandbox/trust controls, workspace roots, and session resume on the CLI transport, with ACP transport gated and fail-closed.",
|
|
831
|
+
controls: {
|
|
832
|
+
allowlist: {
|
|
833
|
+
supported: false,
|
|
834
|
+
behavior: "Cursor Agent exposes force/auto-review modes, not per-request allow lists.",
|
|
835
|
+
},
|
|
836
|
+
denylist: {
|
|
837
|
+
supported: false,
|
|
838
|
+
behavior: "Cursor Agent has no per-request deny-list flag on the tracked surface.",
|
|
839
|
+
},
|
|
840
|
+
mcpServers: {
|
|
841
|
+
supported: false,
|
|
842
|
+
requestField: "mcpServers",
|
|
843
|
+
behavior: "Cursor manages its own MCP configuration via `cursor-agent mcp`; the gateway does not mutate Cursor MCP config.",
|
|
844
|
+
},
|
|
845
|
+
nativeSkills: {
|
|
846
|
+
supported: false,
|
|
847
|
+
behavior: "Cursor owns rules/plugins; the gateway does not discover Cursor-native rules as skills.",
|
|
848
|
+
},
|
|
849
|
+
permissionMode: {
|
|
850
|
+
supported: true,
|
|
851
|
+
requestField: "mode/force/autoReview/sandbox/trust/approvalStrategy/approvalPolicy",
|
|
852
|
+
cliFlag: "--mode/--force/--auto-review/--sandbox/--trust",
|
|
853
|
+
behavior: 'Cursor supports read-only plan/ask modes, Smart Auto-review, force/yolo, sandbox overrides, and workspace trust in headless mode; under approvalStrategy:"mcp_managed", high-impact force/trust/sandbox-disabled requests are gated by the gateway approval manager.',
|
|
854
|
+
},
|
|
855
|
+
promptControl: {
|
|
856
|
+
supported: true,
|
|
857
|
+
requestField: "prompt",
|
|
858
|
+
behavior: "Prompt is passed as the positional prompt to `cursor-agent --print`.",
|
|
859
|
+
},
|
|
860
|
+
session: {
|
|
861
|
+
supported: true,
|
|
862
|
+
requestField: "sessionId/resumeLatest/createNewSession",
|
|
863
|
+
cliFlag: "--resume/--continue",
|
|
864
|
+
behavior: "Resumes a Cursor chat/session (--resume <id>) or the most recent chat (--continue); gateway-created gw-* session ids are tracking ids and are not resumable Cursor chat ids.",
|
|
865
|
+
},
|
|
866
|
+
workspace: {
|
|
867
|
+
supported: true,
|
|
868
|
+
requestField: "workspace/addDir",
|
|
869
|
+
cliFlag: "--workspace/--add-dir",
|
|
870
|
+
behavior: "Sets the Cursor workspace and additional workspace roots; remote HTTP/OAuth callers must use registered workspace aliases/roots rather than raw paths.",
|
|
871
|
+
},
|
|
872
|
+
},
|
|
873
|
+
features: baseFeatures({
|
|
874
|
+
sessionContinuity: true,
|
|
875
|
+
approvalAndSandboxControls: true,
|
|
876
|
+
promptControl: true,
|
|
877
|
+
workspaceControls: true,
|
|
878
|
+
}),
|
|
879
|
+
unsupportedInputs: [
|
|
880
|
+
{
|
|
881
|
+
input: "mcpServers",
|
|
882
|
+
behavior: "not_supported",
|
|
883
|
+
details: "Cursor owns MCP config via `cursor-agent mcp`; gateway request-time MCP server injection is not implemented.",
|
|
884
|
+
},
|
|
885
|
+
{
|
|
886
|
+
input: 'transport:"acp" with CLI-only options',
|
|
887
|
+
behavior: "reject",
|
|
888
|
+
details: "Cursor ACP routing currently accepts prompt/model/session inputs only; mode, outputFormat, workspace, addDir, force, autoReview, sandbox, trust, and prompt/response optimization are rejected instead of silently ignored.",
|
|
889
|
+
},
|
|
890
|
+
],
|
|
891
|
+
},
|
|
808
892
|
};
|
|
809
893
|
const CAPABILITY_CACHE = new Map();
|
|
810
894
|
export function getProviderToolCapabilities(queryOrCli = {}) {
|
|
811
895
|
const query = normalizeQuery(queryOrCli);
|
|
812
|
-
const providers = query.cli ? [query.cli] :
|
|
896
|
+
const providers = query.cli ? [query.cli] : allProviderCapabilityIds(query.providersConfig);
|
|
813
897
|
const entries = providers.map(provider => [
|
|
814
898
|
provider,
|
|
815
899
|
getOneProviderToolCapabilities(provider, query),
|
|
@@ -833,14 +917,35 @@ export function clearProviderToolCapabilitiesCache() {
|
|
|
833
917
|
export function _getCapabilityCacheForTest() {
|
|
834
918
|
return CAPABILITY_CACHE;
|
|
835
919
|
}
|
|
836
|
-
|
|
920
|
+
function providersConfigForQuery(query) {
|
|
921
|
+
return query.providersConfig ?? loadProvidersConfig();
|
|
922
|
+
}
|
|
923
|
+
function enabledApiCapabilityIds(providersConfig) {
|
|
924
|
+
return enabledApiProviders(providersConfig ?? loadProvidersConfig()).map(provider => provider.name);
|
|
925
|
+
}
|
|
926
|
+
function allProviderCapabilityIds(providersConfig) {
|
|
927
|
+
return [
|
|
928
|
+
...new Set([
|
|
929
|
+
...PROVIDER_CAPABILITY_IDS,
|
|
930
|
+
...enabledApiCapabilityIds(providersConfig),
|
|
931
|
+
]),
|
|
932
|
+
];
|
|
933
|
+
}
|
|
934
|
+
export function providerCapabilityIds(providersConfig) {
|
|
935
|
+
return allProviderCapabilityIds(providersConfig);
|
|
936
|
+
}
|
|
937
|
+
export function knownProviderCapabilityIds() {
|
|
837
938
|
return PROVIDER_CAPABILITY_IDS;
|
|
838
939
|
}
|
|
839
940
|
function buildOneProviderToolCapabilities(cli, query) {
|
|
840
941
|
const warnings = [];
|
|
841
942
|
if (!isKnownProviderCapabilityId(cli)) {
|
|
842
|
-
|
|
843
|
-
|
|
943
|
+
const runtime = enabledApiProviders(providersConfigForQuery(query)).find(provider => provider.name === cli);
|
|
944
|
+
if (!runtime) {
|
|
945
|
+
throw new Error(`No tool-capability metadata for provider "${cli}". ` +
|
|
946
|
+
`Known providers: ${allProviderCapabilityIds(query.providersConfig).join(", ")}.`);
|
|
947
|
+
}
|
|
948
|
+
return buildApiProviderToolCapabilities(runtime, query);
|
|
844
949
|
}
|
|
845
950
|
const definition = TOOL_CONTROLS[cli];
|
|
846
951
|
const discoveredSkills = query.includeSkills && cli !== "grok_api" ? discoverSkills(cli, warnings, query) : [];
|
|
@@ -848,7 +953,7 @@ function buildOneProviderToolCapabilities(cli, query) {
|
|
|
848
953
|
? extractProviderTools(cli, discoveredSkills)
|
|
849
954
|
: [];
|
|
850
955
|
const features = { ...definition.features };
|
|
851
|
-
const gatewayRequestTools = cli === "grok_api" && !isXaiProviderEnabled(
|
|
956
|
+
const gatewayRequestTools = cli === "grok_api" && !isXaiProviderEnabled(providersConfigForQuery(query))
|
|
852
957
|
? []
|
|
853
958
|
: [...definition.gatewayRequestTools];
|
|
854
959
|
if (cli === "grok") {
|
|
@@ -864,7 +969,7 @@ function buildOneProviderToolCapabilities(cli, query) {
|
|
|
864
969
|
providerKind: definition.providerKind,
|
|
865
970
|
gatewayRequestTools,
|
|
866
971
|
gatewayRequestTool: gatewayRequestTools[0] ?? definition.gatewayRequestTools[0],
|
|
867
|
-
modelInfo: getModelInfo(cli, query
|
|
972
|
+
modelInfo: getModelInfo(cli, query),
|
|
868
973
|
summary: definition.summary,
|
|
869
974
|
acpContract: { ...ACP_CONTRACT.providers[cli] },
|
|
870
975
|
acp: cloneAcpCapability(ACP_CAPABILITIES[cli]),
|
|
@@ -883,6 +988,172 @@ function buildOneProviderToolCapabilities(cli, query) {
|
|
|
883
988
|
},
|
|
884
989
|
};
|
|
885
990
|
}
|
|
991
|
+
function apiProviderCapabilityDefinition(runtime) {
|
|
992
|
+
const { name, kind } = runtime;
|
|
993
|
+
const forwardsReasoning = kind === "xai-responses";
|
|
994
|
+
const continuity = apiContinuityForKind(kind);
|
|
995
|
+
const continuityTracked = continuity !== "none";
|
|
996
|
+
return {
|
|
997
|
+
providerKind: "api",
|
|
998
|
+
gatewayRequestTools: [`api_${name}_request`],
|
|
999
|
+
summary: `Generic ${kind} API provider "${name}" configured through [providers.${name}]. ` +
|
|
1000
|
+
"HTTP request tool only: no local CLI tools, skills, MCP servers, or workspaces.",
|
|
1001
|
+
controls: {
|
|
1002
|
+
allowlist: {
|
|
1003
|
+
supported: false,
|
|
1004
|
+
behavior: `api_${name}_request has no CLI tool allow-list input.`,
|
|
1005
|
+
},
|
|
1006
|
+
denylist: {
|
|
1007
|
+
supported: false,
|
|
1008
|
+
behavior: `api_${name}_request has no CLI tool deny-list input.`,
|
|
1009
|
+
},
|
|
1010
|
+
mcpServers: {
|
|
1011
|
+
supported: false,
|
|
1012
|
+
behavior: "API requests do not configure or expose MCP servers.",
|
|
1013
|
+
},
|
|
1014
|
+
nativeSkills: {
|
|
1015
|
+
supported: false,
|
|
1016
|
+
behavior: "API requests do not read local provider skills.",
|
|
1017
|
+
},
|
|
1018
|
+
reasoningEffort: forwardsReasoning
|
|
1019
|
+
? {
|
|
1020
|
+
supported: true,
|
|
1021
|
+
requestField: "reasoningEffort",
|
|
1022
|
+
behavior: "Passed to the xAI Responses API reasoning.effort field.",
|
|
1023
|
+
}
|
|
1024
|
+
: {
|
|
1025
|
+
supported: false,
|
|
1026
|
+
requestField: "reasoningEffort",
|
|
1027
|
+
behavior: `Accepted by the schema but ignored by the ${kind} adapter.`,
|
|
1028
|
+
},
|
|
1029
|
+
maxOutputTokens: {
|
|
1030
|
+
supported: true,
|
|
1031
|
+
requestField: "maxOutputTokens",
|
|
1032
|
+
behavior: "Bounds the provider API max output tokens.",
|
|
1033
|
+
},
|
|
1034
|
+
sampling: {
|
|
1035
|
+
supported: true,
|
|
1036
|
+
requestField: "temperature/topP",
|
|
1037
|
+
behavior: "Sampling controls are passed through to the provider API.",
|
|
1038
|
+
},
|
|
1039
|
+
timeout: {
|
|
1040
|
+
supported: true,
|
|
1041
|
+
requestField: "timeoutMs",
|
|
1042
|
+
behavior: "Bounds the API HTTP request timeout.",
|
|
1043
|
+
},
|
|
1044
|
+
session: continuityTracked
|
|
1045
|
+
? {
|
|
1046
|
+
supported: true,
|
|
1047
|
+
requestField: "sessionId/createNewSession",
|
|
1048
|
+
behavior: continuity === "server-side-id"
|
|
1049
|
+
? "Gateway stores the provider continuation handle (previous_response_id) in session metadata."
|
|
1050
|
+
: "Gateway tracks the session (active/owner); stateless adapters resend prior context caller-side without storing conversation content.",
|
|
1051
|
+
}
|
|
1052
|
+
: {
|
|
1053
|
+
supported: false,
|
|
1054
|
+
requestField: "sessionId/createNewSession",
|
|
1055
|
+
behavior: "This provider kind does not support multi-turn continuity.",
|
|
1056
|
+
},
|
|
1057
|
+
},
|
|
1058
|
+
features: baseFeatures({
|
|
1059
|
+
apiProvider: true,
|
|
1060
|
+
structuredTextResponses: true,
|
|
1061
|
+
sessionContinuity: continuityTracked,
|
|
1062
|
+
}),
|
|
1063
|
+
unsupportedInputs: [
|
|
1064
|
+
{
|
|
1065
|
+
input: "localSkills",
|
|
1066
|
+
behavior: "not_supported",
|
|
1067
|
+
details: `api_${name}_request does not inspect local CLI skills.`,
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
input: "allowedTools/disallowedTools",
|
|
1071
|
+
behavior: "not_supported",
|
|
1072
|
+
details: "Tool allow/deny controls are CLI-only and are not routed to the API.",
|
|
1073
|
+
},
|
|
1074
|
+
{
|
|
1075
|
+
input: "workspace/worktree",
|
|
1076
|
+
behavior: "not_supported",
|
|
1077
|
+
details: "API providers have no local workspace or worktree controls.",
|
|
1078
|
+
},
|
|
1079
|
+
],
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
function apiProviderModelInfo(runtime) {
|
|
1083
|
+
const list = runtime.models && runtime.models.length > 0 ? runtime.models : [runtime.defaultModel];
|
|
1084
|
+
const models = {};
|
|
1085
|
+
for (const model of list) {
|
|
1086
|
+
models[model] =
|
|
1087
|
+
model === runtime.defaultModel ? "Configured default model" : "Configured allowlisted model";
|
|
1088
|
+
}
|
|
1089
|
+
if (!(runtime.defaultModel in models)) {
|
|
1090
|
+
models[runtime.defaultModel] = "Configured default model (always permitted)";
|
|
1091
|
+
}
|
|
1092
|
+
return {
|
|
1093
|
+
description: `Generic ${runtime.kind} API provider configured through [providers.${runtime.name}].`,
|
|
1094
|
+
models,
|
|
1095
|
+
defaultModel: runtime.defaultModel,
|
|
1096
|
+
defaultModelSource: `[providers.${runtime.name}].default_model`,
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
function apiProviderConfigSurfaces(runtime) {
|
|
1100
|
+
return [
|
|
1101
|
+
{
|
|
1102
|
+
name: `providers.${runtime.name}`,
|
|
1103
|
+
kind: "gateway",
|
|
1104
|
+
present: true,
|
|
1105
|
+
details: `Generic ${runtime.kind} API provider; secret key material is read only from the named environment variable at request time.`,
|
|
1106
|
+
},
|
|
1107
|
+
{
|
|
1108
|
+
name: "api_key_env",
|
|
1109
|
+
kind: "env",
|
|
1110
|
+
present: runtime.apiKey.length > 0,
|
|
1111
|
+
entries: runtime.apiKeyEnv ? [runtime.apiKeyEnv] : [],
|
|
1112
|
+
details: "Reports only the configured environment variable name and whether a key is resolved (keyless-local providers report false); never the value.",
|
|
1113
|
+
},
|
|
1114
|
+
];
|
|
1115
|
+
}
|
|
1116
|
+
function buildApiProviderToolCapabilities(runtime, query) {
|
|
1117
|
+
const definition = apiProviderCapabilityDefinition(runtime);
|
|
1118
|
+
return {
|
|
1119
|
+
schemaVersion: "provider-tool-capabilities.v2",
|
|
1120
|
+
generatedAt: new Date().toISOString(),
|
|
1121
|
+
cli: runtime.name,
|
|
1122
|
+
providerKind: "api",
|
|
1123
|
+
gatewayRequestTools: [...definition.gatewayRequestTools],
|
|
1124
|
+
gatewayRequestTool: definition.gatewayRequestTools[0],
|
|
1125
|
+
modelInfo: apiProviderModelInfo(runtime),
|
|
1126
|
+
summary: definition.summary,
|
|
1127
|
+
acpContract: {
|
|
1128
|
+
classification: "absent_watchlist",
|
|
1129
|
+
summary: `${runtime.name} is an HTTP API provider with no ACP process transport; watchlist item only.`,
|
|
1130
|
+
},
|
|
1131
|
+
acp: {
|
|
1132
|
+
status: "not_applicable",
|
|
1133
|
+
mediation: "none",
|
|
1134
|
+
targetVersion: `${runtime.kind} API`,
|
|
1135
|
+
entrypoint: null,
|
|
1136
|
+
runtimeEnabled: false,
|
|
1137
|
+
smokeSupported: false,
|
|
1138
|
+
smokeStatus: "unsupported",
|
|
1139
|
+
caveats: ["ACP is a CLI-stdio transport; the HTTP API provider has no ACP surface."],
|
|
1140
|
+
docs: ACP_DOCS_REFERENCE,
|
|
1141
|
+
},
|
|
1142
|
+
controls: cloneControls(definition.controls),
|
|
1143
|
+
features: { ...definition.features },
|
|
1144
|
+
discoveredSkills: [],
|
|
1145
|
+
discoveredProviderTools: [],
|
|
1146
|
+
configSurfaces: apiProviderConfigSurfaces(runtime),
|
|
1147
|
+
unsupportedInputs: query.includeUnsupported ? [...definition.unsupportedInputs] : [],
|
|
1148
|
+
warnings: [],
|
|
1149
|
+
metadata: {
|
|
1150
|
+
deprecatedFields: {
|
|
1151
|
+
gatewayRequestTool: "Use gatewayRequestTools instead.",
|
|
1152
|
+
},
|
|
1153
|
+
cacheTtlMs: CAPABILITY_CACHE_TTL_MS,
|
|
1154
|
+
},
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
886
1157
|
function discoverSkills(cli, warnings, query) {
|
|
887
1158
|
const skills = [];
|
|
888
1159
|
for (const root of skillRoots(cli)) {
|
|
@@ -925,6 +1196,8 @@ function skillRoots(cli) {
|
|
|
925
1196
|
return [{ path: path.join(home, ".vibe", "skills"), source: "user" }];
|
|
926
1197
|
case "devin":
|
|
927
1198
|
return [];
|
|
1199
|
+
case "cursor":
|
|
1200
|
+
return [];
|
|
928
1201
|
}
|
|
929
1202
|
}
|
|
930
1203
|
function readSkill(cli, skillPath, fallbackName, source, warnings, query) {
|
|
@@ -960,6 +1233,7 @@ function normalizeQuery(queryOrCli) {
|
|
|
960
1233
|
includeUnsupported: query.includeUnsupported ?? true,
|
|
961
1234
|
includePaths: query.includePaths ?? false,
|
|
962
1235
|
refresh: query.refresh ?? false,
|
|
1236
|
+
providersConfig: query.providersConfig,
|
|
963
1237
|
};
|
|
964
1238
|
}
|
|
965
1239
|
function capabilityCacheKey(cli, query) {
|
|
@@ -969,8 +1243,33 @@ function capabilityCacheKey(cli, query) {
|
|
|
969
1243
|
includeProviderTools: query.includeProviderTools,
|
|
970
1244
|
includeUnsupported: query.includeUnsupported,
|
|
971
1245
|
includePaths: query.includePaths,
|
|
1246
|
+
providersConfig: query.providersConfig ? providerConfigCacheKey(query.providersConfig) : null,
|
|
972
1247
|
});
|
|
973
1248
|
}
|
|
1249
|
+
function providerConfigCacheKey(config) {
|
|
1250
|
+
return {
|
|
1251
|
+
xai: config.xai
|
|
1252
|
+
? {
|
|
1253
|
+
apiKeyEnv: config.xai.apiKeyEnv,
|
|
1254
|
+
baseUrl: config.xai.baseUrl,
|
|
1255
|
+
defaultModel: config.xai.defaultModel,
|
|
1256
|
+
}
|
|
1257
|
+
: null,
|
|
1258
|
+
providers: Object.fromEntries(Object.entries(config.providers ?? {})
|
|
1259
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
1260
|
+
.map(([name, provider]) => [
|
|
1261
|
+
name,
|
|
1262
|
+
{
|
|
1263
|
+
kind: provider.kind,
|
|
1264
|
+
apiKeyEnv: provider.apiKeyEnv,
|
|
1265
|
+
baseUrl: provider.baseUrl,
|
|
1266
|
+
defaultModel: provider.defaultModel,
|
|
1267
|
+
models: provider.models ?? null,
|
|
1268
|
+
usageInclude: provider.usageInclude ?? null,
|
|
1269
|
+
},
|
|
1270
|
+
])),
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
974
1273
|
function baseFeatures(overrides) {
|
|
975
1274
|
const names = [
|
|
976
1275
|
"gatewayRequestTools",
|
|
@@ -1012,11 +1311,11 @@ function baseFeatures(overrides) {
|
|
|
1012
1311
|
function cloneControls(controls) {
|
|
1013
1312
|
return Object.fromEntries(Object.entries(controls).map(([name, control]) => [name, { name, ...control }]));
|
|
1014
1313
|
}
|
|
1015
|
-
function getModelInfo(cli,
|
|
1314
|
+
function getModelInfo(cli, query) {
|
|
1016
1315
|
if (cli !== "grok_api") {
|
|
1017
|
-
return getAvailableCliInfo(refresh)[cli];
|
|
1316
|
+
return getAvailableCliInfo(query.refresh)[cli];
|
|
1018
1317
|
}
|
|
1019
|
-
const providers =
|
|
1318
|
+
const providers = providersConfigForQuery(query);
|
|
1020
1319
|
const enabled = isXaiProviderEnabled(providers);
|
|
1021
1320
|
const defaultModel = providers.xai?.defaultModel;
|
|
1022
1321
|
return {
|
|
@@ -1031,7 +1330,7 @@ function getModelInfo(cli, refresh) {
|
|
|
1031
1330
|
}
|
|
1032
1331
|
function discoverConfigSurfaces(cli, query, discoveredSkills) {
|
|
1033
1332
|
if (cli === "grok_api") {
|
|
1034
|
-
const providers =
|
|
1333
|
+
const providers = providersConfigForQuery(query);
|
|
1035
1334
|
return [
|
|
1036
1335
|
{
|
|
1037
1336
|
name: "providers.xai",
|