@llumiverse/drivers 1.2.0 → 1.4.0-dev.20260629.090753Z

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 (403) hide show
  1. package/lib/{types/adobe → adobe}/firefly.d.ts +2 -1
  2. package/lib/adobe/firefly.d.ts.map +1 -0
  3. package/lib/adobe/firefly.js +120 -0
  4. package/lib/adobe/firefly.js.map +1 -0
  5. package/lib/anthropic/index.d.ts +23 -0
  6. package/lib/anthropic/index.d.ts.map +1 -0
  7. package/lib/anthropic/index.js +68 -0
  8. package/lib/anthropic/index.js.map +1 -0
  9. package/lib/{types/azure → azure}/azure_foundry.d.ts +9 -7
  10. package/lib/azure/azure_foundry.d.ts.map +1 -0
  11. package/lib/{esm/azure → azure}/azure_foundry.js +138 -124
  12. package/lib/azure/azure_foundry.js.map +1 -0
  13. package/lib/{types/bedrock → bedrock}/converse.d.ts +2 -2
  14. package/lib/bedrock/converse.d.ts.map +1 -0
  15. package/lib/{esm/bedrock → bedrock}/converse.js +86 -79
  16. package/lib/bedrock/converse.js.map +1 -0
  17. package/lib/bedrock/embeddings.d.ts +4 -0
  18. package/lib/bedrock/embeddings.d.ts.map +1 -0
  19. package/lib/bedrock/embeddings.js +359 -0
  20. package/lib/bedrock/embeddings.js.map +1 -0
  21. package/lib/{types/bedrock → bedrock}/index.d.ts +33 -9
  22. package/lib/bedrock/index.d.ts.map +1 -0
  23. package/lib/{esm/bedrock → bedrock}/index.js +599 -324
  24. package/lib/bedrock/index.js.map +1 -0
  25. package/lib/{types/bedrock → bedrock}/nova-image-payload.d.ts +5 -5
  26. package/lib/bedrock/nova-image-payload.d.ts.map +1 -0
  27. package/lib/{esm/bedrock → bedrock}/nova-image-payload.js +31 -36
  28. package/lib/bedrock/nova-image-payload.js.map +1 -0
  29. package/lib/{types/bedrock → bedrock}/payloads.d.ts +1 -1
  30. package/lib/bedrock/payloads.d.ts.map +1 -0
  31. package/lib/{cjs/bedrock → bedrock}/payloads.js.map +1 -1
  32. package/lib/{types/bedrock → bedrock}/s3.d.ts +3 -2
  33. package/lib/bedrock/s3.d.ts.map +1 -0
  34. package/lib/{esm/bedrock → bedrock}/s3.js +27 -23
  35. package/lib/bedrock/s3.js.map +1 -0
  36. package/lib/{types/bedrock → bedrock}/twelvelabs.d.ts +8 -9
  37. package/lib/bedrock/twelvelabs.d.ts.map +1 -0
  38. package/lib/{esm/bedrock → bedrock}/twelvelabs.js +17 -21
  39. package/lib/bedrock/twelvelabs.js.map +1 -0
  40. package/lib/{types/groq → groq}/index.d.ts +6 -4
  41. package/lib/groq/index.d.ts.map +1 -0
  42. package/lib/{esm/groq → groq}/index.js +72 -39
  43. package/lib/groq/index.js.map +1 -0
  44. package/lib/{types/huggingface_ie.d.ts → huggingface_ie.d.ts} +4 -3
  45. package/lib/huggingface_ie.d.ts.map +1 -0
  46. package/lib/{esm/huggingface_ie.js → huggingface_ie.js} +32 -31
  47. package/lib/huggingface_ie.js.map +1 -0
  48. package/lib/index.d.ts +16 -0
  49. package/lib/index.d.ts.map +1 -0
  50. package/lib/index.js +16 -0
  51. package/lib/index.js.map +1 -0
  52. package/lib/{types/mistral → mistral}/index.d.ts +6 -5
  53. package/lib/mistral/index.d.ts.map +1 -0
  54. package/lib/{esm/mistral → mistral}/index.js +66 -38
  55. package/lib/mistral/index.js.map +1 -0
  56. package/lib/{types/mistral → mistral}/types.d.ts +1 -0
  57. package/lib/mistral/types.d.ts.map +1 -0
  58. package/lib/mistral/types.js.map +1 -0
  59. package/lib/{types/openai → openai}/azure_openai.d.ts +5 -4
  60. package/lib/openai/azure_openai.d.ts.map +1 -0
  61. package/lib/{esm/openai → openai}/azure_openai.js +17 -14
  62. package/lib/openai/azure_openai.js.map +1 -0
  63. package/lib/openai/drivers.d.ts +4 -0
  64. package/lib/openai/drivers.d.ts.map +1 -0
  65. package/lib/openai/drivers.js +9 -0
  66. package/lib/openai/drivers.js.map +1 -0
  67. package/lib/{types/openai → openai}/index.d.ts +17 -5
  68. package/lib/openai/index.d.ts.map +1 -0
  69. package/lib/{esm/openai → openai}/index.js +254 -148
  70. package/lib/openai/index.js.map +1 -0
  71. package/lib/{types/openai → openai}/openai.d.ts +3 -3
  72. package/lib/openai/openai.d.ts.map +1 -0
  73. package/lib/openai/openai.js +15 -0
  74. package/lib/openai/openai.js.map +1 -0
  75. package/lib/{types/openai → openai}/openai_compatible.d.ts +3 -3
  76. package/lib/openai/openai_compatible.d.ts.map +1 -0
  77. package/lib/{esm/openai → openai}/openai_compatible.js +14 -11
  78. package/lib/openai/openai_compatible.js.map +1 -0
  79. package/lib/{types/openai → openai}/openai_format.d.ts +3 -2
  80. package/lib/openai/openai_format.d.ts.map +1 -0
  81. package/lib/{esm/openai → openai}/openai_format.js +66 -24
  82. package/lib/openai/openai_format.js.map +1 -0
  83. package/lib/{types/replicate.d.ts → replicate.d.ts} +3 -2
  84. package/lib/replicate.d.ts.map +1 -0
  85. package/lib/{esm/replicate.js → replicate.js} +59 -49
  86. package/lib/replicate.js.map +1 -0
  87. package/lib/shared/claude-messages.d.ts +99 -0
  88. package/lib/shared/claude-messages.d.ts.map +1 -0
  89. package/lib/shared/claude-messages.js +869 -0
  90. package/lib/shared/claude-messages.js.map +1 -0
  91. package/lib/{types/shared → shared}/claude-thinking.d.ts +1 -1
  92. package/lib/shared/claude-thinking.d.ts.map +1 -0
  93. package/lib/{esm/shared → shared}/claude-thinking.js +5 -7
  94. package/lib/shared/claude-thinking.js.map +1 -0
  95. package/lib/shared/debug-prompt.d.ts +5 -0
  96. package/lib/shared/debug-prompt.d.ts.map +1 -0
  97. package/lib/shared/debug-prompt.js +25 -0
  98. package/lib/shared/debug-prompt.js.map +1 -0
  99. package/lib/{types/test-driver → test-driver}/TestErrorCompletionStream.d.ts +1 -1
  100. package/lib/test-driver/TestErrorCompletionStream.d.ts.map +1 -0
  101. package/lib/test-driver/TestErrorCompletionStream.js +16 -0
  102. package/lib/test-driver/TestErrorCompletionStream.js.map +1 -0
  103. package/lib/{types/test-driver → test-driver}/TestValidationErrorCompletionStream.d.ts +1 -1
  104. package/lib/test-driver/TestValidationErrorCompletionStream.d.ts.map +1 -0
  105. package/lib/{esm/test-driver → test-driver}/TestValidationErrorCompletionStream.js +4 -4
  106. package/lib/test-driver/TestValidationErrorCompletionStream.js.map +1 -0
  107. package/lib/{types/test-driver → test-driver}/index.d.ts +3 -3
  108. package/lib/test-driver/index.d.ts.map +1 -0
  109. package/lib/{esm/test-driver → test-driver}/index.js +20 -20
  110. package/lib/test-driver/index.js.map +1 -0
  111. package/lib/{types/test-driver → test-driver}/utils.d.ts +1 -1
  112. package/lib/test-driver/utils.d.ts.map +1 -0
  113. package/lib/{esm/test-driver → test-driver}/utils.js +4 -4
  114. package/lib/test-driver/utils.js.map +1 -0
  115. package/lib/togetherai/index.d.ts +15 -0
  116. package/lib/togetherai/index.d.ts.map +1 -0
  117. package/lib/togetherai/index.js +54 -0
  118. package/lib/togetherai/index.js.map +1 -0
  119. package/lib/{types/togetherai → togetherai}/interfaces.d.ts +1 -1
  120. package/lib/togetherai/interfaces.d.ts.map +1 -0
  121. package/lib/{cjs/watsonx → togetherai}/interfaces.js.map +1 -1
  122. package/lib/vertexai/debug.d.ts +2 -0
  123. package/lib/vertexai/debug.d.ts.map +1 -0
  124. package/lib/{esm/vertexai → vertexai}/debug.js +1 -1
  125. package/lib/vertexai/debug.js.map +1 -0
  126. package/lib/vertexai/embeddings/embed-legacy-multimodal.d.ts +9 -0
  127. package/lib/vertexai/embeddings/embed-legacy-multimodal.d.ts.map +1 -0
  128. package/lib/vertexai/embeddings/embed-legacy-multimodal.js +141 -0
  129. package/lib/vertexai/embeddings/embed-legacy-multimodal.js.map +1 -0
  130. package/lib/vertexai/embeddings/embed.d.ts +13 -0
  131. package/lib/vertexai/embeddings/embed.d.ts.map +1 -0
  132. package/lib/vertexai/embeddings/embed.js +162 -0
  133. package/lib/vertexai/embeddings/embed.js.map +1 -0
  134. package/lib/vertexai/embeddings/source-utils.d.ts +9 -0
  135. package/lib/vertexai/embeddings/source-utils.d.ts.map +1 -0
  136. package/lib/vertexai/embeddings/source-utils.js +18 -0
  137. package/lib/vertexai/embeddings/source-utils.js.map +1 -0
  138. package/lib/{types/vertexai → vertexai}/index.d.ts +31 -19
  139. package/lib/vertexai/index.d.ts.map +1 -0
  140. package/lib/{esm/vertexai → vertexai}/index.js +221 -159
  141. package/lib/vertexai/index.js.map +1 -0
  142. package/lib/vertexai/models/claude.d.ts +16 -0
  143. package/lib/vertexai/models/claude.d.ts.map +1 -0
  144. package/lib/vertexai/models/claude.js +65 -0
  145. package/lib/vertexai/models/claude.js.map +1 -0
  146. package/lib/{types/vertexai → vertexai}/models/gemini.d.ts +29 -5
  147. package/lib/vertexai/models/gemini.d.ts.map +1 -0
  148. package/lib/{esm/vertexai → vertexai}/models/gemini.js +255 -127
  149. package/lib/vertexai/models/gemini.js.map +1 -0
  150. package/lib/{types/vertexai → vertexai}/models/imagen.d.ts +12 -11
  151. package/lib/vertexai/models/imagen.d.ts.map +1 -0
  152. package/lib/{esm/vertexai → vertexai}/models/imagen.js +74 -47
  153. package/lib/vertexai/models/imagen.js.map +1 -0
  154. package/lib/{types/vertexai → vertexai}/models/llama.d.ts +4 -4
  155. package/lib/vertexai/models/llama.d.ts.map +1 -0
  156. package/lib/{esm/vertexai → vertexai}/models/llama.js +37 -33
  157. package/lib/vertexai/models/llama.js.map +1 -0
  158. package/lib/vertexai/models.d.ts +21 -0
  159. package/lib/vertexai/models.d.ts.map +1 -0
  160. package/lib/{esm/vertexai → vertexai}/models.js +12 -9
  161. package/lib/vertexai/models.js.map +1 -0
  162. package/lib/{types/watsonx → watsonx}/index.d.ts +4 -3
  163. package/lib/watsonx/index.d.ts.map +1 -0
  164. package/lib/{esm/watsonx → watsonx}/index.js +69 -41
  165. package/lib/watsonx/index.js.map +1 -0
  166. package/lib/watsonx/interfaces.d.ts.map +1 -0
  167. package/lib/{esm/watsonx → watsonx}/interfaces.js.map +1 -1
  168. package/lib/{types/xai → xai}/index.d.ts +4 -4
  169. package/lib/xai/index.d.ts.map +1 -0
  170. package/lib/{esm/xai → xai}/index.js +22 -18
  171. package/lib/xai/index.js.map +1 -0
  172. package/package.json +88 -36
  173. package/src/adobe/firefly.ts +60 -43
  174. package/src/anthropic/index.ts +121 -0
  175. package/src/azure/azure_foundry.ts +210 -165
  176. package/src/bedrock/converse.ts +124 -101
  177. package/src/bedrock/embeddings.test.ts +508 -0
  178. package/src/bedrock/embeddings.ts +517 -0
  179. package/src/bedrock/error-handling.test.ts +62 -14
  180. package/src/bedrock/index.ts +832 -466
  181. package/src/bedrock/nova-image-payload.ts +98 -91
  182. package/src/bedrock/payloads.ts +9 -9
  183. package/src/bedrock/s3.test.ts +39 -0
  184. package/src/bedrock/s3.ts +57 -38
  185. package/src/bedrock/streaming-tool-use.test.ts +86 -69
  186. package/src/bedrock/twelvelabs.ts +35 -32
  187. package/src/groq/index.ts +143 -60
  188. package/src/http-timeout-wiring.test.ts +263 -0
  189. package/src/huggingface_ie.ts +45 -61
  190. package/src/index.ts +15 -15
  191. package/src/mistral/index.ts +97 -48
  192. package/src/mistral/types.ts +13 -12
  193. package/src/openai/azure_openai.ts +35 -27
  194. package/src/openai/drivers.ts +9 -0
  195. package/src/openai/error-handling.test.ts +308 -113
  196. package/src/openai/index.ts +389 -254
  197. package/src/openai/openai.ts +6 -13
  198. package/src/openai/openai_compatible.ts +35 -25
  199. package/src/openai/openai_format.ts +90 -40
  200. package/src/replicate.ts +108 -86
  201. package/src/shared/claude-messages.test.ts +37 -0
  202. package/src/shared/claude-messages.ts +1085 -0
  203. package/src/shared/claude-thinking.ts +6 -12
  204. package/src/shared/debug-prompt.ts +28 -0
  205. package/src/test-driver/TestErrorCompletionStream.ts +9 -10
  206. package/src/test-driver/TestValidationErrorCompletionStream.ts +9 -10
  207. package/src/test-driver/index.ts +43 -27
  208. package/src/test-driver/utils.ts +8 -8
  209. package/src/togetherai/index.test.ts +32 -0
  210. package/src/togetherai/index.ts +64 -128
  211. package/src/togetherai/interfaces.ts +5 -5
  212. package/src/vertexai/debug.ts +3 -3
  213. package/src/vertexai/embeddings/embed-legacy-multimodal.ts +211 -0
  214. package/src/vertexai/embeddings/embed.ts +206 -0
  215. package/src/vertexai/embeddings/embeddings.test.ts +497 -0
  216. package/src/vertexai/embeddings/source-utils.test.ts +54 -0
  217. package/src/vertexai/embeddings/source-utils.ts +29 -0
  218. package/src/vertexai/index.ts +339 -253
  219. package/src/vertexai/models/claude-error-handling.test.ts +86 -39
  220. package/src/vertexai/models/claude-streaming-spacing.test.ts +82 -75
  221. package/src/vertexai/models/claude.ts +84 -1041
  222. package/src/vertexai/models/gemini-conversation-mutation.test.ts +65 -23
  223. package/src/vertexai/models/gemini-error-handling.test.ts +126 -10
  224. package/src/vertexai/models/gemini-orphaned-tool-result.test.ts +118 -0
  225. package/src/vertexai/models/gemini.ts +372 -182
  226. package/src/vertexai/models/imagen.ts +164 -100
  227. package/src/vertexai/models/llama.ts +70 -48
  228. package/src/vertexai/models.ts +36 -16
  229. package/src/watsonx/index.ts +101 -65
  230. package/src/watsonx/interfaces.ts +10 -15
  231. package/src/xai/index.ts +30 -30
  232. package/lib/cjs/adobe/firefly.js +0 -120
  233. package/lib/cjs/adobe/firefly.js.map +0 -1
  234. package/lib/cjs/azure/azure_foundry.js +0 -432
  235. package/lib/cjs/azure/azure_foundry.js.map +0 -1
  236. package/lib/cjs/bedrock/converse.js +0 -359
  237. package/lib/cjs/bedrock/converse.js.map +0 -1
  238. package/lib/cjs/bedrock/index.js +0 -1508
  239. package/lib/cjs/bedrock/index.js.map +0 -1
  240. package/lib/cjs/bedrock/nova-image-payload.js +0 -207
  241. package/lib/cjs/bedrock/nova-image-payload.js.map +0 -1
  242. package/lib/cjs/bedrock/payloads.js +0 -3
  243. package/lib/cjs/bedrock/s3.js +0 -107
  244. package/lib/cjs/bedrock/s3.js.map +0 -1
  245. package/lib/cjs/bedrock/twelvelabs.js +0 -87
  246. package/lib/cjs/bedrock/twelvelabs.js.map +0 -1
  247. package/lib/cjs/groq/index.js +0 -328
  248. package/lib/cjs/groq/index.js.map +0 -1
  249. package/lib/cjs/huggingface_ie.js +0 -201
  250. package/lib/cjs/huggingface_ie.js.map +0 -1
  251. package/lib/cjs/index.js +0 -31
  252. package/lib/cjs/index.js.map +0 -1
  253. package/lib/cjs/mistral/index.js +0 -176
  254. package/lib/cjs/mistral/index.js.map +0 -1
  255. package/lib/cjs/mistral/types.js +0 -83
  256. package/lib/cjs/mistral/types.js.map +0 -1
  257. package/lib/cjs/openai/azure_openai.js +0 -72
  258. package/lib/cjs/openai/azure_openai.js.map +0 -1
  259. package/lib/cjs/openai/index.js +0 -1129
  260. package/lib/cjs/openai/index.js.map +0 -1
  261. package/lib/cjs/openai/openai.js +0 -21
  262. package/lib/cjs/openai/openai.js.map +0 -1
  263. package/lib/cjs/openai/openai_compatible.js +0 -63
  264. package/lib/cjs/openai/openai_compatible.js.map +0 -1
  265. package/lib/cjs/openai/openai_format.js +0 -131
  266. package/lib/cjs/openai/openai_format.js.map +0 -1
  267. package/lib/cjs/package.json +0 -3
  268. package/lib/cjs/replicate.js +0 -275
  269. package/lib/cjs/replicate.js.map +0 -1
  270. package/lib/cjs/shared/claude-thinking.js +0 -60
  271. package/lib/cjs/shared/claude-thinking.js.map +0 -1
  272. package/lib/cjs/test-driver/TestErrorCompletionStream.js +0 -20
  273. package/lib/cjs/test-driver/TestErrorCompletionStream.js.map +0 -1
  274. package/lib/cjs/test-driver/TestValidationErrorCompletionStream.js +0 -24
  275. package/lib/cjs/test-driver/TestValidationErrorCompletionStream.js.map +0 -1
  276. package/lib/cjs/test-driver/index.js +0 -109
  277. package/lib/cjs/test-driver/index.js.map +0 -1
  278. package/lib/cjs/test-driver/utils.js +0 -30
  279. package/lib/cjs/test-driver/utils.js.map +0 -1
  280. package/lib/cjs/togetherai/index.js +0 -126
  281. package/lib/cjs/togetherai/index.js.map +0 -1
  282. package/lib/cjs/togetherai/interfaces.js +0 -3
  283. package/lib/cjs/togetherai/interfaces.js.map +0 -1
  284. package/lib/cjs/vertexai/debug.js +0 -12
  285. package/lib/cjs/vertexai/debug.js.map +0 -1
  286. package/lib/cjs/vertexai/embeddings/embeddings-image.js +0 -27
  287. package/lib/cjs/vertexai/embeddings/embeddings-image.js.map +0 -1
  288. package/lib/cjs/vertexai/embeddings/embeddings-text.js +0 -23
  289. package/lib/cjs/vertexai/embeddings/embeddings-text.js.map +0 -1
  290. package/lib/cjs/vertexai/index.js +0 -644
  291. package/lib/cjs/vertexai/index.js.map +0 -1
  292. package/lib/cjs/vertexai/models/claude.js +0 -917
  293. package/lib/cjs/vertexai/models/claude.js.map +0 -1
  294. package/lib/cjs/vertexai/models/gemini.js +0 -858
  295. package/lib/cjs/vertexai/models/gemini.js.map +0 -1
  296. package/lib/cjs/vertexai/models/imagen.js +0 -303
  297. package/lib/cjs/vertexai/models/imagen.js.map +0 -1
  298. package/lib/cjs/vertexai/models/llama.js +0 -183
  299. package/lib/cjs/vertexai/models/llama.js.map +0 -1
  300. package/lib/cjs/vertexai/models.js +0 -35
  301. package/lib/cjs/vertexai/models.js.map +0 -1
  302. package/lib/cjs/watsonx/index.js +0 -161
  303. package/lib/cjs/watsonx/index.js.map +0 -1
  304. package/lib/cjs/watsonx/interfaces.js +0 -3
  305. package/lib/cjs/xai/index.js +0 -65
  306. package/lib/cjs/xai/index.js.map +0 -1
  307. package/lib/esm/adobe/firefly.js +0 -116
  308. package/lib/esm/adobe/firefly.js.map +0 -1
  309. package/lib/esm/azure/azure_foundry.js.map +0 -1
  310. package/lib/esm/bedrock/converse.js.map +0 -1
  311. package/lib/esm/bedrock/index.js.map +0 -1
  312. package/lib/esm/bedrock/nova-image-payload.js.map +0 -1
  313. package/lib/esm/bedrock/payloads.js.map +0 -1
  314. package/lib/esm/bedrock/s3.js.map +0 -1
  315. package/lib/esm/bedrock/twelvelabs.js.map +0 -1
  316. package/lib/esm/groq/index.js.map +0 -1
  317. package/lib/esm/huggingface_ie.js.map +0 -1
  318. package/lib/esm/index.js +0 -15
  319. package/lib/esm/index.js.map +0 -1
  320. package/lib/esm/mistral/index.js.map +0 -1
  321. package/lib/esm/mistral/types.js.map +0 -1
  322. package/lib/esm/openai/azure_openai.js.map +0 -1
  323. package/lib/esm/openai/index.js.map +0 -1
  324. package/lib/esm/openai/openai.js +0 -14
  325. package/lib/esm/openai/openai.js.map +0 -1
  326. package/lib/esm/openai/openai_compatible.js.map +0 -1
  327. package/lib/esm/openai/openai_format.js.map +0 -1
  328. package/lib/esm/replicate.js.map +0 -1
  329. package/lib/esm/shared/claude-thinking.js.map +0 -1
  330. package/lib/esm/test-driver/TestErrorCompletionStream.js +0 -16
  331. package/lib/esm/test-driver/TestErrorCompletionStream.js.map +0 -1
  332. package/lib/esm/test-driver/TestValidationErrorCompletionStream.js.map +0 -1
  333. package/lib/esm/test-driver/index.js.map +0 -1
  334. package/lib/esm/test-driver/utils.js.map +0 -1
  335. package/lib/esm/togetherai/index.js +0 -122
  336. package/lib/esm/togetherai/index.js.map +0 -1
  337. package/lib/esm/togetherai/interfaces.js.map +0 -1
  338. package/lib/esm/vertexai/debug.js.map +0 -1
  339. package/lib/esm/vertexai/embeddings/embeddings-image.js +0 -24
  340. package/lib/esm/vertexai/embeddings/embeddings-image.js.map +0 -1
  341. package/lib/esm/vertexai/embeddings/embeddings-text.js +0 -20
  342. package/lib/esm/vertexai/embeddings/embeddings-text.js.map +0 -1
  343. package/lib/esm/vertexai/index.js.map +0 -1
  344. package/lib/esm/vertexai/models/claude.js +0 -906
  345. package/lib/esm/vertexai/models/claude.js.map +0 -1
  346. package/lib/esm/vertexai/models/gemini.js.map +0 -1
  347. package/lib/esm/vertexai/models/imagen.js.map +0 -1
  348. package/lib/esm/vertexai/models/llama.js.map +0 -1
  349. package/lib/esm/vertexai/models.js.map +0 -1
  350. package/lib/esm/watsonx/index.js.map +0 -1
  351. package/lib/esm/xai/index.js.map +0 -1
  352. package/lib/types/adobe/firefly.d.ts.map +0 -1
  353. package/lib/types/azure/azure_foundry.d.ts.map +0 -1
  354. package/lib/types/bedrock/converse.d.ts.map +0 -1
  355. package/lib/types/bedrock/index.d.ts.map +0 -1
  356. package/lib/types/bedrock/nova-image-payload.d.ts.map +0 -1
  357. package/lib/types/bedrock/payloads.d.ts.map +0 -1
  358. package/lib/types/bedrock/s3.d.ts.map +0 -1
  359. package/lib/types/bedrock/twelvelabs.d.ts.map +0 -1
  360. package/lib/types/groq/index.d.ts.map +0 -1
  361. package/lib/types/huggingface_ie.d.ts.map +0 -1
  362. package/lib/types/index.d.ts +0 -15
  363. package/lib/types/index.d.ts.map +0 -1
  364. package/lib/types/mistral/index.d.ts.map +0 -1
  365. package/lib/types/mistral/types.d.ts.map +0 -1
  366. package/lib/types/openai/azure_openai.d.ts.map +0 -1
  367. package/lib/types/openai/index.d.ts.map +0 -1
  368. package/lib/types/openai/openai.d.ts.map +0 -1
  369. package/lib/types/openai/openai_compatible.d.ts.map +0 -1
  370. package/lib/types/openai/openai_format.d.ts.map +0 -1
  371. package/lib/types/replicate.d.ts.map +0 -1
  372. package/lib/types/shared/claude-thinking.d.ts.map +0 -1
  373. package/lib/types/test-driver/TestErrorCompletionStream.d.ts.map +0 -1
  374. package/lib/types/test-driver/TestValidationErrorCompletionStream.d.ts.map +0 -1
  375. package/lib/types/test-driver/index.d.ts.map +0 -1
  376. package/lib/types/test-driver/utils.d.ts.map +0 -1
  377. package/lib/types/togetherai/index.d.ts +0 -23
  378. package/lib/types/togetherai/index.d.ts.map +0 -1
  379. package/lib/types/togetherai/interfaces.d.ts.map +0 -1
  380. package/lib/types/vertexai/debug.d.ts +0 -2
  381. package/lib/types/vertexai/debug.d.ts.map +0 -1
  382. package/lib/types/vertexai/embeddings/embeddings-image.d.ts +0 -11
  383. package/lib/types/vertexai/embeddings/embeddings-image.d.ts.map +0 -1
  384. package/lib/types/vertexai/embeddings/embeddings-text.d.ts +0 -10
  385. package/lib/types/vertexai/embeddings/embeddings-text.d.ts.map +0 -1
  386. package/lib/types/vertexai/index.d.ts.map +0 -1
  387. package/lib/types/vertexai/models/claude.d.ts +0 -119
  388. package/lib/types/vertexai/models/claude.d.ts.map +0 -1
  389. package/lib/types/vertexai/models/gemini.d.ts.map +0 -1
  390. package/lib/types/vertexai/models/imagen.d.ts.map +0 -1
  391. package/lib/types/vertexai/models/llama.d.ts.map +0 -1
  392. package/lib/types/vertexai/models.d.ts +0 -20
  393. package/lib/types/vertexai/models.d.ts.map +0 -1
  394. package/lib/types/watsonx/index.d.ts.map +0 -1
  395. package/lib/types/watsonx/interfaces.d.ts.map +0 -1
  396. package/lib/types/xai/index.d.ts.map +0 -1
  397. package/src/vertexai/embeddings/embeddings-image.ts +0 -52
  398. package/src/vertexai/embeddings/embeddings-text.ts +0 -54
  399. /package/lib/{esm/bedrock → bedrock}/payloads.js +0 -0
  400. /package/lib/{esm/mistral → mistral}/types.js +0 -0
  401. /package/lib/{esm/togetherai → togetherai}/interfaces.js +0 -0
  402. /package/lib/{types/watsonx → watsonx}/interfaces.d.ts +0 -0
  403. /package/lib/{esm/watsonx → watsonx}/interfaces.js +0 -0
