@gajae-code/ai 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (349) hide show
  1. package/CHANGELOG.md +2644 -0
  2. package/README.md +1181 -0
  3. package/dist/types/api-registry.d.ts +30 -0
  4. package/dist/types/auth-broker/client.d.ts +66 -0
  5. package/dist/types/auth-broker/index.d.ts +5 -0
  6. package/dist/types/auth-broker/refresher.d.ts +25 -0
  7. package/dist/types/auth-broker/remote-store.d.ts +96 -0
  8. package/dist/types/auth-broker/server.d.ts +32 -0
  9. package/dist/types/auth-broker/types.d.ts +105 -0
  10. package/dist/types/auth-broker/wire-schemas.d.ts +412 -0
  11. package/dist/types/auth-gateway/http.d.ts +39 -0
  12. package/dist/types/auth-gateway/index.d.ts +3 -0
  13. package/dist/types/auth-gateway/server.d.ts +17 -0
  14. package/dist/types/auth-gateway/types.d.ts +115 -0
  15. package/dist/types/auth-storage.d.ts +641 -0
  16. package/dist/types/cli.d.ts +2 -0
  17. package/dist/types/index.d.ts +49 -0
  18. package/dist/types/model-cache.d.ts +17 -0
  19. package/dist/types/model-manager.d.ts +62 -0
  20. package/dist/types/model-thinking.d.ts +71 -0
  21. package/dist/types/models.d.ts +12 -0
  22. package/dist/types/provider-details.d.ts +24 -0
  23. package/dist/types/provider-models/bundled-references.d.ts +4 -0
  24. package/dist/types/provider-models/descriptors.d.ts +48 -0
  25. package/dist/types/provider-models/google.d.ts +20 -0
  26. package/dist/types/provider-models/index.d.ts +5 -0
  27. package/dist/types/provider-models/ollama.d.ts +7 -0
  28. package/dist/types/provider-models/openai-compat.d.ts +237 -0
  29. package/dist/types/provider-models/special.d.ts +16 -0
  30. package/dist/types/providers/amazon-bedrock.d.ts +36 -0
  31. package/dist/types/providers/anthropic-messages-server-schema.d.ts +450 -0
  32. package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
  33. package/dist/types/providers/anthropic.d.ts +188 -0
  34. package/dist/types/providers/aws-credentials.d.ts +43 -0
  35. package/dist/types/providers/aws-eventstream.d.ts +38 -0
  36. package/dist/types/providers/aws-sigv4.d.ts +55 -0
  37. package/dist/types/providers/azure-openai-responses.d.ts +15 -0
  38. package/dist/types/providers/cursor/gen/agent_pb.d.ts +13022 -0
  39. package/dist/types/providers/cursor.d.ts +42 -0
  40. package/dist/types/providers/error-message.d.ts +27 -0
  41. package/dist/types/providers/github-copilot-headers.d.ts +40 -0
  42. package/dist/types/providers/gitlab-duo.d.ts +27 -0
  43. package/dist/types/providers/google-auth.d.ts +24 -0
  44. package/dist/types/providers/google-gemini-cli.d.ts +72 -0
  45. package/dist/types/providers/google-gemini-headers.d.ts +18 -0
  46. package/dist/types/providers/google-shared.d.ts +163 -0
  47. package/dist/types/providers/google-types.d.ts +138 -0
  48. package/dist/types/providers/google-vertex.d.ts +7 -0
  49. package/dist/types/providers/google.d.ts +4 -0
  50. package/dist/types/providers/grammar.d.ts +1 -0
  51. package/dist/types/providers/kimi.d.ts +27 -0
  52. package/dist/types/providers/mock.d.ts +175 -0
  53. package/dist/types/providers/ollama.d.ts +6 -0
  54. package/dist/types/providers/openai-anthropic-shim.d.ts +31 -0
  55. package/dist/types/providers/openai-chat-server-schema.d.ts +814 -0
  56. package/dist/types/providers/openai-chat-server.d.ts +16 -0
  57. package/dist/types/providers/openai-codex/constants.d.ts +26 -0
  58. package/dist/types/providers/openai-codex/request-transformer.d.ts +49 -0
  59. package/dist/types/providers/openai-codex/response-handler.d.ts +17 -0
  60. package/dist/types/providers/openai-codex-responses.d.ts +67 -0
  61. package/dist/types/providers/openai-completions-compat.d.ts +25 -0
  62. package/dist/types/providers/openai-completions.d.ts +33 -0
  63. package/dist/types/providers/openai-responses-server-schema.d.ts +392 -0
  64. package/dist/types/providers/openai-responses-server.d.ts +17 -0
  65. package/dist/types/providers/openai-responses-shared.d.ts +89 -0
  66. package/dist/types/providers/openai-responses.d.ts +32 -0
  67. package/dist/types/providers/pi-native-client.d.ts +13 -0
  68. package/dist/types/providers/pi-native-server.d.ts +68 -0
  69. package/dist/types/providers/register-builtins.d.ts +31 -0
  70. package/dist/types/providers/synthetic.d.ts +26 -0
  71. package/dist/types/providers/transform-messages.d.ts +12 -0
  72. package/dist/types/providers/vision-guard.d.ts +8 -0
  73. package/dist/types/rate-limit-utils.d.ts +19 -0
  74. package/dist/types/stream.d.ts +24 -0
  75. package/dist/types/types.d.ts +746 -0
  76. package/dist/types/usage/claude.d.ts +3 -0
  77. package/dist/types/usage/gemini.d.ts +2 -0
  78. package/dist/types/usage/github-copilot.d.ts +7 -0
  79. package/dist/types/usage/google-antigravity.d.ts +2 -0
  80. package/dist/types/usage/kimi.d.ts +2 -0
  81. package/dist/types/usage/minimax-code.d.ts +2 -0
  82. package/dist/types/usage/openai-codex.d.ts +3 -0
  83. package/dist/types/usage/shared.d.ts +1 -0
  84. package/dist/types/usage/zai.d.ts +2 -0
  85. package/dist/types/usage.d.ts +258 -0
  86. package/dist/types/utils/abort.d.ts +19 -0
  87. package/dist/types/utils/anthropic-auth.d.ts +31 -0
  88. package/dist/types/utils/discovery/antigravity.d.ts +61 -0
  89. package/dist/types/utils/discovery/codex.d.ts +38 -0
  90. package/dist/types/utils/discovery/cursor.d.ts +23 -0
  91. package/dist/types/utils/discovery/gemini.d.ts +25 -0
  92. package/dist/types/utils/discovery/index.d.ts +4 -0
  93. package/dist/types/utils/discovery/openai-compatible.d.ts +72 -0
  94. package/dist/types/utils/event-stream.d.ts +28 -0
  95. package/dist/types/utils/fireworks-model-id.d.ts +10 -0
  96. package/dist/types/utils/foundry.d.ts +1 -0
  97. package/dist/types/utils/h2-fetch.d.ts +22 -0
  98. package/dist/types/utils/http-inspector.d.ts +31 -0
  99. package/dist/types/utils/idle-iterator.d.ts +67 -0
  100. package/dist/types/utils/json-parse.d.ts +10 -0
  101. package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +18 -0
  102. package/dist/types/utils/oauth/anthropic.d.ts +22 -0
  103. package/dist/types/utils/oauth/api-key-login.d.ts +35 -0
  104. package/dist/types/utils/oauth/api-key-validation.d.ts +27 -0
  105. package/dist/types/utils/oauth/callback-server.d.ts +57 -0
  106. package/dist/types/utils/oauth/cerebras.d.ts +1 -0
  107. package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +18 -0
  108. package/dist/types/utils/oauth/cursor.d.ts +15 -0
  109. package/dist/types/utils/oauth/deepseek.d.ts +10 -0
  110. package/dist/types/utils/oauth/firepass.d.ts +1 -0
  111. package/dist/types/utils/oauth/fireworks.d.ts +1 -0
  112. package/dist/types/utils/oauth/github-copilot.d.ts +38 -0
  113. package/dist/types/utils/oauth/gitlab-duo.d.ts +3 -0
  114. package/dist/types/utils/oauth/google-antigravity.d.ts +11 -0
  115. package/dist/types/utils/oauth/google-gemini-cli.d.ts +10 -0
  116. package/dist/types/utils/oauth/google-oauth-shared.d.ts +28 -0
  117. package/dist/types/utils/oauth/huggingface.d.ts +19 -0
  118. package/dist/types/utils/oauth/index.d.ts +38 -0
  119. package/dist/types/utils/oauth/kagi.d.ts +17 -0
  120. package/dist/types/utils/oauth/kilo.d.ts +5 -0
  121. package/dist/types/utils/oauth/kimi.d.ts +21 -0
  122. package/dist/types/utils/oauth/litellm.d.ts +18 -0
  123. package/dist/types/utils/oauth/lm-studio.d.ts +17 -0
  124. package/dist/types/utils/oauth/minimax-code.d.ts +28 -0
  125. package/dist/types/utils/oauth/moonshot.d.ts +1 -0
  126. package/dist/types/utils/oauth/nanogpt.d.ts +1 -0
  127. package/dist/types/utils/oauth/nvidia.d.ts +18 -0
  128. package/dist/types/utils/oauth/ollama-cloud.d.ts +2 -0
  129. package/dist/types/utils/oauth/ollama.d.ts +18 -0
  130. package/dist/types/utils/oauth/openai-codex.d.ts +21 -0
  131. package/dist/types/utils/oauth/opencode.d.ts +18 -0
  132. package/dist/types/utils/oauth/parallel.d.ts +17 -0
  133. package/dist/types/utils/oauth/perplexity.d.ts +9 -0
  134. package/dist/types/utils/oauth/pkce.d.ts +8 -0
  135. package/dist/types/utils/oauth/qianfan.d.ts +17 -0
  136. package/dist/types/utils/oauth/qwen-portal.d.ts +19 -0
  137. package/dist/types/utils/oauth/synthetic.d.ts +1 -0
  138. package/dist/types/utils/oauth/tavily.d.ts +17 -0
  139. package/dist/types/utils/oauth/together.d.ts +1 -0
  140. package/dist/types/utils/oauth/types.d.ts +44 -0
  141. package/dist/types/utils/oauth/venice.d.ts +18 -0
  142. package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +18 -0
  143. package/dist/types/utils/oauth/vllm.d.ts +16 -0
  144. package/dist/types/utils/oauth/xiaomi.d.ts +19 -0
  145. package/dist/types/utils/oauth/zai.d.ts +18 -0
  146. package/dist/types/utils/oauth/zenmux.d.ts +1 -0
  147. package/dist/types/utils/overflow.d.ts +54 -0
  148. package/dist/types/utils/parse-bind.d.ts +23 -0
  149. package/dist/types/utils/provider-response.d.ts +3 -0
  150. package/dist/types/utils/retry-after.d.ts +3 -0
  151. package/dist/types/utils/retry.d.ts +26 -0
  152. package/dist/types/utils/schema/adapt.d.ts +24 -0
  153. package/dist/types/utils/schema/compatibility.d.ts +30 -0
  154. package/dist/types/utils/schema/dereference.d.ts +11 -0
  155. package/dist/types/utils/schema/draft.d.ts +10 -0
  156. package/dist/types/utils/schema/equality.d.ts +4 -0
  157. package/dist/types/utils/schema/fields.d.ts +49 -0
  158. package/dist/types/utils/schema/index.d.ts +13 -0
  159. package/dist/types/utils/schema/json-schema-validator.d.ts +12 -0
  160. package/dist/types/utils/schema/meta-validator.d.ts +2 -0
  161. package/dist/types/utils/schema/normalize.d.ts +93 -0
  162. package/dist/types/utils/schema/spill.d.ts +8 -0
  163. package/dist/types/utils/schema/stamps.d.ts +25 -0
  164. package/dist/types/utils/schema/types.d.ts +4 -0
  165. package/dist/types/utils/schema/wire.d.ts +54 -0
  166. package/dist/types/utils/schema/zod-decontaminate.d.ts +31 -0
  167. package/dist/types/utils/sse-debug.d.ts +10 -0
  168. package/dist/types/utils/tool-call-healing.d.ts +71 -0
  169. package/dist/types/utils/tool-choice.d.ts +50 -0
  170. package/dist/types/utils/validation.d.ts +17 -0
  171. package/dist/types/utils.d.ts +28 -0
  172. package/package.json +146 -0
  173. package/src/api-registry.ts +96 -0
  174. package/src/auth-broker/client.ts +358 -0
  175. package/src/auth-broker/index.ts +5 -0
  176. package/src/auth-broker/refresher.ts +127 -0
  177. package/src/auth-broker/remote-store.ts +623 -0
  178. package/src/auth-broker/server.ts +644 -0
  179. package/src/auth-broker/types.ts +127 -0
  180. package/src/auth-broker/wire-schemas.ts +200 -0
  181. package/src/auth-gateway/http.ts +194 -0
  182. package/src/auth-gateway/index.ts +3 -0
  183. package/src/auth-gateway/server.ts +717 -0
  184. package/src/auth-gateway/types.ts +134 -0
  185. package/src/auth-storage.ts +4104 -0
  186. package/src/cli.ts +262 -0
  187. package/src/index.ts +54 -0
  188. package/src/model-cache.ts +129 -0
  189. package/src/model-manager.ts +450 -0
  190. package/src/model-thinking.ts +691 -0
  191. package/src/models.json +73853 -0
  192. package/src/models.json.d.ts +9 -0
  193. package/src/models.ts +56 -0
  194. package/src/prompts/turn-aborted-guidance.md +4 -0
  195. package/src/provider-details.ts +90 -0
  196. package/src/provider-models/bundled-references.ts +38 -0
  197. package/src/provider-models/descriptors.ts +308 -0
  198. package/src/provider-models/google.ts +91 -0
  199. package/src/provider-models/index.ts +5 -0
  200. package/src/provider-models/ollama.ts +153 -0
  201. package/src/provider-models/openai-compat.ts +2275 -0
  202. package/src/provider-models/special.ts +67 -0
  203. package/src/providers/amazon-bedrock.ts +849 -0
  204. package/src/providers/anthropic-messages-server-schema.ts +229 -0
  205. package/src/providers/anthropic-messages-server.ts +677 -0
  206. package/src/providers/anthropic.ts +2696 -0
  207. package/src/providers/aws-credentials.ts +501 -0
  208. package/src/providers/aws-eventstream.ts +185 -0
  209. package/src/providers/aws-sigv4.ts +218 -0
  210. package/src/providers/azure-openai-responses.ts +337 -0
  211. package/src/providers/cursor/gen/agent_pb.ts +15274 -0
  212. package/src/providers/cursor/proto/agent.proto +3526 -0
  213. package/src/providers/cursor/proto/buf.gen.yaml +6 -0
  214. package/src/providers/cursor/proto/buf.yaml +17 -0
  215. package/src/providers/cursor.ts +2561 -0
  216. package/src/providers/error-message.ts +21 -0
  217. package/src/providers/github-copilot-headers.ts +140 -0
  218. package/src/providers/gitlab-duo.ts +372 -0
  219. package/src/providers/google-auth.ts +252 -0
  220. package/src/providers/google-gemini-cli.ts +795 -0
  221. package/src/providers/google-gemini-headers.ts +41 -0
  222. package/src/providers/google-shared.ts +902 -0
  223. package/src/providers/google-types.ts +167 -0
  224. package/src/providers/google-vertex.ts +88 -0
  225. package/src/providers/google.ts +41 -0
  226. package/src/providers/grammar.ts +70 -0
  227. package/src/providers/kimi.ts +52 -0
  228. package/src/providers/mock.ts +500 -0
  229. package/src/providers/ollama.ts +544 -0
  230. package/src/providers/openai-anthropic-shim.ts +138 -0
  231. package/src/providers/openai-chat-server-schema.ts +243 -0
  232. package/src/providers/openai-chat-server.ts +628 -0
  233. package/src/providers/openai-codex/constants.ts +43 -0
  234. package/src/providers/openai-codex/request-transformer.ts +161 -0
  235. package/src/providers/openai-codex/response-handler.ts +81 -0
  236. package/src/providers/openai-codex-responses.ts +2598 -0
  237. package/src/providers/openai-completions-compat.ts +279 -0
  238. package/src/providers/openai-completions.ts +1853 -0
  239. package/src/providers/openai-responses-server-schema.ts +290 -0
  240. package/src/providers/openai-responses-server.ts +1183 -0
  241. package/src/providers/openai-responses-shared.ts +800 -0
  242. package/src/providers/openai-responses.ts +621 -0
  243. package/src/providers/pi-native-client.ts +228 -0
  244. package/src/providers/pi-native-server.ts +210 -0
  245. package/src/providers/register-builtins.ts +412 -0
  246. package/src/providers/synthetic.ts +50 -0
  247. package/src/providers/transform-messages.ts +309 -0
  248. package/src/providers/vision-guard.ts +31 -0
  249. package/src/rate-limit-utils.ts +84 -0
  250. package/src/stream.ts +895 -0
  251. package/src/types.ts +884 -0
  252. package/src/usage/claude.ts +431 -0
  253. package/src/usage/gemini.ts +250 -0
  254. package/src/usage/github-copilot.ts +421 -0
  255. package/src/usage/google-antigravity.ts +201 -0
  256. package/src/usage/kimi.ts +271 -0
  257. package/src/usage/minimax-code.ts +31 -0
  258. package/src/usage/openai-codex.ts +503 -0
  259. package/src/usage/shared.ts +10 -0
  260. package/src/usage/zai.ts +247 -0
  261. package/src/usage.ts +183 -0
  262. package/src/utils/abort.ts +51 -0
  263. package/src/utils/anthropic-auth.ts +87 -0
  264. package/src/utils/discovery/antigravity.ts +261 -0
  265. package/src/utils/discovery/codex.ts +371 -0
  266. package/src/utils/discovery/cursor.ts +306 -0
  267. package/src/utils/discovery/gemini.ts +248 -0
  268. package/src/utils/discovery/index.ts +4 -0
  269. package/src/utils/discovery/openai-compatible.ts +224 -0
  270. package/src/utils/event-stream.ts +142 -0
  271. package/src/utils/fireworks-model-id.ts +30 -0
  272. package/src/utils/foundry.ts +8 -0
  273. package/src/utils/h2-fetch.ts +60 -0
  274. package/src/utils/http-inspector.ts +176 -0
  275. package/src/utils/idle-iterator.ts +250 -0
  276. package/src/utils/json-parse.ts +148 -0
  277. package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
  278. package/src/utils/oauth/anthropic.ts +200 -0
  279. package/src/utils/oauth/api-key-login.ts +87 -0
  280. package/src/utils/oauth/api-key-validation.ts +92 -0
  281. package/src/utils/oauth/callback-server.ts +276 -0
  282. package/src/utils/oauth/cerebras.ts +16 -0
  283. package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
  284. package/src/utils/oauth/cursor.ts +157 -0
  285. package/src/utils/oauth/deepseek.ts +53 -0
  286. package/src/utils/oauth/firepass.ts +24 -0
  287. package/src/utils/oauth/fireworks.ts +15 -0
  288. package/src/utils/oauth/github-copilot.ts +362 -0
  289. package/src/utils/oauth/gitlab-duo.ts +123 -0
  290. package/src/utils/oauth/google-antigravity.ts +200 -0
  291. package/src/utils/oauth/google-gemini-cli.ts +256 -0
  292. package/src/utils/oauth/google-oauth-shared.ts +110 -0
  293. package/src/utils/oauth/huggingface.ts +62 -0
  294. package/src/utils/oauth/index.ts +444 -0
  295. package/src/utils/oauth/kagi.ts +47 -0
  296. package/src/utils/oauth/kilo.ts +87 -0
  297. package/src/utils/oauth/kimi.ts +254 -0
  298. package/src/utils/oauth/litellm.ts +47 -0
  299. package/src/utils/oauth/lm-studio.ts +38 -0
  300. package/src/utils/oauth/minimax-code.ts +78 -0
  301. package/src/utils/oauth/moonshot.ts +16 -0
  302. package/src/utils/oauth/nanogpt.ts +15 -0
  303. package/src/utils/oauth/nvidia.ts +70 -0
  304. package/src/utils/oauth/oauth.html +199 -0
  305. package/src/utils/oauth/ollama-cloud.ts +28 -0
  306. package/src/utils/oauth/ollama.ts +47 -0
  307. package/src/utils/oauth/openai-codex.ts +299 -0
  308. package/src/utils/oauth/opencode.ts +49 -0
  309. package/src/utils/oauth/parallel.ts +46 -0
  310. package/src/utils/oauth/perplexity.ts +206 -0
  311. package/src/utils/oauth/pkce.ts +18 -0
  312. package/src/utils/oauth/qianfan.ts +58 -0
  313. package/src/utils/oauth/qwen-portal.ts +60 -0
  314. package/src/utils/oauth/synthetic.ts +16 -0
  315. package/src/utils/oauth/tavily.ts +46 -0
  316. package/src/utils/oauth/together.ts +16 -0
  317. package/src/utils/oauth/types.ts +94 -0
  318. package/src/utils/oauth/venice.ts +59 -0
  319. package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
  320. package/src/utils/oauth/vllm.ts +40 -0
  321. package/src/utils/oauth/xiaomi.ts +137 -0
  322. package/src/utils/oauth/zai.ts +60 -0
  323. package/src/utils/oauth/zenmux.ts +15 -0
  324. package/src/utils/overflow.ts +137 -0
  325. package/src/utils/parse-bind.ts +54 -0
  326. package/src/utils/provider-response.ts +30 -0
  327. package/src/utils/retry-after.ts +110 -0
  328. package/src/utils/retry.ts +54 -0
  329. package/src/utils/schema/CONSTRAINTS.md +164 -0
  330. package/src/utils/schema/adapt.ts +36 -0
  331. package/src/utils/schema/compatibility.ts +435 -0
  332. package/src/utils/schema/dereference.ts +98 -0
  333. package/src/utils/schema/draft.ts +341 -0
  334. package/src/utils/schema/equality.ts +97 -0
  335. package/src/utils/schema/fields.ts +190 -0
  336. package/src/utils/schema/index.ts +13 -0
  337. package/src/utils/schema/json-schema-validator.ts +577 -0
  338. package/src/utils/schema/meta-validator.ts +167 -0
  339. package/src/utils/schema/normalize.ts +1588 -0
  340. package/src/utils/schema/spill.ts +43 -0
  341. package/src/utils/schema/stamps.ts +97 -0
  342. package/src/utils/schema/types.ts +11 -0
  343. package/src/utils/schema/wire.ts +213 -0
  344. package/src/utils/schema/zod-decontaminate.ts +331 -0
  345. package/src/utils/sse-debug.ts +289 -0
  346. package/src/utils/tool-call-healing.ts +271 -0
  347. package/src/utils/tool-choice.ts +99 -0
  348. package/src/utils/validation.ts +1019 -0
  349. package/src/utils.ts +166 -0
