@yansigit/opencodex 2.33.0 → 2.35.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/index-BjCaHxdz.js +112 -0
- package/gui/dist/assets/index-DLkXOXLC.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +79 -2
- package/src/adapters/command-code.ts +141 -23
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/discovery.ts +60 -2
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +162 -0
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +17 -1
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-fs.ts +13 -12
- package/src/adapters/cursor/native-exec-network.ts +3 -5
- package/src/adapters/cursor/native-exec-policy.ts +47 -0
- package/src/adapters/cursor/native-exec-shell.ts +116 -31
- package/src/adapters/cursor/native-exec.ts +38 -10
- package/src/adapters/cursor/protobuf-events.ts +28 -2
- package/src/adapters/cursor/protobuf-request.ts +93 -41
- package/src/adapters/cursor/request-builder.ts +39 -10
- package/src/adapters/cursor/tool-definitions.ts +27 -3
- package/src/adapters/cursor/tool-result-normalize.ts +51 -6
- package/src/adapters/cursor/types.ts +23 -4
- package/src/adapters/cursor.ts +170 -29
- package/src/adapters/google-aistudio-parser.ts +49 -0
- package/src/adapters/google-antigravity-replay.ts +105 -25
- package/src/adapters/google-antigravity-wire.ts +5 -0
- package/src/adapters/google-errors.ts +41 -12
- package/src/adapters/google-http.ts +12 -11
- package/src/adapters/google.ts +219 -36
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +15 -0
- package/src/adapters/kiro-tools.ts +43 -15
- package/src/adapters/kiro.ts +54 -9
- package/src/adapters/openai-chat.ts +286 -242
- package/src/adapters/openai-responses.ts +335 -24
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-catalog-nudge.ts +2 -2
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/bridge.ts +67 -26
- package/src/chat/inbound.ts +29 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/outbound.ts +10 -8
- package/src/cli/account-api.ts +27 -7
- package/src/cli/account-extended.ts +10 -3
- package/src/cli/account.ts +29 -5
- package/src/cli/alias.ts +66 -0
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +13 -1
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +6 -1
- package/src/cli/init.ts +1 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +13 -7
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/registry.ts +6 -1
- package/src/cli/telemetry-commands.ts +25 -0
- package/src/cli/v2.ts +34 -10
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +80 -6
- package/src/codex/auth-api.ts +48 -8
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/catalog/aggregation.ts +6 -0
- package/src/codex/catalog/model-metadata.ts +13 -1
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +16 -0
- package/src/codex/catalog/provider-fetch.ts +20 -3
- package/src/codex/catalog/sync.ts +127 -2
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +22 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +18 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +14 -1
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +123 -18
- package/src/codex/routing.ts +9 -0
- package/src/codex/subagent-model-fallback.ts +198 -27
- package/src/codex/transition-state.ts +107 -8
- package/src/combos/types.ts +10 -0
- package/src/compatibility/openai-responses.ts +33 -1
- package/src/config/autonomous-remediation.ts +21 -0
- package/src/config/provider-validation.ts +14 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +191 -17
- package/src/generated/compatibility-version.json +279 -159
- package/src/generated/model-metadata.ts +3 -0
- package/src/images/loop.ts +5 -4
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +11 -2
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/oauth/aistudio-credentials.ts +65 -0
- package/src/oauth/aistudio-native-daemon.ts +116 -0
- package/src/oauth/aistudio-session-sync.ts +95 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/google-aistudio-auth.ts +98 -0
- package/src/oauth/index.ts +57 -5
- package/src/oauth/key-providers.ts +18 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/login-cli.ts +65 -1
- package/src/oauth/types.ts +15 -0
- package/src/providers/codex-capacity.ts +5 -2
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +29 -1
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +157 -46
- package/src/providers/registry.ts +184 -71
- package/src/providers/slug-codec.ts +52 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +34 -10
- package/src/responses/parser.ts +4 -0
- package/src/responses/schema.ts +5 -1
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/router.ts +43 -2
- package/src/routing/account-pool/cooldown.ts +8 -0
- package/src/routing/account-pool/index.ts +1 -0
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +24 -0
- package/src/server/chat-completions.ts +26 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/effort-policy.ts +16 -0
- package/src/server/index.ts +180 -14
- package/src/server/lifecycle.ts +52 -1
- package/src/server/management/agent-settings-routes.ts +31 -15
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +27 -9
- package/src/server/management/context.ts +9 -0
- package/src/server/management/logs-usage-routes.ts +11 -5
- package/src/server/management/model-routes.ts +266 -0
- package/src/server/management/oauth-account-routes.ts +13 -3
- package/src/server/management/provider-routes.ts +137 -3
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management-api.ts +2 -0
- package/src/server/port-reclaim.ts +19 -1
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +33 -0
- package/src/server/request-log.ts +48 -21
- package/src/server/responses/collaboration.ts +42 -5
- package/src/server/responses/combo-stream-preflight.ts +10 -3
- package/src/server/responses/core.ts +575 -140
- package/src/server/responses/empty-completion-guard.ts +35 -0
- package/src/server/responses/fetch-helpers.ts +14 -6
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/policy-fallback.ts +1 -1
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses/ws-upstream.ts +35 -5
- package/src/server/responses-custom-tool-repair.ts +52 -7
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +14 -2
- package/src/smoke/fingerprint-cache.ts +133 -0
- package/src/smoke/live-scenarios.ts +33 -0
- package/src/smoke/runner.ts +119 -0
- package/src/telemetry/dispatcher.ts +44 -0
- package/src/telemetry/fingerprint.ts +24 -0
- package/src/telemetry/hook.ts +43 -0
- package/src/telemetry/ledger.ts +54 -0
- package/src/telemetry/types.ts +23 -0
- package/src/types/config.ts +66 -14
- package/src/types/provider.ts +79 -1
- package/src/types/request.ts +18 -10
- package/src/types/tools.ts +30 -11
- package/src/types.ts +1 -0
- package/src/usage/command-code-manifest.ts +116 -0
- package/src/usage/cost.ts +2 -2
- package/src/usage/expected-prices.ts +126 -24
- package/src/usage/log.ts +18 -8
- package/src/usage/summary.ts +34 -12
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/index.ts +16 -8
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-DKLr4LTE.js +0 -102
- package/gui/dist/assets/index-DrSQdTRd.css +0 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { create, fromBinary, toBinary, toJson } from "@bufbuild/protobuf";
|
|
2
2
|
import { fromJson, type JsonValue } from "@bufbuild/protobuf";
|
|
3
3
|
import { ValueSchema } from "@bufbuild/protobuf/wkt";
|
|
4
|
-
import type { OcxAssistantContentPart, OcxMessage,
|
|
4
|
+
import type { OcxAssistantContentPart, OcxMessage, OcxToolResultMessage } from "../../types";
|
|
5
5
|
import { namespacedToolName } from "../../types";
|
|
6
6
|
import type { CursorRunRequest } from "./types";
|
|
7
|
+
import { decodeCursorCallId } from "./call-id";
|
|
7
8
|
import { cursorNeedsExternalToolContinuation, isCursorExternalWireModel } from "./discovery";
|
|
8
9
|
import { normalizeCursorToolResultText } from "./tool-result-normalize";
|
|
9
10
|
import { debugProviderDiagnostic } from "../../lib/debug";
|
|
@@ -77,7 +78,22 @@ export const CURSOR_EXTERNAL_ROOT_BYTE_LIMIT = 512 * 1024;
|
|
|
77
78
|
* results already stored in history blobs are visible without a ResumeAction.
|
|
78
79
|
*/
|
|
79
80
|
export const CURSOR_EXTERNAL_TOOL_CONTINUATION_TEXT =
|
|
80
|
-
"Continue: the requested tool results are provided in the conversation history above.";
|
|
81
|
+
"Continue: the requested tool results are provided in the conversation history above. Answer the user request or proceed with the next step directly without repeating status summaries or greetings.";
|
|
82
|
+
|
|
83
|
+
export function externalToolContinuationText(rawMessages?: readonly OcxMessage[]): string {
|
|
84
|
+
const last = rawMessages?.at(-1);
|
|
85
|
+
if (last?.role === "toolResult") {
|
|
86
|
+
const raw = typeof last.content === "string" ? last.content : JSON.stringify(last.content ?? "");
|
|
87
|
+
const trimmed = raw.trim();
|
|
88
|
+
if (
|
|
89
|
+
(last.toolName?.includes("list_agents") || last.toolName?.includes("search"))
|
|
90
|
+
&& (trimmed === "[]" || trimmed === "" || trimmed === "{}" || trimmed === "null")
|
|
91
|
+
) {
|
|
92
|
+
return `${CURSOR_EXTERNAL_TOOL_CONTINUATION_TEXT} If a prior discovery or list tool returned empty results (e.g. no sub-agents currently active), proceed directly with your next concrete action using available tools.`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return CURSOR_EXTERNAL_TOOL_CONTINUATION_TEXT;
|
|
96
|
+
}
|
|
81
97
|
|
|
82
98
|
/** Runtime timezone for protobuf RequestContextEnv (dynamic, never hardcoded). */
|
|
83
99
|
function runtimeTimeZone(): string {
|
|
@@ -165,26 +181,9 @@ function truncateToolResultBlob(entry: RootBlobCandidate, maxBytes: number): Roo
|
|
|
165
181
|
return markerOnly.byteLength <= maxBytes ? markerOnly : null;
|
|
166
182
|
}
|
|
167
183
|
|
|
168
|
-
function structuredOutputPrompt(textFormat: OcxRequestOptions["textFormat"]): string | undefined {
|
|
169
|
-
if (!textFormat) return undefined;
|
|
170
|
-
if (textFormat.type === "json_schema" && textFormat.schema) {
|
|
171
|
-
return [
|
|
172
|
-
"Your response must be a single valid JSON object strictly conforming to this JSON schema:",
|
|
173
|
-
JSON.stringify(textFormat.schema),
|
|
174
|
-
"Do not include any surrounding markdown fences, preamble, or commentary; return raw JSON only.",
|
|
175
|
-
].join("\n");
|
|
176
|
-
}
|
|
177
|
-
if (textFormat.type === "json_object") {
|
|
178
|
-
return "Your response must be a single valid JSON object. Do not include any markdown fences or commentary; return raw JSON only.";
|
|
179
|
-
}
|
|
180
|
-
return undefined;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
184
|
function systemPromptBlobs(request: CursorRunRequest): RootBlobCandidate[] {
|
|
184
185
|
const prompts = request.system.length > 0 ? [...request.system] : ["You are a helpful assistant."];
|
|
185
186
|
if (cursorRequestHasShellAlias(request.tools)) prompts.push(CURSOR_SHELL_ALIAS_SYSTEM_NOTE);
|
|
186
|
-
const structuredPrompt = structuredOutputPrompt(request.textFormat);
|
|
187
|
-
if (structuredPrompt) prompts.push(structuredPrompt);
|
|
188
187
|
const cursorToolGuidance = buildCursorToolGuidanceSystemNote(
|
|
189
188
|
cursorToolsForActivePrompt(request.tools, activePromptText(request), request.toolChoice),
|
|
190
189
|
request.toolChoice,
|
|
@@ -207,11 +206,10 @@ function assistantRootText(
|
|
|
207
206
|
// Cursor builds the actual model prompt from rootPromptMessagesJson (turns[] is UI/display metadata),
|
|
208
207
|
// so prior history must be replayed here or a ResumeAction has nothing model-visible to continue from.
|
|
209
208
|
// The active user message is excluded because it travels in the action. When the continuation cannot
|
|
210
|
-
// rely on native MCP turn state, tool results stay assistant-role text
|
|
211
|
-
// as `<user_query>` (#1992).
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
// replay. Each entry is a SHA-256 blob ID.
|
|
209
|
+
// rely on native MCP turn state, tool results stay assistant-role text with a [Tool Result] /
|
|
210
|
+
// [Tool Error] marker so Cursor does not wrap them as `<user_query>` (#1992). Native resume models
|
|
211
|
+
// already carry the paired MCP result on turns[], so that marker is omitted from root replay — Auto
|
|
212
|
+
// few-shot-mimics it as chat text otherwise. Each entry is a SHA-256 blob ID.
|
|
215
213
|
function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobRequestScopeToken): {
|
|
216
214
|
ids: Uint8Array[];
|
|
217
215
|
byteLength: number;
|
|
@@ -235,6 +233,42 @@ function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobR
|
|
|
235
233
|
const echoToolResultInRoot = cursorNeedsExternalToolContinuation(request.modelId);
|
|
236
234
|
const lastRawIsToolResult = messages.at(-1)?.role === "toolResult";
|
|
237
235
|
const activeUserIndex = lastRawIsToolResult ? -1 : lastActionIndex(messages);
|
|
236
|
+
// Repetition breaker (devlog 260826 gap-9): external full-replay flattens history to text,
|
|
237
|
+
// so N identical assistant/tool-result rounds replay as N identical lines and PRIME the model
|
|
238
|
+
// to emit the same line again (self-reinforcing loop: S2a 180x, identical-probe repetition).
|
|
239
|
+
// Collapse consecutive duplicates into one entry + a count marker, and count collapses so a
|
|
240
|
+
// strategy-change note can be appended when the pattern is severe.
|
|
241
|
+
let lastReplayText: string | undefined;
|
|
242
|
+
let lastReplayEntry: RootBlobCandidate | undefined;
|
|
243
|
+
let collapsedRepeats = 0;
|
|
244
|
+
let maxRunLength = 1;
|
|
245
|
+
let currentRun = 1;
|
|
246
|
+
const pushDeduped = (
|
|
247
|
+
payload: { role: string; content: [{ type: "text"; text: string }] },
|
|
248
|
+
role: RootBlobCandidate["role"],
|
|
249
|
+
opts: { messageIndex: number; text?: string },
|
|
250
|
+
normalized: string,
|
|
251
|
+
): void => {
|
|
252
|
+
if (externalModel && lastReplayText !== undefined && normalized === lastReplayText && lastReplayEntry) {
|
|
253
|
+
collapsedRepeats++;
|
|
254
|
+
currentRun++;
|
|
255
|
+
if (currentRun > maxRunLength) maxRunLength = currentRun;
|
|
256
|
+
const marked = `${normalized}\n[note: this exact output was produced ${currentRun} times in a row]`;
|
|
257
|
+
const replacement = rootBlobCandidate(
|
|
258
|
+
{ role: payload.role, content: [{ type: "text", text: marked }] },
|
|
259
|
+
role,
|
|
260
|
+
opts,
|
|
261
|
+
);
|
|
262
|
+
entries[entries.indexOf(lastReplayEntry)] = replacement;
|
|
263
|
+
lastReplayEntry = replacement;
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
currentRun = 1;
|
|
267
|
+
const entry = rootBlobCandidate(payload, role, opts);
|
|
268
|
+
entries.push(entry);
|
|
269
|
+
lastReplayText = normalized;
|
|
270
|
+
lastReplayEntry = entry;
|
|
271
|
+
};
|
|
238
272
|
|
|
239
273
|
for (let i = 0; i < messages.length; i++) {
|
|
240
274
|
if (i === activeUserIndex) break;
|
|
@@ -246,6 +280,9 @@ function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobR
|
|
|
246
280
|
// A bare string survives blob hydration but external workers reject the completed replay
|
|
247
281
|
// before tokenization (`usedTokens: 0`, then invalid_argument).
|
|
248
282
|
if (text.length > 0) {
|
|
283
|
+
lastReplayText = undefined;
|
|
284
|
+
lastReplayEntry = undefined;
|
|
285
|
+
currentRun = 1;
|
|
249
286
|
entries.push(rootBlobCandidate({
|
|
250
287
|
role: "user",
|
|
251
288
|
content: [{ type: "text", text }],
|
|
@@ -256,25 +293,30 @@ function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobR
|
|
|
256
293
|
// Native Composer state can preserve it through ThinkingMessage/history structures.
|
|
257
294
|
const text = assistantRootText(message, !externalModel).trim();
|
|
258
295
|
if (text.length > 0) {
|
|
259
|
-
|
|
296
|
+
pushDeduped(
|
|
260
297
|
{ role: "assistant", content: [{ type: "text", text }] },
|
|
261
298
|
"assistant",
|
|
262
299
|
{ messageIndex: i },
|
|
263
|
-
|
|
300
|
+
text,
|
|
301
|
+
);
|
|
264
302
|
}
|
|
265
303
|
// Assistant tool CALLS are intentionally NOT replayed as visible "[Tool Call]" text here.
|
|
266
304
|
} else if (message.role === "toolResult") {
|
|
267
|
-
// Native resume models already receive the paired MCP result through turns[].
|
|
268
|
-
//
|
|
305
|
+
// Native resume models already receive the paired MCP result through turns[]. Replaying
|
|
306
|
+
// the same payload as assistant-role "[Tool Result]" / "[tool_result]" text teaches Auto
|
|
307
|
+
// to echo that envelope as chat instead of continuing from the structured result.
|
|
269
308
|
if (!echoToolResultInRoot) continue;
|
|
270
309
|
const text = externalToolResultToText(message);
|
|
271
|
-
|
|
272
|
-
toolResultRootPayload(text),
|
|
273
|
-
"toolResult",
|
|
274
|
-
{ messageIndex: i, text },
|
|
275
|
-
));
|
|
310
|
+
pushDeduped(toolResultRootPayload(text), "toolResult", { messageIndex: i, text }, text);
|
|
276
311
|
}
|
|
277
312
|
}
|
|
313
|
+
// Severe repetition: tell the model ONCE, imperatively, to change strategy.
|
|
314
|
+
if (externalModel && maxRunLength >= 3) {
|
|
315
|
+
entries.push(rootBlobCandidate({
|
|
316
|
+
role: "user",
|
|
317
|
+
content: [{ type: "text", text: `[context note] The transcript above contains the same output repeated ${maxRunLength} times in a row. Repeating it again is a failure. Take a DIFFERENT action now, or state plainly what is blocking progress.` }],
|
|
318
|
+
}, "user", {}));
|
|
319
|
+
}
|
|
278
320
|
|
|
279
321
|
let selected = entries;
|
|
280
322
|
let historyMessageStart = 0;
|
|
@@ -450,6 +492,7 @@ function decodeResultParts(message: OcxToolResultMessage): DecodedResultPart[] |
|
|
|
450
492
|
if (typeof content === "string") return undefined;
|
|
451
493
|
return content.map((part): DecodedResultPart => {
|
|
452
494
|
if (part.type === "text") return { kind: "text", text: part.text };
|
|
495
|
+
if (part.type === "video") return { kind: "text", text: "[video]" };
|
|
453
496
|
const decoded = decodeInlineImage(part.imageUrl);
|
|
454
497
|
return decoded ? { kind: "image", ...decoded } : { kind: "undecodable" };
|
|
455
498
|
});
|
|
@@ -544,7 +587,7 @@ function toolResultToText(message: OcxToolResultMessage): string {
|
|
|
544
587
|
const normalized = normalizedToolResult(message, contentToText(message.content));
|
|
545
588
|
return [
|
|
546
589
|
"[tool_result]",
|
|
547
|
-
`call_id: ${message.toolCallId}`,
|
|
590
|
+
`call_id: ${decodeCursorCallId(message.toolCallId)}`,
|
|
548
591
|
`name: ${namespacedToolName(message.toolNamespace, message.toolName)}`,
|
|
549
592
|
`is_error: ${normalized.isError}`,
|
|
550
593
|
"output:",
|
|
@@ -555,7 +598,7 @@ function toolResultToText(message: OcxToolResultMessage): string {
|
|
|
555
598
|
function externalToolResultToText(message: OcxToolResultMessage): string {
|
|
556
599
|
const normalized = normalizedToolResult(message, contentToText(message.content));
|
|
557
600
|
const label = normalized.isError ? "Tool error" : "Tool output";
|
|
558
|
-
return `${label} for ${namespacedToolName(message.toolNamespace, message.toolName)} (call_id: ${message.toolCallId}, is_error: ${normalized.isError}):\n${normalized.text}`;
|
|
601
|
+
return `${label} for ${namespacedToolName(message.toolNamespace, message.toolName)} (call_id: ${decodeCursorCallId(message.toolCallId)}, is_error: ${normalized.isError}):\n${normalized.text}`;
|
|
559
602
|
}
|
|
560
603
|
|
|
561
604
|
/**
|
|
@@ -611,7 +654,7 @@ function toolCallStep(
|
|
|
611
654
|
args: create(McpArgsSchema, {
|
|
612
655
|
name: toolName,
|
|
613
656
|
toolName,
|
|
614
|
-
toolCallId: part.id,
|
|
657
|
+
toolCallId: decodeCursorCallId(part.id),
|
|
615
658
|
providerIdentifier: OCX_RESPONSES_TOOL_PROVIDER,
|
|
616
659
|
args,
|
|
617
660
|
}),
|
|
@@ -855,8 +898,10 @@ function buildPreparedCursorRunRequest(
|
|
|
855
898
|
? "userMessageAction"
|
|
856
899
|
: "resumeAction";
|
|
857
900
|
const actionText = externalToolContinuation
|
|
858
|
-
? CURSOR_EXTERNAL_TOOL_CONTINUATION_TEXT
|
|
859
|
-
:
|
|
901
|
+
? (request.echoRetryContinuationText ?? CURSOR_EXTERNAL_TOOL_CONTINUATION_TEXT)
|
|
902
|
+
: request.echoRetryContinuationText
|
|
903
|
+
? `${text}\n\n[correction] ${request.echoRetryContinuationText}`
|
|
904
|
+
: text;
|
|
860
905
|
const action = create(ConversationActionSchema, {
|
|
861
906
|
action: actionCase === "userMessageAction"
|
|
862
907
|
? {
|
|
@@ -983,12 +1028,15 @@ function buildPreparedCursorRunRequest(
|
|
|
983
1028
|
displayName: request.modelId,
|
|
984
1029
|
displayNameShort: request.modelId,
|
|
985
1030
|
aliases: [],
|
|
1031
|
+
...(request.maxMode === true ? { maxMode: true } : {}),
|
|
986
1032
|
}),
|
|
987
1033
|
} : {}),
|
|
988
|
-
...(requestedModelParameters.length > 0 ? {
|
|
1034
|
+
...(requestedModelParameters.length > 0 || request.maxMode === true ? {
|
|
989
1035
|
requestedModel: create(RequestedModelSchema, {
|
|
990
1036
|
modelId: request.modelId,
|
|
991
|
-
|
|
1037
|
+
// Max Mode must be raised on BOTH RequestedModel and ModelDetails; missing either
|
|
1038
|
+
// can invalid_argument upstream (devlog 260826 070).
|
|
1039
|
+
maxMode: request.maxMode === true,
|
|
992
1040
|
parameters: requestedModelParameters.map(parameter =>
|
|
993
1041
|
create(RequestedModel_ModelParameterbytesSchema, parameter)),
|
|
994
1042
|
}),
|
|
@@ -1006,7 +1054,11 @@ function buildPreparedCursorRunRequest(
|
|
|
1006
1054
|
// the event-state `clientToolNames` use (live-transport.ts). Advertising the raw `request.tools`
|
|
1007
1055
|
// here would let mcp_tools expose a tool that the event state does not recognize for a generic
|
|
1008
1056
|
// tool-count prompt, so a call to it would be rejected as an unknown Responses tool.
|
|
1009
|
-
|
|
1057
|
+
// An explicitly empty McpTools wrapper (bare API callers) suppresses Cursor's default
|
|
1058
|
+
// native catalog; an absent field lets identified Codex sessions keep it (devlog 260826 040).
|
|
1059
|
+
...(mcpToolDefs.length > 0 || request.suppressDefaultCursorToolCatalog === true
|
|
1060
|
+
? { mcpTools: create(McpToolsSchema, { mcpTools: mcpToolDefs }) }
|
|
1061
|
+
: {}),
|
|
1010
1062
|
});
|
|
1011
1063
|
|
|
1012
1064
|
const message = create(AgentClientMessageSchema, {
|
|
@@ -10,6 +10,8 @@ import type {
|
|
|
10
10
|
import { isAllowedToolChoice, namespacedToolName, toolChoiceAliases, type OcxTool, type OcxToolChoice } from "../../types";
|
|
11
11
|
import type { CursorRequestMessage, CursorRequestedModelParameter, CursorRunRequest } from "./types";
|
|
12
12
|
import { cursorCheckpointModelAffinityId, cursorWireModelSelection, type CursorRoutingLevel } from "./discovery";
|
|
13
|
+
import { cursorUltraBaseModelId } from "./discovery";
|
|
14
|
+
import { decodeCursorCallId } from "./call-id";
|
|
13
15
|
import { cursorEffortSuffix, cursorRequestWireModelIdWithEffort } from "./effort-map";
|
|
14
16
|
import {
|
|
15
17
|
cursorMcpToolEncodedSize,
|
|
@@ -188,13 +190,19 @@ function normalizeCursorModelId(modelId: string, reasoning?: string): {
|
|
|
188
190
|
modelId: string;
|
|
189
191
|
requestedModelParameters?: readonly CursorRequestedModelParameter[];
|
|
190
192
|
routingLevel?: CursorRoutingLevel;
|
|
193
|
+
maxMode?: boolean;
|
|
191
194
|
} {
|
|
192
|
-
|
|
195
|
+
// Synthetic ultra (-1m) picker rows resolve to their wire base with Max Mode on
|
|
196
|
+
// (devlog 260826 070); the marker never reaches the wire.
|
|
197
|
+
const ultraBase = cursorUltraBaseModelId(modelId);
|
|
198
|
+
const selection = cursorWireModelSelection(ultraBase ?? modelId);
|
|
199
|
+
const maxMode = ultraBase !== undefined ? { maxMode: true } : {};
|
|
193
200
|
const id = selection.modelId;
|
|
194
201
|
const suffix = cursorEffortSuffix(id, reasoning);
|
|
195
202
|
if ((id === "grok-4.5-fast" || id === "grok-4.6-fast") && suffix) {
|
|
196
203
|
return {
|
|
197
204
|
...selection,
|
|
205
|
+
...maxMode,
|
|
198
206
|
modelId: id.slice(0, -"-fast".length),
|
|
199
207
|
requestedModelParameters: [
|
|
200
208
|
{ id: "effort", value: suffix },
|
|
@@ -202,7 +210,14 @@ function normalizeCursorModelId(modelId: string, reasoning?: string): {
|
|
|
202
210
|
],
|
|
203
211
|
};
|
|
204
212
|
}
|
|
205
|
-
|
|
213
|
+
if (id === "composer-2.5") {
|
|
214
|
+
return {
|
|
215
|
+
...selection,
|
|
216
|
+
modelId: id,
|
|
217
|
+
requestedModelParameters: [{ id: "fast", value: "false" }],
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return { ...selection, ...maxMode, modelId: suffix ? cursorRequestWireModelIdWithEffort(id, suffix) : id };
|
|
206
221
|
}
|
|
207
222
|
|
|
208
223
|
function contentPartToText(part: OcxContentPart | OcxAssistantContentPart): string | undefined {
|
|
@@ -226,7 +241,7 @@ function contentPartToText(part: OcxContentPart | OcxAssistantContentPart): stri
|
|
|
226
241
|
function toolResultToText(message: OcxToolResultMessage): string {
|
|
227
242
|
return [
|
|
228
243
|
"[tool_result]",
|
|
229
|
-
`call_id: ${message.toolCallId}`,
|
|
244
|
+
`call_id: ${decodeCursorCallId(message.toolCallId)}`,
|
|
230
245
|
`name: ${namespacedToolName(message.toolNamespace, message.toolName)}`,
|
|
231
246
|
`is_error: ${message.isError}`,
|
|
232
247
|
"output:",
|
|
@@ -298,7 +313,7 @@ export function cursorConversationIdFromClientThread(threadId: string, identityS
|
|
|
298
313
|
|
|
299
314
|
/**
|
|
300
315
|
* Resolve the Cursor conversation id for this turn.
|
|
301
|
-
* Priority: force-fresh → isolate helper → remembered →
|
|
316
|
+
* Priority: force-fresh → isolate helper → remembered → client thread owner → random.
|
|
302
317
|
* Never use OpenAI Responses `previous_response_id` (resp_*) or shared `prompt_cache_key`
|
|
303
318
|
* (cache-cohort fingerprint, not conversation ownership).
|
|
304
319
|
*/
|
|
@@ -310,7 +325,7 @@ export function resolveCursorConversationId(
|
|
|
310
325
|
if (options.forceFreshConversation === true) return generatedCursorConversationId();
|
|
311
326
|
if (parsed._cursorIsolateConversation === true) return generatedCursorConversationId();
|
|
312
327
|
if (parsed._cursorConversationId) return parsed._cursorConversationId;
|
|
313
|
-
const threadId = parsed
|
|
328
|
+
const threadId = cursorClientThreadOwner(parsed);
|
|
314
329
|
if (threadId) {
|
|
315
330
|
const recovered = lookupCursorThreadConversation(threadId, parsed._cursorIdentityScope);
|
|
316
331
|
if (recovered) return recovered;
|
|
@@ -319,6 +334,10 @@ export function resolveCursorConversationId(
|
|
|
319
334
|
return generatedCursorConversationId();
|
|
320
335
|
}
|
|
321
336
|
|
|
337
|
+
export function cursorClientThreadOwner(parsed: OcxParsedRequest): string | undefined {
|
|
338
|
+
return parsed._clientThreadId?.trim() || parsed._cursorClientThreadId?.trim() || undefined;
|
|
339
|
+
}
|
|
340
|
+
|
|
322
341
|
function updateFramed(hash: ReturnType<typeof createHash>, value: string): void {
|
|
323
342
|
const bytes = Buffer.from(value, "utf8");
|
|
324
343
|
const length = Buffer.allocUnsafe(4);
|
|
@@ -361,6 +380,7 @@ function lookupPrefixSnapshot(
|
|
|
361
380
|
const modelId = cursorCheckpointModelAffinityId(request.modelId);
|
|
362
381
|
for (let covered = parsed.context.messages.length; covered >= 1; covered--) {
|
|
363
382
|
const snapshot = getCursorCheckpointForPrefix({
|
|
383
|
+
conversationId: request.conversationId,
|
|
364
384
|
prefixDigest: cursorCoveredPrefixDigest(parsed, covered),
|
|
365
385
|
systemDigest,
|
|
366
386
|
coveredMessageCount: covered,
|
|
@@ -404,10 +424,14 @@ function resolveCursorCheckpoint(
|
|
|
404
424
|
snapshot = getCursorCheckpoint(ref);
|
|
405
425
|
if (!snapshot) return { reason: "expired" };
|
|
406
426
|
} else {
|
|
427
|
+
if (
|
|
428
|
+
isolated
|
|
429
|
+
|| (!parsed._cursorConversationId && !cursorClientThreadOwner(parsed))
|
|
430
|
+
) return { reason: "missing_ref" };
|
|
407
431
|
snapshot = lookupPrefixSnapshot(parsed, request, identityScope);
|
|
408
432
|
if (!snapshot) return { reason: "missing_ref" };
|
|
409
433
|
}
|
|
410
|
-
if (
|
|
434
|
+
if (snapshot.conversationId !== request.conversationId) {
|
|
411
435
|
return { reason: "conversation_changed" };
|
|
412
436
|
}
|
|
413
437
|
if (snapshot.identityScope !== identityScope) return { reason: "identity_changed" };
|
|
@@ -436,16 +460,21 @@ export function createCursorRequest(
|
|
|
436
460
|
modelId: model.modelId,
|
|
437
461
|
...(model.requestedModelParameters ? { requestedModelParameters: model.requestedModelParameters } : {}),
|
|
438
462
|
...(model.routingLevel ? { routingLevel: model.routingLevel } : {}),
|
|
463
|
+
...(model.maxMode ? { maxMode: true } : {}),
|
|
439
464
|
conversationId: resolveCursorConversationId(parsed, model.modelId, options),
|
|
440
465
|
system: [...(parsed.context.systemPrompt ?? []), ...(limitNote ? [limitNote] : [])],
|
|
441
466
|
messages,
|
|
442
467
|
rawMessages: parsed.context.messages,
|
|
443
468
|
...(parsed._compactionRequest === true || parsed._contextCompactionBoundary === true ? { contextUsageReset: true } : {}),
|
|
444
469
|
...(parsed._compactionRequest === true ? { contextUsageStoreCheckpoints: false } : {}),
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
470
|
+
...(budget.tools.length ? { tools: budget.tools } : {}),
|
|
471
|
+
// Bare API caller (no tools, no Codex thread identity): suppress Cursor's default
|
|
472
|
+
// native tool catalog instead of paying its ~10-15K token preamble (devlog 260826 040).
|
|
473
|
+
...(budget.tools.length === 0 && !cursorClientThreadOwner(parsed)
|
|
474
|
+
? { suppressDefaultCursorToolCatalog: true }
|
|
475
|
+
: {}),
|
|
476
|
+
...(parsed.options.toolChoice ? { toolChoice: parsed.options.toolChoice } : {}),
|
|
477
|
+
...(parsed.options.parallelToolCalls !== undefined ? { parallelToolCalls: parsed.options.parallelToolCalls } : {}),
|
|
449
478
|
};
|
|
450
479
|
const resolved = resolveCursorCheckpoint(parsed, request, options);
|
|
451
480
|
if ("reason" in resolved) {
|
|
@@ -71,6 +71,13 @@ export const CURSOR_EXEC_COMMAND_INPUT_SCHEMA = {
|
|
|
71
71
|
additionalProperties: false,
|
|
72
72
|
} as const;
|
|
73
73
|
|
|
74
|
+
/** Cursor requires freeform custom tools to advertise their body as one string input. */
|
|
75
|
+
export const CURSOR_FREEFORM_INPUT_SCHEMA = {
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: { input: { type: "string" } },
|
|
78
|
+
required: ["input"],
|
|
79
|
+
} as const;
|
|
80
|
+
|
|
74
81
|
/**
|
|
75
82
|
* Structured single-replacement schema advertised to Cursor models in addition to the freeform
|
|
76
83
|
* `apply_patch` tool. Cursor-trained models reliably emit exact-match replacements (the native
|
|
@@ -386,6 +393,7 @@ export function responsesToolNameFromCursorWire(name: string, cursorToolNameMap?
|
|
|
386
393
|
|
|
387
394
|
/** Schema advertised to Cursor for this tool (may use Cursor-preferred field names like `cmd`). */
|
|
388
395
|
export function cursorToolInputSchema(tool: OcxTool): unknown {
|
|
396
|
+
if (tool.freeform) return CURSOR_FREEFORM_INPUT_SCHEMA;
|
|
389
397
|
return isBareCodexExecCommandTool(tool) ? CURSOR_EXEC_COMMAND_INPUT_SCHEMA : (tool.parameters ?? {});
|
|
390
398
|
}
|
|
391
399
|
|
|
@@ -395,6 +403,7 @@ export function cursorToolInputSchema(tool: OcxTool): unknown {
|
|
|
395
403
|
* treating `cmd` as canonical prevents the `cmd` → `command` rewrite Codex requires (#399).
|
|
396
404
|
*/
|
|
397
405
|
export function cursorToolArgNormalizeSchema(tool: OcxTool): unknown {
|
|
406
|
+
if (tool.freeform) return CURSOR_FREEFORM_INPUT_SCHEMA;
|
|
398
407
|
if (isBareCodexShellBridgeTool(tool)) {
|
|
399
408
|
return shellBridgeArgNormalizeSchema(tool);
|
|
400
409
|
}
|
|
@@ -673,11 +682,20 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
673
682
|
// Code mode: shell/edit/MCP live inside freeform `exec` as nested helpers. Without this the
|
|
674
683
|
// model probes for a top-level shell tool that is not there.
|
|
675
684
|
codeMode
|
|
676
|
-
? `\`${CODEX_UNIFIED_EXEC_TOOL}\` is Codex code mode: its body is JavaScript evaluated in a V8 isolate, not a shell command and not Node. Shell, file edits, and MCP are nested helpers called INSIDE that body as \`await tools.<name>(...)\`, for example \`await tools.exec_command({cmd: \"ls\"})\`.
|
|
685
|
+
? `\`${CODEX_UNIFIED_EXEC_TOOL}\` is Codex code mode: its body is JavaScript evaluated in a V8 isolate, not a shell command and not Node. Shell, file edits, and MCP are nested helpers called INSIDE that body as \`await tools.<name>(...)\`, for example \`await tools.exec_command({cmd: \"ls\"})\`. Read the tool description and the isolate global \`ALL_TOOLS\` (not \`tools.ALL_TOOLS\`) for helpers this turn provides; absence from the top-level catalog or from \`exec\`'s description is not absence. Those nested helpers are not themselves top-level tools, so do not call \`exec_command\` or \`shell_command\` at the top level here${codeModeOtherTopLevelNames.length > 0 ? `; every other tool this turn lists, including ${quotedNames(codeModeOtherTopLevelNames)}, remains callable at the top level as usual` : ""}. Nested \`tools.apply_patch(input)\` is host-executed: the string must begin exactly with \`*** Begin Patch\` and end with \`*** End Patch\` (no trailing \`***\` on those lines). OpenCodex does not rewrite JavaScript inside exec, so a decorated \`*** Begin Patch ***\` envelope is rejected by Codex before the file is touched.`
|
|
677
686
|
: undefined,
|
|
678
687
|
codeMode
|
|
679
688
|
? "In code mode the isolate returns nothing on its own: call `text(...)` (or `notify(...)`) on any value you need to see, or the call completes with empty output. There is no `require`, no `module`, and no filesystem or network globals; reach the host only through the nested helpers."
|
|
680
689
|
: undefined,
|
|
690
|
+
codeMode
|
|
691
|
+
? 'When commands require network access, file writes outside workspace, or fail due to sandbox/permission restrictions, pass `sandbox_permissions: "require_escalated"` and a clear `justification: "..."` to `tools.exec_command`.'
|
|
692
|
+
: undefined,
|
|
693
|
+
codeMode
|
|
694
|
+
? "Nested helpers may appear under longer `mcp_opencodex-responses_*` display names; use the exact helper names exposed by this turn."
|
|
695
|
+
: undefined,
|
|
696
|
+
codeMode
|
|
697
|
+
? "NEVER attempt Cursor-native Shell, Read, Grep, List, or any tool absent from the catalog — they are not executed in this environment and every probe wastes a turn. The exec code cell (with its nested helpers) is the ONLY execution surface; go to it directly on the FIRST attempt and do not narrate switching surfaces."
|
|
698
|
+
: undefined,
|
|
681
699
|
hasBareExec
|
|
682
700
|
? `${shellBridgeLabel} is the Codex Responses shell bridge for this turn, exposed through Cursor's tool protocol; it is not an external MCP server tool. \`shell_command\` and \`exec_command\` are aliases of the same bridge.`
|
|
683
701
|
: undefined,
|
|
@@ -685,7 +703,10 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
685
703
|
? "Your tool list may display it under a longer `mcp_opencodex-responses_shell_command` / `mcp_opencodex-responses_exec_command` name; those are the SAME tool — call whichever your list shows, and do not comment on the naming difference to the user."
|
|
686
704
|
: undefined,
|
|
687
705
|
hasBareExec
|
|
688
|
-
? `
|
|
706
|
+
? `NEVER attempt Cursor-native Shell, Read, Grep, List, or any tool not in the catalog above — they are not executed locally in this environment and every attempt wastes a turn and can stall the session. ${shellBridgeLabel} is the ONLY shell surface; go to it directly on the FIRST attempt, never as a fallback after probing a native tool. Do not narrate switching surfaces ("native is blocked, using the bridge instead") — there is exactly one surface.`
|
|
707
|
+
: undefined,
|
|
708
|
+
hasBareExec
|
|
709
|
+
? "Tool-selection commentary is forbidden: for any shell, read, grep, list, or file operation, your FIRST visible action is the bridge call itself — never a sentence about which tool you will use, which tool was redirected, or switching surfaces. Words like 차단/전환/blocked/switching must not appear in your output for tool-routing reasons."
|
|
689
710
|
: undefined,
|
|
690
711
|
hasBareExec
|
|
691
712
|
? 'When a command requires network access, file writes outside workspace, or fails due to sandbox restrictions, include `sandbox_permissions: "require_escalated"` and `justification: "..."`.'
|
|
@@ -700,6 +721,9 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
700
721
|
? `For file edits, prefer the structured edit tools ${quotedNames(structuredEditNames)} — they take replacements that OpenCodex converts into Codex \`apply_patch\` changes. Include exact leading whitespace in old_string/new_string. Use \`apply_patch\` directly only with a \`*** Begin Patch\` envelope and bare \`@@\` hunks (never git-style \`@@ -n,m +n,m @@\`); never emit patch-like plain text as tool arguments.`
|
|
701
722
|
: "For file edits, use the `apply_patch` tool, not built-in file write/delete tools."
|
|
702
723
|
: undefined,
|
|
724
|
+
hasApplyPatch
|
|
725
|
+
? "Creating or modifying file CONTENT via shell redirection (`>`, `>>`, `printf`/`echo` into a file, `cat <<EOF`, `sed -i`) is forbidden while apply_patch or the structured edit tools are advertised — use those edit tools so the change is reviewable. Shell output redirection is fine for logs/scratch pipes that are not the deliverable file."
|
|
726
|
+
: undefined,
|
|
703
727
|
hasBareExec
|
|
704
728
|
? "For tool-count demos, each counted tool must be a separate Codex shell-bridge invocation/result; do not collapse several requested tools into one chained shell command."
|
|
705
729
|
: undefined,
|
|
@@ -713,7 +737,7 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
713
737
|
"Do not count or report a tool call unless a tool result was actually returned.",
|
|
714
738
|
"When pursuing a multi-step task, check, or verification, do not stop or narrate intended future actions in plain text; immediately call the tool to execute the next step until the task is complete.",
|
|
715
739
|
hasBareExec
|
|
716
|
-
? `
|
|
740
|
+
? `For every file read, directory listing, grep, or shell operation use ${shellBridgeLabel} directly with host-shell-safe commands (POSIX: \`cat\`/\`ls\`/\`rg\`; Windows PowerShell: \`Get-Content\`/\`Get-ChildItem\`/\`Select-String\`). For file edits, use ${structuredEditNames.length > 0 ? `the structured edit tools (${quotedNames(structuredEditNames)}) or ` : ""}\`apply_patch\` when available.`
|
|
717
741
|
: undefined,
|
|
718
742
|
].filter((note): note is string => typeof note === "string");
|
|
719
743
|
return notes.join(" ");
|
|
@@ -29,6 +29,31 @@ function isNodeReplOrComputerUseTool(toolName?: string, toolNamespace?: string):
|
|
|
29
29
|
return lower.startsWith("mcp__node_repl") || lower.startsWith("mcp__computer_use");
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Codex exec / shell-bridge tool names (flat and MCP-prefixed display aliases). An empty result
|
|
34
|
+
* here is almost always a code-mode cell that never called text()/notify() — the cursor model
|
|
35
|
+
* reads the blank [tool_result], concludes prior results were lost, and spirals into
|
|
36
|
+
* re-orientation retries (devlog 260826_cursor_responses_gap, live subagent transcripts).
|
|
37
|
+
*/
|
|
38
|
+
function isCodexExecBridgeTool(toolName?: string, toolNamespace?: string): boolean {
|
|
39
|
+
if (toolNamespace && toolNamespace.includes("opencodex-responses")) return true;
|
|
40
|
+
if (!toolName) return false;
|
|
41
|
+
const lower = toolName.toLowerCase();
|
|
42
|
+
return (
|
|
43
|
+
lower === "exec"
|
|
44
|
+
|| lower === "exec_command"
|
|
45
|
+
|| lower === "shell_command"
|
|
46
|
+
// Codex CLI/desktop native tool names: the multi-round "이전 출력이 비어 있어 처음부터"
|
|
47
|
+
// restart loop reproduced via codex exec because `shell` was not in this set
|
|
48
|
+
// (devlog 260826 gap-8 QA round 2).
|
|
49
|
+
|| lower === "shell"
|
|
50
|
+
|| lower === "local_shell"
|
|
51
|
+
|| lower === "container.exec"
|
|
52
|
+
|| lower.startsWith("mcp_opencodex-responses_")
|
|
53
|
+
|| lower.startsWith("mcp__opencodex-responses__")
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
32
57
|
/** Failure states the Computer Use / node_repl runtime reports as PLAIN TEXT inside a non-error result. */
|
|
33
58
|
const RUNTIME_FAILURE_GUIDANCE: ReadonlyArray<{ marker: string; guidance: string }> = [
|
|
34
59
|
{
|
|
@@ -47,6 +72,22 @@ const RUNTIME_FAILURE_GUIDANCE: ReadonlyArray<{ marker: string; guidance: string
|
|
|
47
72
|
marker: "unsupported import in exec",
|
|
48
73
|
guidance: "Imports are not available in this exec context; use the injected globals instead.",
|
|
49
74
|
},
|
|
75
|
+
{
|
|
76
|
+
marker: "require is not defined",
|
|
77
|
+
guidance: "In Codex code-mode exec (a V8 isolate, not Node.js), require/fs/process are not available. Use await tools.exec_command({ cmd: '...' }) inside exec to inspect files or run CLI tools.",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
marker: "fs is not defined",
|
|
81
|
+
guidance: "In Codex code-mode exec (a V8 isolate, not Node.js), require/fs/process are not available. Use await tools.exec_command({ cmd: '...' }) inside exec to inspect files or run CLI tools.",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
marker: "process is not defined",
|
|
85
|
+
guidance: "In Codex code-mode exec (a V8 isolate, not Node.js), require/fs/process are not available. Use await tools.exec_command({ cmd: '...' }) inside exec to inspect files or run CLI tools.",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
marker: "module is not defined",
|
|
89
|
+
guidance: "In Codex code-mode exec (a V8 isolate, not Node.js), require/fs/process are not available. Use await tools.exec_command({ cmd: '...' }) inside exec to inspect files or run CLI tools.",
|
|
90
|
+
},
|
|
50
91
|
];
|
|
51
92
|
|
|
52
93
|
/** Matches exec wrappers whose only payload is an empty-output marker. */
|
|
@@ -80,13 +121,17 @@ export function normalizeCursorToolResultText(
|
|
|
80
121
|
changed: true,
|
|
81
122
|
};
|
|
82
123
|
}
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
124
|
+
if (isCodexExecBridgeTool(options.toolName, options.toolNamespace) && EMPTY_EXEC_OUTPUT_REGEX.test(text.trim())) {
|
|
125
|
+
return {
|
|
126
|
+
text: "[empty output: the exec cell completed but emitted nothing. This is NOT lost context and NOT a blocked tool — in code mode call text(...) or notify(...) on any value you need to see (a bare await tools.exec_command(...) is not echoed automatically); in shell mode the command simply printed nothing. Do not re-run the same call expecting different output.]",
|
|
127
|
+
isError: false,
|
|
128
|
+
changed: true,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
for (const { marker, guidance } of RUNTIME_FAILURE_GUIDANCE) {
|
|
132
|
+
if (text.includes(marker) && !text.includes("[recovery:") && !text.includes(guidance)) {
|
|
133
|
+
return { text: `${text}\n[recovery: ${guidance}]`, isError: true, changed: true };
|
|
88
134
|
}
|
|
89
135
|
}
|
|
90
136
|
return { text, isError, changed: false };
|
|
91
137
|
}
|
|
92
|
-
|
|
@@ -15,6 +15,26 @@ export interface CursorRunRequest {
|
|
|
15
15
|
requestedModelParameters?: readonly CursorRequestedModelParameter[];
|
|
16
16
|
/** Cursor Router optimization parameter; valid only while modelId is the `default` wire model. */
|
|
17
17
|
routingLevel?: CursorRoutingLevel;
|
|
18
|
+
/**
|
|
19
|
+
* Cursor Max Mode (ultra/big-context). Set from a synthetic `-1m` picker variant; the wire
|
|
20
|
+
* keeps the original model id and raises RequestedModel.maxMode + ModelDetails.maxMode
|
|
21
|
+
* (both fields — missing either can invalid_argument upstream). Devlog 260826 070.
|
|
22
|
+
*/
|
|
23
|
+
maxMode?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Bare API callers (no caller tools, no Codex thread identity) pay a ~10-15K input-token
|
|
26
|
+
* preamble because an absent AgentRunRequest.mcp_tools field makes Cursor inject its default
|
|
27
|
+
* native tool catalog. When true, an explicitly empty McpTools wrapper is serialized instead,
|
|
28
|
+
* suppressing that default. Codex-identified sessions keep the absent-field behavior.
|
|
29
|
+
*/
|
|
30
|
+
suppressDefaultCursorToolCatalog?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Corrective active-turn text for the single envelope-echo retry (devlog 260826 gap-10).
|
|
33
|
+
* When set on an external tool-result continuation, buildPreparedCursorRunRequest uses it as
|
|
34
|
+
* the userMessageAction text instead of the standard continuation text; rawMessages stay
|
|
35
|
+
* untouched so history replay is unchanged.
|
|
36
|
+
*/
|
|
37
|
+
echoRetryContinuationText?: string;
|
|
18
38
|
conversationId: string;
|
|
19
39
|
system: string[];
|
|
20
40
|
messages: CursorRequestMessage[];
|
|
@@ -25,10 +45,9 @@ export interface CursorRunRequest {
|
|
|
25
45
|
* hydration). History stays text-only. data: URLs only in this slice.
|
|
26
46
|
*/
|
|
27
47
|
selectedImages?: readonly ResolvedCursorImage[];
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
parallelToolCalls?: boolean;
|
|
48
|
+
tools?: OcxTool[];
|
|
49
|
+
toolChoice?: OcxRequestOptions["toolChoice"];
|
|
50
|
+
parallelToolCalls?: boolean;
|
|
32
51
|
/**
|
|
33
52
|
* Clear provider-private context-usage carry-forward before this run. Used when Codex starts a
|
|
34
53
|
* newly observed compacted context epoch, so pre-compaction totals are not over-reported while
|