@@ -1,95 +1,150 @@
1
- import type { ApiError } from "@google/genai";
1
+ import type { ApiError } from '@google/genai';
2
2
  import {
3
- type Content, FinishReason, FunctionCallingConfigMode, type FunctionDeclaration, type GenerateContentConfig, type GenerateContentParameters,
3
+ type Content,
4
+ FinishReason,
5
+ FunctionCallingConfigMode,
6
+ type FunctionDeclaration,
7
+ type GenerateContentConfig,
8
+ type GenerateContentParameters,
4
9
  type GenerateContentResponseUsageMetadata,
5
- HarmBlockThreshold, HarmCategory, Modality, type Part,
10
+ HarmBlockThreshold,
11
+ HarmCategory,
12
+ Modality,
13
+ type Part,
6
14
  ProminentPeople,
7
- type SafetySetting, type ThinkingConfig,
15
+ type SafetySetting,
16
+ type ThinkingConfig,
8
17
  ThinkingLevel,
9
- type Tool
10
- } from "@google/genai";
18
+ type Tool,
19
+ } from '@google/genai';
11
20
  import {
12
- type AIModel, type Completion, type CompletionChunkObject, type CompletionResult, type ExecutionOptions,
21
+ type AIModel,
22
+ type Completion,
23
+ type CompletionChunkObject,
24
+ type CompletionResult,
25
+ type ExecutionOptions,
13
26
  type ExecutionTokenUsage,
14
27
  getConversationMeta,
15
28
  getGeminiModelVersion,
16
29
  incrementConversationTurn,
17
30
  isGeminiModelVersionGte,
18
- type JSONObject, LlumiverseError, type LlumiverseErrorContext, ModelType, type PromptOptions, PromptRole,
19
- type PromptSegment, readStreamAsBase64, type StatelessExecutionOptions,
31
+ type JSONObject,
32
+ LlumiverseError,
33
+ type LlumiverseErrorContext,
34
+ ModelType,
35
+ type PromptOptions,
36
+ PromptRole,
37
+ type PromptSegment,
38
+ readStreamAsBase64,
39
+ type StatelessExecutionOptions,
20
40
  stripBase64ImagesFromConversation,
21
41
  stripHeartbeatsFromConversation,
22
- type ToolDefinition, type ToolUse,
42
+ type ToolDefinition,
43
+ type ToolUse,
23
44
  truncateLargeTextInConversation,
24
45
  unwrapConversationArray,
25
- type VertexAIGeminiOptions
26
- } from "@llumiverse/core";
27
- import { asyncMap } from "@llumiverse/core/async";
28
- import type { GenerateContentPrompt, VertexAIDriver } from "../index.js";
29
- import type { ModelDefinition } from "../models.js";
46
+ type VertexAIGeminiOptions,
47
+ } from '@llumiverse/core';
48
+ import { asyncMap } from '@llumiverse/core/async';
49
+ import { truncateBinaryForDebug } from '../../shared/debug-prompt.js';
50
+ import type { GenerateContentPrompt, VertexAIDriver } from '../index.js';
51
+ import type { ModelDefinition } from '../models.js';
52
+
53
+ type GoogleApiErrorLike = Pick<ApiError, 'status' | 'message'>;
30
54
 
