llm-cli-gateway 2.12.2 → 2.13.1
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/.agents/skills/multi-llm-review/SKILL.md +111 -16
- package/CHANGELOG.md +50 -1
- package/README.md +48 -17
- package/dist/acp/errors.js +59 -3
- package/dist/acp/provider-registry.js +13 -0
- package/dist/api-http.js +16 -2
- package/dist/approval-manager.d.ts +1 -1
- package/dist/async-job-manager.d.ts +1 -1
- package/dist/cli-updater.d.ts +1 -1
- package/dist/cli-updater.js +17 -9
- package/dist/config.js +2 -8
- package/dist/doctor.d.ts +2 -2
- package/dist/doctor.js +2 -7
- package/dist/executor.js +2 -0
- package/dist/index.d.ts +52 -10
- package/dist/index.js +607 -19
- package/dist/model-registry.d.ts +1 -1
- package/dist/model-registry.js +12 -16
- package/dist/oauth.js +5 -1
- package/dist/provider-login-guidance.js +18 -0
- package/dist/provider-status.d.ts +1 -1
- package/dist/provider-status.js +8 -13
- package/dist/provider-tool-capabilities.js +85 -0
- package/dist/provider-types.d.ts +4 -0
- package/dist/provider-types.js +10 -0
- 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-orchestrator.js +4 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/setup/status.schema.json +3 -3
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import { estimateTokens, optimizePrompt as optimizePromptText, optimizeResponse
|
|
|
21
21
|
import { loadConfig, loadPersistenceConfig, loadCacheAwarenessConfig, loadProvidersConfig, loadAcpConfig, loadLimitsConfig, defaultGatewayConfigPath, isXaiProviderEnabled, enabledApiProviders, minStableTokensForModel, } from "./config.js";
|
|
22
22
|
import { runAcpRequest } from "./acp/runtime.js";
|
|
23
23
|
import { isAcpError } from "./acp/errors.js";
|
|
24
|
+
import { redactSecrets } from "./secret-redaction.js";
|
|
24
25
|
import { createApiProvider, runApiRequest, apiProviderBreakerState, } from "./api-provider.js";
|
|
25
26
|
import { prepareApiRequest, apiProviderCatalogEntry, ApiModelNotAllowedError, } from "./api-request.js";
|
|
26
27
|
import { checkHealth } from "./health.js";
|
|
@@ -47,22 +48,65 @@ import { currentCaller, resolveValidationReceipt } from "./validation-receipt.js
|
|
|
47
48
|
import { assertUpstreamCliArgs, assertUpstreamCliEnv, buildProviderSubcommandsCompactCatalog, buildUpstreamContractReport, getCliSubcommandContract, probeInstalledCliContract, serializeCliSubcommandContract, UPSTREAM_CLI_CONTRACTS, } from "./upstream-contracts.js";
|
|
48
49
|
import { buildArgvFromGeneration, deriveZodShapeFromGeneration, GROK_FLAG_GENERATION, GROK_GEN_OUTPUT_FORMAT, GROK_GEN_MAIN, GROK_GEN_PROMPT_FILE, GROK_GEN_SINGLE, GROK_GEN_TAIL, } from "./provider-codegen.js";
|
|
49
50
|
import { entrypointFileURL } from "./entrypoint-url.js";
|
|
50
|
-
const logger = {
|
|
51
|
+
export const logger = {
|
|
51
52
|
info: (message, ...args) => {
|
|
52
|
-
console.error(
|
|
53
|
+
console.error(formatLogLine("INFO", message), ...args.map(sanitizeLogArg));
|
|
53
54
|
},
|
|
54
55
|
warn: (message, ...args) => {
|
|
55
|
-
console.error(
|
|
56
|
+
console.error(formatLogLine("WARN", message), ...args.map(sanitizeLogArg));
|
|
56
57
|
},
|
|
57
58
|
error: (message, ...args) => {
|
|
58
|
-
console.error(
|
|
59
|
+
console.error(formatLogLine("ERROR", message), ...args.map(sanitizeLogArg));
|
|
59
60
|
},
|
|
60
61
|
debug: (message, ...args) => {
|
|
61
62
|
if (process.env.DEBUG) {
|
|
62
|
-
console.error(
|
|
63
|
+
console.error(formatLogLine("DEBUG", message), ...args.map(sanitizeLogArg));
|
|
63
64
|
}
|
|
64
65
|
},
|
|
65
66
|
};
|
|
67
|
+
function formatLogLine(level, message) {
|
|
68
|
+
return `[${level}] ${new Date().toISOString()} - ${redactSecrets(message)}`;
|
|
69
|
+
}
|
|
70
|
+
function sanitizeLogArg(value) {
|
|
71
|
+
return sanitizeLogValue(value, new WeakSet(), 0);
|
|
72
|
+
}
|
|
73
|
+
function sanitizeLogValue(value, seen, depth) {
|
|
74
|
+
if (typeof value === "string") {
|
|
75
|
+
return redactSecrets(value);
|
|
76
|
+
}
|
|
77
|
+
if (value instanceof Error) {
|
|
78
|
+
return sanitizeLogError(value);
|
|
79
|
+
}
|
|
80
|
+
if (Array.isArray(value)) {
|
|
81
|
+
if (depth >= 4)
|
|
82
|
+
return "[Array]";
|
|
83
|
+
return value.map(item => sanitizeLogValue(item, seen, depth + 1));
|
|
84
|
+
}
|
|
85
|
+
if (value !== null && typeof value === "object") {
|
|
86
|
+
if (seen.has(value))
|
|
87
|
+
return "[Circular]";
|
|
88
|
+
if (depth >= 4)
|
|
89
|
+
return "[Object]";
|
|
90
|
+
seen.add(value);
|
|
91
|
+
const out = {};
|
|
92
|
+
for (const [key, inner] of Object.entries(value)) {
|
|
93
|
+
out[key] = sanitizeLogValue(inner, seen, depth + 1);
|
|
94
|
+
}
|
|
95
|
+
seen.delete(value);
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
function sanitizeLogError(error) {
|
|
101
|
+
const out = {
|
|
102
|
+
name: redactSecrets(error.name),
|
|
103
|
+
message: redactSecrets(error.message),
|
|
104
|
+
};
|
|
105
|
+
if (error.stack) {
|
|
106
|
+
out.stack = redactSecrets(error.stack);
|
|
107
|
+
}
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
66
110
|
function startWindowsBootstrapperSelfHeal() {
|
|
67
111
|
if (process.platform !== "win32")
|
|
68
112
|
return;
|
|
@@ -162,7 +206,7 @@ export function buildServerInstructions(asyncJobsEnabled, grokApiToolsEnabled =
|
|
|
162
206
|
: '- Async jobs are DISABLED (persistence.backend = "none"): *_request_async and llm_job_* tools are not registered, and sync requests run to completion (no auto-deferral).';
|
|
163
207
|
return `llm-cli-gateway: Multi-LLM orchestration via MCP.
|
|
164
208
|
|
|
165
|
-
Tools: claude_request, codex_request, gemini_request, grok_request, mistral_request, devin_request${apiToolsNote} (sync)${asyncToolsNote} | codex_fork_session (fork a Codex session into a new branch)
|
|
209
|
+
Tools: claude_request, codex_request, gemini_request, grok_request, mistral_request, devin_request, cursor_request${apiToolsNote} (sync)${asyncToolsNote} | codex_fork_session (fork a Codex session into a new branch)
|
|
166
210
|
Validation: validate_with_models, second_opinion, compare_answers, red_team_review, consensus_check, ask_model, synthesize_validation, list_available_models | job_status/job_result (validation jobs)
|
|
167
211
|
${jobsLine}Sessions: session_create, session_list, session_set_active, session_get, session_delete, session_clear_all
|
|
168
212
|
Other: list_models, provider_tool_capabilities, cli_versions, upstream_contracts, provider_subcommands_* (read-only subcommand contract/drift introspection), cli_upgrade, approval_list, llm_process_health, llm_request_result (read back any persisted request, sync or async, by correlationId)
|
|
@@ -345,6 +389,7 @@ const CLI_IDLE_TIMEOUTS = {
|
|
|
345
389
|
gemini: 600_000,
|
|
346
390
|
grok: 600_000,
|
|
347
391
|
mistral: 600_000,
|
|
392
|
+
cursor: 600_000,
|
|
348
393
|
};
|
|
349
394
|
function resolveIdleTimeout(cli, override) {
|
|
350
395
|
if (override !== undefined)
|
|
@@ -884,6 +929,7 @@ export function createErrorResponse(cli, code, stderr, correlationId, error, api
|
|
|
884
929
|
grok: "Run `grok login` (or re-authenticate) and retry.",
|
|
885
930
|
mistral: "Run `vibe --setup` (or re-authenticate) and retry.",
|
|
886
931
|
devin: "Run `devin auth login` (or set WINDSURF_API_KEY) and retry.",
|
|
932
|
+
cursor: "Run `cursor-agent login` (or set CURSOR_API_KEY) and retry.",
|
|
887
933
|
};
|
|
888
934
|
if (error) {
|
|
889
935
|
errorMessage += `:\n${error.message}`;
|
|
@@ -2413,7 +2459,7 @@ export async function handleGrokApiRequest(deps, params) {
|
|
|
2413
2459
|
}
|
|
2414
2460
|
const apiKey = process.env[xaiConfig.apiKeyEnv]?.trim();
|
|
2415
2461
|
if (!apiKey) {
|
|
2416
|
-
return createErrorResponse("grok_api_request", 1, "", corrId, new Error(
|
|
2462
|
+
return createErrorResponse("grok_api_request", 1, "", corrId, new Error("xAI API key is not configured"));
|
|
2417
2463
|
}
|
|
2418
2464
|
safeFlightStart({
|
|
2419
2465
|
correlationId: corrId,
|
|
@@ -3647,6 +3693,347 @@ export async function handleDevinRequestAsync(deps, params) {
|
|
|
3647
3693
|
return createErrorResponse("devin_request_async", 1, "", corrId, error);
|
|
3648
3694
|
}
|
|
3649
3695
|
}
|
|
3696
|
+
export function prepareCursorRequest(params, runtime) {
|
|
3697
|
+
const corrId = params.correlationId ?? randomUUID();
|
|
3698
|
+
let prompt = (params.prompt ?? "").trim();
|
|
3699
|
+
if (!prompt) {
|
|
3700
|
+
return createErrorResponse(params.operation, 1, "prompt is required and cannot be empty", corrId);
|
|
3701
|
+
}
|
|
3702
|
+
const reviewIntegrity = checkReviewIntegrity({ prompt });
|
|
3703
|
+
if (reviewIntegrity.violations.length > 0) {
|
|
3704
|
+
runtime.logger.info(`[${corrId}] Review integrity violations detected: ${reviewIntegrity.violations.map(v => v.type).join(", ")}`, {
|
|
3705
|
+
cli: "cursor",
|
|
3706
|
+
operation: params.operation,
|
|
3707
|
+
score: reviewIntegrity.totalScore,
|
|
3708
|
+
});
|
|
3709
|
+
}
|
|
3710
|
+
const highImpactRequested = Boolean(params.force) || Boolean(params.trust) || params.sandbox === "disabled";
|
|
3711
|
+
let approvalDecision = null;
|
|
3712
|
+
if (params.approvalStrategy === "mcp_managed") {
|
|
3713
|
+
approvalDecision = runtime.approvalManager.decide({
|
|
3714
|
+
cli: "cursor",
|
|
3715
|
+
operation: params.operation,
|
|
3716
|
+
prompt,
|
|
3717
|
+
bypassRequested: highImpactRequested,
|
|
3718
|
+
fullAuto: false,
|
|
3719
|
+
requestedMcpServers: [],
|
|
3720
|
+
policy: params.approvalPolicy,
|
|
3721
|
+
metadata: {
|
|
3722
|
+
model: params.model ?? "default",
|
|
3723
|
+
force: Boolean(params.force),
|
|
3724
|
+
trust: Boolean(params.trust),
|
|
3725
|
+
sandbox: params.sandbox ?? null,
|
|
3726
|
+
},
|
|
3727
|
+
reviewIntegrity,
|
|
3728
|
+
});
|
|
3729
|
+
if (approvalDecision.status !== "approved") {
|
|
3730
|
+
return createApprovalDeniedResponse(params.operation, approvalDecision);
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
if (params.optimizePrompt)
|
|
3734
|
+
prompt = optimizePromptText(prompt);
|
|
3735
|
+
const resolvedModel = resolveModelAlias("cursor", params.model, getCliInfo());
|
|
3736
|
+
const args = ["--print"];
|
|
3737
|
+
const outputFormat = params.outputFormat ?? "text";
|
|
3738
|
+
if (outputFormat !== "text")
|
|
3739
|
+
args.push("--output-format", outputFormat);
|
|
3740
|
+
if (resolvedModel)
|
|
3741
|
+
args.push("--model", resolvedModel);
|
|
3742
|
+
if (params.mode)
|
|
3743
|
+
args.push("--mode", params.mode);
|
|
3744
|
+
if (params.force)
|
|
3745
|
+
args.push("--force");
|
|
3746
|
+
if (params.autoReview)
|
|
3747
|
+
args.push("--auto-review");
|
|
3748
|
+
if (params.sandbox)
|
|
3749
|
+
args.push("--sandbox", params.sandbox);
|
|
3750
|
+
if (params.trust)
|
|
3751
|
+
args.push("--trust");
|
|
3752
|
+
if (params.workspace)
|
|
3753
|
+
args.push("--workspace", params.workspace);
|
|
3754
|
+
for (const dir of params.addDir ?? [])
|
|
3755
|
+
args.push("--add-dir", dir);
|
|
3756
|
+
args.push(prompt);
|
|
3757
|
+
return {
|
|
3758
|
+
corrId,
|
|
3759
|
+
effectivePrompt: prompt,
|
|
3760
|
+
resolvedModel,
|
|
3761
|
+
requestedMcpServers: [],
|
|
3762
|
+
approvalDecision,
|
|
3763
|
+
reviewIntegrity,
|
|
3764
|
+
args,
|
|
3765
|
+
stablePrefixHash: null,
|
|
3766
|
+
stablePrefixTokens: null,
|
|
3767
|
+
};
|
|
3768
|
+
}
|
|
3769
|
+
function rejectUnsupportedCursorAcpParams(params, operation) {
|
|
3770
|
+
const unsupported = [];
|
|
3771
|
+
if (params.mode)
|
|
3772
|
+
unsupported.push("mode");
|
|
3773
|
+
if (params.outputFormat && params.outputFormat !== "text")
|
|
3774
|
+
unsupported.push("outputFormat");
|
|
3775
|
+
if (params.force)
|
|
3776
|
+
unsupported.push("force");
|
|
3777
|
+
if (params.autoReview)
|
|
3778
|
+
unsupported.push("autoReview");
|
|
3779
|
+
if (params.sandbox)
|
|
3780
|
+
unsupported.push("sandbox");
|
|
3781
|
+
if (params.trust)
|
|
3782
|
+
unsupported.push("trust");
|
|
3783
|
+
if (params.workspace)
|
|
3784
|
+
unsupported.push("workspace");
|
|
3785
|
+
if ((params.addDir ?? []).length > 0)
|
|
3786
|
+
unsupported.push("addDir");
|
|
3787
|
+
if (params.resumeLatest)
|
|
3788
|
+
unsupported.push("resumeLatest");
|
|
3789
|
+
if (params.createNewSession)
|
|
3790
|
+
unsupported.push("createNewSession");
|
|
3791
|
+
if (params.optimizePrompt)
|
|
3792
|
+
unsupported.push("optimizePrompt");
|
|
3793
|
+
if (params.optimizeResponse)
|
|
3794
|
+
unsupported.push("optimizeResponse");
|
|
3795
|
+
if (params.forceRefresh)
|
|
3796
|
+
unsupported.push("forceRefresh");
|
|
3797
|
+
if (unsupported.length === 0)
|
|
3798
|
+
return null;
|
|
3799
|
+
return createErrorResponse(operation, 1, "", params.correlationId, new Error(`cursor_request transport=acp does not support these Cursor CLI-only options yet: ${unsupported.join(", ")}. Omit them or use transport=cli.`));
|
|
3800
|
+
}
|
|
3801
|
+
export async function handleCursorRequest(deps, params) {
|
|
3802
|
+
if (params.transport === "acp") {
|
|
3803
|
+
const unsupported = rejectUnsupportedCursorAcpParams(params, "cursor_request");
|
|
3804
|
+
if (unsupported)
|
|
3805
|
+
return unsupported;
|
|
3806
|
+
return runAcpTransport(deps, {
|
|
3807
|
+
provider: "cursor",
|
|
3808
|
+
prompt: params.prompt,
|
|
3809
|
+
model: params.model,
|
|
3810
|
+
sessionId: params.sessionId,
|
|
3811
|
+
correlationId: params.correlationId,
|
|
3812
|
+
});
|
|
3813
|
+
}
|
|
3814
|
+
const runtime = resolveHandlerRuntime(deps);
|
|
3815
|
+
const startTime = Date.now();
|
|
3816
|
+
const prep = prepareCursorRequest({
|
|
3817
|
+
prompt: params.prompt,
|
|
3818
|
+
model: params.model,
|
|
3819
|
+
mode: params.mode,
|
|
3820
|
+
outputFormat: params.outputFormat,
|
|
3821
|
+
force: params.force,
|
|
3822
|
+
autoReview: params.autoReview,
|
|
3823
|
+
sandbox: params.sandbox,
|
|
3824
|
+
trust: params.trust,
|
|
3825
|
+
workspace: params.workspace,
|
|
3826
|
+
addDir: params.addDir,
|
|
3827
|
+
approvalStrategy: params.approvalStrategy,
|
|
3828
|
+
approvalPolicy: params.approvalPolicy,
|
|
3829
|
+
correlationId: params.correlationId,
|
|
3830
|
+
optimizePrompt: params.optimizePrompt,
|
|
3831
|
+
operation: "cursor_request",
|
|
3832
|
+
}, runtime);
|
|
3833
|
+
if (!("args" in prep))
|
|
3834
|
+
return prep;
|
|
3835
|
+
const { corrId, args } = prep;
|
|
3836
|
+
let durationMs = 0;
|
|
3837
|
+
let wasSuccessful = false;
|
|
3838
|
+
safeFlightStart({
|
|
3839
|
+
correlationId: corrId,
|
|
3840
|
+
cli: "cursor",
|
|
3841
|
+
model: prep.resolvedModel || "default",
|
|
3842
|
+
prompt: prep.effectivePrompt,
|
|
3843
|
+
sessionId: params.sessionId,
|
|
3844
|
+
}, runtime);
|
|
3845
|
+
try {
|
|
3846
|
+
const sessionResult = resolveGrokSessionArgs({
|
|
3847
|
+
sessionId: params.sessionId,
|
|
3848
|
+
resumeLatest: params.resumeLatest,
|
|
3849
|
+
createNewSession: params.createNewSession,
|
|
3850
|
+
});
|
|
3851
|
+
if (sessionResult.userProvidedSession) {
|
|
3852
|
+
await getExistingSessionForProvider(deps.sessionManager, sessionResult.effectiveSessionId, "cursor");
|
|
3853
|
+
}
|
|
3854
|
+
args.splice(args.length - 1, 0, ...sessionResult.resumeArgs);
|
|
3855
|
+
let worktreeResolution = {};
|
|
3856
|
+
try {
|
|
3857
|
+
worktreeResolution = await resolveWorkspaceAndWorktreeForRequest({
|
|
3858
|
+
provider: "cursor",
|
|
3859
|
+
workspace: params.workspace,
|
|
3860
|
+
sessionId: sessionResult.effectiveSessionId,
|
|
3861
|
+
runtime,
|
|
3862
|
+
addDir: params.addDir,
|
|
3863
|
+
});
|
|
3864
|
+
}
|
|
3865
|
+
catch (err) {
|
|
3866
|
+
return createErrorResponse("cursor_request", 1, "", corrId, err);
|
|
3867
|
+
}
|
|
3868
|
+
if (worktreeResolution.workspace && params.workspace) {
|
|
3869
|
+
const workspaceFlagIndex = args.indexOf("--workspace");
|
|
3870
|
+
if (workspaceFlagIndex >= 0 && workspaceFlagIndex + 1 < args.length) {
|
|
3871
|
+
args[workspaceFlagIndex + 1] = worktreeResolution.workspace.cwd;
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
const cursorFrHandoff = buildAsyncFlightRecorderHandoff("cursor", prep, params.sessionId, params.outputFormat);
|
|
3875
|
+
const result = await awaitJobOrDefer("cursor", args, corrId, resolveIdleTimeout("cursor", params.idleTimeoutMs), params.outputFormat, params.forceRefresh, runtime, undefined, undefined, cursorFrHandoff.flightRecorderEntry, cursorFrHandoff.extractUsage, undefined, worktreeResolution.cwd);
|
|
3876
|
+
if (isDeferredResponse(result)) {
|
|
3877
|
+
return buildDeferredToolResponse(result, sessionResult.effectiveSessionId);
|
|
3878
|
+
}
|
|
3879
|
+
const { stdout, stderr, code } = result;
|
|
3880
|
+
durationMs = Math.max(0, Date.now() - startTime);
|
|
3881
|
+
if (code !== 0) {
|
|
3882
|
+
safeFlightComplete(corrId, {
|
|
3883
|
+
response: stderr || "",
|
|
3884
|
+
durationMs,
|
|
3885
|
+
retryCount: 0,
|
|
3886
|
+
circuitBreakerState: "closed",
|
|
3887
|
+
optimizationApplied: false,
|
|
3888
|
+
exitCode: code,
|
|
3889
|
+
errorMessage: stderr || `Exit code ${code}`,
|
|
3890
|
+
status: "failed",
|
|
3891
|
+
}, runtime);
|
|
3892
|
+
return createErrorResponse("cursor", code, stderr, corrId);
|
|
3893
|
+
}
|
|
3894
|
+
wasSuccessful = true;
|
|
3895
|
+
let effectiveSessionId = sessionResult.effectiveSessionId;
|
|
3896
|
+
if (sessionResult.userProvidedSession && effectiveSessionId) {
|
|
3897
|
+
const existing = await deps.sessionManager.getSession(effectiveSessionId);
|
|
3898
|
+
if (!existing) {
|
|
3899
|
+
try {
|
|
3900
|
+
await deps.sessionManager.createSession("cursor", "Cursor Session", effectiveSessionId);
|
|
3901
|
+
}
|
|
3902
|
+
catch {
|
|
3903
|
+
const rechecked = await deps.sessionManager.getSession(effectiveSessionId);
|
|
3904
|
+
if (!rechecked)
|
|
3905
|
+
throw new Error(`Failed to create or find session ${effectiveSessionId}`);
|
|
3906
|
+
}
|
|
3907
|
+
}
|
|
3908
|
+
await deps.sessionManager.updateSessionUsage(effectiveSessionId);
|
|
3909
|
+
}
|
|
3910
|
+
else if (!params.createNewSession && !effectiveSessionId) {
|
|
3911
|
+
const newSession = await deps.sessionManager.createSession("cursor", "Cursor Session", `${GATEWAY_SESSION_PREFIX}${randomUUID()}`);
|
|
3912
|
+
effectiveSessionId = newSession.id;
|
|
3913
|
+
}
|
|
3914
|
+
const response = buildCliResponse("cursor", stdout, params.optimizeResponse ?? false, corrId, effectiveSessionId, prep, durationMs, sessionResult.userProvidedSession, params.outputFormat);
|
|
3915
|
+
safeFlightComplete(corrId, {
|
|
3916
|
+
response: stdout,
|
|
3917
|
+
durationMs,
|
|
3918
|
+
retryCount: 0,
|
|
3919
|
+
circuitBreakerState: "closed",
|
|
3920
|
+
optimizationApplied: params.optimizePrompt || (params.optimizeResponse ?? false),
|
|
3921
|
+
exitCode: 0,
|
|
3922
|
+
status: "completed",
|
|
3923
|
+
}, runtime);
|
|
3924
|
+
return response;
|
|
3925
|
+
}
|
|
3926
|
+
catch (error) {
|
|
3927
|
+
const elapsedMs = Math.max(0, Date.now() - startTime);
|
|
3928
|
+
safeFlightComplete(corrId, {
|
|
3929
|
+
response: "",
|
|
3930
|
+
durationMs: elapsedMs,
|
|
3931
|
+
retryCount: 0,
|
|
3932
|
+
circuitBreakerState: "closed",
|
|
3933
|
+
optimizationApplied: false,
|
|
3934
|
+
exitCode: 1,
|
|
3935
|
+
errorMessage: error.message,
|
|
3936
|
+
status: "failed",
|
|
3937
|
+
}, runtime);
|
|
3938
|
+
return createErrorResponse("cursor", 1, "", corrId, error);
|
|
3939
|
+
}
|
|
3940
|
+
finally {
|
|
3941
|
+
runtime.performanceMetrics.recordRequest("cursor", Math.max(0, durationMs || Date.now() - startTime), wasSuccessful);
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
export async function handleCursorRequestAsync(deps, params) {
|
|
3945
|
+
const runtime = resolveHandlerRuntime(deps);
|
|
3946
|
+
const prep = prepareCursorRequest({
|
|
3947
|
+
prompt: params.prompt,
|
|
3948
|
+
model: params.model,
|
|
3949
|
+
mode: params.mode,
|
|
3950
|
+
outputFormat: params.outputFormat,
|
|
3951
|
+
force: params.force,
|
|
3952
|
+
autoReview: params.autoReview,
|
|
3953
|
+
sandbox: params.sandbox,
|
|
3954
|
+
trust: params.trust,
|
|
3955
|
+
workspace: params.workspace,
|
|
3956
|
+
addDir: params.addDir,
|
|
3957
|
+
approvalStrategy: params.approvalStrategy,
|
|
3958
|
+
approvalPolicy: params.approvalPolicy,
|
|
3959
|
+
correlationId: params.correlationId,
|
|
3960
|
+
optimizePrompt: params.optimizePrompt,
|
|
3961
|
+
operation: "cursor_request_async",
|
|
3962
|
+
}, runtime);
|
|
3963
|
+
if (!("args" in prep))
|
|
3964
|
+
return prep;
|
|
3965
|
+
const { corrId, args } = prep;
|
|
3966
|
+
try {
|
|
3967
|
+
const sessionResult = resolveGrokSessionArgs({
|
|
3968
|
+
sessionId: params.sessionId,
|
|
3969
|
+
resumeLatest: params.resumeLatest,
|
|
3970
|
+
createNewSession: params.createNewSession,
|
|
3971
|
+
});
|
|
3972
|
+
if (sessionResult.userProvidedSession) {
|
|
3973
|
+
await getExistingSessionForProvider(deps.sessionManager, sessionResult.effectiveSessionId, "cursor");
|
|
3974
|
+
}
|
|
3975
|
+
args.splice(args.length - 1, 0, ...sessionResult.resumeArgs);
|
|
3976
|
+
let effectiveSessionId = sessionResult.effectiveSessionId;
|
|
3977
|
+
if (sessionResult.userProvidedSession && effectiveSessionId) {
|
|
3978
|
+
const existing = await deps.sessionManager.getSession(effectiveSessionId);
|
|
3979
|
+
if (!existing) {
|
|
3980
|
+
try {
|
|
3981
|
+
await deps.sessionManager.createSession("cursor", "Cursor Session", effectiveSessionId);
|
|
3982
|
+
}
|
|
3983
|
+
catch {
|
|
3984
|
+
const rechecked = await deps.sessionManager.getSession(effectiveSessionId);
|
|
3985
|
+
if (!rechecked)
|
|
3986
|
+
throw new Error(`Failed to create or find session ${effectiveSessionId}`);
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
await deps.sessionManager.updateSessionUsage(effectiveSessionId);
|
|
3990
|
+
}
|
|
3991
|
+
else if (!params.createNewSession && !effectiveSessionId) {
|
|
3992
|
+
const newSession = await deps.sessionManager.createSession("cursor", "Cursor Session", `${GATEWAY_SESSION_PREFIX}${randomUUID()}`);
|
|
3993
|
+
effectiveSessionId = newSession.id;
|
|
3994
|
+
}
|
|
3995
|
+
let worktreeResolution = {};
|
|
3996
|
+
try {
|
|
3997
|
+
worktreeResolution = await resolveWorkspaceAndWorktreeForRequest({
|
|
3998
|
+
provider: "cursor",
|
|
3999
|
+
workspace: params.workspace,
|
|
4000
|
+
sessionId: effectiveSessionId,
|
|
4001
|
+
runtime,
|
|
4002
|
+
addDir: params.addDir,
|
|
4003
|
+
});
|
|
4004
|
+
}
|
|
4005
|
+
catch (err) {
|
|
4006
|
+
return createErrorResponse("cursor_request_async", 1, "", corrId, err);
|
|
4007
|
+
}
|
|
4008
|
+
if (worktreeResolution.workspace && params.workspace) {
|
|
4009
|
+
const workspaceFlagIndex = args.indexOf("--workspace");
|
|
4010
|
+
if (workspaceFlagIndex >= 0 && workspaceFlagIndex + 1 < args.length) {
|
|
4011
|
+
args[workspaceFlagIndex + 1] = worktreeResolution.workspace.cwd;
|
|
4012
|
+
}
|
|
4013
|
+
}
|
|
4014
|
+
assertUpstreamCliArgs("cursor", args);
|
|
4015
|
+
assertUpstreamCliEnv("cursor", undefined);
|
|
4016
|
+
const cursorAsyncFrHandoff = buildAsyncFlightRecorderHandoff("cursor", prep, effectiveSessionId, params.outputFormat);
|
|
4017
|
+
const job = deps.asyncJobManager.startJob("cursor", args, corrId, worktreeResolution.cwd, resolveIdleTimeout("cursor", params.idleTimeoutMs), params.outputFormat, params.forceRefresh, undefined, undefined, cursorAsyncFrHandoff.flightRecorderEntry, cursorAsyncFrHandoff.extractUsage, true);
|
|
4018
|
+
deps.logger.info(`[${corrId}] cursor_request_async started job ${job.id}`);
|
|
4019
|
+
return {
|
|
4020
|
+
content: [
|
|
4021
|
+
{
|
|
4022
|
+
type: "text",
|
|
4023
|
+
text: JSON.stringify({
|
|
4024
|
+
success: true,
|
|
4025
|
+
job,
|
|
4026
|
+
sessionId: effectiveSessionId || null,
|
|
4027
|
+
resumable: sessionResult.userProvidedSession,
|
|
4028
|
+
}, null, 2),
|
|
4029
|
+
},
|
|
4030
|
+
],
|
|
4031
|
+
};
|
|
4032
|
+
}
|
|
4033
|
+
catch (error) {
|
|
4034
|
+
return createErrorResponse("cursor_request_async", 1, "", corrId, error);
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
3650
4037
|
export async function handleMistralRequest(deps, params) {
|
|
3651
4038
|
if (params.transport === "acp") {
|
|
3652
4039
|
return runAcpTransport(deps, {
|
|
@@ -4393,7 +4780,11 @@ export function createGatewayServer(deps = {}) {
|
|
|
4393
4780
|
const claudeSyncFrHandoff = buildAsyncFlightRecorderHandoff("claude", prep, effectiveSessionId, outputFormat);
|
|
4394
4781
|
const result = await awaitJobOrDefer("claude", args, corrId, effectiveIdleTimeout, outputFormat, forceRefresh, runtime, undefined, undefined, claudeSyncFrHandoff.flightRecorderEntry, claudeSyncFrHandoff.extractUsage, prep.stdinPayload, worktreeResolution.cwd);
|
|
4395
4782
|
if (isDeferredResponse(result)) {
|
|
4396
|
-
|
|
4783
|
+
const deferred = buildDeferredToolResponse(result, effectiveSessionId);
|
|
4784
|
+
if (warnings.length > 0) {
|
|
4785
|
+
deferred.warnings = warnings;
|
|
4786
|
+
}
|
|
4787
|
+
return deferred;
|
|
4397
4788
|
}
|
|
4398
4789
|
const { stdout, stderr, code } = result;
|
|
4399
4790
|
durationMs = Math.max(0, Date.now() - startTime);
|
|
@@ -5215,6 +5606,108 @@ export function createGatewayServer(deps = {}) {
|
|
|
5215
5606
|
forceRefresh,
|
|
5216
5607
|
});
|
|
5217
5608
|
});
|
|
5609
|
+
server.tool("cursor_request", "Run a Cursor Agent CLI request synchronously (auto-defers to a pollable job past the sync deadline when async jobs are enabled; otherwise runs to completion). Headless print mode (`cursor-agent --print`).", {
|
|
5610
|
+
prompt: z
|
|
5611
|
+
.string()
|
|
5612
|
+
.min(1, "Prompt cannot be empty")
|
|
5613
|
+
.max(100000, "Prompt too long (max 100k chars)")
|
|
5614
|
+
.describe("Prompt text for Cursor Agent CLI"),
|
|
5615
|
+
model: z.string().optional().describe("Model name or alias passed via --model"),
|
|
5616
|
+
mode: z
|
|
5617
|
+
.enum(["plan", "ask"])
|
|
5618
|
+
.optional()
|
|
5619
|
+
.describe("Cursor execution mode: plan (read-only planning) or ask (Q&A/read-only)"),
|
|
5620
|
+
outputFormat: z
|
|
5621
|
+
.enum(["text", "json", "stream-json"])
|
|
5622
|
+
.default("text")
|
|
5623
|
+
.describe("Cursor --output-format for --print mode"),
|
|
5624
|
+
transport: z
|
|
5625
|
+
.enum(["cli", "acp"])
|
|
5626
|
+
.default("cli")
|
|
5627
|
+
.describe("Transport selector. Default `cli` uses cursor-agent --print. `acp` uses the native cursor-agent acp transport and fails closed unless [acp] and [acp.providers.cursor].runtime_enabled are true."),
|
|
5628
|
+
force: z
|
|
5629
|
+
.boolean()
|
|
5630
|
+
.default(false)
|
|
5631
|
+
.describe("Emit --force (Cursor yolo mode; auto-allows commands unless explicitly denied)"),
|
|
5632
|
+
autoReview: z
|
|
5633
|
+
.boolean()
|
|
5634
|
+
.default(false)
|
|
5635
|
+
.describe("Emit --auto-review (Cursor Smart Auto classifier for tool calls)"),
|
|
5636
|
+
sandbox: z
|
|
5637
|
+
.enum(["enabled", "disabled"])
|
|
5638
|
+
.optional()
|
|
5639
|
+
.describe("Cursor sandbox mode override (--sandbox enabled|disabled)"),
|
|
5640
|
+
trust: z.boolean().default(false).describe("Trust the workspace in headless mode (--trust)"),
|
|
5641
|
+
workspace: z
|
|
5642
|
+
.string()
|
|
5643
|
+
.optional()
|
|
5644
|
+
.describe("Workspace directory or saved workspace name (--workspace). Remote HTTP/OAuth callers must pass a registered workspace alias; local stdio callers may pass local Cursor workspace paths."),
|
|
5645
|
+
addDir: z
|
|
5646
|
+
.array(z.string())
|
|
5647
|
+
.optional()
|
|
5648
|
+
.describe("Additional workspace root directories (--add-dir, repeatable)"),
|
|
5649
|
+
sessionId: z
|
|
5650
|
+
.string()
|
|
5651
|
+
.optional()
|
|
5652
|
+
.describe("Cursor chat/session ID to resume (emits --resume <id>). Note: the gw-* id minted for a brand-new gateway session is not resumable via sessionId; continue with resumeLatest:true."),
|
|
5653
|
+
resumeLatest: z
|
|
5654
|
+
.boolean()
|
|
5655
|
+
.default(false)
|
|
5656
|
+
.describe("Resume the latest Cursor chat (--continue). Note: the gw-* id minted for a brand-new gateway session is not resumable via sessionId; continue with resumeLatest:true."),
|
|
5657
|
+
createNewSession: z.boolean().default(false).describe("Force a new session"),
|
|
5658
|
+
approvalStrategy: z
|
|
5659
|
+
.enum(["legacy", "mcp_managed"])
|
|
5660
|
+
.default("legacy")
|
|
5661
|
+
.describe("Approval strategy: legacy (default) lets Cursor's own flags decide; mcp_managed routes high-impact Cursor controls (force, trust, sandbox disabled) through the gateway approval gate."),
|
|
5662
|
+
approvalPolicy: z
|
|
5663
|
+
.enum(["strict", "balanced", "permissive"])
|
|
5664
|
+
.optional()
|
|
5665
|
+
.describe("Approval policy when approvalStrategy is mcp_managed: strict|balanced|permissive (default balanced). Ignored under legacy strategy."),
|
|
5666
|
+
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
5667
|
+
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
5668
|
+
optimizeResponse: z.boolean().default(false).describe("Optimize response output"),
|
|
5669
|
+
idleTimeoutMs: z
|
|
5670
|
+
.number()
|
|
5671
|
+
.int()
|
|
5672
|
+
.min(30_000)
|
|
5673
|
+
.max(3_600_000)
|
|
5674
|
+
.optional()
|
|
5675
|
+
.describe("Idle timeout in ms (min 30s, max 1h, omit=CLI default)"),
|
|
5676
|
+
forceRefresh: z
|
|
5677
|
+
.boolean()
|
|
5678
|
+
.default(false)
|
|
5679
|
+
.describe("Bypass dedup and force a fresh CLI run even if a recent identical request exists"),
|
|
5680
|
+
}, {
|
|
5681
|
+
title: "Cursor Agent CLI request",
|
|
5682
|
+
readOnlyHint: false,
|
|
5683
|
+
destructiveHint: true,
|
|
5684
|
+
idempotentHint: false,
|
|
5685
|
+
openWorldHint: true,
|
|
5686
|
+
}, async ({ prompt, model, mode, outputFormat, transport, force, autoReview, sandbox, trust, workspace, addDir, sessionId, resumeLatest, createNewSession, approvalStrategy, approvalPolicy, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, }) => {
|
|
5687
|
+
return handleCursorRequest({ sessionManager, logger, runtime }, {
|
|
5688
|
+
prompt,
|
|
5689
|
+
model,
|
|
5690
|
+
mode,
|
|
5691
|
+
outputFormat,
|
|
5692
|
+
transport,
|
|
5693
|
+
force,
|
|
5694
|
+
autoReview,
|
|
5695
|
+
sandbox,
|
|
5696
|
+
trust,
|
|
5697
|
+
workspace,
|
|
5698
|
+
addDir,
|
|
5699
|
+
sessionId,
|
|
5700
|
+
resumeLatest,
|
|
5701
|
+
createNewSession,
|
|
5702
|
+
approvalStrategy,
|
|
5703
|
+
approvalPolicy,
|
|
5704
|
+
correlationId,
|
|
5705
|
+
optimizePrompt,
|
|
5706
|
+
optimizeResponse,
|
|
5707
|
+
idleTimeoutMs,
|
|
5708
|
+
forceRefresh,
|
|
5709
|
+
});
|
|
5710
|
+
});
|
|
5218
5711
|
server.tool("mistral_request", "Run a Mistral Vibe CLI request synchronously (when async jobs are enabled, auto-defers to a pollable job past the sync deadline; otherwise runs to completion). Requires exactly one of prompt or promptParts. Defaults to --agent auto-approve (unattended tool execution); pass permissionMode plan or accept-edits for safer runs.", {
|
|
5219
5712
|
prompt: z
|
|
5220
5713
|
.string()
|
|
@@ -6186,6 +6679,101 @@ export function createGatewayServer(deps = {}) {
|
|
|
6186
6679
|
forceRefresh,
|
|
6187
6680
|
});
|
|
6188
6681
|
});
|
|
6682
|
+
server.tool("cursor_request_async", "Start a Cursor Agent CLI request as a durable background job. Poll with llm_job_status, collect with llm_job_result.", {
|
|
6683
|
+
prompt: z
|
|
6684
|
+
.string()
|
|
6685
|
+
.min(1, "Prompt cannot be empty")
|
|
6686
|
+
.max(100000, "Prompt too long (max 100k chars)")
|
|
6687
|
+
.describe("Prompt text for Cursor Agent CLI"),
|
|
6688
|
+
model: z.string().optional().describe("Model name or alias passed via --model"),
|
|
6689
|
+
mode: z
|
|
6690
|
+
.enum(["plan", "ask"])
|
|
6691
|
+
.optional()
|
|
6692
|
+
.describe("Cursor execution mode: plan (read-only planning) or ask (Q&A/read-only)"),
|
|
6693
|
+
outputFormat: z
|
|
6694
|
+
.enum(["text", "json", "stream-json"])
|
|
6695
|
+
.default("text")
|
|
6696
|
+
.describe("Cursor --output-format for --print mode"),
|
|
6697
|
+
force: z
|
|
6698
|
+
.boolean()
|
|
6699
|
+
.default(false)
|
|
6700
|
+
.describe("Emit --force (Cursor yolo mode; auto-allows commands unless explicitly denied)"),
|
|
6701
|
+
autoReview: z
|
|
6702
|
+
.boolean()
|
|
6703
|
+
.default(false)
|
|
6704
|
+
.describe("Emit --auto-review (Cursor Smart Auto classifier for tool calls)"),
|
|
6705
|
+
sandbox: z
|
|
6706
|
+
.enum(["enabled", "disabled"])
|
|
6707
|
+
.optional()
|
|
6708
|
+
.describe("Cursor sandbox mode override (--sandbox enabled|disabled)"),
|
|
6709
|
+
trust: z.boolean().default(false).describe("Trust the workspace in headless mode"),
|
|
6710
|
+
workspace: z
|
|
6711
|
+
.string()
|
|
6712
|
+
.optional()
|
|
6713
|
+
.describe("Workspace directory or saved workspace name (--workspace). Remote HTTP/OAuth callers must pass a registered workspace alias; local stdio callers may pass local Cursor workspace paths."),
|
|
6714
|
+
addDir: z
|
|
6715
|
+
.array(z.string())
|
|
6716
|
+
.optional()
|
|
6717
|
+
.describe("Additional workspace root directories (--add-dir, repeatable)"),
|
|
6718
|
+
sessionId: z
|
|
6719
|
+
.string()
|
|
6720
|
+
.optional()
|
|
6721
|
+
.describe("Cursor chat/session ID to resume (emits --resume <id>). Note: the gw-* id minted for a brand-new gateway session is not resumable via sessionId; continue with resumeLatest:true."),
|
|
6722
|
+
resumeLatest: z
|
|
6723
|
+
.boolean()
|
|
6724
|
+
.default(false)
|
|
6725
|
+
.describe("Resume the latest Cursor chat (--continue). Note: the gw-* id minted for a brand-new gateway session is not resumable via sessionId; continue with resumeLatest:true."),
|
|
6726
|
+
createNewSession: z.boolean().default(false).describe("Force a new session"),
|
|
6727
|
+
approvalStrategy: z
|
|
6728
|
+
.enum(["legacy", "mcp_managed"])
|
|
6729
|
+
.default("legacy")
|
|
6730
|
+
.describe("Approval strategy: legacy (default) lets Cursor's own flags decide; mcp_managed routes high-impact Cursor controls (force, trust, sandbox disabled) through the gateway approval gate."),
|
|
6731
|
+
approvalPolicy: z
|
|
6732
|
+
.enum(["strict", "balanced", "permissive"])
|
|
6733
|
+
.optional()
|
|
6734
|
+
.describe("Approval policy when approvalStrategy is mcp_managed: strict|balanced|permissive (default balanced). Ignored under legacy strategy."),
|
|
6735
|
+
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
6736
|
+
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
6737
|
+
idleTimeoutMs: z
|
|
6738
|
+
.number()
|
|
6739
|
+
.int()
|
|
6740
|
+
.min(30_000)
|
|
6741
|
+
.max(3_600_000)
|
|
6742
|
+
.optional()
|
|
6743
|
+
.describe("Idle timeout in ms (min 30s, max 1h, omit=CLI default)"),
|
|
6744
|
+
forceRefresh: z
|
|
6745
|
+
.boolean()
|
|
6746
|
+
.default(false)
|
|
6747
|
+
.describe("Bypass dedup and force a fresh CLI run even if a recent identical request exists"),
|
|
6748
|
+
}, {
|
|
6749
|
+
title: "Cursor Agent CLI request (async)",
|
|
6750
|
+
readOnlyHint: false,
|
|
6751
|
+
destructiveHint: true,
|
|
6752
|
+
idempotentHint: false,
|
|
6753
|
+
openWorldHint: true,
|
|
6754
|
+
}, async ({ prompt, model, mode, outputFormat, force, autoReview, sandbox, trust, workspace, addDir, sessionId, resumeLatest, createNewSession, approvalStrategy, approvalPolicy, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, }) => {
|
|
6755
|
+
return handleCursorRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
|
|
6756
|
+
prompt,
|
|
6757
|
+
model,
|
|
6758
|
+
mode,
|
|
6759
|
+
outputFormat,
|
|
6760
|
+
force,
|
|
6761
|
+
autoReview,
|
|
6762
|
+
sandbox,
|
|
6763
|
+
trust,
|
|
6764
|
+
workspace,
|
|
6765
|
+
addDir,
|
|
6766
|
+
sessionId,
|
|
6767
|
+
resumeLatest,
|
|
6768
|
+
createNewSession,
|
|
6769
|
+
approvalStrategy,
|
|
6770
|
+
approvalPolicy,
|
|
6771
|
+
correlationId,
|
|
6772
|
+
optimizePrompt,
|
|
6773
|
+
idleTimeoutMs,
|
|
6774
|
+
forceRefresh,
|
|
6775
|
+
});
|
|
6776
|
+
});
|
|
6189
6777
|
server.tool("mistral_request_async", "Start a Mistral Vibe CLI request as a durable background job. Poll with llm_job_status, collect with llm_job_result.", {
|
|
6190
6778
|
prompt: z
|
|
6191
6779
|
.string()
|
|
@@ -6655,10 +7243,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
6655
7243
|
const listModelsFilterValues = [
|
|
6656
7244
|
...new Set([...CLI_TYPES, ...enabledApiProviders(providers).map(p => p.name)]),
|
|
6657
7245
|
];
|
|
6658
|
-
server.tool("list_models", "List models, aliases, and defaults for one provider (claude|codex|gemini|grok|mistral|devin, or an enabled API provider name), or omit cli to list all providers. API providers are returned under an `apiProviders` array.", {
|
|
7246
|
+
server.tool("list_models", "List models, aliases, and defaults for one provider (claude|codex|gemini|grok|mistral|devin|cursor, or an enabled API provider name), or omit cli to list all providers. API providers are returned under an `apiProviders` array.", {
|
|
6659
7247
|
cli: z
|
|
6660
7248
|
.preprocess(value => (value === "" || value === null ? undefined : value), z.enum(listModelsFilterValues).optional())
|
|
6661
|
-
.describe("Provider filter (claude|codex|gemini|grok|mistral|devin, or an enabled API provider name)"),
|
|
7249
|
+
.describe("Provider filter (claude|codex|gemini|grok|mistral|devin|cursor, or an enabled API provider name)"),
|
|
6662
7250
|
}, {
|
|
6663
7251
|
title: "Provider models",
|
|
6664
7252
|
readOnlyHint: true,
|
|
@@ -6689,10 +7277,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
6689
7277
|
...enabledApiProviders(providers).map(p => p.name),
|
|
6690
7278
|
]),
|
|
6691
7279
|
];
|
|
6692
|
-
server.tool("provider_tool_capabilities", "Report provider tool/feature capabilities and discovered local skill/tool integrations for claude|codex|gemini|grok|mistral|devin|grok_api, or an enabled API provider name.", {
|
|
7280
|
+
server.tool("provider_tool_capabilities", "Report provider tool/feature capabilities and discovered local skill/tool integrations for claude|codex|gemini|grok|mistral|devin|cursor|grok_api, or an enabled API provider name.", {
|
|
6693
7281
|
cli: z
|
|
6694
7282
|
.preprocess(value => (value === "" || value === null ? undefined : value), z.enum(providerToolCapabilitiesFilterValues).optional())
|
|
6695
|
-
.describe("Provider filter (claude|codex|gemini|grok|mistral|devin|grok_api, or an enabled API provider name)"),
|
|
7283
|
+
.describe("Provider filter (claude|codex|gemini|grok|mistral|devin|cursor|grok_api, or an enabled API provider name)"),
|
|
6696
7284
|
includeSkills: z
|
|
6697
7285
|
.boolean()
|
|
6698
7286
|
.default(true)
|
|
@@ -6728,10 +7316,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
6728
7316
|
});
|
|
6729
7317
|
return { content: [{ type: "text", text: JSON.stringify(capabilities, null, 2) }] };
|
|
6730
7318
|
});
|
|
6731
|
-
server.tool("cli_versions", "Report installed provider CLI versions, availability, and login status for all
|
|
7319
|
+
server.tool("cli_versions", "Report installed provider CLI versions, availability, and login status for all registered CLI providers (claude|codex|gemini|grok|mistral|devin|cursor) or one.", {
|
|
6732
7320
|
cli: z
|
|
6733
7321
|
.preprocess(value => (value === "" || value === null ? undefined : value), CLI_TYPE_ENUM.optional())
|
|
6734
|
-
.describe("CLI filter (claude|codex|gemini|grok|mistral|devin)"),
|
|
7322
|
+
.describe("CLI filter (claude|codex|gemini|grok|mistral|devin|cursor)"),
|
|
6735
7323
|
}, {
|
|
6736
7324
|
title: "Provider CLI versions",
|
|
6737
7325
|
readOnlyHint: true,
|
|
@@ -6745,7 +7333,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
6745
7333
|
server.tool("upstream_contracts", "Return the gateway's declared provider CLI contracts; with probeInstalled true, diff against installed --help surfaces to detect flag drift.", {
|
|
6746
7334
|
cli: z
|
|
6747
7335
|
.preprocess(value => (value === "" || value === null ? undefined : value), CLI_TYPE_ENUM.optional())
|
|
6748
|
-
.describe("CLI filter (claude|codex|gemini|grok|mistral|devin)"),
|
|
7336
|
+
.describe("CLI filter (claude|codex|gemini|grok|mistral|devin|cursor)"),
|
|
6749
7337
|
probeInstalled: z
|
|
6750
7338
|
.boolean()
|
|
6751
7339
|
.default(false)
|
|
@@ -6763,7 +7351,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
6763
7351
|
server.tool("provider_subcommands_list", "Return a compact, filterable read-only catalog of declared provider CLI subcommands without flags or raw help.", {
|
|
6764
7352
|
provider: z
|
|
6765
7353
|
.preprocess(value => (value === "" || value === null ? undefined : value), CLI_TYPE_ENUM.optional())
|
|
6766
|
-
.describe("Optional provider filter (claude|codex|gemini|grok|mistral|devin)"),
|
|
7354
|
+
.describe("Optional provider filter (claude|codex|gemini|grok|mistral|devin|cursor)"),
|
|
6767
7355
|
tier: z
|
|
6768
7356
|
.enum(["catalog", "inspect", "execute_candidate", "diagnostic"])
|
|
6769
7357
|
.optional()
|
|
@@ -6813,7 +7401,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
6813
7401
|
};
|
|
6814
7402
|
});
|
|
6815
7403
|
server.tool("provider_subcommand_contract", "Return the detailed read-only contract for exactly one declared provider CLI subcommand.", {
|
|
6816
|
-
provider: CLI_TYPE_ENUM.describe("Provider (claude|codex|gemini|grok|mistral|devin)"),
|
|
7404
|
+
provider: CLI_TYPE_ENUM.describe("Provider (claude|codex|gemini|grok|mistral|devin|cursor)"),
|
|
6817
7405
|
commandPath: z.array(z.string().min(1)).min(1).describe("Command path segments"),
|
|
6818
7406
|
}, {
|
|
6819
7407
|
title: "Provider subcommand contract",
|
|
@@ -6837,7 +7425,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
6837
7425
|
server.tool("provider_subcommand_drift", "Probe declared provider subcommand --help surfaces and return compact drift rows without raw help output.", {
|
|
6838
7426
|
provider: z
|
|
6839
7427
|
.preprocess(value => (value === "" || value === null ? undefined : value), CLI_TYPE_ENUM.optional())
|
|
6840
|
-
.describe("Optional provider filter (claude|codex|gemini|grok|mistral|devin)"),
|
|
7428
|
+
.describe("Optional provider filter (claude|codex|gemini|grok|mistral|devin|cursor)"),
|
|
6841
7429
|
includeClean: z
|
|
6842
7430
|
.boolean()
|
|
6843
7431
|
.default(false)
|
|
@@ -6888,7 +7476,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
6888
7476
|
};
|
|
6889
7477
|
});
|
|
6890
7478
|
server.tool("cli_upgrade", "Plan (dryRun, default true) or execute an upgrade for one provider CLI using its native update mechanism.", {
|
|
6891
|
-
cli: CLI_TYPE_ENUM.describe("CLI to upgrade (claude|codex|gemini|grok|mistral|devin)"),
|
|
7479
|
+
cli: CLI_TYPE_ENUM.describe("CLI to upgrade (claude|codex|gemini|grok|mistral|devin|cursor)"),
|
|
6892
7480
|
target: z
|
|
6893
7481
|
.string()
|
|
6894
7482
|
.min(1)
|