@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,1268 @@
1
+ /**
2
+ * OpenAI Responses HTTP wire-format ↔ vib Context bridge for the auth-gateway.
3
+ *
4
+ * Inbound: parses `POST /v1/responses` request bodies into a {@link ParsedRequest}.
5
+ * Outbound: encodes vib's {@link AssistantMessage} (and event stream) back into
6
+ * the documented `response.*` SSE taxonomy or the non-streaming JSON shape.
7
+ *
8
+ * Spec: https://platform.openai.com/docs/api-reference/responses
9
+ * Inverse direction (source-of-truth for item shapes): ../../providers/openai-responses.ts
10
+ */
11
+
12
+ import { logger } from "@vib-rato/utils";
13
+ import { resolvePromptCacheKey } from "../auth-gateway/http";
14
+ import type { AuthGatewayParsedRequest as ParsedRequest } from "../auth-gateway/types";
15
+ import type {
16
+ AssistantMessage,
17
+ AssistantMessageEventStream,
18
+ Context,
19
+ Message,
20
+ TextContent,
21
+ ThinkingContent,
22
+ Tool,
23
+ ToolCall,
24
+ } from "../types";
25
+ import { sanitizeJsonStrings } from "../utils";
26
+ import {
27
+ type OpenAIResponsesFunctionCallItem,
28
+ type OpenAIResponsesFunctionCallOutputItem,
29
+ type OpenAIResponsesInputContent,
30
+ type OpenAIResponsesOutputContent,
31
+ type OpenAIResponsesReasoningItem,
32
+ type OpenAIResponsesTool,
33
+ openaiResponsesRequestSchema,
34
+ } from "./openai-responses-server-schema";
35
+
36
+ export type { ParsedRequest };
37
+
38
+ // ─── narrow guards ──────────────────────────────────────────────────────────
39
+
40
+ function isReasoningEffort(value: unknown): value is NonNullable<ParsedRequest["options"]["reasoning"]> {
41
+ return (
42
+ value === "minimal" ||
43
+ value === "low" ||
44
+ value === "medium" ||
45
+ value === "high" ||
46
+ value === "xhigh" ||
47
+ value === "max"
48
+ );
49
+ }
50
+
51
+ function isServiceTier(value: unknown): value is NonNullable<ParsedRequest["options"]["serviceTier"]> {
52
+ return value === "auto" || value === "default" || value === "flex" || value === "scale" || value === "priority";
53
+ }
54
+
55
+ function isObj(v: unknown): v is Record<string, unknown> {
56
+ return typeof v === "object" && v !== null && !Array.isArray(v);
57
+ }
58
+
59
+ function asString(v: unknown): string | undefined {
60
+ return typeof v === "string" ? v : undefined;
61
+ }
62
+
63
+ // ─── id helpers ─────────────────────────────────────────────────────────────
64
+
65
+ function uuidNoDashes(): string {
66
+ return crypto.randomUUID().replace(/-/g, "");
67
+ }
68
+
69
+ function makeRespId(): string {
70
+ return `resp_${uuidNoDashes()}`;
71
+ }
72
+
73
+ function makeMsgId(): string {
74
+ return `msg_${uuidNoDashes()}`;
75
+ }
76
+
77
+ function makeReasoningId(): string {
78
+ return `rs_${uuidNoDashes()}`;
79
+ }
80
+
81
+ function makeFuncCallId(): string {
82
+ return `fc_${uuidNoDashes()}`;
83
+ }
84
+
85
+ function makeCustomCallId(): string {
86
+ return `ctc_${uuidNoDashes()}`;
87
+ }
88
+
89
+ // ─── once-only warnings ─────────────────────────────────────────────────────
90
+ // Module-scoped so we don't spam logs once per turn.
91
+
92
+ let warnedImageNotSupported = false;
93
+ let warnedFileNotSupported = false;
94
+ let warnedReasoningSummaryLevel = false;
95
+
96
+ // ─── inbound parser helpers ─────────────────────────────────────────────────
97
+
98
+ function reasoningContentFromItem(
99
+ item: OpenAIResponsesReasoningItem,
100
+ ): Pick<ThinkingContent, "thinking" | "provenance" | "summaryText" | "rawText"> {
101
+ // `summary[]` is provider-displayable; `content[]` is raw reasoning. Keep
102
+ // these channels distinct so a Responses gateway round-trip cannot relabel
103
+ // raw CoT as a summary merely because summary text is absent.
104
+ const summaryText = (item.summary ?? []).map(part => part.text).join("");
105
+ const rawText = (item.content ?? []).map(part => part.text).join("");
106
+ if (summaryText && rawText) {
107
+ return { thinking: summaryText, provenance: "mixed", summaryText, rawText };
108
+ }
109
+ if (summaryText) return { thinking: summaryText, provenance: "summary", summaryText };
110
+ if (rawText) return { thinking: rawText, provenance: "raw", rawText };
111
+ return { thinking: "" };
112
+ }
113
+
114
+ type InputBlockUnion =
115
+ | { type: "input_text"; text: string }
116
+ | { type: "text"; text: string }
117
+ | { type: "input_image"; detail?: "auto" | "low" | "high"; image_url?: string; file_id?: string }
118
+ | { type: "input_file"; file_id?: string; filename?: string; file_data?: string };
119
+
120
+ /**
121
+ * Walk an input message's content array and produce pi-ai's `TextContent[]`.
122
+ * `input_image`/`input_file` blocks become bracketed text placeholders since
123
+ * pi-ai's `ImageContent` only carries inline base64 data and we have no
124
+ * resolver for OpenAI `image_url` / `file_id` references. Logs once per kind.
125
+ */
126
+ function inputContentParts(blocks: OpenAIResponsesInputContent[] | string | undefined): string | TextContent[] {
127
+ if (typeof blocks === "string") return blocks;
128
+ if (!blocks) return [];
129
+ const parts: TextContent[] = [];
130
+ for (const raw of blocks) {
131
+ const block = raw as InputBlockUnion;
132
+ if (block.type === "input_text" || block.type === "text") {
133
+ parts.push({ type: "text", text: block.text });
134
+ } else if (block.type === "input_image") {
135
+ if (!warnedImageNotSupported) {
136
+ warnedImageNotSupported = true;
137
+ logger.warn("openai-responses-server: input_image dropped (no pi-ai bridge for image_url/file_id)", {
138
+ hasUrl: typeof block.image_url === "string",
139
+ hasFileId: typeof block.file_id === "string",
140
+ });
141
+ }
142
+ const ref = block.image_url ?? block.file_id ?? "?";
143
+ parts.push({ type: "text", text: `[image: ${ref}]` });
144
+ } else if (block.type === "input_file") {
145
+ if (!warnedFileNotSupported) {
146
+ warnedFileNotSupported = true;
147
+ logger.warn("openai-responses-server: input_file dropped (no pi-ai bridge for file_id/file_data)", {
148
+ hasFileId: typeof block.file_id === "string",
149
+ hasFileData: typeof block.file_data === "string",
150
+ });
151
+ }
152
+ const ref = block.file_id ?? block.filename ?? "?";
153
+ parts.push({ type: "text", text: `[file: ${ref}]` });
154
+ }
155
+ }
156
+ return parts.length === 1 ? parts[0].text : parts;
157
+ }
158
+
159
+ type OutputBlockUnion =
160
+ | { type: "output_text"; text: string }
161
+ | { type: "text"; text: string }
162
+ | { type: "refusal"; refusal: string };
163
+
164
+ function outputTextOf(blocks: OpenAIResponsesOutputContent[] | string | undefined): TextContent[] {
165
+ if (typeof blocks === "string") return blocks.length > 0 ? [{ type: "text", text: blocks }] : [];
166
+ if (!blocks) return [];
167
+ const out: TextContent[] = [];
168
+ for (const raw of blocks) {
169
+ const block = raw as OutputBlockUnion;
170
+ if (block.type === "output_text" || block.type === "text") {
171
+ out.push({ type: "text", text: block.text });
172
+ } else if (block.type === "refusal") {
173
+ // Preserve the refusal reason so history replay still carries it.
174
+ out.push({ type: "text", text: `[refusal: ${block.refusal}]` });
175
+ }
176
+ }
177
+ return out;
178
+ }
179
+
180
+ // The schema accepts a much wider tool_choice union than the SDK type so the
181
+ // walker narrows against the local schema shape.
182
+ type ParsedToolChoice =
183
+ | "auto"
184
+ | "none"
185
+ | "required"
186
+ | { type: "function"; name: string }
187
+ | { type: "custom"; name: string }
188
+ | {
189
+ type:
190
+ | "web_search_preview"
191
+ | "file_search"
192
+ | "computer_use_preview"
193
+ | "code_interpreter"
194
+ | "image_generation"
195
+ | "mcp";
196
+ }
197
+ | { type: "allowed_tools"; mode: "auto" | "required"; tools: Array<{ type: string; name?: string }> };
198
+
199
+ function mapToolChoice(value: ParsedToolChoice | undefined): ParsedRequest["options"]["toolChoice"] {
200
+ if (value === undefined) return undefined;
201
+ if (value === "auto" || value === "none" || value === "required") return value;
202
+ if ("type" in value) {
203
+ // `custom` (OpenAI code backend apply_patch) and `function` both resolve to the same
204
+ // pi-ai shape: pi-ai's dispatcher matches `Tool.name` AND `customWireName`,
205
+ // so passing the wire name works for either.
206
+ if (value.type === "function" || value.type === "custom") return { name: value.name };
207
+ // Hosted tools + allowed_tools — we don't surface these to pi-ai; fall
208
+ // back to letting the model pick a tool freely.
209
+ return "auto";
210
+ }
211
+ return undefined;
212
+ }
213
+
214
+ function buildTools(tools: Array<OpenAIResponsesTool | { type: string }> | undefined): Tool[] | undefined {
215
+ if (!tools) return undefined;
216
+ const out: Tool[] = [];
217
+ for (const t of tools) {
218
+ // Skip non-function tools (web_search, file_search, …).
219
+ if (t.type !== "function") continue;
220
+ const fn = t as Extract<OpenAIResponsesTool, { type: "function" }>;
221
+ const tool: Tool = {
222
+ name: fn.name,
223
+ description: fn.description ?? "",
224
+ parameters: (fn.parameters ?? {}) as Tool["parameters"],
225
+ };
226
+ if (fn.strict !== undefined && fn.strict !== null) tool.strict = fn.strict;
227
+ out.push(tool);
228
+ }
229
+ return out.length > 0 ? out : undefined;
230
+ }
231
+
232
+ function ensureAssistantPlaceholder(messages: Message[], modelId: string, now: number): AssistantMessage {
233
+ const last = messages[messages.length - 1];
234
+ if (last && last.role === "assistant") return last;
235
+ const placeholder: AssistantMessage = {
236
+ role: "assistant",
237
+ content: [],
238
+ api: "openai-responses",
239
+ provider: "openai",
240
+ model: modelId,
241
+ usage: {
242
+ input: 0,
243
+ output: 0,
244
+ cacheRead: 0,
245
+ cacheWrite: 0,
246
+ totalTokens: 0,
247
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
248
+ },
249
+ stopReason: "stop",
250
+ timestamp: now,
251
+ };
252
+ messages.push(placeholder);
253
+ return placeholder;
254
+ }
255
+
256
+ /** Flatten a function_call_output array form (text + refusal) into a single string. */
257
+ function flattenFunctionOutputArray(blocks: readonly unknown[]): string {
258
+ const parts: string[] = [];
259
+ for (const raw of blocks) {
260
+ if (!isObj(raw)) continue;
261
+ const t = raw.type;
262
+ if (t === "output_text" || t === "text") {
263
+ const text = asString(raw.text);
264
+ if (text) parts.push(text);
265
+ } else if (t === "refusal") {
266
+ const refusal = asString(raw.refusal);
267
+ if (refusal) parts.push(`[refusal: ${refusal}]`);
268
+ }
269
+ }
270
+ return parts.join("");
271
+ }
272
+
273
+ // ─── parseRequest ───────────────────────────────────────────────────────────
274
+
275
+ export function parseRequest(body: unknown, headers?: Headers): ParsedRequest {
276
+ // Header capture is centralized in `auth-gateway/server.ts` (the
277
+ // allow-listed set lands on `options.headers` automatically). We also
278
+ // consult `headers` here to populate `options.promptCacheKey` when the
279
+ // client signals a cache identity outside the body — see the
280
+ // `resolvePromptCacheKey` call further down.
281
+
282
+ const parsed = openaiResponsesRequestSchema.safeParse(body);
283
+ if (!parsed.success) {
284
+ throw new Error(`openai-responses: ${parsed.error.message}`);
285
+ }
286
+ const data = parsed.data;
287
+
288
+ const now = Date.now();
289
+ const messages: Message[] = [];
290
+ const systemPrompt: string[] = [];
291
+
292
+ if (typeof data.instructions === "string" && data.instructions.length > 0) {
293
+ systemPrompt.push(data.instructions);
294
+ }
295
+
296
+ if (typeof data.input === "string") {
297
+ messages.push({ role: "user", content: data.input, timestamp: now });
298
+ } else if (data.input) {
299
+ for (const item of data.input) {
300
+ // Items may omit `type` and rely on `role` (the convenience shape).
301
+ const effectiveType = item.type ?? ("role" in item ? "message" : undefined);
302
+ if (effectiveType === "message") {
303
+ const msg = item as {
304
+ role?: string;
305
+ content?: OpenAIResponsesInputContent[] | OpenAIResponsesOutputContent[] | string;
306
+ };
307
+ switch (msg.role) {
308
+ case "system": {
309
+ const text = inputContentParts(msg.content as OpenAIResponsesInputContent[] | string | undefined);
310
+ const flat = typeof text === "string" ? text : text.map(p => p.text).join("");
311
+ if (flat.length > 0) systemPrompt.push(flat);
312
+ break;
313
+ }
314
+ case "user":
315
+ case "developer": {
316
+ const content = inputContentParts(msg.content as OpenAIResponsesInputContent[] | string | undefined);
317
+ messages.push({ role: msg.role, content, timestamp: now });
318
+ break;
319
+ }
320
+ case "assistant": {
321
+ const parts = outputTextOf(msg.content as OpenAIResponsesOutputContent[] | string | undefined);
322
+ messages.push({
323
+ role: "assistant",
324
+ content: parts,
325
+ api: "openai-responses",
326
+ provider: "openai",
327
+ model: data.model,
328
+ usage: {
329
+ input: 0,
330
+ output: 0,
331
+ cacheRead: 0,
332
+ cacheWrite: 0,
333
+ totalTokens: 0,
334
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
335
+ },
336
+ stopReason: "stop",
337
+ timestamp: now,
338
+ });
339
+ break;
340
+ }
341
+ }
342
+ continue;
343
+ }
344
+ if (effectiveType === "reasoning") {
345
+ const reasoning = item as OpenAIResponsesReasoningItem;
346
+ const content = reasoningContentFromItem(reasoning);
347
+ const thinking: ThinkingContent = {
348
+ type: "thinking",
349
+ ...content,
350
+ thinkingSignature: JSON.stringify(reasoning),
351
+ ...(reasoning.id ? { itemId: reasoning.id } : {}),
352
+ };
353
+ ensureAssistantPlaceholder(messages, data.model, now).content.push(thinking);
354
+ continue;
355
+ }
356
+ if (effectiveType === "function_call") {
357
+ const call = item as OpenAIResponsesFunctionCallItem;
358
+ const argsRaw = call.arguments ?? "{}";
359
+ let args: Record<string, unknown>;
360
+ try {
361
+ const parsedArgs: unknown = JSON.parse(argsRaw);
362
+ args = isObj(parsedArgs) ? parsedArgs : {};
363
+ } catch {
364
+ throw new Error(`openai-responses: function_call ${call.call_id} has invalid JSON arguments`);
365
+ }
366
+ const toolCall: ToolCall = {
367
+ type: "toolCall",
368
+ id: call.call_id,
369
+ name: call.name,
370
+ arguments: args,
371
+ ...(call.id ? { thoughtSignature: call.id } : {}),
372
+ };
373
+ ensureAssistantPlaceholder(messages, data.model, now).content.push(toolCall);
374
+ continue;
375
+ }
376
+ if (effectiveType === "custom_tool_call") {
377
+ const call = item as { id?: string; call_id: string; name: string; input: string };
378
+ // Custom tools carry a raw input string. We stash it in `arguments.input`
379
+ // matching pi-ai's openai-responses-shared convention, and tag the call
380
+ // with `customWireName` so encoders re-emit it as `custom_tool_call`.
381
+ const toolCall: ToolCall = {
382
+ type: "toolCall",
383
+ id: call.call_id,
384
+ name: call.name,
385
+ arguments: { input: call.input ?? "" },
386
+ customWireName: call.name,
387
+ ...(call.id ? { thoughtSignature: call.id } : {}),
388
+ };
389
+ ensureAssistantPlaceholder(messages, data.model, now).content.push(toolCall);
390
+ continue;
391
+ }
392
+ if (effectiveType === "function_call_output") {
393
+ const output = item as OpenAIResponsesFunctionCallOutputItem;
394
+ const toolName = findToolNameById(messages, output.call_id);
395
+ const text =
396
+ typeof output.output === "string"
397
+ ? output.output
398
+ : Array.isArray(output.output)
399
+ ? flattenFunctionOutputArray(output.output)
400
+ : "";
401
+ messages.push({
402
+ role: "toolResult",
403
+ toolCallId: output.call_id,
404
+ toolName,
405
+ content: [{ type: "text", text }],
406
+ isError: false,
407
+ timestamp: now,
408
+ });
409
+ continue;
410
+ }
411
+ if (effectiveType === "custom_tool_call_output") {
412
+ const output = item as { call_id: string; output: string };
413
+ const toolName = findToolNameById(messages, output.call_id);
414
+ messages.push({
415
+ role: "toolResult",
416
+ toolCallId: output.call_id,
417
+ toolName,
418
+ content: [{ type: "text", text: output.output ?? "" }],
419
+ isError: false,
420
+ timestamp: now,
421
+ });
422
+ }
423
+ // Other item types are tolerated but not bridged.
424
+ }
425
+ }
426
+
427
+ const tools = buildTools(data.tools);
428
+ const context: Context = {
429
+ ...(systemPrompt.length > 0 ? { systemPrompt } : {}),
430
+ messages,
431
+ ...(tools ? { tools } : {}),
432
+ };
433
+
434
+ const options: ParsedRequest["options"] = {};
435
+ if (data.max_output_tokens !== undefined) options.maxOutputTokens = data.max_output_tokens;
436
+ if (data.temperature !== undefined) options.temperature = data.temperature;
437
+ if (data.top_p !== undefined) options.topP = data.top_p;
438
+ if (data.stop !== undefined && data.stop !== null) {
439
+ options.stopSequences = typeof data.stop === "string" ? [data.stop] : data.stop;
440
+ }
441
+ const toolChoice = mapToolChoice(data.tool_choice as ParsedToolChoice | undefined);
442
+ if (toolChoice !== undefined) options.toolChoice = toolChoice;
443
+ if (data.reasoning?.effort && isReasoningEffort(data.reasoning.effort)) {
444
+ options.reasoning = data.reasoning.effort;
445
+ }
446
+ // OpenAI summary: `none` → suppress; `auto`/`concise`/`detailed` → request
447
+ // visible summary. pi-ai has no per-level plumbing — log once and let the
448
+ // provider default kick in.
449
+ if (data.reasoning?.summary === "none") {
450
+ options.hideThinkingSummary = true;
451
+ } else if (
452
+ data.reasoning?.summary === "auto" ||
453
+ data.reasoning?.summary === "concise" ||
454
+ data.reasoning?.summary === "detailed"
455
+ ) {
456
+ if (!warnedReasoningSummaryLevel) {
457
+ warnedReasoningSummaryLevel = true;
458
+ logger.debug("openai-responses-server: reasoning.summary level not differentiated", {
459
+ level: data.reasoning.summary,
460
+ });
461
+ }
462
+ }
463
+ if (data.service_tier !== undefined && isServiceTier(data.service_tier)) {
464
+ options.serviceTier = data.service_tier;
465
+ }
466
+ if (data.presence_penalty !== undefined) options.presencePenalty = data.presence_penalty;
467
+ if (data.frequency_penalty !== undefined) options.frequencyPenalty = data.frequency_penalty;
468
+ if (data.parallel_tool_calls !== undefined) options.parallelToolCalls = data.parallel_tool_calls;
469
+ const cacheKey = resolvePromptCacheKey(body, headers);
470
+ if (cacheKey !== undefined) options.promptCacheKey = cacheKey;
471
+ if (data.previous_response_id !== undefined) options.previousResponseId = data.previous_response_id;
472
+ if (data.user !== undefined) options.user = data.user;
473
+ if (isObj(data.metadata)) options.metadata = data.metadata;
474
+ // `store` is a stateful-storage hint that vib's gateway doesn't honour;
475
+ // silently accepted by the schema. No typed slot — drop.
476
+
477
+ return {
478
+ modelId: data.model,
479
+ context,
480
+ stream: data.stream === true,
481
+ options,
482
+ };
483
+ }
484
+
485
+ function findToolNameById(messages: Message[], callId: string): string {
486
+ for (let i = messages.length - 1; i >= 0; i--) {
487
+ const m = messages[i];
488
+ if (m.role !== "assistant") continue;
489
+ for (const c of m.content) {
490
+ if (c.type === "toolCall" && c.id === callId) return c.name;
491
+ }
492
+ }
493
+ return "";
494
+ }
495
+
496
+ // ─── formatError ────────────────────────────────────────────────────────────
497
+
498
+ export function formatError(status: number, type: string, message: string): Response {
499
+ return new Response(JSON.stringify({ error: { message, type } }), {
500
+ status,
501
+ headers: { "Content-Type": "application/json" },
502
+ });
503
+ }
504
+
505
+ // ─── output item builders (shared by streaming + non-streaming encoders) ────
506
+
507
+ type ReasoningOutputItem = {
508
+ type: "reasoning";
509
+ id: string;
510
+ summary: Array<{ type: "summary_text"; text: string }>;
511
+ } & Record<string, unknown>;
512
+
513
+ type MessageOutputItem = {
514
+ type: "message";
515
+ id: string;
516
+ role: "assistant";
517
+ status: "completed";
518
+ content: Array<{ type: "output_text"; text: string; annotations: never[] }>;
519
+ };
520
+
521
+ type FunctionCallOutputItem = {
522
+ type: "function_call";
523
+ id: string;
524
+ call_id: string;
525
+ name: string;
526
+ arguments: string;
527
+ status: "completed";
528
+ };
529
+
530
+ type CustomToolCallOutputItem = {
531
+ type: "custom_tool_call";
532
+ id: string;
533
+ call_id: string;
534
+ name: string;
535
+ input: string;
536
+ status: "completed";
537
+ };
538
+
539
+ type OutputItem = ReasoningOutputItem | MessageOutputItem | FunctionCallOutputItem | CustomToolCallOutputItem;
540
+
541
+ type ResponseStatus = "completed" | "in_progress" | "failed" | "incomplete";
542
+
543
+ function responseStatusForStopReason(message: AssistantMessage): ResponseStatus {
544
+ if (message.stopReason === "length") return "incomplete";
545
+ if (message.stopReason === "error" || message.stopReason === "aborted") return "failed";
546
+ return "completed";
547
+ }
548
+
549
+ /**
550
+ * Privacy boundary for the public Responses envelope: a reasoning item's
551
+ * `summary_text` must carry ONLY provider-displayable summary text, never raw
552
+ * chain-of-thought. A summary is published ONLY for blocks explicitly marked
553
+ * `provenance: "summary" | "mixed"` (the #2304 provenance path), sourced from
554
+ * `summaryText`. Raw-provenance AND unmarked blocks are omitted: unmarked
555
+ * `thinking` can be raw CoT from providers that stream unmarked reasoning (e.g.
556
+ * openai-completions / ollama) which the auth gateway re-encodes into the
557
+ * Responses wire format, so falling open to `part.thinking` would leak raw CoT.
558
+ */
559
+ function envelopeSummaryText(part: ThinkingContent): string | undefined {
560
+ if (part.provenance === "summary" || part.provenance === "mixed") return part.summaryText;
561
+ return undefined;
562
+ }
563
+
564
+ function envelopeSummaryParts(part: ThinkingContent): Array<{ type: "summary_text"; text: string }> {
565
+ const text = envelopeSummaryText(part);
566
+ return text ? [{ type: "summary_text", text }] : [];
567
+ }
568
+
569
+ function normalizeSummaryParts(value: unknown): Array<{ type: "summary_text"; text: string }> {
570
+ // A serialized signature's `summary` is, by the Responses protocol, provider-
571
+ // displayable summary text (raw reasoning lives in content[]/encrypted_content,
572
+ // which is stripped). Coerce to the canonical shape, keeping only well-formed
573
+ // summary_text entries. This is NOT the unsafe `part.thinking` fallback.
574
+ if (!Array.isArray(value)) return [];
575
+ const out: Array<{ type: "summary_text"; text: string }> = [];
576
+ for (const entry of value) {
577
+ if (isObj(entry) && entry.type === "summary_text" && typeof entry.text === "string") {
578
+ out.push({ type: "summary_text", text: entry.text });
579
+ }
580
+ }
581
+ return out;
582
+ }
583
+
584
+ function buildReasoningItem(part: ThinkingContent): ReasoningOutputItem {
585
+ const baseId = part.itemId ?? makeReasoningId();
586
+ if (part.thinkingSignature) {
587
+ try {
588
+ const sigParsed: unknown = JSON.parse(part.thinkingSignature);
589
+ if (isObj(sigParsed) && sigParsed.type === "reasoning") {
590
+ const id = part.itemId ?? asString(sigParsed.id) ?? makeReasoningId();
591
+ // Preserve any extra fields (encrypted_content, …) the original carried,
592
+ // but normalize the summary into the canonical `{type, text}[]` shape.
593
+ const merged: Record<string, unknown> = { ...sigParsed, type: "reasoning", id };
594
+ merged.summary =
595
+ part.provenance === "summary" || part.provenance === "mixed"
596
+ ? envelopeSummaryParts(part)
597
+ : normalizeSummaryParts(sigParsed.summary);
598
+ // Strip any `content[]` (raw `reasoning_text`) the serialized signature
599
+ // carried: raw chain-of-thought must never surface in the public final
600
+ // envelope (#2304 CoT boundary). Opaque top-level `encrypted_content`
601
+ // (when present) is a separate field and is preserved by the spread above.
602
+ delete merged.content;
603
+ return merged as ReasoningOutputItem;
604
+ }
605
+ } catch {
606
+ // Not a serialized Responses reasoning item; fall through to fresh build.
607
+ }
608
+ }
609
+ return {
610
+ type: "reasoning",
611
+ id: baseId,
612
+ summary: envelopeSummaryParts(part),
613
+ };
614
+ }
615
+
616
+ function reasoningItemId(part: ThinkingContent): string {
617
+ if (part.itemId) return part.itemId;
618
+ if (part.thinkingSignature) {
619
+ try {
620
+ const sigParsed: unknown = JSON.parse(part.thinkingSignature);
621
+ if (isObj(sigParsed)) {
622
+ const id = asString(sigParsed.id);
623
+ if (id) return id;
624
+ }
625
+ } catch {
626
+ // Not a serialized Responses reasoning item.
627
+ }
628
+ }
629
+ return makeReasoningId();
630
+ }
631
+
632
+ /**
633
+ * Walk the assistant content array and group consecutive TextContent into a
634
+ * single message item; each ThinkingContent / ToolCall is its own item.
635
+ */
636
+ function buildOutputItems(message: AssistantMessage): OutputItem[] {
637
+ const out: OutputItem[] = [];
638
+ let pendingMessage: MessageOutputItem | null = null;
639
+ const flushMessage = () => {
640
+ if (pendingMessage) {
641
+ out.push(pendingMessage);
642
+ pendingMessage = null;
643
+ }
644
+ };
645
+
646
+ for (const part of message.content) {
647
+ if (part.type === "text") {
648
+ if (!pendingMessage) {
649
+ pendingMessage = {
650
+ type: "message",
651
+ id: makeMsgId(),
652
+ role: "assistant",
653
+ status: "completed",
654
+ content: [],
655
+ };
656
+ }
657
+ pendingMessage.content.push({ type: "output_text", text: part.text, annotations: [] });
658
+ } else if (part.type === "thinking") {
659
+ flushMessage();
660
+ out.push(buildReasoningItem(part));
661
+ } else if (part.type === "toolCall") {
662
+ flushMessage();
663
+ if (part.customWireName) {
664
+ const rawInput =
665
+ typeof part.arguments?.input === "string" ? (part.arguments.input as string).toWellFormed() : "";
666
+ out.push({
667
+ type: "custom_tool_call",
668
+ id: part.thoughtSignature ?? makeCustomCallId(),
669
+ call_id: part.id,
670
+ name: part.customWireName,
671
+ input: rawInput,
672
+ status: "completed",
673
+ });
674
+ } else {
675
+ out.push({
676
+ type: "function_call",
677
+ id: part.thoughtSignature ?? makeFuncCallId(),
678
+ call_id: part.id,
679
+ name: part.name,
680
+ arguments: JSON.stringify(sanitizeJsonStrings(part.arguments ?? {})),
681
+ status: "completed",
682
+ });
683
+ }
684
+ }
685
+ // RedactedThinking / Image are silently dropped — no direct Responses wire representation.
686
+ }
687
+ flushMessage();
688
+ return out;
689
+ }
690
+
691
+ function buildUsage(message: AssistantMessage): Record<string, unknown> {
692
+ const u = message.usage;
693
+ const inputTokens = u.input + u.cacheRead + u.cacheWrite;
694
+ return {
695
+ input_tokens: inputTokens,
696
+ input_tokens_details: { cached_tokens: u.cacheRead },
697
+ output_tokens: u.output,
698
+ output_tokens_details: { reasoning_tokens: u.reasoningTokens ?? 0 },
699
+ total_tokens: inputTokens + u.output,
700
+ };
701
+ }
702
+
703
+ function buildResponseEnvelope(
704
+ message: AssistantMessage,
705
+ requestedModelId: string,
706
+ id: string,
707
+ status: ResponseStatus,
708
+ items: OutputItem[] | [],
709
+ usage: Record<string, unknown> | null,
710
+ ): Record<string, unknown> {
711
+ return {
712
+ id,
713
+ object: "response",
714
+ created_at: Math.floor(message.timestamp / 1000),
715
+ status,
716
+ model: requestedModelId,
717
+ output: items,
718
+ usage,
719
+ ...(status === "incomplete" ? { incomplete_details: { reason: "max_output_tokens" } } : {}),
720
+ ...(status === "failed" ? { error: { message: message.errorMessage ?? "response failed" } } : {}),
721
+ };
722
+ }
723
+
724
+ // ─── encodeResponse (non-streaming) ─────────────────────────────────────────
725
+
726
+ export function encodeResponse(message: AssistantMessage, requestedModelId: string): Record<string, unknown> {
727
+ const items = buildOutputItems(message);
728
+ return buildResponseEnvelope(
729
+ message,
730
+ requestedModelId,
731
+ makeRespId(),
732
+ responseStatusForStopReason(message),
733
+ items,
734
+ buildUsage(message),
735
+ );
736
+ }
737
+
738
+ // ─── encodeStream ───────────────────────────────────────────────────────────
739
+
740
+ interface OpenMessage {
741
+ kind: "message";
742
+ itemId: string;
743
+ outputIndex: number;
744
+ contentIndex: number;
745
+ currentPartText: string;
746
+ content: Array<{ type: "output_text"; text: string; annotations: never[] }>;
747
+ }
748
+ interface OpenReasoning {
749
+ kind: "reasoning";
750
+ itemId: string;
751
+ outputIndex: number;
752
+ summaryText: string;
753
+ summaryPartText: string;
754
+ }
755
+ interface OpenFunctionCall {
756
+ kind: "function_call";
757
+ itemId: string;
758
+ outputIndex: number;
759
+ callId: string;
760
+ name: string;
761
+ argsText: string;
762
+ /** Set when the underlying ToolCall is a custom-tool emission. */
763
+ customWireName?: string;
764
+ }
765
+ type OpenItem = OpenMessage | OpenReasoning | OpenFunctionCall;
766
+
767
+ function sseEvent(name: string, data: unknown): string {
768
+ return `event: ${name}\ndata: ${JSON.stringify(data)}\n\n`;
769
+ }
770
+
771
+ export function encodeStream(
772
+ events: AssistantMessageEventStream,
773
+ requestedModelId: string,
774
+ ): ReadableStream<Uint8Array> {
775
+ const encoder = new TextEncoder();
776
+ const responseId = makeRespId();
777
+ let sequenceNumber = 0;
778
+ const seq = () => sequenceNumber++;
779
+
780
+ return new ReadableStream<Uint8Array>({
781
+ async start(controller) {
782
+ const emit = (name: string, data: Record<string, unknown>) => {
783
+ controller.enqueue(encoder.encode(sseEvent(name, { type: name, sequence_number: seq(), ...data })));
784
+ };
785
+ const emitDone = () => controller.enqueue(encoder.encode("data: [DONE]\n\n"));
786
+
787
+ let createdAt = Math.floor(Date.now() / 1000);
788
+ let outputIndex = 0;
789
+ const state: { open: OpenItem | null } = { open: null };
790
+ const finishedItems: OutputItem[] = [];
791
+
792
+ const responseSnapshot = (status: ResponseStatus, output: OutputItem[] | []) => ({
793
+ id: responseId,
794
+ object: "response",
795
+ created_at: createdAt,
796
+ status,
797
+ model: requestedModelId,
798
+ output,
799
+ usage: null,
800
+ });
801
+
802
+ const openMessage = (): OpenMessage => {
803
+ const itemId = makeMsgId();
804
+ const item = {
805
+ type: "message" as const,
806
+ id: itemId,
807
+ status: "in_progress",
808
+ role: "assistant" as const,
809
+ content: [] as Array<{ type: "output_text"; text: string; annotations: never[] }>,
810
+ };
811
+ emit("response.output_item.added", { output_index: outputIndex, item });
812
+ const next: OpenMessage = {
813
+ kind: "message",
814
+ itemId,
815
+ outputIndex,
816
+ contentIndex: 0,
817
+ currentPartText: "",
818
+ content: [],
819
+ };
820
+ state.open = next;
821
+ return next;
822
+ };
823
+
824
+ const openReasoning = (partial: AssistantMessage, contentIndex: number): OpenReasoning => {
825
+ const part = partial.content[contentIndex];
826
+ const itemId = part && part.type === "thinking" ? reasoningItemId(part) : makeReasoningId();
827
+ const item = {
828
+ type: "reasoning" as const,
829
+ id: itemId,
830
+ summary: [] as Array<{ type: "summary_text"; text: string }>,
831
+ };
832
+ emit("response.output_item.added", { output_index: outputIndex, item });
833
+ const next: OpenReasoning = {
834
+ kind: "reasoning",
835
+ itemId,
836
+ outputIndex,
837
+ summaryText: "",
838
+ summaryPartText: "",
839
+ };
840
+ state.open = next;
841
+ return next;
842
+ };
843
+
844
+ const openToolCall = (partial: AssistantMessage, contentIndex: number): OpenFunctionCall => {
845
+ const part = partial.content[contentIndex];
846
+ const tc = part && part.type === "toolCall" ? part : undefined;
847
+ const customWireName: string | undefined =
848
+ tc && typeof tc.customWireName === "string" && tc.customWireName.length > 0
849
+ ? tc.customWireName
850
+ : undefined;
851
+ const isCustom = customWireName !== undefined;
852
+ const itemId = tc?.thoughtSignature ?? (isCustom ? makeCustomCallId() : makeFuncCallId());
853
+ const callId = tc?.id ?? "";
854
+ const name = customWireName ?? tc?.name ?? "";
855
+ const item = isCustom
856
+ ? {
857
+ type: "custom_tool_call" as const,
858
+ id: itemId,
859
+ call_id: callId,
860
+ name,
861
+ input: "",
862
+ status: "in_progress",
863
+ }
864
+ : {
865
+ type: "function_call" as const,
866
+ id: itemId,
867
+ call_id: callId,
868
+ name,
869
+ arguments: "",
870
+ status: "in_progress",
871
+ };
872
+ emit("response.output_item.added", { output_index: outputIndex, item });
873
+ const next: OpenFunctionCall = {
874
+ kind: "function_call",
875
+ itemId,
876
+ outputIndex,
877
+ callId,
878
+ name,
879
+ argsText: "",
880
+ ...(isCustom ? { customWireName } : {}),
881
+ };
882
+ state.open = next;
883
+ return next;
884
+ };
885
+
886
+ const closeOpen = () => {
887
+ if (!state.open) return;
888
+ if (state.open.kind === "message") {
889
+ const item = {
890
+ type: "message",
891
+ id: state.open.itemId,
892
+ status: "completed",
893
+ role: "assistant",
894
+ content: state.open.content,
895
+ };
896
+ emit("response.output_item.done", { output_index: state.open.outputIndex, item });
897
+ finishedItems.push({
898
+ type: "message",
899
+ id: state.open.itemId,
900
+ role: "assistant",
901
+ status: "completed",
902
+ content: state.open.content,
903
+ });
904
+ } else if (state.open.kind === "reasoning") {
905
+ const summary = state.open.summaryText
906
+ ? [{ type: "summary_text" as const, text: state.open.summaryText }]
907
+ : [];
908
+ // Final reasoning envelope carries the displayable summary ONLY. Raw
909
+ // chain-of-thought is streamed live via response.reasoning_text.delta
910
+ // (the internal raw channel) and is deliberately NOT persisted into the
911
+ // terminal item's content[] — the public final envelope must never carry
912
+ // raw CoT (#2304 CoT boundary).
913
+ const item: ReasoningOutputItem = {
914
+ type: "reasoning",
915
+ id: state.open.itemId,
916
+ summary,
917
+ };
918
+ emit("response.output_item.done", { output_index: state.open.outputIndex, item });
919
+ finishedItems.push(item);
920
+ } else {
921
+ const text = state.open.argsText ?? "";
922
+ if (state.open.customWireName) {
923
+ const item = {
924
+ type: "custom_tool_call",
925
+ id: state.open.itemId,
926
+ call_id: state.open.callId ?? "",
927
+ name: state.open.customWireName,
928
+ input: text,
929
+ status: "completed",
930
+ };
931
+ emit("response.output_item.done", { output_index: state.open.outputIndex, item });
932
+ finishedItems.push({
933
+ type: "custom_tool_call",
934
+ id: state.open.itemId,
935
+ call_id: state.open.callId ?? "",
936
+ name: state.open.customWireName,
937
+ input: text,
938
+ status: "completed",
939
+ });
940
+ } else {
941
+ const item = {
942
+ type: "function_call",
943
+ id: state.open.itemId,
944
+ call_id: state.open.callId ?? "",
945
+ name: state.open.name ?? "",
946
+ arguments: text,
947
+ status: "completed",
948
+ };
949
+ emit("response.output_item.done", { output_index: state.open.outputIndex, item });
950
+ finishedItems.push({
951
+ type: "function_call",
952
+ id: state.open.itemId,
953
+ call_id: state.open.callId ?? "",
954
+ name: state.open.name ?? "",
955
+ arguments: text,
956
+ status: "completed",
957
+ });
958
+ }
959
+ }
960
+ outputIndex++;
961
+ state.open = null;
962
+ };
963
+
964
+ try {
965
+ let finalMessage: AssistantMessage | null = null;
966
+ let failureMessage: AssistantMessage | null = null;
967
+
968
+ for await (const ev of events) {
969
+ switch (ev.type) {
970
+ case "start": {
971
+ createdAt = Math.floor((ev.partial.timestamp || Date.now()) / 1000);
972
+ // response.created — initial envelope.
973
+ controller.enqueue(
974
+ encoder.encode(
975
+ sseEvent("response.created", {
976
+ type: "response.created",
977
+ sequence_number: seq(),
978
+ response: responseSnapshot("in_progress", []),
979
+ }),
980
+ ),
981
+ );
982
+ // response.in_progress — mirrors real OpenAI; some clients gate
983
+ // on it before reading items.
984
+ controller.enqueue(
985
+ encoder.encode(
986
+ sseEvent("response.in_progress", {
987
+ type: "response.in_progress",
988
+ sequence_number: seq(),
989
+ response: responseSnapshot("in_progress", []),
990
+ }),
991
+ ),
992
+ );
993
+ break;
994
+ }
995
+ case "text_start": {
996
+ let cur: OpenMessage;
997
+ if (state.open && state.open.kind === "message") {
998
+ // continue same message item, new content part
999
+ cur = state.open;
1000
+ cur.currentPartText = "";
1001
+ } else {
1002
+ if (state.open) closeOpen();
1003
+ cur = openMessage();
1004
+ }
1005
+ const part = { type: "output_text", text: "", annotations: [] as never[] };
1006
+ emit("response.content_part.added", {
1007
+ item_id: cur.itemId,
1008
+ output_index: cur.outputIndex,
1009
+ content_index: cur.contentIndex,
1010
+ part,
1011
+ });
1012
+ break;
1013
+ }
1014
+ case "text_delta": {
1015
+ if (state.open?.kind !== "message") break;
1016
+ const cur: OpenMessage = state.open;
1017
+ cur.currentPartText += ev.delta;
1018
+ emit("response.output_text.delta", {
1019
+ item_id: cur.itemId,
1020
+ output_index: cur.outputIndex,
1021
+ content_index: cur.contentIndex,
1022
+ delta: ev.delta,
1023
+ logprobs: [],
1024
+ });
1025
+ // TODO: when pi-ai surfaces output_text annotations
1026
+ // (web_search citations, …), emit
1027
+ // `response.output_text.annotation.added` here.
1028
+ break;
1029
+ }
1030
+ case "text_end": {
1031
+ if (state.open?.kind !== "message") break;
1032
+ const cur: OpenMessage = state.open;
1033
+ const text = ev.content ?? cur.currentPartText;
1034
+ emit("response.output_text.done", {
1035
+ item_id: cur.itemId,
1036
+ output_index: cur.outputIndex,
1037
+ content_index: cur.contentIndex,
1038
+ text,
1039
+ logprobs: [],
1040
+ });
1041
+ cur.content.push({ type: "output_text", text, annotations: [] });
1042
+ emit("response.content_part.done", {
1043
+ item_id: cur.itemId,
1044
+ output_index: cur.outputIndex,
1045
+ content_index: cur.contentIndex,
1046
+ part: { type: "output_text", text, annotations: [] },
1047
+ });
1048
+ cur.contentIndex += 1;
1049
+ cur.currentPartText = "";
1050
+ break;
1051
+ }
1052
+ case "thinking_start": {
1053
+ if (state.open) closeOpen();
1054
+ openReasoning(ev.partial, ev.contentIndex);
1055
+ break;
1056
+ }
1057
+ case "thinking_delta": {
1058
+ // Raw reasoning is private. The public Responses gateway emits only
1059
+ // provider-displayable reasoning_summary_* events.
1060
+ break;
1061
+ }
1062
+ case "thinking_end": {
1063
+ if (state.open?.kind !== "reasoning") break;
1064
+ // Raw reasoning is intentionally omitted from every public gateway
1065
+ // frame. Only reasoning_summary_* events populate the terminal item.
1066
+ closeOpen();
1067
+ break;
1068
+ }
1069
+ case "reasoning_summary_start": {
1070
+ if (state.open?.kind !== "reasoning") break;
1071
+ const cur: OpenReasoning = state.open;
1072
+ cur.summaryPartText = "";
1073
+ emit("response.reasoning_summary_part.added", {
1074
+ item_id: cur.itemId,
1075
+ output_index: cur.outputIndex,
1076
+ summary_index: 0,
1077
+ part: { type: "summary_text", text: "" },
1078
+ });
1079
+ break;
1080
+ }
1081
+ case "reasoning_summary_delta": {
1082
+ if (state.open?.kind !== "reasoning") break;
1083
+ const cur: OpenReasoning = state.open;
1084
+ cur.summaryPartText += ev.delta;
1085
+ cur.summaryText += ev.delta;
1086
+ emit("response.reasoning_summary_text.delta", {
1087
+ item_id: cur.itemId,
1088
+ output_index: cur.outputIndex,
1089
+ summary_index: 0,
1090
+ delta: ev.delta,
1091
+ });
1092
+ break;
1093
+ }
1094
+ case "reasoning_summary_end": {
1095
+ if (state.open?.kind !== "reasoning") break;
1096
+ const cur: OpenReasoning = state.open;
1097
+ const text = ev.content ?? cur.summaryPartText;
1098
+ // A separator-only accumulated summary (e.g. a part.done "\n\n" before any
1099
+ // real text) is treated as empty so the real end content wins.
1100
+ if (!cur.summaryText.trim()) cur.summaryText = text;
1101
+ emit("response.reasoning_summary_text.done", {
1102
+ item_id: cur.itemId,
1103
+ output_index: cur.outputIndex,
1104
+ summary_index: 0,
1105
+ text,
1106
+ });
1107
+ emit("response.reasoning_summary_part.done", {
1108
+ item_id: cur.itemId,
1109
+ output_index: cur.outputIndex,
1110
+ summary_index: 0,
1111
+ part: { type: "summary_text", text },
1112
+ });
1113
+ break;
1114
+ }
1115
+ case "toolcall_start": {
1116
+ if (state.open) closeOpen();
1117
+ openToolCall(ev.partial, ev.contentIndex);
1118
+ break;
1119
+ }
1120
+ case "toolcall_delta": {
1121
+ if (state.open?.kind !== "function_call") break;
1122
+ const cur: OpenFunctionCall = state.open;
1123
+ cur.argsText += ev.delta;
1124
+ if (cur.customWireName) {
1125
+ emit("response.custom_tool_call_input.delta", {
1126
+ item_id: cur.itemId,
1127
+ output_index: cur.outputIndex,
1128
+ delta: ev.delta,
1129
+ });
1130
+ } else {
1131
+ emit("response.function_call_arguments.delta", {
1132
+ item_id: cur.itemId,
1133
+ output_index: cur.outputIndex,
1134
+ delta: ev.delta,
1135
+ });
1136
+ }
1137
+ break;
1138
+ }
1139
+ case "toolcall_end": {
1140
+ if (state.open?.kind !== "function_call") break;
1141
+ const cur: OpenFunctionCall = state.open;
1142
+ // Promote possibly-late info from the canonical ToolCall.
1143
+ const tc = ev.toolCall;
1144
+ if (tc.customWireName && !cur.customWireName) cur.customWireName = tc.customWireName;
1145
+ if (tc.thoughtSignature) cur.itemId = tc.thoughtSignature;
1146
+ cur.callId = tc.id;
1147
+ cur.name = cur.customWireName ?? tc.name;
1148
+ if (cur.customWireName) {
1149
+ // Custom tool: raw input string. Streamed deltas accumulated
1150
+ // the wire-level body; fall back to `arguments.input` from
1151
+ // the finalized ToolCall when nothing streamed (rare).
1152
+ const rawInput =
1153
+ cur.argsText ||
1154
+ (typeof tc.arguments?.input === "string" ? (tc.arguments.input as string) : "");
1155
+ cur.argsText = rawInput;
1156
+ emit("response.custom_tool_call_input.done", {
1157
+ item_id: cur.itemId,
1158
+ output_index: cur.outputIndex,
1159
+ input: rawInput,
1160
+ name: cur.name,
1161
+ });
1162
+ } else {
1163
+ // Standard JSON tool: arguments object on the vib side, the
1164
+ // wire wants the JSON string the model emitted (= streamed deltas).
1165
+ const argsJson =
1166
+ cur.argsText.toWellFormed() || JSON.stringify(sanitizeJsonStrings(tc.arguments ?? {}));
1167
+ cur.argsText = argsJson;
1168
+ emit("response.function_call_arguments.done", {
1169
+ item_id: cur.itemId,
1170
+ output_index: cur.outputIndex,
1171
+ arguments: argsJson,
1172
+ name: cur.name,
1173
+ });
1174
+ }
1175
+ closeOpen();
1176
+ break;
1177
+ }
1178
+ case "done": {
1179
+ finalMessage = ev.message;
1180
+ break;
1181
+ }
1182
+ case "error": {
1183
+ failureMessage = ev.error;
1184
+ break;
1185
+ }
1186
+ }
1187
+ }
1188
+
1189
+ if (failureMessage) {
1190
+ if (state.open) closeOpen();
1191
+ controller.enqueue(
1192
+ encoder.encode(
1193
+ sseEvent("response.failed", {
1194
+ type: "response.failed",
1195
+ sequence_number: seq(),
1196
+ response: {
1197
+ ...responseSnapshot("failed", finishedItems),
1198
+ error: { message: failureMessage.errorMessage ?? "stream failed" },
1199
+ },
1200
+ }),
1201
+ ),
1202
+ );
1203
+ emitDone();
1204
+ controller.close();
1205
+ return;
1206
+ }
1207
+
1208
+ if (state.open) closeOpen();
1209
+ const message = finalMessage ?? ((await events.result().catch(() => null)) as AssistantMessage | null);
1210
+
1211
+ // Build the canonical output from the final message so non-streaming
1212
+ // readers see the exact same shape they'd get from encodeResponse().
1213
+ const items = message ? buildOutputItems(message) : finishedItems;
1214
+ const usage = message ? buildUsage(message) : null;
1215
+ const status = message ? responseStatusForStopReason(message) : "completed";
1216
+ const terminalEvent =
1217
+ status === "incomplete"
1218
+ ? "response.incomplete"
1219
+ : status === "failed"
1220
+ ? "response.failed"
1221
+ : "response.completed";
1222
+ controller.enqueue(
1223
+ encoder.encode(
1224
+ sseEvent(terminalEvent, {
1225
+ type: terminalEvent,
1226
+ sequence_number: seq(),
1227
+ response: {
1228
+ id: responseId,
1229
+ object: "response",
1230
+ created_at: createdAt,
1231
+ status,
1232
+ model: requestedModelId,
1233
+ output: items,
1234
+ usage,
1235
+ ...(status === "incomplete" ? { incomplete_details: { reason: "max_output_tokens" } } : {}),
1236
+ ...(status === "failed"
1237
+ ? { error: { message: message?.errorMessage ?? "response failed" } }
1238
+ : {}),
1239
+ },
1240
+ }),
1241
+ ),
1242
+ );
1243
+ emitDone();
1244
+ controller.close();
1245
+ } catch (err) {
1246
+ controller.enqueue(
1247
+ encoder.encode(
1248
+ sseEvent("response.failed", {
1249
+ type: "response.failed",
1250
+ sequence_number: seq(),
1251
+ response: {
1252
+ id: responseId,
1253
+ object: "response",
1254
+ created_at: Math.floor(Date.now() / 1000),
1255
+ status: "failed",
1256
+ model: requestedModelId,
1257
+ output: [],
1258
+ error: { message: err instanceof Error ? err.message : String(err) },
1259
+ },
1260
+ }),
1261
+ ),
1262
+ );
1263
+ emitDone();
1264
+ controller.close();
1265
+ }
1266
+ },
1267
+ });
1268
+ }