31
55
  function supportsStructuredOutput(options: PromptOptions): boolean {
32
56
  // Gemini 1.0 Ultra does not support JSON output, 1.0 Pro does.
33
- return !!options.result_schema && !options.model.includes("ultra");
57
+ return !!options.result_schema && !options.model.includes('ultra');
34
58
  }
35
59
 
36
60
  const geminiSafetySettings: SafetySetting[] = [
37
61
  {
38
62
  category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
39
- threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
63
+ threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH,
40
64
  },
41
65
  {
42
66
  category: HarmCategory.HARM_CATEGORY_HARASSMENT,
43
- threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
67
+ threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH,
44
68
  },
45
69
  {
46
70
  category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
47
- threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
71
+ threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH,
48
72
  },
49
73
  {
50
74
  category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
51
- threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
75
+ threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH,
52
76
  },
53
77
  {
54
78
  category: HarmCategory.HARM_CATEGORY_UNSPECIFIED,
55
- threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
79
+ threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH,
56
80
  },
57
81
  {
58
82
  category: HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY,
59
- threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH
60
- }
83
+ threshold: HarmBlockThreshold.BLOCK_ONLY_HIGH,
84
+ },
61
85
  ];
62
86
 
87
+ function formatGeminiContentForDebug(content: Content): Content {
88
+ return {
89
+ ...content,
90
+ parts: content.parts?.map((part) => {
91
+ if (!part.inlineData?.data) {
92
+ return part;
93
+ }
94
+ return {
95
+ ...part,
96
+ inlineData: {
97
+ ...part.inlineData,
98
+ data: truncateBinaryForDebug(part.inlineData.data),
99
+ },
100
+ } satisfies Part;
101
+ }),
102
+ };
103
+ }
104
+
105
+ export function formatGeminiDebugPrompt(prompt: GenerateContentPrompt): GenerateContentPrompt {
106
+ return {
107
+ ...prompt,
108
+ contents: prompt.contents.map(formatGeminiContentForDebug),
109
+ system: prompt.system ? formatGeminiContentForDebug(prompt.system) : undefined,
110
+ };
111
+ }
112
+
63
113
  // We do the mapping here rather than in common to avoid bringing the SDK into the common package.
