@yansigit/opencodex 2.31.1 → 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.
Files changed (36) hide show
  1. package/gui/dist/assets/{index-DJDp_XER.js → index-Cxt5fZMP.js} +14 -14
  2. package/gui/dist/index.html +1 -1
  3. package/package.json +1 -1
  4. package/src/adapters/base.ts +6 -0
  5. package/src/adapters/command-code-project-context.ts +377 -0
  6. package/src/adapters/command-code.ts +5 -1
  7. package/src/adapters/cursor/cursor-errors.ts +12 -0
  8. package/src/adapters/cursor/live-transport.ts +21 -0
  9. package/src/adapters/cursor/native-exec-bridge.ts +141 -0
  10. package/src/adapters/cursor/thread-continuity.ts +71 -0
  11. package/src/adapters/cursor.ts +90 -28
  12. package/src/adapters/google-http.ts +12 -2
  13. package/src/adapters/google-wire-compiler.ts +91 -2
  14. package/src/adapters/google.ts +83 -15
  15. package/src/config.ts +2 -0
  16. package/src/generated/compatibility-version.json +57 -25
  17. package/src/lab/subject/behavior-fingerprint.ts +1 -1
  18. package/src/oauth/anthropic-routing.ts +129 -219
  19. package/src/oauth/antigravity-routing.ts +165 -0
  20. package/src/oauth/cursor-routing.ts +252 -0
  21. package/src/oauth/index.ts +3 -0
  22. package/src/providers/cursor-pool.ts +3 -3
  23. package/src/routing/account-pool/affinity.ts +125 -0
  24. package/src/routing/account-pool/cooldown.ts +145 -0
  25. package/src/routing/account-pool/index.ts +45 -0
  26. package/src/routing/account-pool/resolve.ts +356 -0
  27. package/src/routing/account-pool/types.ts +32 -0
  28. package/src/routing/compatibility/behavior.ts +3 -0
  29. package/src/server/management/oauth-account-routes.ts +47 -12
  30. package/src/server/responses/core.ts +400 -72
  31. package/src/types/config.ts +8 -0
  32. package/src/types/provider.ts +7 -0
  33. package/src/types/request.ts +13 -3
  34. package/src/usage/log.ts +4 -0
  35. package/src/web-search/gemini-executor.ts +35 -13
  36. package/src/web-search/index.ts +85 -1
@@ -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
  /**
@@ -245,9 +251,13 @@ export interface OcxRequestOptions {
245
251
  /**
246
252
  * Responses `text.format` (json_schema / json_object), preserved for adapters whose
247
253
  * upstream wire has an equivalent. The openai-chat adapter re-nests it as chat
248
- * `response_format`, the exact inverse of responseFormatToText in src/chat/inbound.ts.
249
- * The native passthrough ignores it (it forwards `_rawBody.text` verbatim) and Kiro
250
- * keeps rejecting structured output via `_structuredOutput`.
254
+ * `response_format`, the exact inverse of responseFormatToText in src/chat/inbound.ts; the
255
+ * Google adapter lowers supported requests to Gemini JSON mode (`responseMimeType` /
256
+ * `responseSchema`) but skips requests with tools, Claude models, or image-capable models.
257
+ * The `openai-chat` adapter can omit it for models in `noStructuredOutputModels`; Kiro
258
+ * rejects structured output via `_structuredOutput`; and Cursor has no structured-output
259
+ * wire field and rejects the request before transport.
260
+ * Native passthrough does not consume this option and forwards `_rawBody.text` verbatim.
251
261
  */
