@yansigit/opencodex 2.31.3 → 2.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/README.md +2 -2
  2. package/bin/ocx.mjs +99 -70
  3. package/gui/dist/assets/index-DKLr4LTE.js +102 -0
  4. package/gui/dist/assets/index-DrSQdTRd.css +1 -0
  5. package/gui/dist/index.html +2 -2
  6. package/package.json +6 -5
  7. package/src/adapters/anthropic.ts +25 -13
  8. package/src/adapters/azure.ts +20 -4
  9. package/src/adapters/base.ts +5 -1
  10. package/src/adapters/command-code.ts +42 -10
  11. package/src/adapters/cursor/live-models.ts +8 -0
  12. package/src/adapters/cursor/live-transport.ts +1 -1
  13. package/src/adapters/cursor/native-exec-desktop.ts +16 -0
  14. package/src/adapters/cursor/protobuf-events.ts +158 -7
  15. package/src/adapters/cursor/protobuf-request.ts +33 -15
  16. package/src/adapters/cursor/request-builder.ts +4 -3
  17. package/src/adapters/cursor/tool-definitions.ts +27 -1
  18. package/src/adapters/cursor/types.ts +4 -3
  19. package/src/adapters/cursor.ts +9 -0
  20. package/src/adapters/google-antigravity-replay.ts +2 -2
  21. package/src/adapters/google-antigravity-wire.ts +7 -0
  22. package/src/adapters/google-errors.ts +6 -2
  23. package/src/adapters/google-http.ts +30 -7
  24. package/src/adapters/google-truncation.ts +5 -0
  25. package/src/adapters/google-wire-compiler.ts +38 -6
  26. package/src/adapters/google.ts +154 -33
  27. package/src/adapters/kiro-tools.ts +20 -9
  28. package/src/adapters/kiro.ts +0 -3
  29. package/src/adapters/openai-chat.ts +9 -0
  30. package/src/adapters/openai-responses.ts +4 -1
  31. package/src/adapters/tool-catalog-nudge.ts +1 -1
  32. package/src/adapters/xai-web-search.ts +7 -2
  33. package/src/bridge.ts +133 -24
  34. package/src/claude/context-windows.ts +16 -9
  35. package/src/cli/dispatch.ts +50 -2
  36. package/src/cli/doctor.ts +26 -13
  37. package/src/cli/help.ts +4 -3
  38. package/src/cli/index.ts +20 -6
  39. package/src/cli/models.ts +13 -3
  40. package/src/cli/observe.ts +20 -5
  41. package/src/cli/provider.ts +8 -1
  42. package/src/cli/registry.ts +7 -5
  43. package/src/cli/status.ts +25 -1
  44. package/src/cli/system-restart-client.ts +1 -1
  45. package/src/cli/usage-report.ts +134 -0
  46. package/src/codex/app-server-processes.ts +3 -1
  47. package/src/codex/auth-api.ts +4 -2
  48. package/src/codex/autostart-health.ts +16 -0
  49. package/src/codex/catalog/aggregation.ts +12 -0
  50. package/src/codex/catalog/effort.ts +42 -12
  51. package/src/codex/catalog/metadata.ts +27 -1
  52. package/src/codex/catalog/model-metadata.ts +593 -0
  53. package/src/codex/catalog/parsing.ts +71 -27
  54. package/src/codex/catalog/provider-fetch.ts +189 -33
  55. package/src/codex/catalog/sync.ts +6 -5
  56. package/src/codex/convergence.ts +5 -0
  57. package/src/codex/desktop-app-restart.ts +342 -0
  58. package/src/codex/history-job.ts +32 -3
  59. package/src/codex/history-manifest.ts +112 -0
  60. package/src/codex/history-migration-guardian.ts +5 -5
  61. package/src/codex/history-provider.ts +825 -247
  62. package/src/codex/history-worker.ts +8 -5
  63. package/src/codex/inject.ts +49 -21
  64. package/src/codex/injected-marker.ts +1 -1
  65. package/src/codex/internal/history-writer.ts +4 -3
  66. package/src/codex/native-profile-startup.ts +157 -27
  67. package/src/codex/native-residue.ts +26 -33
  68. package/src/codex/shim.ts +56 -3
  69. package/src/combos/failover.ts +27 -0
  70. package/src/compatibility/index.ts +26 -0
  71. package/src/compatibility/manifest.ts +253 -0
  72. package/src/compatibility/openai-responses.ts +81 -0
  73. package/src/config/atomic-write.ts +219 -0
  74. package/src/config/paths.ts +40 -0
  75. package/src/config/process-state.ts +308 -0
  76. package/src/config/provider-validation.ts +214 -0
  77. package/src/config.ts +153 -814
  78. package/src/generated/compatibility-version.json +232 -148
  79. package/src/images/loop.ts +37 -6
  80. package/src/images/plan.ts +5 -4
  81. package/src/integrations/ownership-policy.ts +141 -0
  82. package/src/integrations/ownership.ts +10 -0
  83. package/src/integrations/state.ts +44 -5
  84. package/src/integrations/writer.ts +6 -0
  85. package/src/lib/azure-identity.ts +154 -0
  86. package/src/lib/bounded-body.ts +14 -2
  87. package/src/lib/debug.ts +42 -0
  88. package/src/lib/errors.ts +14 -0
  89. package/src/lib/process-control.ts +2 -1
  90. package/src/lib/provider-outbound.ts +45 -33
  91. package/src/lib/provider-tls-profile.ts +309 -0
  92. package/src/lib/proxy-env.ts +49 -0
  93. package/src/lib/redact.ts +10 -1
  94. package/src/lib/state-store-registrations.ts +2 -0
  95. package/src/lib/tool-argument-integers.ts +56 -5
  96. package/src/oauth/antigravity-routing.ts +282 -236
  97. package/src/oauth/callback-server.ts +22 -2
  98. package/src/oauth/command-code.ts +5 -16
  99. package/src/oauth/google-antigravity.ts +42 -5
  100. package/src/oauth/health.ts +1 -1
  101. package/src/oauth/index.ts +15 -3
  102. package/src/oauth/kimi.ts +9 -1
  103. package/src/oauth/open-browser-choice.ts +26 -0
  104. package/src/oauth/store.ts +6 -0
  105. package/src/providers/antigravity-quota.ts +3 -1
  106. package/src/providers/api-keys.ts +2 -1
  107. package/src/providers/auto-compact-budget.ts +65 -0
  108. package/src/providers/derive.ts +4 -0
  109. package/src/providers/key-failover.ts +5 -1
  110. package/src/providers/openai-tiers.ts +5 -0
  111. package/src/providers/provider-id-rewrite.ts +1 -0
  112. package/src/providers/quota.ts +59 -13
  113. package/src/providers/registry.ts +4 -2
  114. package/src/providers/request-pacing.ts +33 -6
  115. package/src/providers/xai-transport.ts +21 -0
  116. package/src/reasoning-effort.ts +19 -2
  117. package/src/responses/apply-patch-envelope.ts +63 -0
  118. package/src/responses/custom-tool-compat.ts +132 -38
  119. package/src/responses/google-provider-options.ts +36 -0
  120. package/src/responses/namespace-tool-compat.ts +84 -4
  121. package/src/responses/parser.ts +14 -2
  122. package/src/responses/provider-opaque-metadata.ts +3 -3
  123. package/src/responses/reasoning-replay-cache.ts +81 -3
  124. package/src/responses/schema.ts +37 -0
  125. package/src/responses/state.ts +94 -4
  126. package/src/router.ts +8 -2
  127. package/src/server/auth-cors.ts +37 -7
  128. package/src/server/images.ts +19 -35
  129. package/src/server/index.ts +102 -21
  130. package/src/server/local-management-read-client.ts +1 -1
  131. package/src/server/local-provider-reload-client.ts +1 -1
  132. package/src/server/management/agent-settings-routes.ts +206 -16
  133. package/src/server/management/combo-routes.ts +6 -0
  134. package/src/server/management/config-routes.ts +35 -6
  135. package/src/server/management/context.ts +1 -1
  136. package/src/server/management/logs-usage-routes.ts +27 -6
  137. package/src/server/management/model-routes.ts +8 -4
  138. package/src/server/management/model-rows.ts +4 -0
  139. package/src/server/management/native-integration-routes.ts +2 -1
  140. package/src/server/management/oauth-account-routes.ts +25 -4
  141. package/src/server/management/provider-capability-config.ts +1 -1
  142. package/src/server/management/provider-routes.ts +113 -15
  143. package/src/server/management/routing-profile-routes.ts +3 -0
  144. package/src/server/management/system-restart.ts +1 -1
  145. package/src/server/port-reclaim.ts +1 -1
  146. package/src/server/proxy-liveness.ts +2 -1
  147. package/src/server/request-log-conversation.ts +30 -0
  148. package/src/server/request-log.ts +21 -0
  149. package/src/server/responses/agent-task-recovery.ts +1 -1
  150. package/src/server/responses/codex-auth-error.ts +55 -0
  151. package/src/server/responses/combo-stream-preflight.ts +171 -0
  152. package/src/server/responses/compact.ts +36 -22
  153. package/src/server/responses/core.ts +584 -247
  154. package/src/server/responses/empty-completion-guard.ts +35 -6
  155. package/src/server/responses/fetch-helpers.ts +20 -102
  156. package/src/server/responses/v2-native-parent-override.ts +59 -0
  157. package/src/server/responses/ws-upstream.ts +75 -2
  158. package/src/server/responses-custom-tool-repair.ts +41 -5
  159. package/src/server/responses-undeclared-tool-guard.ts +241 -18
  160. package/src/service.ts +9 -5
  161. package/src/types/config.ts +16 -1
  162. package/src/types/provider.ts +16 -0
  163. package/src/types/request.ts +34 -1
  164. package/src/types/tools.ts +114 -11
  165. package/src/types.ts +7 -1
  166. package/src/update/index.ts +5 -4
  167. package/src/update/job.ts +3 -1
  168. package/src/update/transactional-install.mjs +8 -1
  169. package/src/usage/log.ts +16 -8
  170. package/src/usage/summary.ts +201 -8
  171. package/src/vision/describe.ts +18 -13
  172. package/src/web-search/executor.ts +10 -3
  173. package/src/web-search/gemini-executor.ts +6 -4
  174. package/src/web-search/loop.ts +42 -6
  175. package/gui/dist/assets/index-CGoDO3uO.css +0 -1
  176. package/gui/dist/assets/index-Cxt5fZMP.js +0 -102
