@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
@@ -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,
@@ -94,6 +112,13 @@ export const CURSOR_MULTI_EDIT_INPUT_SCHEMA = {
94
112
  additionalProperties: false,
95
113
  } as const;
96
114
 
115
+ /** Cursor requires freeform custom tools to advertise their body as one string input. */
116
+ export const CURSOR_FREEFORM_INPUT_SCHEMA = {
117
+ type: "object",
118
+ properties: { input: { type: "string" } },
119
+ required: ["input"],
120
+ } as const;
121
+
97
122
  /**
98
123
  * Responses/Codex-side schema used ONLY for arg-key normalization after Cursor returns a call.
99
124
  * Cursor models are trained to emit `cmd`; Codex `shell_command` / `exec_command` validate
@@ -109,6 +134,10 @@ export const CODEX_SHELL_BRIDGE_ARG_NORMALIZE_SCHEMA = {
109
134
  yield_time_ms: { type: "number", description: "Wait before yielding output. Defaults to 10000 ms; effective range is 250-30000 ms." },
110
135
  max_output_tokens: { type: "number", description: "Output token budget. Defaults to 10000 tokens; larger requests may be capped by policy." },
111
136
  max_output_chars: { type: "number", description: "Output character budget when the Responses tool uses chars instead of tokens." },
137
+ sandbox_permissions: { type: "string" },
138
+ justification: { type: "string" },
139
+ prefix_rule: { type: "array" },
140
+ login: { type: "boolean" },
112
141
  },
113
142
  required: ["command"],
114
143
  } as const;
@@ -364,6 +393,7 @@ export function responsesToolNameFromCursorWire(name: string, cursorToolNameMap?
364
393
 
365
394
  /** Schema advertised to Cursor for this tool (may use Cursor-preferred field names like `cmd`). */
366
395
  export function cursorToolInputSchema(tool: OcxTool): unknown {
396
+ if (tool.freeform) return CURSOR_FREEFORM_INPUT_SCHEMA;
367
397
  return isBareCodexExecCommandTool(tool) ? CURSOR_EXEC_COMMAND_INPUT_SCHEMA : (tool.parameters ?? {});
368
398
  }
369
399
 