252
262
  textFormat?: {
253
263
  type: "json_schema" | "json_object";
package/src/usage/log.ts CHANGED
@@ -28,6 +28,8 @@ export type AttemptRecoveryKind =
28
28
  | "key-429"
29
29
  | "rate-limit-429"
30
30
  | "anthropic-oauth-429"
31
+ | "cursor-oauth-auth"
32
+ | "cursor-oauth-429"
31
33
  | "image-413"
32
34
  | "opaque-blob-rejection"
33
35
  | "empty-completion";
@@ -218,6 +220,8 @@ const ATTEMPT_RECOVERY_KINDS = new Set<AttemptRecoveryKind>([
218
220
  "key-429",
219
221
  "rate-limit-429",
220
222
  "anthropic-oauth-429",
223
+ "cursor-oauth-auth",
224
+ "cursor-oauth-429",
221
225
  "image-413",
222
226
  "opaque-blob-rejection",
223
227
  "empty-completion",
@@ -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.map(p => (isRec(p) && typeof p.text === "string" ? p.text : "")).join("");
127
- const sources: WebSearchSource[] = [];
128
- const seen = new Set<string>();
129
- const gm = isRec(candidate.groundingMetadata) ? candidate.groundingMetadata : undefined;
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
  }
@@ -1,4 +1,4 @@
1
- import type { OcxConfig, OcxParsedRequest, OcxProviderConfig } from "../types";
1
+ import type { OcxConfig, OcxContentPart, OcxParsedRequest, OcxProviderConfig } from "../types";
2
2
  import { modelInList, toolChoiceToolPredicate } from "../types";
3
3
  import { isModelTextOnly } from "../vision";
4
4
  import type { SidecarSettings } from "./executor";
@@ -166,6 +166,86 @@ export function resolveSidecarBackend(
166
166
  return "openai";
167
167
  }
168
168
 
169
+ export interface CcaInTurnGrounding {
170
+ search: boolean;
171
+ urlContext: boolean;
172
+ }
173
+
174
+ const HTTP_URL_RE = /https?:\/\/[^\s<>"')\]]+/i;
175
+ const EXPLICIT_SIDECAR_BACKENDS = new Set<WebSearchBackendId>(["openai", "anthropic", "xai", "exa"]);
176
+
177
+ function isGemini3ModelId(modelId: string): boolean {
178
+ return /gemini-3/i.test(modelId);
179
+ }
180
+
181
+ /** Whether a planned media bridge will actually inject tools on this turn. */
182
+ export function mediaBridgeWillRun(
183
+ hasMediaPlan: boolean,
184
+ hasWebSearchPlan: boolean,
185
+ adapterRunsTurn: boolean,
186
+ isStreaming = true,
187
+ ): boolean {
188
+ return hasMediaPlan && isStreaming && (!hasWebSearchPlan || adapterRunsTurn);
189
+ }
190
+
191
+ function messageTextContainsHttpUrl(content: string | OcxContentPart[]): boolean {
192
+ if (typeof content === "string") return HTTP_URL_RE.test(content);
193
+ for (const part of content) {
194
+ if (part.type === "image") {
195
+ const url = part.imageUrl;
196
+ if (url && !url.startsWith("data:") && /^https?:/i.test(url)) return true;
197
+ continue;
198
+ }
199
+ if (part.type === "text" && HTTP_URL_RE.test(part.text ?? "")) return true;
200
+ }
201
+ return false;
202
+ }
203
+
204
+ /** True when request text carries http(s) URLs that become remote-url placeholders upstream. */
205
+ export function requestNeedsCcaUrlContext(parsed: OcxParsedRequest): boolean {
206
+ for (const msg of parsed.context.messages) {
207
+ if (msg.role === "user" || msg.role === "developer") {
208
+ if (messageTextContainsHttpUrl(msg.content)) return true;
209
+ continue;
210
+ }
211
+ if (msg.role === "toolResult" && typeof msg.content === "string" && HTTP_URL_RE.test(msg.content)) return true;
212
+ if (msg.role === "assistant") {
213
+ for (const part of msg.content) {
214
+ if (part.type === "text" && HTTP_URL_RE.test(part.text ?? "")) return true;
215
+ }
216
+ }
217
+ }
218
+ return false;
219
+ }
220
+
221
+ /**
222
+ * Antigravity Gemini in-turn google_search / url_context on the main routed fetch. Returns undefined
223
+ * when the request should use the web-search sidecar (Claude-on-CCA, explicit sidecar backend, or
224
+ * Gemini &lt;3 with Codex function tools, including media bridge tools that will be injected later).
225
+ */
226
+ export function resolveCcaInTurnGrounding(
227
+ config: OcxConfig,
228
+ parsed: OcxParsedRequest,
229
+ isPassthrough: boolean,
230
+ provider: OcxProviderConfig,
231
+ modelId: string,
232
+ hasMediaBridge = false,
233
+ ): CcaInTurnGrounding | undefined {
234
+ if (!parsed._webSearch || isPassthrough) return undefined;
235
+ if (provider.googleMode !== "cloud-code-assist") return undefined;
236
+ if (/claude/i.test(modelId)) return undefined;
237
+ if (!toolChoiceToolPredicate(parsed.options.toolChoice)(buildWebSearchTool())) return undefined;
238
+ const cfg = config.webSearchSidecar ?? {};
239
+ if (cfg.enabled === false) return undefined;
240
+ if (cfg.backend !== undefined && EXPLICIT_SIDECAR_BACKENDS.has(cfg.backend)) return undefined;
241
+ const hasCodexTools = (parsed.context.tools?.length ?? 0) > 0 || hasMediaBridge;
242
+ if (hasCodexTools && !isGemini3ModelId(modelId)) return undefined;
243
+ return {
244
+ search: true,
245
+ urlContext: requestNeedsCcaUrlContext(parsed),
246
+ };
247
+ }
248
+
169
249
  export interface SidecarPlan {
170
250
  /** Which executor runs the search. Anthropic does not require a forward provider. */
171
251
  backend: WebSearchBackendId;
@@ -215,7 +295,11 @@ export function planWebSearch(
215
295
  provider: OcxProviderConfig,
216
296
  modelId: string,
217
297
  openAiSidecar?: ResolvedOpenAiForwardSidecar,
298
+ // Core passes the potential media plan here so sidecar precedence can be resolved
299
+ // before deciding whether the media bridge actually runs.
300
+ hasMediaBridge = false,
218
301
  ): SidecarPlan | undefined {
302
+ if (resolveCcaInTurnGrounding(config, parsed, isPassthrough, provider, modelId, hasMediaBridge)) return undefined;
219
303
  if (!parsed._webSearch || isPassthrough) return undefined;
220
304
  if (!toolChoiceToolPredicate(parsed.options.toolChoice)(buildWebSearchTool())) return undefined;
221
305
  const cfg = config.webSearchSidecar ?? {};