@yansigit/opencodex 2.33.1 → 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 -3
- package/src/adapters/anthropic.ts +79 -2
- package/src/adapters/command-code.ts +40 -3
- 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 +9 -9
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-shell.ts +103 -6
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +74 -39
- package/src/adapters/cursor/request-builder.ts +32 -10
- package/src/adapters/cursor/tool-definitions.ts +13 -10
- package/src/adapters/cursor/tool-result-normalize.ts +35 -3
- package/src/adapters/cursor/types.ts +16 -4
- package/src/adapters/cursor.ts +97 -70
- 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 +156 -65
- 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 +334 -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 +14 -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 +5 -0
- 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 -37
- 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 +229 -153
- 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 +54 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/index.ts +57 -5
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/login-cli.ts +19 -20
- 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 +28 -0
- 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 +73 -14
- package/src/providers/registry.ts +160 -68
- 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 +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +23 -0
- package/src/server/chat-completions.ts +24 -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 +152 -80
- 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 +1 -1
- package/src/server/management/model-routes.ts +266 -0
- 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/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +21 -0
- package/src/server/request-log.ts +46 -20
- package/src/server/responses/collaboration.ts +42 -5
- package/src/server/responses/combo-stream-preflight.ts +10 -3
- package/src/server/responses/core.ts +571 -137
- 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/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 +15 -4
- 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 +77 -0
- package/src/types/request.ts +16 -10
- package/src/types/tools.ts +20 -25
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +17 -7
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-CIDo4y4k.js +0 -102
- package/gui/dist/assets/index-DrSQdTRd.css +0 -1
- package/src/server/aistudio-ws-hub.ts +0 -295
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
1
|
+
import { execFileSync, spawn, spawnSync, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { create } from "@bufbuild/protobuf";
|
|
4
4
|
import {
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
type ExecServerMessage,
|
|
20
20
|
} from "./gen/agent_pb";
|
|
21
21
|
import { errorText, execBytes, execStreamCloseBytes } from "./native-exec-common";
|
|
22
|
+
import { resolveTrustedWindowsTaskkillExe } from "../../lib/windows-elevation";
|
|
22
23
|
export { nativeShellDisabledMessage, type CursorNativeExecPolicyContext } from "./native-exec-policy";
|
|
23
24
|
import { nativeShellDisabledMessage, type CursorNativeExecPolicyContext } from "./native-exec-policy";
|
|
24
25
|
import {
|
|
@@ -34,6 +35,7 @@ export const CURSOR_BACKGROUND_SHELL_TERM_GRACE_MS = 2_000;
|
|
|
34
35
|
|
|
35
36
|
type BackgroundShellTerminationReason = "session_close" | "idle" | "absolute" | "shutdown";
|
|
36
37
|
type BackgroundShellTimer = ReturnType<typeof setTimeout>;
|
|
38
|
+
type ProcessGroupLiveness = "alive" | "gone" | "unknown";
|
|
37
39
|
|
|
38
40
|
export interface BackgroundShellTerminationReport {
|
|
39
41
|
attempted: number;
|
|
@@ -44,10 +46,14 @@ export interface BackgroundShellTerminationReport {
|
|
|
44
46
|
|
|
45
47
|
export interface BackgroundShellRuntime {
|
|
46
48
|
spawn: typeof spawn;
|
|
49
|
+
platform: NodeJS.Platform;
|
|
47
50
|
now(): number;
|
|
48
51
|
setTimer(callback: () => void, delayMs: number): BackgroundShellTimer;
|
|
49
52
|
clearTimer(timer: BackgroundShellTimer): void;
|
|
50
53
|
kill(child: ChildProcessWithoutNullStreams, signal?: NodeJS.Signals): boolean;
|
|
54
|
+
killProcessGroup(pid: number, signal?: NodeJS.Signals): boolean;
|
|
55
|
+
isProcessGroupAlive(pid: number): ProcessGroupLiveness;
|
|
56
|
+
killTree(child: ChildProcessWithoutNullStreams): boolean;
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
interface BackgroundShellEntry {
|
|
@@ -67,10 +73,30 @@ interface BackgroundShellEntry {
|
|
|
67
73
|
|
|
68
74
|
const defaultBackgroundShellRuntime: BackgroundShellRuntime = {
|
|
69
75
|
spawn,
|
|
76
|
+
platform: process.platform,
|
|
70
77
|
now: () => Date.now(),
|
|
71
78
|
setTimer: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
72
79
|
clearTimer: timer => clearTimeout(timer),
|
|
73
80
|
kill: (child, signal) => child.kill(signal),
|
|
81
|
+
killProcessGroup: (pid, signal) => {
|
|
82
|
+
process.kill(-pid, signal);
|
|
83
|
+
return true;
|
|
84
|
+
},
|
|
85
|
+
isProcessGroupAlive: pid => {
|
|
86
|
+
try {
|
|
87
|
+
process.kill(-pid, 0);
|
|
88
|
+
return "alive";
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return (error as NodeJS.ErrnoException).code === "ESRCH" ? "gone" : "unknown";
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
killTree: child => {
|
|
94
|
+
execFileSync(resolveTrustedWindowsTaskkillExe(), ["/PID", String(child.pid), "/T", "/F"], {
|
|
95
|
+
stdio: "pipe",
|
|
96
|
+
windowsHide: true,
|
|
97
|
+
});
|
|
98
|
+
return true;
|
|
99
|
+
},
|
|
74
100
|
};
|
|
75
101
|
|
|
76
102
|
let backgroundShellRuntime = defaultBackgroundShellRuntime;
|
|
@@ -343,6 +369,13 @@ function waitForBackgroundShellClose(entry: BackgroundShellEntry): Promise<boole
|
|
|
343
369
|
});
|
|
344
370
|
}
|
|
345
371
|
|
|
372
|
+
function waitForBackgroundShellGrace(): Promise<void> {
|
|
373
|
+
return new Promise(resolveWait => {
|
|
374
|
+
const timer = backgroundShellRuntime.setTimer(resolveWait, CURSOR_BACKGROUND_SHELL_TERM_GRACE_MS);
|
|
375
|
+
void timer;
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
346
379
|
function tryKillBackgroundShell(entry: BackgroundShellEntry, signal?: NodeJS.Signals): boolean {
|
|
347
380
|
try {
|
|
348
381
|
return backgroundShellRuntime.kill(entry.child, signal);
|
|
@@ -351,6 +384,34 @@ function tryKillBackgroundShell(entry: BackgroundShellEntry, signal?: NodeJS.Sig
|
|
|
351
384
|
}
|
|
352
385
|
}
|
|
353
386
|
|
|
387
|
+
function tryKillBackgroundShellProcessGroup(entry: BackgroundShellEntry, signal?: NodeJS.Signals): boolean {
|
|
388
|
+
const pid = entry.child.pid;
|
|
389
|
+
if (typeof pid !== "number" || !Number.isSafeInteger(pid) || pid <= 0) return false;
|
|
390
|
+
try {
|
|
391
|
+
return backgroundShellRuntime.killProcessGroup(pid, signal);
|
|
392
|
+
} catch {
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function tryKillBackgroundShellTree(entry: BackgroundShellEntry): boolean {
|
|
398
|
+
try {
|
|
399
|
+
return backgroundShellRuntime.killTree(entry.child);
|
|
400
|
+
} catch {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function isBackgroundShellProcessGroupAlive(entry: BackgroundShellEntry): ProcessGroupLiveness {
|
|
406
|
+
const pid = entry.child.pid;
|
|
407
|
+
if (typeof pid !== "number" || !Number.isSafeInteger(pid) || pid <= 0) return "unknown";
|
|
408
|
+
try {
|
|
409
|
+
return backgroundShellRuntime.isProcessGroupAlive(pid);
|
|
410
|
+
} catch {
|
|
411
|
+
return "unknown";
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
354
415
|
async function terminateBackgroundShell(
|
|
355
416
|
entry: BackgroundShellEntry,
|
|
356
417
|
reason: BackgroundShellTerminationReason,
|
|
@@ -368,11 +429,43 @@ async function terminateBackgroundShell(
|
|
|
368
429
|
try { entry.child.stderr.resume(); } catch { /* keep draining when supported */ }
|
|
369
430
|
|
|
370
431
|
let attemptKillFailures = 0;
|
|
371
|
-
|
|
372
|
-
let
|
|
432
|
+
let treeTerminationStarted = false;
|
|
433
|
+
let processGroupTerminationStarted = false;
|
|
434
|
+
let processGroupTerminationAttempted = false;
|
|
435
|
+
let treeTerminationFailed = false;
|
|
436
|
+
if (backgroundShellRuntime.platform === "win32") {
|
|
437
|
+
treeTerminationStarted = tryKillBackgroundShellTree(entry);
|
|
438
|
+
if (!treeTerminationStarted) {
|
|
439
|
+
treeTerminationFailed = true;
|
|
440
|
+
attemptKillFailures += 1;
|
|
441
|
+
if (!tryKillBackgroundShell(entry)) attemptKillFailures += 1;
|
|
442
|
+
}
|
|
443
|
+
} else {
|
|
444
|
+
processGroupTerminationAttempted = true;
|
|
445
|
+
if (tryKillBackgroundShellProcessGroup(entry, "SIGTERM")) {
|
|
446
|
+
processGroupTerminationStarted = true;
|
|
447
|
+
} else {
|
|
448
|
+
attemptKillFailures += 1;
|
|
449
|
+
if (!tryKillBackgroundShell(entry)) attemptKillFailures += 1;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
let closed = processGroupTerminationStarted ? false : await waitForBackgroundShellClose(entry);
|
|
373
453
|
if (!closed && backgroundShells.get(entry.shellId) !== entry) closed = true;
|
|
374
|
-
if (
|
|
375
|
-
|
|
454
|
+
if (processGroupTerminationStarted) {
|
|
455
|
+
await waitForBackgroundShellGrace();
|
|
456
|
+
if (isBackgroundShellProcessGroupAlive(entry) !== "gone" && !tryKillBackgroundShellProcessGroup(entry, "SIGKILL")) {
|
|
457
|
+
attemptKillFailures += 1;
|
|
458
|
+
}
|
|
459
|
+
closed = backgroundShells.get(entry.shellId) !== entry;
|
|
460
|
+
}
|
|
461
|
+
if ((!closed || treeTerminationFailed || processGroupTerminationAttempted && !processGroupTerminationStarted)
|
|
462
|
+
&& !treeTerminationStarted && !processGroupTerminationStarted) {
|
|
463
|
+
if (backgroundShellRuntime.platform === "win32") {
|
|
464
|
+
if (!tryKillBackgroundShell(entry, "SIGKILL")) attemptKillFailures += 1;
|
|
465
|
+
} else if (!tryKillBackgroundShellProcessGroup(entry, "SIGKILL")) {
|
|
466
|
+
attemptKillFailures += 1;
|
|
467
|
+
if (!closed && !tryKillBackgroundShell(entry, "SIGKILL")) attemptKillFailures += 1;
|
|
468
|
+
}
|
|
376
469
|
closed = await waitForBackgroundShellClose(entry);
|
|
377
470
|
if (!closed && backgroundShells.get(entry.shellId) !== entry) closed = true;
|
|
378
471
|
}
|
|
@@ -459,7 +552,11 @@ export function backgroundShellSpawnExec(execMsg: ExecServerMessage, sessionId:
|
|
|
459
552
|
if (!admissionLease) return backgroundShellSpawnError(execMsg, args.command, cwd, "background shell limit reached");
|
|
460
553
|
let child: ChildProcessWithoutNullStreams;
|
|
461
554
|
try {
|
|
462
|
-
child = backgroundShellRuntime.spawn(args.command, {
|
|
555
|
+
child = backgroundShellRuntime.spawn(args.command, {
|
|
556
|
+
cwd,
|
|
557
|
+
shell: true,
|
|
558
|
+
...(backgroundShellRuntime.platform === "win32" ? {} : { detached: true }),
|
|
559
|
+
});
|
|
463
560
|
} catch (err) {
|
|
464
561
|
admissionLease.release();
|
|
465
562
|
return backgroundShellSpawnError(execMsg, args.command, cwd, errorText(err));
|
|
@@ -410,6 +410,19 @@ export function storeCursorBlob(data: Uint8Array, requestScope?: CursorBlobReque
|
|
|
410
410
|
return blobId;
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
+
/**
|
|
414
|
+
* Serve-time integrity for content-addressed blobs (devlog 260826_cursor_responses_gap 080):
|
|
415
|
+
* a raw 32-byte blob id IS the SHA-256 of its bytes, so served data whose digest mismatches
|
|
416
|
+
* the id means in-store corruption — the splice signature behind garbled replayed tool
|
|
417
|
+
* results. Ids longer than 32 bytes (digested-key namespace) and server-minted ids are not
|
|
418
|
+
* content-addressed and always pass.
|
|
419
|
+
*/
|
|
420
|
+
export function cursorBlobServeIntegrityOk(blobId: Uint8Array, served: Uint8Array): boolean {
|
|
421
|
+
if (blobId.byteLength !== 32) return true;
|
|
422
|
+
const digest = createHash("sha256").update(served).digest();
|
|
423
|
+
return digest.equals(Buffer.from(blobId));
|
|
424
|
+
}
|
|
425
|
+
|
|
413
426
|
/**
|
|
414
427
|
* Long-lived pin for blobs referenced by an active Cursor conversation checkpoint.
|
|
415
428
|
* Unlike a request scope, this lease is not sealed and is not released by getBlob hydration.
|
|
@@ -630,6 +643,13 @@ export function handleCursorNativeKv(
|
|
|
630
643
|
if (kvMsg.message.case === "getBlobArgs") {
|
|
631
644
|
const blobKey = key(kvMsg.message.value.blobId);
|
|
632
645
|
const blobData = getBlob(blobKey);
|
|
646
|
+
// Splice-class corruption guard (devlog 260826 080): diagnostic only, never blocks serving.
|
|
647
|
+
if (blobData && !cursorBlobServeIntegrityOk(kvMsg.message.value.blobId, blobData)) {
|
|
648
|
+
debugProviderDiagnostic("cursor", "blob-integrity-mismatch", {
|
|
649
|
+
blobKey: blobKey.slice(0, 18),
|
|
650
|
+
servedBytes: blobData.byteLength,
|
|
651
|
+
});
|
|
652
|
+
}
|
|
633
653
|
if (blobData && requestScope && blobRequestScopes.get(requestScope)?.kind === "request") {
|
|
634
654
|
releaseHydratedBlob(blobKey, requestScope);
|
|
635
655
|
}
|
|
@@ -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";
|
|
@@ -180,26 +181,9 @@ function truncateToolResultBlob(entry: RootBlobCandidate, maxBytes: number): Roo
|
|
|
180
181
|
return markerOnly.byteLength <= maxBytes ? markerOnly : null;
|
|
181
182
|
}
|
|
182
183
|
|
|
183
|
-
function structuredOutputPrompt(textFormat: OcxRequestOptions["textFormat"]): string | undefined {
|
|
184
|
-
if (!textFormat) return undefined;
|
|
185
|
-
if (textFormat.type === "json_schema" && textFormat.schema) {
|
|
186
|
-
return [
|
|
187
|
-
"Your response must be a single valid JSON object strictly conforming to this JSON schema:",
|
|
188
|
-
JSON.stringify(textFormat.schema),
|
|
189
|
-
"Do not include any surrounding markdown fences, preamble, or commentary; return raw JSON only.",
|
|
190
|
-
].join("\n");
|
|
191
|
-
}
|
|
192
|
-
if (textFormat.type === "json_object") {
|
|
193
|
-
return "Your response must be a single valid JSON object. Do not include any markdown fences or commentary; return raw JSON only.";
|
|
194
|
-
}
|
|
195
|
-
return undefined;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
184
|
function systemPromptBlobs(request: CursorRunRequest): RootBlobCandidate[] {
|
|
199
185
|
const prompts = request.system.length > 0 ? [...request.system] : ["You are a helpful assistant."];
|
|
200
186
|
if (cursorRequestHasShellAlias(request.tools)) prompts.push(CURSOR_SHELL_ALIAS_SYSTEM_NOTE);
|
|
201
|
-
const structuredPrompt = structuredOutputPrompt(request.textFormat);
|
|
202
|
-
if (structuredPrompt) prompts.push(structuredPrompt);
|
|
203
187
|
const cursorToolGuidance = buildCursorToolGuidanceSystemNote(
|
|
204
188
|
cursorToolsForActivePrompt(request.tools, activePromptText(request), request.toolChoice),
|
|
205
189
|
request.toolChoice,
|
|
@@ -222,11 +206,10 @@ function assistantRootText(
|
|
|
222
206
|
// Cursor builds the actual model prompt from rootPromptMessagesJson (turns[] is UI/display metadata),
|
|
223
207
|
// so prior history must be replayed here or a ResumeAction has nothing model-visible to continue from.
|
|
224
208
|
// The active user message is excluded because it travels in the action. When the continuation cannot
|
|
225
|
-
// rely on native MCP turn state, tool results stay assistant-role text
|
|
226
|
-
// as `<user_query>` (#1992).
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
// 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.
|
|
230
213
|
function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobRequestScopeToken): {
|
|
231
214
|
ids: Uint8Array[];
|
|
232
215
|
byteLength: number;
|
|
@@ -250,6 +233,42 @@ function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobR
|
|
|
250
233
|
const echoToolResultInRoot = cursorNeedsExternalToolContinuation(request.modelId);
|
|
251
234
|
const lastRawIsToolResult = messages.at(-1)?.role === "toolResult";
|
|
252
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
|
+
};
|
|
253
272
|
|
|
254
273
|
for (let i = 0; i < messages.length; i++) {
|
|
255
274
|
if (i === activeUserIndex) break;
|
|
@@ -261,6 +280,9 @@ function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobR
|
|
|
261
280
|
// A bare string survives blob hydration but external workers reject the completed replay
|
|
262
281
|
// before tokenization (`usedTokens: 0`, then invalid_argument).
|
|
263
282
|
if (text.length > 0) {
|
|
283
|
+
lastReplayText = undefined;
|
|
284
|
+
lastReplayEntry = undefined;
|
|
285
|
+
currentRun = 1;
|
|
264
286
|
entries.push(rootBlobCandidate({
|
|
265
287
|
role: "user",
|
|
266
288
|
content: [{ type: "text", text }],
|
|
@@ -271,25 +293,30 @@ function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobR
|
|
|
271
293
|
// Native Composer state can preserve it through ThinkingMessage/history structures.
|
|
272
294
|
const text = assistantRootText(message, !externalModel).trim();
|
|
273
295
|
if (text.length > 0) {
|
|
274
|
-
|
|
296
|
+
pushDeduped(
|
|
275
297
|
{ role: "assistant", content: [{ type: "text", text }] },
|
|
276
298
|
"assistant",
|
|
277
299
|
{ messageIndex: i },
|
|
278
|
-
|
|
300
|
+
text,
|
|
301
|
+
);
|
|
279
302
|
}
|
|
280
303
|
// Assistant tool CALLS are intentionally NOT replayed as visible "[Tool Call]" text here.
|
|
281
304
|
} else if (message.role === "toolResult") {
|
|
282
|
-
// Native resume models already receive the paired MCP result through turns[].
|
|
283
|
-
//
|
|
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.
|
|
284
308
|
if (!echoToolResultInRoot) continue;
|
|
285
309
|
const text = externalToolResultToText(message);
|
|
286
|
-
|
|
287
|
-
toolResultRootPayload(text),
|
|
288
|
-
"toolResult",
|
|
289
|
-
{ messageIndex: i, text },
|
|
290
|
-
));
|
|
310
|
+
pushDeduped(toolResultRootPayload(text), "toolResult", { messageIndex: i, text }, text);
|
|
291
311
|
}
|
|
292
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
|
+
}
|
|
293
320
|
|
|
294
321
|
let selected = entries;
|
|
295
322
|
let historyMessageStart = 0;
|
|
@@ -465,6 +492,7 @@ function decodeResultParts(message: OcxToolResultMessage): DecodedResultPart[] |
|
|
|
465
492
|
if (typeof content === "string") return undefined;
|
|
466
493
|
return content.map((part): DecodedResultPart => {
|
|
467
494
|
if (part.type === "text") return { kind: "text", text: part.text };
|
|
495
|
+
if (part.type === "video") return { kind: "text", text: "[video]" };
|
|
468
496
|
const decoded = decodeInlineImage(part.imageUrl);
|
|
469
497
|
return decoded ? { kind: "image", ...decoded } : { kind: "undecodable" };
|
|
470
498
|
});
|
|
@@ -559,7 +587,7 @@ function toolResultToText(message: OcxToolResultMessage): string {
|
|
|
559
587
|
const normalized = normalizedToolResult(message, contentToText(message.content));
|
|
560
588
|
return [
|
|
561
589
|
"[tool_result]",
|
|
562
|
-
`call_id: ${message.toolCallId}`,
|
|
590
|
+
`call_id: ${decodeCursorCallId(message.toolCallId)}`,
|
|
563
591
|
`name: ${namespacedToolName(message.toolNamespace, message.toolName)}`,
|
|
564
592
|
`is_error: ${normalized.isError}`,
|
|
565
593
|
"output:",
|
|
@@ -570,7 +598,7 @@ function toolResultToText(message: OcxToolResultMessage): string {
|
|
|
570
598
|
function externalToolResultToText(message: OcxToolResultMessage): string {
|
|
571
599
|
const normalized = normalizedToolResult(message, contentToText(message.content));
|
|
572
600
|
const label = normalized.isError ? "Tool error" : "Tool output";
|
|
573
|
-
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}`;
|
|
574
602
|
}
|
|
575
603
|
|
|
576
604
|
/**
|
|
@@ -626,7 +654,7 @@ function toolCallStep(
|
|
|
626
654
|
args: create(McpArgsSchema, {
|
|
627
655
|
name: toolName,
|
|
628
656
|
toolName,
|
|
629
|
-
toolCallId: part.id,
|
|
657
|
+
toolCallId: decodeCursorCallId(part.id),
|
|
630
658
|
providerIdentifier: OCX_RESPONSES_TOOL_PROVIDER,
|
|
631
659
|
args,
|
|
632
660
|
}),
|
|
@@ -870,7 +898,7 @@ function buildPreparedCursorRunRequest(
|
|
|
870
898
|
? "userMessageAction"
|
|
871
899
|
: "resumeAction";
|
|
872
900
|
const actionText = externalToolContinuation
|
|
873
|
-
? (request.echoRetryContinuationText ??
|
|
901
|
+
? (request.echoRetryContinuationText ?? CURSOR_EXTERNAL_TOOL_CONTINUATION_TEXT)
|
|
874
902
|
: request.echoRetryContinuationText
|
|
875
903
|
? `${text}\n\n[correction] ${request.echoRetryContinuationText}`
|
|
876
904
|
: text;
|
|
@@ -1000,12 +1028,15 @@ function buildPreparedCursorRunRequest(
|
|
|
1000
1028
|
displayName: request.modelId,
|
|
1001
1029
|
displayNameShort: request.modelId,
|
|
1002
1030
|
aliases: [],
|
|
1031
|
+
...(request.maxMode === true ? { maxMode: true } : {}),
|
|
1003
1032
|
}),
|
|
1004
1033
|
} : {}),
|
|
1005
|
-
...(requestedModelParameters.length > 0 ? {
|
|
1034
|
+
...(requestedModelParameters.length > 0 || request.maxMode === true ? {
|
|
1006
1035
|
requestedModel: create(RequestedModelSchema, {
|
|
1007
1036
|
modelId: request.modelId,
|
|
1008
|
-
|
|
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,
|
|
1009
1040
|
parameters: requestedModelParameters.map(parameter =>
|
|
1010
1041
|
create(RequestedModel_ModelParameterbytesSchema, parameter)),
|
|
1011
1042
|
}),
|
|
@@ -1023,7 +1054,11 @@ function buildPreparedCursorRunRequest(
|
|
|
1023
1054
|
// the event-state `clientToolNames` use (live-transport.ts). Advertising the raw `request.tools`
|
|
1024
1055
|
// here would let mcp_tools expose a tool that the event state does not recognize for a generic
|
|
1025
1056
|
// tool-count prompt, so a call to it would be rejected as an unknown Responses tool.
|
|
1026
|
-
|
|
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
|
+
: {}),
|
|
1027
1062
|
});
|
|
1028
1063
|
|
|
1029
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 },
|
|
@@ -209,7 +217,7 @@ function normalizeCursorModelId(modelId: string, reasoning?: string): {
|
|
|
209
217
|
requestedModelParameters: [{ id: "fast", value: "false" }],
|
|
210
218
|
};
|
|
211
219
|
}
|
|
212
|
-
return { ...selection, modelId: suffix ? cursorRequestWireModelIdWithEffort(id, suffix) : id };
|
|
220
|
+
return { ...selection, ...maxMode, modelId: suffix ? cursorRequestWireModelIdWithEffort(id, suffix) : id };
|
|
213
221
|
}
|
|
214
222
|
|
|
215
223
|
function contentPartToText(part: OcxContentPart | OcxAssistantContentPart): string | undefined {
|
|
@@ -233,7 +241,7 @@ function contentPartToText(part: OcxContentPart | OcxAssistantContentPart): stri
|
|
|
233
241
|
function toolResultToText(message: OcxToolResultMessage): string {
|
|
234
242
|
return [
|
|
235
243
|
"[tool_result]",
|
|
236
|
-
`call_id: ${message.toolCallId}`,
|
|
244
|
+
`call_id: ${decodeCursorCallId(message.toolCallId)}`,
|
|
237
245
|
`name: ${namespacedToolName(message.toolNamespace, message.toolName)}`,
|
|
238
246
|
`is_error: ${message.isError}`,
|
|
239
247
|
"output:",
|
|
@@ -305,7 +313,7 @@ export function cursorConversationIdFromClientThread(threadId: string, identityS
|
|
|
305
313
|
|
|
306
314
|
/**
|
|
307
315
|
* Resolve the Cursor conversation id for this turn.
|
|
308
|
-
* Priority: force-fresh → isolate helper → remembered →
|
|
316
|
+
* Priority: force-fresh → isolate helper → remembered → client thread owner → random.
|
|
309
317
|
* Never use OpenAI Responses `previous_response_id` (resp_*) or shared `prompt_cache_key`
|
|
310
318
|
* (cache-cohort fingerprint, not conversation ownership).
|
|
311
319
|
*/
|
|
@@ -317,7 +325,7 @@ export function resolveCursorConversationId(
|
|
|
317
325
|
if (options.forceFreshConversation === true) return generatedCursorConversationId();
|
|
318
326
|
if (parsed._cursorIsolateConversation === true) return generatedCursorConversationId();
|
|
319
327
|
if (parsed._cursorConversationId) return parsed._cursorConversationId;
|
|
320
|
-
const threadId = parsed
|
|
328
|
+
const threadId = cursorClientThreadOwner(parsed);
|
|
321
329
|
if (threadId) {
|
|
322
330
|
const recovered = lookupCursorThreadConversation(threadId, parsed._cursorIdentityScope);
|
|
323
331
|
if (recovered) return recovered;
|
|
@@ -326,6 +334,10 @@ export function resolveCursorConversationId(
|
|
|
326
334
|
return generatedCursorConversationId();
|
|
327
335
|
}
|
|
328
336
|
|
|
337
|
+
export function cursorClientThreadOwner(parsed: OcxParsedRequest): string | undefined {
|
|
338
|
+
return parsed._clientThreadId?.trim() || parsed._cursorClientThreadId?.trim() || undefined;
|
|
339
|
+
}
|
|
340
|
+
|
|
329
341
|
function updateFramed(hash: ReturnType<typeof createHash>, value: string): void {
|
|
330
342
|
const bytes = Buffer.from(value, "utf8");
|
|
331
343
|
const length = Buffer.allocUnsafe(4);
|
|
@@ -368,6 +380,7 @@ function lookupPrefixSnapshot(
|
|
|
368
380
|
const modelId = cursorCheckpointModelAffinityId(request.modelId);
|
|
369
381
|
for (let covered = parsed.context.messages.length; covered >= 1; covered--) {
|
|
370
382
|
const snapshot = getCursorCheckpointForPrefix({
|
|
383
|
+
conversationId: request.conversationId,
|
|
371
384
|
prefixDigest: cursorCoveredPrefixDigest(parsed, covered),
|
|
372
385
|
systemDigest,
|
|
373
386
|
coveredMessageCount: covered,
|
|
@@ -411,10 +424,14 @@ function resolveCursorCheckpoint(
|
|
|
411
424
|
snapshot = getCursorCheckpoint(ref);
|
|
412
425
|
if (!snapshot) return { reason: "expired" };
|
|
413
426
|
} else {
|
|
427
|
+
if (
|
|
428
|
+
isolated
|
|
429
|
+
|| (!parsed._cursorConversationId && !cursorClientThreadOwner(parsed))
|
|
430
|
+
) return { reason: "missing_ref" };
|
|
414
431
|
snapshot = lookupPrefixSnapshot(parsed, request, identityScope);
|
|
415
432
|
if (!snapshot) return { reason: "missing_ref" };
|
|
416
433
|
}
|
|
417
|
-
if (
|
|
434
|
+
if (snapshot.conversationId !== request.conversationId) {
|
|
418
435
|
return { reason: "conversation_changed" };
|
|
419
436
|
}
|
|
420
437
|
if (snapshot.identityScope !== identityScope) return { reason: "identity_changed" };
|
|
@@ -443,16 +460,21 @@ export function createCursorRequest(
|
|
|
443
460
|
modelId: model.modelId,
|
|
444
461
|
...(model.requestedModelParameters ? { requestedModelParameters: model.requestedModelParameters } : {}),
|
|
445
462
|
...(model.routingLevel ? { routingLevel: model.routingLevel } : {}),
|
|
463
|
+
...(model.maxMode ? { maxMode: true } : {}),
|
|
446
464
|
conversationId: resolveCursorConversationId(parsed, model.modelId, options),
|
|
447
465
|
system: [...(parsed.context.systemPrompt ?? []), ...(limitNote ? [limitNote] : [])],
|
|
448
466
|
messages,
|
|
449
467
|
rawMessages: parsed.context.messages,
|
|
450
468
|
...(parsed._compactionRequest === true || parsed._contextCompactionBoundary === true ? { contextUsageReset: true } : {}),
|
|
451
469
|
...(parsed._compactionRequest === true ? { contextUsageStoreCheckpoints: false } : {}),
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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 } : {}),
|
|
456
478
|
};
|
|
457
479
|
const resolved = resolveCursorCheckpoint(parsed, request, options);
|
|
458
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
|
|
@@ -112,13 +119,6 @@ export const CURSOR_MULTI_EDIT_INPUT_SCHEMA = {
|
|
|
112
119
|
additionalProperties: false,
|
|
113
120
|
} as const;
|
|
114
121
|
|
|
115
|
-
/** Cursor requires freeform custom tools to advertise their body as one string input. */
|
|
116
|
-
export const CURSOR_FREEFORM_INPUT_SCHEMA = {
|
|
117
|
-
type: "object",
|
|
118
|
-
properties: { input: { type: "string" } },
|
|
119
|
-
required: ["input"],
|
|
120
|
-
} as const;
|
|
121
|
-
|
|
122
122
|
/**
|
|
123
123
|
* Responses/Codex-side schema used ONLY for arg-key normalization after Cursor returns a call.
|
|
124
124
|
* Cursor models are trained to emit `cmd`; Codex `shell_command` / `exec_command` validate
|
|
@@ -682,13 +682,16 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
682
682
|
// Code mode: shell/edit/MCP live inside freeform `exec` as nested helpers. Without this the
|
|
683
683
|
// model probes for a top-level shell tool that is not there.
|
|
684
684
|
codeMode
|
|
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\"})\`.
|
|
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.`
|
|
686
686
|
: undefined,
|
|
687
687
|
codeMode
|
|
688
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."
|
|
689
689
|
: undefined,
|
|
690
690
|
codeMode
|
|
691
|
-
?
|
|
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."
|
|
692
695
|
: undefined,
|
|
693
696
|
codeMode
|
|
694
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."
|
|
@@ -734,7 +737,7 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
734
737
|
"Do not count or report a tool call unless a tool result was actually returned.",
|
|
735
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.",
|
|
736
739
|
hasBareExec
|
|
737
|
-
? `
|
|
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.`
|
|
738
741
|
: undefined,
|
|
739
742
|
].filter((note): note is string => typeof note === "string");
|
|
740
743
|
return notes.join(" ");
|