@yansigit/opencodex 2.32.0 → 2.33.1

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 (142) hide show
  1. package/README.md +2 -2
  2. package/gui/dist/assets/index-CIDo4y4k.js +102 -0
  3. package/gui/dist/assets/index-DrSQdTRd.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/package.json +8 -5
  6. package/src/adapters/anthropic.ts +20 -6
  7. package/src/adapters/azure.ts +20 -4
  8. package/src/adapters/base.ts +3 -1
  9. package/src/adapters/command-code.ts +131 -17
  10. package/src/adapters/cursor/envelope-echo.ts +162 -0
  11. package/src/adapters/cursor/live-transport.ts +4 -2
  12. package/src/adapters/cursor/native-exec-fs.ts +13 -12
  13. package/src/adapters/cursor/native-exec-network.ts +3 -5
  14. package/src/adapters/cursor/native-exec-policy.ts +47 -0
  15. package/src/adapters/cursor/native-exec-shell.ts +13 -25
  16. package/src/adapters/cursor/native-exec.ts +18 -10
  17. package/src/adapters/cursor/protobuf-events.ts +186 -9
  18. package/src/adapters/cursor/protobuf-request.ts +53 -18
  19. package/src/adapters/cursor/request-builder.ts +11 -3
  20. package/src/adapters/cursor/tool-definitions.ts +49 -2
  21. package/src/adapters/cursor/tool-result-normalize.ts +21 -8
  22. package/src/adapters/cursor/types.ts +11 -3
  23. package/src/adapters/cursor.ts +123 -0
  24. package/src/adapters/google-aistudio-parser.ts +49 -0
  25. package/src/adapters/google-antigravity-replay.ts +2 -2
  26. package/src/adapters/google-antigravity-wire.ts +7 -0
  27. package/src/adapters/google-errors.ts +6 -2
  28. package/src/adapters/google-http.ts +30 -7
  29. package/src/adapters/google-truncation.ts +5 -0
  30. package/src/adapters/google-wire-compiler.ts +38 -6
  31. package/src/adapters/google.ts +242 -28
  32. package/src/adapters/kiro-tools.ts +20 -9
  33. package/src/adapters/openai-chat.ts +9 -0
  34. package/src/adapters/openai-responses.ts +2 -1
  35. package/src/bridge.ts +112 -9
  36. package/src/chat/inbound.ts +15 -0
  37. package/src/claude/context-windows.ts +16 -9
  38. package/src/cli/doctor.ts +2 -2
  39. package/src/cli/index.ts +10 -3
  40. package/src/cli/provider.ts +6 -0
  41. package/src/cli/status.ts +23 -0
  42. package/src/codex/auth-api.ts +4 -2
  43. package/src/codex/autostart-health.ts +16 -0
  44. package/src/codex/catalog/aggregation.ts +12 -12
  45. package/src/codex/catalog/effort.ts +18 -3
  46. package/src/codex/catalog/metadata.ts +27 -1
  47. package/src/codex/catalog/model-metadata.ts +39 -12
  48. package/src/codex/catalog/parsing.ts +38 -27
  49. package/src/codex/catalog/provider-fetch.ts +232 -133
  50. package/src/codex/catalog/sync.ts +1 -1
  51. package/src/codex/convergence.ts +5 -0
  52. package/src/codex/shim.ts +56 -3
  53. package/src/config/provider-validation.ts +37 -0
  54. package/src/config.ts +78 -2
  55. package/src/generated/compatibility-version.json +187 -119
  56. package/src/generated/model-metadata.ts +3 -0
  57. package/src/images/loop.ts +37 -6
  58. package/src/lib/azure-identity.ts +154 -0
  59. package/src/lib/debug.ts +42 -0
  60. package/src/lib/errors.ts +14 -0
  61. package/src/lib/provider-outbound.ts +45 -33
  62. package/src/lib/provider-tls-profile.ts +309 -0
  63. package/src/lib/proxy-env.ts +49 -0
  64. package/src/lib/redact.ts +10 -1
  65. package/src/oauth/aistudio-native-daemon.ts +62 -0
  66. package/src/oauth/aistudio-session-sync.ts +95 -0
  67. package/src/oauth/antigravity-routing.ts +282 -236
  68. package/src/oauth/callback-server.ts +22 -2
  69. package/src/oauth/command-code.ts +5 -16
  70. package/src/oauth/google-aistudio-auth.ts +98 -0
  71. package/src/oauth/google-antigravity.ts +42 -5
  72. package/src/oauth/index.ts +15 -3
  73. package/src/oauth/key-providers.ts +8 -0
  74. package/src/oauth/kimi.ts +9 -1
  75. package/src/oauth/login-cli.ts +66 -1
  76. package/src/oauth/open-browser-choice.ts +26 -0
  77. package/src/oauth/store.ts +6 -0
  78. package/src/providers/antigravity-quota.ts +3 -1
  79. package/src/providers/api-keys.ts +2 -1
  80. package/src/providers/auto-compact-budget.ts +65 -0
  81. package/src/providers/derive.ts +5 -1
  82. package/src/providers/key-failover.ts +5 -1
  83. package/src/providers/openai-tiers.ts +5 -0
  84. package/src/providers/provider-id-rewrite.ts +1 -0
  85. package/src/providers/quota.ts +148 -50
  86. package/src/providers/registry.ts +27 -4
  87. package/src/providers/request-pacing.ts +33 -6
  88. package/src/providers/xai-transport.ts +21 -0
  89. package/src/responses/google-provider-options.ts +36 -0
  90. package/src/responses/namespace-tool-compat.ts +84 -4
  91. package/src/responses/parser.ts +11 -0
  92. package/src/responses/provider-opaque-metadata.ts +3 -3
  93. package/src/responses/schema.ts +37 -0
  94. package/src/responses/state.ts +94 -4
  95. package/src/router.ts +11 -2
  96. package/src/routing/account-pool/cooldown.ts +8 -0
  97. package/src/routing/account-pool/index.ts +1 -0
  98. package/src/server/aistudio-ws-hub.ts +295 -0
  99. package/src/server/auth-cors.ts +29 -0
  100. package/src/server/chat-completions.ts +2 -0
  101. package/src/server/images.ts +19 -35
  102. package/src/server/index.ts +94 -0
  103. package/src/server/management/agent-settings-routes.ts +205 -15
  104. package/src/server/management/combo-routes.ts +6 -0
  105. package/src/server/management/config-routes.ts +31 -5
  106. package/src/server/management/logs-usage-routes.ts +11 -5
  107. package/src/server/management/model-rows.ts +4 -0
  108. package/src/server/management/oauth-account-routes.ts +38 -7
  109. package/src/server/management/provider-routes.ts +113 -15
  110. package/src/server/management/routing-profile-routes.ts +3 -0
  111. package/src/server/port-reclaim.ts +19 -1
  112. package/src/server/request-log-conversation.ts +12 -0
  113. package/src/server/request-log.ts +23 -1
  114. package/src/server/responses/agent-task-recovery.ts +1 -1
  115. package/src/server/responses/compact.ts +30 -1
  116. package/src/server/responses/core.ts +363 -156
  117. package/src/server/responses/empty-completion-guard.ts +35 -6
  118. package/src/server/responses/fetch-helpers.ts +18 -5
  119. package/src/server/responses/policy-fallback.ts +1 -1
  120. package/src/server/responses/v2-native-parent-override.ts +59 -0
  121. package/src/server/responses/ws-upstream.ts +75 -2
  122. package/src/server/responses-undeclared-tool-guard.ts +90 -8
  123. package/src/server/ws-bridge.ts +2 -1
  124. package/src/service.ts +1 -1
  125. package/src/smoke/fingerprint-cache.ts +133 -0
  126. package/src/smoke/live-scenarios.ts +33 -0
  127. package/src/smoke/runner.ts +119 -0
  128. package/src/types/config.ts +16 -1
  129. package/src/types/provider.ts +18 -1
  130. package/src/types/request.ts +30 -0
  131. package/src/types/tools.ts +51 -0
  132. package/src/types.ts +6 -0
  133. package/src/usage/command-code-manifest.ts +116 -0
  134. package/src/usage/cost.ts +2 -2
  135. package/src/usage/expected-prices.ts +83 -0
  136. package/src/usage/log.ts +2 -2
  137. package/src/usage/summary.ts +34 -12
  138. package/src/web-search/gemini-executor.ts +6 -4
  139. package/src/web-search/index.ts +16 -8
  140. package/src/web-search/loop.ts +42 -6
  141. package/gui/dist/assets/index-BG43zwVe.js +0 -102
  142. package/gui/dist/assets/index-CiSI-jrP.css +0 -1