@@ -0,0 +1,795 @@
1
+ /**
2
+ * Google Gemini CLI / Antigravity provider.
3
+ * Shared implementation for both google-gemini-cli and google-antigravity providers.
4
+ * Uses the Cloud Code Assist API endpoint to access Gemini and Anthropic model models.
5
+ */
6
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
7
+ import { scheduler } from "node:timers/promises";
8
+ import { extractHttpStatusFromError, fetchWithRetry, readSseJson } from "@gajae-code/utils";
9
+ import { calculateCost } from "../models";
10
+ import type {
11
+ Api,
12
+ AssistantMessage,
13
+ Context,
14
+ Model,
15
+ StreamFunction,
16
+ StreamOptions,
17
+ TextContent,
18
+ ThinkingContent,
19
+ ToolCall,
20
+ } from "../types";
21
+ import { normalizeSystemPrompts } from "../utils";
22
+ import { AssistantMessageEventStream } from "../utils/event-stream";
23
+ import { appendRawHttpRequestDumpFor400, type RawHttpRequestDump, withHttpStatus } from "../utils/http-inspector";
24
+ // Refresh is the sole responsibility of AuthStorage (broker-aware, single-flighted);
25
+ // the stream provider trusts the access token threaded through `options.apiKey`.
26
+ import { normalizeSchemaForCCA } from "../utils/schema";
27
+ import { ANTIGRAVITY_SYSTEM_INSTRUCTION, getAntigravityUserAgent, getGeminiCliHeaders } from "./google-gemini-headers";
28
+ import type { Content, FunctionCallingConfigMode, ThinkingConfig } from "./google-shared";
29
+ import {
30
+ convertMessages,
31
+ convertTools,
32
+ type GoogleThinkingLevel,
33
+ isThinkingPart,
34
+ mapStopReasonString,
35
+ mapToolChoice,
36
+ nextToolCallId,
37
+ pushBlockEndEvent,
38
+ pushToolCallEvents,
39
+ retainThoughtSignature,
40
+ startTextOrThinkingBlock,
41
+ } from "./google-shared";
42
+
43
+ /**
44
+ * Thinking level for Gemini 3 models. Re-exported from `google-shared` so existing
45
+ * `import { GoogleThinkingLevel } from "./google-gemini-cli"` callers keep working.
46
+ */
47
+ export type { GoogleThinkingLevel };
48
+
49
+ export interface GoogleGeminiCliOptions extends StreamOptions {
50
+ toolChoice?: "auto" | "none" | "any";
51
+ /**
52
+ * Thinking/reasoning configuration.
53
+ * - Gemini 2.x models: use `budgetTokens` to set the thinking budget
54
+ * - Gemini 3 models (gemini-3-pro-*, gemini-3-flash-*): use `level` instead
55
+ *
56
+ * When using `streamSimple`, this is handled automatically based on the model.
57
+ */
58
+ thinking?: {
59
+ enabled: boolean;
60
+ /** Thinking budget in tokens. Use for Gemini 2.x models. */
61
+ budgetTokens?: number;
62
+ /** Thinking level. Use for Gemini 3 models (LOW/HIGH for Pro, MINIMAL/LOW/MEDIUM/HIGH for Flash). */
63
+ level?: GoogleThinkingLevel;
64
+ };
65
+ projectId?: string;
66
+ }
67
+
68
+ const DEFAULT_ENDPOINT = "https://cloudcode-pa.googleapis.com";
69
+ const ANTIGRAVITY_DAILY_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com";
70
+ const ANTIGRAVITY_SANDBOX_ENDPOINT = "https://daily-cloudcode-pa.sandbox.googleapis.com";
71
+ const ANTIGRAVITY_ENDPOINT_FALLBACKS = [ANTIGRAVITY_DAILY_ENDPOINT, ANTIGRAVITY_SANDBOX_ENDPOINT] as const;
72
+
73
+ export {
74
+ ANTIGRAVITY_SYSTEM_INSTRUCTION,
75
+ getAntigravityUserAgent,
76
+ getGeminiCliHeaders,
77
+ getGeminiCliUserAgent,
78
+ } from "./google-gemini-headers";
79
+
80
+ // Retry configuration
81
+ const MAX_RETRIES = 3;
82
+ const BASE_DELAY_MS = 1000;
83
+ const MAX_EMPTY_STREAM_RETRIES = 2;
84
+ const EMPTY_STREAM_BASE_DELAY_MS = 500;
85
+ const RATE_LIMIT_BUDGET_MS = 5 * 60 * 1000;
86
+ const CLAUDE_THINKING_BETA_HEADER = "interleaved-thinking-2025-05-14";
87
+ const GOOGLE_GEMINI_REFRESH_SKEW_MS = 60_000;
88
+ const ANTIGRAVITY_REFRESH_SKEW_MS = 60_000;
89
+
90
+ function isClaudeModel(modelId: string): boolean {
91
+ return modelId.toLowerCase().includes("claude");
92
+ }
93
+
94
+ function needsClaudeThinkingBetaHeader(model: Model<"google-gemini-cli">): boolean {
95
+ return model.provider === "google-antigravity" && model.id.startsWith("claude-") && model.reasoning;
96
+ }
97
+
98
+ function shouldInjectAntigravitySystemInstruction(modelId: string): boolean {
99
+ const normalized = modelId.toLowerCase();
100
+ return normalized.includes("claude") || normalized.includes("gemini-3");
101
+ }
102
+
103
+ /**
104
+ * Extract a clean, user-friendly error message from Google API error response.
105
+ * Parses JSON error responses and returns just the message field.
106
+ */
107
+ function extractErrorMessage(errorText: string): string {
108
+ try {
109
+ const parsed = JSON.parse(errorText) as { error?: { message?: string } };
110
+ if (parsed.error?.message) {
111
+ return parsed.error.message;
112
+ }
113
+ } catch {
114
+ // Not JSON, return as-is
115
+ }
116
+ return errorText;
117
+ }
118
+
119
+ interface GeminiCliApiKeyPayload {
120
+ token?: unknown;
121
+ projectId?: unknown;
122
+ project_id?: unknown;
123
+ refreshToken?: unknown;
124
+ expiresAt?: unknown;
125
+ refresh?: unknown;
126
+ expires?: unknown;
127
+ }
128
+ interface ParsedGeminiCliCredentials {
129
+ accessToken: string;
130
+ projectId: string;
131
+ refreshToken?: string;
132
+ expiresAt?: number;
133
+ }
134
+
135
+ function normalizeExpiryMs(value: unknown): number | undefined {
136
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
137
+ return undefined;
138
+ }
139
+ return value < 10_000_000_000 ? value * 1000 : value;
140
+ }
141
+
142
+ export function parseGeminiCliCredentials(apiKeyRaw: string): ParsedGeminiCliCredentials {
143
+ const invalidCredentialsMessage = "Invalid Google Cloud Code Assist credentials. Use /login to re-authenticate.";
144
+ const missingCredentialsMessage =
145
+ "Missing token or projectId in Google Cloud credentials. Use /login to re-authenticate.";
146
+
147
+ let parsed: GeminiCliApiKeyPayload;
148
+ try {
149
+ parsed = JSON.parse(apiKeyRaw) as GeminiCliApiKeyPayload;
150
+ } catch {
151
+ throw new Error(invalidCredentialsMessage);
152
+ }
153
+
154
+ const projectId =
155
+ typeof parsed.projectId === "string"
156
+ ? parsed.projectId
157
+ : typeof parsed.project_id === "string"
158
+ ? parsed.project_id
159
+ : undefined;
160
+
161
+ if (typeof parsed.token !== "string" || typeof projectId !== "string") {
162
+ throw new Error(missingCredentialsMessage);
163
+ }
164
+
165
+ const refreshToken =
166
+ typeof parsed.refreshToken === "string"
167
+ ? parsed.refreshToken
168
+ : typeof parsed.refresh === "string"
169
+ ? parsed.refresh
170
+ : undefined;
171
+ const expiresAt = normalizeExpiryMs(parsed.expiresAt ?? parsed.expires);
172
+
173
+ return {
174
+ accessToken: parsed.token,
175
+ projectId,
176
+ refreshToken,
177
+ expiresAt,
178
+ };
179
+ }
180
+
181
+ export function shouldRefreshGeminiCliCredentials(
182
+ expiresAt: number | undefined,
183
+ isAntigravity: boolean,
184
+ nowMs = Date.now(),
185
+ ): boolean {
186
+ if (expiresAt === undefined) {
187
+ return false;
188
+ }
189
+
190
+ const skewMs = isAntigravity ? ANTIGRAVITY_REFRESH_SKEW_MS : GOOGLE_GEMINI_REFRESH_SKEW_MS;
191
+ return nowMs + skewMs >= expiresAt;
192
+ }
193
+
194
+ interface CloudCodeAssistRequest {
195
+ project: string;
196
+ model: string;
197
+ request: {
198
+ contents: Content[];
199
+ sessionId?: string;
200
+ systemInstruction?: { role?: string; parts: { text: string }[] };
201
+ generationConfig?: {
202
+ maxOutputTokens?: number;
203
+ temperature?: number;
204
+ topP?: number;
205
+ topK?: number;
206
+ minP?: number;
207
+ presencePenalty?: number;
208
+ repetitionPenalty?: number;
209
+ thinkingConfig?: ThinkingConfig;
210
+ };
211
+ tools?: { functionDeclarations: Record<string, unknown>[] }[] | undefined;
212
+ toolConfig?: {
213
+ functionCallingConfig: {
214
+ mode: FunctionCallingConfigMode;
215
+ };
216
+ };
217
+ };
218
+ requestType?: string;
219
+ userAgent?: string;
220
+ requestId?: string;
221
+ }
222
+
223
+ interface CloudCodeAssistResponseChunk {
224
+ response?: {
225
+ candidates?: Array<{
226
+ content?: {
227
+ role: string;
228
+ parts?: Array<{
229
+ text?: string;
230
+ thought?: boolean;
231
+ thoughtSignature?: string;
232
+ functionCall?: {
233
+ name: string;
234
+ args: Record<string, unknown>;
235
+ id?: string;
236
+ };
237
+ }>;
238
+ };
239
+ finishReason?: string;
240
+ }>;
241
+ usageMetadata?: {
242
+ promptTokenCount?: number;
243
+ candidatesTokenCount?: number;
244
+ thoughtsTokenCount?: number;
245
+ totalTokenCount?: number;
246
+ cachedContentTokenCount?: number;
247
+ };
248
+ modelVersion?: string;
249
+ responseId?: string;
250
+ };
251
+ traceId?: string;
252
+ }
253
+
254
+ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
255
+ model: Model<"google-gemini-cli">,
256
+ context: Context,
257
+ options?: GoogleGeminiCliOptions,
258
+ ): AssistantMessageEventStream => {
259
+ const stream = new AssistantMessageEventStream();
260
+
261
+ (async () => {
262
+ const startTime = Date.now();
263
+ let firstTokenTime: number | undefined;
264
+
265
+ const output: AssistantMessage = {
266
+ role: "assistant",
267
+ content: [],
268
+ api: "google-gemini-cli" as Api,
269
+ provider: model.provider,
270
+ model: model.id,
271
+ usage: {
272
+ input: 0,
273
+ output: 0,
274
+ cacheRead: 0,
275
+ cacheWrite: 0,
276
+ totalTokens: 0,
277
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
278
+ },
279
+ stopReason: "stop",
280
+ timestamp: Date.now(),
281
+ };
282
+ let rawRequestDump: RawHttpRequestDump | undefined;
283
+
284
+ try {
285
+ const apiKeyRaw = options?.apiKey;
286
+ if (!apiKeyRaw) {
287
+ throw new Error("Google Cloud Code Assist requires OAuth authentication. Use /login to authenticate.");
288
+ }
289
+
290
+ const isAntigravity = model.provider === "google-antigravity";
291
+ const parsedCredentials = parseGeminiCliCredentials(apiKeyRaw);
292
+ // AuthStorage already refreshed credentials before threading them
293
+ // here (see {@link OAUTH_REFRESH_SKEW_MS}). If the credential lands
294
+ // expired we bail rather than POSTing a stale token; the next call
295
+ // — driven by AuthStorage's invalidate+retry path — will carry a
296
+ // fresh credential.
297
+ if (
298
+ shouldRefreshGeminiCliCredentials(parsedCredentials.expiresAt, isAntigravity) &&
299
+ parsedCredentials.expiresAt !== undefined &&
300
+ Date.now() >= parsedCredentials.expiresAt
301
+ ) {
302
+ throw new Error(
303
+ "OAuth token expired before request — please retry; AuthStorage will refresh on the next attempt.",
304
+ );
305
+ }
306
+ const { accessToken, projectId } = parsedCredentials;
307
+
308
+ const baseUrl = model.baseUrl?.trim();
309
+ const endpoints = baseUrl ? [baseUrl] : isAntigravity ? ANTIGRAVITY_ENDPOINT_FALLBACKS : [DEFAULT_ENDPOINT];
310
+
311
+ let requestBody = buildRequest(model, context, projectId, options, isAntigravity);
312
+ const replacementPayload = await options?.onPayload?.(requestBody, model);
313
+ if (replacementPayload !== undefined) {
314
+ requestBody = replacementPayload as typeof requestBody;
315
+ }
316
+ const headers = isAntigravity ? { "User-Agent": getAntigravityUserAgent() } : getGeminiCliHeaders(model.id);
317
+
318
+ const requestHeaders = {
319
+ Authorization: `Bearer ${accessToken}`,
320
+ "Content-Type": "application/json",
321
+ Accept: "text/event-stream",
322
+ ...headers,
323
+ ...(needsClaudeThinkingBetaHeader(model) ? { "anthropic-beta": CLAUDE_THINKING_BETA_HEADER } : {}),
324
+ ...(options?.headers ?? {}),
325
+ };
326
+ const requestBodyJson = JSON.stringify(requestBody);
327
+ rawRequestDump = {
328
+ provider: model.provider,
329
+ api: output.api,
330
+ model: model.id,
331
+ method: "POST",
332
+ body: requestBody,
333
+ headers: requestHeaders,
334
+ };
335
+
336
+ const response = await fetchWithRetry(
337
+ attempt => `${endpoints[Math.min(attempt, endpoints.length - 1)]}/v1internal:streamGenerateContent?alt=sse`,
338
+ {
339
+ method: "POST",
340
+ headers: requestHeaders,
341
+ body: requestBodyJson,
342
+ signal: options?.signal,
343
+ maxAttempts: MAX_RETRIES + 1,
344
+ defaultDelayMs: attempt => BASE_DELAY_MS * 2 ** attempt,
345
+ maxDelayMs: options?.maxRetryDelayMs ?? RATE_LIMIT_BUDGET_MS,
346
+ fetch: options?.fetch,
347
+ },
348
+ );
349
+ if (!response.ok) {
350
+ const errorText = await response.text();
351
+ throw withHttpStatus(
352
+ new Error(`Cloud Code Assist API error (${response.status}): ${extractErrorMessage(errorText)}`),
353
+ response.status,
354
+ );
355
+ }
356
+ const requestUrl = response.url;
357
+
358
+ let started = false;
359
+ const ensureStarted = () => {
360
+ if (!started) {
361
+ if (!firstTokenTime) firstTokenTime = Date.now();
362
+ stream.push({ type: "start", partial: output });
363
+ started = true;
364
+ }
365
+ };
366
+
367
+ const resetOutput = () => {
368
+ output.content = [];
369
+ output.usage = {
370
+ input: 0,
371
+ output: 0,
372
+ cacheRead: 0,
373
+ cacheWrite: 0,
374
+ totalTokens: 0,
375
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
376
+ };
377
+ output.stopReason = "stop";
378
+ output.errorMessage = undefined;
379
+ output.timestamp = Date.now();
380
+ started = false;
381
+ };
382
+
383
+ const streamResponse = async (activeResponse: Response): Promise<boolean> => {
384
+ if (!activeResponse.body) {
385
+ throw new Error("No response body");
386
+ }
387
+
388
+ let hasContent = false;
389
+ let currentBlock: TextContent | ThinkingContent | null = null;
390
+ const blocks = output.content;
391
+ const blockIndex = () => blocks.length - 1;
392
+
393
+ for await (const chunk of readSseJson<CloudCodeAssistResponseChunk>(
394
+ activeResponse.body!,
395
+ options?.signal,
396
+ event => options?.onSseEvent?.({ event: event.event, data: event.data, raw: [...event.raw] }, model),
397
+ )) {
398
+ const responseData = chunk.response;
399
+ if (!responseData) continue;
400
+
401
+ const candidate = responseData.candidates?.[0];
402
+ if (candidate?.content?.parts) {
403
+ for (const part of candidate.content.parts) {
404
+ if (part.text !== undefined) {
405
+ hasContent = true;
406
+ const isThinking = isThinkingPart(part);
407
+ if (
408
+ !currentBlock ||
409
+ (isThinking && currentBlock.type !== "thinking") ||
410
+ (!isThinking && currentBlock.type !== "text")
411
+ ) {
412
+ if (currentBlock) {
413
+ pushBlockEndEvent(currentBlock, blockIndex(), output, stream);
414
+ }
415
+ currentBlock = startTextOrThinkingBlock(isThinking, output, stream, ensureStarted);
416
+ }
417
+ if (currentBlock.type === "thinking") {
418
+ currentBlock.thinking += part.text;
419
+ currentBlock.thinkingSignature = retainThoughtSignature(
420
+ currentBlock.thinkingSignature,
421
+ part.thoughtSignature,
422
+ );
423
+ stream.push({
424
+ type: "thinking_delta",
425
+ contentIndex: blockIndex(),
426
+ delta: part.text,
427
+ partial: output,
428
+ });
429
+ } else {
430
+ currentBlock.text += part.text;
431
+ currentBlock.textSignature = retainThoughtSignature(
432
+ currentBlock.textSignature,
433
+ part.thoughtSignature,
434
+ );
435
+ stream.push({
436
+ type: "text_delta",
437
+ contentIndex: blockIndex(),
438
+ delta: part.text,
439
+ partial: output,
440
+ });
441
+ }
442
+ }
443
+
444
+ if (part.functionCall) {
445
+ hasContent = true;
446
+ if (currentBlock) {
447
+ pushBlockEndEvent(currentBlock, blockIndex(), output, stream);
448
+ currentBlock = null;
449
+ }
450
+
451
+ const providedId = part.functionCall.id;
452
+ const needsNewId =
453
+ !providedId || output.content.some(b => b.type === "toolCall" && b.id === providedId);
454
+ const toolCallId = needsNewId ? nextToolCallId(part.functionCall.name || "tool") : providedId;
455
+
456
+ const toolCall: ToolCall = {
457
+ type: "toolCall",
458
+ id: toolCallId,
459
+ name: part.functionCall.name || "",
460
+ arguments: part.functionCall.args as Record<string, unknown>,
461
+ ...(part.thoughtSignature && { thoughtSignature: part.thoughtSignature }),
462
+ };
463
+
464
+ output.content.push(toolCall);
465
+ ensureStarted();
466
+ pushToolCallEvents(toolCall, blockIndex(), output, stream);
467
+ }
468
+ }
469
+ }
470
+
471
+ if (candidate?.finishReason) {
472
+ output.stopReason = mapStopReasonString(candidate.finishReason);
473
+ if (output.content.some(b => b.type === "toolCall")) {
474
+ output.stopReason = "toolUse";
475
+ }
476
+ }
477
+
478
+ if (responseData.usageMetadata) {
479
+ // promptTokenCount includes cachedContentTokenCount, so subtract to get fresh input
480
+ const promptTokens = responseData.usageMetadata.promptTokenCount || 0;
481
+ const cacheReadTokens = responseData.usageMetadata.cachedContentTokenCount || 0;
482
+ const thinkingTokens = responseData.usageMetadata.thoughtsTokenCount || 0;
483
+ output.usage = {
484
+ input: promptTokens - cacheReadTokens,
485
+ output: (responseData.usageMetadata.candidatesTokenCount || 0) + thinkingTokens,
486
+ cacheRead: cacheReadTokens,
487
+ cacheWrite: 0,
488
+ totalTokens: responseData.usageMetadata.totalTokenCount || 0,
489
+ ...(thinkingTokens > 0 ? { reasoningTokens: thinkingTokens } : {}),
490
+ cost: {
491
+ input: 0,
492
+ output: 0,
493
+ cacheRead: 0,
494
+ cacheWrite: 0,
495
+ total: 0,
496
+ },
497
+ };
498
+ calculateCost(model, output.usage);
499
+ }
500
+ }
501
+
502
+ if (currentBlock) {
503
+ pushBlockEndEvent(currentBlock, blockIndex(), output, stream);
504
+ }
505
+
506
+ return hasContent;
507
+ };
508
+
509
+ let receivedContent = false;
510
+ let currentResponse = response;
511
+
512
+ for (let emptyAttempt = 0; emptyAttempt <= MAX_EMPTY_STREAM_RETRIES; emptyAttempt++) {
513
+ if (options?.signal?.aborted) {
514
+ throw new Error("Request was aborted");
515
+ }
516
+
517
+ if (emptyAttempt > 0) {
518
+ const backoffMs = EMPTY_STREAM_BASE_DELAY_MS * 2 ** (emptyAttempt - 1);
519
+ try {
520
+ await scheduler.wait(backoffMs, { signal: options?.signal });
521
+ } catch {
522
+ // Normalize AbortError to expected message for consistent error handling
523
+ throw new Error("Request was aborted");
524
+ }
525
+
526
+ if (!requestUrl) {
527
+ throw new Error("Missing request URL");
528
+ }
529
+
530
+ currentResponse = await (options?.fetch ?? fetch)(requestUrl, {
531
+ method: "POST",
532
+ headers: requestHeaders,
533
+ body: requestBodyJson,
534
+ signal: options?.signal,
535
+ });
536
+
537
+ if (!currentResponse.ok) {
538
+ const retryErrorText = await currentResponse.text();
539
+ throw withHttpStatus(
540
+ new Error(`Cloud Code Assist API error (${currentResponse.status}): ${retryErrorText}`),
541
+ currentResponse.status,
542
+ );
543
+ }
544
+ }
545
+
546
+ const streamed = await streamResponse(currentResponse);
547
+ if (streamed) {
548
+ receivedContent = true;
549
+ break;
550
+ }
551
+
552
+ if (emptyAttempt < MAX_EMPTY_STREAM_RETRIES) {
553
+ resetOutput();
554
+ }
555
+ }
556
+
557
+ if (!receivedContent) {
558
+ throw new Error("Cloud Code Assist API returned an empty response");
559
+ }
560
+
561
+ if (options?.signal?.aborted) {
562
+ throw new Error("Request was aborted");
563
+ }
564
+
565
+ if (output.stopReason === "aborted" || output.stopReason === "error") {
566
+ throw new Error(output.errorMessage ?? "An unknown error occurred");
567
+ }
568
+
569
+ output.duration = Date.now() - startTime;
570
+ if (firstTokenTime) output.ttft = firstTokenTime - startTime;
571
+ stream.push({ type: "done", reason: output.stopReason, message: output });
572
+ stream.end();
573
+ } catch (error) {
574
+ for (const block of output.content) {
575
+ if ("index" in block) {
576
+ delete (block as { index?: number }).index;
577
+ }
578
+ }
579
+ output.stopReason = options?.signal?.aborted ? "aborted" : "error";
580
+ output.errorStatus = extractHttpStatusFromError(error);
581
+ output.errorMessage = await appendRawHttpRequestDumpFor400(
582
+ error instanceof Error ? error.message : JSON.stringify(error),
583
+ error,
584
+ rawRequestDump,
585
+ );
586
+ output.duration = Date.now() - startTime;
587
+ if (firstTokenTime) output.ttft = firstTokenTime - startTime;
588
+ stream.push({ type: "error", reason: output.stopReason, error: output });
589
+ stream.end();
590
+ }
591
+ })();
592
+
593
+ return stream;
594
+ };
595
+
596
+ const INT63_MASK = (1n << 63n) - 1n;
597
+ const ANTIGRAVITY_RANDOM_BOUND = 9_000_000_000_000_000_000n;
598
+
599
+ function formatSignedDecimalSessionId(value: bigint): string {
600
+ return `-${value.toString()}`;
601
+ }
602
+
603
+ function deriveSignedDecimalFromHash(text: string): string {
604
+ const digest = createHash("sha256").update(text).digest();
605
+ let value = 0n;
606
+ for (let index = 0; index < 8; index += 1) {
607
+ value = (value << 8n) | BigInt(digest[index] ?? 0);
608
+ }
609
+ return formatSignedDecimalSessionId(value & INT63_MASK);
610
+ }
611
+
612
+ function randomBoundedInt63(maxExclusive: bigint): bigint {
613
+ while (true) {
614
+ const bytes = randomBytes(8);
615
+ let value = 0n;
616
+ for (const byte of bytes) {
617
+ value = (value << 8n) | BigInt(byte);
618
+ }
619
+ value &= INT63_MASK;
620
+ if (value < maxExclusive) {
621
+ return value;
622
+ }
623
+ }
624
+ }
625
+
626
+ function randomSignedDecimalSessionId(): string {
627
+ return formatSignedDecimalSessionId(randomBoundedInt63(ANTIGRAVITY_RANDOM_BOUND));
628
+ }
629
+
630
+ function getFirstUserTextForAntigravitySession(context: Context): string | undefined {
631
+ for (const message of context.messages) {
632
+ if (message.role !== "user") {
633
+ continue;
634
+ }
635
+
636
+ if (typeof message.content === "string") {
637
+ return message.content;
638
+ }
639
+
640
+ if (Array.isArray(message.content)) {
641
+ const firstTextPart = message.content.find((item): item is TextContent => item.type === "text");
642
+ return firstTextPart?.text;
643
+ }
644
+
645
+ return undefined;
646
+ }
647
+
648
+ return undefined;
649
+ }
650
+
651
+ function deriveAntigravitySessionId(context: Context): string {
652
+ const text = getFirstUserTextForAntigravitySession(context);
653
+ if (text && text.trim().length > 0) {
654
+ return deriveSignedDecimalFromHash(text);
655
+ }
656
+
657
+ return randomSignedDecimalSessionId();
658
+ }
659
+
660
+ function normalizeAntigravityTools(
661
+ tools: CloudCodeAssistRequest["request"]["tools"],
662
+ ): CloudCodeAssistRequest["request"]["tools"] {
663
+ return tools?.map(tool => ({
664
+ ...tool,
665
+ functionDeclarations: tool.functionDeclarations.map(declaration => {
666
+ if ("parameters" in declaration) {
667
+ return declaration;
668
+ }
669
+
670
+ const { parametersJsonSchema, ...rest } = declaration;
671
+ return {
672
+ ...rest,
673
+ parameters: normalizeSchemaForCCA(parametersJsonSchema),
674
+ };
675
+ }),
676
+ }));
677
+ }
678
+
679
+ export function buildRequest(
680
+ model: Model<"google-gemini-cli">,
681
+ context: Context,
682
+ projectId: string,
683
+ options: GoogleGeminiCliOptions = {},
684
+ isAntigravity = false,
685
+ ): CloudCodeAssistRequest {
686
+ const systemPrompts = normalizeSystemPrompts(context.systemPrompt);
687
+ const contents = convertMessages(model, context);
688
+ const generationConfig: CloudCodeAssistRequest["request"]["generationConfig"] = {};
689
+ if (options.temperature !== undefined) {
690
+ generationConfig.temperature = options.temperature;
691
+ }
692
+ if (options.maxTokens !== undefined) {
693
+ generationConfig.maxOutputTokens = options.maxTokens;
694
+ }
695
+ if (options.topP !== undefined) {
696
+ generationConfig.topP = options.topP;
697
+ }
698
+ if (options.topK !== undefined) {
699
+ generationConfig.topK = options.topK;
700
+ }
701
+ if (options.minP !== undefined) {
702
+ generationConfig.minP = options.minP;
703
+ }
704
+ if (options.presencePenalty !== undefined) {
705
+ generationConfig.presencePenalty = options.presencePenalty;
706
+ }
707
+ if (options.repetitionPenalty !== undefined) {
708
+ generationConfig.repetitionPenalty = options.repetitionPenalty;
709
+ }
710
+
711
+ // Thinking config
712
+ if (options.thinking?.enabled && model.reasoning) {
713
+ generationConfig.thinkingConfig = {
714
+ includeThoughts: true,
715
+ };
716
+ // Gemini 3 models use thinkingLevel, older models use thinkingBudget
717
+ if (options.thinking.level !== undefined) {
718
+ // Cast to any since our GoogleThinkingLevel mirrors Google's ThinkingLevel enum values
719
+ generationConfig.thinkingConfig.thinkingLevel = options.thinking.level as any;
720
+ } else if (options.thinking.budgetTokens !== undefined) {
721
+ generationConfig.thinkingConfig.thinkingBudget = options.thinking.budgetTokens;
722
+ }
723
+ }
724
+
725
+ const request: CloudCodeAssistRequest["request"] = {
726
+ contents,
727
+ };
728
+
729
+ if (isAntigravity) {
730
+ request.sessionId = deriveAntigravitySessionId(context);
731
+ }
732
+
733
+ // System instruction must be object with parts, not plain string
734
+ if (systemPrompts.length > 0) {
735
+ request.systemInstruction = {
736
+ parts: systemPrompts.map(text => ({ text })),
737
+ };
738
+ }
739
+
740
+ if (Object.keys(generationConfig).length > 0) {
741
+ request.generationConfig = generationConfig;
742
+ }
743
+
744
+ if (context.tools && context.tools.length > 0) {
745
+ const convertedTools = convertTools(context.tools, model);
746
+ request.tools = isAntigravity ? normalizeAntigravityTools(convertedTools) : convertedTools;
747
+ if (options.toolChoice) {
748
+ request.toolConfig = {
749
+ functionCallingConfig: {
750
+ mode: mapToolChoice(options.toolChoice),
751
+ },
752
+ };
753
+ }
754
+ }
755
+
756
+ if (isAntigravity && !isClaudeModel(model.id) && request.generationConfig?.maxOutputTokens !== undefined) {
757
+ delete request.generationConfig.maxOutputTokens;
758
+ if (Object.keys(request.generationConfig).length === 0) {
759
+ delete request.generationConfig;
760
+ }
761
+ }
762
+
763
+ if (isAntigravity && isClaudeModel(model.id)) {
764
+ request.toolConfig = {
765
+ functionCallingConfig: {
766
+ mode: "VALIDATED" as FunctionCallingConfigMode,
767
+ },
768
+ };
769
+ }
770
+
771
+ if (isAntigravity && shouldInjectAntigravitySystemInstruction(model.id)) {
772
+ const existingParts = request.systemInstruction?.parts ?? [];
773
+ request.systemInstruction = {
774
+ role: "user",
775
+ parts: [
776
+ { text: ANTIGRAVITY_SYSTEM_INSTRUCTION },
777
+ { text: `Please ignore following [ignore]${ANTIGRAVITY_SYSTEM_INSTRUCTION}[/ignore]` },
778
+ ...existingParts,
779
+ ],
780
+ };
781
+ }
782
+
783
+ return {
784
+ project: projectId,
785
+ model: model.id,
786
+ request,
787
+ ...(isAntigravity
788
+ ? {
789
+ requestType: "agent",
790
+ userAgent: "antigravity",
791
+ requestId: `agent-${randomUUID()}`,
792
+ }
793
+ : {}),
794
+ };
795
+ }