64
- function getProminentPeopleOption(prominentPeople?: "PROMINENT_PEOPLE_UNSPECIFIED" | "ALLOW_PROMINENT_PEOPLE" | "BLOCK_PROMINENT_PEOPLE") {
114
+ function getProminentPeopleOption(
115
+ prominentPeople?: 'PROMINENT_PEOPLE_UNSPECIFIED' | 'ALLOW_PROMINENT_PEOPLE' | 'BLOCK_PROMINENT_PEOPLE',
116
+ ) {
65
117
  switch (prominentPeople) {
66
- case "ALLOW_PROMINENT_PEOPLE":
118
+ case 'ALLOW_PROMINENT_PEOPLE':
67
119
  return ProminentPeople.ALLOW_PROMINENT_PEOPLE;
68
- case "BLOCK_PROMINENT_PEOPLE":
120
+ case 'BLOCK_PROMINENT_PEOPLE':
69
121
  return ProminentPeople.BLOCK_PROMINENT_PEOPLE;
70
- case "PROMINENT_PEOPLE_UNSPECIFIED":
122
+ case 'PROMINENT_PEOPLE_UNSPECIFIED':
71
123
  return ProminentPeople.PROMINENT_PEOPLE_UNSPECIFIED;
72
124
  default:
73
125
  return undefined;
74
126
  }
75
127
  }
76
128
 
77
- function getGeminiPayload(options: ExecutionOptions, prompt: GenerateContentPrompt): GenerateContentParameters {
129
+ export function getGeminiPayload(options: ExecutionOptions, prompt: GenerateContentPrompt): GenerateContentParameters {
78
130
  const model_options = options.model_options as VertexAIGeminiOptions | undefined;
79
131
  const tools = getToolDefinitions(options.tools);
80
132
 
81
133
  // When no tools are provided but conversation contains functionCall/functionResponse parts
82
134
  // (e.g. checkpoint summary calls), convert them to text to avoid API errors.
83
135
  // Use a local variable to avoid mutating the caller's conversation object.
84
- let payloadContents = prompt.contents;
136
+ let payloadContents = mergeConsecutiveRole(prompt.contents);
85
137
  if (!tools && payloadContents) {
86
- const hasToolParts = payloadContents.some(c =>
87
- c.parts?.some(p => p.functionCall || p.functionResponse)
88
- );
138
+ const hasToolParts = payloadContents.some((c) => c.parts?.some((p) => p.functionCall || p.functionResponse));
89
139
  if (hasToolParts) {
90
140
  payloadContents = convertGeminiFunctionPartsToText(payloadContents);
91
141
  }
92
142
  }
143
+ // Drop functionResponse parts whose functionCall was lost (e.g. compaction),
144
+ // which would otherwise trip Gemini's functionCall/functionResponse pairing.
145
+ if (payloadContents) {
146
+ payloadContents = fixOrphanedToolResults(payloadContents);
147
+ }
93
148
 
94
149
  const useStructuredOutput = supportsStructuredOutput(options) && !tools;
95
150
 
@@ -112,21 +167,23 @@ function getGeminiPayload(options: ExecutionOptions, prompt: GenerateContentProm
112
167
  prominentPeople: getProminentPeopleOption(model_options?.prominent_people),
113
168
  outputMimeType: model_options?.output_mime_type,
114
169
  outputCompressionQuality: model_options?.output_compression_quality,
115
- }
116
- }
170
+ },
171
+ };
117
172
 
118
173
  const config: GenerateContentConfig = {
119
174
  systemInstruction: prompt.system,
120
175
  safetySettings: geminiSafetySettings,
121
176
  tools: tools ? [tools] : undefined,
122
- toolConfig: tools ? {
123
- functionCallingConfig: {
124
- mode: FunctionCallingConfigMode.AUTO,
125
- }
126
- } : undefined,
177
+ toolConfig: tools
178
+ ? {
179
+ functionCallingConfig: {
180
+ mode: FunctionCallingConfigMode.AUTO,
181
+ },
182
+ }
183
+ : undefined,
127
184
  candidateCount: 1,
128
185
  //JSON/Structured output
129
- responseMimeType: useStructuredOutput ? "application/json" : undefined,
186
+ responseMimeType: useStructuredOutput ? 'application/json' : undefined,
130
187
  responseJsonSchema: useStructuredOutput ? options.result_schema : undefined,
131
188
  //Model options
132
189
  temperature: model_options?.temperature,
@@ -139,12 +196,12 @@ function getGeminiPayload(options: ExecutionOptions, prompt: GenerateContentProm
139
196
  seed: model_options?.seed,
140
197
  thinkingConfig: geminiThinkingConfig(options),
141
198
  labels: options.labels,
142
- }
199
+ };
143
200
 
144
201
  return {
145
202
  model: options.model,
146
203
  contents: payloadContents,
147
- config: options.model.toLowerCase().includes("image") ? configNanoBanana : config,
204
+ config: options.model.toLowerCase().includes('image') ? configNanoBanana : config,
148
205
  };
149
206
  }
150
207
 
