@vib-rato/ai 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (525) hide show
  1. package/CHANGELOG.md +3354 -0
  2. package/README.md +1194 -0
  3. package/dist/types/adapter-internals/provider-safety-stop.d.ts +45 -0
  4. package/dist/types/api-registry.d.ts +30 -0
  5. package/dist/types/auth-broker/client.d.ts +80 -0
  6. package/dist/types/auth-broker/index.d.ts +5 -0
  7. package/dist/types/auth-broker/redact.d.ts +7 -0
  8. package/dist/types/auth-broker/refresher.d.ts +25 -0
  9. package/dist/types/auth-broker/remote-store.d.ts +153 -0
  10. package/dist/types/auth-broker/server.d.ts +32 -0
  11. package/dist/types/auth-broker/types.d.ts +132 -0
  12. package/dist/types/auth-broker/wire-schemas.d.ts +555 -0
  13. package/dist/types/auth-gateway/http.d.ts +40 -0
  14. package/dist/types/auth-gateway/index.d.ts +3 -0
  15. package/dist/types/auth-gateway/server.d.ts +70 -0
  16. package/dist/types/auth-gateway/types.d.ts +129 -0
  17. package/dist/types/auth-storage.d.ts +1074 -0
  18. package/dist/types/cli.d.ts +2 -0
  19. package/dist/types/codex-tools.d.ts +4 -0
  20. package/dist/types/context-cap-policy.d.ts +68 -0
  21. package/dist/types/core.d.ts +35 -0
  22. package/dist/types/index.d.ts +55 -0
  23. package/dist/types/model-cache.d.ts +24 -0
  24. package/dist/types/model-manager.d.ts +77 -0
  25. package/dist/types/model-pricing.d.ts +3 -0
  26. package/dist/types/model-retirements.d.ts +6 -0
  27. package/dist/types/model-thinking.d.ts +100 -0
  28. package/dist/types/models.d.ts +21 -0
  29. package/dist/types/openai-completions-compat.d.ts +34 -0
  30. package/dist/types/provider-details.d.ts +24 -0
  31. package/dist/types/provider-models/bundled-references.d.ts +4 -0
  32. package/dist/types/provider-models/descriptors.d.ts +48 -0
  33. package/dist/types/provider-models/google.d.ts +20 -0
  34. package/dist/types/provider-models/index.d.ts +5 -0
  35. package/dist/types/provider-models/ollama.d.ts +7 -0
  36. package/dist/types/provider-models/openai-compat.d.ts +293 -0
  37. package/dist/types/provider-models/special.d.ts +29 -0
  38. package/dist/types/providers/amazon-bedrock.d.ts +60 -0
  39. package/dist/types/providers/anthropic-messages-server-schema.d.ts +450 -0
  40. package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
  41. package/dist/types/providers/anthropic.d.ts +280 -0
  42. package/dist/types/providers/aws-credential-config.d.ts +19 -0
  43. package/dist/types/providers/aws-credentials.d.ts +43 -0
  44. package/dist/types/providers/aws-eventstream.d.ts +38 -0
  45. package/dist/types/providers/aws-sigv4.d.ts +55 -0
  46. package/dist/types/providers/azure-openai-responses.d.ts +22 -0
  47. package/dist/types/providers/composer-discipline.d.ts +32 -0
  48. package/dist/types/providers/cursor/client-version.d.ts +10 -0
  49. package/dist/types/providers/cursor/exec-modern.d.ts +98 -0
  50. package/dist/types/providers/cursor/gen/agent_pb.d.ts +16769 -0
  51. package/dist/types/providers/cursor-pi-args.d.ts +119 -0
  52. package/dist/types/providers/cursor.d.ts +72 -0
  53. package/dist/types/providers/dashscope-token-plan-headers.d.ts +57 -0
  54. package/dist/types/providers/error-message.d.ts +27 -0
  55. package/dist/types/providers/github-copilot-headers.d.ts +40 -0
  56. package/dist/types/providers/gitlab-duo.d.ts +27 -0
  57. package/dist/types/providers/google-auth.d.ts +26 -0
  58. package/dist/types/providers/google-gemini-cli.d.ts +75 -0
  59. package/dist/types/providers/google-gemini-headers.d.ts +43 -0
  60. package/dist/types/providers/google-shared.d.ts +183 -0
  61. package/dist/types/providers/google-types.d.ts +138 -0
  62. package/dist/types/providers/google-vertex.d.ts +11 -0
  63. package/dist/types/providers/google.d.ts +4 -0
  64. package/dist/types/providers/grammar.d.ts +1 -0
  65. package/dist/types/providers/kimi.d.ts +27 -0
  66. package/dist/types/providers/kiro-api-key.d.ts +50 -0
  67. package/dist/types/providers/kiro-codewhisperer.d.ts +11 -0
  68. package/dist/types/providers/mock.d.ts +189 -0
  69. package/dist/types/providers/ollama.d.ts +41 -0
  70. package/dist/types/providers/openai-anthropic-shim.d.ts +31 -0
  71. package/dist/types/providers/openai-bounded-rate-limits.d.ts +3 -0
  72. package/dist/types/providers/openai-chat-server-schema.d.ts +1733 -0
  73. package/dist/types/providers/openai-chat-server.d.ts +16 -0
  74. package/dist/types/providers/openai-codex/constants.d.ts +26 -0
  75. package/dist/types/providers/openai-codex/request-transformer.d.ts +50 -0
  76. package/dist/types/providers/openai-codex/response-handler.d.ts +18 -0
  77. package/dist/types/providers/openai-codex-responses.d.ts +71 -0
  78. package/dist/types/providers/openai-completions-compat.d.ts +6 -0
  79. package/dist/types/providers/openai-completions.d.ts +35 -0
  80. package/dist/types/providers/openai-opencodex-responses.d.ts +10 -0
  81. package/dist/types/providers/openai-request-transform.d.ts +4 -0
  82. package/dist/types/providers/openai-responses-server-schema.d.ts +392 -0
  83. package/dist/types/providers/openai-responses-server.d.ts +17 -0
  84. package/dist/types/providers/openai-responses-shared.d.ts +106 -0
  85. package/dist/types/providers/openai-responses.d.ts +37 -0
  86. package/dist/types/providers/pi-native-client.d.ts +29 -0
  87. package/dist/types/providers/pi-native-server.d.ts +60 -0
  88. package/dist/types/providers/register-builtins.d.ts +59 -0
  89. package/dist/types/providers/synthetic.d.ts +26 -0
  90. package/dist/types/providers/transform-messages.d.ts +33 -0
  91. package/dist/types/providers/vision-guard.d.ts +8 -0
  92. package/dist/types/rate-limit-utils.d.ts +19 -0
  93. package/dist/types/stream.d.ts +45 -0
  94. package/dist/types/types.d.ts +1073 -0
  95. package/dist/types/usage/claude.d.ts +3 -0
  96. package/dist/types/usage/gemini.d.ts +2 -0
  97. package/dist/types/usage/github-copilot.d.ts +7 -0
  98. package/dist/types/usage/google-antigravity.d.ts +2 -0
  99. package/dist/types/usage/grok-cli.d.ts +17 -0
  100. package/dist/types/usage/kimi.d.ts +4 -0
  101. package/dist/types/usage/minimax-code.d.ts +2 -0
  102. package/dist/types/usage/openai-codex.d.ts +3 -0
  103. package/dist/types/usage/shared.d.ts +1 -0
  104. package/dist/types/usage/zai.d.ts +2 -0
  105. package/dist/types/usage.d.ts +264 -0
  106. package/dist/types/utils/abort.d.ts +19 -0
  107. package/dist/types/utils/anthropic-auth.d.ts +39 -0
  108. package/dist/types/utils/block-symbols.d.ts +6 -0
  109. package/dist/types/utils/discovery/antigravity.d.ts +67 -0
  110. package/dist/types/utils/discovery/codex.d.ts +38 -0
  111. package/dist/types/utils/discovery/cursor.d.ts +49 -0
  112. package/dist/types/utils/discovery/gemini.d.ts +25 -0
  113. package/dist/types/utils/discovery/index.d.ts +4 -0
  114. package/dist/types/utils/discovery/openai-compatible.d.ts +81 -0
  115. package/dist/types/utils/event-stream.d.ts +41 -0
  116. package/dist/types/utils/fallback-transport.d.ts +110 -0
  117. package/dist/types/utils/fireworks-model-id.d.ts +10 -0
  118. package/dist/types/utils/foundry.d.ts +11 -0
  119. package/dist/types/utils/h2-fetch.d.ts +22 -0
  120. package/dist/types/utils/http-inspector.d.ts +59 -0
  121. package/dist/types/utils/idle-iterator.d.ts +122 -0
  122. package/dist/types/utils/json-parse.d.ts +98 -0
  123. package/dist/types/utils/oauth/alibaba-token-plan.d.ts +19 -0
  124. package/dist/types/utils/oauth/anthropic.d.ts +41 -0
  125. package/dist/types/utils/oauth/api-key-login.d.ts +38 -0
  126. package/dist/types/utils/oauth/api-key-validation.d.ts +39 -0
  127. package/dist/types/utils/oauth/bizrouter.d.ts +1 -0
  128. package/dist/types/utils/oauth/callback-server.d.ts +80 -0
  129. package/dist/types/utils/oauth/cerebras.d.ts +1 -0
  130. package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +18 -0
  131. package/dist/types/utils/oauth/commandcode.d.ts +1 -0
  132. package/dist/types/utils/oauth/cursor.d.ts +15 -0
  133. package/dist/types/utils/oauth/deepinfra.d.ts +1 -0
  134. package/dist/types/utils/oauth/deepseek.d.ts +10 -0
  135. package/dist/types/utils/oauth/firepass.d.ts +1 -0
  136. package/dist/types/utils/oauth/fireworks.d.ts +1 -0
  137. package/dist/types/utils/oauth/fugu.d.ts +1 -0
  138. package/dist/types/utils/oauth/github-copilot.d.ts +38 -0
  139. package/dist/types/utils/oauth/gitlab-duo.d.ts +3 -0
  140. package/dist/types/utils/oauth/glm-zcode.d.ts +71 -0
  141. package/dist/types/utils/oauth/google-antigravity.d.ts +11 -0
  142. package/dist/types/utils/oauth/google-gemini-cli.d.ts +10 -0
  143. package/dist/types/utils/oauth/google-oauth-shared.d.ts +28 -0
  144. package/dist/types/utils/oauth/huggingface.d.ts +19 -0
  145. package/dist/types/utils/oauth/index.d.ts +39 -0
  146. package/dist/types/utils/oauth/kagi.d.ts +17 -0
  147. package/dist/types/utils/oauth/kilo.d.ts +5 -0
  148. package/dist/types/utils/oauth/kimi.d.ts +17 -0
  149. package/dist/types/utils/oauth/kiro.d.ts +71 -0
  150. package/dist/types/utils/oauth/litellm.d.ts +18 -0
  151. package/dist/types/utils/oauth/lm-studio.d.ts +17 -0
  152. package/dist/types/utils/oauth/mara.d.ts +1 -0
  153. package/dist/types/utils/oauth/minimax-code.d.ts +28 -0
  154. package/dist/types/utils/oauth/moonshot.d.ts +1 -0
  155. package/dist/types/utils/oauth/nanogpt.d.ts +1 -0
  156. package/dist/types/utils/oauth/nvidia.d.ts +18 -0
  157. package/dist/types/utils/oauth/ollama-cloud.d.ts +2 -0
  158. package/dist/types/utils/oauth/ollama.d.ts +18 -0
  159. package/dist/types/utils/oauth/openai-codex.d.ts +21 -0
  160. package/dist/types/utils/oauth/opencode.d.ts +18 -0
  161. package/dist/types/utils/oauth/opengateway.d.ts +1 -0
  162. package/dist/types/utils/oauth/openrouter.d.ts +1 -0
  163. package/dist/types/utils/oauth/parallel.d.ts +17 -0
  164. package/dist/types/utils/oauth/perplexity.d.ts +4 -0
  165. package/dist/types/utils/oauth/pkce.d.ts +8 -0
  166. package/dist/types/utils/oauth/qianfan.d.ts +17 -0
  167. package/dist/types/utils/oauth/qwen-portal.d.ts +19 -0
  168. package/dist/types/utils/oauth/sglang.d.ts +16 -0
  169. package/dist/types/utils/oauth/synthetic.d.ts +1 -0
  170. package/dist/types/utils/oauth/tavily.d.ts +17 -0
  171. package/dist/types/utils/oauth/together.d.ts +1 -0
  172. package/dist/types/utils/oauth/types.d.ts +56 -0
  173. package/dist/types/utils/oauth/venice.d.ts +18 -0
  174. package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +18 -0
  175. package/dist/types/utils/oauth/vllm.d.ts +16 -0
  176. package/dist/types/utils/oauth/xai.d.ts +30 -0
  177. package/dist/types/utils/oauth/xiaomi.d.ts +25 -0
  178. package/dist/types/utils/oauth/zai.d.ts +18 -0
  179. package/dist/types/utils/oauth/zenmux.d.ts +1 -0
  180. package/dist/types/utils/overflow.d.ts +14 -0
  181. package/dist/types/utils/parse-bind.d.ts +26 -0
  182. package/dist/types/utils/provider-response.d.ts +6 -0
  183. package/dist/types/utils/provider-safety-stop.d.ts +8 -0
  184. package/dist/types/utils/proxy.d.ts +7 -0
  185. package/dist/types/utils/retry-after.d.ts +3 -0
  186. package/dist/types/utils/retry-budget.d.ts +1 -0
  187. package/dist/types/utils/retry.d.ts +29 -0
  188. package/dist/types/utils/schema/adapt.d.ts +24 -0
  189. package/dist/types/utils/schema/compatibility.d.ts +30 -0
  190. package/dist/types/utils/schema/dereference.d.ts +11 -0
  191. package/dist/types/utils/schema/draft.d.ts +10 -0
  192. package/dist/types/utils/schema/equality.d.ts +4 -0
  193. package/dist/types/utils/schema/fields.d.ts +49 -0
  194. package/dist/types/utils/schema/index.d.ts +14 -0
  195. package/dist/types/utils/schema/json-schema-validator.d.ts +12 -0
  196. package/dist/types/utils/schema/meta-validator.d.ts +2 -0
  197. package/dist/types/utils/schema/normalize.d.ts +93 -0
  198. package/dist/types/utils/schema/root-combinator.d.ts +12 -0
  199. package/dist/types/utils/schema/spill.d.ts +8 -0
  200. package/dist/types/utils/schema/stamps.d.ts +25 -0
  201. package/dist/types/utils/schema/types.d.ts +4 -0
  202. package/dist/types/utils/schema/wire.d.ts +54 -0
  203. package/dist/types/utils/schema/zod-decontaminate.d.ts +31 -0
  204. package/dist/types/utils/sse-debug.d.ts +10 -0
  205. package/dist/types/utils/tool-call-healing.d.ts +80 -0
  206. package/dist/types/utils/tool-choice-capability.d.ts +57 -0
  207. package/dist/types/utils/tool-choice.d.ts +50 -0
  208. package/dist/types/utils/validation.d.ts +17 -0
  209. package/dist/types/utils.d.ts +117 -0
  210. package/package.json +152 -0
  211. package/src/adapter-internals/provider-safety-stop.d.ts +45 -0
  212. package/src/adapter-internals/provider-safety-stop.ts +156 -0
  213. package/src/api-registry.d.ts +30 -0
  214. package/src/api-registry.ts +96 -0
  215. package/src/auth-broker/client.ts +444 -0
  216. package/src/auth-broker/index.ts +5 -0
  217. package/src/auth-broker/redact.ts +39 -0
  218. package/src/auth-broker/refresher.ts +130 -0
  219. package/src/auth-broker/remote-store.ts +1576 -0
  220. package/src/auth-broker/server.ts +764 -0
  221. package/src/auth-broker/types.ts +164 -0
  222. package/src/auth-broker/wire-schemas.ts +261 -0
  223. package/src/auth-gateway/http.ts +198 -0
  224. package/src/auth-gateway/index.ts +3 -0
  225. package/src/auth-gateway/server.ts +1315 -0
  226. package/src/auth-gateway/types.ts +160 -0
  227. package/src/auth-storage.ts +7312 -0
  228. package/src/cli.ts +269 -0
  229. package/src/codex-tools.d.ts +4 -0
  230. package/src/codex-tools.ts +24 -0
  231. package/src/context-cap-policy.d.ts +68 -0
  232. package/src/context-cap-policy.ts +123 -0
  233. package/src/core.ts +44 -0
  234. package/src/index.ts +61 -0
  235. package/src/model-cache.ts +236 -0
  236. package/src/model-manager.ts +744 -0
  237. package/src/model-pricing.d.ts +3 -0
  238. package/src/model-pricing.ts +68 -0
  239. package/src/model-retirements.d.ts +6 -0
  240. package/src/model-retirements.ts +19 -0
  241. package/src/model-thinking.d.ts +100 -0
  242. package/src/model-thinking.ts +1054 -0
  243. package/src/models.d.ts +21 -0
  244. package/src/models.json +94672 -0
  245. package/src/models.json.d.ts +9 -0
  246. package/src/models.ts +126 -0
  247. package/src/openai-completions-compat.d.ts +34 -0
  248. package/src/openai-completions-compat.ts +383 -0
  249. package/src/prompts/composer-bash-policy-recovery.md +1 -0
  250. package/src/prompts/cursor-composer-bash-policy-recovery.md +1 -0
  251. package/src/prompts/cursor-composer-edit-discipline.md +7 -0
  252. package/src/prompts/turn-aborted-guidance.md +4 -0
  253. package/src/provider-details.ts +90 -0
  254. package/src/provider-models/bundled-references.ts +38 -0
  255. package/src/provider-models/descriptors.ts +392 -0
  256. package/src/provider-models/google.ts +92 -0
  257. package/src/provider-models/index.ts +5 -0
  258. package/src/provider-models/ollama.ts +159 -0
  259. package/src/provider-models/openai-compat.ts +2920 -0
  260. package/src/provider-models/special.ts +185 -0
  261. package/src/providers/amazon-bedrock.d.ts +60 -0
  262. package/src/providers/amazon-bedrock.ts +939 -0
  263. package/src/providers/anthropic-messages-server-schema.ts +229 -0
  264. package/src/providers/anthropic-messages-server.ts +839 -0
  265. package/src/providers/anthropic.d.ts +280 -0
  266. package/src/providers/anthropic.ts +4421 -0
  267. package/src/providers/aws-credential-config.d.ts +19 -0
  268. package/src/providers/aws-credential-config.ts +179 -0
  269. package/src/providers/aws-credentials.d.ts +43 -0
  270. package/src/providers/aws-credentials.ts +457 -0
  271. package/src/providers/aws-eventstream.d.ts +38 -0
  272. package/src/providers/aws-eventstream.ts +185 -0
  273. package/src/providers/aws-sigv4.d.ts +55 -0
  274. package/src/providers/aws-sigv4.ts +218 -0
  275. package/src/providers/azure-openai-responses.d.ts +22 -0
  276. package/src/providers/azure-openai-responses.ts +447 -0
  277. package/src/providers/composer-discipline.d.ts +32 -0
  278. package/src/providers/composer-discipline.ts +95 -0
  279. package/src/providers/cursor/client-version.d.ts +10 -0
  280. package/src/providers/cursor/client-version.ts +10 -0
  281. package/src/providers/cursor/exec-modern.d.ts +98 -0
  282. package/src/providers/cursor/exec-modern.ts +497 -0
  283. package/src/providers/cursor/gen/agent_pb.d.ts +16769 -0
  284. package/src/providers/cursor/gen/agent_pb.ts +19780 -0
  285. package/src/providers/cursor/proto/agent.proto +4533 -0
  286. package/src/providers/cursor/proto/buf.gen.yaml +6 -0
  287. package/src/providers/cursor/proto/buf.yaml +17 -0
  288. package/src/providers/cursor-pi-args.d.ts +119 -0
  289. package/src/providers/cursor-pi-args.ts +187 -0
  290. package/src/providers/cursor.d.ts +72 -0
  291. package/src/providers/cursor.ts +3396 -0
  292. package/src/providers/dashscope-token-plan-headers.d.ts +57 -0
  293. package/src/providers/dashscope-token-plan-headers.ts +84 -0
  294. package/src/providers/error-message.d.ts +27 -0
  295. package/src/providers/error-message.ts +21 -0
  296. package/src/providers/github-copilot-headers.d.ts +40 -0
  297. package/src/providers/github-copilot-headers.ts +140 -0
  298. package/src/providers/gitlab-duo.d.ts +27 -0
  299. package/src/providers/gitlab-duo.ts +393 -0
  300. package/src/providers/google-auth.d.ts +26 -0
  301. package/src/providers/google-auth.ts +262 -0
  302. package/src/providers/google-gemini-cli.d.ts +75 -0
  303. package/src/providers/google-gemini-cli.ts +969 -0
  304. package/src/providers/google-gemini-headers.d.ts +43 -0
  305. package/src/providers/google-gemini-headers.ts +100 -0
  306. package/src/providers/google-shared.d.ts +183 -0
  307. package/src/providers/google-shared.ts +1105 -0
  308. package/src/providers/google-types.d.ts +138 -0
  309. package/src/providers/google-types.ts +167 -0
  310. package/src/providers/google-vertex.d.ts +11 -0
  311. package/src/providers/google-vertex.ts +124 -0
  312. package/src/providers/google.d.ts +4 -0
  313. package/src/providers/google.ts +41 -0
  314. package/src/providers/grammar.d.ts +1 -0
  315. package/src/providers/grammar.ts +70 -0
  316. package/src/providers/kimi.d.ts +27 -0
  317. package/src/providers/kimi.ts +52 -0
  318. package/src/providers/kiro-api-key.d.ts +50 -0
  319. package/src/providers/kiro-api-key.ts +786 -0
  320. package/src/providers/kiro-codewhisperer.d.ts +11 -0
  321. package/src/providers/kiro-codewhisperer.ts +600 -0
  322. package/src/providers/mock.ts +526 -0
  323. package/src/providers/ollama.d.ts +41 -0
  324. package/src/providers/ollama.ts +645 -0
  325. package/src/providers/openai-anthropic-shim.d.ts +31 -0
  326. package/src/providers/openai-anthropic-shim.ts +156 -0
  327. package/src/providers/openai-bounded-rate-limits.d.ts +3 -0
  328. package/src/providers/openai-bounded-rate-limits.ts +57 -0
  329. package/src/providers/openai-chat-server-schema.ts +254 -0
  330. package/src/providers/openai-chat-server.ts +724 -0
  331. package/src/providers/openai-codex/constants.d.ts +26 -0
  332. package/src/providers/openai-codex/constants.ts +43 -0
  333. package/src/providers/openai-codex/request-transformer.d.ts +50 -0
  334. package/src/providers/openai-codex/request-transformer.ts +219 -0
  335. package/src/providers/openai-codex/response-handler.d.ts +18 -0
  336. package/src/providers/openai-codex/response-handler.ts +111 -0
  337. package/src/providers/openai-codex-responses.d.ts +71 -0
  338. package/src/providers/openai-codex-responses.ts +3288 -0
  339. package/src/providers/openai-completions-compat.d.ts +6 -0
  340. package/src/providers/openai-completions-compat.ts +6 -0
  341. package/src/providers/openai-completions.d.ts +35 -0
  342. package/src/providers/openai-completions.ts +2294 -0
  343. package/src/providers/openai-opencodex-responses.ts +174 -0
  344. package/src/providers/openai-request-transform.d.ts +4 -0
  345. package/src/providers/openai-request-transform.ts +136 -0
  346. package/src/providers/openai-responses-server-schema.ts +290 -0
  347. package/src/providers/openai-responses-server.ts +1268 -0
  348. package/src/providers/openai-responses-shared.d.ts +106 -0
  349. package/src/providers/openai-responses-shared.ts +1253 -0
  350. package/src/providers/openai-responses.d.ts +37 -0
  351. package/src/providers/openai-responses.ts +989 -0
  352. package/src/providers/pi-native-client.d.ts +29 -0
  353. package/src/providers/pi-native-client.ts +243 -0
  354. package/src/providers/pi-native-server.ts +488 -0
  355. package/src/providers/register-builtins.d.ts +59 -0
  356. package/src/providers/register-builtins.ts +544 -0
  357. package/src/providers/synthetic.d.ts +26 -0
  358. package/src/providers/synthetic.ts +50 -0
  359. package/src/providers/transform-messages.d.ts +33 -0
  360. package/src/providers/transform-messages.ts +408 -0
  361. package/src/providers/vision-guard.d.ts +8 -0
  362. package/src/providers/vision-guard.ts +31 -0
  363. package/src/rate-limit-utils.d.ts +19 -0
  364. package/src/rate-limit-utils.ts +102 -0
  365. package/src/stream.d.ts +45 -0
  366. package/src/stream.ts +1306 -0
  367. package/src/types.d.ts +1073 -0
  368. package/src/types.ts +1305 -0
  369. package/src/usage/claude.ts +449 -0
  370. package/src/usage/gemini.ts +250 -0
  371. package/src/usage/github-copilot.ts +421 -0
  372. package/src/usage/google-antigravity.ts +201 -0
  373. package/src/usage/grok-cli.ts +259 -0
  374. package/src/usage/kimi.ts +285 -0
  375. package/src/usage/minimax-code.ts +31 -0
  376. package/src/usage/openai-codex.ts +503 -0
  377. package/src/usage/shared.ts +10 -0
  378. package/src/usage/zai.ts +247 -0
  379. package/src/usage.ts +190 -0
  380. package/src/utils/abort.d.ts +19 -0
  381. package/src/utils/abort.ts +51 -0
  382. package/src/utils/anthropic-auth.ts +95 -0
  383. package/src/utils/block-symbols.d.ts +6 -0
  384. package/src/utils/block-symbols.ts +11 -0
  385. package/src/utils/discovery/antigravity.ts +275 -0
  386. package/src/utils/discovery/codex.ts +362 -0
  387. package/src/utils/discovery/cursor.ts +388 -0
  388. package/src/utils/discovery/gemini.ts +248 -0
  389. package/src/utils/discovery/index.ts +4 -0
  390. package/src/utils/discovery/openai-compatible.ts +379 -0
  391. package/src/utils/event-stream.d.ts +41 -0
  392. package/src/utils/event-stream.ts +269 -0
  393. package/src/utils/fallback-transport.d.ts +110 -0
  394. package/src/utils/fallback-transport.ts +411 -0
  395. package/src/utils/fireworks-model-id.d.ts +10 -0
  396. package/src/utils/fireworks-model-id.ts +30 -0
  397. package/src/utils/foundry.d.ts +11 -0
  398. package/src/utils/foundry.ts +18 -0
  399. package/src/utils/h2-fetch.ts +60 -0
  400. package/src/utils/http-inspector.d.ts +59 -0
  401. package/src/utils/http-inspector.ts +380 -0
  402. package/src/utils/idle-iterator.d.ts +122 -0
  403. package/src/utils/idle-iterator.ts +410 -0
  404. package/src/utils/json-parse.d.ts +98 -0
  405. package/src/utils/json-parse.ts +607 -0
  406. package/src/utils/oauth/alibaba-token-plan.ts +60 -0
  407. package/src/utils/oauth/anthropic.ts +233 -0
  408. package/src/utils/oauth/api-key-login.ts +98 -0
  409. package/src/utils/oauth/api-key-validation.ts +344 -0
  410. package/src/utils/oauth/bizrouter.ts +15 -0
  411. package/src/utils/oauth/callback-server.d.ts +80 -0
  412. package/src/utils/oauth/callback-server.ts +359 -0
  413. package/src/utils/oauth/cerebras.ts +16 -0
  414. package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
  415. package/src/utils/oauth/commandcode.ts +17 -0
  416. package/src/utils/oauth/cursor.ts +157 -0
  417. package/src/utils/oauth/deepinfra.ts +15 -0
  418. package/src/utils/oauth/deepseek.ts +53 -0
  419. package/src/utils/oauth/firepass.ts +24 -0
  420. package/src/utils/oauth/fireworks.ts +15 -0
  421. package/src/utils/oauth/fugu.ts +15 -0
  422. package/src/utils/oauth/github-copilot.d.ts +38 -0
  423. package/src/utils/oauth/github-copilot.ts +362 -0
  424. package/src/utils/oauth/gitlab-duo.ts +123 -0
  425. package/src/utils/oauth/glm-zcode.d.ts +71 -0
  426. package/src/utils/oauth/glm-zcode.ts +433 -0
  427. package/src/utils/oauth/google-antigravity.ts +200 -0
  428. package/src/utils/oauth/google-gemini-cli.ts +256 -0
  429. package/src/utils/oauth/google-oauth-shared.ts +110 -0
  430. package/src/utils/oauth/huggingface.ts +62 -0
  431. package/src/utils/oauth/index.ts +558 -0
  432. package/src/utils/oauth/kagi.ts +47 -0
  433. package/src/utils/oauth/kilo.ts +87 -0
  434. package/src/utils/oauth/kimi.d.ts +17 -0
  435. package/src/utils/oauth/kimi.ts +275 -0
  436. package/src/utils/oauth/kiro.ts +448 -0
  437. package/src/utils/oauth/litellm.ts +47 -0
  438. package/src/utils/oauth/lm-studio.ts +38 -0
  439. package/src/utils/oauth/mara.ts +16 -0
  440. package/src/utils/oauth/minimax-code.ts +78 -0
  441. package/src/utils/oauth/moonshot.ts +16 -0
  442. package/src/utils/oauth/nanogpt.ts +15 -0
  443. package/src/utils/oauth/nvidia.ts +70 -0
  444. package/src/utils/oauth/oauth.html +199 -0
  445. package/src/utils/oauth/ollama-cloud.ts +28 -0
  446. package/src/utils/oauth/ollama.ts +47 -0
  447. package/src/utils/oauth/openai-codex.ts +299 -0
  448. package/src/utils/oauth/opencode.ts +49 -0
  449. package/src/utils/oauth/opengateway.ts +15 -0
  450. package/src/utils/oauth/openrouter.ts +16 -0
  451. package/src/utils/oauth/parallel.ts +46 -0
  452. package/src/utils/oauth/perplexity.ts +225 -0
  453. package/src/utils/oauth/pkce.ts +18 -0
  454. package/src/utils/oauth/qianfan.ts +58 -0
  455. package/src/utils/oauth/qwen-portal.ts +60 -0
  456. package/src/utils/oauth/sglang.ts +42 -0
  457. package/src/utils/oauth/synthetic.ts +15 -0
  458. package/src/utils/oauth/tavily.ts +46 -0
  459. package/src/utils/oauth/together.ts +16 -0
  460. package/src/utils/oauth/types.d.ts +56 -0
  461. package/src/utils/oauth/types.ts +122 -0
  462. package/src/utils/oauth/venice.ts +59 -0
  463. package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
  464. package/src/utils/oauth/vllm.ts +42 -0
  465. package/src/utils/oauth/xai.ts +246 -0
  466. package/src/utils/oauth/xiaomi.ts +199 -0
  467. package/src/utils/oauth/zai.ts +60 -0
  468. package/src/utils/oauth/zenmux.ts +15 -0
  469. package/src/utils/overflow.ts +275 -0
  470. package/src/utils/parse-bind.ts +81 -0
  471. package/src/utils/provider-response.d.ts +6 -0
  472. package/src/utils/provider-response.ts +30 -0
  473. package/src/utils/provider-safety-stop.ts +8 -0
  474. package/src/utils/proxy.d.ts +7 -0
  475. package/src/utils/proxy.ts +652 -0
  476. package/src/utils/retry-after.d.ts +3 -0
  477. package/src/utils/retry-after.ts +110 -0
  478. package/src/utils/retry-budget.d.ts +1 -0
  479. package/src/utils/retry-budget.ts +4 -0
  480. package/src/utils/retry.d.ts +29 -0
  481. package/src/utils/retry.ts +67 -0
  482. package/src/utils/schema/CONSTRAINTS.md +164 -0
  483. package/src/utils/schema/adapt.d.ts +24 -0
  484. package/src/utils/schema/adapt.ts +36 -0
  485. package/src/utils/schema/compatibility.d.ts +30 -0
  486. package/src/utils/schema/compatibility.ts +435 -0
  487. package/src/utils/schema/dereference.d.ts +11 -0
  488. package/src/utils/schema/dereference.ts +98 -0
  489. package/src/utils/schema/draft.d.ts +10 -0
  490. package/src/utils/schema/draft.ts +341 -0
  491. package/src/utils/schema/equality.d.ts +4 -0
  492. package/src/utils/schema/equality.ts +97 -0
  493. package/src/utils/schema/fields.d.ts +49 -0
  494. package/src/utils/schema/fields.ts +190 -0
  495. package/src/utils/schema/index.d.ts +14 -0
  496. package/src/utils/schema/index.ts +14 -0
  497. package/src/utils/schema/json-schema-validator.d.ts +12 -0
  498. package/src/utils/schema/json-schema-validator.ts +577 -0
  499. package/src/utils/schema/meta-validator.d.ts +2 -0
  500. package/src/utils/schema/meta-validator.ts +167 -0
  501. package/src/utils/schema/normalize.d.ts +93 -0
  502. package/src/utils/schema/normalize.ts +1588 -0
  503. package/src/utils/schema/root-combinator.d.ts +12 -0
  504. package/src/utils/schema/root-combinator.ts +143 -0
  505. package/src/utils/schema/spill.d.ts +8 -0
  506. package/src/utils/schema/spill.ts +43 -0
  507. package/src/utils/schema/stamps.d.ts +25 -0
  508. package/src/utils/schema/stamps.ts +97 -0
  509. package/src/utils/schema/types.d.ts +4 -0
  510. package/src/utils/schema/types.ts +11 -0
  511. package/src/utils/schema/wire.d.ts +54 -0
  512. package/src/utils/schema/wire.ts +213 -0
  513. package/src/utils/schema/zod-decontaminate.d.ts +31 -0
  514. package/src/utils/schema/zod-decontaminate.ts +331 -0
  515. package/src/utils/sse-debug.d.ts +10 -0
  516. package/src/utils/sse-debug.ts +289 -0
  517. package/src/utils/tool-call-healing.d.ts +80 -0
  518. package/src/utils/tool-call-healing.ts +298 -0
  519. package/src/utils/tool-choice-capability.d.ts +57 -0
  520. package/src/utils/tool-choice-capability.ts +633 -0
  521. package/src/utils/tool-choice.d.ts +50 -0
  522. package/src/utils/tool-choice.ts +99 -0
  523. package/src/utils/validation.ts +1080 -0
  524. package/src/utils.d.ts +117 -0
  525. package/src/utils.ts +523 -0
