@yansigit/opencodex 2.31.2 → 2.31.3
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/gui/dist/assets/{index-BAMgarF9.js → index-Cxt5fZMP.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/command-code-project-context.ts +377 -0
- package/src/adapters/command-code.ts +5 -1
- package/src/adapters/cursor/live-transport.ts +21 -0
- package/src/adapters/cursor/native-exec-bridge.ts +141 -0
- package/src/adapters/google-http.ts +12 -2
- package/src/adapters/google-wire-compiler.ts +83 -2
- package/src/adapters/google.ts +57 -15
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +23 -15
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/oauth/index.ts +3 -0
- package/src/routing/compatibility/behavior.ts +3 -0
- package/src/server/responses/core.ts +17 -7
- package/src/types/provider.ts +7 -0
- package/src/types/request.ts +6 -0
- package/src/web-search/gemini-executor.ts +35 -13
- package/src/web-search/index.ts +85 -1
|
@@ -94,10 +94,35 @@ function compileContents(value: unknown, toWireName: (name: string) => string):
|
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
const GOOGLE_BUILTIN_TOOL_KEYS = new Set([
|
|
98
|
+
"googleSearch", "google_search",
|
|
99
|
+
"urlContext", "url_context",
|
|
100
|
+
"codeExecution", "code_execution",
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
function isGoogleBuiltinToolObject(rawTool: unknown): boolean {
|
|
104
|
+
if (!isObject(rawTool)) return false;
|
|
105
|
+
const keys = Object.keys(rawTool);
|
|
106
|
+
return keys.length > 0 && keys.every(key => GOOGLE_BUILTIN_TOOL_KEYS.has(key));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function passthroughGoogleBuiltinTools(rawTool: unknown): unknown[] {
|
|
110
|
+
if (!isObject(rawTool) || Array.isArray(rawTool.functionDeclarations)) return [];
|
|
111
|
+
if (!isGoogleBuiltinToolObject(rawTool)) return [];
|
|
112
|
+
const out: Record<string, unknown> = {};
|
|
113
|
+
for (const key of Object.keys(rawTool)) {
|
|
114
|
+
if (key === "googleSearch" || key === "google_search") out.google_search = {};
|
|
115
|
+
else if (key === "urlContext" || key === "url_context") out.url_context = {};
|
|
116
|
+
else if (key === "codeExecution" || key === "code_execution") out.code_execution = rawTool[key] ?? {};
|
|
117
|
+
}
|
|
118
|
+
return Object.keys(out).length > 0 ? [out] : [];
|
|
119
|
+
}
|
|
120
|
+
|
|
97
121
|
function compileTools(value: unknown, toWireName: (name: string) => string): unknown[] | undefined {
|
|
98
122
|
if (!Array.isArray(value)) return undefined;
|
|
99
123
|
const tools = value.flatMap(rawTool => {
|
|
100
|
-
|
|
124
|
+
const builtins = passthroughGoogleBuiltinTools(rawTool);
|
|
125
|
+
if (!isObject(rawTool) || !Array.isArray(rawTool.functionDeclarations)) return builtins;
|
|
101
126
|
const functionDeclarations = rawTool.functionDeclarations.flatMap(rawDeclaration => {
|
|
102
127
|
if (!isObject(rawDeclaration) || typeof rawDeclaration.name !== "string") return [];
|
|
103
128
|
return [{
|
|
@@ -106,7 +131,10 @@ function compileTools(value: unknown, toWireName: (name: string) => string): unk
|
|
|
106
131
|
parameters: sanitizeGeminiToolParameters(rawDeclaration.parameters),
|
|
107
132
|
}];
|
|
108
133
|
});
|
|
109
|
-
return
|
|
134
|
+
return [
|
|
135
|
+
...builtins,
|
|
136
|
+
...(functionDeclarations.length > 0 ? [{ functionDeclarations }] : []),
|
|
137
|
+
];
|
|
110
138
|
});
|
|
111
139
|
return tools.length > 0 ? tools : undefined;
|
|
112
140
|
}
|
|
@@ -238,3 +266,56 @@ export function repairGoogleInvalidRequestBody(body: string, errorPayload: strin
|
|
|
238
266
|
}
|
|
239
267
|
return changed ? JSON.stringify(parsed) : undefined;
|
|
240
268
|
}
|
|
269
|
+
|
|
270
|
+
function stripBuiltinToolsFromRoot(root: JsonObject): boolean {
|
|
271
|
+
if (!Array.isArray(root.tools)) return false;
|
|
272
|
+
const before = root.tools.length;
|
|
273
|
+
const filtered = root.tools.filter(rawTool => !isGoogleBuiltinToolObject(rawTool));
|
|
274
|
+
root.tools = filtered;
|
|
275
|
+
return filtered.length !== before;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** One mixed-tool 400 replay: drop known built-in siblings and keep functionDeclarations. */
|
|
279
|
+
export function stripGoogleBuiltinToolsFromWireBody(body: string): string | undefined {
|
|
280
|
+
let parsed: unknown;
|
|
281
|
+
try {
|
|
282
|
+
parsed = JSON.parse(body) as unknown;
|
|
283
|
+
} catch {
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
if (!isObject(parsed)) return undefined;
|
|
287
|
+
const root = isObject(parsed.request) ? parsed.request : parsed;
|
|
288
|
+
if (!stripBuiltinToolsFromRoot(root)) return undefined;
|
|
289
|
+
return JSON.stringify(parsed);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function isGoogleMixedBuiltinToolError(errorPayload: string): boolean {
|
|
293
|
+
const mentionsBuiltin = /\b(?:google[_ ]?search|url[_ ]?context|code[_ ]?execution|built[- ]?in(?:\s+tools?)?)\b/i.test(errorPayload);
|
|
294
|
+
if (!mentionsBuiltin) return false;
|
|
295
|
+
|
|
296
|
+
// A mixed-tool error names a builtin AND a function/tool term AND a coexistence verb.
|
|
297
|
+
// Builtin mention is gated above; each pattern below requires a coexistence verb to
|
|
298
|
+
// appear within a bounded window of a tool/function/declaration term (or the phrase
|
|
299
|
+
// itself implies coexistence, e.g. "mutually exclusive"). Bare "coexist"/"alongside"
|
|
300
|
+
// without a nearby tool/function term is NOT enough — a schema error can say fields
|
|
301
|
+
// "coexist" while merely mentioning a builtin.
|
|
302
|
+
const describesIncompatibleCoexistence = [
|
|
303
|
+
/\bmix\w*\b[^\n.!?]{0,80}\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b/i,
|
|
304
|
+
/\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b[^\n.!?]{0,80}\bmix\w*\b/i,
|
|
305
|
+
/\bcombin\w*\b[^\n.!?]{0,80}\bwith\b/i,
|
|
306
|
+
/\bcoexist\w*\b[^\n.!?]{0,80}\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b/i,
|
|
307
|
+
/\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b[^\n.!?]{0,80}\bcoexist\w*\b/i,
|
|
308
|
+
/\balongside\b[^\n.!?]{0,80}\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b/i,
|
|
309
|
+
/\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b[^\n.!?]{0,80}\balongside\b/i,
|
|
310
|
+
/\buse\w*\b[^\n.!?]{0,80}\btogether\b/i,
|
|
311
|
+
/\btogether\b[^\n.!?]{0,80}\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b/i,
|
|
312
|
+
/\buse\w*\b[^\n.!?]{0,80}\bwith\b[^\n.!?]{0,80}\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b/i,
|
|
313
|
+
/\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b[^\n.!?]{0,80}\bwith\b[^\n.!?]{0,80}\buse\w*\b/i,
|
|
314
|
+
/\bnot supported with\b[^\n.!?]{0,80}\b(?:tool\w*|function(?:[_ ]declarations?)?|declaration\w*)\b/i,
|
|
315
|
+
/\bmutually exclusive\b/i,
|
|
316
|
+
/\bincompatib\w*\b[^\n.!?]{0,80}\b(?:coexist\w*|combin\w*|alongside|used together|mutually exclusive)\b/i,
|
|
317
|
+
/\b(?:coexist\w*|combin\w*|alongside|used together|mutually exclusive)\b[^\n.!?]{0,80}\bincompatib\w*\b/i,
|
|
318
|
+
].some(pattern => pattern.test(errorPayload));
|
|
319
|
+
|
|
320
|
+
return describesIncompatibleCoexistence;
|
|
321
|
+
}
|
package/src/adapters/google.ts
CHANGED
|
@@ -28,6 +28,12 @@ import { sanitizeGeminiToolParameters } from "./google-tool-schema";
|
|
|
28
28
|
import { identifyRoutedModel } from "./identity";
|
|
29
29
|
import { antigravityUsesReplayCache, applyAntigravityReplay, clearAntigravityReplay, observeAntigravityReplay } from "./google-antigravity-replay";
|
|
30
30
|
import { resolveAntigravityEffortWireModel } from "../providers/antigravity-models";
|
|
31
|
+
import {
|
|
32
|
+
extractCcaGroundingSources,
|
|
33
|
+
formatCcaGroundingSourcesAppendix,
|
|
34
|
+
isCcaSearchSuggestionHtml,
|
|
35
|
+
} from "../web-search/gemini-executor";
|
|
36
|
+
import type { WebSearchSource } from "../web-search/parse";
|
|
31
37
|
import { googleVertexLocationConfigError } from "../providers/google-vertex-location";
|
|
32
38
|
import { lookupReplayThoughtSignature } from "../responses/thought-signature-replay";
|
|
33
39
|
import {
|
|
@@ -335,22 +341,29 @@ function messagesToGeminiFormat(
|
|
|
335
341
|
return { systemInstruction, contents };
|
|
336
342
|
}
|
|
337
343
|
|
|
338
|
-
function toolsToGeminiFormat(
|
|
339
|
-
|
|
344
|
+
function toolsToGeminiFormat(
|
|
345
|
+
parsed: OcxParsedRequest,
|
|
346
|
+
wireModelId: string,
|
|
347
|
+
): unknown[] | undefined {
|
|
348
|
+
const grounding = parsed._ccaInTurnGrounding;
|
|
340
349
|
const allowed = isAllowedToolChoice(parsed.options.toolChoice)
|
|
341
350
|
? new Set(parsed.options.toolChoice.allowedTools)
|
|
342
351
|
: undefined;
|
|
343
352
|
const tools = allowed
|
|
344
|
-
? parsed.context.tools
|
|
345
|
-
: parsed.context.tools;
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
353
|
+
? parsed.context.tools?.filter(t => toolAllowedByChoice(t, allowed, parsed.context.tools)) ?? []
|
|
354
|
+
: parsed.context.tools ?? [];
|
|
355
|
+
const functionDeclarations = tools.map(t => ({
|
|
356
|
+
name: namespacedToolName(t.namespace, t.name),
|
|
357
|
+
description: t.description,
|
|
358
|
+
parameters: t.parameters,
|
|
359
|
+
}));
|
|
360
|
+
const wireTools: unknown[] = [];
|
|
361
|
+
if (functionDeclarations.length > 0) wireTools.push({ functionDeclarations });
|
|
362
|
+
if (grounding && !/claude/i.test(wireModelId)) {
|
|
363
|
+
if (grounding.search) wireTools.push({ google_search: {} });
|
|
364
|
+
if (grounding.urlContext) wireTools.push({ url_context: {} });
|
|
365
|
+
}
|
|
366
|
+
return wireTools.length > 0 ? wireTools : undefined;
|
|
354
367
|
}
|
|
355
368
|
|
|
356
369
|
/**
|
|
@@ -509,11 +522,12 @@ function googleToolCallMetadataFromPart(
|
|
|
509
522
|
* Keep that provider visibility bit authoritative here so the streaming and buffered parsers
|
|
510
523
|
* cannot accidentally expose the same hidden reasoning through different event types.
|
|
511
524
|
*/
|
|
512
|
-
function googlePartTextEvent(part: GoogleResponsePart): AdapterEvent | undefined {
|
|
525
|
+
function googlePartTextEvent(part: GoogleResponsePart, filterCcaSearchSuggestionHtml = false): AdapterEvent | undefined {
|
|
513
526
|
// A malformed scalar/object is not text and must not cross the AdapterEvent boundary. Dropping
|
|
514
527
|
// only this optional field preserves the rest of the part without inventing assistant output by
|
|
515
528
|
// coercion; an empty string keeps its existing no-event behavior.
|
|
516
529
|
if (typeof part.text !== "string" || part.text.length === 0) return undefined;
|
|
530
|
+
if (filterCcaSearchSuggestionHtml && isCcaSearchSuggestionHtml(part.text)) return undefined;
|
|
517
531
|
return part.thought === true
|
|
518
532
|
? { type: "reasoning_raw_delta", text: part.text }
|
|
519
533
|
: { type: "text_delta", text: part.text };
|
|
@@ -669,6 +683,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
669
683
|
let vertexReplayModel: string | undefined;
|
|
670
684
|
let vertexReplaySession: string | undefined;
|
|
671
685
|
let restoreGoogleToolName = (name: string): string => name;
|
|
686
|
+
const emitInTurnGroundingSourcesQueue: boolean[] = [];
|
|
672
687
|
return {
|
|
673
688
|
name: "google",
|
|
674
689
|
|
|
@@ -702,7 +717,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
702
717
|
identityModelId,
|
|
703
718
|
provider.googleMode === "cloud-code-assist",
|
|
704
719
|
);
|
|
705
|
-
const tools = toolsToGeminiFormat(parsed);
|
|
720
|
+
const tools = toolsToGeminiFormat(parsed, routedModelId);
|
|
706
721
|
|
|
707
722
|
const body: Record<string, unknown> = { contents };
|
|
708
723
|
if (systemInstruction) body.systemInstruction = systemInstruction;
|
|
@@ -836,6 +851,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
836
851
|
if (/claude/i.test(wireModelId)) {
|
|
837
852
|
headers["anthropic-beta"] = "interleaved-thinking-2025-05-14";
|
|
838
853
|
}
|
|
854
|
+
emitInTurnGroundingSourcesQueue.push(!!parsed._ccaInTurnGrounding);
|
|
839
855
|
return { url, method: "POST", headers, body: JSON.stringify(envelope) };
|
|
840
856
|
}
|
|
841
857
|
|
|
@@ -861,6 +877,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
861
877
|
if (apiKey) {
|
|
862
878
|
const url = `https://aiplatform.googleapis.com/v1/publishers/google/models/${parsed.modelId}:${method}${streamParam}`;
|
|
863
879
|
headers["x-goog-api-key"] = apiKey;
|
|
880
|
+
emitInTurnGroundingSourcesQueue.push(!!parsed._ccaInTurnGrounding);
|
|
864
881
|
return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
|
|
865
882
|
}
|
|
866
883
|
const project = provider.project || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT;
|
|
@@ -873,6 +890,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
873
890
|
const url = `https://${host}/v1/projects/${project}/locations/${location}/publishers/google/models/${parsed.modelId}:${method}${streamParam}`;
|
|
874
891
|
const token = await getVertexAccessToken();
|
|
875
892
|
headers["Authorization"] = `Bearer ${token}`;
|
|
893
|
+
emitInTurnGroundingSourcesQueue.push(!!parsed._ccaInTurnGrounding);
|
|
876
894
|
return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
|
|
877
895
|
}
|
|
878
896
|
|
|
@@ -884,10 +902,14 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
884
902
|
|
|
885
903
|
const compiled = compileGoogleWireBody(body);
|
|
886
904
|
restoreGoogleToolName = compiled.restoreToolName;
|
|
905
|
+
emitInTurnGroundingSourcesQueue.push(!!parsed._ccaInTurnGrounding);
|
|
887
906
|
return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
|
|
888
907
|
},
|
|
889
908
|
|
|
890
909
|
async *parseStream(response: Response, budget: TranslatorBudget): AsyncGenerator<AdapterEvent> {
|
|
910
|
+
const emitInTurnGroundingSources = emitInTurnGroundingSourcesQueue.shift() ?? false;
|
|
911
|
+
const filterCcaSearchSuggestionHtml =
|
|
912
|
+
provider.googleMode === "cloud-code-assist" && emitInTurnGroundingSources;
|
|
891
913
|
if (!response.body) {
|
|
892
914
|
yield { type: "error", message: "No response body" };
|
|
893
915
|
return;
|
|
@@ -912,6 +934,14 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
912
934
|
let sawAnyFrame = false;
|
|
913
935
|
let sawTerminalSignal = false;
|
|
914
936
|
let pendingStreamThoughtSig: string | undefined;
|
|
937
|
+
const groundingSources: WebSearchSource[] = [];
|
|
938
|
+
let groundingSourcesEmitted = false;
|
|
939
|
+
|
|
940
|
+
const mergeGroundingSources = (groundingMetadata: unknown): void => {
|
|
941
|
+
for (const source of extractCcaGroundingSources(groundingMetadata)) {
|
|
942
|
+
if (!groundingSources.some(existing => existing.url === source.url)) groundingSources.push(source);
|
|
943
|
+
}
|
|
944
|
+
};
|
|
915
945
|
|
|
916
946
|
const handleDataLine = async function* (line: string): AsyncGenerator<AdapterEvent, "continue" | "content" | "terminate"> {
|
|
917
947
|
const payload = line.slice(5).trim();
|
|
@@ -1006,8 +1036,13 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1006
1036
|
const candidate = rawCandidate as {
|
|
1007
1037
|
content?: unknown;
|
|
1008
1038
|
finishReason?: string;
|
|
1039
|
+
groundingMetadata?: unknown;
|
|
1009
1040
|
};
|
|
1010
1041
|
|
|
1042
|
+
if (emitInTurnGroundingSources && candidate.groundingMetadata) {
|
|
1043
|
+
mergeGroundingSources(candidate.groundingMetadata);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1011
1046
|
if (typeof candidate.finishReason === "string" && candidate.finishReason) {
|
|
1012
1047
|
lastFinishReason = candidate.finishReason;
|
|
1013
1048
|
sawTerminalSignal = true;
|
|
@@ -1055,7 +1090,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1055
1090
|
if (part.thought === true && sig && isLikelyRealThoughtSignature(sig)) {
|
|
1056
1091
|
pendingStreamThoughtSig = sig;
|
|
1057
1092
|
}
|
|
1058
|
-
const textEvent = googlePartTextEvent(part);
|
|
1093
|
+
const textEvent = googlePartTextEvent(part, filterCcaSearchSuggestionHtml);
|
|
1059
1094
|
if (textEvent) {
|
|
1060
1095
|
emittedContentEvent = true;
|
|
1061
1096
|
yield textEvent;
|
|
@@ -1166,6 +1201,13 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
1166
1201
|
yield { type: "error", message: "upstream stream ended without a terminal signal — possible truncation" };
|
|
1167
1202
|
return;
|
|
1168
1203
|
}
|
|
1204
|
+
if (emitInTurnGroundingSources && !groundingSourcesEmitted && groundingSources.length > 0) {
|
|
1205
|
+
const appendix = formatCcaGroundingSourcesAppendix(groundingSources);
|
|
1206
|
+
if (appendix) {
|
|
1207
|
+
groundingSourcesEmitted = true;
|
|
1208
|
+
yield { type: "text_delta", text: appendix };
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1169
1211
|
const stopReason = lastFinishReason === "MAX_TOKENS"
|
|
1170
1212
|
? "max_tokens"
|
|
1171
1213
|
: ["SAFETY", "RECITATION", "BLOCKLIST", "PROHIBITED_CONTENT", "SPII"].includes(lastFinishReason ?? "")
|
package/src/config.ts
CHANGED
|
@@ -735,6 +735,8 @@ const providerConfigSchema = z.object({
|
|
|
735
735
|
// accepted, persisted, and then silently resolved to the `code_mode_only` default — the
|
|
736
736
|
// operator asked for shell mode, got code mode, and was told nothing (#2106).
|
|
737
737
|
codexToolMode: z.enum(["code_mode_only", "shell"]).optional(),
|
|
738
|
+
// Validated rather than passed through: same rationale as codexToolMode above.
|
|
739
|
+
projectContext: z.enum(["off", "on"]).optional(),
|
|
738
740
|
responsesItemIdRepair: z.object({
|
|
739
741
|
message: z.array(z.string().min(1)).optional(),
|
|
740
742
|
reasoning: z.array(z.string().min(1)).optional(),
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"path": "package.json",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "fa2d431959a8bfdaa38e582dc186e5cea86bd59a191cb47e590c4b769d31c497"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"path": "scripts/model-metadata.source.json",
|
|
@@ -52,9 +52,13 @@
|
|
|
52
52
|
"path": "src/adapters/cline-pass-deepseek-v4-tool-replay.ts",
|
|
53
53
|
"sha256": "da664327db4e0bae8ab689230deec415ad3f6d0f18c596dcc5b94c8a8d08a345"
|
|
54
54
|
},
|
|
55
|
+
{
|
|
56
|
+
"path": "src/adapters/command-code-project-context.ts",
|
|
57
|
+
"sha256": "72bcc19ad2ce2a3f743391d6388c67912e3cf3bfc244dd6b410638c2c34241a5"
|
|
58
|
+
},
|
|
55
59
|
{
|
|
56
60
|
"path": "src/adapters/command-code.ts",
|
|
57
|
-
"sha256": "
|
|
61
|
+
"sha256": "dbed5da61ccf72aa3a250270e8b25ada850c05fce06b10326b2fc132416aa24a"
|
|
58
62
|
},
|
|
59
63
|
{
|
|
60
64
|
"path": "src/adapters/cursor.ts",
|
|
@@ -122,7 +126,7 @@
|
|
|
122
126
|
},
|
|
123
127
|
{
|
|
124
128
|
"path": "src/adapters/cursor/live-transport.ts",
|
|
125
|
-
"sha256": "
|
|
129
|
+
"sha256": "797e54c0aa7cd032aba8947016178d7346dded57506eb767ce635523f8ed5d71"
|
|
126
130
|
},
|
|
127
131
|
{
|
|
128
132
|
"path": "src/adapters/cursor/mcp-config.ts",
|
|
@@ -136,6 +140,10 @@
|
|
|
136
140
|
"path": "src/adapters/cursor/message-mapper.ts",
|
|
137
141
|
"sha256": "aa83d8db1616a8f43badb15dfc6ee8601e0604a50cb91569555f0f4488418f1c"
|
|
138
142
|
},
|
|
143
|
+
{
|
|
144
|
+
"path": "src/adapters/cursor/native-exec-bridge.ts",
|
|
145
|
+
"sha256": "0a5c5f67d0ab02563f57e3f0dfdac096c2586b9e46b8f51601063d6e531e5a40"
|
|
146
|
+
},
|
|
139
147
|
{
|
|
140
148
|
"path": "src/adapters/cursor/native-exec-common.ts",
|
|
141
149
|
"sha256": "4682a899e9e626fc7fe7a481bf565468c44641d30414d2e3de560c703ce090b8"
|
|
@@ -226,7 +234,7 @@
|
|
|
226
234
|
},
|
|
227
235
|
{
|
|
228
236
|
"path": "src/adapters/google-http.ts",
|
|
229
|
-
"sha256": "
|
|
237
|
+
"sha256": "1432cd6bc49988200e87d8703f0659928efa937806e76f6522167ea62b360d56"
|
|
230
238
|
},
|
|
231
239
|
{
|
|
232
240
|
"path": "src/adapters/google-tool-schema.ts",
|
|
@@ -238,11 +246,11 @@
|
|
|
238
246
|
},
|
|
239
247
|
{
|
|
240
248
|
"path": "src/adapters/google-wire-compiler.ts",
|
|
241
|
-
"sha256": "
|
|
249
|
+
"sha256": "b6fc399cc8945e43ceaeb9580a69a28c10f6941e6c567802485f5589437f2086"
|
|
242
250
|
},
|
|
243
251
|
{
|
|
244
252
|
"path": "src/adapters/google.ts",
|
|
245
|
-
"sha256": "
|
|
253
|
+
"sha256": "eae2446639701e12df75bade1fd871d6275efda162fe42ba09353c42f9b5f197"
|
|
246
254
|
},
|
|
247
255
|
{
|
|
248
256
|
"path": "src/adapters/identity.ts",
|
|
@@ -1098,7 +1106,7 @@
|
|
|
1098
1106
|
},
|
|
1099
1107
|
{
|
|
1100
1108
|
"path": "src/config.ts",
|
|
1101
|
-
"sha256": "
|
|
1109
|
+
"sha256": "d87f6e979e9e4e51ac958ca2ab8e3e48401894f5cfa166924430ce45089528cc"
|
|
1102
1110
|
},
|
|
1103
1111
|
{
|
|
1104
1112
|
"path": "src/config/provider-name.ts",
|
|
@@ -1694,7 +1702,7 @@
|
|
|
1694
1702
|
},
|
|
1695
1703
|
{
|
|
1696
1704
|
"path": "src/lab/subject/behavior-fingerprint.ts",
|
|
1697
|
-
"sha256": "
|
|
1705
|
+
"sha256": "bbc988927141dc5e4d4f895b3ffbdded4a07153802ea62b1ad775d136f7a6022"
|
|
1698
1706
|
},
|
|
1699
1707
|
{
|
|
1700
1708
|
"path": "src/lab/subject/installation-salt.ts",
|
|
@@ -2046,7 +2054,7 @@
|
|
|
2046
2054
|
},
|
|
2047
2055
|
{
|
|
2048
2056
|
"path": "src/oauth/index.ts",
|
|
2049
|
-
"sha256": "
|
|
2057
|
+
"sha256": "d867f09896669ab76d4886daa7ddd9b91476f3fbcbc6b9f41c518e3aab04e393"
|
|
2050
2058
|
},
|
|
2051
2059
|
{
|
|
2052
2060
|
"path": "src/oauth/key-providers.ts",
|
|
@@ -2386,7 +2394,7 @@
|
|
|
2386
2394
|
},
|
|
2387
2395
|
{
|
|
2388
2396
|
"path": "src/routing/compatibility/behavior.ts",
|
|
2389
|
-
"sha256": "
|
|
2397
|
+
"sha256": "1c3008ab085dc8bb0033f23b5eaa640553484036db1ad3e44ccbff1d12618225"
|
|
2390
2398
|
},
|
|
2391
2399
|
{
|
|
2392
2400
|
"path": "src/routing/compatibility/catalog.ts",
|
|
@@ -2774,7 +2782,7 @@
|
|
|
2774
2782
|
},
|
|
2775
2783
|
{
|
|
2776
2784
|
"path": "src/server/responses/core.ts",
|
|
2777
|
-
"sha256": "
|
|
2785
|
+
"sha256": "e0f29663b051f0ba820b1d0a5ca460a6dcde3f219be950db91e53f305d87a07d"
|
|
2778
2786
|
},
|
|
2779
2787
|
{
|
|
2780
2788
|
"path": "src/server/responses/empty-completion-guard.ts",
|
|
@@ -2958,11 +2966,11 @@
|
|
|
2958
2966
|
},
|
|
2959
2967
|
{
|
|
2960
2968
|
"path": "src/types/provider.ts",
|
|
2961
|
-
"sha256": "
|
|
2969
|
+
"sha256": "e1b496ef630fc6bbd2829eda217975a1aaa464ff27c9a2a5dabba44220c82dfb"
|
|
2962
2970
|
},
|
|
2963
2971
|
{
|
|
2964
2972
|
"path": "src/types/request.ts",
|
|
2965
|
-
"sha256": "
|
|
2973
|
+
"sha256": "729aa89dcc03f4255a2e5fdb8f5000efc73625072a855d3e52eec543841bbc20"
|
|
2966
2974
|
},
|
|
2967
2975
|
{
|
|
2968
2976
|
"path": "src/types/tools.ts",
|
|
@@ -3106,11 +3114,11 @@
|
|
|
3106
3114
|
},
|
|
3107
3115
|
{
|
|
3108
3116
|
"path": "src/web-search/gemini-executor.ts",
|
|
3109
|
-
"sha256": "
|
|
3117
|
+
"sha256": "7a2d576680af12d9894896dac3bfaab9cec62a3f959857271bc041baba52f83e"
|
|
3110
3118
|
},
|
|
3111
3119
|
{
|
|
3112
3120
|
"path": "src/web-search/index.ts",
|
|
3113
|
-
"sha256": "
|
|
3121
|
+
"sha256": "6751c704b888ac114cca3b7f2e6dc840ceb33164b2d585e74aa3a3c399bee1fb"
|
|
3114
3122
|
},
|
|
3115
3123
|
{
|
|
3116
3124
|
"path": "src/web-search/loop.ts",
|
|
@@ -3,7 +3,7 @@ import { jcsStringify } from "../digest";
|
|
|
3
3
|
import type { LabBehaviorSource, LabBehaviorValues } from "../live/types";
|
|
4
4
|
|
|
5
5
|
const CLOSED_KEYS = new Set([
|
|
6
|
-
"wire.adapter", "wire.upstreamProtocol", "wire.responsesPath", "wire.commandCodeVersion", "wire.modelSuffixMode",
|
|
6
|
+
"wire.adapter", "wire.upstreamProtocol", "wire.responsesPath", "wire.commandCodeVersion", "wire.commandCodeProjectContext", "wire.modelSuffixMode",
|
|
7
7
|
"auth.mode", "auth.transport",
|
|
8
8
|
"responses.stateful", "responses.upstreamStreaming", "responses.serviceTier", "responses.fastWireKind", "responses.fastWireValue", "responses.snapshotRepair", "responses.itemIdRepair",
|
|
9
9
|
"limits.contextWindow", "limits.maxInputTokens", "limits.maxOutputTokens",
|
package/src/oauth/index.ts
CHANGED
|
@@ -1073,6 +1073,9 @@ export function upsertOAuthProvider(config: OcxConfig, provider: string): void {
|
|
|
1073
1073
|
if (existing?.commandCodeVersion !== undefined) {
|
|
1074
1074
|
next.commandCodeVersion = existing.commandCodeVersion;
|
|
1075
1075
|
}
|
|
1076
|
+
if (existing?.projectContext !== undefined) {
|
|
1077
|
+
next.projectContext = existing.projectContext;
|
|
1078
|
+
}
|
|
1076
1079
|
// User-configured price overlays are operator data, not preset state; a
|
|
1077
1080
|
// re-login, add-account, or reauth must not silently drop them from the
|
|
1078
1081
|
// Logs/Usage estimates.
|
|
@@ -148,6 +148,9 @@ export function resolveProductionBehaviorValues(
|
|
|
148
148
|
"wire.upstreamProtocol": behaviorRow("provider_config", upstreamProtocol),
|
|
149
149
|
"wire.responsesPath": behaviorRow("provider_config", effective.responsesPath ?? null),
|
|
150
150
|
"wire.commandCodeVersion": behaviorRow("provider_config", effective.commandCodeVersion ?? null),
|
|
151
|
+
...(adapter === "command-code"
|
|
152
|
+
? { "wire.commandCodeProjectContext": behaviorRow("provider_config", effective.projectContext ?? "off") }
|
|
153
|
+
: {}),
|
|
151
154
|
"wire.modelSuffixMode": behaviorRow(
|
|
152
155
|
"provider_config",
|
|
153
156
|
effective.modelSuffixBracketStrip === true ? "bracket_strip" : "none",
|
|
@@ -125,7 +125,7 @@ import {
|
|
|
125
125
|
rotateCursorAccountOnAuth,
|
|
126
126
|
} from "../../oauth/cursor-routing";
|
|
127
127
|
import { getAccountCredential } from "../../oauth/store";
|
|
128
|
-
import { buildWebSearchTool, planWebSearch, runWithWebSearch, shouldResolveOpenAiWebSearchSidecar } from "../../web-search";
|
|
128
|
+
import { buildWebSearchTool, mediaBridgeWillRun, planWebSearch, resolveCcaInTurnGrounding, runWithWebSearch, shouldResolveOpenAiWebSearchSidecar } from "../../web-search";
|
|
129
129
|
import { buildImageTool, buildVideoTool, planImageBridge, planVideoBridge, runWithImageBridge, clampImageMaxRounds, IMAGE_GEN_TOOL_NAME, VIDEO_GEN_TOOL_NAME } from "../../images";
|
|
130
130
|
import { describeImagesInPlace, isModelTextOnly, planVisionSidecar, resolveOpenAiVisionModel, shouldResolveOpenAiVisionSidecar, stripImagesInPlace } from "../../vision";
|
|
131
131
|
import { createAdapterEventQueue, preflightAdapterEvents, type AdapterEventQueue } from "../../adapters/run-turn-queue";
|
|
@@ -4061,13 +4061,23 @@ async function handleResponsesInner(
|
|
|
4061
4061
|
// - non-runTurn: web-search wins over image when both eligible (documented priority)
|
|
4062
4062
|
// - runTurn: image bridge may run (it supports runTurn); web-search is skipped so runTurn
|
|
4063
4063
|
// can proceed for web-search-only turns
|
|
4064
|
-
const wsPlan = !routedCompaction
|
|
4065
|
-
? planWebSearch(config, parsed, false, route.provider, route.modelId, openAiSidecar)
|
|
4066
|
-
: undefined;
|
|
4067
4064
|
const imgPlan = !routedCompaction ? await planImageBridge(config, parsed, route.provider) : undefined;
|
|
4068
4065
|
const vidPlan = !routedCompaction ? await planVideoBridge(config, parsed, route.provider) : undefined;
|
|
4069
|
-
const
|
|
4070
|
-
|
|
4066
|
+
const hasMediaPlan = !!(imgPlan || vidPlan);
|
|
4067
|
+
// A media plan is not enough to suppress Gemini 2.x grounding: both bridges inject tools only
|
|
4068
|
+
// on streaming turns. This is the potential-injection value used to resolve sidecar precedence.
|
|
4069
|
+
const mediaMayInject = mediaBridgeWillRun(hasMediaPlan, false, !!adapter.runTurn, parsed.stream);
|
|
4070
|
+
const wsPlan = !routedCompaction
|
|
4071
|
+
? planWebSearch(config, parsed, false, route.provider, route.modelId, openAiSidecar, mediaMayInject)
|
|
4072
|
+
: undefined;
|
|
4073
|
+
const webSearchWinsMedia = !!wsPlan && !adapter.runTurn;
|
|
4074
|
+
const mediaWillRun = mediaBridgeWillRun(hasMediaPlan, !!wsPlan, !!adapter.runTurn, parsed.stream);
|
|
4075
|
+
const ccaInTurnGrounding = !routedCompaction
|
|
4076
|
+
? resolveCcaInTurnGrounding(config, parsed, false, route.provider, route.modelId, mediaWillRun)
|
|
4077
|
+
: undefined;
|
|
4078
|
+
if (ccaInTurnGrounding) parsed._ccaInTurnGrounding = ccaInTurnGrounding;
|
|
4079
|
+
const canRunWebSearch = webSearchWinsMedia && !ccaInTurnGrounding;
|
|
4080
|
+
if (hasMediaPlan && webSearchWinsMedia) {
|
|
4071
4081
|
// Web search takes priority when both are active — the media bridge cannot run
|
|
4072
4082
|
// alongside runWithWebSearch. Surface a runtime signal so the user knows their
|
|
4073
4083
|
// configured video/image bridge was skipped for this turn, rather than silently
|
|
@@ -4075,7 +4085,7 @@ async function handleResponsesInner(
|
|
|
4075
4085
|
if (vidPlan) console.warn("[videos] video bridge skipped: web search is active for this turn");
|
|
4076
4086
|
if (imgPlan) console.warn("[images] image bridge skipped: web search is active for this turn");
|
|
4077
4087
|
}
|
|
4078
|
-
if (
|
|
4088
|
+
if (mediaWillRun || (imgPlan && !parsed.stream && !webSearchWinsMedia)) {
|
|
4079
4089
|
// The image bridge detects a hosted image_generation tool and requires streaming.
|
|
4080
4090
|
// The video bridge activates from config and injects a tool — it also needs streaming
|
|
4081
4091
|
// (the loop returns SSE). For video-only (no imgPlan) on a non-streaming request, skip
|
package/src/types/provider.ts
CHANGED
|
@@ -169,6 +169,13 @@ export interface OcxProviderConfig {
|
|
|
169
169
|
* version here instead of waiting for a code change. Absent uses the adapter's current default.
|
|
170
170
|
*/
|
|
171
171
|
commandCodeVersion?: string;
|
|
172
|
+
/**
|
|
173
|
+
* Command Code OAuth `/alpha/generate` project-context envelope. When `"on"`, the adapter
|
|
174
|
+
* fills `memory`, `taste`, and `skills` from bounded files under `process.cwd()`. Absent or
|
|
175
|
+
* `"off"` keeps the empty envelope (`memory: ""`, `taste: null`, `skills: null`). Does not
|
|
176
|
+
* enable taste learning (`x-taste-learning` stays `"false"`).
|
|
177
|
+
*/
|
|
178
|
+
projectContext?: "off" | "on";
|
|
172
179
|
/**
|
|
173
180
|
* Responses upstream that stores nothing server-side (DeepSeek documents "the API
|
|
174
181
|
* is stateless"). Stateful request parameters are dropped, `store` is pinned false,
|
package/src/types/request.ts
CHANGED
|
@@ -94,6 +94,12 @@ export interface OcxParsedRequest {
|
|
|
94
94
|
* executes searches via the gpt-5.4-mini sidecar (see src/web-search). Absent when not requested.
|
|
95
95
|
*/
|
|
96
96
|
_webSearch?: Record<string, unknown>;
|
|
97
|
+
/**
|
|
98
|
+
* Antigravity Gemini in-turn CCA grounding: google_search and optional url_context ride the main
|
|
99
|
+
* routed fetch instead of the web-search sidecar loop. Set by core.ts when resolveCcaInTurnGrounding
|
|
100
|
+
* matches; consumed by the Google adapter at buildRequest/parseStream time.
|
|
101
|
+
*/
|
|
102
|
+
_ccaInTurnGrounding?: { search: boolean; urlContext: boolean };
|
|
97
103
|
/** Hosted image_generation tool config stashed for the image bridge sidecar (see src/images). */
|
|
98
104
|
_imageGeneration?: { toolNames: Set<string>; originalTool?: Record<string, unknown> };
|
|
99
105
|
/**
|
|
@@ -19,6 +19,7 @@ import { ANTIGRAVITY_REQUEST_UA } from "../adapters/google-antigravity-wire";
|
|
|
19
19
|
import { resolveAntigravityEffortWireModel } from "../providers/antigravity-models";
|
|
20
20
|
import { getProviderRegistryEntry } from "../providers/registry";
|
|
21
21
|
import { MAX_SIDECAR_RESPONSE_BYTES, type WebSearchSource } from "./parse";
|
|
22
|
+
import { appendSafeWebSearchSource } from "./sources";
|
|
22
23
|
import { BASE_INSTRUCTION, IMAGE_INSTRUCTION, type SidecarOutcome, type SidecarSettings } from "./executor";
|
|
23
24
|
|
|
24
25
|
const CCA_FALLBACK_BASE = "https://daily-cloudcode-pa.googleapis.com";
|
|
@@ -117,25 +118,46 @@ export async function runGeminiWebSearch(
|
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
/** Map a CCA generateContent payload (possibly wrapped in {response}) to text + grounding sources. */
|
|
121
|
+
export function extractCcaGroundingSources(groundingMetadata: unknown): WebSearchSource[] {
|
|
122
|
+
const sources: WebSearchSource[] = [];
|
|
123
|
+
const gm = isRec(groundingMetadata) ? groundingMetadata : undefined;
|
|
124
|
+
if (!gm || !Array.isArray(gm.groundingChunks)) return sources;
|
|
125
|
+
for (const chunk of gm.groundingChunks) {
|
|
126
|
+
const web = isRec(chunk) && isRec(chunk.web) ? chunk.web : undefined;
|
|
127
|
+
if (!web) continue;
|
|
128
|
+
// Route through the shared safe-source validator: enforces http(s) scheme,
|
|
129
|
+
// control-character filtering, dedup, and per-message count/byte budgets.
|
|
130
|
+
appendSafeWebSearchSource(sources, { url: web.uri, title: web.title });
|
|
131
|
+
}
|
|
132
|
+
return sources;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Skip search-suggestion HTML widgets Google may attach alongside grounded answers. */
|
|
136
|
+
export function isCcaSearchSuggestionHtml(text: string): boolean {
|
|
137
|
+
const trimmed = text.trim();
|
|
138
|
+
if (!trimmed.startsWith("<")) return false;
|
|
139
|
+
return /<style[\s>]/i.test(trimmed) || /search[_-]?suggest/i.test(trimmed) || /grounding-widget/i.test(trimmed);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function formatCcaGroundingSourcesAppendix(sources: readonly WebSearchSource[]): string {
|
|
143
|
+
if (sources.length === 0) return "";
|
|
144
|
+
const lines = ["", "Sources:"];
|
|
145
|
+
sources.slice(0, 8).forEach((s, i) => {
|
|
146
|
+
lines.push(`[${i + 1}] ${s.title ? `${s.title} — ` : ""}${s.url}`);
|
|
147
|
+
});
|
|
148
|
+
return lines.join("\n");
|
|
149
|
+
}
|
|
150
|
+
|
|
120
151
|
export function mapCcaGroundedResponse(payload: unknown): SidecarOutcome {
|
|
121
152
|
const root = isRec(payload) && isRec(payload.response) ? payload.response : payload;
|
|
122
153
|
if (!isRec(root)) return { text: "", sources: [], error: "gemini sidecar returned a non-JSON or empty body" };
|
|
123
154
|
const candidate = Array.isArray(root.candidates) && isRec(root.candidates[0]) ? root.candidates[0] : undefined;
|
|
124
155
|
if (!candidate) return { text: "", sources: [], error: "gemini sidecar returned no candidates" };
|
|
125
156
|
const parts = isRec(candidate.content) && Array.isArray(candidate.content.parts) ? candidate.content.parts : [];
|
|
126
|
-
const text = parts
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
if (gm && Array.isArray(gm.groundingChunks)) {
|
|
131
|
-
for (const chunk of gm.groundingChunks) {
|
|
132
|
-
const web = isRec(chunk) && isRec(chunk.web) ? chunk.web : undefined;
|
|
133
|
-
const uri = web && typeof web.uri === "string" ? web.uri : undefined;
|
|
134
|
-
if (!uri || seen.has(uri)) continue;
|
|
135
|
-
seen.add(uri);
|
|
136
|
-
sources.push({ url: uri, ...(typeof web?.title === "string" && web.title.length > 0 ? { title: web.title } : {}) });
|
|
137
|
-
}
|
|
138
|
-
}
|
|
157
|
+
const text = parts
|
|
158
|
+
.map(p => (isRec(p) && typeof p.text === "string" && !isCcaSearchSuggestionHtml(p.text) ? p.text : ""))
|
|
159
|
+
.join("");
|
|
160
|
+
const sources = extractCcaGroundingSources(candidate.groundingMetadata);
|
|
139
161
|
if (text.length === 0) return { text: "", sources, error: "gemini sidecar returned no text" };
|
|
140
162
|
return { text, sources };
|
|
141
163
|
}
|