@@ -373,6 +403,7 @@ export function cursorToolInputSchema(tool: OcxTool): unknown {
373
403
  * treating `cmd` as canonical prevents the `cmd` → `command` rewrite Codex requires (#399).
374
404
  */
375
405
  export function cursorToolArgNormalizeSchema(tool: OcxTool): unknown {
406
+ if (tool.freeform) return CURSOR_FREEFORM_INPUT_SCHEMA;
376
407
  if (isBareCodexShellBridgeTool(tool)) {
377
408
  return shellBridgeArgNormalizeSchema(tool);
378
409
  }
@@ -651,11 +682,17 @@ export function buildCursorToolGuidanceSystemNote(
651
682
  // Code mode: shell/edit/MCP live inside freeform `exec` as nested helpers. Without this the
652
683
  // model probes for a top-level shell tool that is not there.
653
684
  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.`
685
+ ? `\`${CODEX_UNIFIED_EXEC_TOOL}\` is Codex code mode: its body is JavaScript evaluated in a V8 isolate, not a shell command and not Node. Shell, file edits, and MCP are nested helpers called INSIDE that body as \`await tools.<name>(...)\`, for example \`await tools.exec_command({cmd: \"ls\"})\`. 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
686
  : undefined,
656
687
  codeMode
657
688
  ? "In code mode the isolate returns nothing on its own: call `text(...)` (or `notify(...)`) on any value you need to see, or the call completes with empty output. There is no `require`, no `module`, and no filesystem or network globals; reach the host only through the nested helpers."
658
689
  : undefined,
690
+ codeMode
691
+ ? "To read, search, or inspect files or run CLI commands, call `await tools.exec_command({ cmd: '...' })` inside `exec`. Do not write Node.js scripts or `require('fs')`."
692
+ : undefined,
693
+ codeMode
694
+ ? "NEVER attempt Cursor-native Shell, Read, Grep, List, or any tool absent from the catalog — they are not executed in this environment and every probe wastes a turn. The exec code cell (with its nested helpers) is the ONLY execution surface; go to it directly on the FIRST attempt and do not narrate switching surfaces."
695
+ : undefined,
659
696
  hasBareExec
660
697
  ? `${shellBridgeLabel} is the Codex Responses shell bridge for this turn, exposed through Cursor's tool protocol; it is not an external MCP server tool. \`shell_command\` and \`exec_command\` are aliases of the same bridge.`
661
698
  : undefined,
@@ -663,7 +700,13 @@ export function buildCursorToolGuidanceSystemNote(
663
700
  ? "Your tool list may display it under a longer `mcp_opencodex-responses_shell_command` / `mcp_opencodex-responses_exec_command` name; those are the SAME tool — call whichever your list shows, and do not comment on the naming difference to the user."
664
701
  : undefined,
665
702
  hasBareExec
666
- ? `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}.`
703
+ ? `NEVER attempt Cursor-native Shell, Read, Grep, List, or any tool not in the catalog above — they are not executed locally in this environment and every attempt wastes a turn and can stall the session. ${shellBridgeLabel} is the ONLY shell surface; go to it directly on the FIRST attempt, never as a fallback after probing a native tool. Do not narrate switching surfaces ("native is blocked, using the bridge instead") — there is exactly one surface.`
704
+ : undefined,
705
+ hasBareExec
706
+ ? "Tool-selection commentary is forbidden: for any shell, read, grep, list, or file operation, your FIRST visible action is the bridge call itself — never a sentence about which tool you will use, which tool was redirected, or switching surfaces. Words like 차단/전환/blocked/switching must not appear in your output for tool-routing reasons."
707
+ : undefined,
708
+ hasBareExec
709
+ ? 'When a command requires network access, file writes outside workspace, or fails due to sandbox restrictions, include `sandbox_permissions: "require_escalated"` and `justification: "..."`.'
667
710
  : undefined,
668
711
  hostShellNote,
669
712
  "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.",
@@ -675,6 +718,9 @@ export function buildCursorToolGuidanceSystemNote(
675
718
  ? `For file edits, prefer the structured edit tools ${quotedNames(structuredEditNames)} — they take replacements that OpenCodex converts into Codex \`apply_patch\` changes. Include exact leading whitespace in old_string/new_string. Use \`apply_patch\` directly only with a \`*** Begin Patch\` envelope and bare \`@@\` hunks (never git-style \`@@ -n,m +n,m @@\`); never emit patch-like plain text as tool arguments.`
676
719
  : "For file edits, use the `apply_patch` tool, not built-in file write/delete tools."
677
720
  : undefined,
721
+ hasApplyPatch
722
+ ? "Creating or modifying file CONTENT via shell redirection (`>`, `>>`, `printf`/`echo` into a file, `cat <<EOF`, `sed -i`) is forbidden while apply_patch or the structured edit tools are advertised — use those edit tools so the change is reviewable. Shell output redirection is fine for logs/scratch pipes that are not the deliverable file."
723
+ : undefined,
678
724
  hasBareExec
679
725
  ? "For tool-count demos, each counted tool must be a separate Codex shell-bridge invocation/result; do not collapse several requested tools into one chained shell command."
680
726
  : undefined,
@@ -686,6 +732,7 @@ export function buildCursorToolGuidanceSystemNote(
686
732
  ? `Use ${discoveryTools} only for explicit discovery/resource tasks, not generic tool-count demos.`
687
733
  : undefined,
688
734
  "Do not count or report a tool call unless a tool result was actually returned.",
735
+ "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
736
  hasBareExec
690
737
  ? `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
738
  : undefined,
@@ -44,8 +44,24 @@ const RUNTIME_FAILURE_GUIDANCE: ReadonlyArray<{ marker: string; guidance: string
44
44
  guidance: "The node_repl session keeps earlier declarations; rename the variable or use var/reassignment instead of redeclaring.",
45
45
  },
46
46
  {
47
- marker: "unsupported import in exec",
48
- guidance: "Imports are not available in this exec context; use the injected globals instead.",
47
+ marker: "unsupported import in exec",
48
+ guidance: "Imports are not available in this exec context; use the injected globals instead.",
49
+ },
50
+ {
51
+ marker: "require is not defined",
52
+ guidance: "In Codex code-mode exec (a V8 isolate, not Node.js), require/fs/process are not available. Use await tools.exec_command({ cmd: '...' }) inside exec to inspect files or run CLI tools.",
53
+ },
54
+ {
55
+ marker: "fs is not defined",
56
+ guidance: "In Codex code-mode exec (a V8 isolate, not Node.js), require/fs/process are not available. Use await tools.exec_command({ cmd: '...' }) inside exec to inspect files or run CLI tools.",
57
+ },
58
+ {
59
+ marker: "process is not defined",
60
+ guidance: "In Codex code-mode exec (a V8 isolate, not Node.js), require/fs/process are not available. Use await tools.exec_command({ cmd: '...' }) inside exec to inspect files or run CLI tools.",
61
+ },
62
+ {
63
+ marker: "module is not defined",
64
+ guidance: "In Codex code-mode exec (a V8 isolate, not Node.js), require/fs/process are not available. Use await tools.exec_command({ cmd: '...' }) inside exec to inspect files or run CLI tools.",
49
65
  },
50
66
  ];
51
67
 
@@ -80,13 +96,10 @@ export function normalizeCursorToolResultText(
80
96
  changed: true,
81
97
  };
82
98
  }
83
- if (!isError) {
84
- for (const { marker, guidance } of RUNTIME_FAILURE_GUIDANCE) {
85
- if (text.includes(marker)) {
86
- return { text: `${text}\n[recovery: ${guidance}]`, isError: true, changed: true };
87
- }
99
+ for (const { marker, guidance } of RUNTIME_FAILURE_GUIDANCE) {
100
+ if (text.includes(marker) && !text.includes("[recovery:") && !text.includes(guidance)) {
101
+ return { text: `${text}\n[recovery: ${guidance}]`, isError: true, changed: true };
88
102
  }
89
103
  }
90
104
  return { text, isError, changed: false };
91
105
  }
92
-
@@ -15,6 +15,13 @@ export interface CursorRunRequest {
15
15
  requestedModelParameters?: readonly CursorRequestedModelParameter[];
16
16
  /** Cursor Router optimization parameter; valid only while modelId is the `default` wire model. */
17
17
  routingLevel?: CursorRoutingLevel;
18
+ /**
19
+ * Corrective active-turn text for the single envelope-echo retry (devlog 260826 gap-10).
20
+ * When set on an external tool-result continuation, buildPreparedCursorRunRequest uses it as
21
+ * the userMessageAction text instead of the standard continuation text; rawMessages stay
22
+ * untouched so history replay is unchanged.
23
+ */
24
+ echoRetryContinuationText?: string;
18
25
  conversationId: string;
19
26
  system: string[];
20
27
  messages: CursorRequestMessage[];
@@ -25,9 +32,10 @@ export interface CursorRunRequest {
25
32
  * hydration). History stays text-only. data: URLs only in this slice.
26
33
  */
27
34
  selectedImages?: readonly ResolvedCursorImage[];
28
- tools?: OcxTool[];
29
- toolChoice?: OcxRequestOptions["toolChoice"];
30
- parallelToolCalls?: boolean;
35
+ tools?: OcxTool[];
36
+ toolChoice?: OcxRequestOptions["toolChoice"];
37
+ textFormat?: OcxRequestOptions["textFormat"];
38
+ parallelToolCalls?: boolean;
31
39
  /**
32
40
  * Clear provider-private context-usage carry-forward before this run. Used when Codex starts a
33
41
  * newly observed compacted context epoch, so pre-compaction totals are not over-reported while
@@ -41,6 +41,15 @@ import {
41
41
  shouldSurfaceCursorOverflowFirst,
42
42
  } from "./cursor/thread-continuity";
43
43
  import { runCursorTurnWithRetry } from "./cursor/transport-retry";
44
+ import { cursorRequestHasShellAlias, cursorRequestUsesCodeMode } from "./cursor/tool-definitions";
45
+ import {
46
+ CURSOR_ECHO_RETRY_CONTINUATION_TEXT,
47
+ CURSOR_ROUTING_COMMENTARY_RETRY_TEXT,
48
+ CursorEnvelopeEchoSniffer,
49
+ CursorRoutingCommentaryError,
50
+ CursorRoutingCommentarySniffer,
51
+ CursorToolResultEchoError,
52
+ } from "./cursor/envelope-echo";
44
53
  import {
45
54
  createDisabledCursorTransport,
46
55
  CursorTransportDisabledError,
@@ -208,6 +217,38 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
208
217
  };
209
218
 
210
219
  const runOnce = async (activeRequest: ReturnType<typeof createCursorRequest>) => {
220
+ const effort = _parsed.options.reasoning;
221
+ const isHeavyReasoning = effort === "high"
222
+ || effort === "max"
223
+ || effort === "xhigh"
224
+ || activeRequest.modelId.includes("grok-4.6")
225
+ || activeRequest.modelId.includes("kimi-k3")
226
+ || activeRequest.modelId.includes("opus-4-8");
227
+ const heartbeatOnlyMs = isHeavyReasoning ? 300_000 : 180_000;
228
+ const armEchoSniffer =
229
+ isCursorExternalWireModel(activeRequest.modelId)
230
+ && (_parsed.context.messages ?? []).some(message => message.role === "toolResult");
231
+ const echoSniffer = armEchoSniffer ? new CursorEnvelopeEchoSniffer() : undefined;
232
+ const armRoutingCommentarySniffer =
233
+ isCursorExternalWireModel(activeRequest.modelId)
234
+ && (
235
+ cursorRequestUsesCodeMode(activeRequest.tools, activeRequest.toolChoice)
236
+ || cursorRequestHasShellAlias(activeRequest.tools)
237
+ );
238
+ const routingCommentarySniffer = armRoutingCommentarySniffer
239
+ ? new CursorRoutingCommentarySniffer()
240
+ : undefined;
241
+ let guardHeld: AdapterEvent[] = [];
242
+ const releaseGuardHeld = () => {
243
+ for (const held of guardHeld) {
244
+ if (held.type !== "heartbeat") emittedOutput = true;
245
+ emit(held);
246
+ }
247
+ guardHeld = [];
248
+ };
249
+ const guardsSettled = () =>
250
+ (!echoSniffer || echoSniffer.settled)
251
+ && (!routingCommentarySniffer || routingCommentarySniffer.settled);
211
252
  await runCursorTurnWithRetry(
212
253
  makeTransport,
213
254
  {
@@ -216,6 +257,7 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
216
257
  translatorBudget: incoming.translatorBudget,
217
258
  requestDeclaresFullAccess: cursorRequestDeclaresFullAccess(activeRequest),
218
259
  sessionId: activeRequest.conversationId,
260
+ streamHeartbeatOnlyFailMs: heartbeatOnlyMs,
219
261
  ...(incoming.providerFetch ? { fetch: incoming.providerFetch } : {}),
220
262
  },
221
263
  activeRequest,
@@ -237,6 +279,43 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
237
279
  },
238
280
  });
239
281
  for (const event of events) {
282
+ if (!guardsSettled()) {
283
+ if (event.type === "text_delta") {
284
+ guardHeld.push(event);
285
+ if (echoSniffer && !echoSniffer.settled) {
286
+ const decision = echoSniffer.feed(event.text);
287
+ if (decision.kind === "echo") {
288
+ guardHeld = [];
289
+ throw new CursorToolResultEchoError(decision.marker);
290
+ }
291
+ }
292
+ if (routingCommentarySniffer && !routingCommentarySniffer.settled) {
293
+ const decision = routingCommentarySniffer.feed(event.text);
294
+ if (decision.kind === "hallucination") {
295
+ guardHeld = [];
296
+ throw new CursorRoutingCommentaryError();
297
+ }
298
+ }
299
+ if (guardsSettled()) releaseGuardHeld();
300
+ continue;
301
+ } else if (event.type === "thinking_delta" || event.type === "heartbeat") {
302
+ // Reasoning before first text stays ordered; liveness still passes through.
303
+ if (event.type === "thinking_delta") {
304
+ guardHeld.push(event);
305
+ continue;
306
+ }
307
+ } else {
308
+ // A tool call, done, or error closes the text quarantine. Re-check the full
309
+ // held first line before release so a fragmented routing claim cannot leak.
310
+ echoSniffer?.finish();
311
+ const routingDecision = routingCommentarySniffer?.finish();
312
+ if (routingDecision?.kind === "hallucination") {
313
+ guardHeld = [];
314
+ throw new CursorRoutingCommentaryError();
315
+ }
316
+ releaseGuardHeld();
317
+ }
318
+ }
240
319
  if (event.type !== "heartbeat") emittedOutput = true;
241
320
  if (event.type === "done") {
242
321
  commitCapturedCheckpoint(activeRequest);
@@ -290,6 +369,50 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
290
369
  await runOnce(request);
291
370
  break;
292
371
  } catch (err) {
372
+ const outputGuardRetryText =
373
+ err instanceof CursorToolResultEchoError
374
+ ? CURSOR_ECHO_RETRY_CONTINUATION_TEXT
375
+ : err instanceof CursorRoutingCommentaryError
376
+ ? CURSOR_ROUTING_COMMENTARY_RETRY_TEXT
377
+ : undefined;
378
+ // One-shot corrective retry for guarded external output (devlog 260826 gap-10/11).
379
+ // The quarantine guarantees no client-visible delta escaped, so a fresh-conversation
380
+ // retry is safe. A second rejection propagates as an error rather than looping.
381
+ if (
382
+ outputGuardRetryText
383
+ && !emittedOutput
384
+ && !replayUnsafe
385
+ && !incoming.abortSignal?.aborted
386
+ ) {
387
+ debugProviderDiagnostic(
388
+ "cursor",
389
+ err instanceof CursorToolResultEchoError
390
+ ? "envelope-echo-retry"
391
+ : "routing-commentary-retry",
392
+ {
393
+ wireModel: request.modelId,
394
+ conversationHash: request.conversationId.slice(0, 16),
395
+ },
396
+ );
397
+ const echoedConversationId = request.conversationId;
398
+ lastTransport = undefined;
399
+ _parsed._cursorConversationId = undefined;
400
+ request = {
401
+ ...createCursorRequest(_parsed, { forceFreshConversation: true }),
402
+ echoRetryContinuationText: outputGuardRetryText,
403
+ };
404
+ rekeyContextUsage(echoedConversationId, request.conversationId);
405
+ _parsed._cursorConversationId = request.conversationId;
406
+ if (_parsed._clientThreadId && _parsed._cursorIsolateConversation !== true) {
407
+ rememberCursorThreadConversation(
408
+ _parsed._clientThreadId,
409
+ request.conversationId,
410
+ _parsed._cursorIdentityScope,
411
+ );
412
+ }
413
+ await runOnce(request);
414
+ break;
415
+ }
293
416
  const overflowRemintSafe =
294
417
  !lastRawIsToolResult
295
418
  && !emittedOutput
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Resilient streaming chunk parser for Google AI Studio MakerSuite private RPC protocol.
3
+ */
4
+
5
+ export interface MakerSuiteParsedResult {
6
+ text: string;
7
+ thought?: string;
8
+ thoughtSignature?: string;
9
+ }
10
+
11
+ export function parseMakerSuiteChunk(raw: string): MakerSuiteParsedResult {
12
+ const result: MakerSuiteParsedResult = { text: "" };
13
+ if (!raw || typeof raw !== "string") return result;
14
+
15
+ // 1. Extract regular model response text: [null, "escaped_text"]
16
+ const textMatches = raw.matchAll(/\[\s*null\s*,\s*"((?:[^"\\]|\\.)*)"/g);
17
+ for (const m of textMatches) {
18
+ if (!m[1]) continue;
19
+ try {
20
+ const decoded = JSON.parse('"' + m[1] + '"');
21
+ if (decoded) result.text += decoded;
22
+ } catch (err) {
23
+ void err;
24
+ result.text += m[1];
25
+ }
26
+ }
27
+
28
+ // 2. Extract model reasoning / thoughts: [true, "escaped_thought"]
29
+ const thoughtMatches = raw.matchAll(/\[\s*true\s*,\s*"((?:[^"\\]|\\.)*)"/g);
30
+ for (const m of thoughtMatches) {
31
+ if (!m[1]) continue;
32
+ try {
33
+ const decoded = JSON.parse('"' + m[1] + '"');
34
+ if (decoded) result.thought = (result.thought ?? "") + decoded;
35
+ } catch (err) {
36
+ void err;
37
+ result.thought = (result.thought ?? "") + m[1];
38
+ }
39
+ }
40
+
41
+ // 3. Extract Gemini thought signature token if present
42
+ const sigMatch = raw.match(/"(ErQ[A-Za-z0-9+/=_-]{30,})"/);
43
+ if (sigMatch?.[1]) {
44
+ result.thoughtSignature = sigMatch[1];
45
+ }
46
+
47
+ return result;
48
+ }
49
+
@@ -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 = 2 * 1024 * 1024;
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 = 64 * 1024;
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
- const trimmed = payloadText.trim();
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
- return payloadText.toLowerCase().includes(ANTIGRAVITY_GEO_BLOCKED_MARKER);
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 {
@@ -7,7 +7,7 @@ import {
7
7
  } from "./google-errors";
8
8
  import { isGoogleMixedBuiltinToolError, repairGoogleInvalidRequestBody, stripGoogleBuiltinToolsFromWireBody } from "./google-wire-compiler";
9
9
  import { normalizeUpstreamHttpErrorResponse, readDisplaySafeErrorPayloadText } from "./upstream-http-error";
10
- import { recordAntigravityCooldown } from "../oauth/antigravity-routing";
10
+ import { recordAntigravityCooldown, recordAntigravitySyntheticFailure } from "../oauth/antigravity-routing";
11
11
  import {
12
12
  antigravityHostCandidates,
13
13
  canonicalAntigravityHttpsHost,
@@ -178,6 +178,7 @@ function responseWithBufferedBody(
178
178
  async function prepareCcaSseResponse(
179
179
  response: Response,
180
180
  fetchPeer: (() => Promise<Response>) | undefined,
181
+ accountId?: string,
181
182
  ): Promise<Response> {
182
183
  if (!response.body) return fetchPeer ? fetchPeer() : response;
183
184
  const reader = response.body.getReader();
@@ -209,6 +210,13 @@ async function prepareCcaSseResponse(
209
210
  return failoverOrPassthrough();
210
211
  }
211
212
  if (probe === "quota_exhausted" || probe === "geo_blocked") {
213
+ if (accountId) {
214
+ recordAntigravitySyntheticFailure(accountId, {
215
+ code: probe === "quota_exhausted" ? 429 : 403,
216
+ status: probe === "quota_exhausted" ? "RESOURCE_EXHAUSTED" : "PERMISSION_DENIED",
217
+ message: probe === "quota_exhausted" ? "quota exceeded" : "user location is not supported",
218
+ });
219
+ }
212
220
  const status = probe === "quota_exhausted" ? 429 : 403;
213
221
  return passthrough(undefined, status);
214
222
  }
@@ -237,6 +245,13 @@ async function prepareCcaSseResponse(
237
245
  return failoverOrPassthrough();
238
246
  }
239
247
  if (probe === "quota_exhausted" || probe === "geo_blocked") {
248
+ if (accountId) {
249
+ recordAntigravitySyntheticFailure(accountId, {
250
+ code: probe === "quota_exhausted" ? 429 : 403,
251
+ status: probe === "quota_exhausted" ? "RESOURCE_EXHAUSTED" : "PERMISSION_DENIED",
252
+ message: probe === "quota_exhausted" ? "quota exceeded" : "user location is not supported",
253
+ });
254
+ }
240
255
  const status = probe === "quota_exhausted" ? 429 : 403;
241
256
  return passthrough(overflow, status);
242
257
  }
@@ -262,6 +277,8 @@ function isUnavailableResponse(response: Response): boolean {
262
277
  export interface GoogleRetryOptions {
263
278
  /** Repair-and-replay structurally invalid 400 bodies (Vertex/Antigravity behavior). */
264
279
  repairInvalid400?: boolean;
280
+ /** Let the Responses layer own Antigravity's single same-account 429 replay. */
281
+ retry429?: boolean;
265
282
  }
266
283
 
267
284
  async function normalizeFinalGoogleError(label: string, res: Response, signal?: AbortSignal): Promise<Response> {
@@ -291,13 +308,15 @@ async function recordAntigravityHttpCooldown(
291
308
  if (response.status === 429) {
292
309
  recordAntigravityCooldown(
293
310
  accountId,
294
- isQuotaExhaustedBody(payloadText) ? "quota_exhausted" : "rate_limited",
295
- retryAfterMs(response.headers.get("retry-after")),
311
+ response.headers.get("retry-after"),
312
+ Date.now(),
313
+ isQuotaExhaustedBody(payloadText) ? "quota" : "rate-limit",
314
+ "synthetic",
296
315
  );
297
316
  return true;
298
317
  }
299
- if (isAntigravityGeoBlockedBody(payloadText)) {
300
- recordAntigravityCooldown(accountId, "geo_blocked");
318
+ if (response.status === 403 || isAntigravityGeoBlockedBody(payloadText)) {
319
+ recordAntigravityCooldown(accountId, response.headers.get("retry-after"), Date.now(), "geoblock", "synthetic");
301
320
  return true;
302
321
  }
303
322
  return false;
@@ -318,6 +337,7 @@ async function fetchGoogleWithRetryInternal(
318
337
  opts: GoogleRetryOptions = {},
319
338
  ): Promise<Response> {
320
339
  const repairInvalid400 = opts.repairInvalid400 ?? true;
340
+ const retry429 = opts.retry429 ?? true;
321
341
  const timeoutMs = ctx.timeoutMs ?? 200_000;
322
342
  const executor = ctx.executor ?? globalThis.fetch;
323
343
  let activeRequest = request;
@@ -369,7 +389,7 @@ async function fetchGoogleWithRetryInternal(
369
389
  opts,
370
390
  )
371
391
  : undefined;
372
- return prepareCcaSseResponse(res, fetchPeer);
392
+ return prepareCcaSseResponse(res, fetchPeer, ctx.accountId);
373
393
  }
374
394
  if (res.status === 400 && repairInvalid400 && !compatibilityReplayUsed) {
375
395
  let payloadText = "";
@@ -397,6 +417,9 @@ async function fetchGoogleWithRetryInternal(
397
417
  continue;
398
418
  }
399
419
  }
420
+ if (res.status === 429 && !retry429) {
421
+ return ctx.returnRawErrors ? res : normalizeFinalGoogleError(label, res, ctx.abortSignal);
422
+ }
400
423
  if (!retryableGoogleStatus(res.status) || attempt === GOOGLE_RETRY_ATTEMPTS - 1) {
401
424
  return ctx.returnRawErrors ? res : normalizeFinalGoogleError(label, res, ctx.abortSignal);
402
425
  }
@@ -466,5 +489,5 @@ export function fetchVertexWithRetry(request: AdapterRequest, ctx: AdapterFetchC
466
489
 
467
490
  /** Antigravity (Cloud Code Assist) retry wrapper. */
468
491
  export function fetchAntigravityWithRetry(request: AdapterRequest, ctx: AdapterFetchContext = {}): Promise<Response> {
469
- return fetchGoogleWithRetry("Antigravity", request, ctx);
492
+ return fetchGoogleWithRetry("Antigravity", request, ctx, { retry429: false });
470
493
  }
@@ -12,6 +12,11 @@ export function vertexTruncationErrorMessage(reason?: string): string {
12
12
  return `Vertex AI response truncated upstream before the turn completed${suffix}`;
13
13
  }
14
14
 
15
+ export function googleTruncationErrorMessage(reason?: string): string {
16
+ const suffix = reason ? ` (${redactSecretString(reason).slice(0, 160)})` : "";
17
+ return `Google AI Studio response truncated upstream before the turn completed${suffix}`;
18
+ }
19
+
15
20
  /**
16
21
  * Whether a finished turn must fail closed. A truncation reason arriving mid tool call always
17
22
  * does. MALFORMED_FUNCTION_CALL fails closed even with zero started calls: the malformed call
@@ -5,6 +5,15 @@ type JsonObject = Record<string, unknown>;
5
5
 
6
6
  const GOOGLE_TOOL_NAME = /^[A-Za-z_][A-Za-z0-9_-]{0,63}$/;
7
7
  const GOOGLE_THINKING_LEVELS = new Set(["minimal", "low", "medium", "high"]);
8
+ const GOOGLE_SAFETY_CATEGORIES = new Set([
9
+ "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_SEXUALLY_EXPLICIT", "HARM_CATEGORY_DANGEROUS_CONTENT",
10
+ "HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_CIVIC_INTEGRITY", "HARM_CATEGORY_JAILBREAK",
11
+ ]);
12
+ const GOOGLE_SAFETY_THRESHOLDS = new Set([
13
+ "HARM_BLOCK_THRESHOLD_UNSPECIFIED", "BLOCK_LOW_AND_ABOVE", "BLOCK_MEDIUM_AND_ABOVE",
14
+ "BLOCK_ONLY_HIGH", "BLOCK_NONE", "OFF",
15
+ ]);
16
+ const GOOGLE_CACHED_CONTENT = /^(?:cachedContents\/[^/?#\s]+|projects\/[^/?#\s]+\/locations\/[^/?#\s]+\/cachedContents\/[^/?#\s]+)$/;
8
17
 
9
18
  function isObject(value: unknown): value is JsonObject {
10
19
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -158,12 +167,18 @@ function compileGenerationConfig(value: unknown): JsonObject | undefined {
158
167
  ))].slice(0, 5);
159
168
  if (stopSequences.length > 0) out.stopSequences = stopSequences;
160
169
  }
161
- if (isObject(value.thinkingConfig) && typeof value.thinkingConfig.thinkingLevel === "string") {
162
- const raw = value.thinkingConfig.thinkingLevel.toLowerCase();
163
- const thinkingLevel = GOOGLE_THINKING_LEVELS.has(raw)
164
- ? raw
165
- : (["xhigh", "max", "ultra"].includes(raw) ? "high" : undefined);
166
- if (thinkingLevel) out.thinkingConfig = { thinkingLevel };
170
+ if (isObject(value.thinkingConfig)) {
171
+ const config: JsonObject = {};
172
+ if (typeof value.thinkingConfig.thinkingBudget === "number"
173
+ && Number.isSafeInteger(value.thinkingConfig.thinkingBudget)
174
+ && value.thinkingConfig.thinkingBudget >= -1) config.thinkingBudget = value.thinkingConfig.thinkingBudget;
175
+ if (typeof value.thinkingConfig.includeThoughts === "boolean") config.includeThoughts = value.thinkingConfig.includeThoughts;
176
+ if (typeof value.thinkingConfig.thinkingLevel === "string") {
177
+ const raw = value.thinkingConfig.thinkingLevel.toLowerCase();
178
+ const thinkingLevel = GOOGLE_THINKING_LEVELS.has(raw) ? raw : (["xhigh", "max", "ultra"].includes(raw) ? "high" : undefined);
179
+ if (thinkingLevel && config.thinkingBudget === undefined) config.thinkingLevel = thinkingLevel;
180
+ }
181
+ if (Object.keys(config).length > 0) out.thinkingConfig = config;
167
182
  }
168
183
  if (Array.isArray(value.responseModalities)) {
169
184
  const valid = value.responseModalities.filter((m): m is string => typeof m === "string" && ["TEXT", "IMAGE", "AUDIO"].includes(m));
@@ -180,6 +195,20 @@ function compileGenerationConfig(value: unknown): JsonObject | undefined {
180
195
  return Object.keys(out).length > 0 ? out : undefined;
181
196
  }
182
197
 
198
+ function compileSafetySettings(value: unknown): unknown[] | undefined {
199
+ if (!Array.isArray(value) || value.length > 16) return undefined;
200
+ const seen = new Set<string>();
201
+ const out: JsonObject[] = [];
202
+ for (const setting of value) {
203
+ if (!isObject(setting) || typeof setting.category !== "string" || !GOOGLE_SAFETY_CATEGORIES.has(setting.category)
204
+ || typeof setting.threshold !== "string" || !GOOGLE_SAFETY_THRESHOLDS.has(setting.threshold)
205
+ || seen.has(setting.category)) return undefined;
206
+ seen.add(setting.category);
207
+ out.push({ category: setting.category, threshold: setting.threshold });
208
+ }
209
+ return out.length > 0 ? out : undefined;
210
+ }
211
+
183
212
  function compileToolConfig(value: unknown, toWireName: (name: string) => string): JsonObject | undefined {
184
213
  if (!isObject(value) || !isObject(value.functionCallingConfig)) return undefined;
185
214
  const raw = value.functionCallingConfig;
@@ -216,6 +245,9 @@ export function compileGoogleWireBody(input: unknown): {
216
245
  if (generationConfig) body.generationConfig = generationConfig;
217
246
  const toolConfig = compileToolConfig(source.toolConfig, names.toWire);
218
247
  if (toolConfig) body.toolConfig = toolConfig;
248
+ const safetySettings = compileSafetySettings(source.safetySettings);
249
+ if (safetySettings) body.safetySettings = safetySettings;
250
+ if (typeof source.cachedContent === "string" && GOOGLE_CACHED_CONTENT.test(source.cachedContent)) body.cachedContent = source.cachedContent;
219
251
  if (typeof source.sessionId === "string" && source.sessionId.length > 0) body.sessionId = source.sessionId;
220
252
  return { body, restoreToolName: names.fromWire };
221
253
  }