@cortexkit/opencode-antigravity-auth 1.0.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 (271) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +714 -0
  3. package/dist/index.d.ts +4 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +15175 -0
  6. package/dist/index.js.map +7 -0
  7. package/dist/src/antigravity/oauth.d.ts +2 -0
  8. package/dist/src/antigravity/oauth.d.ts.map +1 -0
  9. package/dist/src/antigravity/oauth.js +3 -0
  10. package/dist/src/antigravity/oauth.js.map +1 -0
  11. package/dist/src/constants.d.ts +2 -0
  12. package/dist/src/constants.d.ts.map +1 -0
  13. package/dist/src/constants.js +3 -0
  14. package/dist/src/constants.js.map +1 -0
  15. package/dist/src/hooks/auto-update-checker/cache.d.ts +3 -0
  16. package/dist/src/hooks/auto-update-checker/cache.d.ts.map +1 -0
  17. package/dist/src/hooks/auto-update-checker/cache.js +71 -0
  18. package/dist/src/hooks/auto-update-checker/cache.js.map +1 -0
  19. package/dist/src/hooks/auto-update-checker/checker.d.ts +16 -0
  20. package/dist/src/hooks/auto-update-checker/checker.d.ts.map +1 -0
  21. package/dist/src/hooks/auto-update-checker/checker.js +234 -0
  22. package/dist/src/hooks/auto-update-checker/checker.js.map +1 -0
  23. package/dist/src/hooks/auto-update-checker/constants.d.ts +9 -0
  24. package/dist/src/hooks/auto-update-checker/constants.d.ts.map +1 -0
  25. package/dist/src/hooks/auto-update-checker/constants.js +23 -0
  26. package/dist/src/hooks/auto-update-checker/constants.js.map +1 -0
  27. package/dist/src/hooks/auto-update-checker/index.d.ts +34 -0
  28. package/dist/src/hooks/auto-update-checker/index.d.ts.map +1 -0
  29. package/dist/src/hooks/auto-update-checker/index.js +122 -0
  30. package/dist/src/hooks/auto-update-checker/index.js.map +1 -0
  31. package/dist/src/hooks/auto-update-checker/logging.d.ts +3 -0
  32. package/dist/src/hooks/auto-update-checker/logging.d.ts.map +1 -0
  33. package/dist/src/hooks/auto-update-checker/logging.js +9 -0
  34. package/dist/src/hooks/auto-update-checker/logging.js.map +1 -0
  35. package/dist/src/hooks/auto-update-checker/types.d.ts +25 -0
  36. package/dist/src/hooks/auto-update-checker/types.d.ts.map +1 -0
  37. package/dist/src/hooks/auto-update-checker/types.js +1 -0
  38. package/dist/src/hooks/auto-update-checker/types.js.map +1 -0
  39. package/dist/src/plugin/accounts.d.ts +232 -0
  40. package/dist/src/plugin/accounts.d.ts.map +1 -0
  41. package/dist/src/plugin/accounts.js +1163 -0
  42. package/dist/src/plugin/accounts.js.map +1 -0
  43. package/dist/src/plugin/agy-transport.d.ts +2 -0
  44. package/dist/src/plugin/agy-transport.d.ts.map +1 -0
  45. package/dist/src/plugin/agy-transport.js +3 -0
  46. package/dist/src/plugin/agy-transport.js.map +1 -0
  47. package/dist/src/plugin/auth-doctor.d.ts +30 -0
  48. package/dist/src/plugin/auth-doctor.d.ts.map +1 -0
  49. package/dist/src/plugin/auth-doctor.js +144 -0
  50. package/dist/src/plugin/auth-doctor.js.map +1 -0
  51. package/dist/src/plugin/auth-drift.d.ts +13 -0
  52. package/dist/src/plugin/auth-drift.d.ts.map +1 -0
  53. package/dist/src/plugin/auth-drift.js +70 -0
  54. package/dist/src/plugin/auth-drift.js.map +1 -0
  55. package/dist/src/plugin/auth.d.ts +2 -0
  56. package/dist/src/plugin/auth.d.ts.map +1 -0
  57. package/dist/src/plugin/auth.js +3 -0
  58. package/dist/src/plugin/auth.js.map +1 -0
  59. package/dist/src/plugin/cache/index.d.ts +5 -0
  60. package/dist/src/plugin/cache/index.d.ts.map +1 -0
  61. package/dist/src/plugin/cache/index.js +5 -0
  62. package/dist/src/plugin/cache/index.js.map +1 -0
  63. package/dist/src/plugin/cache/signature-cache.d.ts +111 -0
  64. package/dist/src/plugin/cache/signature-cache.d.ts.map +1 -0
  65. package/dist/src/plugin/cache/signature-cache.js +375 -0
  66. package/dist/src/plugin/cache/signature-cache.js.map +1 -0
  67. package/dist/src/plugin/cache.d.ts +44 -0
  68. package/dist/src/plugin/cache.d.ts.map +1 -0
  69. package/dist/src/plugin/cache.js +247 -0
  70. package/dist/src/plugin/cache.js.map +1 -0
  71. package/dist/src/plugin/cli.d.ts +44 -0
  72. package/dist/src/plugin/cli.d.ts.map +1 -0
  73. package/dist/src/plugin/cli.js +153 -0
  74. package/dist/src/plugin/cli.js.map +1 -0
  75. package/dist/src/plugin/config/index.d.ts +16 -0
  76. package/dist/src/plugin/config/index.d.ts.map +1 -0
  77. package/dist/src/plugin/config/index.js +16 -0
  78. package/dist/src/plugin/config/index.js.map +1 -0
  79. package/dist/src/plugin/config/loader.d.ts +36 -0
  80. package/dist/src/plugin/config/loader.d.ts.map +1 -0
  81. package/dist/src/plugin/config/loader.js +140 -0
  82. package/dist/src/plugin/config/loader.js.map +1 -0
  83. package/dist/src/plugin/config/models.d.ts +3 -0
  84. package/dist/src/plugin/config/models.d.ts.map +1 -0
  85. package/dist/src/plugin/config/models.js +2 -0
  86. package/dist/src/plugin/config/models.js.map +1 -0
  87. package/dist/src/plugin/config/schema.d.ts +141 -0
  88. package/dist/src/plugin/config/schema.d.ts.map +1 -0
  89. package/dist/src/plugin/config/schema.js +504 -0
  90. package/dist/src/plugin/config/schema.js.map +1 -0
  91. package/dist/src/plugin/config/updater.d.ts +55 -0
  92. package/dist/src/plugin/config/updater.d.ts.map +1 -0
  93. package/dist/src/plugin/config/updater.js +127 -0
  94. package/dist/src/plugin/config/updater.js.map +1 -0
  95. package/dist/src/plugin/core/streaming/index.d.ts +3 -0
  96. package/dist/src/plugin/core/streaming/index.d.ts.map +1 -0
  97. package/dist/src/plugin/core/streaming/index.js +3 -0
  98. package/dist/src/plugin/core/streaming/index.js.map +1 -0
  99. package/dist/src/plugin/core/streaming/transformer.d.ts +12 -0
  100. package/dist/src/plugin/core/streaming/transformer.d.ts.map +1 -0
  101. package/dist/src/plugin/core/streaming/transformer.js +447 -0
  102. package/dist/src/plugin/core/streaming/transformer.js.map +1 -0
  103. package/dist/src/plugin/core/streaming/types.d.ts +34 -0
  104. package/dist/src/plugin/core/streaming/types.d.ts.map +1 -0
  105. package/dist/src/plugin/core/streaming/types.js +1 -0
  106. package/dist/src/plugin/core/streaming/types.js.map +1 -0
  107. package/dist/src/plugin/debug.d.ts +94 -0
  108. package/dist/src/plugin/debug.d.ts.map +1 -0
  109. package/dist/src/plugin/debug.js +383 -0
  110. package/dist/src/plugin/debug.js.map +1 -0
  111. package/dist/src/plugin/errors.d.ts +28 -0
  112. package/dist/src/plugin/errors.d.ts.map +1 -0
  113. package/dist/src/plugin/errors.js +42 -0
  114. package/dist/src/plugin/errors.js.map +1 -0
  115. package/dist/src/plugin/fingerprint.d.ts +2 -0
  116. package/dist/src/plugin/fingerprint.d.ts.map +1 -0
  117. package/dist/src/plugin/fingerprint.js +3 -0
  118. package/dist/src/plugin/fingerprint.js.map +1 -0
  119. package/dist/src/plugin/gemini-dump.d.ts +47 -0
  120. package/dist/src/plugin/gemini-dump.d.ts.map +1 -0
  121. package/dist/src/plugin/gemini-dump.js +238 -0
  122. package/dist/src/plugin/gemini-dump.js.map +1 -0
  123. package/dist/src/plugin/image-saver.d.ts +25 -0
  124. package/dist/src/plugin/image-saver.d.ts.map +1 -0
  125. package/dist/src/plugin/image-saver.js +86 -0
  126. package/dist/src/plugin/image-saver.js.map +1 -0
  127. package/dist/src/plugin/logger.d.ts +23 -0
  128. package/dist/src/plugin/logger.d.ts.map +1 -0
  129. package/dist/src/plugin/logger.js +80 -0
  130. package/dist/src/plugin/logger.js.map +1 -0
  131. package/dist/src/plugin/logging-utils.d.ts +23 -0
  132. package/dist/src/plugin/logging-utils.d.ts.map +1 -0
  133. package/dist/src/plugin/logging-utils.js +92 -0
  134. package/dist/src/plugin/logging-utils.js.map +1 -0
  135. package/dist/src/plugin/model-registry.d.ts +2 -0
  136. package/dist/src/plugin/model-registry.d.ts.map +1 -0
  137. package/dist/src/plugin/model-registry.js +3 -0
  138. package/dist/src/plugin/model-registry.js.map +1 -0
  139. package/dist/src/plugin/project.d.ts +2 -0
  140. package/dist/src/plugin/project.d.ts.map +1 -0
  141. package/dist/src/plugin/project.js +3 -0
  142. package/dist/src/plugin/project.js.map +1 -0
  143. package/dist/src/plugin/prompt-context.d.ts +18 -0
  144. package/dist/src/plugin/prompt-context.d.ts.map +1 -0
  145. package/dist/src/plugin/prompt-context.js +99 -0
  146. package/dist/src/plugin/prompt-context.js.map +1 -0
  147. package/dist/src/plugin/quota.d.ts +44 -0
  148. package/dist/src/plugin/quota.d.ts.map +1 -0
  149. package/dist/src/plugin/quota.js +336 -0
  150. package/dist/src/plugin/quota.js.map +1 -0
  151. package/dist/src/plugin/recovery/constants.d.ts +22 -0
  152. package/dist/src/plugin/recovery/constants.d.ts.map +1 -0
  153. package/dist/src/plugin/recovery/constants.js +43 -0
  154. package/dist/src/plugin/recovery/constants.js.map +1 -0
  155. package/dist/src/plugin/recovery/index.d.ts +12 -0
  156. package/dist/src/plugin/recovery/index.d.ts.map +1 -0
  157. package/dist/src/plugin/recovery/index.js +12 -0
  158. package/dist/src/plugin/recovery/index.js.map +1 -0
  159. package/dist/src/plugin/recovery/storage.d.ts +24 -0
  160. package/dist/src/plugin/recovery/storage.d.ts.map +1 -0
  161. package/dist/src/plugin/recovery/storage.js +354 -0
  162. package/dist/src/plugin/recovery/storage.js.map +1 -0
  163. package/dist/src/plugin/recovery/types.d.ts +116 -0
  164. package/dist/src/plugin/recovery/types.d.ts.map +1 -0
  165. package/dist/src/plugin/recovery/types.js +6 -0
  166. package/dist/src/plugin/recovery/types.js.map +1 -0
  167. package/dist/src/plugin/recovery.d.ts +61 -0
  168. package/dist/src/plugin/recovery.d.ts.map +1 -0
  169. package/dist/src/plugin/recovery.js +381 -0
  170. package/dist/src/plugin/recovery.js.map +1 -0
  171. package/dist/src/plugin/refresh-queue.d.ts +100 -0
  172. package/dist/src/plugin/refresh-queue.d.ts.map +1 -0
  173. package/dist/src/plugin/refresh-queue.js +247 -0
  174. package/dist/src/plugin/refresh-queue.js.map +1 -0
  175. package/dist/src/plugin/request-helpers.d.ts +278 -0
  176. package/dist/src/plugin/request-helpers.d.ts.map +1 -0
  177. package/dist/src/plugin/request-helpers.js +2323 -0
  178. package/dist/src/plugin/request-helpers.js.map +1 -0
  179. package/dist/src/plugin/request.d.ts +102 -0
  180. package/dist/src/plugin/request.d.ts.map +1 -0
  181. package/dist/src/plugin/request.js +1658 -0
  182. package/dist/src/plugin/request.js.map +1 -0
  183. package/dist/src/plugin/rotation.d.ts +169 -0
  184. package/dist/src/plugin/rotation.d.ts.map +1 -0
  185. package/dist/src/plugin/rotation.js +328 -0
  186. package/dist/src/plugin/rotation.js.map +1 -0
  187. package/dist/src/plugin/search.d.ts +25 -0
  188. package/dist/src/plugin/search.d.ts.map +1 -0
  189. package/dist/src/plugin/search.js +200 -0
  190. package/dist/src/plugin/search.js.map +1 -0
  191. package/dist/src/plugin/server.d.ts +23 -0
  192. package/dist/src/plugin/server.d.ts.map +1 -0
  193. package/dist/src/plugin/server.js +324 -0
  194. package/dist/src/plugin/server.js.map +1 -0
  195. package/dist/src/plugin/storage.d.ts +150 -0
  196. package/dist/src/plugin/storage.d.ts.map +1 -0
  197. package/dist/src/plugin/storage.js +628 -0
  198. package/dist/src/plugin/storage.js.map +1 -0
  199. package/dist/src/plugin/stores/signature-store.d.ts +5 -0
  200. package/dist/src/plugin/stores/signature-store.d.ts.map +1 -0
  201. package/dist/src/plugin/stores/signature-store.js +25 -0
  202. package/dist/src/plugin/stores/signature-store.js.map +1 -0
  203. package/dist/src/plugin/thinking-recovery.d.ts +90 -0
  204. package/dist/src/plugin/thinking-recovery.d.ts.map +1 -0
  205. package/dist/src/plugin/thinking-recovery.js +330 -0
  206. package/dist/src/plugin/thinking-recovery.js.map +1 -0
  207. package/dist/src/plugin/token.d.ts +19 -0
  208. package/dist/src/plugin/token.d.ts.map +1 -0
  209. package/dist/src/plugin/token.js +130 -0
  210. package/dist/src/plugin/token.js.map +1 -0
  211. package/dist/src/plugin/transform/claude.d.ts +92 -0
  212. package/dist/src/plugin/transform/claude.d.ts.map +1 -0
  213. package/dist/src/plugin/transform/claude.js +280 -0
  214. package/dist/src/plugin/transform/claude.js.map +1 -0
  215. package/dist/src/plugin/transform/cross-model-sanitizer.d.ts +2 -0
  216. package/dist/src/plugin/transform/cross-model-sanitizer.d.ts.map +1 -0
  217. package/dist/src/plugin/transform/cross-model-sanitizer.js +3 -0
  218. package/dist/src/plugin/transform/cross-model-sanitizer.js.map +1 -0
  219. package/dist/src/plugin/transform/gemini.d.ts +100 -0
  220. package/dist/src/plugin/transform/gemini.d.ts.map +1 -0
  221. package/dist/src/plugin/transform/gemini.js +446 -0
  222. package/dist/src/plugin/transform/gemini.js.map +1 -0
  223. package/dist/src/plugin/transform/index.d.ts +2 -0
  224. package/dist/src/plugin/transform/index.d.ts.map +1 -0
  225. package/dist/src/plugin/transform/index.js +3 -0
  226. package/dist/src/plugin/transform/index.js.map +1 -0
  227. package/dist/src/plugin/transform/model-resolver.d.ts +2 -0
  228. package/dist/src/plugin/transform/model-resolver.d.ts.map +1 -0
  229. package/dist/src/plugin/transform/model-resolver.js +3 -0
  230. package/dist/src/plugin/transform/model-resolver.js.map +1 -0
  231. package/dist/src/plugin/transform/types.d.ts +2 -0
  232. package/dist/src/plugin/transform/types.d.ts.map +1 -0
  233. package/dist/src/plugin/transform/types.js +3 -0
  234. package/dist/src/plugin/transform/types.js.map +1 -0
  235. package/dist/src/plugin/types.d.ts +80 -0
  236. package/dist/src/plugin/types.d.ts.map +1 -0
  237. package/dist/src/plugin/types.js +1 -0
  238. package/dist/src/plugin/types.js.map +1 -0
  239. package/dist/src/plugin/ui/ansi.d.ts +32 -0
  240. package/dist/src/plugin/ui/ansi.d.ts.map +1 -0
  241. package/dist/src/plugin/ui/ansi.js +52 -0
  242. package/dist/src/plugin/ui/ansi.js.map +1 -0
  243. package/dist/src/plugin/ui/auth-menu.d.ts +59 -0
  244. package/dist/src/plugin/ui/auth-menu.d.ts.map +1 -0
  245. package/dist/src/plugin/ui/auth-menu.js +362 -0
  246. package/dist/src/plugin/ui/auth-menu.js.map +1 -0
  247. package/dist/src/plugin/ui/confirm.d.ts +2 -0
  248. package/dist/src/plugin/ui/confirm.d.ts.map +1 -0
  249. package/dist/src/plugin/ui/confirm.js +15 -0
  250. package/dist/src/plugin/ui/confirm.js.map +1 -0
  251. package/dist/src/plugin/ui/model-status.d.ts +28 -0
  252. package/dist/src/plugin/ui/model-status.d.ts.map +1 -0
  253. package/dist/src/plugin/ui/model-status.js +80 -0
  254. package/dist/src/plugin/ui/model-status.js.map +1 -0
  255. package/dist/src/plugin/ui/quota-status.d.ts +82 -0
  256. package/dist/src/plugin/ui/quota-status.d.ts.map +1 -0
  257. package/dist/src/plugin/ui/quota-status.js +251 -0
  258. package/dist/src/plugin/ui/quota-status.js.map +1 -0
  259. package/dist/src/plugin/ui/select.d.ts +23 -0
  260. package/dist/src/plugin/ui/select.d.ts.map +1 -0
  261. package/dist/src/plugin/ui/select.js +254 -0
  262. package/dist/src/plugin/ui/select.js.map +1 -0
  263. package/dist/src/plugin/version.d.ts +2 -0
  264. package/dist/src/plugin/version.d.ts.map +1 -0
  265. package/dist/src/plugin/version.js +3 -0
  266. package/dist/src/plugin/version.js.map +1 -0
  267. package/dist/src/plugin.d.ts +32 -0
  268. package/dist/src/plugin.d.ts.map +1 -0
  269. package/dist/src/plugin.js +3125 -0
  270. package/dist/src/plugin.js.map +1 -0
  271. package/package.json +70 -0