@@ -1,7 +1,8 @@
1
- import type { AdapterFetchContext, AdapterRequest, ProviderAdapter } from "./base";
1
+ import type { AdapterFetchContext, AdapterRequest, IncomingMeta, ProviderAdapter } from "./base";
2
2
  import { debugDroppedFrame } from "../lib/debug";
3
3
  import { createToolCallIdAllocator } from "./tool-call-id";
4
4
  import { createImageBudget, materializeInlineImage, MAX_ENCODED_BYTES_PER_IMAGE, artifactHttpUrl } from "../images/artifacts";
5
+ import { OcxRequestValidationError } from "../lib/errors";
5
6
  import type {
6
7
  AdapterEvent,
7
8
  OcxAssistantMessage,
@@ -19,8 +20,9 @@ import { contentPartsToText, parseDataUrl } from "./image";
19
20
  import { getVertexAccessToken } from "../lib/gcp-adc";
20
21
  import { fetchAntigravityWithRetry, fetchVertexWithRetry } from "./google-http";
21
22
  import { safeAntigravityHttpErrorMessage, safeVertexHttpErrorMessage } from "./google-errors";
22
- import { isVertexTruncatedTurn, vertexTruncationErrorMessage } from "./google-truncation";
23
- import { ANTIGRAVITY_REQUEST_UA, antigravitySessionId, isLikelyRealThoughtSignature, sanitizeAntigravityClaudeSignatures } from "./google-antigravity-wire";
23
+ import { sanitizeUpstreamErrorText } from "./upstream-http-error";
24
+ import { googleTruncationErrorMessage, isVertexTruncatedTurn, vertexTruncationErrorMessage } from "./google-truncation";
25
+ import { ANTIGRAVITY_REQUEST_UA, ANTIGRAVITY_SIGNATURE_BYPASS_SENTINEL, antigravitySessionId, isLikelyRealThoughtSignature, sanitizeAntigravityClaudeSignatures } from "./google-antigravity-wire";
24
26
  import { repairGoogleToolPairs, stripTrailingClaudePrefill } from "./google-antigravity-tools";
25
27
  import { canonicalAntigravityHttpsHost, isAntigravityHttpsHost } from "./google-antigravity-hosts";
26
28
  import { compileGoogleWireBody } from "./google-wire-compiler";
@@ -45,6 +47,20 @@ import {
45
47
  } from "../lib/translator-budget";
46
48
  import { buildNonOpenAIToolCatalogNudgeForTools } from "./tool-catalog-nudge";
47
49
  import { configuredReasoningEfforts, mapReasoningEffort } from "../reasoning-effort";
50
+ import { normalizeAntigravityProviderError } from "../oauth/antigravity-routing";
51
+ import { buildAiStudioHeaders, parseGoogleCookieJar } from "../oauth/google-aistudio-auth";
52
+ import { cookieHeaderFromSession, loadAiStudioSession } from "../oauth/aistudio-session-sync";
53
+ import { parseMakerSuiteChunk } from "./google-aistudio-parser";
54
+ import { globalAiStudioRelayHub } from "../server/aistudio-ws-hub";
55
+
56
+ const INLINE_ERROR_URL_USERINFO = /https?:\/\/[^\s"'<>]*@/gi;
57
+
58
+ function safeAntigravityInlineErrorMessage(value: unknown): string | undefined {
59
+ if (typeof value !== "string") return undefined;
60
+ return sanitizeUpstreamErrorText(value)
61
+ .replace(INLINE_ERROR_URL_USERINFO, "[REDACTED_URL]")
62
+ .slice(0, 500);
63
+ }
48
64
 
49
65
  // Google-family models (Gemini/Vertex/Antigravity) tend to emit long running commentary between
50
66
  // tool calls. This steers them to keep the BETWEEN-STEP text to one line and reason internally
@@ -139,6 +155,19 @@ const GEMINI_EMPTY_PLACEHOLDER = "(empty)";
139
155
  const GEMINI_EMPTY_TOOL_OUTPUT_PLACEHOLDER = "(empty tool output)";
140
156
  const GEMINI_MISSING_TOOL_RESULT = "[missing tool_result for this tool_use in history]";
141
157
 
158
+ function appendGeminiContent(
159
+ contents: unknown[],
160
+ next: { role: string; parts: unknown[] },
161
+ mergeAdjacentUsers = true,
162
+ ): void {
163
+ const previous = contents.at(-1) as { role?: unknown; parts?: unknown[] } | undefined;
164
+ if (mergeAdjacentUsers && previous?.role === "user" && next.role === "user" && Array.isArray(previous.parts)) {
165
+ previous.parts.push(...next.parts);
166
+ } else {
167
+ contents.push(next);
168
+ }
169
+ }
170
+
142
171
  /** A Gemini text part, or undefined when the value cannot form a valid non-empty text block. */
143
172
  function geminiTextPart(text: unknown): { text: string } | undefined {
144
173
  return typeof text === "string" && text.length > 0 ? { text } : undefined;
@@ -208,6 +237,11 @@ function messagesToGeminiFormat(
208
237
  const systemInstruction = { parts: [{ text: systemText }] };
209
238
 
210
239
  const contents: unknown[] = [];
240
+ let userMergeBarrier = false;
241
+ const appendContent = (next: { role: string; parts: unknown[] }): void => {
242
+ appendGeminiContent(contents, next, !userMergeBarrier);
243
+ userMergeBarrier = false;
244
+ };
211
245
  const messages = repairGoogleToolPairs(parsed.context.messages, { dropUnmatchedCalls: repairToolPairs });
212
246
 
213
247
  const callIds = createToolCallIdAllocator();
@@ -226,7 +260,7 @@ function messagesToGeminiFormat(
226
260
  case "user":
227
261
  case "developer": {
228
262
  if (typeof msg.content === "string") {
229
- contents.push({ role: "user", parts: [{ text: msg.content || GEMINI_EMPTY_PLACEHOLDER }] });
263
+ appendContent({ role: "user", parts: [{ text: msg.content || GEMINI_EMPTY_PLACEHOLDER }] });
230
264
  } else {
231
265
  const parts: unknown[] = [];
232
266
  for (const p of msg.content as OcxContentPart[]) {
@@ -241,7 +275,7 @@ function messagesToGeminiFormat(
241
275
  const textPart = geminiTextPart(p.text);
242
276
  if (textPart) parts.push(textPart);
243
277
  }
244
- contents.push({ role: "user", parts: parts.length > 0 ? parts : [{ text: GEMINI_EMPTY_PLACEHOLDER }] });
278
+ appendContent({ role: "user", parts: parts.length > 0 ? parts : [{ text: GEMINI_EMPTY_PLACEHOLDER }] });
245
279
  }
246
280
  break;
247
281
  }
@@ -291,8 +325,11 @@ function messagesToGeminiFormat(
291
325
  // A turn with nothing Gemini can represent (e.g. thinking-only) would serialize as
292
326
  // `parts: []`, which the Anthropic translation rejects. Skip it, as the Anthropic
293
327
  // adapter does for its own empty assistant content.
294
- if (parts.length === 0) break;
295
- contents.push({ role: "model", parts });
328
+ if (parts.length === 0) {
329
+ userMergeBarrier = true;
330
+ break;
331
+ }
332
+ appendContent({ role: "model", parts });
296
333
  if (toolCalls.length > 0) {
297
334
  // Gemini/Claude-on-Antigravity requires one adjacent response batch for the whole
298
335
  // function-call turn. Replayed histories can be interrupted, reversed, duplicated, or
@@ -321,7 +358,7 @@ function messagesToGeminiFormat(
321
358
  for (const orphan of orphanResults) {
322
359
  responseParts.push(...geminiOrphanToolResultParts(orphan));
323
360
  }
324
- contents.push({ role: "user", parts: responseParts });
361
+ appendContent({ role: "user", parts: responseParts });
325
362
  i = j - 1;
326
363
  }
327
364
  break;
@@ -332,7 +369,7 @@ function messagesToGeminiFormat(
332
369
  // batch never reach here (the assistant branch consumes them). Standalone or
333
370
  // barrier-delayed results still have to stay visible — especially image-bearing
334
371
  // screenshots — as explicit user text rather than vanishing or 400ing CCA.
335
- contents.push({ role: "user", parts: geminiOrphanToolResultParts(msg as OcxToolResultMessage) });
372
+ appendContent({ role: "user", parts: geminiOrphanToolResultParts(msg as OcxToolResultMessage) });
336
373
  break;
337
374
  }
338
375
  }
@@ -395,6 +432,13 @@ function usageFromGemini(usage: Record<string, number> | undefined): OcxUsage |
395
432
  };
396
433
  }
397
434
 
435
+ function googlePromptFeedbackError(root: Record<string, unknown>): Extract<AdapterEvent, { type: "error" }> | undefined {
436
+ const feedback = root.promptFeedback;
437
+ if (!isGoogleRecord(feedback) || typeof feedback.blockReason !== "string" || !feedback.blockReason.trim()) return undefined;
438
+ const reason = sanitizeUpstreamErrorText(feedback.blockReason).slice(0, 160);
439
+ return { type: "error", message: `google response blocked by prompt feedback: ${reason}` };
440
+ }
441
+
398
442
  /**
399
443
  * Cap on the buffered non-streaming response body (100 MiB), matching
400
444
  * IMAGES_RESPONSE_MAX_BYTES in src/server/images.ts. Enforced by streaming the
@@ -500,6 +544,21 @@ function googlePartThoughtSignature(part: GoogleResponsePart): string | undefine
500
544
  return typeof nested === "string" && nested.length > 0 ? nested : undefined;
501
545
  }
502
546
 
547
+ function ensureThoughtSignatureBypassSentinel(contents: unknown[], modelId?: string): void {
548
+ if (modelId && !/gemini-(?:3.7|2.5)|thinking/i.test(modelId)) return;
549
+ for (const c of contents as { role?: string; parts?: unknown[] }[]) {
550
+ if (c?.role !== "model" || !Array.isArray(c.parts)) continue;
551
+ for (const p of c.parts) {
552
+ if (p && typeof p === "object") {
553
+ const partObj = p as Record<string, unknown>;
554
+ if (partObj.functionCall && !partObj.thoughtSignature && !partObj.thought_signature) {
555
+ partObj.thoughtSignature = ANTIGRAVITY_SIGNATURE_BYPASS_SENTINEL;
556
+ }
557
+ }
558
+ }
559
+ }
560
+ }
561
+
503
562
  /**
504
563
  * Carry a Gemini thought signature with the exact function-call part that produced it. Google
505
564
  * validates the signature against that specific part, so it must ride the individual tool call
@@ -672,6 +731,7 @@ function invalidGoogleShapeEvent(
672
731
  export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapter {
673
732
  // Per-request closure: resolveAdapter builds a fresh adapter per request (server.ts), so buildRequest
674
733
  // can stash the CCA model/session for parseStream's reasoning-replay observation.
734
+ let observeProviderError: IncomingMeta["onProviderError"];
675
735
  let antigravityModel: string | undefined;
676
736
  let antigravitySession: string | undefined;
677
737
  // Vertex returns the same opaque Gemini thought signatures as CCA, but its replay namespace
@@ -681,14 +741,68 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
681
741
  let vertexReplaySession: string | undefined;
682
742
  let restoreGoogleToolName = (name: string): string => name;
683
743
  const emitInTurnGroundingSourcesQueue: boolean[] = [];
744
+ const truncationErrorMessage = provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist"
745
+ ? vertexTruncationErrorMessage
746
+ : googleTruncationErrorMessage;
684
747
  return {
685
748
  name: "google",
749
+ validateRequest(parsed: OcxParsedRequest) {
750
+ if (provider.googleMode === "cloud-code-assist" && parsed.options.providerOptions?.google) {
751
+ throw new OcxRequestValidationError("provider_options.google is not supported on Google Cloud Code Assist routes");
752
+ }
753
+ const googleOptions = parsed.options.providerOptions?.google;
754
+ if (isImageCapableModel(parsed.modelId)
755
+ && (googleOptions?.thinkingBudget !== undefined || googleOptions?.includeThoughts !== undefined)) {
756
+ throw new OcxRequestValidationError(
757
+ "provider_options.google thinking_budget and include_thoughts are not supported for image-capable Gemini models",
758
+ );
759
+ }
760
+ },
686
761
 
687
762
  // Vertex + Antigravity get Kiro-style retry/timeout + classified, redacted errors.
688
763
  // Direct AI-Studio uses the canonical server transport (fetchWithTransientRetry), which
689
764
  // retries transient 5xx responses through providerFetch while preserving multi-key pool
690
765
  // 429 rotation and raw error formatting.
691
- ...(provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist"
766
+ ...(provider.googleMode === "ai-studio-web"
767
+ ? {
768
+ fetchResponse: async (request: AdapterRequest, ctx?: AdapterFetchContext): Promise<Response> => {
769
+ if (globalAiStudioRelayHub.hasActiveSessions()) {
770
+ const streamRes = await globalAiStudioRelayHub.dispatchStream(
771
+ {
772
+ url: request.url,
773
+ method: request.method,
774
+ headers: request.headers,
775
+ body: request.body,
776
+ },
777
+ ctx?.abortSignal,
778
+ );
779
+ const encoder = new TextEncoder();
780
+ const bodyStream = new ReadableStream({
781
+ async start(controller) {
782
+ try {
783
+ for await (const chunk of streamRes.chunks) {
784
+ controller.enqueue(encoder.encode(chunk));
785
+ }
786
+ controller.close();
787
+ } catch (err) {
788
+ controller.error(err);
789
+ }
790
+ },
791
+ });
792
+ return new Response(bodyStream, {
793
+ status: 200,
794
+ headers: { "Content-Type": "text/event-stream" },
795
+ });
796
+ }
797
+ return fetch(request.url, {
798
+ method: request.method,
799
+ headers: request.headers,
800
+ body: request.body,
801
+ signal: ctx?.abortSignal,
802
+ });
803
+ },
804
+ }
805
+ : provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist"
692
806
  ? {
693
807
  fetchResponse: (request: AdapterRequest, ctx?: AdapterFetchContext): Promise<Response> =>
694
808
  (provider.googleMode === "cloud-code-assist" ? fetchAntigravityWithRetry : fetchVertexWithRetry)(request, ctx),
@@ -697,7 +811,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
697
811
  }
698
812
  : {}),
699
813
 
700
- async buildRequest(parsed: OcxParsedRequest) {
814
+ async buildRequest(parsed: OcxParsedRequest, options?: IncomingMeta) {
815
+ observeProviderError = options?.onProviderError;
701
816
  const routedModelId = provider.googleMode === "cloud-code-assist"
702
817
  ? resolveAntigravityEffortWireModel(
703
818
  parsed.modelId,
@@ -723,6 +838,9 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
723
838
  // catalog is a guaranteed upstream 400.
724
839
  const toolConfig = tools ? toolChoiceToGeminiToolConfig(parsed) : undefined;
725
840
  if (toolConfig) body.toolConfig = toolConfig;
841
+ const googleOptions = parsed.options.providerOptions?.google;
842
+ if (googleOptions?.safetySettings) body.safetySettings = googleOptions.safetySettings;
843
+ if (googleOptions?.cachedContent) body.cachedContent = googleOptions.cachedContent;
726
844
 
727
845
  const generationConfig: Record<string, unknown> = {};
728
846
  if (parsed.options.maxOutputTokens) generationConfig.maxOutputTokens = parsed.options.maxOutputTokens;
@@ -744,7 +862,13 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
744
862
  const thinkingLevel = thinkingEligible
745
863
  ? mapReasoningEffort(provider, parsed.modelId, parsed.options.reasoning)
746
864
  : undefined;
747
- if (thinkingLevel) generationConfig.thinkingConfig = { thinkingLevel };
865
+ if (!isImageCapableModel(parsed.modelId)) {
866
+ const thinkingConfig: Record<string, unknown> = {};
867
+ if (googleOptions?.thinkingBudget !== undefined) thinkingConfig.thinkingBudget = googleOptions.thinkingBudget;
868
+ else if (thinkingLevel) thinkingConfig.thinkingLevel = thinkingLevel;
869
+ if (googleOptions?.includeThoughts !== undefined) thinkingConfig.includeThoughts = googleOptions.includeThoughts;
870
+ if (Object.keys(thinkingConfig).length > 0) generationConfig.thinkingConfig = thinkingConfig;
871
+ }
748
872
  if (!generationConfig.thinkingConfig && isImageCapableModel(parsed.modelId)) {
749
873
  generationConfig.responseModalities = ["TEXT", "IMAGE"];
750
874
  }
@@ -816,6 +940,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
816
940
  const strippedModelTail = /claude/i.test(wireModelId) ? stripTrailingClaudePrefill(contents) : false;
817
941
  if (antigravityUsesReplayCache(wireModelId)) {
818
942
  applyAntigravityReplay(wireModelId, sessionId, contents);
943
+ ensureThoughtSignatureBypassSentinel(contents);
819
944
  } else {
820
945
  sanitizeAntigravityClaudeSignatures(contents);
821
946
  }
@@ -891,6 +1016,22 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
891
1016
  return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
892
1017
  }
893
1018
 
1019
+ if (provider.googleMode === "ai-studio-web") {
1020
+ const base = (provider.baseUrl || "https://alkalimakersuite-pa.clients6.google.com").replace(/\/+$/, "");
1021
+ const url = `${base}/v1internal:${method}${streamParam}`;
1022
+ const cookieInput = provider.apiKey || provider.headers?.["Cookie"] || cookieHeaderFromSession(loadAiStudioSession()) || "";
1023
+ const jar = parseGoogleCookieJar(cookieInput);
1024
+ const aiStudioHeaders = await buildAiStudioHeaders(jar, "https://aistudio.google.com");
1025
+ Object.assign(headers, aiStudioHeaders);
1026
+ const compiled = compileGoogleWireBody({ ...body, model: routedModelId });
1027
+ restoreGoogleToolName = compiled.restoreToolName;
1028
+ if (Array.isArray((compiled.body as { contents?: unknown[] }).contents)) {
1029
+ ensureThoughtSignatureBypassSentinel((compiled.body as { contents: unknown[] }).contents, routedModelId);
1030
+ }
1031
+ emitInTurnGroundingSourcesQueue.push(!!parsed._ccaInTurnGrounding);
1032
+ return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
1033
+ }
1034
+
894
1035
  // ai-studio (default): Generative Language API + x-goog-api-key.
895
1036
  const url = `${provider.baseUrl}/v1beta/models/${routedModelId}:${method}${streamParam}`;
896
1037
  const apiKey = provider.apiKey?.trim();
@@ -899,6 +1040,9 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
899
1040
 
900
1041
  const compiled = compileGoogleWireBody(body);
901
1042
  restoreGoogleToolName = compiled.restoreToolName;
1043
+ if (Array.isArray((compiled.body as { contents?: unknown[] }).contents)) {
1044
+ ensureThoughtSignatureBypassSentinel((compiled.body as { contents: unknown[] }).contents, routedModelId);
1045
+ }
902
1046
  emitInTurnGroundingSourcesQueue.push(!!parsed._ccaInTurnGrounding);
903
1047
  return { url, method: "POST", headers, body: JSON.stringify(compiled.body) };
904
1048
  },
@@ -970,17 +1114,31 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
970
1114
 
971
1115
  // Inline provider error inside a 200 stream → terminal error (see openai-chat.ts).
972
1116
  if (chunk.error) {
973
- const err = chunk.error as { message?: string } | undefined;
1117
+ const rawError = chunk.error as { code?: unknown; status?: unknown; message?: unknown };
1118
+ const safeMessage = safeAntigravityInlineErrorMessage(rawError.message);
1119
+ const error = normalizeAntigravityProviderError({
1120
+ code: rawError.code,
1121
+ status: rawError.status,
1122
+ message: safeMessage,
1123
+ });
1124
+ if (provider.googleMode === "cloud-code-assist" && error) observeProviderError?.(error);
1125
+ const err = { ...(error ?? {}), message: error?.message ?? safeMessage ?? "upstream error" };
974
1126
  // Clear-on-invalid: a signature rejection means our replayed thoughtSignatures are stale.
975
1127
  // Drop the cache entry so the next turn starts clean instead of re-injecting a bad sig.
976
1128
  const replayModel = provider.googleMode === "cloud-code-assist" ? antigravityModel : vertexReplayModel;
977
1129
  const replaySession = provider.googleMode === "cloud-code-assist" ? antigravitySession : vertexReplaySession;
978
1130
  if ((provider.googleMode === "cloud-code-assist" || provider.googleMode === "vertex")
979
1131
  && replayModel && replaySession
1132
+ && !/missing.*thought_signature/i.test(err?.message ?? "")
980
1133
  && /signature|invalid_argument|invalid argument/i.test(err?.message ?? "")) {
981
1134
  clearAntigravityReplay(replayModel, replaySession);
982
1135
  }
983
- yield { type: "error", message: err?.message ?? "upstream error" };
1136
+ yield {
1137
+ type: "error",
1138
+ ...(error?.status !== undefined ? { status: error.status } : {}),
1139
+ ...(error?.code ? { code: error.code } : {}),
1140
+ message: err.message ?? "upstream error",
1141
+ };
984
1142
  return "terminate";
985
1143
  }
986
1144
 
@@ -1010,7 +1168,14 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
1010
1168
  // absent key and an empty array are already skipped here; `null` joins them. A non-null
1011
1169
  // non-array container is still claimed structure the parser cannot read, and stays
1012
1170
  // terminal.
1013
- if (rawCandidates === undefined || rawCandidates === null) return "continue";
1171
+ if (rawCandidates === undefined || rawCandidates === null) {
1172
+ const feedbackError = googlePromptFeedbackError(root);
1173
+ if (feedbackError) {
1174
+ yield feedbackError;
1175
+ return "terminate";
1176
+ }
1177
+ return "continue";
1178
+ }
1014
1179
  if (!Array.isArray(rawCandidates)) {
1015
1180
  yield invalidGoogleShapeEvent({
1016
1181
  reason: "candidates_not_array",
@@ -1018,7 +1183,14 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
1018
1183
  });
1019
1184
  return "terminate";
1020
1185
  }
1021
- if (rawCandidates.length === 0) return "continue";
1186
+ if (rawCandidates.length === 0) {
1187
+ const feedbackError = googlePromptFeedbackError(root);
1188
+ if (feedbackError) {
1189
+ yield feedbackError;
1190
+ return "terminate";
1191
+ }
1192
+ return "continue";
1193
+ }
1022
1194
  const rawCandidate = rawCandidates[0];
1023
1195
  if (!isGoogleRecord(rawCandidate)) {
1024
1196
  // Unlike a root `data: null` keepalive, this is a claimed response candidate. Treat it
@@ -1086,7 +1258,7 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
1086
1258
  if (parts) {
1087
1259
  for (const part of parts) {
1088
1260
  const sig = googlePartThoughtSignature(part);
1089
- if (part.thought === true && sig && isLikelyRealThoughtSignature(sig)) {
1261
+ if (sig && isLikelyRealThoughtSignature(sig) && (part.thought === true || !part.functionCall)) {
1090
1262
  pendingStreamThoughtSig = sig;
1091
1263
  }
1092
1264
  const textEvent = googlePartTextEvent(part, filterCcaSearchSuggestionHtml);
@@ -1173,6 +1345,16 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
1173
1345
  if (result === "content") sawContentEvent = true;
1174
1346
  continue;
1175
1347
  }
1348
+ if (provider.googleMode === "ai-studio-web") {
1349
+ const parsed = parseMakerSuiteChunk(line);
1350
+ if (parsed.text) {
1351
+ sawAnyFrame = true;
1352
+ sawTerminalSignal = true;
1353
+ sawContentEvent = true;
1354
+ yield { type: "text_delta", text: parsed.text };
1355
+ continue;
1356
+ }
1357
+ }
1176
1358
  sawLiveness = true;
1177
1359
  if (line.startsWith(":") || !line.trim()) continue;
1178
1360
  debugDroppedFrame("google", line);
@@ -1185,15 +1367,31 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
1185
1367
  if (residual.startsWith(":")) {
1186
1368
  yield { type: "heartbeat" };
1187
1369
  } else if (!residual.startsWith("data:")) {
1188
- yield { type: "error", message: "upstream stream ended with an incomplete SSE frame — possible truncation" };
1370
+ try {
1371
+ const parsedErr = JSON.parse(residual);
1372
+ if (parsedErr.error?.message) {
1373
+ yield { type: "error", message: parsedErr.error.message };
1374
+ return;
1375
+ }
1376
+ } catch (err) {
1377
+ void err;
1378
+ }
1379
+ if (provider.googleMode === "ai-studio-web") {
1380
+ const parsed = parseMakerSuiteChunk(residual);
1381
+ if (parsed.text) {
1382
+ yield { type: "text_delta", text: parsed.text };
1383
+ yield { type: "done" };
1384
+ return;
1385
+ }
1386
+ }
1387
+ yield { type: "error", message: `upstream non-SSE response: ${residual.slice(0, 300)}` };
1189
1388
  return;
1190
1389
  } else if ((yield* handleDataLine(residual)) === "terminate") return;
1191
1390
  }
1192
1391
  // Fail-closed: a turn cut off mid tool call (MAX_TOKENS / MALFORMED_FUNCTION_CALL) surfaces
1193
1392
  // an error instead of a silently-incomplete done. Mirrors kiro-truncation.
1194
- if ((provider.googleMode === "vertex" || provider.googleMode === "cloud-code-assist")
1195
- && isVertexTruncatedTurn(lastFinishReason, toolCallsStarted)) {
1196
- yield { type: "error", message: vertexTruncationErrorMessage(lastFinishReason) };
1393
+ if (isVertexTruncatedTurn(lastFinishReason, toolCallsStarted)) {
1394
+ yield { type: "error", message: truncationErrorMessage(lastFinishReason) };
1197
1395
  return;
1198
1396
  }
1199
1397
  if (!sawAnyFrame || !sawTerminalSignal) {
@@ -1237,7 +1435,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
1237
1435
  // Cloud Code Assist exposes only the SSE transport. Unary callers still use this
1238
1436
  // buffered adapter entry point, so collect the exact same events parseStream emits
1239
1437
  // instead of maintaining a second CCA JSON parser.
1240
- if (provider.googleMode === "cloud-code-assist") {
1438
+ const isSse = response.headers.get("content-type")?.includes("text/event-stream") ?? false;
1439
+ if ((provider.googleMode === "cloud-code-assist" && isSse) || provider.googleMode === "ai-studio-web") {
1241
1440
  const events: AdapterEvent[] = [];
1242
1441
  let previousTail: AdapterEvent | undefined;
1243
1442
  try {
@@ -1326,10 +1525,24 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
1326
1525
  return events;
1327
1526
  };
1328
1527
  if (raw.error) {
1329
- const err = raw.error as { message?: string };
1330
- return finish([{ type: "error", message: err.message ?? "upstream error" }]);
1528
+ const rawError = raw.error as { code?: unknown; status?: unknown; message?: unknown };
1529
+ const safeMessage = safeAntigravityInlineErrorMessage(rawError.message);
1530
+ const error = normalizeAntigravityProviderError({
1531
+ code: rawError.code,
1532
+ status: rawError.status,
1533
+ message: safeMessage,
1534
+ });
1535
+ if (error) observeProviderError?.(error);
1536
+ return finish([{
1537
+ type: "error",
1538
+ ...(error?.status !== undefined ? { status: error.status } : {}),
1539
+ ...(error?.code ? { code: error.code } : {}),
1540
+ message: error?.message ?? safeMessage ?? "upstream error",
1541
+ }]);
1331
1542
  }
1332
- const json = raw;
1543
+ const json = (provider.googleMode === "cloud-code-assist" && raw.response && typeof raw.response === "object" && !Array.isArray(raw.response))
1544
+ ? (raw.response as Record<string, unknown>)
1545
+ : raw;
1333
1546
  const events: AdapterEvent[] = [];
1334
1547
 
1335
1548
  const rawCandidates: unknown = json.candidates;
@@ -1345,6 +1558,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
1345
1558
  }
1346
1559
  const candidates = rawCandidates as { finishReason?: string }[] | undefined;
1347
1560
  if (!candidates?.length) {
1561
+ const feedbackError = googlePromptFeedbackError(json);
1562
+ if (feedbackError) return finish([feedbackError]);
1348
1563
  return finish([{ type: "error", message: "google response contained no candidates" }]);
1349
1564
  }
1350
1565
  const rawCandidate: unknown = candidates[0];
@@ -1417,9 +1632,8 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
1417
1632
 
1418
1633
  // Fail-closed truncation, same as the stream path: a non-stream turn cut off mid tool call
1419
1634
  // (MAX_TOKENS / MALFORMED_FUNCTION_CALL) surfaces an error instead of a silent done.
1420
- if (provider.googleMode === "vertex"
1421
- && isVertexTruncatedTurn(candidate.finishReason, toolCallsStarted)) {
1422
- return finish([{ type: "error", message: vertexTruncationErrorMessage(candidate.finishReason) }]);
1635
+ if (isVertexTruncatedTurn(candidate.finishReason, toolCallsStarted)) {
1636
+ return finish([{ type: "error", message: truncationErrorMessage(candidate.finishReason) }]);
1423
1637
  }
1424
1638
 
1425
1639
  const usage = json.usageMetadata as Record<string, number> | undefined;
@@ -172,6 +172,12 @@ function omittedToolCatalogNotice(kept: number, omitted: readonly OcxTool[], reg
172
172
  return `[opencodex] Kiro's outbound catalog budget allows ${kept} of ${kept + omitted.length} client tools this turn. Omitted and unavailable this turn: ${summary}.`;
173
173
  }
174
174
 
175
+ function boundedCatalogPriority(tool: OcxTool): number {
176
+ if (tool.loadedFromToolSearch) return 0;
177
+ if (tool.toolSearch) return 1;
178
+ return 2;
179
+ }
180
+
175
181
  export function convertKiroToolContext(
176
182
  parsed: OcxParsedRequest,
177
183
  registry: KiroToolNameRegistry = createKiroToolNameRegistry(),
@@ -181,9 +187,7 @@ export function convertKiroToolContext(
181
187
  // Validate every listed name even when tool_choice:none emulates a tool-free turn.
182
188
  for (const tool of tools) registry.alias(namespacedToolName(tool.namespace, tool.name));
183
189
  const effectiveTools = parsed.options.toolChoice === "none" ? [] : tools;
184
- const convertedTools: unknown[] = [];
185
- let omittedAt = effectiveTools.length;
186
- for (const [index, tool] of effectiveTools.entries()) {
190
+ const convertedEntries = effectiveTools.map((tool, index) => {
187
191
  const description = tool.description || `Tool: ${tool.name}`;
188
192
  // Send the full namespaced wire name (e.g. mcp__chrome-devtools__navigate_page) so Kiro echoes
189
193
  // it back; the bridge's toolNsMap is keyed by this name and restores the MCP namespace Codex
@@ -198,19 +202,26 @@ export function convertKiroToolContext(
198
202
  inputSchema: { json: ensureRootObjectType(sanitizeKiroSchema(tool.parameters ?? {})) },
199
203
  },
200
204
  };
201
- // Preserve declaration order and only omit a suffix. Ranking tools would make a catalog change
202
- // silently alter which capability disappears; this deterministic policy is paired with a
203
- // model-visible omission notice so unavailable tools are explicit rather than assumed absent.
205
+ return { tool, index, converted };
206
+ });
207
+ const exceedsBudget = convertedEntries.length > MAX_KIRO_TOOL_COUNT
208
+ || serializedToolCatalogBytes(convertedEntries.map(entry => entry.converted)) > MAX_KIRO_TOOL_CATALOG_BYTES;
209
+ const candidates = exceedsBudget
210
+ ? convertedEntries.toSorted((a, b) => boundedCatalogPriority(a.tool) - boundedCatalogPriority(b.tool) || a.index - b.index)
211
+ : convertedEntries;
212
+ const convertedTools: unknown[] = [];
213
+ let omittedAt = candidates.length;
214
+ for (const [index, entry] of candidates.entries()) {
204
215
  if (
205
216
  convertedTools.length >= MAX_KIRO_TOOL_COUNT
206
- || serializedToolCatalogBytes([...convertedTools, converted]) > MAX_KIRO_TOOL_CATALOG_BYTES
217
+ || serializedToolCatalogBytes([...convertedTools, entry.converted]) > MAX_KIRO_TOOL_CATALOG_BYTES
207
218
  ) {
208
219
  omittedAt = index;
209
220
  break;
210
221
  }
211
- convertedTools.push(converted);
222
+ convertedTools.push(entry.converted);
212
223
  }
213
- const omittedTools = effectiveTools.slice(omittedAt);
224
+ const omittedTools = candidates.slice(omittedAt).map(entry => entry.tool);
214
225
  return {
215
226
  tools: convertedTools,
216
227
  systemAdditions: omittedTools.length > 0 ? [omittedToolCatalogNotice(convertedTools.length, omittedTools, registry)] : [],
@@ -1244,6 +1244,15 @@ function toolsToChatFormat(parsed: OcxParsedRequest, provider: OcxProviderConfig
1244
1244
  },
1245
1245
  }];
1246
1246
  });
1247
+ if (xaiTarget) {
1248
+ const omitted = tools.length - formatted.length;
1249
+ debugProviderDiagnostic("openai-chat", "tool-catalog", {
1250
+ declared: tools.length,
1251
+ emitted: formatted.length,
1252
+ omitted,
1253
+ ...(omitted > 0 ? { omissionCause: "xai_schema_not_lossless" } : {}),
1254
+ });
1255
+ }
1247
1256
  return formatted.length > 0 ? formatted : undefined;
1248
1257
  }
1249
1258
 
@@ -38,6 +38,7 @@ export const FORWARD_HEADERS = [
38
38
  "x-codex-beta-features",
39
39
  "x-codex-installation-id",
40
40
  "x-codex-parent-thread-id",
41
+ "x-session-id",
41
42
  "x-codex-turn-metadata",
42
43
  "x-codex-turn-state",
43
44
  "x-codex-window-id",
@@ -1686,7 +1687,7 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
1686
1687
  let convertedRoutedCustomToolNames: Set<string> | undefined;
1687
1688
  let routedCustomToolRepairNames: Set<string> | undefined;
1688
1689
  let convertedRoutedToolSearchNames: Set<string> | undefined;
1689
- let convertedRoutedNamespaceToolAliases: Map<string, { namespace: string; name: string }> | undefined;
1690
+ let convertedRoutedNamespaceToolAliases: Map<string, { namespace: string; name: string; kind: "function" | "custom" }> | undefined;
1690
1691
  const unexpandedMiss = !!parsed.previousResponseId && parsed._previousResponseInputExpanded !== true;
1691
1692
  let outBody = stripPreviousResponseId(
1692
1693
  parsed._rawBody,