@@ -159,16 +216,16 @@ function extractCompletionResults(content: Content): CompletionResult[] {
159
216
  for (const part of parts) {
160
217
  if (part.text) {
161
218
  results.push({
162
- type: "text",
163
- value: part.text
219
+ type: 'text',
220
+ value: part.text,
164
221
  });
165
222
  } else if (part.inlineData) {
166
- const base64ImageBytes: string = part.inlineData.data ?? "";
167
- const mimeType = part.inlineData.mimeType ?? "image/png";
223
+ const base64ImageBytes: string = part.inlineData.data ?? '';
224
+ const mimeType = part.inlineData.mimeType ?? 'image/png';
168
225
  const imageUrl = `data:${mimeType};base64,${base64ImageBytes}`;
169
226
  results.push({
170
- type: "image",
171
- value: imageUrl
227
+ type: 'image',
228
+ value: imageUrl,
172
229
  });
173
230
  }
174
231
  }
@@ -200,8 +257,8 @@ function collectToolUseParts(content: Content): ToolUse[] | undefined {
200
257
  export function mergeConsecutiveRole(contents: Content[] | undefined): Content[] {
201
258
  if (!contents || contents.length === 0) return [];
202
259
 
203
- const needsMerging = contents.some((content, i) =>
204
- i < contents.length - 1 && content.role === contents[i + 1].role
260
+ const needsMerging = contents.some(
261
+ (content, i) => i < contents.length - 1 && content.role === contents[i + 1].role,
205
262
  );
206
263
  // If no merging needed, return original array
207
264
  if (!needsMerging) {
@@ -226,19 +283,61 @@ export function mergeConsecutiveRole(contents: Content[] | undefined): Content[]
226
283
  return result;
227
284
  }
228
285
 
286
+ /**
287
+ * Drop functionResponse parts whose name has no matching functionCall in the
288
+ * immediately-preceding `model` content. Gemini pairs a functionResponse to its
289
+ * functionCall by name; a response left dangling after its call was dropped
290
+ * (e.g. by conversation compaction/trimming, or an unmergeable parallel batch)
291
+ * causes the API to reject the request. Mirrors the same guard added to the
292
+ * Claude, Bedrock, and OpenAI drivers.
293
+ *
294
+ * Run after mergeConsecutiveRole so parallel responses that were split across
295
+ * user contents are first recombined under the model turn that issued the calls,
296
+ * and are therefore not mistaken for orphans.
297
+ */
298
+ export function fixOrphanedToolResults(contents: Content[]): Content[] {
299
+ if (contents.length === 0) return contents;
300
+ const result: Content[] = [];
301
+ for (let i = 0; i < contents.length; i++) {
302
+ const content = contents[i];
303
+ if (content.role !== 'user' || !content.parts) {
304
+ result.push(content);
305
+ continue;
306
+ }
307
+ const hasFunctionResponse = content.parts.some((part) => part.functionResponse);
308
+ if (!hasFunctionResponse) {
309
+ result.push(content);
310
+ continue;
311
+ }
312
+ const prev = contents[i - 1];
313
+ const allowedNames = new Set<string>();
314
+ if (prev && prev.role === 'model' && prev.parts) {
315
+ for (const part of prev.parts) {
316
+ if (part.functionCall?.name) allowedNames.add(part.functionCall.name);
317
+ }
318
+ }
319
+ const filtered = content.parts.filter((part) =>
320
+ part.functionResponse ? allowedNames.has(part.functionResponse.name ?? '') : true,
321
+ );
322
+ // Drop the content if every part was an orphaned functionResponse.
323
+ if (filtered.length === 0) continue;
324
+ result.push(filtered.length === content.parts.length ? content : { ...content, parts: filtered });
325
+ }
326
+ return result;
327
+ }
328
+
229
329
  const supportedFinishReasons: FinishReason[] = [
230
330
  FinishReason.MAX_TOKENS,
231
331
  FinishReason.STOP,
232
332
  FinishReason.FINISH_REASON_UNSPECIFIED,
233
- ]
333
+ ];
234
334
 
235
335
  // Finish reasons that indicate tool call issues but should be recovered gracefully
236
336
  // instead of throwing an error. The tool_use is still extracted and returned
237
337
  // so the workflow can generate a proper toolError response.
238
338
  const recoverableToolCallReasons = [
239
339
  'UNEXPECTED_TOOL_CALL', // Model called an undeclared tool
240
- ]
241
-
340
+ ];
242
341
 
243
342
  function geminiThinkingBudget(option: StatelessExecutionOptions) {
244
343
  const model_options = option.model_options as VertexAIGeminiOptions | undefined;
@@ -252,7 +351,7 @@ function geminiThinkingBudget(option: StatelessExecutionOptions) {
252
351
  // Set minimum thinking level by default.
253
352
  // Docs: https://ai.google.dev/gemini-api/docs/thinking#set-budget
254
353
  if (getGeminiModelVersion(option.model) === '2.5') {
255
- if (option.model.includes("pro")) {
354
+ if (option.model.includes('pro')) {
256
355
  return 128;
257
356
  }
258
357
  return 0;
@@ -260,37 +359,40 @@ function geminiThinkingBudget(option: StatelessExecutionOptions) {
260
359
  return undefined;
261
360
  }
262
361
 
263
- function geminiThinkingLevelForEffort(model: string, effort: VertexAIGeminiOptions["effort"]): ThinkingLevel | undefined {
264
- if (model.includes("gemini-3-pro-image")) {
362
+ function geminiThinkingLevelForEffort(
363
+ model: string,
364
+ effort: VertexAIGeminiOptions['effort'],
365
+ ): ThinkingLevel | undefined {
366
+ if (model.includes('gemini-3-pro-image')) {
265
367
  return ThinkingLevel.HIGH;
266
368
  }
267
- if (model.includes("gemini-3.1-flash-image")) {
268
- return effort === "low" ? ThinkingLevel.MINIMAL : ThinkingLevel.HIGH;
369
+ if (model.includes('gemini-3.1-flash-image')) {
370
+ return effort === 'low' ? ThinkingLevel.MINIMAL : ThinkingLevel.HIGH;
269
371
  }
270
372
  switch (effort) {
271
- case "low":
373
+ case 'low':
272
374
  return ThinkingLevel.LOW;
273
- case "medium":
375
+ case 'medium':
274
376
  return ThinkingLevel.MEDIUM;
275
- case "high":
377
+ case 'high':
276
378
  return ThinkingLevel.HIGH;
277
379
  default:
278
380
  return undefined;
279
381
  }
280
382
  }
281
383
 
282
- function geminiBudgetForEffort(model: string, effort: NonNullable<VertexAIGeminiOptions["effort"]>): number {
283
- const isFlashLite = model.includes("flash-lite");
284
- const isFlash = model.includes("flash") && !isFlashLite;
285
- const isPro = model.includes("pro");
384
+ function geminiBudgetForEffort(model: string, effort: NonNullable<VertexAIGeminiOptions['effort']>): number {
385
+ const isFlashLite = model.includes('flash-lite');
386
+ const isFlash = model.includes('flash') && !isFlashLite;
387
+ const isPro = model.includes('pro');
286
388
 
287
- if (effort === "low") {
389
+ if (effort === 'low') {
288
390
  if (isPro) return 128;
289
391
  if (isFlashLite) return 512;
290
392
  if (isFlash) return 1;
291
393
  return 1024;
292
394
  }
293
- if (effort === "medium") {
395
+ if (effort === 'medium') {
294
396
  return 8192;
295
397
  }
296
398
  if (isPro) return 32768;
@@ -329,21 +431,20 @@ function geminiThinkingConfig(option: StatelessExecutionOptions): ThinkingConfig
329
431
  if (isGeminiModelVersionGte(option.model, '3.0')) {
330
432
  return {
331
433
  includeThoughts: include_thoughts,
332
- thinkingLevel: option.model.includes("gemini-3-pro-image") ? ThinkingLevel.HIGH : ThinkingLevel.LOW
434
+ thinkingLevel: option.model.includes('gemini-3-pro-image') ? ThinkingLevel.HIGH : ThinkingLevel.LOW,
333
435
  };
334
436
  }
335
437
  if (isGeminiModelVersionGte(option.model, '2.5')) {
336
438
  const thinking_budget_tokens = geminiThinkingBudget(option) ?? 0;
337
439
  return {
338
440
  includeThoughts: include_thoughts,
339
- thinkingBudget: thinking_budget_tokens
441
+ thinkingBudget: thinking_budget_tokens,
340
442
  };
341
443
  }
342
444
  }
343
445
 
344
446
  export class GeminiModelDefinition implements ModelDefinition<GenerateContentPrompt> {
345
-
346
- model: AIModel
447
+ model: AIModel;
347
448
 
348
449
  constructor(modelId: string) {
349
450
  this.model = {
@@ -351,18 +452,22 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
351
452
  name: modelId,
352
453
  provider: 'vertexai',
353
454
  type: ModelType.Text,
354
- can_stream: true
455
+ can_stream: true,
355
456
  } satisfies AIModel;
356
457
  }
357
458
 
358
- async createPrompt(_driver: VertexAIDriver, segments: PromptSegment[], options: ExecutionOptions): Promise<GenerateContentPrompt> {
359
- const splits = options.model.split("/");
459
+ async createPrompt(
460
+ _driver: VertexAIDriver,
461
+ segments: PromptSegment[],
462
+ options: ExecutionOptions,
463
+ ): Promise<GenerateContentPrompt> {
464
+ const splits = options.model.split('/');
360
465
  const modelName = splits[splits.length - 1];
361
466
  options = { ...options, model: modelName };
362
467
 
363
468
  const schema = options.result_schema;
364
469
  let contents: Content[] = [];
365
- let system: Content | undefined = { role: "user", parts: [] }; // Single content block for system messages
470
+ let system: Content | undefined = { role: 'user', parts: [] }; // Single content block for system messages
366
471
 
367
472
  const safety: Content[] = [];
368
473
 
@@ -371,17 +476,19 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
371
476
  if (msg.role === PromptRole.system) {
372
477
  // Text only for system messages
373
478
  if (msg.files && msg.files.length > 0) {
374
- throw new Error("Gemini does not support files/images etc. in system messages. Only text content is allowed.");
479
+ throw new Error(
480
+ 'Gemini does not support files/images etc. in system messages. Only text content is allowed.',
481
+ );
375
482
  }
376
483
 
377
484
  if (msg.content) {
378
485
  system.parts?.push({
379
- text: msg.content
486
+ text: msg.content,
380
487
  });
381
488
  }
382
489
  } else if (msg.role === PromptRole.tool) {
383
490
  if (!msg.tool_use_id) {
384
- throw new Error("Tool response missing tool_use_id");
491
+ throw new Error('Tool response missing tool_use_id');
385
492
  }
386
493
  // Build functionResponse part with optional thought_signature for Gemini thinking models
387
494
  const functionResponsePart: Part = {
@@ -394,9 +501,10 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
394
501
  };
395
502
  contents.push({
396
503
  role: 'user',
397
- parts: [functionResponsePart]
504
+ parts: [functionResponsePart],
398
505
  });
399
- } else { // PromptRole.user, PromptRole.assistant, PromptRole.safety
506
+ } else {
507
+ // PromptRole.user, PromptRole.assistant, PromptRole.safety
400
508
  const parts: Part[] = [];
401
509
  // Text content handling
402
510
  if (msg.content) {
@@ -408,15 +516,16 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
408
516
  // File content handling
409
517
  if (msg.files) {
410
518
  for (const f of msg.files) {
411
- const fileUrl = await f.getURL();
412
- const isGsUrl = fileUrl.startsWith('gs://') || fileUrl.startsWith('https://storage.googleapis.com/');
519
+ const fileUri = await f.getURI();
520
+ const isGsUri =
521
+ fileUri.startsWith('gs://') || fileUri.startsWith('https://storage.googleapis.com/');
413
522
 
414
- if (isGsUrl) {
523
+ if (isGsUri) {
415
524
  parts.push({
416
525
  fileData: {
417
- fileUri: fileUrl,
418
- mimeType: f.mime_type
419
- }
526
+ fileUri,
527
+ mimeType: f.mime_type,
528
+ },
420
529
  });
421
530
  } else {
422
531
  // Inline data handling
@@ -425,8 +534,8 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
425
534
  parts.push({
426
535
  inlineData: {
427
536
  data,
428
- mimeType: f.mime_type
429
- }
537
+ mimeType: f.mime_type,
538
+ },
430
539
  });
431
540
  }
432
541
  }
@@ -453,15 +562,17 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
453
562
  if (supportsStructuredOutput(options) && !options.tools) {
454
563
  // Gemini structured output is unnecessarily sparse. Adding encouragement to fill the fields.
455
564
  // Putting JSON in prompt is not recommended by Google, when using structured output.
456
- system.parts?.push({ text: "Fill all appropriate fields in the JSON output." });
565
+ system.parts?.push({ text: 'Fill all appropriate fields in the JSON output.' });
457
566
  } else {
458
567
  // Fallback to putting the schema in the system instructions, if not using structured output.
459
568
  if (options.tools) {
460
569
  system.parts?.push({
461
- text: "When not calling tools, the output must be a JSON object using the following JSON Schema:\n" + JSON.stringify(schema)
570
+ text: `When not calling tools, the output must be a JSON object using the following JSON Schema:\n${JSON.stringify(schema)}`,
462
571
  });
463
572
  } else {
464
- system.parts?.push({ text: "The output must be a JSON object using the following JSON Schema:\n" + JSON.stringify(schema) });
573
+ system.parts?.push({
574
+ text: `The output must be a JSON object using the following JSON Schema:\n${JSON.stringify(schema)}`,
575
+ });
465
576
  }
466
577
  }
467
578
  }
@@ -482,8 +593,11 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
482
593
  return { contents, system };
483
594
  }
484
595
 
485
- usageMetadataToTokenUsage(usageMetadata: GenerateContentResponseUsageMetadata | undefined): ExecutionTokenUsage {
486
- if (!usageMetadata || !usageMetadata.totalTokenCount) {
596
+ usageMetadataToTokenUsage(
597
+ driver: VertexAIDriver,
598
+ usageMetadata: GenerateContentResponseUsageMetadata | undefined,
599
+ ): ExecutionTokenUsage {
600
+ if (!usageMetadata?.totalTokenCount) {
487
601
  return {};
488
602
  }
489
603
  const tokenUsage: ExecutionTokenUsage = {
@@ -494,16 +608,20 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
494
608
  };
495
609
 
496
610
  //Output/Response side
497
- tokenUsage.result = (usageMetadata.candidatesTokenCount ?? 0)
498
- + (usageMetadata.thoughtsTokenCount ?? 0)
499
- + (usageMetadata.toolUsePromptTokenCount ?? 0);
611
+ tokenUsage.result =
612
+ (usageMetadata.candidatesTokenCount ?? 0) +
613
+ (usageMetadata.thoughtsTokenCount ?? 0) +
614
+ (usageMetadata.toolUsePromptTokenCount ?? 0);
500
615
 
501
616
  if ((tokenUsage.total ?? 0) !== (tokenUsage.prompt ?? 0) + tokenUsage.result) {
502
- console.warn("[VertexAI] Gemini token usage mismatch: total does not equal prompt + result", {
503
- total: tokenUsage.total,
504
- prompt: tokenUsage.prompt,
505
- result: tokenUsage.result
506
- });
617
+ // Token-accounting mismatch: warn-level diagnostic (the call still
618
+ // returns the best-effort tokenUsage). Use the driver's structured
619
+ // logger so we don't promote stderr writes to ERROR in serverless
620
+ // log aggregators — see the recoverable-tool-call sites below.
621
+ driver.logger.warn(
622
+ { total: tokenUsage.total, prompt: tokenUsage.prompt, result: tokenUsage.result },
623
+ '[VertexAI] Gemini token usage mismatch: total does not equal prompt + result',
624
+ );
507
625
  }
508
626
 
509
627
  if (!tokenUsage.result) {
@@ -513,10 +631,14 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
513
631
  return tokenUsage;
514
632
  }
515
633
 
516
- async requestTextCompletion(driver: VertexAIDriver, prompt: GenerateContentPrompt, options: ExecutionOptions): Promise<Completion> {
517
- const splits = options.model.split("/");
518
- let region: string | undefined = undefined;
519
- if (splits[0] === "locations" && splits.length >= 2) {
634
+ async requestTextCompletion(
635
+ driver: VertexAIDriver,
636
+ prompt: GenerateContentPrompt,
637
+ options: ExecutionOptions,
638
+ ): Promise<Completion> {
639
+ const splits = options.model.split('/');
640
+ let region: string | undefined;
641
+ if (splits[0] === 'locations' && splits.length >= 2) {
520
642
  region = splits[1];
521
643
  }
522
644
  const modelName = splits[splits.length - 1];
@@ -535,45 +657,61 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
535
657
  prompt.contents = conversation;
536
658
 
537
659
  // TODO: Remove hack, use global endpoint manually if needed.
538
- if (options.model.includes("gemini-2.5-flash-image")) {
539
- region = "global"; // Gemini Flash Image only available in global region, this is for nano-banana model
660
+ if (options.model.includes('gemini-2.5-flash-image')) {
661
+ region = 'global'; // Gemini Flash Image only available in global region, this is for nano-banana model
540
662
  }
541
663
 
542
664
  const model_options = options.model_options as VertexAIGeminiOptions | undefined;
543
- const client = driver.getGoogleGenAIClient(region, model_options?.flex ?? false);
665
+ const client = driver.getGoogleGenAIClient(region, model_options?.flex ?? false, options.httpTimeout);
544
666
 
545
667
  const payload = getGeminiPayload(options, prompt);
546
668
  const response = await client.models.generateContent(payload);
547
669
 
548
- const token_usage: ExecutionTokenUsage = this.usageMetadataToTokenUsage(response.usageMetadata);
670
+ const token_usage: ExecutionTokenUsage = this.usageMetadataToTokenUsage(driver, response.usageMetadata);
549
671
 
550
672
  let tool_use: ToolUse[] | undefined;
551
- let finish_reason: string | undefined, result: any;
552
- const candidate = response.candidates && response.candidates[0];
673
+ let finish_reason: string | undefined, result: CompletionResult[] | undefined;
674
+ const candidate = response.candidates?.[0];
553
675
  if (candidate) {
554
676
  switch (candidate.finishReason) {
555
- case FinishReason.MAX_TOKENS: finish_reason = "length"; break;
556
- case FinishReason.STOP: finish_reason = "stop"; break;
557
- default: finish_reason = candidate.finishReason;
677
+ case FinishReason.MAX_TOKENS:
678
+ finish_reason = 'length';
679
+ break;
680
+ case FinishReason.STOP:
681
+ finish_reason = 'stop';
682
+ break;
683
+ default:
684
+ finish_reason = candidate.finishReason;
558
685
  }
559
686
  const content = candidate.content;
560
687
 
561
688
  // Check for unsupported finish reasons, but allow recoverable tool call issues
562
689
  const isRecoverableToolCall = recoverableToolCallReasons.includes(candidate.finishReason as string);
563
- if (candidate.finishReason && !supportedFinishReasons.includes(candidate.finishReason) && !isRecoverableToolCall) {
564
- throw new Error(`Unsupported finish reason: ${candidate.finishReason}, `
565
- + `finish message: ${candidate.finishMessage}, `
566
- + `content: ${JSON.stringify(content, null, 2)}, safety: ${JSON.stringify(candidate.safetyRatings, null, 2)}`);
690
+ if (
691
+ candidate.finishReason &&
692
+ !supportedFinishReasons.includes(candidate.finishReason) &&
693
+ !isRecoverableToolCall
694
+ ) {
695
+ throw new Error(
696
+ `Unsupported finish reason: ${candidate.finishReason}, ` +
697
+ `finish message: ${candidate.finishMessage}, ` +
698
+ `content: ${JSON.stringify(content, null, 2)}, safety: ${JSON.stringify(candidate.safetyRatings, null, 2)}`,
699
+ );
567
700
  }
568
701
 
569
702
  if (content) {
570
703
  tool_use = collectToolUseParts(content);
571
704
 
572
705
  // For recoverable tool call issues, log warning but continue processing
573
- // The workflow will handle the invalid tool call gracefully
706
+ // The workflow will handle the invalid tool call gracefully.
707
+ // Route through the driver's structured logger instead of `console.warn`
708
+ // so downstream runtimes (e.g. Cloud Run) don't promote stderr writes
709
+ // to ERROR severity for what is, by definition, a recoverable event.
574
710
  if (isRecoverableToolCall && tool_use && tool_use.length > 0) {
575
- console.warn(`[Gemini] Recoverable tool call issue (${candidate.finishReason}): ` +
576
- `Model tried to call undeclared tool(s): ${tool_use.map(t => t.tool_name).join(', ')}`);
711
+ driver.logger.warn(
712
+ `[Gemini] Recoverable tool call issue (${candidate.finishReason}): ` +
713
+ `Model tried to call undeclared tool(s): ${tool_use.map((t) => t.tool_name).join(', ')}`,
714
+ );
577
715
  }
578
716
 
579
717
  result = extractCompletionResults(content);
@@ -581,10 +719,8 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
581
719
  }
582
720
  }
583
721
 
584
-
585
-
586
722
  if (tool_use) {
587
- finish_reason = "tool_use";
723
+ finish_reason = 'tool_use';
588
724
  }
589
725
 
590
726
  // Increment turn counter for deferred stripping
@@ -595,7 +731,7 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
595
731
  const stripOptions = {
596
732
  keepForTurns: options.stripImagesAfterTurns ?? Infinity,
597
733
  currentTurn,
598
- textMaxTokens: options.stripTextMaxTokens
734
+ textMaxTokens: options.stripTextMaxTokens,
599
735
  };
600
736
  let processedConversation = stripBase64ImagesFromConversation(conversation, stripOptions);
601
737
 
@@ -612,19 +748,23 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
612
748
  const finalConversation = storeSystemInConversation(processedConversation, prompt.system);
613
749
 
614
750
  return {
615
- result: result && result.length > 0 ? result : [{ type: "text" as const, value: '' }],
751
+ result: result && result.length > 0 ? result : [{ type: 'text' as const, value: '' }],
616
752
  token_usage: token_usage,
617
753
  finish_reason: finish_reason,
618
754
  original_response: options.include_original_response ? response : undefined,
619
755
  conversation: finalConversation,
620
- tool_use
756
+ tool_use,
621
757
  } satisfies Completion;
622
758
  }
623
759
 
624
- async requestTextCompletionStream(driver: VertexAIDriver, prompt: GenerateContentPrompt, options: ExecutionOptions): Promise<AsyncIterable<CompletionChunkObject>> {
625
- const splits = options.model.split("/");
626
- let region: string | undefined = undefined;
627
- if (splits[0] === "locations" && splits.length >= 2) {
760
+ async requestTextCompletionStream(
761
+ driver: VertexAIDriver,
762
+ prompt: GenerateContentPrompt,
763
+ options: ExecutionOptions,
764
+ ): Promise<AsyncIterable<CompletionChunkObject>> {
765
+ const splits = options.model.split('/');
766
+ let region: string | undefined;
767
+ if (splits[0] === 'locations' && splits.length >= 2) {
628
768
  region = splits[1];
629
769
  }
630
770
  const modelName = splits[splits.length - 1];
@@ -643,44 +783,60 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
643
783
  const conversation = updateConversation(options.conversation, prompt.contents);
644
784
  prompt.contents = conversation;
645
785
 
646
- if (options.model.includes("gemini-2.5-flash-image")) {
647
- region = "global"; // Gemini Flash Image only available in global region, this is for nano-banana model
786
+ if (options.model.includes('gemini-2.5-flash-image')) {
787
+ region = 'global'; // Gemini Flash Image only available in global region, this is for nano-banana model
648
788
  }
649
789
 
650
790
  const model_options = options.model_options as VertexAIGeminiOptions | undefined;
651
- const client = driver.getGoogleGenAIClient(region, model_options?.flex ?? false);
791
+ const client = driver.getGoogleGenAIClient(region, model_options?.flex ?? false, options.httpTimeout);
652
792
 
653
793
  const payload = getGeminiPayload(options, prompt);
654
794
  const response = await client.models.generateContentStream(payload);
655
795
 
656
796
  const stream = asyncMap(response, async (item) => {
657
- const token_usage: ExecutionTokenUsage = this.usageMetadataToTokenUsage(item.usageMetadata);
797
+ const token_usage: ExecutionTokenUsage = this.usageMetadataToTokenUsage(driver, item.usageMetadata);
658
798
  if (item.candidates && item.candidates.length > 0) {
659
799
  for (const candidate of item.candidates) {
660
800
  let tool_use: ToolUse[] | undefined;
661
801
  let finish_reason: string | undefined;
662
802
  switch (candidate.finishReason) {
663
- case FinishReason.MAX_TOKENS: finish_reason = "length"; break;
664
- case FinishReason.STOP: finish_reason = "stop"; break;
665
- default: finish_reason = candidate.finishReason;
803
+ case FinishReason.MAX_TOKENS:
804
+ finish_reason = 'length';
805
+ break;
806
+ case FinishReason.STOP:
807
+ finish_reason = 'stop';
808
+ break;
809
+ default:
810
+ finish_reason = candidate.finishReason;
666
811
  }
667
812
  // Check for unsupported finish reasons, but allow recoverable tool call issues
668
813
  const isRecoverableToolCall = recoverableToolCallReasons.includes(candidate.finishReason as string);
669
- if (candidate.finishReason && !supportedFinishReasons.includes(candidate.finishReason) && !isRecoverableToolCall) {
670
- throw new Error(`Unsupported finish reason: ${candidate.finishReason}, `
671
- + `finish message: ${candidate.finishMessage}, `
672
- + `content: ${JSON.stringify(candidate.content, null, 2)}, safety: ${JSON.stringify(candidate.safetyRatings, null, 2)}`);
814
+ if (
815
+ candidate.finishReason &&
816
+ !supportedFinishReasons.includes(candidate.finishReason) &&
817
+ !isRecoverableToolCall
818
+ ) {
819
+ throw new Error(
820
+ `Unsupported finish reason: ${candidate.finishReason}, ` +
821
+ `finish message: ${candidate.finishMessage}, ` +
822
+ `content: ${JSON.stringify(candidate.content, null, 2)}, safety: ${JSON.stringify(candidate.safetyRatings, null, 2)}`,
823
+ );
673
824
  }
674
825
  if (candidate.content?.role === 'model') {
675
826
  // Collect all parts in order (text and images)
676
827
  const combinedResults = extractCompletionResults(candidate.content);
677
828
  tool_use = collectToolUseParts(candidate.content);
678
829
  if (tool_use) {
679
- finish_reason = "tool_use";
680
- // Log warning for recoverable tool call issues
830
+ finish_reason = 'tool_use';
831
+ // Log warning for recoverable tool call issues — see the
832
+ // matching site in `requestTextCompletion` above for why
833
+ // we route through the driver's logger instead of
834
+ // `console.warn`.
681
835
  if (isRecoverableToolCall) {
682
- console.warn(`[Gemini] Recoverable tool call issue (${candidate.finishReason}): ` +
683
- `Model tried to call undeclared tool(s): ${tool_use.map(t => t.tool_name).join(', ')}`);
836
+ driver.logger.warn(
837
+ `[Gemini] Recoverable tool call issue (${candidate.finishReason}): ` +
838
+ `Model tried to call undeclared tool(s): ${tool_use.map((t) => t.tool_name).join(', ')}`,
839
+ );
684
840
  }
685
841
  }
686
842
  return {
@@ -694,8 +850,10 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
694
850
  }
695
851
  //No normal output, returning block reason if it exists.
696
852
  return {
697
- result: item.promptFeedback?.blockReasonMessage ? [{ type: "text" as const, value: item.promptFeedback.blockReasonMessage }] : [],
698
- finish_reason: item.promptFeedback?.blockReason ?? "",
853
+ result: item.promptFeedback?.blockReasonMessage
854
+ ? [{ type: 'text' as const, value: item.promptFeedback.blockReasonMessage }]
855
+ : [],
856
+ finish_reason: item.promptFeedback?.blockReason ?? '',
699
857
  token_usage: token_usage,
700
858
  };
701
859
  });
@@ -705,11 +863,11 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
705
863
 
706
864
  /**
707
865
  * Format Google API errors into LlumiverseError with proper status codes and retryability.
708
- *
866
+ *
709
867
  * Google API errors follow AIP-193 standard:
710
868
  * - ApiError.status: HTTP status code
711
869
  * - ApiError.message: Error message
712
- *
870
+ *
713
871
  * Common error codes:
714
872
  * - 400 (INVALID_ARGUMENT): Invalid request parameters
715
873
  * - 401 (UNAUTHENTICATED): Authentication required
@@ -719,15 +877,11 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
719
877
  * - 500 (INTERNAL): Internal server error
720
878
  * - 503 (UNAVAILABLE): Service temporarily unavailable
721
879
  * - 504 (DEADLINE_EXCEEDED): Request timeout
722
- *
880
+ *
723
881
  * @see https://google.aip.dev/193
724
882
  * @see https://docs.cloud.google.com/vertex-ai/generative-ai/docs/model-reference/api-errors
725
883
  */
726
- formatLlumiverseError(
727
- _driver: VertexAIDriver,
728
- error: unknown,
729
- context: LlumiverseErrorContext
730
- ): LlumiverseError {
884
+ formatLlumiverseError(_driver: VertexAIDriver, error: unknown, context: LlumiverseErrorContext): LlumiverseError {
731
885
  // Check if it's a Google API error with status code
732
886
  const isApiError = this.isGoogleApiError(error);
733
887
 
@@ -737,11 +891,11 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
737
891
  throw error;
738
892
  }
739
893
 
740
- const apiError = error as ApiError;
894
+ const apiError = error;
741
895
  const httpStatusCode = apiError.status;
742
896
 
743
897
  // Extract error message
744
- const message = apiError.message || String(error);
898
+ const message = apiError.message;
745
899
 
746
900
  // Build user-facing message with status code
747
901
  let userMessage = message;
@@ -752,7 +906,7 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
752
906
  }
753
907
 
754
908
  // Determine retryability based on Google error codes
755
- const retryable = this.isGeminiErrorRetryable(httpStatusCode);
909
+ const retryable = this.isGeminiErrorRetryable(httpStatusCode, message);
756
910
 
757
911
  // Extract error name/type from message if present
758
912
  const errorName = this.extractErrorName(message);
@@ -763,34 +917,36 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
763
917
  context,
764
918
  error,
765
919
  httpStatusCode,
766
- errorName
920
+ errorName,
767
921
  );
768
922
  }
769
923
 
770
924
  /**
771
925
  * Type guard to check if error is a Google API error.
772
926
  */
773
- private isGoogleApiError(error: unknown): error is ApiError {
927
+ private isGoogleApiError(error: unknown): error is GoogleApiErrorLike {
774
928
  return (
775
929
  error !== null &&
776
930
  typeof error === 'object' &&
777
931
  'status' in error &&
778
- typeof (error as any).status === 'number' &&
779
- 'message' in error
932
+ typeof (error as { status?: unknown }).status === 'number' &&
933
+ 'message' in error &&
934
+ typeof (error as { message?: unknown }).message === 'string'
780
935
  );
781
936
  }
782
937
 
783
938
  /**
784
939
  * Determine if a Google API error is retryable based on HTTP status code.
785
- *
940
+ *
786
941
  * Retryable errors (per Google AIP-194):
787
942
  * - 408 (REQUEST_TIMEOUT): Request timeout
943
+ * - 499 (CANCELLED / Client Closed Request): Transport cancellation
788
944
  * - 429 (RESOURCE_EXHAUSTED): Rate limit exceeded, quota exhausted
789
945
  * - 500 (INTERNAL): Internal server error
790
946
  * - 502 (BAD_GATEWAY): Bad gateway
791
947
  * - 503 (UNAVAILABLE): Service temporarily unavailable
792
948
  * - 504 (DEADLINE_EXCEEDED): Gateway timeout
793
- *
949
+ *
794
950
  * Non-retryable errors:
795
951
  * - 400 (INVALID_ARGUMENT): Invalid request parameters
796
952
  * - 401 (UNAUTHENTICATED): Authentication required
@@ -798,19 +954,48 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
798
954
  * - 404 (NOT_FOUND): Resource not found
799
955
  * - 409 (CONFLICT): Resource conflict
800
956
  * - Other 4xx client errors
801
- *
957
+ *
958
+ * Exception: certain 400s from Vertex AI's inline URL fetcher (used when
959
+ * passing a file by URL to multimodal models) surface as INVALID_ARGUMENT
960
+ * but are actually transient throttling/rate-limit signals on the
961
+ * fetcher, not a bad request. Detect those by message substring and
962
+ * treat them as retryable.
963
+ *
802
964
  * @param httpStatusCode - The HTTP status code from the API error
965
+ * @param message - The error message (used to detect transient 400 sub-cases)
803
966
  * @returns True if retryable, false if not retryable, undefined if unknown
804
967
  */
805
- private isGeminiErrorRetryable(httpStatusCode: number): boolean | undefined {
968
+ private isGeminiErrorRetryable(httpStatusCode: number, message?: string): boolean | undefined {
969
+ if (message && this.isNonRetryableAuthError(message)) return false;
970
+
806
971
  // Retryable status codes
807
972
  if (httpStatusCode === 408) return true; // Request timeout
808
973
  if (httpStatusCode === 429) return true; // Rate limit/quota
974
+ if (httpStatusCode === 499) return true; // Client closed / operation cancelled
809
975
  if (httpStatusCode === 502) return true; // Bad gateway
810
976
  if (httpStatusCode === 503) return true; // Service unavailable
811
977
  if (httpStatusCode === 504) return true; // Gateway timeout
812
978
  if (httpStatusCode >= 500 && httpStatusCode < 600) return true; // Other 5xx server errors
813
979
 
980
+ // Vertex AI URL fetcher transient throttling, surfaced as 400 INVALID_ARGUMENT
981
+ // but really a Google-side rate limit on the inline-content fetcher.
982
+ if (httpStatusCode === 400 && message) {
983
+ if (
984
+ message.includes('URL_REJECTED-REJECTED_CLIENT_THROTTLED') ||
985
+ message.includes('URL_REJECTED-REJECTED_RATE_LIMITED')
986
+ ) {
987
+ return true;
988
+ }
989
+ }
990
+
991
+ // A transport-level abort/cancel (request-timeout / dropped connection, sometimes reported
992
+ // as 499 client-closed) or a deadline-exceeded is transient and should be retried,
993
+ // even though it carries a 4xx status. Honor it before the 4xx -> non-retryable rule.
994
+ if (message) {
995
+ const lower = message.toLowerCase();
996
+ if (lower.includes('aborted') || lower.includes('cancelled') || lower.includes('deadline')) return true;
997
+ }
998
+
814
999
  // Non-retryable 4xx client errors
815
1000
  if (httpStatusCode >= 400 && httpStatusCode < 500) return false;
816
1001
 
@@ -818,6 +1003,11 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
818
1003
  return undefined;
819
1004
  }
820
1005
 
1006
+ private isNonRetryableAuthError(message: string): boolean {
1007
+ const lowerMessage = message.toLowerCase();
1008
+ return lowerMessage.includes('invalid_grant') || lowerMessage.includes("credential's issuer");
1009
+ }
1010
+
821
1011
  /**
822
1012
  * Extract error type name from error message.
823
1013
  * Google errors often include the error type in the message.
@@ -826,9 +1016,10 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
826
1016
  private extractErrorName(message: string): string | undefined {
827
1017
  // Common Google error patterns
828
1018
  const patterns = [
829
- /^([A-Z_]+):/, // "ERROR_NAME: message"
1019
+ /^Error code ([a-zA-Z0-9_-]+):/, // "Error code invalid_grant: message"
1020
+ /^([A-Z_]+):/, // "ERROR_NAME: message"
830
1021
  /\[([A-Z_]+)\]/, // "[ERROR_NAME] message"
831
- /^(\w+Error):/, // "ErrorTypeError: message"
1022
+ /^(\w+Error):/, // "ErrorTypeError: message"
832
1023
  ];
833
1024
 
834
1025
  for (const pattern of patterns) {
@@ -840,31 +1031,30 @@ export class GeminiModelDefinition implements ModelDefinition<GenerateContentPro
840
1031
 
841
1032
  return undefined;
842
1033
  }
843
-
844
1034
  }
845
1035
 
846
-
847
1036
  /**
848
1037
  * Converts functionCall and functionResponse parts to text parts in Gemini Content[].
849
1038
  * Preserves tool call information while removing structured parts that require
850
1039
  * tools/toolConfig to be defined in the API request.
851
1040
  */
852
1041
  export function convertGeminiFunctionPartsToText(contents: Content[]): Content[] {
853
- return contents.map(content => {
1042
+ return contents.map((content) => {
854
1043
  if (!content.parts) return content;
855
- const hasFunctionParts = content.parts.some(p => p.functionCall || p.functionResponse);
1044
+ const hasFunctionParts = content.parts.some((p) => p.functionCall || p.functionResponse);
856
1045
  if (!hasFunctionParts) return content;
857
1046
 
858
- const newParts = content.parts.map(part => {
1047
+ const newParts = content.parts.map((part) => {
859
1048
  if (part.functionCall) {
860
1049
  const argsStr = part.functionCall.args ? JSON.stringify(part.functionCall.args) : '';
861
- const truncated = argsStr.length > 500 ? argsStr.substring(0, 500) + '...' : argsStr;
1050
+ const truncated = argsStr.length > 500 ? `${argsStr.substring(0, 500)}...` : argsStr;
862
1051
  return { text: `[Tool call: ${part.functionCall.name}(${truncated})]` };
863
1052
  }
864
1053
  if (part.functionResponse) {
865
1054
  const respStr = part.functionResponse.response
866
- ? JSON.stringify(part.functionResponse.response) : 'No response';
867
- const truncated = respStr.length > 500 ? respStr.substring(0, 500) + '...' : respStr;
1055
+ ? JSON.stringify(part.functionResponse.response)
1056
+ : 'No response';
1057
+ const truncated = respStr.length > 500 ? `${respStr.substring(0, 500)}...` : respStr;
868
1058
  return { text: `[Tool result for ${part.functionResponse.name}: ${truncated}]` };
869
1059
  }
870
1060
  return part;
@@ -881,7 +1071,7 @@ function getToolDefinitions(tools: ToolDefinition[] | undefined | null): Tool |
881
1071
  // For multiple tools, we have multiple functions in one tool.
882
1072
  return {
883
1073
  functionDeclarations: tools.map(getToolFunction),
884
- }
1074
+ };
885
1075
  }
886
1076
 
887
1077
  function getToolFunction(tool: ToolDefinition): FunctionDeclaration {
@@ -904,7 +1094,7 @@ function getToolFunction(tool: ToolDefinition): FunctionDeclaration {
904
1094
  function updateConversation(conversation: unknown, prompt: Content[]): Content[] {
905
1095
  // Unwrap array if wrapped, otherwise treat as array
906
1096
  const unwrapped = unwrapConversationArray<Content>(conversation);
907
- const convArray = unwrapped ?? (conversation as Content[] || []);
1097
+ const convArray = unwrapped ?? ((conversation as Content[]) || []);
908
1098
  return convArray.concat(prompt);
909
1099
  }
910
1100
 
@@ -933,7 +1123,7 @@ function extractSystemFromConversation(conversation: unknown): Content | undefin
933
1123
  function storeSystemInConversation(conversation: unknown, system: Content | undefined): unknown {
934
1124
  if (!system) return conversation;
935
1125
  if (typeof conversation === 'object' && conversation !== null) {
936
- return { ...conversation as object, [SYSTEM_KEY]: system };
1126
+ return { ...(conversation as object), [SYSTEM_KEY]: system };
937
1127
  }
938
1128
  return conversation;
939
1129
  }
@@ -951,10 +1141,10 @@ function storeSystemInConversation(conversation: unknown, system: Content | unde
951
1141
  */
952
1142
  function formatFunctionResponse(response: string): JSONObject {
953
1143
  response = response.trim();
954
- if (response.startsWith("{") && response.endsWith("}")) {
1144
+ if (response.startsWith('{') && response.endsWith('}')) {
955
1145
  try {
956
1146
  return JSON.parse(response);
957
- } catch (e) {
1147
+ } catch {
958
1148
  return { output: response };
959
1149
  }
960
1150
  } else {