@yansigit/opencodex 2.32.0 → 2.33.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 +2 -2
- package/gui/dist/assets/index-DKLr4LTE.js +102 -0
- package/gui/dist/assets/index-DrSQdTRd.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +6 -5
- package/src/adapters/anthropic.ts +20 -6
- package/src/adapters/azure.ts +20 -4
- package/src/adapters/base.ts +3 -1
- package/src/adapters/command-code.ts +40 -7
- package/src/adapters/cursor/live-transport.ts +1 -1
- package/src/adapters/cursor/protobuf-events.ts +158 -7
- package/src/adapters/cursor/protobuf-request.ts +33 -15
- package/src/adapters/cursor/request-builder.ts +4 -3
- package/src/adapters/cursor/tool-definitions.ts +27 -1
- package/src/adapters/cursor/types.ts +4 -3
- package/src/adapters/cursor.ts +9 -0
- package/src/adapters/google-antigravity-replay.ts +2 -2
- package/src/adapters/google-antigravity-wire.ts +7 -0
- package/src/adapters/google-errors.ts +6 -2
- package/src/adapters/google-http.ts +30 -7
- package/src/adapters/google-truncation.ts +5 -0
- package/src/adapters/google-wire-compiler.ts +38 -6
- package/src/adapters/google.ts +148 -26
- package/src/adapters/kiro-tools.ts +20 -9
- package/src/adapters/openai-chat.ts +9 -0
- package/src/adapters/openai-responses.ts +1 -1
- package/src/bridge.ts +112 -9
- package/src/claude/context-windows.ts +16 -9
- package/src/cli/doctor.ts +2 -2
- package/src/cli/index.ts +9 -2
- package/src/cli/provider.ts +6 -0
- package/src/cli/status.ts +23 -0
- package/src/codex/auth-api.ts +4 -2
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +12 -12
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/model-metadata.ts +39 -12
- package/src/codex/catalog/parsing.ts +38 -27
- package/src/codex/catalog/provider-fetch.ts +198 -133
- package/src/codex/catalog/sync.ts +1 -1
- package/src/codex/convergence.ts +5 -0
- package/src/codex/shim.ts +56 -3
- package/src/config/provider-validation.ts +37 -0
- package/src/config.ts +78 -2
- package/src/generated/compatibility-version.json +120 -96
- package/src/images/loop.ts +37 -6
- package/src/lib/azure-identity.ts +154 -0
- package/src/lib/debug.ts +42 -0
- package/src/lib/errors.ts +14 -0
- package/src/lib/provider-outbound.ts +45 -33
- package/src/lib/provider-tls-profile.ts +309 -0
- package/src/lib/proxy-env.ts +49 -0
- package/src/lib/redact.ts +10 -1
- package/src/oauth/antigravity-routing.ts +282 -236
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/command-code.ts +5 -16
- package/src/oauth/google-antigravity.ts +42 -5
- package/src/oauth/index.ts +15 -3
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +6 -0
- package/src/providers/antigravity-quota.ts +3 -1
- package/src/providers/api-keys.ts +2 -1
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/derive.ts +4 -0
- package/src/providers/key-failover.ts +5 -1
- package/src/providers/openai-tiers.ts +5 -0
- package/src/providers/provider-id-rewrite.ts +1 -0
- package/src/providers/quota.ts +59 -13
- package/src/providers/registry.ts +3 -1
- package/src/providers/request-pacing.ts +33 -6
- package/src/providers/xai-transport.ts +21 -0
- package/src/responses/google-provider-options.ts +36 -0
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +11 -0
- package/src/responses/provider-opaque-metadata.ts +3 -3
- package/src/responses/schema.ts +37 -0
- package/src/responses/state.ts +94 -4
- package/src/router.ts +8 -2
- package/src/server/auth-cors.ts +28 -0
- package/src/server/images.ts +19 -35
- package/src/server/management/agent-settings-routes.ts +205 -15
- package/src/server/management/combo-routes.ts +6 -0
- package/src/server/management/config-routes.ts +31 -5
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/oauth-account-routes.ts +25 -4
- package/src/server/management/provider-routes.ts +113 -15
- package/src/server/management/routing-profile-routes.ts +3 -0
- package/src/server/request-log.ts +21 -0
- package/src/server/responses/agent-task-recovery.ts +1 -1
- package/src/server/responses/compact.ts +30 -1
- package/src/server/responses/core.ts +359 -153
- package/src/server/responses/empty-completion-guard.ts +35 -6
- package/src/server/responses/fetch-helpers.ts +18 -5
- package/src/server/responses/v2-native-parent-override.ts +59 -0
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-undeclared-tool-guard.ts +90 -8
- package/src/service.ts +1 -1
- package/src/types/config.ts +16 -1
- package/src/types/provider.ts +16 -0
- package/src/types/request.ts +28 -0
- package/src/types/tools.ts +27 -0
- package/src/types.ts +6 -0
- package/src/web-search/gemini-executor.ts +6 -4
- package/src/web-search/loop.ts +42 -6
- package/gui/dist/assets/index-BG43zwVe.js +0 -102
- package/gui/dist/assets/index-CiSI-jrP.css +0 -1
package/src/adapters/base.ts
CHANGED
|
@@ -19,6 +19,8 @@ export interface IncomingMeta {
|
|
|
19
19
|
* anthropic adapter consumes it; others ignore it.
|
|
20
20
|
*/
|
|
21
21
|
imageTierBias?: number;
|
|
22
|
+
/** Provider-scoped structured error observation; never receives ordinary model payloads. */
|
|
23
|
+
onProviderError?: (error: { code?: string; status?: number; message?: string }) => void;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export interface ProviderAdapter {
|
|
@@ -81,7 +83,7 @@ export interface AdapterRequest {
|
|
|
81
83
|
/** Client tool-search names actually lowered to upstream function calls for this request. */
|
|
82
84
|
convertedRoutedToolSearchNames?: ReadonlySet<string>;
|
|
83
85
|
/** Upstream-only aliases for namespace tools flattened in this request. */
|
|
84
|
-
convertedRoutedNamespaceToolAliases?: ReadonlyMap<string, { namespace: string; name: string }>;
|
|
86
|
+
convertedRoutedNamespaceToolAliases?: ReadonlyMap<string, { namespace: string; name: string; kind: "function" | "custom" }>;
|
|
85
87
|
/** Releases observation of a serialized request body after its final fetch attempt settles. */
|
|
86
88
|
releaseBodyObservation?: () => void;
|
|
87
89
|
/** Exact reasoning parameter emitted by the adapter, for request-log diagnostics only. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { execFile as execFileCallback } from "node:child_process";
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
4
|
import { opendir } from "node:fs/promises";
|
|
@@ -12,7 +12,7 @@ import { commandCodeReasoningEfforts, refreshCommandCodeReasoningEfforts } from
|
|
|
12
12
|
import { identifyRoutedModel } from "./identity";
|
|
13
13
|
import { buildNonOpenAIToolCatalogNudgeForTools } from "./tool-catalog-nudge";
|
|
14
14
|
import { parseDataUrl } from "./image";
|
|
15
|
-
import {
|
|
15
|
+
import { redactSecretString } from "../lib/redact";
|
|
16
16
|
|
|
17
17
|
// Retain the short ids emitted by the first local integration. New requests use the live catalog's
|
|
18
18
|
// provider-native IDs directly; this map is compatibility-only and is not a model fallback list.
|
|
@@ -27,6 +27,23 @@ function canonicalCommandCodeModelId(modelId: string): string {
|
|
|
27
27
|
return Object.hasOwn(COMMAND_CODE_MODEL_ALIASES, modelId) ? COMMAND_CODE_MODEL_ALIASES[modelId]! : modelId;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/** Surface Command Code's JSON error message to sidecar callers instead of a bare HTTP status. */
|
|
31
|
+
export function formatCommandCodeErrorBody(_status: number, _headers: Headers, payloadText: string): string {
|
|
32
|
+
try {
|
|
33
|
+
const payload = JSON.parse(payloadText) as unknown;
|
|
34
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return "";
|
|
35
|
+
const error = (payload as Record<string, unknown>).error;
|
|
36
|
+
const message = error && typeof error === "object" && !Array.isArray(error)
|
|
37
|
+
? (error as Record<string, unknown>).message
|
|
38
|
+
: undefined;
|
|
39
|
+
return typeof message === "string" && message.trim()
|
|
40
|
+
? redactSecretString(message.trim()).slice(0, 400)
|
|
41
|
+
: "";
|
|
42
|
+
} catch {
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
/** Flatten tool-result content for the text-only wire output, keeping an `[image]` marker per image part in content order. */
|
|
31
48
|
function toolResultText(content: string | OcxContentPart[]): string {
|
|
32
49
|
if (typeof content === "string") return content;
|
|
@@ -210,6 +227,24 @@ function projectSlug(cwd: string): string {
|
|
|
210
227
|
return cwd.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "").toLowerCase().slice(0, 64) || "workspace";
|
|
211
228
|
}
|
|
212
229
|
|
|
230
|
+
export function commandCodeSessionId(parsed: OcxParsedRequest): string {
|
|
231
|
+
// Shared prompt-cache cohorts intentionally do not identify one conversation. Keep them out
|
|
232
|
+
// of upstream session affinity or unrelated conversations can pin to the same worker.
|
|
233
|
+
const threadId = parsed._clientThreadId?.trim();
|
|
234
|
+
const replayId = parsed._reasoningReplayScope?.clientThreadId?.trim();
|
|
235
|
+
const cacheKey = !parsed._promptCacheKeyIsSharedCohort ? parsed.options.promptCacheKey?.trim() : undefined;
|
|
236
|
+
const identity = threadId
|
|
237
|
+
? ["thread", threadId]
|
|
238
|
+
: replayId
|
|
239
|
+
? ["replay", replayId]
|
|
240
|
+
: cacheKey
|
|
241
|
+
? ["cache", cacheKey]
|
|
242
|
+
: undefined;
|
|
243
|
+
if (!identity) return randomUUID();
|
|
244
|
+
const hex = createHash("sha256").update(`command-code:${identity[0]}\0${identity[1]}`).digest("hex");
|
|
245
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
213
248
|
interface GitWorkspaceInfo {
|
|
214
249
|
isGitRepo: boolean;
|
|
215
250
|
currentBranch: string;
|
|
@@ -451,6 +486,7 @@ export function createCommandCodeAdapter(provider: OcxProviderConfig): ProviderA
|
|
|
451
486
|
const executor = (provider as OcxProviderConfig & { fetch?: typeof globalThis.fetch }).fetch ?? globalThis.fetch;
|
|
452
487
|
return {
|
|
453
488
|
name: "command-code",
|
|
489
|
+
formatErrorBody: formatCommandCodeErrorBody,
|
|
454
490
|
async buildRequest(parsed: OcxParsedRequest): Promise<AdapterRequest> {
|
|
455
491
|
if (!provider.apiKey) throw new Error("Command Code credential missing — run ocx login command-code");
|
|
456
492
|
const cwd = currentWorkingDirectory();
|
|
@@ -463,11 +499,8 @@ export function createCommandCodeAdapter(provider: OcxProviderConfig): ProviderA
|
|
|
463
499
|
...(choiceInstruction ? [choiceInstruction] : []),
|
|
464
500
|
].join("\n\n"), parsed.modelId);
|
|
465
501
|
const reasoningEffort = supportedCommandCodeEffort(provider, parsed.modelId, parsed.options.reasoning);
|
|
466
|
-
const projectContext = provider.projectContext === "on"
|
|
467
|
-
? await loadCommandCodeProjectContext(cwd)
|
|
468
|
-
: EMPTY_COMMAND_CODE_PROJECT_CONTEXT;
|
|
469
502
|
const body = {
|
|
470
|
-
config: await commandCodeConfig(cwd),
|
|
503
|
+
config: await commandCodeConfig(cwd), memory: "", taste: null, skills: null,
|
|
471
504
|
permissionMode: "standard", mode: "agent",
|
|
472
505
|
params: {
|
|
473
506
|
model: canonicalCommandCodeModelId(parsed.modelId),
|
|
@@ -490,7 +523,7 @@ export function createCommandCodeAdapter(provider: OcxProviderConfig): ProviderA
|
|
|
490
523
|
"x-cli-environment": "production",
|
|
491
524
|
"x-taste-learning": "false",
|
|
492
525
|
"x-co-flag": "false",
|
|
493
|
-
"x-session-id":
|
|
526
|
+
"x-session-id": commandCodeSessionId(parsed),
|
|
494
527
|
};
|
|
495
528
|
if (cwd) headers["x-project-slug"] = projectSlug(cwd);
|
|
496
529
|
return {
|
|
@@ -108,7 +108,7 @@ const CURSOR_STREAM_SILENCE_FAIL_MS = 30_000;
|
|
|
108
108
|
* for this long is equally stuck — the server is alive but the turn is not progressing.
|
|
109
109
|
* Reset on every decoded frame that is not liveness-only.
|
|
110
110
|
*/
|
|
111
|
-
const CURSOR_STREAM_HEARTBEAT_ONLY_FAIL_MS =
|
|
111
|
+
const CURSOR_STREAM_HEARTBEAT_ONLY_FAIL_MS = 180_000;
|
|
112
112
|
/**
|
|
113
113
|
* After `turnEnded` is decoded, the application turn is complete. A server that keeps
|
|
114
114
|
* HTTP/2 open past this point cannot hold the turn hostage (senpi #1062): we close our side
|
|
@@ -176,6 +176,7 @@ export interface CursorProtobufEventState {
|
|
|
176
176
|
*/
|
|
177
177
|
syntheticStructuredEditToolNames?: ReadonlySet<string>;
|
|
178
178
|
translatorBudget?: TranslatorBudget;
|
|
179
|
+
textToolCallBuffer?: string;
|
|
179
180
|
}
|
|
180
181
|
|
|
181
182
|
|
|
@@ -329,7 +330,7 @@ function isCompleteJson(text: string): boolean {
|
|
|
329
330
|
|
|
330
331
|
/** Schema-normalize a JSON-text argument blob for a named tool, if a schema is known. */
|
|
331
332
|
function normalizeJsonText(text: string, toolName: string | undefined, state: CursorProtobufEventState): string {
|
|
332
|
-
const schema = toolSchemaForWireName(state, toolName);
|
|
333
|
+
const schema = toolSchemaForWireName(state, toolName) ?? (toolName ? defaultShellBridgeArgNormalizeSchema(toolName) : undefined);
|
|
333
334
|
if (!schema) return text;
|
|
334
335
|
try {
|
|
335
336
|
const parsed = JSON.parse(text);
|
|
@@ -1240,14 +1241,159 @@ export function mapCursorProtobufServerMessage(
|
|
|
1240
1241
|
return [];
|
|
1241
1242
|
}
|
|
1242
1243
|
|
|
1244
|
+
const TOOL_CALL_START_PREFIX = "[TOOL_CALL]";
|
|
1245
|
+
const TOOL_CALL_ARGS_MARKER = "[ARGS]";
|
|
1246
|
+
|
|
1247
|
+
function findPotentialMarkerPrefix(text: string): number {
|
|
1248
|
+
for (let len = Math.min(TOOL_CALL_START_PREFIX.length - 1, text.length); len >= 1; len--) {
|
|
1249
|
+
const candidate = text.slice(text.length - len);
|
|
1250
|
+
if (TOOL_CALL_START_PREFIX.startsWith(candidate)) {
|
|
1251
|
+
return text.length - len;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
return -1;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
function parseCursorTextToolCalls(text: string, state: CursorProtobufEventState): CursorServerMessage[] {
|
|
1258
|
+
if (!state.clientToolNames) {
|
|
1259
|
+
return text ? [{ type: "text", text: normalizeCursorTextToolMarkers(text) }] : [];
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
let remaining = (state.textToolCallBuffer ?? "") + text;
|
|
1263
|
+
state.textToolCallBuffer = undefined;
|
|
1264
|
+
|
|
1265
|
+
if (!remaining) return [];
|
|
1266
|
+
|
|
1267
|
+
const out: CursorServerMessage[] = [];
|
|
1268
|
+
|
|
1269
|
+
while (remaining.length > 0) {
|
|
1270
|
+
const startIndex = remaining.indexOf(TOOL_CALL_START_PREFIX);
|
|
1271
|
+
if (startIndex === -1) {
|
|
1272
|
+
const partialIndex = findPotentialMarkerPrefix(remaining);
|
|
1273
|
+
if (partialIndex !== -1) {
|
|
1274
|
+
if (partialIndex > 0) {
|
|
1275
|
+
out.push({ type: "text", text: normalizeCursorTextToolMarkers(remaining.slice(0, partialIndex)) });
|
|
1276
|
+
}
|
|
1277
|
+
state.textToolCallBuffer = remaining.slice(partialIndex);
|
|
1278
|
+
break;
|
|
1279
|
+
}
|
|
1280
|
+
out.push({ type: "text", text: normalizeCursorTextToolMarkers(remaining) });
|
|
1281
|
+
break;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// Check if the candidate at startIndex is actually a tool call
|
|
1285
|
+
const candidateAfter = remaining.slice(startIndex + TOOL_CALL_START_PREFIX.length);
|
|
1286
|
+
const argsPos = candidateAfter.indexOf(TOOL_CALL_ARGS_MARKER);
|
|
1287
|
+
const namePart = argsPos !== -1 ? candidateAfter.slice(0, argsPos).trim() : candidateAfter;
|
|
1288
|
+
const isCandidateValid = namePart.length > 0 && namePart.length <= 64 && /^[a-zA-Z0-9_-]+$/.test(namePart);
|
|
1289
|
+
|
|
1290
|
+
if (!isCandidateValid && argsPos === -1) {
|
|
1291
|
+
// Not a valid tool call and no [ARGS]; treat as plain text
|
|
1292
|
+
out.push({ type: "text", text: normalizeCursorTextToolMarkers(remaining) });
|
|
1293
|
+
break;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
if (startIndex > 0) {
|
|
1297
|
+
out.push({ type: "text", text: normalizeCursorTextToolMarkers(remaining.slice(0, startIndex)) });
|
|
1298
|
+
remaining = remaining.slice(startIndex);
|
|
1299
|
+
continue;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
const argsMarkerIndex = remaining.indexOf(TOOL_CALL_ARGS_MARKER, TOOL_CALL_START_PREFIX.length);
|
|
1303
|
+
if (argsMarkerIndex === -1) {
|
|
1304
|
+
const nameCandidate = remaining.slice(TOOL_CALL_START_PREFIX.length);
|
|
1305
|
+
if (nameCandidate.length > 64 || /[^a-zA-Z0-9_-]/.test(nameCandidate)) {
|
|
1306
|
+
out.push({ type: "text", text: normalizeCursorTextToolMarkers(remaining) });
|
|
1307
|
+
break;
|
|
1308
|
+
}
|
|
1309
|
+
state.textToolCallBuffer = remaining;
|
|
1310
|
+
break;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
const rawName = remaining.slice(TOOL_CALL_START_PREFIX.length, argsMarkerIndex).trim();
|
|
1314
|
+
const wireName = normalizeCursorWireName(rawName);
|
|
1315
|
+
const advertisedName = resolveAdvertisedClientToolName(state, wireName);
|
|
1316
|
+
|
|
1317
|
+
if (!advertisedName) {
|
|
1318
|
+
const endOfMarker = argsMarkerIndex + TOOL_CALL_ARGS_MARKER.length;
|
|
1319
|
+
out.push({ type: "text", text: normalizeCursorTextToolMarkers(remaining.slice(0, endOfMarker)) });
|
|
1320
|
+
remaining = remaining.slice(endOfMarker);
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
const argsStart = argsMarkerIndex + TOOL_CALL_ARGS_MARKER.length;
|
|
1325
|
+
let argsEnd = -1;
|
|
1326
|
+
const trimmedOffset = remaining.slice(argsStart).search(/\S/);
|
|
1327
|
+
if (trimmedOffset !== -1 && remaining[argsStart + trimmedOffset] === "{") {
|
|
1328
|
+
let depth = 0;
|
|
1329
|
+
let inString = false;
|
|
1330
|
+
let escape = false;
|
|
1331
|
+
const jsonStart = argsStart + trimmedOffset;
|
|
1332
|
+
for (let i = jsonStart; i < remaining.length; i++) {
|
|
1333
|
+
const char = remaining[i];
|
|
1334
|
+
if (escape) {
|
|
1335
|
+
escape = false;
|
|
1336
|
+
continue;
|
|
1337
|
+
}
|
|
1338
|
+
if (char === "\\") {
|
|
1339
|
+
escape = true;
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
if (char === '"') {
|
|
1343
|
+
inString = !inString;
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
if (!inString) {
|
|
1347
|
+
if (char === "{") depth++;
|
|
1348
|
+
else if (char === "}") {
|
|
1349
|
+
depth--;
|
|
1350
|
+
if (depth === 0) {
|
|
1351
|
+
argsEnd = i + 1;
|
|
1352
|
+
break;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
if (argsEnd === -1) {
|
|
1360
|
+
state.textToolCallBuffer = remaining;
|
|
1361
|
+
break;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
const argsJson = remaining.slice(argsStart, argsEnd).trim();
|
|
1365
|
+
const toolCallId = `call_${crypto.randomUUID().replace(/-/g, "").slice(0, 16)}`;
|
|
1366
|
+
|
|
1367
|
+
const recordEvents = recordToolCall(state, toolCallId, wireName);
|
|
1368
|
+
out.push(...recordEvents);
|
|
1369
|
+
if (!recordEvents.some(e => e.type === "error")) {
|
|
1370
|
+
const open = state.openToolCalls.get(toolCallId);
|
|
1371
|
+
if (open) open.args = argsJson;
|
|
1372
|
+
let finalArgs = normalizeJsonText(argsJson, wireName, state);
|
|
1373
|
+
if (state.freeformToolNames?.has(open?.name ?? "") && !cursorFreeformWrapperValid(finalArgs)) {
|
|
1374
|
+
try {
|
|
1375
|
+
const parsed = JSON.parse(finalArgs) as unknown;
|
|
1376
|
+
if (typeof parsed === "string") {
|
|
1377
|
+
finalArgs = JSON.stringify({ input: parsed });
|
|
1378
|
+
}
|
|
1379
|
+
} catch {
|
|
1380
|
+
finalArgs = JSON.stringify({ input: finalArgs });
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
out.push(...commitToolCall(state, toolCallId, finalArgs));
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
remaining = remaining.slice(argsEnd);
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
return out;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1243
1392
|
if (serverMessage.message.case !== "interactionUpdate") return [];
|
|
1244
1393
|
const update = serverMessage.message.value.message;
|
|
1245
1394
|
switch (update.case) {
|
|
1246
1395
|
case "textDelta":
|
|
1247
|
-
|
|
1248
|
-
// the advertised wire name before any client sees the text. Real frames are already
|
|
1249
|
-
// normalized structurally (mcpWireNameFromArgs above).
|
|
1250
|
-
return update.value.text ? [{ type: "text", text: normalizeCursorTextToolMarkers(update.value.text) }] : [];
|
|
1396
|
+
return update.value.text ? parseCursorTextToolCalls(update.value.text, state) : [];
|
|
1251
1397
|
case "thinkingDelta":
|
|
1252
1398
|
return update.value.text ? [{ type: "thinking", thinking: update.value.text }] : [];
|
|
1253
1399
|
case "toolCallStarted": {
|
|
@@ -1364,18 +1510,23 @@ export function resolvedTurnUsage(state: CursorProtobufEventState): OcxUsage {
|
|
|
1364
1510
|
*/
|
|
1365
1511
|
export function finalizeTurnEvents(state: CursorProtobufEventState): CursorServerMessage[] {
|
|
1366
1512
|
state.terminated = true;
|
|
1513
|
+
const prefixEvents: CursorServerMessage[] = [];
|
|
1514
|
+
if (state.textToolCallBuffer) {
|
|
1515
|
+
prefixEvents.push({ type: "text", text: normalizeCursorTextToolMarkers(state.textToolCallBuffer) });
|
|
1516
|
+
state.textToolCallBuffer = undefined;
|
|
1517
|
+
}
|
|
1367
1518
|
if (state.openToolCalls.size > 0) {
|
|
1368
1519
|
const openCallIds = [...state.openToolCalls.keys()];
|
|
1369
1520
|
const openIds = openCallIds.join(", ");
|
|
1370
1521
|
// Clear so a second turnEnded (should not happen, but defensive) doesn't re-emit.
|
|
1371
1522
|
for (const callId of openCallIds) state.translatorBudget?.closeCall(callId);
|
|
1372
1523
|
state.openToolCalls.clear();
|
|
1373
|
-
return [{ type: "error", message: `Cursor stream ended with incomplete tool call(s): ${openIds}. Arguments may be truncated; the call was not committed.` }];
|
|
1524
|
+
return [...prefixEvents, { type: "error", message: `Cursor stream ended with incomplete tool call(s): ${openIds}. Arguments may be truncated; the call was not committed.` }];
|
|
1374
1525
|
}
|
|
1375
1526
|
// Surface the absolute context size (when Cursor reported a checkpoint) as both totalTokens and
|
|
1376
1527
|
// the estimated input side of Codex's visible `input + output` counter. Codex status lines can
|
|
1377
1528
|
// render the additive pair instead of total_tokens, so leaving inputTokens at 0 makes a 16k-context
|
|
1378
1529
|
// first turn display as "9 used". Keep outputTokens as the per-turn delta and clamp the inferred
|
|
1379
1530
|
// input to 0 in case Cursor reports a checkpoint smaller than the streamed output delta.
|
|
1380
|
-
return [{ type: "done", usage: resolvedTurnUsage(state) }];
|
|
1531
|
+
return [...prefixEvents, { type: "done", usage: resolvedTurnUsage(state) }];
|
|
1381
1532
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
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, OcxToolResultMessage } from "../../types";
|
|
4
|
+
import type { OcxAssistantContentPart, OcxMessage, OcxRequestOptions, OcxToolResultMessage } from "../../types";
|
|
5
5
|
import { namespacedToolName } from "../../types";
|
|
6
6
|
import type { CursorRunRequest } from "./types";
|
|
7
7
|
import { cursorNeedsExternalToolContinuation, isCursorExternalWireModel } from "./discovery";
|
|
@@ -165,9 +165,26 @@ function truncateToolResultBlob(entry: RootBlobCandidate, maxBytes: number): Roo
|
|
|
165
165
|
return markerOnly.byteLength <= maxBytes ? markerOnly : null;
|
|
166
166
|
}
|
|
167
167
|
|
|
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
|
+
|
|
168
183
|
function systemPromptBlobs(request: CursorRunRequest): RootBlobCandidate[] {
|
|
169
184
|
const prompts = request.system.length > 0 ? [...request.system] : ["You are a helpful assistant."];
|
|
170
185
|
if (cursorRequestHasShellAlias(request.tools)) prompts.push(CURSOR_SHELL_ALIAS_SYSTEM_NOTE);
|
|
186
|
+
const structuredPrompt = structuredOutputPrompt(request.textFormat);
|
|
187
|
+
if (structuredPrompt) prompts.push(structuredPrompt);
|
|
171
188
|
const cursorToolGuidance = buildCursorToolGuidanceSystemNote(
|
|
172
189
|
cursorToolsForActivePrompt(request.tools, activePromptText(request), request.toolChoice),
|
|
173
190
|
request.toolChoice,
|
|
@@ -190,10 +207,11 @@ function assistantRootText(
|
|
|
190
207
|
// Cursor builds the actual model prompt from rootPromptMessagesJson (turns[] is UI/display metadata),
|
|
191
208
|
// so prior history must be replayed here or a ResumeAction has nothing model-visible to continue from.
|
|
192
209
|
// The active user message is excluded because it travels in the action. When the continuation cannot
|
|
193
|
-
// rely on native MCP turn state, tool results stay assistant-role text
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
210
|
+
// rely on native MCP turn state, tool results stay assistant-role text so Cursor does not wrap them
|
|
211
|
+
// as `<user_query>` (#1992). External replay uses a neutral "Tool output" label; protocol markers
|
|
212
|
+
// such as [Tool Result] are reserved for native wire encoding because external models echo them.
|
|
213
|
+
// Native resume models already carry the paired MCP result on turns[], so it is omitted from root
|
|
214
|
+
// replay. Each entry is a SHA-256 blob ID.
|
|
197
215
|
function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobRequestScopeToken): {
|
|
198
216
|
ids: Uint8Array[];
|
|
199
217
|
byteLength: number;
|
|
@@ -246,14 +264,10 @@ function rootPromptMessages(request: CursorRunRequest, requestScope: CursorBlobR
|
|
|
246
264
|
}
|
|
247
265
|
// Assistant tool CALLS are intentionally NOT replayed as visible "[Tool Call]" text here.
|
|
248
266
|
} else if (message.role === "toolResult") {
|
|
249
|
-
// Native resume models already receive the paired MCP result through turns[].
|
|
250
|
-
//
|
|
251
|
-
// to echo that envelope as chat instead of continuing from the structured result.
|
|
267
|
+
// Native resume models already receive the paired MCP result through turns[]. External
|
|
268
|
+
// replay uses neutral text here so models do not echo protocol envelopes as chat.
|
|
252
269
|
if (!echoToolResultInRoot) continue;
|
|
253
|
-
|
|
254
|
-
// node_repl result is an error even when the runtime said isError=false).
|
|
255
|
-
const prefix = normalizedToolResult(message, contentToText(message.content)).isError ? "[Tool Error]" : "[Tool Result]";
|
|
256
|
-
const text = `${prefix}\n${toolResultToText(message)}`;
|
|
270
|
+
const text = externalToolResultToText(message);
|
|
257
271
|
entries.push(rootBlobCandidate(
|
|
258
272
|
toolResultRootPayload(text),
|
|
259
273
|
"toolResult",
|
|
@@ -538,6 +552,12 @@ function toolResultToText(message: OcxToolResultMessage): string {
|
|
|
538
552
|
].join("\n");
|
|
539
553
|
}
|
|
540
554
|
|
|
555
|
+
function externalToolResultToText(message: OcxToolResultMessage): string {
|
|
556
|
+
const normalized = normalizedToolResult(message, contentToText(message.content));
|
|
557
|
+
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}`;
|
|
559
|
+
}
|
|
560
|
+
|
|
541
561
|
/**
|
|
542
562
|
* Shared #1920 normalization entry: pure-text results only. Image-bearing or
|
|
543
563
|
* encrypted results pass through untouched (their content is not plain text).
|
|
@@ -721,12 +741,10 @@ function conversationTurns(
|
|
|
721
741
|
// #1920/#1866: this external-replay site bypasses toolResultToText, so it
|
|
722
742
|
// must consume the normalizer directly — cursor/grok-4.6 is the exact
|
|
723
743
|
// reported repro path for empty Computer Use results.
|
|
724
|
-
const normalized = normalizedToolResult(message, contentToText(message.content));
|
|
725
|
-
const prefix = normalized.isError ? "[Tool Error]" : "[Tool Result]";
|
|
726
744
|
current.steps.push(storeCursorBlob(toBinary(ConversationStepSchema, create(ConversationStepSchema, {
|
|
727
745
|
message: {
|
|
728
746
|
case: "assistantMessage",
|
|
729
|
-
value: create(AssistantMessageSchema, { text:
|
|
747
|
+
value: create(AssistantMessageSchema, { text: externalToolResultToText(message) }),
|
|
730
748
|
},
|
|
731
749
|
})), requestScope));
|
|
732
750
|
continue;
|
|
@@ -442,9 +442,10 @@ export function createCursorRequest(
|
|
|
442
442
|
rawMessages: parsed.context.messages,
|
|
443
443
|
...(parsed._compactionRequest === true || parsed._contextCompactionBoundary === true ? { contextUsageReset: true } : {}),
|
|
444
444
|
...(parsed._compactionRequest === true ? { contextUsageStoreCheckpoints: false } : {}),
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
...(parsed.options.
|
|
445
|
+
...(budget.tools.length ? { tools: budget.tools } : {}),
|
|
446
|
+
...(parsed.options.toolChoice ? { toolChoice: parsed.options.toolChoice } : {}),
|
|
447
|
+
...(parsed.options.textFormat ? { textFormat: parsed.options.textFormat } : {}),
|
|
448
|
+
...(parsed.options.parallelToolCalls !== undefined ? { parallelToolCalls: parsed.options.parallelToolCalls } : {}),
|
|
448
449
|
};
|
|
449
450
|
const resolved = resolveCursorCheckpoint(parsed, request, options);
|
|
450
451
|
if ("reason" in resolved) {
|
|
@@ -48,6 +48,24 @@ export const CURSOR_EXEC_COMMAND_INPUT_SCHEMA = {
|
|
|
48
48
|
tty: { type: "boolean", description: "True allocates a PTY for the command; false or omitted uses plain pipes." },
|
|
49
49
|
yield_time_ms: { type: "number", description: "Wait before yielding output. Defaults to 10000 ms; effective range is 250-30000 ms." },
|
|
50
50
|
max_output_tokens: { type: "number", description: "Output token budget. Defaults to 10000 tokens; larger requests may be capped by policy." },
|
|
51
|
+
sandbox_permissions: {
|
|
52
|
+
type: "string",
|
|
53
|
+
enum: ["use_default", "require_escalated"],
|
|
54
|
+
description: "Per-command sandbox override. Defaults to use_default; use require_escalated for unsandboxed execution.",
|
|
55
|
+
},
|
|
56
|
+
justification: {
|
|
57
|
+
type: "string",
|
|
58
|
+
description: "User-facing approval question for require_escalated; omit otherwise.",
|
|
59
|
+
},
|
|
60
|
+
prefix_rule: {
|
|
61
|
+
type: "array",
|
|
62
|
+
items: { type: "string" },
|
|
63
|
+
description: "Reusable approval prefix for cmd, only with sandbox_permissions: require_escalated; for example [\"git\", \"pull\"].",
|
|
64
|
+
},
|
|
65
|
+
login: {
|
|
66
|
+
type: "boolean",
|
|
67
|
+
description: "True runs the shell with -l/-i semantics; false disables them. Defaults to true.",
|
|
68
|
+
},
|
|
51
69
|
},
|
|
52
70
|
required: ["cmd"],
|
|
53
71
|
additionalProperties: false,
|
|
@@ -109,6 +127,10 @@ export const CODEX_SHELL_BRIDGE_ARG_NORMALIZE_SCHEMA = {
|
|
|
109
127
|
yield_time_ms: { type: "number", description: "Wait before yielding output. Defaults to 10000 ms; effective range is 250-30000 ms." },
|
|
110
128
|
max_output_tokens: { type: "number", description: "Output token budget. Defaults to 10000 tokens; larger requests may be capped by policy." },
|
|
111
129
|
max_output_chars: { type: "number", description: "Output character budget when the Responses tool uses chars instead of tokens." },
|
|
130
|
+
sandbox_permissions: { type: "string" },
|
|
131
|
+
justification: { type: "string" },
|
|
132
|
+
prefix_rule: { type: "array" },
|
|
133
|
+
login: { type: "boolean" },
|
|
112
134
|
},
|
|
113
135
|
required: ["command"],
|
|
114
136
|
} as const;
|
|
@@ -651,7 +673,7 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
651
673
|
// Code mode: shell/edit/MCP live inside freeform `exec` as nested helpers. Without this the
|
|
652
674
|
// model probes for a top-level shell tool that is not there.
|
|
653
675
|
codeMode
|
|
654
|
-
? `\`${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.`
|
|
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\"})\`. 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\`. 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` : ""}. Your tool list may display tools under a longer \`mcp_opencodex-responses_*\` name; call whichever your list shows. 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.`
|
|
655
677
|
: undefined,
|
|
656
678
|
codeMode
|
|
657
679
|
? "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."
|
|
@@ -665,6 +687,9 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
665
687
|
hasBareExec
|
|
666
688
|
? `Prefer the Codex shell bridge over Cursor-native Shell/Read. If a Cursor-native file read, directory listing, grep, or shell operation is rejected, continue with the listed catalog tool ${shellBridgeLabel}.`
|
|
667
689
|
: undefined,
|
|
690
|
+
hasBareExec
|
|
691
|
+
? 'When a command requires network access, file writes outside workspace, or fails due to sandbox restrictions, include `sandbox_permissions: "require_escalated"` and `justification: "..."`.'
|
|
692
|
+
: undefined,
|
|
668
693
|
hostShellNote,
|
|
669
694
|
"Cursor product features (Chronicle, screen recording, Notes, Plans, background agents) are available only if this turn's catalog lists a matching tool; do not offer or promise them otherwise.",
|
|
670
695
|
hasBareExec
|
|
@@ -686,6 +711,7 @@ export function buildCursorToolGuidanceSystemNote(
|
|
|
686
711
|
? `Use ${discoveryTools} only for explicit discovery/resource tasks, not generic tool-count demos.`
|
|
687
712
|
: undefined,
|
|
688
713
|
"Do not count or report a tool call unless a tool result was actually returned.",
|
|
714
|
+
"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.",
|
|
689
715
|
hasBareExec
|
|
690
716
|
? `If a Cursor-native file read, directory listing, grep, or shell operation is rejected by the runtime, use ${shellBridgeLabel} with an equivalent host-shell-safe command (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.`
|
|
691
717
|
: undefined,
|
|
@@ -25,9 +25,10 @@ export interface CursorRunRequest {
|
|
|
25
25
|
* hydration). History stays text-only. data: URLs only in this slice.
|
|
26
26
|
*/
|
|
27
27
|
selectedImages?: readonly ResolvedCursorImage[];
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
tools?: OcxTool[];
|
|
29
|
+
toolChoice?: OcxRequestOptions["toolChoice"];
|
|
30
|
+
textFormat?: OcxRequestOptions["textFormat"];
|
|
31
|
+
parallelToolCalls?: boolean;
|
|
31
32
|
/**
|
|
32
33
|
* Clear provider-private context-usage carry-forward before this run. Used when Codex starts a
|
|
33
34
|
* newly observed compacted context epoch, so pre-compaction totals are not over-reported while
|
package/src/adapters/cursor.ts
CHANGED
|
@@ -208,6 +208,14 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
|
|
|
208
208
|
};
|
|
209
209
|
|
|
210
210
|
const runOnce = async (activeRequest: ReturnType<typeof createCursorRequest>) => {
|
|
211
|
+
const effort = _parsed.options.reasoning;
|
|
212
|
+
const isHeavyReasoning = effort === "high"
|
|
213
|
+
|| effort === "max"
|
|
214
|
+
|| effort === "xhigh"
|
|
215
|
+
|| activeRequest.modelId.includes("grok-4.6")
|
|
216
|
+
|| activeRequest.modelId.includes("kimi-k3")
|
|
217
|
+
|| activeRequest.modelId.includes("opus-4-8");
|
|
218
|
+
const heartbeatOnlyMs = isHeavyReasoning ? 300_000 : 180_000;
|
|
211
219
|
await runCursorTurnWithRetry(
|
|
212
220
|
makeTransport,
|
|
213
221
|
{
|
|
@@ -216,6 +224,7 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
|
|
|
216
224
|
translatorBudget: incoming.translatorBudget,
|
|
217
225
|
requestDeclaresFullAccess: cursorRequestDeclaresFullAccess(activeRequest),
|
|
218
226
|
sessionId: activeRequest.conversationId,
|
|
227
|
+
streamHeartbeatOnlyFailMs: heartbeatOnlyMs,
|
|
219
228
|
...(incoming.providerFetch ? { fetch: incoming.providerFetch } : {}),
|
|
220
229
|
},
|
|
221
230
|
activeRequest,
|
|
@@ -38,9 +38,9 @@ const REPLAY_TTL_MS = 60 * 60 * 1000; // 1h
|
|
|
38
38
|
export const ANTIGRAVITY_REPLAY_MAX_ENTRIES = 10_240;
|
|
39
39
|
const REPLAY_EVICT_BATCH = 128;
|
|
40
40
|
const REPLAY_MAX_CALLS_PER_SESSION = 256;
|
|
41
|
-
export const ANTIGRAVITY_REPLAY_MAX_BYTES_PER_SESSION =
|
|
41
|
+
export const ANTIGRAVITY_REPLAY_MAX_BYTES_PER_SESSION = 8 * 1024 * 1024;
|
|
42
42
|
export const ANTIGRAVITY_REPLAY_MAX_TOTAL_BYTES = 64 * 1024 * 1024;
|
|
43
|
-
const REPLAY_MAX_SIGNATURE_BYTES =
|
|
43
|
+
const REPLAY_MAX_SIGNATURE_BYTES = 1024 * 1024;
|
|
44
44
|
/** Fixed 64-hex outer key length, counted once per session entry. */
|
|
45
45
|
const REPLAY_SESSION_KEY_BYTES = 64;
|
|
46
46
|
const REPLAY_SNAPSHOT_FILE = "antigravity-replay.json";
|
|
@@ -11,6 +11,13 @@ import { antigravityUserAgent } from "./client-fingerprint";
|
|
|
11
11
|
*/
|
|
12
12
|
export const ANTIGRAVITY_REQUEST_UA = antigravityUserAgent();
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Bypass sentinel for Google Cloud Code Assist (Antigravity).
|
|
16
|
+
* When a historical functionCall part lacks a thought_signature, Antigravity rejects
|
|
17
|
+
* the request with HTTP 400 unless this sentinel is provided.
|
|
18
|
+
*/
|
|
19
|
+
export const ANTIGRAVITY_SIGNATURE_BYPASS_SENTINEL = "skip_thought_signature_validator";
|
|
20
|
+
|
|
14
21
|
/**
|
|
15
22
|
* Whether a stored `OcxToolCall.thoughtSignature` is a REAL upstream Gemini signature versus a
|
|
16
23
|
* foreign id that must not be forwarded to Gemini/Antigravity.
|
|
@@ -2,7 +2,10 @@ import { parseUpstreamJsonPayload, safeUpstreamErrorString, sanitizeUpstreamErro
|
|
|
2
2
|
|
|
3
3
|
/** Pull the human detail out of the Google API error envelope `{error:{message,status,code}}`. */
|
|
4
4
|
function googleErrorDetail(payloadText: string): { message?: string; status?: string } {
|
|
5
|
-
|
|
5
|
+
let trimmed = payloadText.trim();
|
|
6
|
+
if (trimmed.startsWith("data:")) {
|
|
7
|
+
trimmed = trimmed.replace(/^data:\s*/, "").trim();
|
|
8
|
+
}
|
|
6
9
|
if (!trimmed || (!trimmed.startsWith("{") && !trimmed.startsWith("["))) {
|
|
7
10
|
return { message: trimmed || undefined };
|
|
8
11
|
}
|
|
@@ -18,7 +21,8 @@ function googleErrorDetail(payloadText: string): { message?: string; status?: st
|
|
|
18
21
|
const ANTIGRAVITY_GEO_BLOCKED_MARKER = "user location is not supported for the api use";
|
|
19
22
|
|
|
20
23
|
export function isAntigravityGeoBlockedBody(payloadText: string): boolean {
|
|
21
|
-
|
|
24
|
+
const lower = payloadText.toLowerCase();
|
|
25
|
+
return lower.includes(ANTIGRAVITY_GEO_BLOCKED_MARKER) || lower.includes("location is not supported");
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
function classifyGoogle(label: string, status: number | undefined, enumStatus: string | undefined, text: string): string {
|