@@ -208,6 +208,14 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
208
208
  };
209
209
 
210
210
  const runOnce = async (activeRequest: ReturnType<typeof createCursorRequest>) => {
211
+ const effort = _parsed.options.reasoning;
212
+ const isHeavyReasoning = effort === "high"
213
+ || effort === "max"
214
+ || effort === "xhigh"
215
+ || activeRequest.modelId.includes("grok-4.6")
216
+ || activeRequest.modelId.includes("kimi-k3")
217
+ || activeRequest.modelId.includes("opus-4-8");
218
+ const heartbeatOnlyMs = isHeavyReasoning ? 300_000 : 180_000;
211
219
  await runCursorTurnWithRetry(
212
220
  makeTransport,
213
221
  {
@@ -216,6 +224,7 @@ export function createCursorAdapter(provider: OcxProviderConfig, deps: CursorAda
216
224
  translatorBudget: incoming.translatorBudget,
217
225
  requestDeclaresFullAccess: cursorRequestDeclaresFullAccess(activeRequest),
218
226
  sessionId: activeRequest.conversationId,
227
+ streamHeartbeatOnlyFailMs: heartbeatOnlyMs,
219
228
  ...(incoming.providerFetch ? { fetch: incoming.providerFetch } : {}),
220
229
  },
221
230
  activeRequest,
@@ -38,9 +38,9 @@ const REPLAY_TTL_MS = 60 * 60 * 1000; // 1h
38
38
  export const ANTIGRAVITY_REPLAY_MAX_ENTRIES = 10_240;
39
39
  const REPLAY_EVICT_BATCH = 128;
40
40
  const REPLAY_MAX_CALLS_PER_SESSION = 256;
41
- export const ANTIGRAVITY_REPLAY_MAX_BYTES_PER_SESSION = 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
  }