@@ -0,0 +1,1658 @@
1
+ import crypto from "node:crypto";
2
+ import { ANTIGRAVITY_ENDPOINT, GEMINI_CLI_ENDPOINT, EMPTY_SCHEMA_PLACEHOLDER_NAME, EMPTY_SCHEMA_PLACEHOLDER_DESCRIPTION, SKIP_THOUGHT_SIGNATURE, getRandomizedHeaders, } from "../constants";
3
+ import { cacheSignature, getCachedSignature } from "./cache";
4
+ import { getKeepThinking } from "./config";
5
+ import { createStreamingTransformer, transformSseLine, transformStreamingPayload, } from "./core/streaming";
6
+ import { defaultSignatureStore } from "./stores/signature-store";
7
+ import { DEBUG_MESSAGE_PREFIX, isDebugEnabled, isDebugTuiEnabled, logAntigravityDebugResponse, logCacheStats, } from "./debug";
8
+ import { createLogger } from "./logger";
9
+ import { cleanJSONSchemaForAntigravity, DEFAULT_THINKING_BUDGET, deepFilterThinkingBlocks, extractThinkingConfig, extractVariantThinkingConfig, extractUsageFromSsePayload, extractUsageMetadata, fixToolResponseGrouping, validateAndFixClaudeToolPairing, applyToolPairingFixes, injectParameterSignatures, injectToolHardeningInstruction, isThinkingCapableModel, normalizeThinkingConfig, parseAntigravityApiBody, resolveThinkingConfig, rewriteAntigravityPreviewAccessError, transformThinkingParts, } from "./request-helpers";
10
+ import { CLAUDE_TOOL_SYSTEM_INSTRUCTION, CLAUDE_DESCRIPTION_PROMPT, } from "../constants";
11
+ import { analyzeConversationState, closeToolLoopForThinking, needsThinkingRecovery, } from "./thinking-recovery";
12
+ import { sanitizeCrossModelPayloadInPlace } from "./transform/cross-model-sanitizer";
13
+ import { isGemini3Model, isImageGenerationModel, buildImageGenerationConfig, applyGeminiTransforms } from "./transform";
14
+ import { resolveModelWithTier, resolveModelWithVariant, resolveModelForHeaderStyle, isClaudeModel, isClaudeThinkingModel, CLAUDE_THINKING_MAX_OUTPUT_TOKENS, computeClaudeMaxOutputTokens, appendClaudeThinkingHint, } from "./transform";
15
+ import { detectErrorType } from "./recovery";
16
+ import { getSessionFingerprint, buildFingerprintHeaders } from "./fingerprint";
17
+ import { appendGeminiDumpResponseText, createGeminiDumpResponseTransform, noteGeminiDumpResponse, } from "./gemini-dump";
18
+ const log = createLogger("request");
19
+ const PLUGIN_SESSION_ID = `-${crypto.randomUUID()}`;
20
+ const sessionDisplayedThinkingHashes = new Set();
21
+ const MIN_SIGNATURE_LENGTH = 50;
22
+ const ANTIGRAVITY_ENVELOPE_FIELD_ORDER = [
23
+ "project",
24
+ "requestId",
25
+ "request",
26
+ "model",
27
+ "userAgent",
28
+ "requestType",
29
+ "enabledCreditTypes",
30
+ ];
31
+ function buildAntigravityRequestId(type = "agent") {
32
+ if (type === "checkpoint") {
33
+ return `checkpoint/${crypto.randomUUID()}`;
34
+ }
35
+ return `agent/${crypto.randomUUID()}/${Date.now()}/${crypto.randomUUID()}/2`;
36
+ }
37
+ function getAgyMaxOutputTokens(model) {
38
+ const lower = model.toLowerCase();
39
+ if (lower === "gemini-3.5-flash-low" || lower === "gemini-3.5-flash-extra-low" || lower === "gemini-3-flash-agent") {
40
+ return 65536;
41
+ }
42
+ if (lower === "gemini-3.1-pro-low" || lower === "gemini-pro-agent") {
43
+ return 65535;
44
+ }
45
+ if (lower === "claude-sonnet-4-6" || lower === "claude-opus-4-6-thinking") {
46
+ return 64000;
47
+ }
48
+ return undefined;
49
+ }
50
+ function applyAgyGenerationDefaults(model, generationConfig, headerStyle) {
51
+ if (headerStyle !== "antigravity") {
52
+ return;
53
+ }
54
+ const maxOutputTokens = getAgyMaxOutputTokens(model);
55
+ if (maxOutputTokens !== undefined) {
56
+ generationConfig.maxOutputTokens = maxOutputTokens;
57
+ delete generationConfig.max_output_tokens;
58
+ }
59
+ }
60
+ function orderAntigravityEnvelope(body) {
61
+ const ordered = {};
62
+ const remaining = new Set(Object.keys(body));
63
+ for (const key of ANTIGRAVITY_ENVELOPE_FIELD_ORDER) {
64
+ if (key in body) {
65
+ ordered[key] = body[key];
66
+ remaining.delete(key);
67
+ }
68
+ }
69
+ for (const key of remaining) {
70
+ ordered[key] = body[key];
71
+ }
72
+ return ordered;
73
+ }
74
+ function buildSignatureSessionKey(sessionId, model, conversationKey, projectKey) {
75
+ const modelKey = typeof model === "string" && model.trim() ? model.toLowerCase() : "unknown";
76
+ const projectPart = typeof projectKey === "string" && projectKey.trim()
77
+ ? projectKey.trim()
78
+ : "default";
79
+ const conversationPart = typeof conversationKey === "string" && conversationKey.trim()
80
+ ? conversationKey.trim()
81
+ : "default";
82
+ return `${sessionId}:${modelKey}:${projectPart}:${conversationPart}`;
83
+ }
84
+ /**
85
+ * JSON.stringify replacer — operates AT the serialization layer.
86
+ * Every key-value pair passes through this function during stringify.
87
+ * Nothing can bypass it — no code path, no nesting depth, no object structure.
88
+ *
89
+ * Preserves Schema objects in tool declarations (e.g., {type: "boolean"})
90
+ * by checking for JSON Schema primitive types. Everything else that's
91
+ * a non-string `thinking` value gets flattened to "".
92
+ */
93
+ const JSON_SCHEMA_TYPES = new Set(["boolean", "string", "number", "integer", "array", "object"]);
94
+ function thinkingSafeReplacer(key, value) {
95
+ if (key === "thinking" && typeof value === "object" && value !== null) {
96
+ // Preserve Schema objects in tool declarations (e.g., {type: "boolean"})
97
+ const rec = value;
98
+ if (typeof rec.type === "string" && JSON_SCHEMA_TYPES.has(rec.type)) {
99
+ return value;
100
+ }
101
+ // Flatten any non-string, non-Schema thinking to empty string
102
+ return "";
103
+ }
104
+ return value;
105
+ }
106
+ /** Stringify with built-in thinking sanitization. Impossible to bypass. */
107
+ function ensureThinkingFields(obj) {
108
+ if (!obj || typeof obj !== "object")
109
+ return;
110
+ if (Array.isArray(obj)) {
111
+ for (const item of obj)
112
+ ensureThinkingFields(item);
113
+ return;
114
+ }
115
+ const rec = obj;
116
+ // Fix: check for missing OR undefined OR non-string thinking field.
117
+ // JSON.stringify silently drops undefined values, so key-exists-but-undefined
118
+ // produces { type: "thinking", signature: "..." } with NO thinking field.
119
+ if (rec.type === "thinking" && typeof rec.thinking !== "string") {
120
+ rec.thinking = "";
121
+ }
122
+ if (rec.thought === true && typeof rec.text !== "string") {
123
+ rec.text = "";
124
+ }
125
+ for (const val of Object.values(rec)) {
126
+ ensureThinkingFields(val);
127
+ }
128
+ }
129
+ function safeStringify(obj) {
130
+ ensureThinkingFields(obj);
131
+ return JSON.stringify(obj, thinkingSafeReplacer);
132
+ }
133
+ function shouldCacheThinkingSignatures(model) {
134
+ if (typeof model !== "string")
135
+ return false;
136
+ const lower = model.toLowerCase();
137
+ // Both Claude and Gemini 3 models require thought signature caching
138
+ // for multi-turn conversations with function calling
139
+ return lower.includes("claude") || lower.includes("gemini-3");
140
+ }
141
+ function hashConversationSeed(seed) {
142
+ return crypto.createHash("sha256").update(seed, "utf8").digest("hex").slice(0, 16);
143
+ }
144
+ function extractTextFromContent(content) {
145
+ if (typeof content === "string") {
146
+ return content;
147
+ }
148
+ if (!Array.isArray(content)) {
149
+ return "";
150
+ }
151
+ for (const block of content) {
152
+ if (!block || typeof block !== "object") {
153
+ continue;
154
+ }
155
+ const anyBlock = block;
156
+ if (typeof anyBlock.text === "string") {
157
+ return anyBlock.text;
158
+ }
159
+ if (anyBlock.text && typeof anyBlock.text === "object" && typeof anyBlock.text.text === "string") {
160
+ return anyBlock.text.text;
161
+ }
162
+ }
163
+ return "";
164
+ }
165
+ function extractConversationSeedFromMessages(messages) {
166
+ const system = messages.find((message) => message?.role === "system");
167
+ const users = messages.filter((message) => message?.role === "user");
168
+ const firstUser = users[0];
169
+ const lastUser = users.length > 0 ? users[users.length - 1] : undefined;
170
+ const systemText = system ? extractTextFromContent(system.content) : "";
171
+ const userText = firstUser ? extractTextFromContent(firstUser.content) : "";
172
+ const fallbackUserText = !userText && lastUser ? extractTextFromContent(lastUser.content) : "";
173
+ return [systemText, userText || fallbackUserText].filter(Boolean).join("|");
174
+ }
175
+ function extractConversationSeedFromContents(contents) {
176
+ const users = contents.filter((content) => content?.role === "user");
177
+ const firstUser = users[0];
178
+ const lastUser = users.length > 0 ? users[users.length - 1] : undefined;
179
+ const primaryUser = firstUser && Array.isArray(firstUser.parts) ? extractTextFromContent(firstUser.parts) : "";
180
+ if (primaryUser) {
181
+ return primaryUser;
182
+ }
183
+ if (lastUser && Array.isArray(lastUser.parts)) {
184
+ return extractTextFromContent(lastUser.parts);
185
+ }
186
+ return "";
187
+ }
188
+ function resolveConversationKey(requestPayload) {
189
+ const anyPayload = requestPayload;
190
+ const candidates = [
191
+ anyPayload.conversationId,
192
+ anyPayload.conversation_id,
193
+ anyPayload.thread_id,
194
+ anyPayload.threadId,
195
+ anyPayload.chat_id,
196
+ anyPayload.chatId,
197
+ anyPayload.sessionId,
198
+ anyPayload.session_id,
199
+ anyPayload.metadata?.conversation_id,
200
+ anyPayload.metadata?.conversationId,
201
+ anyPayload.metadata?.thread_id,
202
+ anyPayload.metadata?.threadId,
203
+ ];
204
+ for (const candidate of candidates) {
205
+ if (typeof candidate === "string" && candidate.trim()) {
206
+ return candidate.trim();
207
+ }
208
+ }
209
+ const systemSeed = extractTextFromContent(anyPayload.systemInstruction?.parts
210
+ ?? anyPayload.systemInstruction
211
+ ?? anyPayload.system
212
+ ?? anyPayload.system_instruction);
213
+ const messageSeed = Array.isArray(anyPayload.messages)
214
+ ? extractConversationSeedFromMessages(anyPayload.messages)
215
+ : Array.isArray(anyPayload.contents)
216
+ ? extractConversationSeedFromContents(anyPayload.contents)
217
+ : "";
218
+ const seed = [systemSeed, messageSeed].filter(Boolean).join("|");
219
+ if (!seed) {
220
+ return undefined;
221
+ }
222
+ return `seed-${hashConversationSeed(seed)}`;
223
+ }
224
+ function resolveConversationKeyFromRequests(requestObjects) {
225
+ for (const req of requestObjects) {
226
+ const key = resolveConversationKey(req);
227
+ if (key) {
228
+ return key;
229
+ }
230
+ }
231
+ return undefined;
232
+ }
233
+ function resolveProjectKey(candidate, fallback) {
234
+ if (typeof candidate === "string" && candidate.trim()) {
235
+ return candidate.trim();
236
+ }
237
+ if (typeof fallback === "string" && fallback.trim()) {
238
+ return fallback.trim();
239
+ }
240
+ return undefined;
241
+ }
242
+ function formatDebugLinesForThinking(lines) {
243
+ const cleaned = lines
244
+ .map((line) => line.trim())
245
+ .filter((line) => line.length > 0)
246
+ .slice(-50);
247
+ const prelude = `[ThinkingResolution] source=debug_tui lines=${cleaned.length}`;
248
+ return `${DEBUG_MESSAGE_PREFIX}\n- ${prelude}\n${cleaned.map((line) => `- ${line}`).join("\n")}`;
249
+ }
250
+ function injectDebugThinking(response, debugText) {
251
+ if (!response || typeof response !== "object") {
252
+ return response;
253
+ }
254
+ const resp = response;
255
+ if (Array.isArray(resp.candidates) && resp.candidates.length > 0) {
256
+ const candidates = resp.candidates.slice();
257
+ const first = candidates[0];
258
+ if (first &&
259
+ typeof first === "object" &&
260
+ first.content &&
261
+ typeof first.content === "object" &&
262
+ Array.isArray(first.content.parts)) {
263
+ const parts = [{ thought: true, text: debugText }, ...first.content.parts];
264
+ candidates[0] = { ...first, content: { ...first.content, parts } };
265
+ return { ...resp, candidates };
266
+ }
267
+ return resp;
268
+ }
269
+ if (Array.isArray(resp.content)) {
270
+ const content = [{ type: "thinking", thinking: debugText }, ...resp.content];
271
+ return { ...resp, content };
272
+ }
273
+ if (!resp.reasoning_content) {
274
+ return { ...resp, reasoning_content: debugText };
275
+ }
276
+ return resp;
277
+ }
278
+ /**
279
+ * Synthetic thinking placeholder text used when keep_thinking=true but debug mode is off.
280
+ * Injected via the same path as debug text (injectDebugThinking) to ensure consistent
281
+ * signature caching and multi-turn handling.
282
+ */
283
+ const SYNTHETIC_THINKING_PLACEHOLDER = "[Thinking preserved]\n";
284
+ function stripInjectedDebugFromParts(parts) {
285
+ if (!Array.isArray(parts)) {
286
+ return parts;
287
+ }
288
+ // Use .map() with empty text sentinels instead of .filter() to preserve
289
+ // array indices and prevent prompt cache invalidation.
290
+ return parts.map((part) => {
291
+ if (!part || typeof part !== "object") {
292
+ return part;
293
+ }
294
+ const record = part;
295
+ const text = typeof record.text === "string"
296
+ ? record.text
297
+ : typeof record.thinking === "string"
298
+ ? record.thinking
299
+ : undefined;
300
+ // Replace debug blocks and synthetic thinking placeholders with empty text sentinel
301
+ if (text && (text.startsWith(DEBUG_MESSAGE_PREFIX) || text.startsWith(SYNTHETIC_THINKING_PLACEHOLDER.trim()))) {
302
+ const sentinel = { text: "." };
303
+ if (record.cache_control !== undefined)
304
+ sentinel.cache_control = record.cache_control;
305
+ return sentinel;
306
+ }
307
+ return part;
308
+ });
309
+ }
310
+ function stripInjectedDebugFromRequestPayload(payload) {
311
+ const anyPayload = payload;
312
+ if (Array.isArray(anyPayload.contents)) {
313
+ anyPayload.contents = anyPayload.contents.map((content) => {
314
+ if (!content || typeof content !== "object") {
315
+ return content;
316
+ }
317
+ if (Array.isArray(content.parts)) {
318
+ return { ...content, parts: stripInjectedDebugFromParts(content.parts) };
319
+ }
320
+ if (Array.isArray(content.content)) {
321
+ return { ...content, content: stripInjectedDebugFromParts(content.content) };
322
+ }
323
+ return content;
324
+ });
325
+ }
326
+ if (Array.isArray(anyPayload.messages)) {
327
+ anyPayload.messages = anyPayload.messages.map((message) => {
328
+ if (!message || typeof message !== "object") {
329
+ return message;
330
+ }
331
+ if (Array.isArray(message.content)) {
332
+ return { ...message, content: stripInjectedDebugFromParts(message.content) };
333
+ }
334
+ return message;
335
+ });
336
+ }
337
+ }
338
+ function isValidRequestPart(part) {
339
+ if (!part || typeof part !== "object") {
340
+ return false;
341
+ }
342
+ const record = part;
343
+ return (Object.prototype.hasOwnProperty.call(record, "text") ||
344
+ Object.prototype.hasOwnProperty.call(record, "functionCall") ||
345
+ Object.prototype.hasOwnProperty.call(record, "functionResponse") ||
346
+ Object.prototype.hasOwnProperty.call(record, "inlineData") ||
347
+ Object.prototype.hasOwnProperty.call(record, "fileData") ||
348
+ Object.prototype.hasOwnProperty.call(record, "executableCode") ||
349
+ Object.prototype.hasOwnProperty.call(record, "codeExecutionResult") ||
350
+ Object.prototype.hasOwnProperty.call(record, "thought"));
351
+ }
352
+ function sanitizeRequestPayloadForAntigravity(payload) {
353
+ const anyPayload = payload;
354
+ if (Array.isArray(anyPayload.contents)) {
355
+ // Use .map() instead of .map().filter() to preserve array indices for prompt cache stability
356
+ anyPayload.contents = anyPayload.contents
357
+ .map((content) => {
358
+ if (!content || typeof content !== "object") {
359
+ // Preserve non-object entries as empty sentinel instead of filtering
360
+ return { role: "user", parts: [{ text: "." }] };
361
+ }
362
+ const contentRecord = content;
363
+ const rawParts = Array.isArray(contentRecord.parts) ? contentRecord.parts : [];
364
+ let foundFirstFunctionCall = false;
365
+ const sanitizedParts = rawParts.map((part) => {
366
+ // Replace invalid parts with sentinel to preserve array indices for cache stability
367
+ if (!isValidRequestPart(part)) {
368
+ return { text: "." };
369
+ }
370
+ return part;
371
+ }).map((part) => {
372
+ if (part && typeof part === "object" && part.functionCall) {
373
+ let sig = part.thoughtSignature || part.thought_signature;
374
+ // Only the first functionCall part in a block should have the signature.
375
+ // If it's the first one and missing a valid signature, inject the sentinel
376
+ // to prevent the API from rejecting the request with a 400 error.
377
+ if (!foundFirstFunctionCall) {
378
+ foundFirstFunctionCall = true;
379
+ if (!sig || sig.length < MIN_SIGNATURE_LENGTH) {
380
+ sig = SKIP_THOUGHT_SIGNATURE;
381
+ }
382
+ }
383
+ else {
384
+ // Parallel function calls MUST NOT have a signature
385
+ sig = undefined;
386
+ }
387
+ if (sig) {
388
+ return { ...part, thought_signature: sig, thoughtSignature: sig };
389
+ }
390
+ // If not the first part, just return the part without adding any signature keys
391
+ const newPart = { ...part };
392
+ delete newPart.thoughtSignature;
393
+ delete newPart.thought_signature;
394
+ return newPart;
395
+ }
396
+ return part;
397
+ });
398
+ if (sanitizedParts.length === 0) {
399
+ // Preserve as empty text sentinel instead of filtering out
400
+ return { ...contentRecord, parts: [{ text: "." }] };
401
+ }
402
+ return {
403
+ ...contentRecord,
404
+ parts: sanitizedParts,
405
+ };
406
+ });
407
+ }
408
+ if (Array.isArray(anyPayload.messages)) {
409
+ anyPayload.messages = anyPayload.messages.map((message) => {
410
+ if (!message || typeof message !== "object") {
411
+ return { role: "user", content: [{ type: "text", text: "." }] };
412
+ }
413
+ const messageRecord = message;
414
+ const rawContent = Array.isArray(messageRecord.content) ? messageRecord.content : messageRecord.content;
415
+ if (!Array.isArray(rawContent)) {
416
+ return messageRecord;
417
+ }
418
+ const sanitizedContent = rawContent.map((block) => {
419
+ if (!block || typeof block !== "object") {
420
+ return { type: "text", text: "." };
421
+ }
422
+ const blockRecord = block;
423
+ if (blockRecord.type === "text") {
424
+ const text = blockRecord.text;
425
+ if (typeof text !== "string" || text.trim().length === 0) {
426
+ const sentinel = { type: "text", text: "." };
427
+ if (blockRecord.cache_control !== undefined)
428
+ sentinel.cache_control = blockRecord.cache_control;
429
+ return sentinel;
430
+ }
431
+ }
432
+ return block;
433
+ });
434
+ if (sanitizedContent.length === 0) {
435
+ return { ...messageRecord, content: [{ type: "text", text: "." }] };
436
+ }
437
+ return {
438
+ ...messageRecord,
439
+ content: sanitizedContent,
440
+ };
441
+ });
442
+ }
443
+ const systemInstruction = anyPayload.systemInstruction;
444
+ if (systemInstruction && typeof systemInstruction === "object" && !Array.isArray(systemInstruction)) {
445
+ const sys = systemInstruction;
446
+ if (Array.isArray(sys.parts)) {
447
+ // Use .map() with sentinels instead of .filter() to preserve array indices
448
+ // and prevent prompt cache invalidation from index shifts.
449
+ const sanitizedSystemParts = sys.parts.map((part) => {
450
+ if (isValidRequestPart(part))
451
+ return part;
452
+ const record = part;
453
+ const sentinel = { text: "." };
454
+ if (record?.cache_control !== undefined)
455
+ sentinel.cache_control = record.cache_control;
456
+ return sentinel;
457
+ });
458
+ // Only delete systemInstruction if ALL parts were invalid (all sentinels, no real content)
459
+ const hasRealContent = sanitizedSystemParts.some((p) => p && typeof p === "object" && typeof p.text === "string" && p.text !== ".");
460
+ if (hasRealContent) {
461
+ sys.parts = sanitizedSystemParts;
462
+ }
463
+ else {
464
+ delete anyPayload.systemInstruction;
465
+ }
466
+ }
467
+ }
468
+ }
469
+ function isGeminiToolUsePart(part) {
470
+ return !!(part && typeof part === "object" && (part.functionCall || part.tool_use || part.toolUse));
471
+ }
472
+ function isGeminiThinkingPart(part) {
473
+ return !!(part &&
474
+ typeof part === "object" &&
475
+ (part.thought === true || part.type === "thinking" || part.type === "reasoning"));
476
+ }
477
+ // Sentinel value used when signature recovery fails - allows Claude to handle gracefully
478
+ // by redacting the thinking block instead of rejecting the request entirely.
479
+ // Reference: LLM-API-Key-Proxy uses this pattern for Gemini 3 tool calls.
480
+ const SENTINEL_SIGNATURE = "skip_thought_signature_validator";
481
+ function getThinkingPartText(part) {
482
+ if (!part || typeof part !== "object") {
483
+ return "";
484
+ }
485
+ if (typeof part.text === "string") {
486
+ return part.text;
487
+ }
488
+ if (typeof part.thinking === "string") {
489
+ return part.thinking;
490
+ }
491
+ return "";
492
+ }
493
+ function hasCachedMatchingSignature(part, sessionId) {
494
+ if (!part || typeof part !== "object") {
495
+ return false;
496
+ }
497
+ const text = getThinkingPartText(part);
498
+ if (!text) {
499
+ return false;
500
+ }
501
+ const expectedSignature = getCachedSignature(sessionId, text);
502
+ if (!expectedSignature) {
503
+ return false;
504
+ }
505
+ if (part.thought === true) {
506
+ return part.thoughtSignature === expectedSignature;
507
+ }
508
+ return part.signature === expectedSignature;
509
+ }
510
+ function ensureThoughtSignature(part, sessionId) {
511
+ if (!part || typeof part !== "object") {
512
+ return part;
513
+ }
514
+ if (!sessionId) {
515
+ return part;
516
+ }
517
+ const text = getThinkingPartText(part);
518
+ if (!text) {
519
+ return part;
520
+ }
521
+ if (part.thought === true) {
522
+ return { ...part, thoughtSignature: SENTINEL_SIGNATURE };
523
+ }
524
+ if (part.type === "thinking" || part.type === "reasoning" || part.type === "redacted_thinking") {
525
+ return { ...part, signature: SENTINEL_SIGNATURE };
526
+ }
527
+ return part;
528
+ }
529
+ function hasSignedThinkingPart(part, sessionId) {
530
+ if (!part || typeof part !== "object") {
531
+ return false;
532
+ }
533
+ if (part.thought === true) {
534
+ if (part.thoughtSignature === SENTINEL_SIGNATURE || part.thoughtSignature === SKIP_THOUGHT_SIGNATURE) {
535
+ return true;
536
+ }
537
+ if (typeof part.thoughtSignature !== "string" || part.thoughtSignature.length < MIN_SIGNATURE_LENGTH) {
538
+ return false;
539
+ }
540
+ if (!sessionId) {
541
+ return true;
542
+ }
543
+ return hasCachedMatchingSignature(part, sessionId);
544
+ }
545
+ if (part.type === "thinking" || part.type === "reasoning" || part.type === "redacted_thinking") {
546
+ if (part.signature === SENTINEL_SIGNATURE || part.signature === SKIP_THOUGHT_SIGNATURE) {
547
+ return true;
548
+ }
549
+ if (typeof part.signature !== "string" || part.signature.length < MIN_SIGNATURE_LENGTH) {
550
+ return false;
551
+ }
552
+ if (!sessionId) {
553
+ return true;
554
+ }
555
+ return hasCachedMatchingSignature(part, sessionId);
556
+ }
557
+ return false;
558
+ }
559
+ function ensureThinkingBeforeToolUseInContents(contents, signatureSessionKey) {
560
+ return contents.map((content) => {
561
+ if (!content || typeof content !== "object" || !Array.isArray(content.parts)) {
562
+ return content;
563
+ }
564
+ const role = content.role;
565
+ if (role !== "model" && role !== "assistant") {
566
+ return content;
567
+ }
568
+ const parts = content.parts;
569
+ const hasToolUse = parts.some(isGeminiToolUsePart);
570
+ if (!hasToolUse) {
571
+ return content;
572
+ }
573
+ // Check if any thinking part has a valid signed signature
574
+ const hasSignedThinking = parts.some(p => isGeminiThinkingPart(p) && hasSignedThinkingPart(ensureThoughtSignature(p, signatureSessionKey), signatureSessionKey));
575
+ if (hasSignedThinking) {
576
+ // Ensure signatures on thinking parts in-place — NO reordering to preserve array indices (cache-friendly)
577
+ return { ...content, parts: parts.map(p => isGeminiThinkingPart(p) ? ensureThoughtSignature(p, signatureSessionKey) : p) };
578
+ }
579
+ // Replace thinking parts with sentinels in-place to preserve array indices (cache-friendly).
580
+ // Deleting parts via .filter() shifts array indices → changes hash → busts prompt cache.
581
+ const lastThinking = defaultSignatureStore.get(signatureSessionKey);
582
+ log.debug("Replacing thinking with sentinels in-place", { signatureSessionKey, hasCachedSig: !!lastThinking });
583
+ const newParts = parts.map(p => {
584
+ if (!isGeminiThinkingPart(p))
585
+ return p;
586
+ const cc = p.cache_control;
587
+ // Use plain empty text part — thinking-format sentinels get converted by the proxy
588
+ // into Claude thinking blocks missing the required `thinking` field.
589
+ const sentinel = { text: "." };
590
+ if (cc)
591
+ sentinel.cache_control = cc;
592
+ return sentinel;
593
+ });
594
+ return { ...content, parts: newParts };
595
+ });
596
+ }
597
+ function ensureMessageThinkingSignature(block, sessionId) {
598
+ if (!block || typeof block !== "object") {
599
+ return block;
600
+ }
601
+ if (block.type !== "thinking" && block.type !== "redacted_thinking") {
602
+ return block;
603
+ }
604
+ const text = getThinkingPartText(block);
605
+ if (!text) {
606
+ return block;
607
+ }
608
+ if (!sessionId) {
609
+ return block;
610
+ }
611
+ return { ...block, signature: SKIP_THOUGHT_SIGNATURE };
612
+ }
613
+ function hasToolUseInContents(contents) {
614
+ return contents.some((content) => {
615
+ if (!content || typeof content !== "object" || !Array.isArray(content.parts)) {
616
+ return false;
617
+ }
618
+ return content.parts.some(isGeminiToolUsePart);
619
+ });
620
+ }
621
+ function hasSignedThinkingInContents(contents, sessionId) {
622
+ return contents.some((content) => {
623
+ if (!content || typeof content !== "object" || !Array.isArray(content.parts)) {
624
+ return false;
625
+ }
626
+ return content.parts.some((part) => hasSignedThinkingPart(part, sessionId));
627
+ });
628
+ }
629
+ function hasToolUseInMessages(messages) {
630
+ return messages.some((message) => {
631
+ if (!message || typeof message !== "object" || !Array.isArray(message.content)) {
632
+ return false;
633
+ }
634
+ return message.content.some((block) => block && typeof block === "object" && (block.type === "tool_use" || block.type === "tool_result"));
635
+ });
636
+ }
637
+ function hasSignedThinkingInMessages(messages, sessionId) {
638
+ return messages.some((message) => {
639
+ if (!message || typeof message !== "object" || !Array.isArray(message.content)) {
640
+ return false;
641
+ }
642
+ return message.content.some((block) => hasSignedThinkingPart(block, sessionId));
643
+ });
644
+ }
645
+ function ensureThinkingBeforeToolUseInMessages(messages, signatureSessionKey) {
646
+ return messages.map((message) => {
647
+ if (!message || typeof message !== "object" || !Array.isArray(message.content)) {
648
+ return message;
649
+ }
650
+ if (message.role !== "assistant") {
651
+ return message;
652
+ }
653
+ const blocks = message.content;
654
+ const hasToolUse = blocks.some((b) => b && typeof b === "object" && (b.type === "tool_use" || b.type === "tool_result"));
655
+ if (!hasToolUse) {
656
+ return message;
657
+ }
658
+ const isThinkingBlock = (b) => b && typeof b === "object" && (b.type === "thinking" || b.type === "redacted_thinking");
659
+ // Check if any thinking block has a valid signed signature
660
+ const hasSignedThinking = blocks.some((b) => isThinkingBlock(b) && hasSignedThinkingPart(ensureMessageThinkingSignature(b, signatureSessionKey), signatureSessionKey));
661
+ if (hasSignedThinking) {
662
+ // Ensure signatures on thinking blocks in-place — NO reordering to preserve array indices (cache-friendly)
663
+ return { ...message, content: blocks.map((b) => isThinkingBlock(b) ? ensureMessageThinkingSignature(b, signatureSessionKey) : b) };
664
+ }
665
+ // Replace thinking blocks with sentinels in-place to preserve array indices (cache-friendly).
666
+ // Deleting/reordering via .filter() shifts indices → changes hash → busts prompt cache.
667
+ const lastThinking = defaultSignatureStore.get(signatureSessionKey);
668
+ log.debug("Replacing thinking with sentinels in-place (Messages format)", { signatureSessionKey, hasCachedSig: !!lastThinking });
669
+ return { ...message, content: blocks.map((b) => {
670
+ if (!isThinkingBlock(b))
671
+ return b;
672
+ const thinkingText = lastThinking ? lastThinking.text : (typeof b.thinking === "string" ? b.thinking : typeof b.text === "string" ? b.text : "");
673
+ const cc = b.cache_control;
674
+ // Use plain empty text part — thinking-format sentinels get converted by the proxy
675
+ // into Claude thinking blocks missing the required `thinking` field.
676
+ const sentinel = { text: "." };
677
+ if (cc)
678
+ sentinel.cache_control = cc;
679
+ return sentinel;
680
+ }) };
681
+ });
682
+ }
683
+ /**
684
+ * Gets the stable session ID for this plugin instance.
685
+ */
686
+ export function getPluginSessionId() {
687
+ return PLUGIN_SESSION_ID;
688
+ }
689
+ let _lastCacheStats = null;
690
+ export function getLastCacheStats() {
691
+ return _lastCacheStats;
692
+ }
693
+ function generateSyntheticProjectId() {
694
+ const adjectives = ["useful", "bright", "swift", "calm", "bold"];
695
+ const nouns = ["fuze", "wave", "spark", "flow", "core"];
696
+ const adj = adjectives[Math.floor(Math.random() * adjectives.length)];
697
+ const noun = nouns[Math.floor(Math.random() * nouns.length)];
698
+ const randomPart = crypto.randomUUID().slice(0, 5).toLowerCase();
699
+ return `${adj}-${noun}-${randomPart}`;
700
+ }
701
+ const STREAM_ACTION = "streamGenerateContent";
702
+ /**
703
+ * Detects requests headed to the Google Generative Language API so we can intercept them.
704
+ */
705
+ export function isGenerativeLanguageRequest(input) {
706
+ return typeof input === "string" && input.includes("generativelanguage.googleapis.com");
707
+ }
708
+ export function prepareAntigravityRequest(input, init, accessToken, projectId, endpointOverride, headerStyle = "antigravity", forceThinkingRecovery = false, options) {
709
+ const baseInit = { ...init };
710
+ const headers = new Headers(init?.headers ?? {});
711
+ let resolvedProjectId = projectId?.trim() || "";
712
+ let toolDebugMissing = 0;
713
+ const toolDebugSummaries = [];
714
+ let toolDebugPayload;
715
+ let sessionId;
716
+ let needsSignedThinkingWarmup = false;
717
+ let thinkingRecoveryMessage;
718
+ if (!isGenerativeLanguageRequest(input)) {
719
+ return {
720
+ request: input,
721
+ init: { ...baseInit, headers },
722
+ streaming: false,
723
+ headerStyle,
724
+ };
725
+ }
726
+ headers.set("Authorization", `Bearer ${accessToken}`);
727
+ headers.delete("x-api-key");
728
+ headers.delete("x-goog-api-key");
729
+ headers.delete("x-session-affinity");
730
+ // Strip x-goog-user-project header to prevent 403 auth/license conflicts.
731
+ // This header is added by OpenCode/AI SDK and can force project-level checks
732
+ // that are not required for Antigravity/Gemini CLI OAuth requests.
733
+ headers.delete("x-goog-user-project");
734
+ const match = input.match(/\/models\/([^:]+):(\w+)/);
735
+ if (!match) {
736
+ return {
737
+ request: input,
738
+ init: { ...baseInit, headers },
739
+ streaming: false,
740
+ headerStyle,
741
+ };
742
+ }
743
+ const [, rawModel = "", rawAction = ""] = match;
744
+ const requestedModel = rawModel;
745
+ const resolved = resolveModelForHeaderStyle(rawModel, headerStyle);
746
+ let effectiveModel = resolved.actualModel;
747
+ const streaming = rawAction === STREAM_ACTION;
748
+ const defaultEndpoint = headerStyle === "gemini-cli" ? GEMINI_CLI_ENDPOINT : ANTIGRAVITY_ENDPOINT;
749
+ const baseEndpoint = endpointOverride ?? defaultEndpoint;
750
+ const transformedUrl = `${baseEndpoint}/v1internal:${rawAction}${streaming ? "?alt=sse" : ""}`;
751
+ const isClaude = isClaudeModel(resolved.actualModel);
752
+ const isClaudeThinking = isClaudeThinkingModel(resolved.actualModel);
753
+ const keepThinkingEnabled = getKeepThinking();
754
+ // Tier-based thinking configuration from model resolver (can be overridden by variant config)
755
+ let tierThinkingBudget = resolved.thinkingBudget;
756
+ let tierThinkingLevel = resolved.thinkingLevel;
757
+ let signatureSessionKey = buildSignatureSessionKey(PLUGIN_SESSION_ID, effectiveModel, undefined, resolveProjectKey(projectId));
758
+ let body = baseInit.body;
759
+ if (typeof baseInit.body === "string" && baseInit.body) {
760
+ try {
761
+ const parsedBody = JSON.parse(baseInit.body);
762
+ const isWrapped = typeof parsedBody.project === "string" && "request" in parsedBody;
763
+ if (isWrapped) {
764
+ const wrappedBody = {
765
+ ...parsedBody,
766
+ model: effectiveModel,
767
+ };
768
+ if (headerStyle === "antigravity") {
769
+ if (typeof wrappedBody.requestId !== "string" || !wrappedBody.requestId) {
770
+ wrappedBody.requestId = buildAntigravityRequestId("agent");
771
+ }
772
+ if (typeof wrappedBody.userAgent !== "string" || !wrappedBody.userAgent) {
773
+ wrappedBody.userAgent = "antigravity";
774
+ }
775
+ if (typeof wrappedBody.requestType !== "string" || !wrappedBody.requestType) {
776
+ wrappedBody.requestType = "agent";
777
+ }
778
+ }
779
+ // Some callers may already send an Antigravity-wrapped body.
780
+ // We still need to sanitize Claude thinking blocks (remove cache_control)
781
+ // and attach a stable sessionId so multi-turn signature caching works.
782
+ const requestRoot = wrappedBody.request;
783
+ const requestObjects = [];
784
+ if (requestRoot && typeof requestRoot === "object") {
785
+ requestObjects.push(requestRoot);
786
+ const nested = requestRoot.request;
787
+ if (nested && typeof nested === "object") {
788
+ requestObjects.push(nested);
789
+ }
790
+ }
791
+ const conversationKey = resolveConversationKeyFromRequests(requestObjects);
792
+ // Strip tier suffix from model for cache key to prevent cache misses on tier change
793
+ // e.g., "claude-opus-4-6-thinking-high" -> "claude-opus-4-6-thinking"
794
+ const modelForCacheKey = effectiveModel.replace(/-(minimal|low|medium|high)$/i, "");
795
+ signatureSessionKey = buildSignatureSessionKey(PLUGIN_SESSION_ID, modelForCacheKey, conversationKey, resolveProjectKey(parsedBody.project));
796
+ if (requestObjects.length > 0) {
797
+ sessionId = signatureSessionKey;
798
+ }
799
+ for (const req of requestObjects) {
800
+ // Use stable session ID for signature caching across multi-turn conversations
801
+ req.sessionId = signatureSessionKey;
802
+ stripInjectedDebugFromRequestPayload(req);
803
+ if (isClaude) {
804
+ // Step 0: Sanitize cross-model metadata (strips Gemini signatures when sending to Claude)
805
+ sanitizeCrossModelPayloadInPlace(req, { targetModel: effectiveModel });
806
+ // Step 1: Strip corrupted/unsigned thinking blocks FIRST
807
+ deepFilterThinkingBlocks(req, signatureSessionKey, getCachedSignature, true);
808
+ // Step 2: THEN inject signed thinking from cache (after stripping)
809
+ if (isClaudeThinking && keepThinkingEnabled && Array.isArray(req.contents)) {
810
+ req.contents = ensureThinkingBeforeToolUseInContents(req.contents, signatureSessionKey);
811
+ }
812
+ if (isClaudeThinking && keepThinkingEnabled && Array.isArray(req.messages)) {
813
+ req.messages = ensureThinkingBeforeToolUseInMessages(req.messages, signatureSessionKey);
814
+ }
815
+ // Step 3: Apply tool pairing fixes (ID assignment, response matching, orphan recovery)
816
+ applyToolPairingFixes(req, true);
817
+ }
818
+ }
819
+ // Guard against assistant prefill: Claude rejects conversations ending
820
+ // with model/assistant messages. After context compaction, the conversation
821
+ // can end with a model message — append synthetic user message to fix.
822
+ if (isClaude) {
823
+ for (const req of requestObjects) {
824
+ if (Array.isArray(req.contents)) {
825
+ const contents = req.contents;
826
+ const lastContent = contents[contents.length - 1];
827
+ if (lastContent?.role === "model" || lastContent?.role === "assistant") {
828
+ contents.push({ role: "user", parts: [{ text: "[Continue]" }] });
829
+ }
830
+ }
831
+ if (Array.isArray(req.messages)) {
832
+ const messages = req.messages;
833
+ const lastMessage = messages[messages.length - 1];
834
+ if (lastMessage?.role === "model" || lastMessage?.role === "assistant") {
835
+ messages.push({ role: "user", parts: [{ text: "[Continue]" }] });
836
+ }
837
+ }
838
+ }
839
+ }
840
+ if (isClaudeThinking && keepThinkingEnabled && sessionId) {
841
+ const hasToolUse = requestObjects.some((req) => (Array.isArray(req.contents) && hasToolUseInContents(req.contents)) ||
842
+ (Array.isArray(req.messages) && hasToolUseInMessages(req.messages)));
843
+ const hasSignedThinking = requestObjects.some((req) => (Array.isArray(req.contents) && hasSignedThinkingInContents(req.contents, signatureSessionKey)) ||
844
+ (Array.isArray(req.messages) && hasSignedThinkingInMessages(req.messages, signatureSessionKey)));
845
+ const hasCachedThinking = defaultSignatureStore.has(signatureSessionKey);
846
+ needsSignedThinkingWarmup = hasToolUse && !hasSignedThinking && !hasCachedThinking;
847
+ }
848
+ body = safeStringify(headerStyle === "antigravity" ? orderAntigravityEnvelope(wrappedBody) : wrappedBody);
849
+ }
850
+ else {
851
+ const requestPayload = { ...parsedBody };
852
+ const rawGenerationConfig = requestPayload.generationConfig;
853
+ const extraBody = requestPayload.extra_body;
854
+ const variantConfig = extractVariantThinkingConfig(requestPayload.providerOptions, rawGenerationConfig);
855
+ const isGemini3 = effectiveModel.toLowerCase().includes("gemini-3");
856
+ log.debug(`[ThinkingResolution] rawModel=${rawModel} resolvedModel=${effectiveModel} resolvedTier=${tierThinkingLevel ?? "none"} variantLevel=${variantConfig?.thinkingLevel ?? "none"} variantBudget=${variantConfig?.thinkingBudget ?? "none"} providerOptions.google=${JSON.stringify(requestPayload.providerOptions?.google ?? null)} generationConfig.thinkingConfig=${JSON.stringify(rawGenerationConfig?.thinkingConfig ?? null)}`);
857
+ if (variantConfig?.thinkingLevel && isGemini3) {
858
+ // Gemini 3 native format - use thinkingLevel directly
859
+ const variantModelBase = rawModel
860
+ .replace(/-preview-customtools$/i, "")
861
+ .replace(/-preview$/i, "")
862
+ .replace(/-(minimal|low|medium|high)$/i, "");
863
+ const variantResolved = resolveModelForHeaderStyle(`${variantModelBase}-${variantConfig.thinkingLevel}`, headerStyle);
864
+ effectiveModel = variantResolved.actualModel;
865
+ tierThinkingBudget = variantResolved.thinkingBudget;
866
+ tierThinkingLevel = variantResolved.thinkingLevel ?? (variantResolved.thinkingBudget ? undefined : variantConfig.thinkingLevel);
867
+ }
868
+ else if (variantConfig?.thinkingBudget) {
869
+ if (isGemini3) {
870
+ // Legacy format for Gemini 3 - convert with deprecation warning
871
+ log.warn("[Deprecated] Using thinkingBudget for Gemini 3 model. Use thinkingLevel instead.");
872
+ tierThinkingLevel = variantConfig.thinkingBudget <= 8192 ? "low"
873
+ : variantConfig.thinkingBudget <= 16384 ? "medium" : "high";
874
+ tierThinkingBudget = undefined;
875
+ }
876
+ else {
877
+ // Claude / Gemini 2.5 - use budget directly
878
+ tierThinkingBudget = variantConfig.thinkingBudget;
879
+ tierThinkingLevel = undefined;
880
+ }
881
+ }
882
+ if (isClaude) {
883
+ if (!requestPayload.toolConfig) {
884
+ requestPayload.toolConfig = {};
885
+ }
886
+ if (typeof requestPayload.toolConfig === "object" && requestPayload.toolConfig !== null) {
887
+ const toolConfig = requestPayload.toolConfig;
888
+ if (!toolConfig.functionCallingConfig) {
889
+ toolConfig.functionCallingConfig = {};
890
+ }
891
+ if (typeof toolConfig.functionCallingConfig === "object" && toolConfig.functionCallingConfig !== null) {
892
+ toolConfig.functionCallingConfig.mode = "VALIDATED";
893
+ }
894
+ }
895
+ }
896
+ // Resolve thinking configuration based on user settings and model capabilities
897
+ // Image generation models don't support thinking - skip thinking config entirely
898
+ const isImageModel = isImageGenerationModel(effectiveModel);
899
+ const userThinkingConfig = isImageModel ? undefined : extractThinkingConfig(requestPayload, rawGenerationConfig, extraBody);
900
+ const hasAssistantHistory = Array.isArray(requestPayload.contents) &&
901
+ requestPayload.contents.some((c) => c?.role === "model" || c?.role === "assistant");
902
+ const effectiveUserThinkingConfig = isImageModel ? undefined : userThinkingConfig;
903
+ // For image models, add imageConfig instead of thinkingConfig
904
+ if (isImageModel) {
905
+ const imageConfig = buildImageGenerationConfig();
906
+ const generationConfig = (rawGenerationConfig ?? {});
907
+ generationConfig.imageConfig = imageConfig;
908
+ // Remove any thinkingConfig that might have been set
909
+ delete generationConfig.thinkingConfig;
910
+ // Set reasonable defaults for image generation
911
+ if (!generationConfig.candidateCount) {
912
+ generationConfig.candidateCount = 1;
913
+ }
914
+ requestPayload.generationConfig = generationConfig;
915
+ // Add safety settings for image generation (permissive to allow creative content)
916
+ if (!requestPayload.safetySettings) {
917
+ requestPayload.safetySettings = [
918
+ { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_ONLY_HIGH" },
919
+ { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_ONLY_HIGH" },
920
+ { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_ONLY_HIGH" },
921
+ { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_ONLY_HIGH" },
922
+ { category: "HARM_CATEGORY_CIVIC_INTEGRITY", threshold: "BLOCK_ONLY_HIGH" },
923
+ ];
924
+ }
925
+ // Image models don't support tools - remove them entirely
926
+ delete requestPayload.tools;
927
+ delete requestPayload.toolConfig;
928
+ // Replace system instruction with a simple image generation prompt
929
+ // Image models should not receive agentic coding assistant instructions
930
+ requestPayload.systemInstruction = {
931
+ parts: [{ text: "You are an AI image generator. Generate images based on user descriptions. Focus on creating high-quality, visually appealing images that match the user's request." }]
932
+ };
933
+ }
934
+ else {
935
+ const finalThinkingConfig = resolveThinkingConfig(effectiveUserThinkingConfig, resolved.isThinkingModel ?? isThinkingCapableModel(effectiveModel), isClaude, hasAssistantHistory);
936
+ const normalizedThinking = normalizeThinkingConfig(finalThinkingConfig);
937
+ if (normalizedThinking) {
938
+ // Use tier-based thinking budget if specified via model suffix, otherwise fall back to user config
939
+ const thinkingBudget = tierThinkingBudget ?? normalizedThinking.thinkingBudget;
940
+ // Build thinking config based on model type
941
+ let thinkingConfig;
942
+ if (isClaudeThinking && headerStyle !== "antigravity") {
943
+ // Claude-on-Gemini fallback uses snake_case keys.
944
+ thinkingConfig = {
945
+ include_thoughts: normalizedThinking.includeThoughts ?? true,
946
+ ...(typeof thinkingBudget === "number" && thinkingBudget > 0
947
+ ? { thinking_budget: thinkingBudget }
948
+ : {}),
949
+ };
950
+ }
951
+ else if (tierThinkingLevel) {
952
+ // Gemini 3 uses thinkingLevel string (low/medium/high)
953
+ thinkingConfig = {
954
+ includeThoughts: normalizedThinking.includeThoughts,
955
+ thinkingLevel: tierThinkingLevel,
956
+ };
957
+ }
958
+ else {
959
+ // Gemini 2.5 and others use numeric budget
960
+ thinkingConfig = {
961
+ includeThoughts: normalizedThinking.includeThoughts,
962
+ ...(typeof thinkingBudget === "number" && thinkingBudget > 0 ? { thinkingBudget } : {}),
963
+ };
964
+ }
965
+ if (rawGenerationConfig) {
966
+ rawGenerationConfig.thinkingConfig = thinkingConfig;
967
+ applyAgyGenerationDefaults(effectiveModel, rawGenerationConfig, headerStyle);
968
+ if (isClaudeThinking && typeof thinkingBudget === "number" && thinkingBudget > 0) {
969
+ const currentMax = (rawGenerationConfig.maxOutputTokens ?? rawGenerationConfig.max_output_tokens);
970
+ if (headerStyle === "antigravity" && !currentMax) {
971
+ rawGenerationConfig.maxOutputTokens = 64000;
972
+ }
973
+ else if (!currentMax || currentMax <= thinkingBudget) {
974
+ rawGenerationConfig.maxOutputTokens = computeClaudeMaxOutputTokens(thinkingBudget);
975
+ }
976
+ if (rawGenerationConfig.max_output_tokens !== undefined) {
977
+ delete rawGenerationConfig.max_output_tokens;
978
+ }
979
+ }
980
+ requestPayload.generationConfig = rawGenerationConfig;
981
+ }
982
+ else {
983
+ const generationConfig = { thinkingConfig };
984
+ if (isClaudeThinking && typeof thinkingBudget === "number" && thinkingBudget > 0) {
985
+ generationConfig.maxOutputTokens = headerStyle === "antigravity"
986
+ ? 64000
987
+ : computeClaudeMaxOutputTokens(thinkingBudget);
988
+ }
989
+ applyAgyGenerationDefaults(effectiveModel, generationConfig, headerStyle);
990
+ requestPayload.generationConfig = generationConfig;
991
+ }
992
+ }
993
+ else if (rawGenerationConfig?.thinkingConfig) {
994
+ delete rawGenerationConfig.thinkingConfig;
995
+ applyAgyGenerationDefaults(effectiveModel, rawGenerationConfig, headerStyle);
996
+ requestPayload.generationConfig = rawGenerationConfig;
997
+ }
998
+ else if (rawGenerationConfig) {
999
+ applyAgyGenerationDefaults(effectiveModel, rawGenerationConfig, headerStyle);
1000
+ requestPayload.generationConfig = rawGenerationConfig;
1001
+ }
1002
+ } // End of else block for non-image models
1003
+ // Clean up thinking fields from extra_body
1004
+ if (extraBody) {
1005
+ delete extraBody.thinkingConfig;
1006
+ delete extraBody.thinking;
1007
+ }
1008
+ delete requestPayload.thinkingConfig;
1009
+ delete requestPayload.thinking;
1010
+ if ("system_instruction" in requestPayload) {
1011
+ requestPayload.systemInstruction = requestPayload.system_instruction;
1012
+ delete requestPayload.system_instruction;
1013
+ }
1014
+ // Normalize cached_content → cachedContent (camelCase) but preserve the value.
1015
+ // OpenCode uses cachedContent for prompt caching — deleting it busts cache.
1016
+ const cachedContentFromExtra = typeof requestPayload.extra_body === "object" && requestPayload.extra_body
1017
+ ? requestPayload.extra_body.cached_content ??
1018
+ requestPayload.extra_body.cachedContent
1019
+ : undefined;
1020
+ const cachedContent = requestPayload.cached_content ??
1021
+ requestPayload.cachedContent ??
1022
+ cachedContentFromExtra;
1023
+ if (cachedContent) {
1024
+ requestPayload.cachedContent = cachedContent;
1025
+ }
1026
+ // Only delete the snake_case duplicate — preserve camelCase
1027
+ delete requestPayload.cached_content;
1028
+ if (requestPayload.extra_body && typeof requestPayload.extra_body === "object") {
1029
+ delete requestPayload.extra_body.cached_content;
1030
+ delete requestPayload.extra_body.cachedContent;
1031
+ if (Object.keys(requestPayload.extra_body).length === 0) {
1032
+ delete requestPayload.extra_body;
1033
+ }
1034
+ }
1035
+ // Normalize tools. For Claude models, keep full function declarations (names + schemas).
1036
+ const hasTools = Array.isArray(requestPayload.tools) && requestPayload.tools.length > 0;
1037
+ if (hasTools) {
1038
+ if (isClaude) {
1039
+ const functionDeclarations = [];
1040
+ const passthroughTools = [];
1041
+ const normalizeSchema = (schema) => {
1042
+ const createPlaceholderSchema = (base = {}) => ({
1043
+ ...base,
1044
+ type: "object",
1045
+ properties: {
1046
+ [EMPTY_SCHEMA_PLACEHOLDER_NAME]: {
1047
+ type: "boolean",
1048
+ description: EMPTY_SCHEMA_PLACEHOLDER_DESCRIPTION,
1049
+ },
1050
+ },
1051
+ required: [EMPTY_SCHEMA_PLACEHOLDER_NAME],
1052
+ });
1053
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
1054
+ toolDebugMissing += 1;
1055
+ return createPlaceholderSchema();
1056
+ }
1057
+ const cleaned = cleanJSONSchemaForAntigravity(schema);
1058
+ if (!cleaned || typeof cleaned !== "object" || Array.isArray(cleaned)) {
1059
+ toolDebugMissing += 1;
1060
+ return createPlaceholderSchema();
1061
+ }
1062
+ // Claude VALIDATED mode requires tool parameters to be an object schema
1063
+ // with at least one property.
1064
+ const hasProperties = cleaned.properties &&
1065
+ typeof cleaned.properties === "object" &&
1066
+ Object.keys(cleaned.properties).length > 0;
1067
+ cleaned.type = "object";
1068
+ if (!hasProperties) {
1069
+ cleaned.properties = {
1070
+ [EMPTY_SCHEMA_PLACEHOLDER_NAME]: {
1071
+ type: "boolean",
1072
+ description: EMPTY_SCHEMA_PLACEHOLDER_DESCRIPTION,
1073
+ },
1074
+ };
1075
+ cleaned.required = Array.isArray(cleaned.required)
1076
+ ? Array.from(new Set([...cleaned.required, EMPTY_SCHEMA_PLACEHOLDER_NAME]))
1077
+ : [EMPTY_SCHEMA_PLACEHOLDER_NAME];
1078
+ }
1079
+ return cleaned;
1080
+ };
1081
+ requestPayload.tools.forEach((tool) => {
1082
+ const pushDeclaration = (decl, source) => {
1083
+ const schema = decl?.parameters ||
1084
+ decl?.parametersJsonSchema ||
1085
+ decl?.input_schema ||
1086
+ decl?.inputSchema ||
1087
+ tool.parameters ||
1088
+ tool.parametersJsonSchema ||
1089
+ tool.input_schema ||
1090
+ tool.inputSchema ||
1091
+ tool.function?.parameters ||
1092
+ tool.function?.parametersJsonSchema ||
1093
+ tool.function?.input_schema ||
1094
+ tool.function?.inputSchema ||
1095
+ tool.custom?.parameters ||
1096
+ tool.custom?.parametersJsonSchema ||
1097
+ tool.custom?.input_schema;
1098
+ let name = decl?.name ||
1099
+ tool.name ||
1100
+ tool.function?.name ||
1101
+ tool.custom?.name ||
1102
+ `tool-${functionDeclarations.length}`;
1103
+ // Sanitize tool name: must be alphanumeric with underscores, no special chars
1104
+ name = String(name).replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
1105
+ const description = decl?.description ||
1106
+ tool.description ||
1107
+ tool.function?.description ||
1108
+ tool.custom?.description ||
1109
+ "";
1110
+ functionDeclarations.push({
1111
+ name,
1112
+ description: String(description || ""),
1113
+ parameters: normalizeSchema(schema),
1114
+ });
1115
+ toolDebugSummaries.push(`decl=${name},src=${source},hasSchema=${schema ? "y" : "n"}`);
1116
+ };
1117
+ if (Array.isArray(tool.functionDeclarations) && tool.functionDeclarations.length > 0) {
1118
+ tool.functionDeclarations.forEach((decl) => pushDeclaration(decl, "functionDeclarations"));
1119
+ return;
1120
+ }
1121
+ // Fall back to function/custom style definitions.
1122
+ if (tool.function ||
1123
+ tool.custom ||
1124
+ tool.parameters ||
1125
+ tool.input_schema ||
1126
+ tool.inputSchema) {
1127
+ pushDeclaration(tool.function ?? tool.custom ?? tool, "function/custom");
1128
+ return;
1129
+ }
1130
+ // Preserve any non-function tool entries (e.g., codeExecution) untouched.
1131
+ passthroughTools.push(tool);
1132
+ });
1133
+ const finalTools = [];
1134
+ if (functionDeclarations.length > 0) {
1135
+ finalTools.push({ functionDeclarations });
1136
+ }
1137
+ requestPayload.tools = finalTools.concat(passthroughTools);
1138
+ }
1139
+ else {
1140
+ // Gemini-specific tool normalization and feature injection
1141
+ const geminiResult = applyGeminiTransforms(requestPayload, {
1142
+ model: effectiveModel,
1143
+ normalizedThinking: undefined, // Thinking config already applied above (lines 816-880)
1144
+ tierThinkingBudget,
1145
+ tierThinkingLevel: tierThinkingLevel,
1146
+ });
1147
+ toolDebugMissing = geminiResult.toolDebugMissing;
1148
+ toolDebugSummaries.push(...geminiResult.toolDebugSummaries);
1149
+ }
1150
+ try {
1151
+ toolDebugPayload = JSON.stringify(requestPayload.tools);
1152
+ }
1153
+ catch {
1154
+ toolDebugPayload = undefined;
1155
+ }
1156
+ // Apply Claude tool hardening (ported from LLM-API-Key-Proxy)
1157
+ // Injects parameter signatures into descriptions and adds system instruction
1158
+ // Can be disabled via config.claude_tool_hardening = false to reduce context size
1159
+ const enableToolHardening = options?.claudeToolHardening ?? true;
1160
+ if (enableToolHardening && isClaude && Array.isArray(requestPayload.tools) && requestPayload.tools.length > 0) {
1161
+ // Inject parameter signatures into tool descriptions
1162
+ requestPayload.tools = injectParameterSignatures(requestPayload.tools, CLAUDE_DESCRIPTION_PROMPT);
1163
+ // Inject tool hardening system instruction
1164
+ injectToolHardeningInstruction(requestPayload, CLAUDE_TOOL_SYSTEM_INSTRUCTION);
1165
+ }
1166
+ // Append interleaved thinking hint for Claude thinking models with tools.
1167
+ // Must come AFTER tool hardening so it is the last system instruction part,
1168
+ // preserving the stable prefix for prompt cache matching.
1169
+ if (isClaudeThinking && Array.isArray(requestPayload.tools) && requestPayload.tools.length > 0) {
1170
+ appendClaudeThinkingHint(requestPayload);
1171
+ }
1172
+ }
1173
+ const conversationKey = resolveConversationKey(requestPayload);
1174
+ signatureSessionKey = buildSignatureSessionKey(PLUGIN_SESSION_ID, effectiveModel, conversationKey, resolveProjectKey(projectId));
1175
+ // For Claude models, filter out unsigned thinking blocks (required by Claude API)
1176
+ // Attempts to restore signatures from cache for multi-turn conversations
1177
+ // Handle both Gemini-style contents[] and Anthropic-style messages[] payloads.
1178
+ if (isClaude) {
1179
+ // Step 0: Sanitize cross-model metadata (strips Gemini signatures when sending to Claude)
1180
+ sanitizeCrossModelPayloadInPlace(requestPayload, { targetModel: effectiveModel });
1181
+ // Step 1: Strip corrupted/unsigned thinking blocks FIRST
1182
+ deepFilterThinkingBlocks(requestPayload, signatureSessionKey, getCachedSignature, true);
1183
+ // Step 2: THEN inject signed thinking from cache (after stripping)
1184
+ if (isClaudeThinking && keepThinkingEnabled && Array.isArray(requestPayload.contents)) {
1185
+ requestPayload.contents = ensureThinkingBeforeToolUseInContents(requestPayload.contents, signatureSessionKey);
1186
+ }
1187
+ if (isClaudeThinking && keepThinkingEnabled && Array.isArray(requestPayload.messages)) {
1188
+ requestPayload.messages = ensureThinkingBeforeToolUseInMessages(requestPayload.messages, signatureSessionKey);
1189
+ }
1190
+ // Step 3: Check if warmup needed (AFTER injection attempt)
1191
+ if (isClaudeThinking && keepThinkingEnabled) {
1192
+ const hasToolUse = (Array.isArray(requestPayload.contents) && hasToolUseInContents(requestPayload.contents)) ||
1193
+ (Array.isArray(requestPayload.messages) && hasToolUseInMessages(requestPayload.messages));
1194
+ const hasSignedThinking = (Array.isArray(requestPayload.contents) && hasSignedThinkingInContents(requestPayload.contents, signatureSessionKey)) ||
1195
+ (Array.isArray(requestPayload.messages) && hasSignedThinkingInMessages(requestPayload.messages, signatureSessionKey));
1196
+ const hasCachedThinking = defaultSignatureStore.has(signatureSessionKey);
1197
+ needsSignedThinkingWarmup = hasToolUse && !hasSignedThinking && !hasCachedThinking;
1198
+ }
1199
+ }
1200
+ // For Claude models, ensure functionCall/tool use parts carry IDs (required by Anthropic).
1201
+ // We use a two-pass approach: first collect all functionCalls and assign IDs,
1202
+ // then match functionResponses to their corresponding calls using a FIFO queue per function name.
1203
+ if (isClaude && Array.isArray(requestPayload.contents)) {
1204
+ let toolCallCounter = 0;
1205
+ // Track pending call IDs per function name as a FIFO queue
1206
+ const pendingCallIdsByName = new Map();
1207
+ // First pass: assign IDs to all functionCalls and collect them
1208
+ requestPayload.contents = requestPayload.contents.map((content) => {
1209
+ if (!content || !Array.isArray(content.parts)) {
1210
+ return content;
1211
+ }
1212
+ const newParts = content.parts.map((part) => {
1213
+ if (part && typeof part === "object" && part.functionCall) {
1214
+ const call = { ...part.functionCall };
1215
+ if (!call.id) {
1216
+ call.id = `tool-call-${++toolCallCounter}`;
1217
+ }
1218
+ const nameKey = typeof call.name === "string" ? call.name : `tool-${toolCallCounter}`;
1219
+ // Push to the queue for this function name
1220
+ const queue = pendingCallIdsByName.get(nameKey) || [];
1221
+ queue.push(call.id);
1222
+ pendingCallIdsByName.set(nameKey, queue);
1223
+ return { ...part, functionCall: call };
1224
+ }
1225
+ return part;
1226
+ });
1227
+ return { ...content, parts: newParts };
1228
+ });
1229
+ // Second pass: match functionResponses to their corresponding calls (FIFO order)
1230
+ requestPayload.contents = requestPayload.contents.map((content) => {
1231
+ if (!content || !Array.isArray(content.parts)) {
1232
+ return content;
1233
+ }
1234
+ const newParts = content.parts.map((part) => {
1235
+ if (part && typeof part === "object" && part.functionResponse) {
1236
+ const resp = { ...part.functionResponse };
1237
+ if (!resp.id && typeof resp.name === "string") {
1238
+ const queue = pendingCallIdsByName.get(resp.name);
1239
+ if (queue && queue.length > 0) {
1240
+ // Consume the first pending ID (FIFO order)
1241
+ resp.id = queue.shift();
1242
+ pendingCallIdsByName.set(resp.name, queue);
1243
+ }
1244
+ }
1245
+ return { ...part, functionResponse: resp };
1246
+ }
1247
+ return part;
1248
+ });
1249
+ return { ...content, parts: newParts };
1250
+ });
1251
+ // Third pass: Apply orphan recovery for mismatched tool IDs
1252
+ // This handles cases where context compaction or other processes
1253
+ // create ID mismatches between calls and responses.
1254
+ // Ported from LLM-API-Key-Proxy's _fix_tool_response_grouping()
1255
+ requestPayload.contents = fixToolResponseGrouping(requestPayload.contents);
1256
+ }
1257
+ // Fourth pass: Fix Claude format tool pairing (defense in depth)
1258
+ // Handles orphaned tool_use blocks in Claude's messages[] format
1259
+ if (Array.isArray(requestPayload.messages)) {
1260
+ requestPayload.messages = validateAndFixClaudeToolPairing(requestPayload.messages);
1261
+ }
1262
+ // =====================================================================
1263
+ // LAST RESORT RECOVERY: "Let it crash and start again"
1264
+ // =====================================================================
1265
+ // If after all our processing we're STILL in a bad state (tool loop without
1266
+ // thinking at turn start), don't try to fix it - just close the turn and
1267
+ // start fresh. This prevents permanent session breakage.
1268
+ //
1269
+ // This handles cases where:
1270
+ // - Context compaction stripped thinking blocks
1271
+ // - Signature cache miss
1272
+ // - Any other corruption we couldn't repair
1273
+ // - API error indicated thinking_block_order issue (forceThinkingRecovery=true)
1274
+ //
1275
+ // The synthetic messages allow Claude to generate fresh thinking on the
1276
+ // new turn instead of failing with "Expected thinking but found text".
1277
+ if (isClaudeThinking && Array.isArray(requestPayload.contents)) {
1278
+ const conversationState = analyzeConversationState(requestPayload.contents);
1279
+ // Force recovery if API returned thinking_block_order error (retry case)
1280
+ // or if proactive check detects we need recovery
1281
+ if (forceThinkingRecovery || needsThinkingRecovery(conversationState)) {
1282
+ // Set message for toast notification (shown in plugin.ts, respects quiet mode)
1283
+ thinkingRecoveryMessage = forceThinkingRecovery
1284
+ ? "Thinking recovery: retrying with fresh turn (API error)"
1285
+ : "Thinking recovery: restarting turn (corrupted context)";
1286
+ requestPayload.contents = closeToolLoopForThinking(requestPayload.contents);
1287
+ defaultSignatureStore.delete(signatureSessionKey);
1288
+ }
1289
+ }
1290
+ // Guard against assistant prefill: Claude rejects conversations ending
1291
+ // with model/assistant messages. After context compaction, the conversation
1292
+ // can end with a model message — append synthetic user message to fix.
1293
+ if (isClaude) {
1294
+ if (Array.isArray(requestPayload.contents)) {
1295
+ const lastContent = requestPayload.contents[requestPayload.contents.length - 1];
1296
+ if (lastContent?.role === "model" || lastContent?.role === "assistant") {
1297
+ requestPayload.contents.push({ role: "user", parts: [{ text: "[Continue]" }] });
1298
+ }
1299
+ }
1300
+ if (Array.isArray(requestPayload.messages)) {
1301
+ const lastMessage = requestPayload.messages[requestPayload.messages.length - 1];
1302
+ if (lastMessage?.role === "model" || lastMessage?.role === "assistant") {
1303
+ requestPayload.messages.push({ role: "user", parts: [{ text: "[Continue]" }] });
1304
+ }
1305
+ }
1306
+ }
1307
+ if ("model" in requestPayload) {
1308
+ delete requestPayload.model;
1309
+ }
1310
+ stripInjectedDebugFromRequestPayload(requestPayload);
1311
+ sanitizeRequestPayloadForAntigravity(requestPayload);
1312
+ const effectiveProjectId = projectId?.trim() || (headerStyle === "antigravity" ? generateSyntheticProjectId() : "");
1313
+ resolvedProjectId = effectiveProjectId;
1314
+ // System instruction injection removed — CLIProxyAPI v6.9.x no longer injects it
1315
+ const wrappedBody = headerStyle === "antigravity"
1316
+ ? {
1317
+ project: effectiveProjectId,
1318
+ requestId: buildAntigravityRequestId("agent"),
1319
+ request: requestPayload,
1320
+ model: effectiveModel,
1321
+ userAgent: "antigravity",
1322
+ requestType: "agent",
1323
+ }
1324
+ : {
1325
+ project: effectiveProjectId,
1326
+ model: effectiveModel,
1327
+ request: requestPayload,
1328
+ };
1329
+ if (wrappedBody.request && typeof wrappedBody.request === 'object') {
1330
+ // Use stable session ID for signature caching across multi-turn conversations
1331
+ sessionId = signatureSessionKey;
1332
+ wrappedBody.request.sessionId = signatureSessionKey;
1333
+ }
1334
+ body = safeStringify(headerStyle === "antigravity" ? orderAntigravityEnvelope(wrappedBody) : wrappedBody);
1335
+ }
1336
+ }
1337
+ catch {
1338
+ throw new Error("Failed to build Antigravity request body");
1339
+ }
1340
+ }
1341
+ // agy CLI does not send an Accept header on streamGenerateContent requests.
1342
+ // Avoid adding one here; the response is selected by ?alt=sse.
1343
+ // Add interleaved thinking header for Claude thinking models
1344
+ // This enables real-time streaming of thinking tokens
1345
+ if (isClaudeThinking) {
1346
+ const existing = headers.get("anthropic-beta");
1347
+ const interleavedHeader = "interleaved-thinking-2025-05-14";
1348
+ if (existing) {
1349
+ if (!existing.includes(interleavedHeader)) {
1350
+ headers.set("anthropic-beta", `${existing},${interleavedHeader}`);
1351
+ }
1352
+ }
1353
+ else {
1354
+ headers.set("anthropic-beta", interleavedHeader);
1355
+ }
1356
+ }
1357
+ if (headerStyle === "antigravity") {
1358
+ // Use randomized headers as the fallback pool for Antigravity mode
1359
+ const selectedHeaders = getRandomizedHeaders("antigravity", requestedModel);
1360
+ // Antigravity mode: Match Antigravity Manager behavior
1361
+ // AM only sends User-Agent on content requests — no X-Goog-Api-Client, no Client-Metadata header
1362
+ // (ideType=ANTIGRAVITY goes in request body metadata via project.ts, not as a header)
1363
+ const fingerprint = options?.fingerprint ?? getSessionFingerprint();
1364
+ const fingerprintHeaders = buildFingerprintHeaders(fingerprint);
1365
+ headers.set("User-Agent", fingerprintHeaders["User-Agent"] || selectedHeaders["User-Agent"]);
1366
+ headers.set("Accept-Encoding", "gzip");
1367
+ }
1368
+ else {
1369
+ // Gemini CLI mode: match official google-gemini/gemini-cli User-Agent format
1370
+ const geminiCliHeaders = getRandomizedHeaders("gemini-cli", requestedModel);
1371
+ headers.set("User-Agent", geminiCliHeaders["User-Agent"]);
1372
+ if (geminiCliHeaders["X-Goog-Api-Client"])
1373
+ headers.set("X-Goog-Api-Client", geminiCliHeaders["X-Goog-Api-Client"]);
1374
+ if (geminiCliHeaders["Client-Metadata"])
1375
+ headers.set("Client-Metadata", geminiCliHeaders["Client-Metadata"]);
1376
+ }
1377
+ return {
1378
+ request: transformedUrl,
1379
+ init: {
1380
+ ...baseInit,
1381
+ headers,
1382
+ body,
1383
+ },
1384
+ streaming,
1385
+ requestedModel,
1386
+ effectiveModel: effectiveModel,
1387
+ projectId: resolvedProjectId,
1388
+ endpoint: transformedUrl,
1389
+ sessionId,
1390
+ toolDebugMissing,
1391
+ toolDebugSummary: toolDebugSummaries.slice(0, 20).join(" | "),
1392
+ toolDebugPayload,
1393
+ needsSignedThinkingWarmup,
1394
+ headerStyle,
1395
+ thinkingRecoveryMessage,
1396
+ };
1397
+ }
1398
+ export function buildThinkingWarmupBody(bodyText, isClaudeThinking) {
1399
+ if (!bodyText || !isClaudeThinking) {
1400
+ return null;
1401
+ }
1402
+ let parsed;
1403
+ try {
1404
+ parsed = JSON.parse(bodyText);
1405
+ }
1406
+ catch {
1407
+ return null;
1408
+ }
1409
+ const warmupPrompt = "Warmup request for thinking signature.";
1410
+ const updateRequest = (req) => {
1411
+ req.contents = [{ role: "user", parts: [{ text: warmupPrompt }] }];
1412
+ delete req.tools;
1413
+ delete req.toolConfig;
1414
+ const generationConfig = (req.generationConfig ?? {});
1415
+ generationConfig.thinkingConfig = {
1416
+ include_thoughts: true,
1417
+ thinking_budget: DEFAULT_THINKING_BUDGET,
1418
+ };
1419
+ generationConfig.maxOutputTokens = computeClaudeMaxOutputTokens(DEFAULT_THINKING_BUDGET);
1420
+ req.generationConfig = generationConfig;
1421
+ };
1422
+ if (parsed.request && typeof parsed.request === "object") {
1423
+ updateRequest(parsed.request);
1424
+ const nested = parsed.request.request;
1425
+ if (nested && typeof nested === "object") {
1426
+ updateRequest(nested);
1427
+ }
1428
+ }
1429
+ else {
1430
+ updateRequest(parsed);
1431
+ }
1432
+ return safeStringify(parsed);
1433
+ }
1434
+ /**
1435
+ * Normalizes Antigravity responses: applies retry headers, extracts cache usage into headers,
1436
+ * rewrites preview errors, flattens streaming payloads, and logs debug metadata.
1437
+ *
1438
+ * For streaming SSE responses, uses TransformStream for true real-time incremental streaming.
1439
+ * Thinking/reasoning tokens are transformed and forwarded immediately as they arrive.
1440
+ */
1441
+ export async function transformAntigravityResponse(response, streaming, debugContext, requestedModel, projectId, endpoint, effectiveModel, sessionId, toolDebugMissing, toolDebugSummary, toolDebugPayload, debugLines, dumpContext) {
1442
+ const contentType = response.headers.get("content-type") ?? "";
1443
+ const isJsonResponse = contentType.includes("application/json");
1444
+ const isEventStreamResponse = contentType.includes("text/event-stream");
1445
+ // Generate text for thinking injection:
1446
+ // - If debug=true: inject full debug logs
1447
+ // - If keep_thinking=true (but no debug): inject placeholder to trigger signature caching
1448
+ // Both use the same injection path (injectDebugThinking) for consistent behavior
1449
+ const debugText = isDebugTuiEnabled() && Array.isArray(debugLines) && debugLines.length > 0
1450
+ ? formatDebugLinesForThinking(debugLines)
1451
+ : getKeepThinking()
1452
+ ? SYNTHETIC_THINKING_PLACEHOLDER
1453
+ : undefined;
1454
+ const cacheSignatures = shouldCacheThinkingSignatures(effectiveModel);
1455
+ if (!isJsonResponse && !isEventStreamResponse) {
1456
+ logAntigravityDebugResponse(debugContext, response, {
1457
+ note: "Non-JSON response (body omitted)",
1458
+ });
1459
+ return response;
1460
+ }
1461
+ // For successful streaming responses, use TransformStream to transform SSE events
1462
+ // while maintaining real-time streaming (no buffering of entire response).
1463
+ // This enables thinking tokens to be displayed as they arrive, like the Codex plugin.
1464
+ if (streaming && response.ok && isEventStreamResponse && response.body) {
1465
+ const headers = new Headers(response.headers);
1466
+ logAntigravityDebugResponse(debugContext, response, {
1467
+ note: "Streaming SSE response (real-time transform)",
1468
+ });
1469
+ noteGeminiDumpResponse(dumpContext, response);
1470
+ const rawDumpTransformer = createGeminiDumpResponseTransform(dumpContext);
1471
+ const sourceBody = rawDumpTransformer ? response.body.pipeThrough(rawDumpTransformer) : response.body;
1472
+ const streamingTransformer = createStreamingTransformer(defaultSignatureStore, {
1473
+ onCacheSignature: cacheSignature,
1474
+ onInjectDebug: injectDebugThinking,
1475
+ onUsageMetadata: (usage) => {
1476
+ if (effectiveModel) {
1477
+ const cacheRead = usage.cachedContentTokenCount;
1478
+ const totalInput = usage.promptTokenCount ?? usage.totalTokenCount;
1479
+ const hitRate = totalInput > 0 ? Math.round((cacheRead / totalInput) * 100) : 0;
1480
+ const status = cacheRead > 0 ? "HIT" : "MISS";
1481
+ logCacheStats(effectiveModel, cacheRead, 0, totalInput);
1482
+ log.debug(`[Cache] ${status} model=${effectiveModel} read=${cacheRead} total=${totalInput} hitRate=${hitRate}%`);
1483
+ _lastCacheStats = { model: effectiveModel, read: cacheRead, total: totalInput, hitRate };
1484
+ }
1485
+ },
1486
+ transformThinkingParts,
1487
+ }, {
1488
+ signatureSessionKey: sessionId,
1489
+ debugText,
1490
+ cacheSignatures,
1491
+ displayedThinkingHashes: effectiveModel && isGemini3Model(effectiveModel) ? sessionDisplayedThinkingHashes : undefined,
1492
+ // injectSyntheticThinking removed - keep_thinking now unified with debug via debugText
1493
+ });
1494
+ return new Response(sourceBody.pipeThrough(streamingTransformer), {
1495
+ status: response.status,
1496
+ statusText: response.statusText,
1497
+ headers,
1498
+ });
1499
+ }
1500
+ const responseFallback = response.clone();
1501
+ try {
1502
+ const headers = new Headers(response.headers);
1503
+ const text = await response.text();
1504
+ noteGeminiDumpResponse(dumpContext, response);
1505
+ appendGeminiDumpResponseText(dumpContext, text);
1506
+ if (!response.ok) {
1507
+ let errorBody;
1508
+ try {
1509
+ errorBody = JSON.parse(text);
1510
+ }
1511
+ catch {
1512
+ errorBody = { error: { message: text } };
1513
+ }
1514
+ // Inject Debug Info
1515
+ if (errorBody?.error) {
1516
+ const rawErrorMessage = typeof errorBody.error.message === "string" && errorBody.error.message.length > 0
1517
+ ? errorBody.error.message
1518
+ : "Unknown error";
1519
+ const errorType = detectErrorType(rawErrorMessage);
1520
+ const debugInfo = `\n\n[Debug Info]\nRequested Model: ${requestedModel || "Unknown"}\nEffective Model: ${effectiveModel || "Unknown"}\nProject: ${projectId || "Unknown"}\nEndpoint: ${endpoint || "Unknown"}\nStatus: ${response.status}\nRequest ID: ${headers.get("x-request-id") || "N/A"}${toolDebugMissing !== undefined ? `\nTool Debug Missing: ${toolDebugMissing}` : ""}${toolDebugSummary ? `\nTool Debug Summary: ${toolDebugSummary}` : ""}${toolDebugPayload ? `\nTool Debug Payload: ${toolDebugPayload}` : ""}`;
1521
+ const injectedDebug = debugText ? `\n\n${debugText}` : "";
1522
+ errorBody.error.message = rawErrorMessage + debugInfo + injectedDebug;
1523
+ // Check if this is a recoverable thinking error - throw to trigger retry
1524
+ if (errorType === "thinking_block_order") {
1525
+ const recoveryError = new Error("THINKING_RECOVERY_NEEDED");
1526
+ recoveryError.recoveryType = errorType;
1527
+ recoveryError.originalError = errorBody;
1528
+ recoveryError.debugInfo = debugInfo;
1529
+ throw recoveryError;
1530
+ }
1531
+ // Detect context length / prompt too long errors - signal to caller for toast
1532
+ const errorMessage = errorBody.error.message?.toLowerCase() || "";
1533
+ if (errorMessage.includes("prompt is too long") ||
1534
+ errorMessage.includes("context length exceeded") ||
1535
+ errorMessage.includes("context_length_exceeded") ||
1536
+ errorMessage.includes("maximum context length")) {
1537
+ headers.set("x-antigravity-context-error", "prompt_too_long");
1538
+ }
1539
+ // Detect tool pairing errors - signal to caller for toast
1540
+ if (errorMessage.includes("tool_use") &&
1541
+ errorMessage.includes("tool_result") &&
1542
+ (errorMessage.includes("without") || errorMessage.includes("immediately after"))) {
1543
+ headers.set("x-antigravity-context-error", "tool_pairing");
1544
+ }
1545
+ return new Response(JSON.stringify(errorBody), {
1546
+ status: response.status,
1547
+ statusText: response.statusText,
1548
+ headers
1549
+ });
1550
+ }
1551
+ if (errorBody?.error?.details && Array.isArray(errorBody.error.details)) {
1552
+ const retryInfo = errorBody.error.details.find((detail) => detail['@type'] === 'type.googleapis.com/google.rpc.RetryInfo');
1553
+ if (retryInfo?.retryDelay) {
1554
+ const match = retryInfo.retryDelay.match(/^([\d.]+)s$/);
1555
+ if (match && match[1]) {
1556
+ const retrySeconds = parseFloat(match[1]);
1557
+ if (!isNaN(retrySeconds) && retrySeconds > 0) {
1558
+ const retryAfterSec = Math.ceil(retrySeconds).toString();
1559
+ const retryAfterMs = Math.ceil(retrySeconds * 1000).toString();
1560
+ headers.set('Retry-After', retryAfterSec);
1561
+ headers.set('retry-after-ms', retryAfterMs);
1562
+ }
1563
+ }
1564
+ }
1565
+ }
1566
+ }
1567
+ const init = {
1568
+ status: response.status,
1569
+ statusText: response.statusText,
1570
+ headers,
1571
+ };
1572
+ const usageFromSse = streaming && isEventStreamResponse ? extractUsageFromSsePayload(text) : null;
1573
+ const parsed = !streaming || !isEventStreamResponse ? parseAntigravityApiBody(text) : null;
1574
+ const patched = parsed ? rewriteAntigravityPreviewAccessError(parsed, response.status, requestedModel) : null;
1575
+ const effectiveBody = patched ?? parsed ?? undefined;
1576
+ const usage = usageFromSse ?? (effectiveBody ? extractUsageMetadata(effectiveBody) : null);
1577
+ // Log cache stats when available
1578
+ if (usage && effectiveModel) {
1579
+ const cacheRead = usage.cachedContentTokenCount ?? 0;
1580
+ const totalInput = usage.promptTokenCount ?? usage.totalTokenCount ?? 0;
1581
+ const hitRate = totalInput > 0 ? Math.round((cacheRead / totalInput) * 100) : 0;
1582
+ const status = cacheRead > 0 ? "HIT" : "MISS";
1583
+ logCacheStats(effectiveModel, cacheRead, 0, totalInput);
1584
+ log.debug(`[Cache] ${status} model=${effectiveModel} read=${cacheRead} total=${totalInput} hitRate=${hitRate}%`);
1585
+ }
1586
+ if (usage?.cachedContentTokenCount !== undefined) {
1587
+ headers.set("x-antigravity-cached-content-token-count", String(usage.cachedContentTokenCount));
1588
+ if (usage.totalTokenCount !== undefined) {
1589
+ headers.set("x-antigravity-total-token-count", String(usage.totalTokenCount));
1590
+ }
1591
+ if (usage.promptTokenCount !== undefined) {
1592
+ headers.set("x-antigravity-prompt-token-count", String(usage.promptTokenCount));
1593
+ }
1594
+ if (usage.candidatesTokenCount !== undefined) {
1595
+ headers.set("x-antigravity-candidates-token-count", String(usage.candidatesTokenCount));
1596
+ }
1597
+ }
1598
+ logAntigravityDebugResponse(debugContext, response, {
1599
+ body: text,
1600
+ note: streaming ? "Streaming SSE payload (buffered fallback)" : undefined,
1601
+ headersOverride: headers,
1602
+ });
1603
+ // Note: successful streaming responses are handled above via TransformStream.
1604
+ // This path only handles non-streaming responses or failed streaming responses.
1605
+ if (!parsed) {
1606
+ return new Response(text, init);
1607
+ }
1608
+ if (effectiveBody?.response !== undefined) {
1609
+ let responseBody = effectiveBody.response;
1610
+ // Inject thinking text (debug logs or "[Thinking preserved]" placeholder)
1611
+ // Both debug=true and keep_thinking=true use the same path now
1612
+ if (debugText) {
1613
+ responseBody = injectDebugThinking(responseBody, debugText);
1614
+ }
1615
+ const transformed = transformThinkingParts(responseBody);
1616
+ return new Response(JSON.stringify(transformed), init);
1617
+ }
1618
+ if (patched) {
1619
+ return new Response(JSON.stringify(patched), init);
1620
+ }
1621
+ return new Response(text, init);
1622
+ }
1623
+ catch (error) {
1624
+ if (error instanceof Error && error.message === "THINKING_RECOVERY_NEEDED") {
1625
+ throw error;
1626
+ }
1627
+ logAntigravityDebugResponse(debugContext, response, {
1628
+ error,
1629
+ note: "Failed to transform Antigravity response",
1630
+ });
1631
+ return responseFallback;
1632
+ }
1633
+ }
1634
+ export const __testExports = {
1635
+ buildSignatureSessionKey,
1636
+ hashConversationSeed,
1637
+ extractTextFromContent,
1638
+ extractConversationSeedFromMessages,
1639
+ extractConversationSeedFromContents,
1640
+ resolveConversationKey,
1641
+ resolveProjectKey,
1642
+ isGeminiToolUsePart,
1643
+ isGeminiThinkingPart,
1644
+ ensureThoughtSignature,
1645
+ hasSignedThinkingPart,
1646
+ hasSignedThinkingInContents,
1647
+ hasSignedThinkingInMessages,
1648
+ hasToolUseInContents,
1649
+ hasToolUseInMessages,
1650
+ ensureThinkingBeforeToolUseInContents,
1651
+ ensureThinkingBeforeToolUseInMessages,
1652
+ generateSyntheticProjectId,
1653
+ MIN_SIGNATURE_LENGTH,
1654
+ transformSseLine,
1655
+ transformStreamingPayload,
1656
+ createStreamingTransformer,
1657
+ };
1658
+ //# sourceMappingURL=request.js.map