@@ -0,0 +1,1253 @@
1
+ import { structuredCloneJSON } from "@vib-rato/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 { modelSupportsReasoningControl } from "../model-thinking";
15
+ import { calculateCost } from "../models";
16
+ import {
17
+ type Api,
18
+ type AssistantMessage,
19
+ type ImageContent,
20
+ type Model,
21
+ resolveServiceTier,
22
+ type ServiceTier,
23
+ type StopReason,
24
+ type StreamOptions,
25
+ shouldSendServiceTier,
26
+ type TextContent,
27
+ type TextSignatureV1,
28
+ type ThinkingContent,
29
+ type ToolCall,
30
+ type ToolResultMessage,
31
+ } from "../types";
32
+ import { normalizeResponsesToolCallId, sanitizeJsonStrings } from "../utils";
33
+ import type { AssistantMessageEventStream } from "../utils/event-stream";
34
+ import { SERVER_OVERLOADED_PROVIDER_CODE } from "../utils/fallback-transport";
35
+ import { captureUnicodeEscapeEvidence, isCompleteJson, parseStreamingJson } from "../utils/json-parse";
36
+ import { areJsonValuesEqual } from "../utils/schema";
37
+ import { joinTextWithImagePlaceholder, NON_VISION_IMAGE_PLACEHOLDER, partitionVisionContent } from "./vision-guard";
38
+
39
+ const OPENAI_RESPONSES_PROGRESS_EVENT_TYPES = new Set([
40
+ "response.created",
41
+ "response.output_item.added",
42
+ "response.reasoning_summary_part.added",
43
+ "response.reasoning_summary_text.delta",
44
+ "response.reasoning_summary_part.done",
45
+ "response.reasoning_text.delta",
46
+ "response.content_part.added",
47
+ "response.output_text.delta",
48
+ "response.refusal.delta",
49
+ "response.function_call_arguments.delta",
50
+ "response.function_call_arguments.done",
51
+ "response.custom_tool_call_input.delta",
52
+ "response.custom_tool_call_input.done",
53
+ "response.output_item.done",
54
+ "response.completed",
55
+ "response.failed",
56
+ "error",
57
+ ]);
58
+
59
+ export function isOpenAIResponsesProgressEvent(event: unknown): boolean {
60
+ if (!event || typeof event !== "object") return false;
61
+ const type = (event as { type?: unknown }).type;
62
+ return typeof type === "string" && OPENAI_RESPONSES_PROGRESS_EVENT_TYPES.has(type);
63
+ }
64
+
65
+ export function encodeTextSignatureV1(id: string, phase?: TextSignatureV1["phase"]): string {
66
+ const payload: TextSignatureV1 = { v: 1, id };
67
+ if (phase) payload.phase = phase;
68
+ return JSON.stringify(payload);
69
+ }
70
+
71
+ export function parseTextSignature(
72
+ signature: string | undefined,
73
+ ): { id: string; phase?: TextSignatureV1["phase"] } | undefined {
74
+ if (!signature) return undefined;
75
+ if (signature.startsWith("{")) {
76
+ try {
77
+ const parsed = JSON.parse(signature) as Partial<TextSignatureV1>;
78
+ if (parsed.v === 1 && typeof parsed.id === "string") {
79
+ if (parsed.phase === "commentary" || parsed.phase === "final_answer") {
80
+ return { id: parsed.id, phase: parsed.phase };
81
+ }
82
+ return { id: parsed.id };
83
+ }
84
+ } catch {
85
+ // Fall through to legacy plain-string handling.
86
+ }
87
+ }
88
+ return { id: signature };
89
+ }
90
+
91
+ export function encodeResponsesToolCallId(callId: string, itemId: string | null | undefined): string {
92
+ const stableItemId = itemId && itemId.length > 0 ? itemId : `fc_${Bun.hash(callId).toString(36)}`;
93
+ return `${callId}|${stableItemId}`;
94
+ }
95
+
96
+ export function normalizeResponsesToolCallIdForTransform(
97
+ id: string,
98
+ model?: Model<Api>,
99
+ source?: AssistantMessage,
100
+ ): string {
101
+ if (!id.includes("|")) return id;
102
+ const isForeignToolCall =
103
+ source != null && model != null && (source.provider !== model.provider || source.api !== model.api);
104
+ if (isForeignToolCall) {
105
+ const [callId, itemId] = id.split("|");
106
+ const normalizeIdPart = (part: string): string => {
107
+ const sanitized = part.replace(/[^a-zA-Z0-9_-]/g, "_");
108
+ const truncated = sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
109
+ return truncated.replace(/_+$/, "");
110
+ };
111
+ const normalizedCallId = normalizeIdPart(callId);
112
+ let normalizedItemId = `fc_${Bun.hash(itemId).toString(36)}`;
113
+ if (normalizedItemId.length > 64) normalizedItemId = normalizedItemId.slice(0, 64);
114
+ return `${normalizedCallId}|${normalizedItemId}`;
115
+ }
116
+ const normalized = normalizeResponsesToolCallId(id);
117
+ return `${normalized.callId}|${normalized.itemId}`;
118
+ }
119
+
120
+ export function collectKnownCallIds(messages: ResponseInput): Set<string> {
121
+ const knownCallIds = new Set<string>();
122
+ for (const item of messages) {
123
+ if (item.type === "function_call" && typeof item.call_id === "string") {
124
+ knownCallIds.add(item.call_id);
125
+ } else if (
126
+ (item as { type?: string }).type === "custom_tool_call" &&
127
+ typeof (item as { call_id?: string }).call_id === "string"
128
+ ) {
129
+ knownCallIds.add((item as { call_id: string }).call_id);
130
+ }
131
+ }
132
+ return knownCallIds;
133
+ }
134
+
135
+ /** Scan replay items for call_ids that were originally custom tool calls. */
136
+ export function collectCustomCallIds(messages: ResponseInput): Set<string> {
137
+ const customCallIds = new Set<string>();
138
+ for (const item of messages) {
139
+ if (
140
+ (item as { type?: string }).type === "custom_tool_call" &&
141
+ typeof (item as { call_id?: string }).call_id === "string"
142
+ ) {
143
+ customCallIds.add((item as { call_id: string }).call_id);
144
+ }
145
+ }
146
+ return customCallIds;
147
+ }
148
+
149
+ /**
150
+ * Convert orphan `function_call_output` / `custom_tool_call_output` items —
151
+ * those whose `call_id` has no matching preceding `function_call` /
152
+ * `custom_tool_call` in the same input — into assistant text notes.
153
+ *
154
+ * The Responses API rejects unpaired outputs with
155
+ * `400 No tool call found for function call output with call_id …`. Orphans
156
+ * sneak in through two paths today:
157
+ *
158
+ * - A previous turn's `providerPayload` snapshot replaces the input array via
159
+ * the `dt: false` splice (see {@link convertConversationMessages}), wiping
160
+ * the matching `function_call` while leaving the matching
161
+ * `function_call_output` queued in a later `toolResult`.
162
+ * - A locally-rejected tool call (argument-validation failure, hook reject,
163
+ * aborted turn before the call streamed) produces a tool result without a
164
+ * `function_call` ever landing in any persisted provider payload.
165
+ *
166
+ * Dropping the result loses information the model needs to recover; sending
167
+ * it as-is 400s the request. Folding it into an assistant `message` preserves
168
+ * the payload (call_id + truncated output) while staying within the Responses
169
+ * input grammar. Matches the behavior of {@link transformRequestBody} in the
170
+ * OpenAI code backend provider — issue #1351 / regression of #472.
171
+ */
172
+ export function repairOrphanResponsesToolOutputs(input: ResponseInput): ResponseInput {
173
+ const knownCallIds = new Set<string>();
174
+ for (const item of input) {
175
+ const t = (item as { type?: string }).type;
176
+ const callId = (item as { call_id?: unknown }).call_id;
177
+ if (typeof callId !== "string") continue;
178
+ if (t === "function_call" || t === "custom_tool_call") knownCallIds.add(callId);
179
+ }
180
+ let hasOrphan = false;
181
+ for (const item of input) {
182
+ const t = (item as { type?: string }).type;
183
+ if (t !== "function_call_output" && t !== "custom_tool_call_output") continue;
184
+ const callId = (item as { call_id?: unknown }).call_id;
185
+ if (typeof callId === "string" && !knownCallIds.has(callId)) {
186
+ hasOrphan = true;
187
+ break;
188
+ }
189
+ }
190
+ if (!hasOrphan) return input;
191
+ return input.map(item => {
192
+ const t = (item as { type?: string }).type;
193
+ if (t !== "function_call_output" && t !== "custom_tool_call_output") return item;
194
+ const record = item as { call_id?: unknown; output?: unknown; name?: unknown };
195
+ const callId = record.call_id;
196
+ if (typeof callId !== "string" || knownCallIds.has(callId)) return item;
197
+ const toolName = typeof record.name === "string" && record.name.length > 0 ? record.name : "tool";
198
+ const rawOutput = record.output;
199
+ let text: string;
200
+ if (typeof rawOutput === "string") text = rawOutput;
201
+ else if (rawOutput == null) text = "";
202
+ else {
203
+ try {
204
+ text = JSON.stringify(rawOutput);
205
+ } catch {
206
+ text = String(rawOutput);
207
+ }
208
+ }
209
+ const ORPHAN_OUTPUT_LIMIT = 16_000;
210
+ if (text.length > ORPHAN_OUTPUT_LIMIT) text = `${text.slice(0, ORPHAN_OUTPUT_LIMIT)}\n...[truncated]`;
211
+ return {
212
+ type: "message",
213
+ role: "assistant",
214
+ content: `[Orphan ${toolName} result; call_id=${callId}]: ${text}`,
215
+ } as ResponseInput[number];
216
+ });
217
+ }
218
+
219
+ export function convertResponsesInputContent(
220
+ content: string | Array<TextContent | ImageContent>,
221
+ supportsImages: boolean,
222
+ ): ResponseInputContent[] | undefined {
223
+ if (typeof content === "string") {
224
+ if (content.trim().length === 0) return undefined;
225
+ return [{ type: "input_text", text: content.toWellFormed() } satisfies ResponseInputText];
226
+ }
227
+
228
+ const { textBlocks, imageBlocks, omittedImages } = partitionVisionContent(content, supportsImages);
229
+ const normalizedContent: ResponseInputContent[] = [];
230
+ for (const item of textBlocks) {
231
+ const text = item.text.toWellFormed();
232
+ if (text.trim().length === 0) continue;
233
+ normalizedContent.push({
234
+ type: "input_text",
235
+ text,
236
+ } satisfies ResponseInputText);
237
+ }
238
+ for (const item of imageBlocks) {
239
+ normalizedContent.push({
240
+ type: "input_image",
241
+ detail: "auto",
242
+ image_url: `data:${item.mimeType};base64,${item.data}`,
243
+ } satisfies ResponseInputImage);
244
+ }
245
+ if (omittedImages) {
246
+ normalizedContent.push({
247
+ type: "input_text",
248
+ text: NON_VISION_IMAGE_PLACEHOLDER,
249
+ } satisfies ResponseInputText);
250
+ }
251
+ return normalizedContent.length > 0 ? normalizedContent : undefined;
252
+ }
253
+
254
+ export function convertResponsesAssistantMessage<TApi extends Api>(
255
+ assistantMsg: AssistantMessage,
256
+ model: Model<TApi>,
257
+ msgIndex: number,
258
+ knownCallIds: Set<string>,
259
+ includeThinkingSignatures = true,
260
+ customCallIds?: Set<string>,
261
+ ): ResponseInput {
262
+ const outputItems: ResponseInput = [];
263
+ const isDifferentModel =
264
+ assistantMsg.model !== model.id && assistantMsg.provider === model.provider && assistantMsg.api === model.api;
265
+
266
+ for (const block of assistantMsg.content) {
267
+ if (block.type === "thinking" && assistantMsg.stopReason !== "error") {
268
+ if (!includeThinkingSignatures) {
269
+ continue;
270
+ }
271
+ if (block.thinkingSignature) {
272
+ outputItems.push(JSON.parse(block.thinkingSignature) as ResponseReasoningItem);
273
+ }
274
+ continue;
275
+ }
276
+
277
+ if (block.type === "text") {
278
+ const parsedSignature = parseTextSignature(block.textSignature);
279
+ let msgId = parsedSignature?.id;
280
+ if (!msgId) {
281
+ msgId = `msg_${msgIndex}`;
282
+ } else if (msgId.length > 64) {
283
+ msgId = `msg_${Bun.hash(msgId).toString(36)}`;
284
+ }
285
+ outputItems.push({
286
+ type: "message",
287
+ role: "assistant",
288
+ content: [{ type: "output_text", text: block.text.toWellFormed(), annotations: [] }],
289
+ status: "completed",
290
+ id: msgId,
291
+ phase: parsedSignature?.phase,
292
+ } satisfies ResponseOutputMessage);
293
+ continue;
294
+ }
295
+
296
+ if (block.type !== "toolCall") {
297
+ continue;
298
+ }
299
+
300
+ const normalized = normalizeResponsesToolCallId(block.id, block.customWireName ? "ctc" : "fc");
301
+ let itemId: string | undefined = normalized.itemId;
302
+ if (isDifferentModel && (itemId?.startsWith("fc_") || itemId?.startsWith("fcr_") || itemId?.startsWith("ctc_"))) {
303
+ itemId = undefined;
304
+ }
305
+ knownCallIds.add(normalized.callId);
306
+ if (block.customWireName) {
307
+ const rawInput = typeof block.arguments?.input === "string" ? block.arguments.input.toWellFormed() : "";
308
+ customCallIds?.add(normalized.callId);
309
+ outputItems.push({
310
+ type: "custom_tool_call",
311
+ id: itemId,
312
+ call_id: normalized.callId,
313
+ name: block.customWireName,
314
+ input: rawInput,
315
+ } as ResponseInput[number]);
316
+ continue;
317
+ }
318
+ outputItems.push({
319
+ type: "function_call",
320
+ id: itemId,
321
+ call_id: normalized.callId,
322
+ name: block.name,
323
+ arguments: JSON.stringify(sanitizeJsonStrings(block.arguments ?? {})),
324
+ });
325
+ }
326
+
327
+ return outputItems;
328
+ }
329
+
330
+ export function appendResponsesToolResultMessages<TApi extends Api>(
331
+ messages: ResponseInput,
332
+ toolResults: readonly ToolResultMessage[],
333
+ model: Model<TApi>,
334
+ strictResponsesPairing: boolean,
335
+ knownCallIds: ReadonlySet<string>,
336
+ customCallIds?: ReadonlySet<string>,
337
+ ): void {
338
+ const supportsImages = model.input.includes("image");
339
+ const imageParts: ResponseInputContent[] = [];
340
+
341
+ for (const toolResult of toolResults) {
342
+ appendResponsesToolResultOutput(
343
+ messages,
344
+ imageParts,
345
+ toolResult,
346
+ supportsImages,
347
+ strictResponsesPairing,
348
+ knownCallIds,
349
+ customCallIds,
350
+ );
351
+ }
352
+
353
+ if (imageParts.length === 0) {
354
+ return;
355
+ }
356
+
357
+ messages.push({ role: "user", content: imageParts });
358
+ }
359
+
360
+ /**
361
+ * Append the Responses items for one tool result of a batch (#4807).
362
+ *
363
+ * Emits the paired `function_call_output` / `custom_tool_call_output` in
364
+ * `messages` — keeping every output of the batch contiguous — and collects
365
+ * supported image blocks into `imageParts` instead of emitting a standalone
366
+ * user message per result. A per-result image user message interleaves with
367
+ * sibling outputs of the same assistant tool-call turn; once an OpenAI
368
+ * Responses → Anthropic Messages proxy groups consecutive outputs into the
369
+ * single user message carrying `tool_result` blocks, the interleaved image
370
+ * user message splits that group and leaves a `tool_use` without its
371
+ * immediately-following `tool_result`, which Anthropic rejects with a 400 on
372
+ * every replay of the poisoned tail.
373
+ */
374
+ function appendResponsesToolResultOutput(
375
+ messages: ResponseInput,
376
+ imageParts: ResponseInputContent[],
377
+ toolResult: ToolResultMessage,
378
+ supportsImages: boolean,
379
+ strictResponsesPairing: boolean,
380
+ knownCallIds: ReadonlySet<string>,
381
+ customCallIds?: ReadonlySet<string>,
382
+ ): void {
383
+ const textResult = toolResult.content
384
+ .filter((block): block is TextContent => block.type === "text")
385
+ .map(block => block.text)
386
+ .join("\n");
387
+ const hasImages = toolResult.content.some((block): block is ImageContent => block.type === "image");
388
+ const omittedImages = hasImages && !supportsImages;
389
+ const normalized = normalizeResponsesToolCallId(toolResult.toolCallId);
390
+ if (strictResponsesPairing && !knownCallIds.has(normalized.callId)) {
391
+ return;
392
+ }
393
+
394
+ const output = (
395
+ omittedImages
396
+ ? joinTextWithImagePlaceholder(textResult, true)
397
+ : textResult.length > 0
398
+ ? textResult
399
+ : "(see attached image)"
400
+ ).toWellFormed();
401
+ if (customCallIds?.has(normalized.callId)) {
402
+ messages.push({
403
+ type: "custom_tool_call_output",
404
+ call_id: normalized.callId,
405
+ output,
406
+ } as ResponseInput[number]);
407
+ } else {
408
+ messages.push({
409
+ type: "function_call_output",
410
+ call_id: normalized.callId,
411
+ output,
412
+ });
413
+ }
414
+
415
+ if (!hasImages || !supportsImages) {
416
+ return;
417
+ }
418
+
419
+ if (imageParts.length === 0) {
420
+ imageParts.push({ type: "input_text", text: "Attached image(s) from tool result:" } satisfies ResponseInputText);
421
+ }
422
+ // Label each result's image group with its call id so parallel results keep
423
+ // image-to-call attribution inside the single collected user message (#4807).
424
+ imageParts.push({
425
+ type: "input_text",
426
+ text: `call_id=${normalized.callId}`,
427
+ } satisfies ResponseInputText);
428
+ for (const block of toolResult.content) {
429
+ if (block.type === "image") {
430
+ imageParts.push({
431
+ type: "input_image",
432
+ detail: "auto",
433
+ image_url: `data:${block.mimeType};base64,${block.data}`,
434
+ } satisfies ResponseInputImage);
435
+ }
436
+ }
437
+ }
438
+
439
+ export interface ProcessResponsesStreamOptions {
440
+ onFirstToken?: () => void;
441
+ onOutputItemDone?: (item: ResponseOutputItem) => void;
442
+ }
443
+
444
+ export async function processResponsesStream<TApi extends Api>(
445
+ openaiStream: AsyncIterable<OpenAI.Responses.ResponseStreamEvent>,
446
+ output: AssistantMessage,
447
+ stream: AssistantMessageEventStream,
448
+ model: Model<TApi>,
449
+ options?: ProcessResponsesStreamOptions,
450
+ ): Promise<void> {
451
+ type StreamItem = ResponseReasoningItem | ResponseOutputMessage | ResponseFunctionToolCall | ResponseCustomToolCall;
452
+ type StreamBlock = ThinkingContent | TextContent | (ToolCall & { partialJson: string });
453
+ interface ItemEntry {
454
+ item: StreamItem;
455
+ block: StreamBlock;
456
+ blockContentIndex: number;
457
+ summaryBuffer: string;
458
+ rawBuffer: string;
459
+ summaryStarted: boolean;
460
+ /**
461
+ * Raw `arguments` carried by the item's `response.output_item.added` snapshot.
462
+ * Kept out of the streaming buffer (a relay may put a `{}` placeholder here)
463
+ * but retained as the lowest-precedence source for relays that supply the
464
+ * real payload only in that snapshot.
465
+ */
466
+ addedArguments: string;
467
+ /**
468
+ * Set when this entry's tool identity is ambiguous (a duplicate `call_id`,
469
+ * an `id`/`call_id` namespace collision, or any other shape where a delta
470
+ * cannot be unambiguously attributed). The entry is finalized as
471
+ * `incompleteArguments` so the agent loop rejects it instead of executing
472
+ * possibly-misattributed arguments.
473
+ */
474
+ ambiguousIdentity: boolean;
475
+ /**
476
+ * Whether this entry has already been finalized by a terminal
477
+ * `response.output_item.done`. A duplicate terminal event for the same item
478
+ * must not emit a second `toolcall_end`/`text_end`/`thinking_end`.
479
+ */
480
+ finalized: boolean;
481
+ }
482
+ // Per-item argument buffer keyed on stable item identity. Multiple tool-call
483
+ // items can stream interleaved argument deltas in one response, so a single
484
+ // most-recent slot would mis-attribute deltas to the wrong item.
485
+ const items = new Map<string, ItemEntry>();
486
+ let lastKey: string | null = null;
487
+ const idKey = (id: string) => `id:${id}`;
488
+ const callKey = (id: string) => `call:${id}`;
489
+ const idxKey = (n: number) => `idx:${n}`;
490
+ const hasIndex = (n: number | undefined): n is number => typeof n === "number" && Number.isFinite(n);
491
+ const resolveEntry = (
492
+ itemId: string | undefined,
493
+ outputIndex: number | undefined,
494
+ // Fallback to the most-recently-added entry (`lastKey`) when the event
495
+ // cannot be resolved by identity:
496
+ // - "never": tool ghost events with an explicit but unmatched key are ignored.
497
+ // - "no-key": only when BOTH item_id and a finite output_index are absent —
498
+ // the legacy single continuation-style tool delta/done shape.
499
+ // - "always": continuation-style non-tool events (reasoning/text), which may
500
+ // legitimately omit identity and target the open block.
501
+ fallback: "never" | "no-key" | "always",
502
+ ): ItemEntry | undefined => {
503
+ if (itemId) {
504
+ const byId = items.get(idKey(itemId));
505
+ const byCallId = items.get(callKey(itemId));
506
+ // Ambiguous identity: `item_id` matches one entry as its canonical id and
507
+ // a *different* entry as its `call_id` (an id/call_id namespace collision).
508
+ // Picking either silently mis-attributes the payload, so mark both
509
+ // ambiguous and drop the delta instead of resolving.
510
+ if (byId && byCallId && byId !== byCallId) {
511
+ byId.ambiguousIdentity = true;
512
+ byCallId.ambiguousIdentity = true;
513
+ return undefined;
514
+ }
515
+ if (byId) return byId;
516
+ if (byCallId) return byCallId;
517
+ }
518
+ if (hasIndex(outputIndex)) {
519
+ const byIdx = items.get(idxKey(outputIndex));
520
+ if (byIdx) return byIdx;
521
+ }
522
+ const hasExplicitKey = !!itemId || hasIndex(outputIndex);
523
+ const allowLastKey = fallback === "always" || (fallback === "no-key" && !hasExplicitKey);
524
+ if (allowLastKey && lastKey) return items.get(lastKey);
525
+ return undefined;
526
+ };
527
+ const registerEntry = (item: StreamItem, block: StreamBlock, outputIndex: number | undefined): ItemEntry => {
528
+ output.content.push(block);
529
+ const entry: ItemEntry = {
530
+ item,
531
+ block,
532
+ blockContentIndex: output.content.length - 1,
533
+ summaryBuffer: "",
534
+ rawBuffer: "",
535
+ summaryStarted: false,
536
+ addedArguments: item.type === "function_call" ? (item.arguments ?? "") : "",
537
+ ambiguousIdentity: false,
538
+ finalized: false,
539
+ };
540
+ // Primary key prefers the stable item id; if the wire omits it, fall back to
541
+ // the positional index. A synthetic key keeps the entry addressable as lastKey
542
+ // for continuation-style non-tool events even when neither is present.
543
+ const key = item.id ? idKey(item.id) : hasIndex(outputIndex) ? idxKey(outputIndex) : `seq:${items.size}`;
544
+ items.set(key, entry);
545
+ // Index alias: only claim it when no other entry already holds it. Two items
546
+ // sharing one `output_index` (a relay defect) must not have the second steal
547
+ // the alias and drop the first's index-routed deltas; each stays addressable
548
+ // by its own stable id/call_id, and the index keeps resolving to the first
549
+ // occupant rather than silently reassigning.
550
+ if (hasIndex(outputIndex)) {
551
+ const idxK = idxKey(outputIndex);
552
+ if (!items.has(idxK)) items.set(idxK, entry);
553
+ }
554
+ if ((item.type === "function_call" || item.type === "custom_tool_call") && item.call_id) {
555
+ const callK = callKey(item.call_id);
556
+ const existing = items.get(callK);
557
+ // Duplicate `call_id` in one response: two distinct items claim the same
558
+ // alias. Fail closed for both — neither's arguments can be trusted to
559
+ // belong to the right call once their deltas and terminals are aliased.
560
+ if (existing && existing !== entry) {
561
+ existing.ambiguousIdentity = true;
562
+ entry.ambiguousIdentity = true;
563
+ } else if (!existing) {
564
+ items.set(callK, entry);
565
+ }
566
+ }
567
+ // Detect an id/call_id collision at registration too: a new item whose id
568
+ // equals another item's call_id (or vice versa) makes id-based resolution
569
+ // ambiguous for any delta keyed on that shared string.
570
+ if (item.id) {
571
+ const callAliasOfOther = items.get(callKey(item.id));
572
+ if (callAliasOfOther && callAliasOfOther !== entry) {
573
+ callAliasOfOther.ambiguousIdentity = true;
574
+ entry.ambiguousIdentity = true;
575
+ }
576
+ }
577
+ lastKey = key;
578
+ return entry;
579
+ };
580
+ const dropEntry = (itemId: string | undefined, outputIndex: number | undefined, callId?: string): void => {
581
+ const entry =
582
+ (itemId ? (items.get(idKey(itemId)) ?? items.get(callKey(itemId))) : undefined) ??
583
+ (callId ? items.get(callKey(callId)) : undefined) ??
584
+ (hasIndex(outputIndex) ? items.get(idxKey(outputIndex)) : undefined);
585
+ if (!entry) return;
586
+ entry.finalized = true;
587
+ for (const [key, candidate] of items) {
588
+ if (candidate !== entry) continue;
589
+ items.delete(key);
590
+ if (lastKey === key) lastKey = null;
591
+ }
592
+ };
593
+ let sawFirstToken = false;
594
+
595
+ for await (const event of openaiStream) {
596
+ if (event.type === "response.created") {
597
+ output.responseId = event.response.id;
598
+ } else if (event.type === "response.output_item.added") {
599
+ if (!sawFirstToken) {
600
+ sawFirstToken = true;
601
+ options?.onFirstToken?.();
602
+ }
603
+ const item = event.item;
604
+ const outputIndex = event.output_index;
605
+ if (item.type === "reasoning") {
606
+ const block: ThinkingContent = { type: "thinking", thinking: "", itemId: item.id };
607
+ const entry = registerEntry(item, block, outputIndex);
608
+ stream.push({ type: "thinking_start", contentIndex: entry.blockContentIndex, partial: output });
609
+ } else if (item.type === "message") {
610
+ const block: TextContent = { type: "text", text: "" };
611
+ const entry = registerEntry(item, block, outputIndex);
612
+ stream.push({ type: "text_start", contentIndex: entry.blockContentIndex, partial: output });
613
+ } else if (item.type === "function_call") {
614
+ const block: ToolCall & { partialJson: string } = {
615
+ type: "toolCall",
616
+ id: encodeResponsesToolCallId(item.call_id, item.id),
617
+ name: item.name,
618
+ arguments: {},
619
+ partialJson: "",
620
+ };
621
+ const entry = registerEntry(item, block, outputIndex);
622
+ stream.push({ type: "toolcall_start", contentIndex: entry.blockContentIndex, partial: output });
623
+ } else if (item.type === "custom_tool_call") {
624
+ const block: ToolCall & { partialJson: string } = {
625
+ type: "toolCall",
626
+ id: encodeResponsesToolCallId(item.call_id, item.id),
627
+ // Preserve the raw wire name (e.g. `apply_patch`). The agent-loop
628
+ // dispatcher matches it against both `Tool.name` and
629
+ // `Tool.customWireName`, so this stays wire-accurate through
630
+ // history replay while still routing to the right handler.
631
+ name: item.name,
632
+ arguments: { input: item.input ?? "" },
633
+ customWireName: item.name,
634
+ // Custom tools stream a raw string, but we reuse `partialJson` as the
635
+ // accumulation buffer so later code that inspects the field still works.
636
+ partialJson: item.input ?? "",
637
+ };
638
+ const entry = registerEntry(item, block, outputIndex);
639
+ stream.push({ type: "toolcall_start", contentIndex: entry.blockContentIndex, partial: output });
640
+ }
641
+ } else if (event.type === "response.reasoning_summary_part.added") {
642
+ const entry = resolveEntry(event.item_id, event.output_index, "always");
643
+ if (entry?.item.type === "reasoning" && entry.block.type === "thinking") {
644
+ entry.item.summary = entry.item.summary || [];
645
+ entry.item.summary.push(event.part);
646
+ if (!entry.summaryStarted) {
647
+ entry.summaryStarted = true;
648
+ stream.push({ type: "reasoning_summary_start", contentIndex: entry.blockContentIndex, partial: output });
649
+ }
650
+ }
651
+ } else if (event.type === "response.reasoning_summary_text.delta") {
652
+ const entry = resolveEntry(event.item_id, event.output_index, "always");
653
+ if (entry?.item.type === "reasoning" && entry.block.type === "thinking") {
654
+ entry.item.summary = entry.item.summary || [];
655
+ const lastPart = entry.item.summary[entry.item.summary.length - 1];
656
+ if (lastPart) {
657
+ entry.block.thinking += event.delta;
658
+ entry.summaryBuffer += event.delta;
659
+ lastPart.text += event.delta;
660
+ stream.push({
661
+ type: "reasoning_summary_delta",
662
+ contentIndex: entry.blockContentIndex,
663
+ delta: event.delta,
664
+ partial: output,
665
+ });
666
+ }
667
+ }
668
+ } else if (event.type === "response.reasoning_summary_part.done") {
669
+ const entry = resolveEntry(event.item_id, event.output_index, "always");
670
+ if (entry?.item.type === "reasoning" && entry.block.type === "thinking") {
671
+ entry.item.summary = entry.item.summary || [];
672
+ const lastPart = entry.item.summary[entry.item.summary.length - 1];
673
+ if (lastPart) {
674
+ entry.block.thinking += "\n\n";
675
+ entry.summaryBuffer += "\n\n";
676
+ lastPart.text += "\n\n";
677
+ stream.push({
678
+ type: "reasoning_summary_delta",
679
+ contentIndex: entry.blockContentIndex,
680
+ delta: "\n\n",
681
+ partial: output,
682
+ });
683
+ }
684
+ }
685
+ } else if (event.type === "response.reasoning_text.delta") {
686
+ // Raw reasoning text delta from local providers that stream thinking
687
+ // directly rather than via the OpenAI summary tracking protocol.
688
+ const entry = resolveEntry(event.item_id, event.output_index, "always");
689
+ if (entry?.item.type === "reasoning" && entry.block.type === "thinking") {
690
+ entry.block.thinking += event.delta;
691
+ entry.rawBuffer += event.delta;
692
+ stream.push({
693
+ type: "thinking_delta",
694
+ contentIndex: entry.blockContentIndex,
695
+ delta: event.delta,
696
+ partial: output,
697
+ });
698
+ }
699
+ } else if (event.type === "response.content_part.added") {
700
+ const entry = resolveEntry(event.item_id, event.output_index, "always");
701
+ if (entry?.item.type === "message") {
702
+ entry.item.content = entry.item.content || [];
703
+ if (event.part.type === "output_text" || event.part.type === "refusal") {
704
+ entry.item.content.push(event.part);
705
+ }
706
+ }
707
+ } else if (event.type === "response.output_text.delta") {
708
+ const entry = resolveEntry(event.item_id, event.output_index, "always");
709
+ if (entry?.item.type === "message" && entry.block.type === "text") {
710
+ const lastPart = entry.item.content?.[entry.item.content.length - 1];
711
+ if (lastPart?.type === "output_text") {
712
+ entry.block.text += event.delta;
713
+ lastPart.text += event.delta;
714
+ stream.push({
715
+ type: "text_delta",
716
+ contentIndex: entry.blockContentIndex,
717
+ delta: event.delta,
718
+ partial: output,
719
+ });
720
+ }
721
+ }
722
+ } else if (event.type === "response.refusal.delta") {
723
+ const entry = resolveEntry(event.item_id, event.output_index, "always");
724
+ if (entry?.item.type === "message" && entry.block.type === "text") {
725
+ const lastPart = entry.item.content?.[entry.item.content.length - 1];
726
+ if (lastPart?.type === "refusal") {
727
+ entry.block.text += event.delta;
728
+ lastPart.refusal += event.delta;
729
+ stream.push({
730
+ type: "text_delta",
731
+ contentIndex: entry.blockContentIndex,
732
+ delta: event.delta,
733
+ partial: output,
734
+ });
735
+ }
736
+ }
737
+ } else if (event.type === "response.function_call_arguments.delta") {
738
+ const entry = resolveEntry(event.item_id, event.output_index, "no-key");
739
+ if (entry?.item.type === "function_call" && entry.block.type === "toolCall") {
740
+ entry.block.partialJson += event.delta;
741
+ entry.block.arguments = parseStreamingJson(entry.block.partialJson);
742
+ stream.push({
743
+ type: "toolcall_delta",
744
+ contentIndex: entry.blockContentIndex,
745
+ delta: event.delta,
746
+ partial: output,
747
+ });
748
+ }
749
+ } else if (event.type === "response.function_call_arguments.done") {
750
+ const entry = resolveEntry(event.item_id, event.output_index, "no-key");
751
+ if (entry?.item.type === "function_call" && entry.block.type === "toolCall") {
752
+ entry.block.partialJson = event.arguments;
753
+ entry.block.arguments = parseStreamingJson(entry.block.partialJson);
754
+ }
755
+ } else if (event.type === "response.custom_tool_call_input.delta") {
756
+ const entry = resolveEntry(event.item_id, event.output_index, "no-key");
757
+ if (entry?.item.type === "custom_tool_call" && entry.block.type === "toolCall") {
758
+ entry.block.partialJson += event.delta;
759
+ entry.block.arguments = { input: entry.block.partialJson };
760
+ stream.push({
761
+ type: "toolcall_delta",
762
+ contentIndex: entry.blockContentIndex,
763
+ delta: event.delta,
764
+ partial: output,
765
+ });
766
+ }
767
+ } else if (event.type === "response.custom_tool_call_input.done") {
768
+ const entry = resolveEntry(event.item_id, event.output_index, "no-key");
769
+ if (entry?.item.type === "custom_tool_call" && entry.block.type === "toolCall") {
770
+ entry.block.partialJson = event.input;
771
+ entry.block.arguments = { input: event.input };
772
+ }
773
+ } else if (event.type === "response.output_item.done") {
774
+ const item = structuredCloneJSON(event.item);
775
+ options?.onOutputItemDone?.(item);
776
+ // A tool item may be registered under its call id alone (relays that omit
777
+ // item ids in `added`) and then introduce an item id in the terminal event,
778
+ // so both identities are tried before the positional fallback.
779
+ const isToolItem = item.type === "function_call" || item.type === "custom_tool_call";
780
+ const entry =
781
+ resolveEntry(item.id, event.output_index, "never") ??
782
+ (isToolItem && item.call_id ? resolveEntry(item.call_id, event.output_index, "never") : undefined);
783
+ // A duplicate terminal event for an item already finalized (dropped) must
784
+ // not emit a second end event. After finalization the entry is gone from
785
+ // the map, so a second `output_item.done` for the same tool item resolves
786
+ // to no live entry — skip it rather than re-emitting.
787
+ // An orphan terminal event (no preceding `output_item.added`, so no live
788
+ // entry) for a tool item must not synthesize a phantom block at a stale
789
+ // content index. Only finalize tool items that resolved to a live entry.
790
+ if (isToolItem && !entry) continue;
791
+ if (item.type === "reasoning") {
792
+ // Prefer the streamed summary buffer only when it carries real text. When it
793
+ // holds only synthetic separators (e.g. a part.done arrived before/without any
794
+ // summary_text delta), fall back to the canonical `item.summary` from
795
+ // output_item.done so the materialized summaryText is not blank/separator-only.
796
+ const bufferSummary = entry?.summaryBuffer ?? "";
797
+ const itemSummary = item.summary?.map(part => part.text).join("\n\n") ?? "";
798
+ const summaryText = bufferSummary.trim() ? bufferSummary : itemSummary;
799
+ const rawText =
800
+ entry?.rawBuffer || (item.content?.[0]?.type === "reasoning_text" ? (item.content[0].text ?? "") : "");
801
+ const reasoningBlock =
802
+ entry?.block.type === "thinking"
803
+ ? entry.block
804
+ : (output.content.find(b => b.type === "thinking" && (b as ThinkingContent).itemId === item.id) as
805
+ | ThinkingContent
806
+ | undefined);
807
+ if (reasoningBlock) {
808
+ const mutable = reasoningBlock as {
809
+ provenance?: "summary" | "raw" | "mixed";
810
+ summaryText?: string;
811
+ rawText?: string;
812
+ };
813
+ if (mutable.provenance === undefined) {
814
+ if (mutable.summaryText === undefined && summaryText) mutable.summaryText = summaryText;
815
+ if (mutable.rawText === undefined && rawText) mutable.rawText = rawText;
816
+ mutable.provenance =
817
+ summaryText && rawText ? "mixed" : summaryText ? "summary" : rawText ? "raw" : undefined;
818
+ }
819
+ // Finalized display string must exclude raw CoT when a summary exists.
820
+ // Derive it from the STORED write-once provenance fields (falling back to
821
+ // this event's locals only for a first classification) so a later or
822
+ // duplicate finalization carrying only raw can never overwrite a summary/
823
+ // mixed block's safe display with raw CoT. Raw-only stays raw.
824
+ {
825
+ const effSummary = mutable.summaryText ?? summaryText;
826
+ const effRaw = mutable.rawText ?? rawText;
827
+ reasoningBlock.thinking = mutable.provenance === "raw" ? effRaw : effSummary || effRaw;
828
+ }
829
+ reasoningBlock.thinkingSignature = JSON.stringify(item);
830
+ const reasoningBlockIndex =
831
+ entry?.block === reasoningBlock ? entry.blockContentIndex : output.content.indexOf(reasoningBlock);
832
+ if (summaryText) {
833
+ // If the summary text came only from the canonical item.summary (no
834
+ // streamed summary deltas/part.added), no reasoning_summary_start was
835
+ // emitted. Emit one now so consumers that open a summary on start
836
+ // (e.g. the Responses SSE encoder) don't receive an orphaned end.
837
+ if (!entry?.summaryStarted) {
838
+ stream.push({
839
+ type: "reasoning_summary_start",
840
+ contentIndex: reasoningBlockIndex,
841
+ partial: output,
842
+ });
843
+ }
844
+ stream.push({
845
+ type: "reasoning_summary_end",
846
+ contentIndex: reasoningBlockIndex,
847
+ content: summaryText,
848
+ partial: output,
849
+ });
850
+ }
851
+ stream.push({
852
+ type: "thinking_end",
853
+ contentIndex: reasoningBlockIndex,
854
+ content: reasoningBlock.thinking,
855
+ partial: output,
856
+ });
857
+ }
858
+ dropEntry(item.id, event.output_index);
859
+ } else if (item.type === "message" && entry?.block.type === "text") {
860
+ const block = entry.block;
861
+ block.text = item.content
862
+ .map(part => (part.type === "output_text" ? (part.text ?? "") : (part.refusal ?? "")))
863
+ .join("");
864
+ block.textSignature = encodeTextSignatureV1(item.id, item.phase ?? undefined);
865
+ stream.push({
866
+ type: "text_end",
867
+ contentIndex: entry.blockContentIndex,
868
+ content: block.text,
869
+ partial: output,
870
+ });
871
+ dropEntry(item.id, event.output_index);
872
+ } else if (item.type === "function_call") {
873
+ // The terminal item is canonical. Some compatible Responses relays put an
874
+ // empty placeholder in output_item.added and only provide real arguments
875
+ // here. When streamed arguments also exist, require agreement rather than
876
+ // silently choosing one source — but compare the decoded payloads, since a
877
+ // relay that re-serializes the terminal item (different key spacing or
878
+ // escaping) is not a disagreement about what the model asked for.
879
+ const streamedArguments = entry?.block.type === "toolCall" ? entry.block.partialJson : "";
880
+ const finalArguments = item.arguments ?? "";
881
+ const hasStreamedArguments = streamedArguments.length > 0;
882
+ const hasFinalArguments = finalArguments.length > 0;
883
+ const conflictingArgumentSources =
884
+ hasStreamedArguments &&
885
+ hasFinalArguments &&
886
+ streamedArguments !== finalArguments &&
887
+ !isEquivalentJsonPayload(streamedArguments, finalArguments);
888
+ // Source precedence: terminal, then streamed deltas, then the `added`
889
+ // snapshot. The last one only matters for relays that never emit deltas
890
+ // and leave the terminal `arguments` empty; without it their real payload
891
+ // would silently degrade to `{}`.
892
+ const rawArguments = hasFinalArguments
893
+ ? finalArguments
894
+ : hasStreamedArguments
895
+ ? streamedArguments
896
+ : (entry?.addedArguments ?? "");
897
+ const decodedArguments =
898
+ conflictingArgumentSources || !isCompleteJson(rawArguments)
899
+ ? undefined
900
+ : parseStreamingJson(rawArguments);
901
+ // Function-call arguments must decode to a JSON object; `null`, arrays and
902
+ // scalars cannot be dispatched against a tool schema, so they fail closed
903
+ // instead of reaching validation as a non-record value. An ambiguous
904
+ // tool-call identity (duplicate call_id, id/call_id collision) also fails
905
+ // closed: attribution of the streamed/terminal payload is unsafe.
906
+ const ambiguousIdentity = entry?.ambiguousIdentity ?? false;
907
+ const incompleteArguments = ambiguousIdentity || !isJsonRecord(decodedArguments);
908
+ const args = incompleteArguments ? {} : (decodedArguments as Record<string, unknown>);
909
+ // Typed reason lets the agent loop give accurate recovery guidance instead
910
+ // of always suggesting "split the work" (truncation-only) for a malformed
911
+ // or conflicting terminal payload, or an ambiguous identity.
912
+ const incompleteArgumentsReason: "malformed" | "conflicting" | "ambiguous" | undefined = incompleteArguments
913
+ ? ambiguousIdentity
914
+ ? "ambiguous"
915
+ : conflictingArgumentSources
916
+ ? "conflicting"
917
+ : "malformed"
918
+ : undefined;
919
+ const toolCall: ToolCall = {
920
+ type: "toolCall",
921
+ id: encodeResponsesToolCallId(item.call_id, item.id),
922
+ name: item.name,
923
+ arguments: args,
924
+ ...(incompleteArguments ? { incompleteArguments: true, incompleteArgumentsReason } : {}),
925
+ };
926
+ captureUnicodeEscapeEvidence(toolCall, rawArguments);
927
+ if (entry?.block.type === "toolCall") {
928
+ entry.block.id = toolCall.id;
929
+ entry.block.name = toolCall.name;
930
+ entry.block.arguments = args;
931
+ delete entry.block.escapedNonAsciiArguments;
932
+ delete entry.block.escapedUnicodeArgumentEvidence;
933
+ captureUnicodeEscapeEvidence(entry.block, rawArguments);
934
+ if (incompleteArguments) {
935
+ entry.block.incompleteArguments = true;
936
+ entry.block.incompleteArgumentsReason = incompleteArgumentsReason;
937
+ } else {
938
+ delete entry.block.incompleteArguments;
939
+ delete entry.block.incompleteArgumentsReason;
940
+ }
941
+ }
942
+ const contentIndex = entry?.blockContentIndex ?? output.content.length - 1;
943
+ dropEntry(item.id, event.output_index, item.call_id);
944
+ stream.push({ type: "toolcall_end", contentIndex, toolCall, partial: output });
945
+ } else if (item.type === "custom_tool_call") {
946
+ const rawInput =
947
+ entry?.block.type === "toolCall" && entry.block.partialJson
948
+ ? entry.block.partialJson
949
+ : (item.input ?? "");
950
+ const toolCall: ToolCall = {
951
+ type: "toolCall",
952
+ id: encodeResponsesToolCallId(item.call_id, item.id),
953
+ name: item.name,
954
+ arguments: { input: rawInput },
955
+ customWireName: item.name,
956
+ };
957
+ if (entry?.block.type === "toolCall") {
958
+ entry.block.id = toolCall.id;
959
+ entry.block.name = toolCall.name;
960
+ entry.block.arguments = { input: rawInput };
961
+ }
962
+ const contentIndex = entry?.blockContentIndex ?? output.content.length - 1;
963
+ dropEntry(item.id, event.output_index, item.call_id);
964
+ stream.push({ type: "toolcall_end", contentIndex, toolCall, partial: output });
965
+ }
966
+ } else if (event.type === "response.completed") {
967
+ const response = event.response;
968
+ if (response?.id) {
969
+ output.responseId = response.id;
970
+ }
971
+ populateResponsesUsageFromResponse(output, response?.usage);
972
+ calculateCost(model, output.usage);
973
+ output.stopReason = mapOpenAIResponsesStopReason(response?.status);
974
+ if (response?.status === "failed" || response?.status === "cancelled") {
975
+ const error = response?.error ?? (response as any)?.status_details?.error;
976
+ const details = response?.incomplete_details;
977
+ const statusDetailsReason = (response as any)?.status_details?.reason;
978
+ const message = error
979
+ ? `${error.code || "unknown"}: ${error.message || "no message"}`
980
+ : details?.reason
981
+ ? `incomplete: ${details.reason}`
982
+ : typeof statusDetailsReason === "string" && statusDetailsReason.length > 0
983
+ ? `status_details: ${statusDetailsReason}`
984
+ : "Unknown error (no error details in response)";
985
+ // A `cancelled` response is not a capacity rejection and may already
986
+ // have produced observable work, so only the `failed` status carries
987
+ // the typed code onward.
988
+ throw createResponsesFailedError(message, response.status === "failed" ? error?.code : undefined);
989
+ }
990
+ // A response cut short for length (`incomplete`) may have stopped
991
+ // mid-tool-call. Any tool-call item still tracked in `items` never
992
+ // received its terminal `output_item.done`, so it was cut off; flag it
993
+ // (along with any finalized-but-unparseable JSON call) so the agent loop
994
+ // rejects it instead of executing repaired/partial arguments.
995
+ const openBlocks = new Set<unknown>(Array.from(items.values(), entry => entry.block));
996
+ flagTruncatedToolCalls(output, output.stopReason, block => !openBlocks.has(block));
997
+ if (output.content.some(block => block.type === "toolCall") && output.stopReason === "stop") {
998
+ output.stopReason = "toolUse";
999
+ }
1000
+ } else if (event.type === "error") {
1001
+ throw new Error(`Error Code ${event.code}: ${event.message}` || "Unknown error");
1002
+ } else if (event.type === "response.failed") {
1003
+ const error = event.response?.error ?? (event.response as any)?.status_details?.error;
1004
+ const details = event.response?.incomplete_details;
1005
+ const message = error
1006
+ ? `${error.code || "unknown"}: ${error.message || "no message"}`
1007
+ : details?.reason
1008
+ ? `incomplete: ${details.reason}`
1009
+ : "Unknown error (no error details in response)";
1010
+ throw createResponsesFailedError(message, error?.code);
1011
+ }
1012
+ }
1013
+ }
1014
+
1015
+ /**
1016
+ * A terminal failure envelope arrives inside an HTTP 200 stream — as
1017
+ * `response.failed`, or as `response.completed` with a `failed` response status
1018
+ * — so the typed `error.code` is the only structured evidence the transport can
1019
+ * keep. Both shapes carry the same structured failure and are typed identically.
1020
+ * Exactly OpenAI's capacity-overload code is carried through as transport facts,
1021
+ * matched case-sensitively; every other failure stays a plain error, so an
1022
+ * untyped, cased, or malformed code can never reach a typed retry admission. The
1023
+ * display message is unchanged either way.
1024
+ */
1025
+ function createResponsesFailedError(message: string, code: string | undefined): Error {
1026
+ if (code !== SERVER_OVERLOADED_PROVIDER_CODE) return new Error(message);
1027
+ const error = new Error(message) as Error & { openaiErrorCode?: string };
1028
+ error.openaiErrorCode = SERVER_OVERLOADED_PROVIDER_CODE;
1029
+ return error;
1030
+ }
1031
+
1032
+ /**
1033
+ * Whether two raw JSON argument strings decode to the same value. Used to tell a
1034
+ * relay's re-serialization of the same tool arguments apart from a genuine
1035
+ * disagreement between the streamed and terminal payloads; anything that does
1036
+ * not decode cleanly on both sides is treated as a disagreement (fail closed).
1037
+ */
1038
+ /** Whether a decoded JSON value is a plain object usable as tool-call arguments. */
1039
+ function isJsonRecord(value: unknown): value is Record<string, unknown> {
1040
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1041
+ }
1042
+
1043
+ function isEquivalentJsonPayload(left: string, right: string): boolean {
1044
+ if (!isCompleteJson(left) || !isCompleteJson(right)) return false;
1045
+ try {
1046
+ return areJsonValuesEqual(JSON.parse(left), JSON.parse(right));
1047
+ } catch {
1048
+ return false;
1049
+ }
1050
+ }
1051
+
1052
+ /**
1053
+ * Mark tool-call blocks left incomplete by a length-truncated response so the
1054
+ * agent loop rejects them instead of executing a best-effort partial parse.
1055
+ *
1056
+ * The universal signal is finalization: a call that never received its terminal
1057
+ * `output_item.done` (passed in via `isFinalized`) was cut off mid-arguments.
1058
+ * This covers both JSON function calls and raw-input custom tools without
1059
+ * mis-flagging a *completed* custom tool whose raw input is not valid JSON. As a
1060
+ * defensive secondary, a finalized JSON function call whose buffered arguments
1061
+ * still don't parse (e.g. a misbehaving relay) is flagged too. No-op unless the
1062
+ * turn stopped for length.
1063
+ *
1064
+ * Shared by both Responses providers (`openai-responses`, `openai-codex-responses`).
1065
+ */
1066
+ export function flagTruncatedToolCalls(
1067
+ output: AssistantMessage,
1068
+ stopReason: StopReason,
1069
+ isFinalized: (block: ToolCall) => boolean,
1070
+ ): void {
1071
+ if (stopReason !== "length") return;
1072
+ for (const block of output.content) {
1073
+ if (block.type !== "toolCall") continue;
1074
+ if (!isFinalized(block)) {
1075
+ block.incompleteArguments = true;
1076
+ block.incompleteArgumentsReason = "truncated";
1077
+ continue;
1078
+ }
1079
+ // Finalized: custom tools carry raw (non-JSON) input and are complete once
1080
+ // finalized; only JSON function calls get the parse double-check.
1081
+ if (!block.customWireName) {
1082
+ const partial = (block as { partialJson?: string }).partialJson;
1083
+ if (partial !== undefined && !isCompleteJson(partial)) {
1084
+ block.incompleteArguments = true;
1085
+ block.incompleteArgumentsReason = "truncated";
1086
+ }
1087
+ }
1088
+ }
1089
+ }
1090
+
1091
+ export function mapOpenAIResponsesStopReason(status: OpenAI.Responses.ResponseStatus | undefined): StopReason {
1092
+ if (!status) return "stop";
1093
+ switch (status) {
1094
+ case "completed":
1095
+ return "stop";
1096
+ case "incomplete":
1097
+ return "length";
1098
+ case "failed":
1099
+ case "cancelled":
1100
+ return "error";
1101
+ case "in_progress":
1102
+ case "queued":
1103
+ return "stop";
1104
+ default: {
1105
+ const exhaustive: never = status;
1106
+ throw new Error(`Unhandled stop reason: ${exhaustive}`);
1107
+ }
1108
+ }
1109
+ }
1110
+
1111
+ /** Initial empty `AssistantMessage` that streaming providers accumulate into. */
1112
+ export function createInitialResponsesAssistantMessage(api: Api, provider: string, modelId: string): AssistantMessage {
1113
+ return {
1114
+ role: "assistant",
1115
+ content: [],
1116
+ api,
1117
+ provider,
1118
+ model: modelId,
1119
+ usage: {
1120
+ input: 0,
1121
+ output: 0,
1122
+ cacheRead: 0,
1123
+ cacheWrite: 0,
1124
+ totalTokens: 0,
1125
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
1126
+ },
1127
+ stopReason: "stop",
1128
+ timestamp: Date.now(),
1129
+ };
1130
+ }
1131
+
1132
+ /** Extension fields we add on top of `ResponseCreateParamsStreaming` across the Responses-family providers. */
1133
+ export type ResponsesSamplingParamsExtras = {
1134
+ top_p?: number;
1135
+ top_k?: number;
1136
+ min_p?: number;
1137
+ presence_penalty?: number;
1138
+ repetition_penalty?: number;
1139
+ };
1140
+
1141
+ type CommonResponsesParams = OpenAI.Responses.ResponseCreateParamsStreaming & ResponsesSamplingParamsExtras;
1142
+
1143
+ type CommonSamplingOptions = Pick<
1144
+ StreamOptions,
1145
+ "temperature" | "topP" | "topK" | "minP" | "presencePenalty" | "repetitionPenalty" | "maxTokens"
1146
+ > & { serviceTier?: ServiceTier };
1147
+
1148
+ /**
1149
+ * Apply the common `StreamOptions` → Responses sampling-parameter mapping (max output tokens,
1150
+ * temperature, top-p/k, min-p, presence/repetition penalties, service tier). Mutates `params`.
1151
+ */
1152
+ export function applyCommonResponsesSamplingParams<P extends CommonResponsesParams>(
1153
+ params: P,
1154
+ options: CommonSamplingOptions | undefined,
1155
+ provider: string,
1156
+ supportsServiceTier = false,
1157
+ ): void {
1158
+ if (options?.maxTokens) params.max_output_tokens = options.maxTokens;
1159
+ if (options?.temperature !== undefined) params.temperature = options.temperature;
1160
+ if (options?.topP !== undefined) params.top_p = options.topP;
1161
+ if (options?.topK !== undefined) params.top_k = options.topK;
1162
+ if (options?.minP !== undefined) params.min_p = options.minP;
1163
+ if (options?.presencePenalty !== undefined) params.presence_penalty = options.presencePenalty;
1164
+ if (options?.repetitionPenalty !== undefined) params.repetition_penalty = options.repetitionPenalty;
1165
+ if (shouldSendServiceTier(options?.serviceTier, provider, supportsServiceTier)) {
1166
+ const resolved = resolveServiceTier(options?.serviceTier, provider);
1167
+ if (resolved === "flex" || resolved === "scale" || resolved === "priority") {
1168
+ params.service_tier = resolved;
1169
+ }
1170
+ }
1171
+ }
1172
+
1173
+ type ReasoningOptions = {
1174
+ reasoning?: string;
1175
+ reasoningSummary?: "auto" | "detailed" | "concise" | null;
1176
+ };
1177
+
1178
+ /**
1179
+ * Apply reasoning-related Responses parameters: enable encrypted reasoning content for replay,
1180
+ * set effort/summary when requested, and otherwise inject the GPT-5 "Juice: 0" no-reasoning hack.
1181
+ * Mutates `params` and may push a developer message into `messages`.
1182
+ */
1183
+ export function applyResponsesReasoningParams<P extends OpenAI.Responses.ResponseCreateParamsStreaming>(
1184
+ params: P,
1185
+ model: Model<Api>,
1186
+ options: ReasoningOptions | undefined,
1187
+ messages: ResponseInput,
1188
+ mapEffort?: (effort: string) => string,
1189
+ ): void {
1190
+ if (!model.reasoning) return;
1191
+ // Always request encrypted reasoning content so reasoning items can be replayed in
1192
+ // multi-turn conversations when store is false (items aren't persisted server-side, so
1193
+ // we must include the full content). See: https://github.com/can1357/oh-my-pi/issues/41
1194
+ params.include = ["reasoning.encrypted_content"];
1195
+ if (!modelSupportsReasoningControl(model)) return;
1196
+
1197
+ if (options?.reasoning || options?.reasoningSummary !== undefined) {
1198
+ const requested = options?.reasoning || "medium";
1199
+ type ReasoningParam = NonNullable<OpenAI.Responses.ResponseCreateParamsStreaming["reasoning"]>;
1200
+ const reasoningParams: ReasoningParam = {
1201
+ effort: (mapEffort ? mapEffort(requested) : requested) as ReasoningParam["effort"],
1202
+ };
1203
+ if (options?.reasoningSummary !== null) {
1204
+ reasoningParams.summary = options?.reasoningSummary || "auto";
1205
+ }
1206
+ params.reasoning = reasoningParams as P["reasoning"];
1207
+ } else if (model.name.toLowerCase().startsWith("gpt-5")) {
1208
+ // Jesus Christ, see https://community.openai.com/t/need-reasoning-false-option-for-gpt-5/1351588/7
1209
+ messages.push({
1210
+ role: "developer",
1211
+ content: [{ type: "input_text", text: "# Juice: 0 !important" }],
1212
+ });
1213
+ }
1214
+ }
1215
+
1216
+ /** Populate `output.usage` from a Responses-API `response.usage` payload. Does not invoke `calculateCost`. */
1217
+ export function populateResponsesUsageFromResponse(
1218
+ output: AssistantMessage,
1219
+ usage:
1220
+ | {
1221
+ input_tokens?: number | null;
1222
+ output_tokens?: number | null;
1223
+ total_tokens?: number | null;
1224
+ input_tokens_details?: {
1225
+ cached_tokens?: number | null;
1226
+ cache_write_tokens?: number | null;
1227
+ } | null;
1228
+ output_tokens_details?: { reasoning_tokens?: number | null } | null;
1229
+ }
1230
+ | null
1231
+ | undefined,
1232
+ ): void {
1233
+ if (!usage) return;
1234
+ const inputTokens = usage.input_tokens || 0;
1235
+ const cachedTokens = usage.input_tokens_details?.cached_tokens || 0;
1236
+ const reportedCacheWrite = usage.input_tokens_details?.cache_write_tokens || 0;
1237
+ const cacheWriteTokens =
1238
+ Number.isSafeInteger(reportedCacheWrite) &&
1239
+ reportedCacheWrite >= 0 &&
1240
+ cachedTokens + reportedCacheWrite <= inputTokens
1241
+ ? reportedCacheWrite
1242
+ : 0;
1243
+ const reasoningTokens = usage.output_tokens_details?.reasoning_tokens || 0;
1244
+ output.usage = {
1245
+ input: Math.max(0, inputTokens - cachedTokens - cacheWriteTokens),
1246
+ output: usage.output_tokens || 0,
1247
+ cacheRead: cachedTokens,
1248
+ cacheWrite: cacheWriteTokens,
1249
+ totalTokens: usage.total_tokens || 0,
1250
+ ...(reasoningTokens > 0 ? { reasoningTokens } : {}),
1251
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
1252
+ };
1253
+ }