@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,800 @@
1
+ import { structuredCloneJSON } from "@gajae-code/utils";
2
+ import type OpenAI from "openai";
3
+ import type {
4
+ ResponseCustomToolCall,
5
+ ResponseFunctionToolCall,
6
+ ResponseInput,
7
+ ResponseInputContent,
8
+ ResponseInputImage,
9
+ ResponseInputText,
10
+ ResponseOutputItem,
11
+ ResponseOutputMessage,
12
+ ResponseReasoningItem,
13
+ } from "openai/resources/responses/responses";
14
+ import { calculateCost } from "../models";
15
+ import {
16
+ type Api,
17
+ type AssistantMessage,
18
+ type ImageContent,
19
+ type Model,
20
+ resolveServiceTier,
21
+ type ServiceTier,
22
+ type StopReason,
23
+ type StreamOptions,
24
+ shouldSendServiceTier,
25
+ type TextContent,
26
+ type TextSignatureV1,
27
+ type ThinkingContent,
28
+ type ToolCall,
29
+ type ToolResultMessage,
30
+ } from "../types";
31
+ import { normalizeResponsesToolCallId } from "../utils";
32
+ import type { AssistantMessageEventStream } from "../utils/event-stream";
33
+ import { parseStreamingJson } from "../utils/json-parse";
34
+ import { joinTextWithImagePlaceholder, NON_VISION_IMAGE_PLACEHOLDER, partitionVisionContent } from "./vision-guard";
35
+
36
+ export function encodeTextSignatureV1(id: string, phase?: TextSignatureV1["phase"]): string {
37
+ const payload: TextSignatureV1 = { v: 1, id };
38
+ if (phase) payload.phase = phase;
39
+ return JSON.stringify(payload);
40
+ }
41
+
42
+ export function parseTextSignature(
43
+ signature: string | undefined,
44
+ ): { id: string; phase?: TextSignatureV1["phase"] } | undefined {
45
+ if (!signature) return undefined;
46
+ if (signature.startsWith("{")) {
47
+ try {
48
+ const parsed = JSON.parse(signature) as Partial<TextSignatureV1>;
49
+ if (parsed.v === 1 && typeof parsed.id === "string") {
50
+ if (parsed.phase === "commentary" || parsed.phase === "final_answer") {
51
+ return { id: parsed.id, phase: parsed.phase };
52
+ }
53
+ return { id: parsed.id };
54
+ }
55
+ } catch {
56
+ // Fall through to legacy plain-string handling.
57
+ }
58
+ }
59
+ return { id: signature };
60
+ }
61
+
62
+ export function encodeResponsesToolCallId(callId: string, itemId: string | null | undefined): string {
63
+ const stableItemId = itemId && itemId.length > 0 ? itemId : `fc_${Bun.hash(callId).toString(36)}`;
64
+ return `${callId}|${stableItemId}`;
65
+ }
66
+
67
+ export function normalizeResponsesToolCallIdForTransform(
68
+ id: string,
69
+ model?: Model<Api>,
70
+ source?: AssistantMessage,
71
+ ): string {
72
+ if (!id.includes("|")) return id;
73
+ const isForeignToolCall =
74
+ source != null && model != null && (source.provider !== model.provider || source.api !== model.api);
75
+ if (isForeignToolCall) {
76
+ const [callId, itemId] = id.split("|");
77
+ const normalizeIdPart = (part: string): string => {
78
+ const sanitized = part.replace(/[^a-zA-Z0-9_-]/g, "_");
79
+ const truncated = sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
80
+ return truncated.replace(/_+$/, "");
81
+ };
82
+ const normalizedCallId = normalizeIdPart(callId);
83
+ let normalizedItemId = `fc_${Bun.hash(itemId).toString(36)}`;
84
+ if (normalizedItemId.length > 64) normalizedItemId = normalizedItemId.slice(0, 64);
85
+ return `${normalizedCallId}|${normalizedItemId}`;
86
+ }
87
+ const normalized = normalizeResponsesToolCallId(id);
88
+ return `${normalized.callId}|${normalized.itemId}`;
89
+ }
90
+
91
+ export function collectKnownCallIds(messages: ResponseInput): Set<string> {
92
+ const knownCallIds = new Set<string>();
93
+ for (const item of messages) {
94
+ if (item.type === "function_call" && typeof item.call_id === "string") {
95
+ knownCallIds.add(item.call_id);
96
+ } else if (
97
+ (item as { type?: string }).type === "custom_tool_call" &&
98
+ typeof (item as { call_id?: string }).call_id === "string"
99
+ ) {
100
+ knownCallIds.add((item as { call_id: string }).call_id);
101
+ }
102
+ }
103
+ return knownCallIds;
104
+ }
105
+
106
+ /** Scan replay items for call_ids that were originally custom tool calls. */
107
+ export function collectCustomCallIds(messages: ResponseInput): Set<string> {
108
+ const customCallIds = new Set<string>();
109
+ for (const item of messages) {
110
+ if (
111
+ (item as { type?: string }).type === "custom_tool_call" &&
112
+ typeof (item as { call_id?: string }).call_id === "string"
113
+ ) {
114
+ customCallIds.add((item as { call_id: string }).call_id);
115
+ }
116
+ }
117
+ return customCallIds;
118
+ }
119
+
120
+ /**
121
+ * Convert orphan `function_call_output` / `custom_tool_call_output` items —
122
+ * those whose `call_id` has no matching preceding `function_call` /
123
+ * `custom_tool_call` in the same input — into assistant text notes.
124
+ *
125
+ * The Responses API rejects unpaired outputs with
126
+ * `400 No tool call found for function call output with call_id …`. Orphans
127
+ * sneak in through two paths today:
128
+ *
129
+ * - A previous turn's `providerPayload` snapshot replaces the input array via
130
+ * the `dt: false` splice (see {@link convertConversationMessages}), wiping
131
+ * the matching `function_call` while leaving the matching
132
+ * `function_call_output` queued in a later `toolResult`.
133
+ * - A locally-rejected tool call (argument-validation failure, hook reject,
134
+ * aborted turn before the call streamed) produces a tool result without a
135
+ * `function_call` ever landing in any persisted provider payload.
136
+ *
137
+ * Dropping the result loses information the model needs to recover; sending
138
+ * it as-is 400s the request. Folding it into an assistant `message` preserves
139
+ * the payload (call_id + truncated output) while staying within the Responses
140
+ * input grammar. Matches the behavior of {@link transformRequestBody} in the
141
+ * OpenAI code backend provider — issue #1351 / regression of #472.
142
+ */
143
+ export function repairOrphanResponsesToolOutputs(input: ResponseInput): ResponseInput {
144
+ const knownCallIds = new Set<string>();
145
+ for (const item of input) {
146
+ const t = (item as { type?: string }).type;
147
+ const callId = (item as { call_id?: unknown }).call_id;
148
+ if (typeof callId !== "string") continue;
149
+ if (t === "function_call" || t === "custom_tool_call") knownCallIds.add(callId);
150
+ }
151
+ let hasOrphan = false;
152
+ for (const item of input) {
153
+ const t = (item as { type?: string }).type;
154
+ if (t !== "function_call_output" && t !== "custom_tool_call_output") continue;
155
+ const callId = (item as { call_id?: unknown }).call_id;
156
+ if (typeof callId === "string" && !knownCallIds.has(callId)) {
157
+ hasOrphan = true;
158
+ break;
159
+ }
160
+ }
161
+ if (!hasOrphan) return input;
162
+ return input.map(item => {
163
+ const t = (item as { type?: string }).type;
164
+ if (t !== "function_call_output" && t !== "custom_tool_call_output") return item;
165
+ const record = item as { call_id?: unknown; output?: unknown; name?: unknown };
166
+ const callId = record.call_id;
167
+ if (typeof callId !== "string" || knownCallIds.has(callId)) return item;
168
+ const toolName = typeof record.name === "string" && record.name.length > 0 ? record.name : "tool";
169
+ const rawOutput = record.output;
170
+ let text: string;
171
+ if (typeof rawOutput === "string") text = rawOutput;
172
+ else if (rawOutput == null) text = "";
173
+ else {
174
+ try {
175
+ text = JSON.stringify(rawOutput);
176
+ } catch {
177
+ text = String(rawOutput);
178
+ }
179
+ }
180
+ const ORPHAN_OUTPUT_LIMIT = 16_000;
181
+ if (text.length > ORPHAN_OUTPUT_LIMIT) text = `${text.slice(0, ORPHAN_OUTPUT_LIMIT)}\n...[truncated]`;
182
+ return {
183
+ type: "message",
184
+ role: "assistant",
185
+ content: `[Orphan ${toolName} result; call_id=${callId}]: ${text}`,
186
+ } as ResponseInput[number];
187
+ });
188
+ }
189
+
190
+ export function convertResponsesInputContent(
191
+ content: string | Array<TextContent | ImageContent>,
192
+ supportsImages: boolean,
193
+ ): ResponseInputContent[] | undefined {
194
+ if (typeof content === "string") {
195
+ if (content.trim().length === 0) return undefined;
196
+ return [{ type: "input_text", text: content.toWellFormed() } satisfies ResponseInputText];
197
+ }
198
+
199
+ const { textBlocks, imageBlocks, omittedImages } = partitionVisionContent(content, supportsImages);
200
+ const normalizedContent: ResponseInputContent[] = [];
201
+ for (const item of textBlocks) {
202
+ const text = item.text.toWellFormed();
203
+ if (text.trim().length === 0) continue;
204
+ normalizedContent.push({
205
+ type: "input_text",
206
+ text,
207
+ } satisfies ResponseInputText);
208
+ }
209
+ for (const item of imageBlocks) {
210
+ normalizedContent.push({
211
+ type: "input_image",
212
+ detail: "auto",
213
+ image_url: `data:${item.mimeType};base64,${item.data}`,
214
+ } satisfies ResponseInputImage);
215
+ }
216
+ if (omittedImages) {
217
+ normalizedContent.push({
218
+ type: "input_text",
219
+ text: NON_VISION_IMAGE_PLACEHOLDER,
220
+ } satisfies ResponseInputText);
221
+ }
222
+ return normalizedContent.length > 0 ? normalizedContent : undefined;
223
+ }
224
+
225
+ export function convertResponsesAssistantMessage<TApi extends Api>(
226
+ assistantMsg: AssistantMessage,
227
+ model: Model<TApi>,
228
+ msgIndex: number,
229
+ knownCallIds: Set<string>,
230
+ includeThinkingSignatures = true,
231
+ customCallIds?: Set<string>,
232
+ ): ResponseInput {
233
+ const outputItems: ResponseInput = [];
234
+ const isDifferentModel =
235
+ assistantMsg.model !== model.id && assistantMsg.provider === model.provider && assistantMsg.api === model.api;
236
+
237
+ for (const block of assistantMsg.content) {
238
+ if (block.type === "thinking" && assistantMsg.stopReason !== "error") {
239
+ if (!includeThinkingSignatures) {
240
+ continue;
241
+ }
242
+ if (block.thinkingSignature) {
243
+ outputItems.push(JSON.parse(block.thinkingSignature) as ResponseReasoningItem);
244
+ }
245
+ continue;
246
+ }
247
+
248
+ if (block.type === "text") {
249
+ const parsedSignature = parseTextSignature(block.textSignature);
250
+ let msgId = parsedSignature?.id;
251
+ if (!msgId) {
252
+ msgId = `msg_${msgIndex}`;
253
+ } else if (msgId.length > 64) {
254
+ msgId = `msg_${Bun.hash(msgId).toString(36)}`;
255
+ }
256
+ outputItems.push({
257
+ type: "message",
258
+ role: "assistant",
259
+ content: [{ type: "output_text", text: block.text.toWellFormed(), annotations: [] }],
260
+ status: "completed",
261
+ id: msgId,
262
+ phase: parsedSignature?.phase,
263
+ } satisfies ResponseOutputMessage);
264
+ continue;
265
+ }
266
+
267
+ if (block.type !== "toolCall") {
268
+ continue;
269
+ }
270
+
271
+ const normalized = normalizeResponsesToolCallId(block.id, block.customWireName ? "ctc" : "fc");
272
+ let itemId: string | undefined = normalized.itemId;
273
+ if (isDifferentModel && (itemId?.startsWith("fc_") || itemId?.startsWith("fcr_") || itemId?.startsWith("ctc_"))) {
274
+ itemId = undefined;
275
+ }
276
+ knownCallIds.add(normalized.callId);
277
+ if (block.customWireName) {
278
+ const rawInput = typeof block.arguments?.input === "string" ? block.arguments.input : "";
279
+ customCallIds?.add(normalized.callId);
280
+ outputItems.push({
281
+ type: "custom_tool_call",
282
+ id: itemId,
283
+ call_id: normalized.callId,
284
+ name: block.customWireName,
285
+ input: rawInput,
286
+ } as ResponseInput[number]);
287
+ continue;
288
+ }
289
+ outputItems.push({
290
+ type: "function_call",
291
+ id: itemId,
292
+ call_id: normalized.callId,
293
+ name: block.name,
294
+ arguments: JSON.stringify(block.arguments),
295
+ });
296
+ }
297
+
298
+ return outputItems;
299
+ }
300
+
301
+ export function appendResponsesToolResultMessages<TApi extends Api>(
302
+ messages: ResponseInput,
303
+ toolResult: ToolResultMessage,
304
+ model: Model<TApi>,
305
+ strictResponsesPairing: boolean,
306
+ knownCallIds: ReadonlySet<string>,
307
+ customCallIds?: ReadonlySet<string>,
308
+ ): void {
309
+ const supportsImages = model.input.includes("image");
310
+ const textResult = toolResult.content
311
+ .filter((block): block is TextContent => block.type === "text")
312
+ .map(block => block.text)
313
+ .join("\n");
314
+ const hasImages = toolResult.content.some((block): block is ImageContent => block.type === "image");
315
+ const omittedImages = hasImages && !supportsImages;
316
+ const normalized = normalizeResponsesToolCallId(toolResult.toolCallId);
317
+ if (strictResponsesPairing && !knownCallIds.has(normalized.callId)) {
318
+ return;
319
+ }
320
+
321
+ const output = (
322
+ omittedImages
323
+ ? joinTextWithImagePlaceholder(textResult, true)
324
+ : textResult.length > 0
325
+ ? textResult
326
+ : "(see attached image)"
327
+ ).toWellFormed();
328
+ if (customCallIds?.has(normalized.callId)) {
329
+ messages.push({
330
+ type: "custom_tool_call_output",
331
+ call_id: normalized.callId,
332
+ output,
333
+ } as ResponseInput[number]);
334
+ } else {
335
+ messages.push({
336
+ type: "function_call_output",
337
+ call_id: normalized.callId,
338
+ output,
339
+ });
340
+ }
341
+
342
+ if (!hasImages || !supportsImages) {
343
+ return;
344
+ }
345
+
346
+ const contentParts: ResponseInputContent[] = [
347
+ { type: "input_text", text: "Attached image(s) from tool result:" } satisfies ResponseInputText,
348
+ ];
349
+ for (const block of toolResult.content) {
350
+ if (block.type === "image") {
351
+ contentParts.push({
352
+ type: "input_image",
353
+ detail: "auto",
354
+ image_url: `data:${block.mimeType};base64,${block.data}`,
355
+ } satisfies ResponseInputImage);
356
+ }
357
+ }
358
+ messages.push({ role: "user", content: contentParts });
359
+ }
360
+
361
+ export interface ProcessResponsesStreamOptions {
362
+ onFirstToken?: () => void;
363
+ onOutputItemDone?: (item: ResponseOutputItem) => void;
364
+ }
365
+
366
+ export async function processResponsesStream<TApi extends Api>(
367
+ openaiStream: AsyncIterable<OpenAI.Responses.ResponseStreamEvent>,
368
+ output: AssistantMessage,
369
+ stream: AssistantMessageEventStream,
370
+ model: Model<TApi>,
371
+ options?: ProcessResponsesStreamOptions,
372
+ ): Promise<void> {
373
+ let currentItem:
374
+ | ResponseReasoningItem
375
+ | ResponseOutputMessage
376
+ | ResponseFunctionToolCall
377
+ | ResponseCustomToolCall
378
+ | null = null;
379
+ let currentBlock: ThinkingContent | TextContent | (ToolCall & { partialJson: string }) | null = null;
380
+ const blocks = output.content;
381
+ const blockIndex = () => blocks.length - 1;
382
+ let sawFirstToken = false;
383
+
384
+ for await (const event of openaiStream) {
385
+ if (event.type === "response.created") {
386
+ output.responseId = event.response.id;
387
+ } else if (event.type === "response.output_item.added") {
388
+ if (!sawFirstToken) {
389
+ sawFirstToken = true;
390
+ options?.onFirstToken?.();
391
+ }
392
+ const item = event.item;
393
+ if (item.type === "reasoning") {
394
+ currentItem = item;
395
+ currentBlock = { type: "thinking", thinking: "", itemId: item.id };
396
+ output.content.push(currentBlock);
397
+ stream.push({ type: "thinking_start", contentIndex: blockIndex(), partial: output });
398
+ } else if (item.type === "message") {
399
+ currentItem = item;
400
+ currentBlock = { type: "text", text: "" };
401
+ output.content.push(currentBlock);
402
+ stream.push({ type: "text_start", contentIndex: blockIndex(), partial: output });
403
+ } else if (item.type === "function_call") {
404
+ currentItem = item;
405
+ currentBlock = {
406
+ type: "toolCall",
407
+ id: encodeResponsesToolCallId(item.call_id, item.id),
408
+ name: item.name,
409
+ arguments: {},
410
+ partialJson: item.arguments || "",
411
+ };
412
+ output.content.push(currentBlock);
413
+ stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
414
+ } else if (item.type === "custom_tool_call") {
415
+ currentItem = item;
416
+ currentBlock = {
417
+ type: "toolCall",
418
+ id: encodeResponsesToolCallId(item.call_id, item.id),
419
+ // Preserve the raw wire name (e.g. `apply_patch`). The agent-loop
420
+ // dispatcher matches it against both `Tool.name` and
421
+ // `Tool.customWireName`, so this stays wire-accurate through
422
+ // history replay while still routing to the right handler.
423
+ name: item.name,
424
+ arguments: { input: item.input ?? "" },
425
+ customWireName: item.name,
426
+ // Custom tools stream a raw string, but we reuse `partialJson` as the
427
+ // accumulation buffer so later code that inspects the field still works.
428
+ partialJson: item.input ?? "",
429
+ };
430
+ output.content.push(currentBlock);
431
+ stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
432
+ }
433
+ } else if (event.type === "response.reasoning_summary_part.added") {
434
+ if (currentItem?.type === "reasoning") {
435
+ currentItem.summary = currentItem.summary || [];
436
+ currentItem.summary.push(event.part);
437
+ }
438
+ } else if (event.type === "response.reasoning_summary_text.delta") {
439
+ if (currentItem?.type === "reasoning" && currentBlock?.type === "thinking") {
440
+ currentItem.summary = currentItem.summary || [];
441
+ const lastPart = currentItem.summary[currentItem.summary.length - 1];
442
+ if (lastPart) {
443
+ currentBlock.thinking += event.delta;
444
+ lastPart.text += event.delta;
445
+ stream.push({
446
+ type: "thinking_delta",
447
+ contentIndex: blockIndex(),
448
+ delta: event.delta,
449
+ partial: output,
450
+ });
451
+ }
452
+ }
453
+ } else if (event.type === "response.reasoning_summary_part.done") {
454
+ if (currentItem?.type === "reasoning" && currentBlock?.type === "thinking") {
455
+ currentItem.summary = currentItem.summary || [];
456
+ const lastPart = currentItem.summary[currentItem.summary.length - 1];
457
+ if (lastPart) {
458
+ currentBlock.thinking += "\n\n";
459
+ lastPart.text += "\n\n";
460
+ stream.push({
461
+ type: "thinking_delta",
462
+ contentIndex: blockIndex(),
463
+ delta: "\n\n",
464
+ partial: output,
465
+ });
466
+ }
467
+ }
468
+ } else if (event.type === "response.reasoning_text.delta") {
469
+ // Raw reasoning text delta from local providers that stream thinking
470
+ // directly rather than via the OpenAI summary tracking protocol.
471
+ if (currentItem?.type === "reasoning" && currentBlock?.type === "thinking") {
472
+ currentBlock.thinking += event.delta;
473
+ stream.push({
474
+ type: "thinking_delta",
475
+ contentIndex: blockIndex(),
476
+ delta: event.delta,
477
+ partial: output,
478
+ });
479
+ }
480
+ } else if (event.type === "response.content_part.added") {
481
+ if (currentItem?.type === "message") {
482
+ currentItem.content = currentItem.content || [];
483
+ if (event.part.type === "output_text" || event.part.type === "refusal") {
484
+ currentItem.content.push(event.part);
485
+ }
486
+ }
487
+ } else if (event.type === "response.output_text.delta") {
488
+ if (currentItem?.type === "message" && currentBlock?.type === "text") {
489
+ const lastPart = currentItem.content?.[currentItem.content.length - 1];
490
+ if (lastPart?.type === "output_text") {
491
+ currentBlock.text += event.delta;
492
+ lastPart.text += event.delta;
493
+ stream.push({
494
+ type: "text_delta",
495
+ contentIndex: blockIndex(),
496
+ delta: event.delta,
497
+ partial: output,
498
+ });
499
+ }
500
+ }
501
+ } else if (event.type === "response.refusal.delta") {
502
+ if (currentItem?.type === "message" && currentBlock?.type === "text") {
503
+ const lastPart = currentItem.content?.[currentItem.content.length - 1];
504
+ if (lastPart?.type === "refusal") {
505
+ currentBlock.text += event.delta;
506
+ lastPart.refusal += event.delta;
507
+ stream.push({
508
+ type: "text_delta",
509
+ contentIndex: blockIndex(),
510
+ delta: event.delta,
511
+ partial: output,
512
+ });
513
+ }
514
+ }
515
+ } else if (event.type === "response.function_call_arguments.delta") {
516
+ if (currentItem?.type === "function_call" && currentBlock?.type === "toolCall") {
517
+ currentBlock.partialJson += event.delta;
518
+ currentBlock.arguments = parseStreamingJson(currentBlock.partialJson);
519
+ stream.push({
520
+ type: "toolcall_delta",
521
+ contentIndex: blockIndex(),
522
+ delta: event.delta,
523
+ partial: output,
524
+ });
525
+ }
526
+ } else if (event.type === "response.function_call_arguments.done") {
527
+ if (currentItem?.type === "function_call" && currentBlock?.type === "toolCall") {
528
+ currentBlock.partialJson = event.arguments;
529
+ currentBlock.arguments = parseStreamingJson(currentBlock.partialJson);
530
+ }
531
+ } else if (event.type === "response.custom_tool_call_input.delta") {
532
+ if (currentItem?.type === "custom_tool_call" && currentBlock?.type === "toolCall") {
533
+ currentBlock.partialJson += event.delta;
534
+ currentBlock.arguments = { input: currentBlock.partialJson };
535
+ stream.push({
536
+ type: "toolcall_delta",
537
+ contentIndex: blockIndex(),
538
+ delta: event.delta,
539
+ partial: output,
540
+ });
541
+ }
542
+ } else if (event.type === "response.custom_tool_call_input.done") {
543
+ if (currentItem?.type === "custom_tool_call" && currentBlock?.type === "toolCall") {
544
+ currentBlock.partialJson = event.input;
545
+ currentBlock.arguments = { input: event.input };
546
+ }
547
+ } else if (event.type === "response.output_item.done") {
548
+ const item = structuredCloneJSON(event.item);
549
+ options?.onOutputItemDone?.(item);
550
+ if (item.type === "reasoning") {
551
+ const thinking =
552
+ item.summary?.length > 0
553
+ ? item.summary.map(part => part.text).join("\n\n")
554
+ : item.content?.[0]?.type === "reasoning_text"
555
+ ? (item.content[0].text ?? "")
556
+ : "";
557
+ const reasoningBlock = output.content.find(
558
+ b => b.type === "thinking" && (b as ThinkingContent).itemId === item.id,
559
+ ) as ThinkingContent | undefined;
560
+ if (reasoningBlock) {
561
+ reasoningBlock.thinking = thinking;
562
+ reasoningBlock.thinkingSignature = JSON.stringify(item);
563
+ const reasoningBlockIndex = output.content.indexOf(reasoningBlock);
564
+ stream.push({
565
+ type: "thinking_end",
566
+ contentIndex: reasoningBlockIndex,
567
+ content: thinking,
568
+ partial: output,
569
+ });
570
+ }
571
+ if ((currentBlock as ThinkingContent | null)?.itemId === item.id) currentBlock = null;
572
+ } else if (item.type === "message" && currentBlock?.type === "text") {
573
+ currentBlock.text = item.content
574
+ .map(part => (part.type === "output_text" ? (part.text ?? "") : (part.refusal ?? "")))
575
+ .join("");
576
+ currentBlock.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);
577
+ stream.push({
578
+ type: "text_end",
579
+ contentIndex: blockIndex(),
580
+ content: currentBlock.text,
581
+ partial: output,
582
+ });
583
+ currentBlock = null;
584
+ } else if (item.type === "function_call") {
585
+ const args =
586
+ currentBlock?.type === "toolCall" && currentBlock.partialJson
587
+ ? parseStreamingJson(currentBlock.partialJson)
588
+ : parseStreamingJson(item.arguments || "{}");
589
+ const toolCall: ToolCall = {
590
+ type: "toolCall",
591
+ id: encodeResponsesToolCallId(item.call_id, item.id),
592
+ name: item.name,
593
+ arguments: args,
594
+ };
595
+ currentBlock = null;
596
+ stream.push({ type: "toolcall_end", contentIndex: blockIndex(), toolCall, partial: output });
597
+ } else if (item.type === "custom_tool_call") {
598
+ const rawInput =
599
+ currentBlock?.type === "toolCall" && currentBlock.partialJson
600
+ ? currentBlock.partialJson
601
+ : (item.input ?? "");
602
+ const toolCall: ToolCall = {
603
+ type: "toolCall",
604
+ id: encodeResponsesToolCallId(item.call_id, item.id),
605
+ name: item.name,
606
+ arguments: { input: rawInput },
607
+ customWireName: item.name,
608
+ };
609
+ currentBlock = null;
610
+ stream.push({ type: "toolcall_end", contentIndex: blockIndex(), toolCall, partial: output });
611
+ }
612
+ } else if (event.type === "response.completed") {
613
+ const response = event.response;
614
+ if (response?.id) {
615
+ output.responseId = response.id;
616
+ }
617
+ populateResponsesUsageFromResponse(output, response?.usage);
618
+ calculateCost(model, output.usage);
619
+ output.stopReason = mapOpenAIResponsesStopReason(response?.status);
620
+ if (response?.status === "failed" || response?.status === "cancelled") {
621
+ const error = response?.error ?? (response as any)?.status_details?.error;
622
+ const details = response?.incomplete_details;
623
+ const statusDetailsReason = (response as any)?.status_details?.reason;
624
+ const message = error
625
+ ? `${error.code || "unknown"}: ${error.message || "no message"}`
626
+ : details?.reason
627
+ ? `incomplete: ${details.reason}`
628
+ : typeof statusDetailsReason === "string" && statusDetailsReason.length > 0
629
+ ? `status_details: ${statusDetailsReason}`
630
+ : "Unknown error (no error details in response)";
631
+ throw new Error(message);
632
+ }
633
+ if (output.content.some(block => block.type === "toolCall") && output.stopReason === "stop") {
634
+ output.stopReason = "toolUse";
635
+ }
636
+ } else if (event.type === "error") {
637
+ throw new Error(`Error Code ${event.code}: ${event.message}` || "Unknown error");
638
+ } else if (event.type === "response.failed") {
639
+ const error = event.response?.error ?? (event.response as any)?.status_details?.error;
640
+ const details = event.response?.incomplete_details;
641
+ const message = error
642
+ ? `${error.code || "unknown"}: ${error.message || "no message"}`
643
+ : details?.reason
644
+ ? `incomplete: ${details.reason}`
645
+ : "Unknown error (no error details in response)";
646
+ throw new Error(message);
647
+ }
648
+ }
649
+ }
650
+
651
+ export function mapOpenAIResponsesStopReason(status: OpenAI.Responses.ResponseStatus | undefined): StopReason {
652
+ if (!status) return "stop";
653
+ switch (status) {
654
+ case "completed":
655
+ return "stop";
656
+ case "incomplete":
657
+ return "length";
658
+ case "failed":
659
+ case "cancelled":
660
+ return "error";
661
+ case "in_progress":
662
+ case "queued":
663
+ return "stop";
664
+ default: {
665
+ const exhaustive: never = status;
666
+ throw new Error(`Unhandled stop reason: ${exhaustive}`);
667
+ }
668
+ }
669
+ }
670
+
671
+ /** Initial empty `AssistantMessage` that streaming providers accumulate into. */
672
+ export function createInitialResponsesAssistantMessage(api: Api, provider: string, modelId: string): AssistantMessage {
673
+ return {
674
+ role: "assistant",
675
+ content: [],
676
+ api,
677
+ provider,
678
+ model: modelId,
679
+ usage: {
680
+ input: 0,
681
+ output: 0,
682
+ cacheRead: 0,
683
+ cacheWrite: 0,
684
+ totalTokens: 0,
685
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
686
+ },
687
+ stopReason: "stop",
688
+ timestamp: Date.now(),
689
+ };
690
+ }
691
+
692
+ /** Extension fields we add on top of `ResponseCreateParamsStreaming` across the Responses-family providers. */
693
+ export type ResponsesSamplingParamsExtras = {
694
+ top_p?: number;
695
+ top_k?: number;
696
+ min_p?: number;
697
+ presence_penalty?: number;
698
+ repetition_penalty?: number;
699
+ };
700
+
701
+ type CommonResponsesParams = OpenAI.Responses.ResponseCreateParamsStreaming & ResponsesSamplingParamsExtras;
702
+
703
+ type CommonSamplingOptions = Pick<
704
+ StreamOptions,
705
+ "temperature" | "topP" | "topK" | "minP" | "presencePenalty" | "repetitionPenalty" | "maxTokens"
706
+ > & { serviceTier?: ServiceTier };
707
+
708
+ /**
709
+ * Apply the common `StreamOptions` → Responses sampling-parameter mapping (max output tokens,
710
+ * temperature, top-p/k, min-p, presence/repetition penalties, service tier). Mutates `params`.
711
+ */
712
+ export function applyCommonResponsesSamplingParams<P extends CommonResponsesParams>(
713
+ params: P,
714
+ options: CommonSamplingOptions | undefined,
715
+ provider: string,
716
+ ): void {
717
+ if (options?.maxTokens) params.max_output_tokens = options.maxTokens;
718
+ if (options?.temperature !== undefined) params.temperature = options.temperature;
719
+ if (options?.topP !== undefined) params.top_p = options.topP;
720
+ if (options?.topK !== undefined) params.top_k = options.topK;
721
+ if (options?.minP !== undefined) params.min_p = options.minP;
722
+ if (options?.presencePenalty !== undefined) params.presence_penalty = options.presencePenalty;
723
+ if (options?.repetitionPenalty !== undefined) params.repetition_penalty = options.repetitionPenalty;
724
+ if (shouldSendServiceTier(options?.serviceTier, provider)) {
725
+ const resolved = resolveServiceTier(options?.serviceTier, provider);
726
+ if (resolved === "flex" || resolved === "scale" || resolved === "priority") {
727
+ params.service_tier = resolved;
728
+ }
729
+ }
730
+ }
731
+
732
+ type ReasoningOptions = {
733
+ reasoning?: string;
734
+ reasoningSummary?: "auto" | "detailed" | "concise" | null;
735
+ };
736
+
737
+ /**
738
+ * Apply reasoning-related Responses parameters: enable encrypted reasoning content for replay,
739
+ * set effort/summary when requested, and otherwise inject the GPT-5 "Juice: 0" no-reasoning hack.
740
+ * Mutates `params` and may push a developer message into `messages`.
741
+ */
742
+ export function applyResponsesReasoningParams<P extends OpenAI.Responses.ResponseCreateParamsStreaming>(
743
+ params: P,
744
+ model: Model<Api>,
745
+ options: ReasoningOptions | undefined,
746
+ messages: ResponseInput,
747
+ mapEffort?: (effort: string) => string,
748
+ ): void {
749
+ if (!model.reasoning) return;
750
+ // Always request encrypted reasoning content so reasoning items can be replayed in
751
+ // multi-turn conversations when store is false (items aren't persisted server-side, so
752
+ // we must include the full content). See: https://github.com/can1357/gajae-code/issues/41
753
+ params.include = ["reasoning.encrypted_content"];
754
+
755
+ if (options?.reasoning || options?.reasoningSummary !== undefined) {
756
+ const requested = options?.reasoning || "medium";
757
+ type ReasoningParam = NonNullable<OpenAI.Responses.ResponseCreateParamsStreaming["reasoning"]>;
758
+ const reasoningParams: ReasoningParam = {
759
+ effort: (mapEffort ? mapEffort(requested) : requested) as ReasoningParam["effort"],
760
+ };
761
+ if (options?.reasoningSummary !== null) {
762
+ reasoningParams.summary = options?.reasoningSummary || "auto";
763
+ }
764
+ params.reasoning = reasoningParams as P["reasoning"];
765
+ } else if (model.name.toLowerCase().startsWith("gpt-5")) {
766
+ // Jesus Christ, see https://community.openai.com/t/need-reasoning-false-option-for-gpt-5/1351588/7
767
+ messages.push({
768
+ role: "developer",
769
+ content: [{ type: "input_text", text: "# Juice: 0 !important" }],
770
+ });
771
+ }
772
+ }
773
+
774
+ /** Populate `output.usage` from a Responses-API `response.usage` payload. Does not invoke `calculateCost`. */
775
+ export function populateResponsesUsageFromResponse(
776
+ output: AssistantMessage,
777
+ usage:
778
+ | {
779
+ input_tokens?: number | null;
780
+ output_tokens?: number | null;
781
+ total_tokens?: number | null;
782
+ input_tokens_details?: { cached_tokens?: number | null } | null;
783
+ output_tokens_details?: { reasoning_tokens?: number | null } | null;
784
+ }
785
+ | null
786
+ | undefined,
787
+ ): void {
788
+ if (!usage) return;
789
+ const cachedTokens = usage.input_tokens_details?.cached_tokens || 0;
790
+ const reasoningTokens = usage.output_tokens_details?.reasoning_tokens || 0;
791
+ output.usage = {
792
+ input: (usage.input_tokens || 0) - cachedTokens,
793
+ output: usage.output_tokens || 0,
794
+ cacheRead: cachedTokens,
795
+ cacheWrite: 0,
796
+ totalTokens: usage.total_tokens || 0,
797
+ ...(reasoningTokens > 0 ? { reasoningTokens } : {}),
798
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
799
+ };
800
+ }