@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,309 @@
1
+ import turnAbortedGuidance from "../prompts/turn-aborted-guidance.md" with { type: "text" };
2
+ import type {
3
+ Api,
4
+ AssistantMessage,
5
+ DeveloperMessage,
6
+ Message,
7
+ Model,
8
+ ToolCall,
9
+ ToolResultMessage,
10
+ UserMessage,
11
+ } from "../types";
12
+
13
+ const enum ToolCallStatus {
14
+ /** Tool call has received a result (real or synthetic for orphan) */
15
+ Resolved = 1,
16
+ /** Tool call was from an aborted message; synthetic result injected, skip real results */
17
+ Aborted = 2,
18
+ }
19
+
20
+ /**
21
+ * Normalize tool call ID for cross-provider compatibility.
22
+ * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
23
+ * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).
24
+ *
25
+ * For aborted/errored turns, this function:
26
+ * - Preserves tool call structure (unlike converting to text summaries)
27
+ * - Injects synthetic "aborted" tool results
28
+ * - Adds a <turn-aborted> guidance marker for the model
29
+ */
30
+ export function transformMessages<TApi extends Api>(
31
+ messages: Message[],
32
+ model: Model<TApi>,
33
+ normalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string,
34
+ ): Message[] {
35
+ // Build a map of original tool call IDs to normalized IDs
36
+ const toolCallIdMap = new Map<string, string>();
37
+
38
+ const latestAssistantIndex = messages.findLastIndex(msg => msg.role === "assistant");
39
+ // First pass: transform messages (thinking blocks, tool call ID normalization)
40
+ const transformed = messages.map((msg, index) => {
41
+ // User and developer messages pass through unchanged
42
+ if (msg.role === "user" || msg.role === "developer") {
43
+ return msg;
44
+ }
45
+
46
+ // Handle toolResult messages - normalize toolCallId if we have a mapping
47
+ if (msg.role === "toolResult") {
48
+ const normalizedId = toolCallIdMap.get(msg.toolCallId);
49
+ if (normalizedId && normalizedId !== msg.toolCallId) {
50
+ return { ...msg, toolCallId: normalizedId };
51
+ }
52
+ return msg;
53
+ }
54
+
55
+ // Assistant messages need transformation check
56
+ if (msg.role === "assistant") {
57
+ const assistantMsg = msg as AssistantMessage;
58
+ const isSameModel =
59
+ assistantMsg.provider === model.provider &&
60
+ assistantMsg.api === model.api &&
61
+ assistantMsg.model === model.id;
62
+
63
+ const mustPreserveLatestAnthropicThinking =
64
+ index === latestAssistantIndex &&
65
+ model.api === "anthropic-messages" &&
66
+ assistantMsg.api === "anthropic-messages";
67
+ // Aborted/errored messages may have partially-streamed thinking signatures.
68
+ // A partial signature is invalid and will be rejected by the API, so we must
69
+ // strip signatures from thinking blocks in these messages.
70
+ const hasInvalidSignatures = assistantMsg.stopReason === "aborted" || assistantMsg.stopReason === "error";
71
+
72
+ const transformedContent = assistantMsg.content.flatMap(block => {
73
+ if (block.type === "thinking") {
74
+ // Strip signature from aborted/errored messages — it's likely incomplete
75
+ const sanitized =
76
+ hasInvalidSignatures && block.thinkingSignature ? { ...block, thinkingSignature: undefined } : block;
77
+ if (mustPreserveLatestAnthropicThinking) return sanitized;
78
+ // For same model: keep thinking blocks with signatures (needed for replay)
79
+ // even if the thinking text is empty (OpenAI encrypted reasoning)
80
+ if (isSameModel && sanitized.thinkingSignature) return sanitized;
81
+ // Skip empty thinking blocks, convert others to plain text
82
+ if (!sanitized.thinking || sanitized.thinking.trim() === "") return [];
83
+ if (isSameModel) return sanitized;
84
+ return {
85
+ type: "text" as const,
86
+ text: sanitized.thinking,
87
+ };
88
+ }
89
+
90
+ if (block.type === "redactedThinking") {
91
+ if (mustPreserveLatestAnthropicThinking) return block;
92
+ if (isSameModel) return block;
93
+ return [];
94
+ }
95
+
96
+ if (block.type === "text") {
97
+ if (isSameModel) return block;
98
+ return {
99
+ type: "text" as const,
100
+ text: block.text,
101
+ };
102
+ }
103
+
104
+ if (block.type === "toolCall") {
105
+ const toolCall = block as ToolCall;
106
+ let normalizedToolCall: ToolCall = toolCall;
107
+
108
+ if (!isSameModel && toolCall.thoughtSignature) {
109
+ normalizedToolCall = { ...toolCall };
110
+ delete (normalizedToolCall as { thoughtSignature?: string }).thoughtSignature;
111
+ }
112
+
113
+ if (!isSameModel && normalizeToolCallId) {
114
+ const normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);
115
+ if (normalizedId !== toolCall.id) {
116
+ toolCallIdMap.set(toolCall.id, normalizedId);
117
+ normalizedToolCall = { ...normalizedToolCall, id: normalizedId };
118
+ }
119
+ }
120
+
121
+ return normalizedToolCall;
122
+ }
123
+
124
+ return block;
125
+ });
126
+
127
+ return {
128
+ ...assistantMsg,
129
+ content: transformedContent,
130
+ };
131
+ }
132
+ return msg;
133
+ });
134
+ const realToolResultIds = new Set(
135
+ transformed.filter((msg): msg is ToolResultMessage => msg.role === "toolResult").map(msg => msg.toolCallId),
136
+ );
137
+
138
+ // Anthropic rejects `tool_result` blocks whose `tool_use_id` does not appear in a prior
139
+ // `tool_use` block. After handoff/compaction folds an assistant turn into a summary
140
+ // string, the user-side `toolResult` for that turn can survive while the originating
141
+ // `tool_use` disappears — leaving an orphan that triggers HTTP 400. Track the set of
142
+ // `tool_use` ids that survive transformation so the second pass can drop orphans cleanly.
143
+ const validToolUseIds = new Set<string>();
144
+ for (const msg of transformed) {
145
+ if (msg.role !== "assistant") continue;
146
+ for (const block of msg.content) {
147
+ if (block.type === "toolCall") validToolUseIds.add(block.id);
148
+ }
149
+ }
150
+
151
+ // Second pass: insert synthetic empty tool results for orphaned tool calls
152
+ // and preserve aborted/errored tool results when they were already persisted.
153
+ const result: Message[] = [];
154
+ let pendingToolCalls: ToolCall[] = [];
155
+ let pendingAbortedToolCalls = new Map<string, ToolCall>();
156
+ let pendingAbortedTimestamp: number | undefined;
157
+ // Track tool call status: whether resolved (has result) or aborted (synthetic result injected, skip later real results)
158
+ const toolCallStatus = new Map<string, ToolCallStatus>();
159
+
160
+ const flushPendingToolCalls = (timestamp: number): void => {
161
+ if (pendingToolCalls.length === 0) return;
162
+ for (const tc of pendingToolCalls) {
163
+ if (!toolCallStatus.has(tc.id) && !realToolResultIds.has(tc.id)) {
164
+ result.push({
165
+ role: "toolResult",
166
+ toolCallId: tc.id,
167
+ toolName: tc.name,
168
+ content: [{ type: "text", text: "No result provided" }],
169
+ isError: true,
170
+ timestamp,
171
+ } as ToolResultMessage);
172
+ toolCallStatus.set(tc.id, ToolCallStatus.Resolved);
173
+ }
174
+ }
175
+ pendingToolCalls = [];
176
+ };
177
+
178
+ const flushPendingAbortedToolCalls = (): void => {
179
+ if (pendingAbortedTimestamp === undefined) return;
180
+ for (const tc of pendingAbortedToolCalls.values()) {
181
+ if (!toolCallStatus.has(tc.id)) {
182
+ result.push({
183
+ role: "toolResult",
184
+ toolCallId: tc.id,
185
+ toolName: tc.name,
186
+ content: [{ type: "text", text: "aborted" }],
187
+ isError: true,
188
+ timestamp: pendingAbortedTimestamp,
189
+ } as ToolResultMessage);
190
+ toolCallStatus.set(tc.id, ToolCallStatus.Aborted);
191
+ }
192
+ }
193
+ result.push({
194
+ role: "developer",
195
+ content: turnAbortedGuidance,
196
+ timestamp: pendingAbortedTimestamp + 1,
197
+ } as DeveloperMessage);
198
+ pendingAbortedToolCalls = new Map();
199
+ pendingAbortedTimestamp = undefined;
200
+ };
201
+
202
+ for (let i = 0; i < transformed.length; i++) {
203
+ const msg = transformed[i];
204
+ const messageTimestamp = "timestamp" in msg && typeof msg.timestamp === "number" ? msg.timestamp : Date.now();
205
+
206
+ if (msg.role === "assistant") {
207
+ flushPendingToolCalls(messageTimestamp);
208
+ flushPendingAbortedToolCalls();
209
+
210
+ const assistantMsg = msg as AssistantMessage;
211
+ const toolCalls = assistantMsg.content.filter(b => b.type === "toolCall") as ToolCall[];
212
+
213
+ if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
214
+ // Keep the assistant message with tool calls intact. If real tool results follow, preserve them;
215
+ // otherwise synthesize aborted results before the next turn boundary.
216
+ result.push(msg);
217
+ pendingAbortedToolCalls = new Map(toolCalls.map(toolCall => [toolCall.id, toolCall] as const));
218
+ pendingAbortedTimestamp = assistantMsg.timestamp;
219
+ continue;
220
+ }
221
+
222
+ if (toolCalls.length > 0) {
223
+ pendingToolCalls = toolCalls;
224
+ }
225
+
226
+ result.push(msg);
227
+ } else if (msg.role === "toolResult") {
228
+ if (pendingAbortedToolCalls.has(msg.toolCallId)) {
229
+ pendingAbortedToolCalls.delete(msg.toolCallId);
230
+ toolCallStatus.set(msg.toolCallId, ToolCallStatus.Resolved);
231
+ result.push(msg);
232
+ continue;
233
+ }
234
+
235
+ if (toolCallStatus.get(msg.toolCallId) === ToolCallStatus.Aborted) continue;
236
+
237
+ if (!validToolUseIds.has(msg.toolCallId)) {
238
+ // Orphan `tool_result`: the originating `tool_use` is not present in the
239
+ // transformed history (typically because handoff/compaction folded the
240
+ // assistant message into a summary string while the user-side result
241
+ // survived). Sending the block as-is would 400 the request, so it must
242
+ // be dropped.
243
+ //
244
+ // If a pending tool-call window is still open (either normal or
245
+ // aborted), the orphan cannot be replaced with a developer note here:
246
+ //
247
+ // * Anthropic requires the next message after an assistant `tool_use`
248
+ // to be the matching `tool_result`. Inserting a developer message
249
+ // would break that contiguity.
250
+ // * `flushPendingAbortedToolCalls` synthesizes "aborted" results
251
+ // without checking whether a real result lands later in history
252
+ // (unlike `flushPendingToolCalls`, which is gated by
253
+ // `realToolResultIds`). Calling it here would convert a legitimate
254
+ // later `tool_result` into a synthetic "aborted" one via the
255
+ // `ToolCallStatus.Aborted` skip-guard.
256
+ //
257
+ // Drop the orphan silently in that case; the upcoming real
258
+ // `tool_result` will land normally on the next iteration.
259
+ if (pendingToolCalls.length > 0 || pendingAbortedToolCalls.size > 0) {
260
+ continue;
261
+ }
262
+ // No pending tool-call window: safe to preserve the text payload so the
263
+ // model still sees what the tool returned.
264
+ //
265
+ // The note is emitted with `role: "user"` rather than `role: "developer"`
266
+ // because the developer role is elevated by some providers:
267
+ //
268
+ // * Ollama maps `developer` -> `system` (highest instruction priority).
269
+ // * OpenAI chat-completions reasoning models forward `developer` as
270
+ // `developer` (above-user instruction priority).
271
+ //
272
+ // Stale, model-untrusted tool output must not gain instruction priority
273
+ // above user/developer messages it lived alongside before compaction.
274
+ // `user` role is mapped to plain user content by every provider, so the
275
+ // content survives without ever being treated as an instruction the
276
+ // model should obey.
277
+ const textParts: string[] = [];
278
+ for (const part of msg.content) {
279
+ if (part.type === "text" && part.text.trim() !== "") textParts.push(part.text);
280
+ }
281
+ if (textParts.length > 0) {
282
+ const errorAttr = msg.isError ? ' is-error="true"' : "";
283
+ result.push({
284
+ role: "user",
285
+ content: `<stale-tool-result tool="${msg.toolName}" id="${msg.toolCallId}"${errorAttr}>\n${textParts.join("\n")}\n</stale-tool-result>`,
286
+ timestamp: messageTimestamp,
287
+ } as UserMessage);
288
+ }
289
+ continue;
290
+ }
291
+
292
+ toolCallStatus.set(msg.toolCallId, ToolCallStatus.Resolved);
293
+ result.push(msg);
294
+ } else if (msg.role === "user" || msg.role === "developer") {
295
+ flushPendingToolCalls(messageTimestamp);
296
+ flushPendingAbortedToolCalls();
297
+ result.push(msg);
298
+ } else {
299
+ flushPendingToolCalls(messageTimestamp);
300
+ flushPendingAbortedToolCalls();
301
+ result.push(msg);
302
+ }
303
+ }
304
+
305
+ flushPendingToolCalls(Date.now());
306
+ flushPendingAbortedToolCalls();
307
+
308
+ return result;
309
+ }
@@ -0,0 +1,31 @@
1
+ import type { ImageContent, TextContent } from "../types";
2
+
3
+ export const NON_VISION_IMAGE_PLACEHOLDER = "[image omitted: model does not support vision]";
4
+
5
+ export function partitionVisionContent(
6
+ content: ReadonlyArray<TextContent | ImageContent>,
7
+ supportsImages: boolean,
8
+ ): {
9
+ textBlocks: TextContent[];
10
+ imageBlocks: ImageContent[];
11
+ omittedImages: boolean;
12
+ } {
13
+ const textBlocks = content.filter((block): block is TextContent => block.type === "text");
14
+ const imageBlocks = content.filter((block): block is ImageContent => block.type === "image");
15
+ return {
16
+ textBlocks,
17
+ imageBlocks: supportsImages ? imageBlocks : [],
18
+ omittedImages: !supportsImages && imageBlocks.length > 0,
19
+ };
20
+ }
21
+
22
+ export function joinTextWithImagePlaceholder(text: string, omittedImages: boolean): string {
23
+ const parts: string[] = [];
24
+ if (text.length > 0) {
25
+ parts.push(text);
26
+ }
27
+ if (omittedImages) {
28
+ parts.push(NON_VISION_IMAGE_PLACEHOLDER);
29
+ }
30
+ return parts.join("\n");
31
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Rate limit reason classification and backoff calculation utilities.
3
+ * Ported from opencode-antigravity-auth plugin for consistency.
4
+ */
5
+
6
+ export type RateLimitReason =
7
+ | "QUOTA_EXHAUSTED"
8
+ | "RATE_LIMIT_EXCEEDED"
9
+ | "MODEL_CAPACITY_EXHAUSTED"
10
+ | "SERVER_ERROR"
11
+ | "UNKNOWN";
12
+
13
+ const QUOTA_EXHAUSTED_BACKOFF_MS = 30 * 60 * 1000; // 30 min
14
+ const RATE_LIMIT_EXCEEDED_BACKOFF_MS = 30 * 1000; // 30s
15
+ const MODEL_CAPACITY_BASE_MS = 45 * 1000; // 45s base
16
+ const MODEL_CAPACITY_JITTER_MS = 30 * 1000; // ±15s
17
+ const SERVER_ERROR_BACKOFF_MS = 20 * 1000; // 20s
18
+
19
+ /**
20
+ * Classify a rate-limit error message into a reason category.
21
+ * Priority order: MODEL_CAPACITY > RATE_LIMIT > QUOTA > SERVER_ERROR > UNKNOWN.
22
+ *
23
+ * "resource exhausted" maps to MODEL_CAPACITY (transient, short wait)
24
+ * "quota exceeded" maps to QUOTA_EXHAUSTED (long wait, switch account)
25
+ */
26
+ export function parseRateLimitReason(errorMessage: string): RateLimitReason {
27
+ const lower = errorMessage.toLowerCase();
28
+
29
+ if (
30
+ lower.includes("capacity") ||
31
+ lower.includes("overloaded") ||
32
+ lower.includes("529") ||
33
+ lower.includes("503") ||
34
+ lower.includes("resource exhausted")
35
+ ) {
36
+ return "MODEL_CAPACITY_EXHAUSTED";
37
+ }
38
+
39
+ if (
40
+ lower.includes("per minute") ||
41
+ lower.includes("rate limit") ||
42
+ lower.includes("too many requests") ||
43
+ lower.includes("presque")
44
+ ) {
45
+ return "RATE_LIMIT_EXCEEDED";
46
+ }
47
+
48
+ if (lower.includes("exhausted") || lower.includes("quota") || lower.includes("usage limit")) {
49
+ return "QUOTA_EXHAUSTED";
50
+ }
51
+
52
+ if (lower.includes("500") || lower.includes("internal error") || lower.includes("internal server error")) {
53
+ return "SERVER_ERROR";
54
+ }
55
+
56
+ return "UNKNOWN";
57
+ }
58
+
59
+ /**
60
+ * Calculate backoff delay in ms for a given rate limit reason.
61
+ * MODEL_CAPACITY gets jitter to prevent thundering herd.
62
+ */
63
+ export function calculateRateLimitBackoffMs(reason: RateLimitReason): number {
64
+ switch (reason) {
65
+ case "QUOTA_EXHAUSTED":
66
+ return QUOTA_EXHAUSTED_BACKOFF_MS;
67
+ case "RATE_LIMIT_EXCEEDED":
68
+ return RATE_LIMIT_EXCEEDED_BACKOFF_MS;
69
+ case "MODEL_CAPACITY_EXHAUSTED":
70
+ return MODEL_CAPACITY_BASE_MS + Math.random() * MODEL_CAPACITY_JITTER_MS;
71
+ case "SERVER_ERROR":
72
+ return SERVER_ERROR_BACKOFF_MS;
73
+ default:
74
+ return QUOTA_EXHAUSTED_BACKOFF_MS; // conservative default
75
+ }
76
+ }
77
+
78
+ /** Detect usage/quota limit errors in error messages (persistent, requires credential switch). */
79
+ const USAGE_LIMIT_PATTERN =
80
+ /usage.?limit|usage_limit_reached|usage_not_included|limit_reached|quota.?exceeded|resource.?exhausted/i;
81
+
82
+ export function isUsageLimitError(errorMessage: string): boolean {
83
+ return USAGE_LIMIT_PATTERN.test(errorMessage);
84
+ }