@bitkyc08/opencodex 2.79.0 → 2.81.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/README.md +3 -3
- package/gui/dist/assets/App-7yhoys8a.js +54 -0
- package/gui/dist/assets/Tray-EwAOsP0S.js +1 -0
- package/gui/dist/assets/index-RsoaAxXY.js +94 -0
- package/gui/dist/assets/quota-summary-D2lN8tDm.js +1 -0
- package/gui/dist/index.html +1 -1
- package/gui/dist/provider-icons/claude-green.svg +1 -0
- package/package.json +3 -2
- package/src/adapters/anthropic-model-contract.ts +8 -2
- package/src/adapters/anthropic.ts +7 -2
- package/src/adapters/codebuddy/mcp-server.ts +2 -25
- package/src/adapters/codebuddy/tool-bridge.ts +1 -7
- package/src/adapters/coding-agent/mcp-server.ts +33 -0
- package/src/adapters/coding-agent/protocol.ts +122 -17
- package/src/adapters/coding-agent/tool-catalog.ts +75 -0
- package/src/adapters/coding-agent/turn.ts +101 -44
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +2 -0
- package/src/adapters/cursor/live-transport.ts +5 -3
- package/src/adapters/cursor/native-exec-fs.ts +12 -9
- package/src/adapters/cursor/native-exec.ts +13 -5
- package/src/adapters/cursor/protobuf-request.ts +13 -7
- package/src/adapters/cursor/request-builder.ts +23 -10
- package/src/adapters/cursor/tool-definitions.ts +9 -6
- package/src/adapters/cursor/tool-guidance.ts +29 -8
- package/src/adapters/cursor/tool-naming.ts +40 -7
- package/src/adapters/cursor/tool-wording.ts +46 -0
- package/src/adapters/cursor.ts +3 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/devin.ts +25 -11
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -4
- package/src/adapters/physical-send.ts +8 -5
- package/src/adapters/qoder/adapter.ts +41 -6
- package/src/adapters/qoder/scaffold-guard.ts +2 -3
- package/src/adapters/zed.ts +16 -1
- package/src/claude/inbound-cache-stabilize.ts +4 -9
- package/src/claude/inbound.ts +10 -0
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/claude/intercept/picker-listener.ts +71 -4
- package/src/cli/account-anthropic-threshold.ts +14 -5
- package/src/cli/account-api.ts +3 -1
- package/src/cli/account-extended.ts +13 -9
- package/src/cli/account-policy-dto.ts +1 -1
- package/src/cli/account-policy.ts +13 -4
- package/src/cli/account.ts +3 -3
- package/src/cli/capabilities-accounts.ts +2 -2
- package/src/cli/capabilities-agents-routing.ts +24 -0
- package/src/cli/capabilities-integrations.ts +16 -0
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/dispatch.ts +10 -1
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +11 -5
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/integration-preview.ts +8 -1
- package/src/cli/integrations.ts +32 -3
- package/src/cli/message-args.ts +34 -0
- package/src/cli/message-command.ts +43 -0
- package/src/cli/message-runtime.ts +6 -0
- package/src/cli/observe.ts +3 -1
- package/src/cli/provider.ts +29 -6
- package/src/cli/registry.ts +24 -2
- package/src/clients/config-export/commandcode.ts +131 -0
- package/src/clients/config-export/contracts.ts +12 -0
- package/src/clients/config-export.ts +64 -1
- package/src/codex/auth-api/account-list.ts +11 -3
- package/src/codex/auth-api/main-account-probe.ts +25 -8
- package/src/codex/auth-api/pool-mode-gate.ts +33 -9
- package/src/codex/auth-api/pool-quota-probe.ts +15 -1
- package/src/codex/auth-api/routes.ts +1 -0
- package/src/codex/autostart-health.ts +3 -3
- package/src/codex/catalog/auto-review.ts +13 -6
- package/src/codex/catalog/build-entries.ts +15 -7
- package/src/codex/catalog/control-plane.ts +45 -0
- package/src/codex/catalog/effort.ts +11 -1
- package/src/codex/catalog/gather-capture.ts +5 -3
- package/src/codex/catalog/parsing.ts +2 -1
- package/src/codex/catalog/provider-models.ts +15 -9
- package/src/codex/control-plane-models.ts +6 -0
- package/src/codex/journal.ts +36 -5
- package/src/codex/shim-overlay.ts +9 -1
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev.ts +6 -3
- package/src/combos/types.ts +1 -1
- package/src/config/diagnostics.ts +27 -16
- package/src/config/load-degrade.ts +34 -0
- package/src/config/schema/anthropic-account-pool.ts +143 -0
- package/src/config/schema/config-schema.ts +7 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +347 -199
- package/src/generated/model-metadata.ts +5 -4
- package/src/images/loop.ts +2 -1
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +7 -0
- package/src/integrations/registry.ts +8 -0
- package/src/integrations/state.ts +9 -2
- package/src/integrations/writer.ts +8 -2
- package/src/lib/lab-live-route-production.ts +9 -1
- package/src/lib/request-execution-budget.ts +236 -58
- package/src/lib/spend-pool-alias-validation.ts +79 -0
- package/src/lib/spend-pool-continuity.ts +34 -0
- package/src/lib/spend-reservation-ledger.ts +412 -50
- package/src/lib/state-store-registrations.ts +20 -3
- package/src/lib/upstream-retry.ts +17 -5
- package/src/lib/workflow-budget.ts +53 -11
- package/src/messaging/budget.ts +45 -0
- package/src/messaging/discovery.ts +57 -0
- package/src/messaging/envelope.ts +49 -0
- package/src/messaging/input.ts +37 -0
- package/src/messaging/rpc.ts +196 -0
- package/src/messaging/send.ts +58 -0
- package/src/messaging/socket.ts +72 -0
- package/src/messaging/types.ts +29 -0
- package/src/oauth/anthropic-account-refusal.ts +51 -17
- package/src/oauth/anthropic-account-threshold.ts +8 -2
- package/src/oauth/anthropic-continuity.ts +15 -1
- package/src/oauth/anthropic-model-quota.ts +120 -48
- package/src/oauth/anthropic-model-routes.ts +11 -3
- package/src/oauth/anthropic-oauth-definitions.ts +98 -0
- package/src/oauth/anthropic-pool-config.ts +30 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
- package/src/oauth/anthropic-routing.ts +1202 -866
- package/src/oauth/anthropic-send-ownership.ts +41 -0
- package/src/oauth/anthropic.ts +4 -1
- package/src/oauth/generic-account-failover.ts +3 -1
- package/src/oauth/health.ts +8 -6
- package/src/oauth/index.ts +90 -173
- package/src/oauth/kiro.ts +22 -9
- package/src/oauth/login-cli.ts +2 -0
- package/src/oauth/model-discovery-auth.ts +43 -0
- package/src/oauth/pool-kernel.ts +11 -5
- package/src/oauth/pool-settings-capability.ts +5 -3
- package/src/oauth/store-anthropic-instance.ts +87 -0
- package/src/oauth/store.ts +67 -9
- package/src/oauth/token-guardian.ts +2 -0
- package/src/oauth/types.ts +2 -0
- package/src/protocols/plan-snapshot.ts +5 -0
- package/src/protocols/settings.ts +20 -5
- package/src/providers/anthropic-instance-id.ts +47 -0
- package/src/providers/anthropic-instance.ts +48 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
- package/src/providers/derive.ts +2 -0
- package/src/providers/kiro-models.ts +3 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/quota/account-cache.ts +37 -11
- package/src/providers/quota/anthropic-account-quota.ts +93 -0
- package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
- package/src/providers/quota/report-cache.ts +24 -1
- package/src/providers/quota/vendor-probes-oauth.ts +46 -14
- package/src/providers/quota.ts +22 -32
- package/src/providers/registry/entries-core.ts +35 -30
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +7 -4
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/registry.ts +3 -3
- package/src/responses/spill-store.ts +5 -1
- package/src/responses/state/metrics.ts +87 -0
- package/src/responses/state/snapshot-codec.ts +6 -0
- package/src/responses/state/spill-queue.ts +1 -0
- package/src/responses/state/unforced-store-false.ts +55 -0
- package/src/responses/state.ts +50 -66
- package/src/router.ts +28 -4
- package/src/routing/compatibility/assemble.ts +1 -1
- package/src/routing/quota.ts +7 -3
- package/src/server/auth-cors.ts +5 -0
- package/src/server/chat-completions.ts +6 -1
- package/src/server/chat-native.ts +45 -25
- package/src/server/claude-messages.ts +16 -2
- package/src/server/gui-static.ts +38 -4
- package/src/server/index/spend-ledger-lifecycle.ts +10 -4
- package/src/server/index.ts +16 -16
- package/src/server/inference/context.ts +1 -1
- package/src/server/lifecycle.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +30 -6
- package/src/server/management/anthropic-account-threshold.ts +12 -9
- package/src/server/management/anthropic-pool-settings.ts +72 -0
- package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
- package/src/server/management/config-routes.ts +27 -7
- package/src/server/management/decision-routes.ts +2 -1
- package/src/server/management/integration-routes.ts +25 -2
- package/src/server/management/oauth-account-routes.ts +79 -98
- package/src/server/management/provider-instance-ownership.ts +44 -0
- package/src/server/management/provider-routes.ts +21 -28
- package/src/server/management/route-registry.ts +3 -1
- package/src/server/management/routing-profile-routes.ts +4 -4
- package/src/server/management/system-restart.ts +44 -14
- package/src/server/management/system-routes.ts +24 -2
- package/src/server/management/vision-sidecar-options.ts +9 -6
- package/src/server/management/web-search-sidecar-options.ts +51 -2
- package/src/server/management-auth.ts +6 -0
- package/src/server/messages-native-eligibility.ts +3 -2
- package/src/server/messages-native-oauth.ts +86 -41
- package/src/server/messages-native-scope.ts +4 -1
- package/src/server/messages-native-selector.ts +39 -0
- package/src/server/messages-native.ts +84 -25
- package/src/server/request-log.ts +5 -0
- package/src/server/responses/adapter-continuation.ts +29 -17
- package/src/server/responses/adapter-dispatch.ts +47 -17
- package/src/server/responses/antigravity-validation-refusal.ts +20 -6
- package/src/server/responses/combo-requested-effort.ts +14 -0
- package/src/server/responses/compact.ts +25 -8
- package/src/server/responses/compaction-recovery.ts +1 -1
- package/src/server/responses/core-combo-native.ts +4 -1
- package/src/server/responses/core-combo.ts +21 -18
- package/src/server/responses/core-normalize.ts +1 -0
- package/src/server/responses/core-options.ts +3 -1
- package/src/server/responses/core-replay.ts +5 -0
- package/src/server/responses/passthrough-delivery.ts +7 -3
- package/src/server/responses/passthrough-dispatch.ts +21 -8
- package/src/server/responses/request-prepare.ts +37 -5
- package/src/server/responses/request-send-budget.ts +74 -37
- package/src/server/responses/request-spend.ts +325 -33
- package/src/server/responses/request-transport.ts +132 -52
- package/src/server/responses/run-turn-execution.ts +11 -3
- package/src/server/responses/sidecar-execution.ts +96 -14
- package/src/server/responses-undeclared-tool-guard.ts +5 -1
- package/src/server/workflow-refusal.ts +30 -0
- package/src/service/desktop-startup.ts +100 -3
- package/src/service/ownership-mutation-lease.d.mts +1 -1
- package/src/service/ownership-mutation-lease.mjs +5 -7
- package/src/sidecar/anthropic-binding.ts +79 -0
- package/src/sidecar/auth.ts +76 -2
- package/src/types/anthropic-account-pool.ts +33 -0
- package/src/types/config.ts +12 -28
- package/src/types/provider.ts +5 -0
- package/src/types/tools.ts +7 -0
- package/src/types.ts +4 -0
- package/src/usage/cost.ts +3 -2
- package/src/usage/expected-prices.ts +66 -14
- package/src/vision/anthropic-describe.ts +16 -3
- package/src/vision/index.ts +3 -0
- package/src/vision/plan.ts +19 -6
- package/src/web-search/alpha-search.ts +7 -1
- package/src/web-search/anthropic-executor.ts +16 -4
- package/src/web-search/header-deadline.ts +67 -0
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +32 -10
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-BOUa-ek3.js +0 -54
- package/gui/dist/assets/Tray-DUtzB4zF.js +0 -1
- package/gui/dist/assets/index-DwHNDF4s.js +0 -94
- package/gui/dist/assets/quota-summary-BuL4WIBo.js +0 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { namespacedToolName, requiresToolCall, toolChoiceToolPredicate, type OcxParsedRequest } from "../../types";
|
|
2
|
+
import { stripResponsesOnlyEncryptedMarker } from "../responses-tool-schema";
|
|
3
|
+
|
|
4
|
+
// The selected catalog also supplies one comma-joined --allowed-tools argv value.
|
|
5
|
+
// Bound that full argument separately from individual names for Windows process creation.
|
|
6
|
+
export const CODING_AGENT_TOOL_LIMITS = Object.freeze({
|
|
7
|
+
maxTools: 128,
|
|
8
|
+
maxNameBytes: 512,
|
|
9
|
+
maxAllowedToolsArgBytes: 8 * 1024,
|
|
10
|
+
maxToolBytes: 256 * 1024,
|
|
11
|
+
maxCatalogBytes: 2 * 1024 * 1024,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const encoder = new TextEncoder();
|
|
15
|
+
const INVALID_NAME_PART = /[,\s\u0000-\u001f\u007f-\u009f]/u;
|
|
16
|
+
|
|
17
|
+
export class CodingAgentToolCatalogError extends Error {
|
|
18
|
+
constructor(message: string) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "CodingAgentToolCatalogError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isValidCodingAgentToolNamePart(value: unknown): value is string {
|
|
25
|
+
if (typeof value !== "string" || !value || INVALID_NAME_PART.test(value)) return false;
|
|
26
|
+
// TextEncoder replaces lone surrogates, which would change tool identity on the MCP wire.
|
|
27
|
+
for (let i = 0; i < value.length; i++) {
|
|
28
|
+
const unit = value.charCodeAt(i);
|
|
29
|
+
if (unit >= 0xd800 && unit <= 0xdbff) {
|
|
30
|
+
const next = value.charCodeAt(++i);
|
|
31
|
+
if (!(next >= 0xdc00 && next <= 0xdfff)) return false;
|
|
32
|
+
} else if (unit >= 0xdc00 && unit <= 0xdfff) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** One client-owned catalog; the CLI spelling is only a projection of its wire names. */
|
|
40
|
+
export function buildCodingAgentToolCatalog(parsed: OcxParsedRequest, serverName: string) {
|
|
41
|
+
const declared = parsed.context.tools ?? [];
|
|
42
|
+
const selected = declared.filter(toolChoiceToolPredicate(parsed.options.toolChoice, declared));
|
|
43
|
+
if (selected.length > CODING_AGENT_TOOL_LIMITS.maxTools) throw new CodingAgentToolCatalogError("tool catalog exceeds tool count limit");
|
|
44
|
+
const requireToolCall = requiresToolCall(parsed.options.toolChoice);
|
|
45
|
+
const emittedNameMap = new Map<string, string>();
|
|
46
|
+
// JSON array brackets and commas count too; avoid serializing the whole catalog twice.
|
|
47
|
+
let catalogBytes = 2;
|
|
48
|
+
let allowedToolsArgBytes = 0;
|
|
49
|
+
const tools = selected.map((tool, index) => {
|
|
50
|
+
if (!tool || !isValidCodingAgentToolNamePart(tool.name) || (tool.namespace !== undefined && !isValidCodingAgentToolNamePart(tool.namespace))) {
|
|
51
|
+
throw new CodingAgentToolCatalogError("invalid tool name or namespace");
|
|
52
|
+
}
|
|
53
|
+
const wireName = namespacedToolName(tool.namespace, tool.name);
|
|
54
|
+
if (encoder.encode(wireName).byteLength > CODING_AGENT_TOOL_LIMITS.maxNameBytes) {
|
|
55
|
+
throw new CodingAgentToolCatalogError("tool name exceeds byte limit");
|
|
56
|
+
}
|
|
57
|
+
const definition = {
|
|
58
|
+
name: wireName,
|
|
59
|
+
description: tool.description || `Tool: ${wireName}`,
|
|
60
|
+
inputSchema: stripResponsesOnlyEncryptedMarker(tool.parameters) as Record<string, unknown>,
|
|
61
|
+
};
|
|
62
|
+
const bytes = encoder.encode(JSON.stringify(definition)).byteLength;
|
|
63
|
+
if (bytes > CODING_AGENT_TOOL_LIMITS.maxToolBytes) throw new CodingAgentToolCatalogError("tool definition exceeds byte limit");
|
|
64
|
+
catalogBytes += bytes + (index ? 1 : 0);
|
|
65
|
+
if (catalogBytes > CODING_AGENT_TOOL_LIMITS.maxCatalogBytes) throw new CodingAgentToolCatalogError("tool catalog exceeds byte limit");
|
|
66
|
+
const emittedName = `mcp__${serverName}__${wireName}`;
|
|
67
|
+
allowedToolsArgBytes += encoder.encode(emittedName).byteLength + (index ? 1 : 0);
|
|
68
|
+
if (allowedToolsArgBytes > CODING_AGENT_TOOL_LIMITS.maxAllowedToolsArgBytes) {
|
|
69
|
+
throw new CodingAgentToolCatalogError("allowed-tools argument exceeds byte limit");
|
|
70
|
+
}
|
|
71
|
+
emittedNameMap.set(emittedName, wireName);
|
|
72
|
+
return definition;
|
|
73
|
+
});
|
|
74
|
+
return { tools, emittedNameMap, requireToolCall };
|
|
75
|
+
}
|
|
@@ -4,6 +4,8 @@ import { tmpdir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig } from "../../types";
|
|
6
6
|
import { isTranslatorBudgetExceededError } from "../../lib/translator-budget";
|
|
7
|
+
import { SendBudgetExhaustedError } from "../../lib/upstream-retry";
|
|
8
|
+
import type { SingleUseDispatchPermit } from "../../lib/request-execution-budget";
|
|
7
9
|
import { commandInvocation } from "../../lib/win-exec";
|
|
8
10
|
import { isStandaloneBinary } from "../../lib/standalone";
|
|
9
11
|
import { modelRecordValue } from "../../reasoning-effort";
|
|
@@ -43,6 +45,9 @@ export interface CodingAgentDeps {
|
|
|
43
45
|
killWindowsProcessTree?: KillWindowsProcessTreeFn;
|
|
44
46
|
/** Test seam for a catalog/config write failure after private bridge-directory creation. */
|
|
45
47
|
writeToolBridgeFile?: typeof writeFile;
|
|
48
|
+
/** Injectable timer primitives for deterministic tests without sleeping. */
|
|
49
|
+
setTimeout?: typeof setTimeout;
|
|
50
|
+
clearTimeout?: typeof clearTimeout;
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
const DEFAULT_TIMEOUT_MS = 300_000;
|
|
@@ -113,28 +118,35 @@ export interface CodingAgentTurnInput {
|
|
|
113
118
|
/** Family-specific scoped env builder (credential + region switch on top of baseScopedEnv). */
|
|
114
119
|
buildEnv: (profile: CodingAgentProviderProfile, apiKey: string) => Record<string, string>;
|
|
115
120
|
/**
|
|
116
|
-
* Opt-in
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* names back to request wire names, and terminates the process tree at `message_stop` because
|
|
120
|
-
* the capture-only MCP handler intentionally never answers. Execution stays with the client.
|
|
121
|
+
* Opt-in isolated MCP catalog. The turn writes a catalog and MCP config to a private temp dir,
|
|
122
|
+
* advertises only the selected names, and returns structured CLI tool intent to the client.
|
|
123
|
+
* The MCP handler never executes the client tool.
|
|
121
124
|
*/
|
|
122
125
|
toolBridge?: CodingAgentToolBridgeInput;
|
|
123
126
|
deps: CodingAgentDeps;
|
|
124
127
|
}
|
|
125
128
|
|
|
126
|
-
/** Opt-in
|
|
129
|
+
/** Opt-in isolated tool catalog for one coding-agent CLI turn. */
|
|
127
130
|
export interface CodingAgentToolBridgeInput {
|
|
128
131
|
/** MCP server name advertised to the CLI; tool_use blocks render it as `mcp__<name>__<tool>`. */
|
|
129
132
|
serverName: string;
|
|
130
|
-
/** Absolute path of the
|
|
133
|
+
/** Absolute path of the MCP server module, run with the serving runtime. */
|
|
131
134
|
serverModulePath: string;
|
|
132
135
|
/** Validated tool catalog advertised over ListTools; the server never executes a call. */
|
|
133
136
|
tools: ReadonlyArray<{ name: string; description: string; inputSchema: Record<string, unknown> }>;
|
|
134
137
|
/** CLI-emitted tool name (`mcp__<server>__<tool>`) to the request's wire tool name. */
|
|
135
138
|
emittedNameMap: Map<string, string>;
|
|
136
|
-
/**
|
|
139
|
+
/** Tool_use blocks accepted in one assistant message. */
|
|
137
140
|
maxTurnToolCalls: number;
|
|
141
|
+
/** Render prior tool calls with the CLI-emitted names. Off by default so CodeBuddy history is unchanged. */
|
|
142
|
+
projectHistoryToolNames?: boolean;
|
|
143
|
+
/** Finish the turn when a complete assistant frame supplies structured tool_use blocks. */
|
|
144
|
+
completeAssistantToolUse?: boolean;
|
|
145
|
+
/** Authoritative tool-turn stop signal; existing coding-agent bridges default to message_stop. */
|
|
146
|
+
toolTurnCompletionSignal?: "message_stop" | "assistant_tool_use_stop";
|
|
147
|
+
/** Family-specific CLI flag spelling and compiled MCP helper entrypoint. */
|
|
148
|
+
allowedToolsFlag?: string;
|
|
149
|
+
standaloneEntrypoint?: string;
|
|
138
150
|
/**
|
|
139
151
|
* The request's `tool_choice` requires a tool call (`required`, or a named selection).
|
|
140
152
|
* The nested CLI has no documented force-tool flag, so this is enforced locally: a
|
|
@@ -164,6 +176,8 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
164
176
|
const killGraceMs = deps.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
|
|
165
177
|
const reapTimeoutMs = deps.reapTimeoutMs ?? (killGraceMs * 2 + 250);
|
|
166
178
|
const platform = deps.platform ?? process.platform;
|
|
179
|
+
const setTimeoutFn = deps.setTimeout ?? setTimeout;
|
|
180
|
+
const clearTimeoutFn = deps.clearTimeout ?? clearTimeout;
|
|
167
181
|
|
|
168
182
|
if (incoming.abortSignal?.aborted) {
|
|
169
183
|
emit({ type: "error", message: "Coding-agent turn was aborted before start." });
|
|
@@ -240,7 +254,11 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
240
254
|
[toolBridge.serverName]: {
|
|
241
255
|
type: "stdio",
|
|
242
256
|
command: process.execPath,
|
|
243
|
-
args:
|
|
257
|
+
args: toolBridge.standaloneEntrypoint
|
|
258
|
+
? (isStandaloneBinary()
|
|
259
|
+
? [toolBridge.standaloneEntrypoint, catalogPath]
|
|
260
|
+
: [toolBridge.serverModulePath, catalogPath])
|
|
261
|
+
: codeBuddyMcpInvocation(toolBridge.serverModulePath, catalogPath),
|
|
244
262
|
defer_loading: false,
|
|
245
263
|
alwaysLoad: true,
|
|
246
264
|
},
|
|
@@ -265,12 +283,25 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
265
283
|
const args = buildArgs(profile, parsed, provider);
|
|
266
284
|
if (toolBridge && toolBridgeMcpConfigPath) {
|
|
267
285
|
// Exact names close the wildcard domain; --strict-mcp-config (family args) keeps user
|
|
268
|
-
// servers out, so
|
|
269
|
-
args.push("--allowedTools", [...toolBridge.emittedNameMap.keys()].join(","), "--mcp-config", toolBridgeMcpConfigPath);
|
|
286
|
+
// servers out, so this isolated server is the only capability this turn can reach.
|
|
287
|
+
args.push(toolBridge.allowedToolsFlag ?? "--allowedTools", [...toolBridge.emittedNameMap.keys()].join(","), "--mcp-config", toolBridgeMcpConfigPath);
|
|
270
288
|
}
|
|
271
289
|
const env = buildEnv(profile, apiKey);
|
|
272
290
|
const invocation = commandInvocation(binary, args, platform, { env });
|
|
273
291
|
|
|
292
|
+
// An opaque CLI invocation is one send. Its internal retries are not observable here;
|
|
293
|
+
// terminal usage still settles the complete amount, including usage above the estimate.
|
|
294
|
+
let producer: { close(): void } | undefined;
|
|
295
|
+
let permit: SingleUseDispatchPermit | undefined;
|
|
296
|
+
try {
|
|
297
|
+
producer = incoming.sendBudget?.beginSpendProducer?.();
|
|
298
|
+
const decision = incoming.sendBudget?.reserveDispatch({
|
|
299
|
+
sendClass: "initial", targetKey: `${provider.adapter}|${profile.canonicalBaseUrl}|${parsed.modelId}`,
|
|
300
|
+
});
|
|
301
|
+
if (decision && !decision.allowed) throw new SendBudgetExhaustedError();
|
|
302
|
+
permit = decision?.allowed ? decision.permit : undefined;
|
|
303
|
+
if (permit && !permit.use()) throw new SendBudgetExhaustedError();
|
|
304
|
+
incoming.onPhysicalSend?.({ ordinal: 1 });
|
|
274
305
|
let child: ChildProcess;
|
|
275
306
|
try {
|
|
276
307
|
child = spawnFn(invocation.file, invocation.args, {
|
|
@@ -288,9 +319,6 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
288
319
|
code: "cli_spawn_failed",
|
|
289
320
|
retryable: false,
|
|
290
321
|
});
|
|
291
|
-
// A synchronous spawn() throw skips the event-loop `finally` below, so the private
|
|
292
|
-
// bridge dir would leak unless it is removed here as well.
|
|
293
|
-
if (toolBridgeDir) await rm(toolBridgeDir, { recursive: true, force: true }).catch(() => undefined);
|
|
294
322
|
return;
|
|
295
323
|
}
|
|
296
324
|
|
|
@@ -339,7 +367,7 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
339
367
|
// mcp-server.ts exits on stdin EOF, so this ladder reaps the whole tree without knowing
|
|
340
368
|
// the grandchild pid.
|
|
341
369
|
try { child.kill("SIGTERM"); } catch { /* already gone */ }
|
|
342
|
-
killTimer =
|
|
370
|
+
killTimer = setTimeoutFn(() => {
|
|
343
371
|
try { child.kill("SIGKILL"); } catch { /* already gone */ }
|
|
344
372
|
}, killGraceMs);
|
|
345
373
|
};
|
|
@@ -352,7 +380,7 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
352
380
|
stopStream();
|
|
353
381
|
};
|
|
354
382
|
incoming.abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
355
|
-
const timeoutTimer =
|
|
383
|
+
const timeoutTimer = setTimeoutFn(() => {
|
|
356
384
|
kill();
|
|
357
385
|
stopStream();
|
|
358
386
|
emitOnce({ type: "error", message: `${profile.label} turn timed out.`, status: 504, errorType: "upstream_error", code: "timeout", retryable: true });
|
|
@@ -364,7 +392,7 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
364
392
|
});
|
|
365
393
|
|
|
366
394
|
const cleanup = (): void => {
|
|
367
|
-
|
|
395
|
+
clearTimeoutFn(timeoutTimer);
|
|
368
396
|
incoming.abortSignal?.removeEventListener("abort", onAbort);
|
|
369
397
|
try { child.stdin?.destroy(); } catch { /* ignore */ }
|
|
370
398
|
// Termination is owned by the reap step below, not here: killing in cleanup would set
|
|
@@ -374,9 +402,10 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
374
402
|
let streamProtocolError: string | undefined;
|
|
375
403
|
let streamProtocolCode: string | undefined;
|
|
376
404
|
let turnError: string | undefined;
|
|
405
|
+
let failClosed = false;
|
|
377
406
|
// A successful result frame that arrived after every captured tool call completed but
|
|
378
|
-
// before
|
|
379
|
-
// done(tool_use) at
|
|
407
|
+
// before an authoritative tool-turn stop. The bridge contract still ends the leg with
|
|
408
|
+
// synthesized done(tool_use) at that stop, so result-derived done(stop) is deferred and its
|
|
380
409
|
// usage (authoritative vendor accounting) is folded into the synthesis. Set inside the
|
|
381
410
|
// stream loop; read by the synthesis and the stream-end error selection below.
|
|
382
411
|
let deferredResultDone: Extract<AdapterEvent, { type: "done" }> | undefined;
|
|
@@ -389,7 +418,13 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
389
418
|
maxToolBlockStarts: toolBridge?.maxTurnToolCalls,
|
|
390
419
|
strictToolBlockCapture: Boolean(toolBridge),
|
|
391
420
|
partialToolCallIds: toolBridge ? new Set<string>() : undefined,
|
|
421
|
+
completeAssistantToolUse: toolBridge?.completeAssistantToolUse,
|
|
422
|
+
toolBridgeNames: toolBridge?.completeAssistantToolUse ? toolBridge.emittedNameMap : undefined,
|
|
392
423
|
};
|
|
424
|
+
const toolTurnCompletionSignal = toolBridge?.toolTurnCompletionSignal ?? "message_stop";
|
|
425
|
+
const sawSelectedToolTurnStop = (): boolean => toolTurnCompletionSignal === "assistant_tool_use_stop"
|
|
426
|
+
? state.sawAssistantToolUseStop === true
|
|
427
|
+
: state.sawMessageStop === true;
|
|
393
428
|
|
|
394
429
|
try {
|
|
395
430
|
// Write the replayed conversation, then close stdin so a single-shot turn can complete.
|
|
@@ -403,14 +438,16 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
403
438
|
const historyCharLimit = projectedHistoryCharLimit(
|
|
404
439
|
modelRecordValue(provider.modelContextWindows, parsed.modelId) ?? provider.contextWindow,
|
|
405
440
|
);
|
|
406
|
-
|
|
441
|
+
const vendorNameByWire = toolBridge?.projectHistoryToolNames
|
|
442
|
+
? new Map([...toolBridge.emittedNameMap].map(([emitted, wire]) => [wire, emitted]))
|
|
443
|
+
: undefined;
|
|
444
|
+
for (const line of buildConversationInput(parsed, { maxHistoryChars: historyCharLimit, vendorNameByWire })) stdin.write(`${line}\n`);
|
|
407
445
|
stdin.end();
|
|
408
446
|
}
|
|
409
447
|
const stdout = child.stdout;
|
|
410
448
|
if (!stdout) throw new CodingAgentProtocolError(`${profile.label} CLI produced no stdout stream`);
|
|
411
449
|
try {
|
|
412
450
|
let initValidated = false;
|
|
413
|
-
let failClosed = false;
|
|
414
451
|
for await (const message of readJsonLines(stdout)) {
|
|
415
452
|
if (incoming.abortSignal?.aborted) break;
|
|
416
453
|
if (toolBridge) {
|
|
@@ -432,13 +469,19 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
432
469
|
? message.event as Record<string, unknown>
|
|
433
470
|
: undefined;
|
|
434
471
|
const rawBlock = rawEvent?.content_block;
|
|
472
|
+
const assistantMessage = toolBridge.completeAssistantToolUse && message.type === "assistant"
|
|
473
|
+
&& message.message !== null && typeof message.message === "object"
|
|
474
|
+
? message.message as Record<string, unknown> : undefined;
|
|
475
|
+
const completeToolUse = Array.isArray(assistantMessage?.content) && assistantMessage.content.some(block =>
|
|
476
|
+
block !== null && typeof block === "object" && (block as Record<string, unknown>).type === "tool_use",
|
|
477
|
+
);
|
|
435
478
|
if (
|
|
436
479
|
!initValidated
|
|
437
|
-
&& rawEvent?.type === "content_block_start"
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
480
|
+
&& (completeToolUse || (rawEvent?.type === "content_block_start"
|
|
481
|
+
&& rawBlock !== null
|
|
482
|
+
&& typeof rawBlock === "object"
|
|
483
|
+
&& !Array.isArray(rawBlock)
|
|
484
|
+
&& (rawBlock as Record<string, unknown>).type === "tool_use"))
|
|
442
485
|
) {
|
|
443
486
|
emitOnce({
|
|
444
487
|
type: "error",
|
|
@@ -522,7 +565,7 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
522
565
|
// other bridge contract violations use.
|
|
523
566
|
emitOnce({
|
|
524
567
|
type: "error",
|
|
525
|
-
message: "
|
|
568
|
+
message: "Coding-agent CLI finished without calling the required tool.",
|
|
526
569
|
status: 502,
|
|
527
570
|
errorType: "upstream_error",
|
|
528
571
|
code: "tool_call_required",
|
|
@@ -567,9 +610,9 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
567
610
|
// Every captured call completed and the CLI settled with a successful result before
|
|
568
611
|
// message_stop (instead of parking on the never-answering capture server). Emitting
|
|
569
612
|
// this done(stop) now would end the turn as a text completion and skip the
|
|
570
|
-
// synthesized done(tool_use) the client contract expects. Defer it:
|
|
571
|
-
//
|
|
572
|
-
// ends without
|
|
613
|
+
// synthesized done(tool_use) the client contract expects. Defer it: an authoritative
|
|
614
|
+
// tool-turn stop synthesizes the terminal event with this frame's usage, and a stream
|
|
615
|
+
// that ends without a stop fails closed with protocol_error below.
|
|
573
616
|
deferredResultDone = event;
|
|
574
617
|
continue;
|
|
575
618
|
}
|
|
@@ -578,10 +621,11 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
578
621
|
: event);
|
|
579
622
|
}
|
|
580
623
|
if (failClosed) break;
|
|
624
|
+
const sawToolTurnStop = sawSelectedToolTurnStop();
|
|
581
625
|
if (
|
|
582
626
|
toolBridge
|
|
583
627
|
&& !terminalEmitted
|
|
584
|
-
&&
|
|
628
|
+
&& sawToolTurnStop
|
|
585
629
|
&& (state.toolBlockStarts ?? 0) > 0
|
|
586
630
|
&& (state.completedToolCalls ?? 0) !== (state.toolBlockStarts ?? 0)
|
|
587
631
|
) {
|
|
@@ -596,9 +640,16 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
596
640
|
kill();
|
|
597
641
|
break;
|
|
598
642
|
}
|
|
599
|
-
if (toolBridge && !terminalEmitted &&
|
|
600
|
-
|
|
601
|
-
|
|
643
|
+
if (toolBridge && !terminalEmitted && sawToolTurnStop && (state.toolBlockStarts ?? 0) > 0
|
|
644
|
+
&& (state.completedToolCalls ?? 0) === (state.toolBlockStarts ?? 0)) {
|
|
645
|
+
if (!initValidated) {
|
|
646
|
+
emitOnce({ type: "error", message: "Coding-agent CLI ended before the tool bridge init handshake completed.",
|
|
647
|
+
status: 502, errorType: "upstream_error", code: "tool_bridge_init_missing", retryable: false });
|
|
648
|
+
kill();
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
// Both partial and complete tool calls require the validated handshake.
|
|
652
|
+
// The capture-only MCP handler never answers, so the CLI parks after the tool-turn stop.
|
|
602
653
|
// The completed tool_use blocks are this turn's structured output: end the leg here
|
|
603
654
|
// and terminate the tree; the client executes, and the next request continues.
|
|
604
655
|
// Pre-result usage snapshots keep this terminated leg accountable: no result frame
|
|
@@ -626,24 +677,21 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
626
677
|
} finally {
|
|
627
678
|
releaseOpenToolBlocks(state);
|
|
628
679
|
cleanup();
|
|
629
|
-
if (toolBridgeDir) {
|
|
630
|
-
await rm(toolBridgeDir, { recursive: true, force: true }).catch(() => undefined);
|
|
631
|
-
}
|
|
632
680
|
}
|
|
633
681
|
|
|
634
682
|
// Reap the process so no zombie is left behind (§三十): wait for the real `close`, and
|
|
635
683
|
// force-terminate only if it lingers past the grace window after the stream ended.
|
|
636
|
-
const graceTimer =
|
|
684
|
+
const graceTimer = setTimeoutFn(() => { kill(); }, killGraceMs);
|
|
637
685
|
let reapTimer: ReturnType<typeof setTimeout> | undefined;
|
|
638
686
|
await Promise.race([
|
|
639
687
|
processLifecycle,
|
|
640
688
|
new Promise<void>(resolve => {
|
|
641
|
-
reapTimer =
|
|
689
|
+
reapTimer = setTimeoutFn(resolve, reapTimeoutMs);
|
|
642
690
|
}),
|
|
643
691
|
]);
|
|
644
|
-
|
|
645
|
-
if (reapTimer)
|
|
646
|
-
if (killTimer)
|
|
692
|
+
clearTimeoutFn(graceTimer);
|
|
693
|
+
if (reapTimer) clearTimeoutFn(reapTimer);
|
|
694
|
+
if (killTimer) clearTimeoutFn(killTimer);
|
|
647
695
|
|
|
648
696
|
if (!terminalEmitted) {
|
|
649
697
|
const stderr = redactSecrets(boundedStderr(stderrChunks), profile.tokenEnv, apiKey);
|
|
@@ -686,10 +734,10 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
686
734
|
code: "process_exit_error",
|
|
687
735
|
retryable: false,
|
|
688
736
|
});
|
|
689
|
-
} else if (toolBridge && deferredResultDone !== undefined && !
|
|
737
|
+
} else if (toolBridge && deferredResultDone !== undefined && !sawSelectedToolTurnStop()) {
|
|
690
738
|
emitOnce({
|
|
691
739
|
type: "error",
|
|
692
|
-
message: `${profile.label} CLI delivered a terminal result before
|
|
740
|
+
message: `${profile.label} CLI delivered a terminal result before the authoritative tool-turn stop on a tool-bridge turn.`,
|
|
693
741
|
status: 502,
|
|
694
742
|
errorType: "upstream_error",
|
|
695
743
|
code: "protocol_error",
|
|
@@ -709,6 +757,15 @@ export async function runCodingAgentTurn(input: CodingAgentTurnInput): Promise<v
|
|
|
709
757
|
});
|
|
710
758
|
}
|
|
711
759
|
}
|
|
760
|
+
} finally {
|
|
761
|
+
try { permit?.release(); }
|
|
762
|
+
finally {
|
|
763
|
+
try { producer?.close(); }
|
|
764
|
+
finally {
|
|
765
|
+
if (toolBridgeDir) await rm(toolBridgeDir, { recursive: true, force: true }).catch(() => undefined);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
712
769
|
}
|
|
713
770
|
|
|
714
771
|
function boundedStderr(chunks: string[]): string {
|
|
@@ -140,6 +140,14 @@ export const CURSOR_CAPABILITIES: Record<string, CursorCapability> = {
|
|
|
140
140
|
thinking: { levels: FULL, order: T },
|
|
141
141
|
},
|
|
142
142
|
},
|
|
143
|
+
// 261008 Haiku 5.5: cursor.com/docs/models/claude-haiku-5-5, 1M and low..max.
|
|
144
|
+
// Preemptive regular-only shape until GetUsableModels confirms a thinking variant.
|
|
145
|
+
"claude-haiku-5-5": {
|
|
146
|
+
displayName: "Claude Haiku 5.5",
|
|
147
|
+
window: CONTEXT_1M,
|
|
148
|
+
defaultVariant: "regular",
|
|
149
|
+
variants: { regular: { levels: FULL } },
|
|
150
|
+
},
|
|
143
151
|
// 260929 Claude Sonnet 5.5: cursor.com/docs/models/claude-sonnet-5-5 lists it (1M max context),
|
|
144
152
|
// but the live GetUsableModels roster does not yet. Shaped like Opus 5.5 (flat effort ids, no
|
|
145
153
|
// thinking variant); re-shape from the roster once it appears.
|
|
@@ -41,6 +41,8 @@ const CURSOR_MODEL_EFFORT_TIERS: Record<string, readonly string[]> = {
|
|
|
41
41
|
"claude-opus-5-5-fast": ["low", "medium", "high", "xhigh", "max"],
|
|
42
42
|
// 260929 preemptive Sonnet 5.5: Anthropic documents low..max; flat ids like Opus 5.5 until the
|
|
43
43
|
// live GetUsableModels roster lists the model.
|
|
44
|
+
// 261008 preemptive Haiku 5.5: flat regular low..max ids, per Cursor docs.
|
|
45
|
+
"claude-haiku-5-5": ["low", "medium", "high", "xhigh", "max"],
|
|
44
46
|
"claude-sonnet-5-5": ["low", "medium", "high", "xhigh", "max"],
|
|
45
47
|
"claude-sonnet-5": ["low", "medium", "high", "xhigh", "max"],
|
|
46
48
|
"glm-5.2": ["high", "max"],
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import { activePromptText, prepareCursorRunRequest } from "./protobuf-request";
|
|
17
17
|
import { prepareCursorRawMessages, resolveActiveCursorImages } from "./images";
|
|
18
18
|
import { isCursorExternalWireModel } from "./discovery";
|
|
19
|
+
import { cursorUsesPlainToolWording } from "./tool-wording";
|
|
19
20
|
import { cursorRequestMessagesFromRaw } from "./request-builder";
|
|
20
21
|
import {
|
|
21
22
|
createCursorContextUsageTracker,
|
|
@@ -706,7 +707,7 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
706
707
|
const syntheticStructuredEditToolNames = new Set(
|
|
707
708
|
(cursorVisibleTools ?? [])
|
|
708
709
|
.filter(isCursorSyntheticStructuredEditTool)
|
|
709
|
-
.map(cursorToolWireName),
|
|
710
|
+
.map(tool => cursorToolWireName(tool, cursorVisibleTools)),
|
|
710
711
|
);
|
|
711
712
|
const freeformToolNames = new Set(
|
|
712
713
|
(cursorVisibleTools ?? [])
|
|
@@ -718,12 +719,13 @@ class LiveCursorTransport implements CursorTransport {
|
|
|
718
719
|
clientToolDefs,
|
|
719
720
|
rejectNativeFileMutations: cursorRequestAdvertisesApplyPatch(request.tools, request.toolChoice),
|
|
720
721
|
structuredEditAvailable: syntheticStructuredEditToolNames.size > 0,
|
|
721
|
-
nativeExecRedirectHint: cursorNativeExecRedirectHint(cursorVisibleTools, this.execContext.mcpToolDefs ?? []),
|
|
722
|
+
nativeExecRedirectHint: cursorNativeExecRedirectHint(cursorVisibleTools, this.execContext.mcpToolDefs ?? [], activeRequest.modelId),
|
|
723
|
+
plainToolWording: cursorUsesPlainToolWording(activeRequest.modelId),
|
|
722
724
|
};
|
|
723
725
|
const toolSchemas = new Map<string, unknown>();
|
|
724
726
|
const cursorToolNameMap = new Map<string, string>();
|
|
725
727
|
for (const tool of cursorVisibleTools ?? []) {
|
|
726
|
-
const cursorWireName = cursorToolWireName(tool);
|
|
728
|
+
const cursorWireName = cursorToolWireName(tool, cursorVisibleTools);
|
|
727
729
|
// Normalize against Responses/Codex field names, not the Cursor advertisement schema.
|
|
728
730
|
// Advertising `cmd` while also storing that schema here left `cmd` unmapped and Codex
|
|
729
731
|
// rejected shell_command with "missing field `command`" (#399).
|
|
@@ -35,14 +35,17 @@ import {
|
|
|
35
35
|
} from "./gen/agent_pb";
|
|
36
36
|
import { errorText, execBytes, lineCount, textDecoder, textEncoder } from "./native-exec-common";
|
|
37
37
|
|
|
38
|
+
import { CURSOR_TOOL_CALL_CONTINUATION } from "./tool-wording";
|
|
39
|
+
|
|
38
40
|
const MAX_GREP_FILES = 500;
|
|
39
41
|
const MAX_GREP_RESULTS = 200;
|
|
40
42
|
const MAX_FILE_BYTES = 1_000_000;
|
|
41
43
|
|
|
42
|
-
function codexNativeMutationRefusal(operation: "write" | "delete", structuredEditAvailable: boolean): string {
|
|
44
|
+
function codexNativeMutationRefusal(operation: "write" | "delete", structuredEditAvailable: boolean, plainToolWording = false): string {
|
|
43
45
|
const structuredHint = structuredEditAvailable
|
|
44
46
|
? " Use the structured edit tools (`edit_file` / `multi_edit`) or the `apply_patch` tool for file edits so Codex can approve the change, enforce sandbox policy, show diffs, and record rollout."
|
|
45
47
|
: " Use the `apply_patch` tool for file edits so Codex can approve the change, enforce sandbox policy, show diffs, and record rollout.";
|
|
48
|
+
if (plainToolWording) return `Cursor-native ${operation} is not available for this request.${structuredHint} No file was changed. ${CURSOR_TOOL_CALL_CONTINUATION}`;
|
|
46
49
|
return `Make this ${operation} through the Codex edit path instead.${structuredHint} No file was changed. Do NOT narrate this redirect or comment on tool availability — just make the edit call.`;
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -87,24 +90,24 @@ export function readExec(execMsg: ExecServerMessage): Uint8Array {
|
|
|
87
90
|
}
|
|
88
91
|
}
|
|
89
92
|
|
|
90
|
-
export function rejectWriteExecForApplyPatch(execMsg: ExecServerMessage, structuredEditAvailable = false): Uint8Array {
|
|
93
|
+
export function rejectWriteExecForApplyPatch(execMsg: ExecServerMessage, structuredEditAvailable = false, plainToolWording = false): Uint8Array {
|
|
91
94
|
if (execMsg.message.case !== "writeArgs") throw new Error("invalid write exec");
|
|
92
95
|
const path = resolve(execMsg.message.value.path);
|
|
93
96
|
return execBytes(execMsg, "writeResult", create(WriteResultSchema, {
|
|
94
97
|
result: {
|
|
95
98
|
case: "rejected",
|
|
96
|
-
value: create(WriteRejectedSchema, { path, reason: codexNativeMutationRefusal("write", structuredEditAvailable) }),
|
|
99
|
+
value: create(WriteRejectedSchema, { path, reason: codexNativeMutationRefusal("write", structuredEditAvailable, plainToolWording) }),
|
|
97
100
|
},
|
|
98
101
|
}));
|
|
99
102
|
}
|
|
100
103
|
|
|
101
|
-
export function rejectWriteExecForPolicy(execMsg: ExecServerMessage, hint?: string): Uint8Array {
|
|
104
|
+
export function rejectWriteExecForPolicy(execMsg: ExecServerMessage, hint?: string, plainToolWording = false): Uint8Array {
|
|
102
105
|
if (execMsg.message.case !== "writeArgs") throw new Error("invalid write exec");
|
|
103
106
|
const path = resolve(execMsg.message.value.path);
|
|
104
107
|
return execBytes(execMsg, "writeResult", create(WriteResultSchema, {
|
|
105
108
|
result: {
|
|
106
109
|
case: "rejected",
|
|
107
|
-
value: create(WriteRejectedSchema, { path, reason: `${hint ?? NATIVE_LOCAL_EXEC_DISABLED} No file was changed.` }),
|
|
110
|
+
value: create(WriteRejectedSchema, { path, reason: plainToolWording ? `No file was changed. ${hint ?? NATIVE_LOCAL_EXEC_DISABLED}` : `${hint ?? NATIVE_LOCAL_EXEC_DISABLED} No file was changed.` }),
|
|
108
111
|
},
|
|
109
112
|
}));
|
|
110
113
|
}
|
|
@@ -136,24 +139,24 @@ export function writeExec(execMsg: ExecServerMessage): Uint8Array {
|
|
|
136
139
|
}
|
|
137
140
|
}
|
|
138
141
|
|
|
139
|
-
export function rejectDeleteExecForApplyPatch(execMsg: ExecServerMessage, structuredEditAvailable = false): Uint8Array {
|
|
142
|
+
export function rejectDeleteExecForApplyPatch(execMsg: ExecServerMessage, structuredEditAvailable = false, plainToolWording = false): Uint8Array {
|
|
140
143
|
if (execMsg.message.case !== "deleteArgs") throw new Error("invalid delete exec");
|
|
141
144
|
const path = resolve(execMsg.message.value.path);
|
|
142
145
|
return execBytes(execMsg, "deleteResult", create(DeleteResultSchema, {
|
|
143
146
|
result: {
|
|
144
147
|
case: "rejected",
|
|
145
|
-
value: create(DeleteRejectedSchema, { path, reason: codexNativeMutationRefusal("delete", structuredEditAvailable) }),
|
|
148
|
+
value: create(DeleteRejectedSchema, { path, reason: codexNativeMutationRefusal("delete", structuredEditAvailable, plainToolWording) }),
|
|
146
149
|
},
|
|
147
150
|
}));
|
|
148
151
|
}
|
|
149
152
|
|
|
150
|
-
export function rejectDeleteExecForPolicy(execMsg: ExecServerMessage, hint?: string): Uint8Array {
|
|
153
|
+
export function rejectDeleteExecForPolicy(execMsg: ExecServerMessage, hint?: string, plainToolWording = false): Uint8Array {
|
|
151
154
|
if (execMsg.message.case !== "deleteArgs") throw new Error("invalid delete exec");
|
|
152
155
|
const path = resolve(execMsg.message.value.path);
|
|
153
156
|
return execBytes(execMsg, "deleteResult", create(DeleteResultSchema, {
|
|
154
157
|
result: {
|
|
155
158
|
case: "rejected",
|
|
156
|
-
value: create(DeleteRejectedSchema, { path, reason: `${hint ?? NATIVE_LOCAL_EXEC_DISABLED} No file was changed.` }),
|
|
159
|
+
value: create(DeleteRejectedSchema, { path, reason: plainToolWording ? `No file was changed. ${hint ?? NATIVE_LOCAL_EXEC_DISABLED}` : `${hint ?? NATIVE_LOCAL_EXEC_DISABLED} No file was changed.` }),
|
|
157
160
|
},
|
|
158
161
|
}));
|
|
159
162
|
}
|
|
@@ -57,6 +57,7 @@ import { OCX_RESPONSES_TOOL_PROVIDER } from "./tool-definitions";
|
|
|
57
57
|
import { CODEX_UNIFIED_EXEC_TOOL, cursorRequestHasExecutionPath, cursorRequestHasShellAlias, cursorRequestUsesCodeMode, cursorToolWireName } from "./tool-naming";
|
|
58
58
|
import { CODE_MODE_RESULT_ECHO_SENTENCE } from "../exec-tool-result-normalize";
|
|
59
59
|
import type { OcxTool } from "../../types";
|
|
60
|
+
import { cursorPlainNativeExecFallback, cursorPlainNativeExecRedirectHint, cursorUsesPlainToolWording } from "./tool-wording";
|
|
60
61
|
|
|
61
62
|
export type CursorNativeExecDeps = CursorNativeNetworkDeps & CursorNativeToolDeps;
|
|
62
63
|
|
|
@@ -80,6 +81,8 @@ export interface CursorNativeExecContext extends CursorNativeExecDeps {
|
|
|
80
81
|
structuredEditAvailable?: boolean;
|
|
81
82
|
/** Catalog-aware redirect text for denied native fs/shell attempts (undefined = default bridge wording). */
|
|
82
83
|
nativeExecRedirectHint?: string;
|
|
84
|
+
/** Claude-family Cursor targets receive factual redirects, without narration restrictions. */
|
|
85
|
+
plainToolWording?: boolean;
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
const REDIRECT_HINT_MAX_TOOLS = 16;
|
|
@@ -93,7 +96,9 @@ const REDIRECT_HINT_MAX_TOOLS = 16;
|
|
|
93
96
|
export function cursorNativeExecRedirectHint(
|
|
94
97
|
tools: readonly Pick<OcxTool, "namespace" | "name" | "freeform">[] | undefined,
|
|
95
98
|
mcpToolDefs: readonly Pick<McpToolDefinition, "name" | "providerIdentifier">[] = [],
|
|
99
|
+
modelId?: string,
|
|
96
100
|
): string | undefined {
|
|
101
|
+
if (cursorUsesPlainToolWording(modelId)) return cursorPlainNativeExecRedirectHint(tools, mcpToolDefs);
|
|
97
102
|
const clientTools = tools ?? [];
|
|
98
103
|
if (cursorRequestHasShellAlias(clientTools)) return undefined;
|
|
99
104
|
// Code mode (freeform unified `exec`, no bare shell bridge): the default bridge wording names
|
|
@@ -107,7 +112,7 @@ export function cursorNativeExecRedirectHint(
|
|
|
107
112
|
// A request with no client tools but configured MCP tools still gets those named; a request that
|
|
108
113
|
// advertises nothing at all keeps the default bridge wording.
|
|
109
114
|
const names = [...new Set([
|
|
110
|
-
...clientTools.map(cursorToolWireName),
|
|
115
|
+
...clientTools.map(tool => cursorToolWireName(tool, clientTools)),
|
|
111
116
|
...mcpToolDefs.map(def => `mcp_${def.providerIdentifier}_${def.name}`),
|
|
112
117
|
])];
|
|
113
118
|
if (names.length === 0) return undefined;
|
|
@@ -704,6 +709,9 @@ export function cursorBlobStoreDebugSnapshotForTests(): Array<{
|
|
|
704
709
|
|
|
705
710
|
export async function handleCursorNativeExec(execMsg: ExecServerMessage, deps: CursorNativeExecContext = {}): Promise<Uint8Array[]> {
|
|
706
711
|
const execCase = execMsg.message.case;
|
|
712
|
+
if (deps.plainToolWording && !deps.nativeExecRedirectHint) {
|
|
713
|
+
deps = { ...deps, nativeExecRedirectHint: cursorPlainNativeExecFallback([...(deps.clientToolDefs ?? []), ...(deps.mcpToolDefs ?? [])]) };
|
|
714
|
+
}
|
|
707
715
|
if (execCase === "requestContextArgs") {
|
|
708
716
|
const tools = [...(deps.mcpToolDefs ?? []), ...(deps.clientToolDefs ?? [])];
|
|
709
717
|
return [execBytes(execMsg, "requestContextResult", create(RequestContextResultSchema, {
|
|
@@ -712,8 +720,8 @@ export async function handleCursorNativeExec(execMsg: ExecServerMessage, deps: C
|
|
|
712
720
|
}
|
|
713
721
|
if (!cursorUnsafeNativeLocalExecEnabled(deps)) {
|
|
714
722
|
if (execCase === "readArgs") return [rejectReadExecForPolicy(execMsg, deps.nativeExecRedirectHint)];
|
|
715
|
-
if (execCase === "writeArgs") return [rejectWriteExecForPolicy(execMsg, deps.nativeExecRedirectHint)];
|
|
716
|
-
if (execCase === "deleteArgs") return [rejectDeleteExecForPolicy(execMsg, deps.nativeExecRedirectHint)];
|
|
723
|
+
if (execCase === "writeArgs") return [rejectWriteExecForPolicy(execMsg, deps.nativeExecRedirectHint, deps.plainToolWording)];
|
|
724
|
+
if (execCase === "deleteArgs") return [rejectDeleteExecForPolicy(execMsg, deps.nativeExecRedirectHint, deps.plainToolWording)];
|
|
717
725
|
if (execCase === "lsArgs") return [rejectLsExecForPolicy(execMsg, deps.nativeExecRedirectHint)];
|
|
718
726
|
if (execCase === "grepArgs") return [rejectGrepExecForPolicy(execMsg, deps.nativeExecRedirectHint)];
|
|
719
727
|
if (execCase === "shellArgs") return [rejectShellExecForPolicy(execMsg, deps.nativeExecRedirectHint)];
|
|
@@ -723,8 +731,8 @@ export async function handleCursorNativeExec(execMsg: ExecServerMessage, deps: C
|
|
|
723
731
|
if (execCase === "fetchArgs") return [rejectFetchExecForPolicy(execMsg, deps.nativeExecRedirectHint)];
|
|
724
732
|
}
|
|
725
733
|
if (execCase === "readArgs") return [readExec(execMsg)];
|
|
726
|
-
if (execCase === "writeArgs") return [deps.rejectNativeFileMutations ? rejectWriteExecForApplyPatch(execMsg, deps.structuredEditAvailable === true) : writeExec(execMsg)];
|
|
727
|
-
if (execCase === "deleteArgs") return [deps.rejectNativeFileMutations ? rejectDeleteExecForApplyPatch(execMsg, deps.structuredEditAvailable === true) : deleteExec(execMsg)];
|
|
734
|
+
if (execCase === "writeArgs") return [deps.rejectNativeFileMutations ? rejectWriteExecForApplyPatch(execMsg, deps.structuredEditAvailable === true, deps.plainToolWording) : writeExec(execMsg)];
|
|
735
|
+
if (execCase === "deleteArgs") return [deps.rejectNativeFileMutations ? rejectDeleteExecForApplyPatch(execMsg, deps.structuredEditAvailable === true, deps.plainToolWording) : deleteExec(execMsg)];
|
|
728
736
|
if (execCase === "lsArgs") return [lsExec(execMsg)];
|
|
729
737
|
if (execCase === "grepArgs") return [grepExec(execMsg)];
|
|
730
738
|
if (execCase === "shellArgs") return [shellExec(execMsg, deps.nativeExecRedirectHint)];
|