@gajae-code/ai 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (349) hide show
  1. package/CHANGELOG.md +2644 -0
  2. package/README.md +1181 -0
  3. package/dist/types/api-registry.d.ts +30 -0
  4. package/dist/types/auth-broker/client.d.ts +66 -0
  5. package/dist/types/auth-broker/index.d.ts +5 -0
  6. package/dist/types/auth-broker/refresher.d.ts +25 -0
  7. package/dist/types/auth-broker/remote-store.d.ts +96 -0
  8. package/dist/types/auth-broker/server.d.ts +32 -0
  9. package/dist/types/auth-broker/types.d.ts +105 -0
  10. package/dist/types/auth-broker/wire-schemas.d.ts +412 -0
  11. package/dist/types/auth-gateway/http.d.ts +39 -0
  12. package/dist/types/auth-gateway/index.d.ts +3 -0
  13. package/dist/types/auth-gateway/server.d.ts +17 -0
  14. package/dist/types/auth-gateway/types.d.ts +115 -0
  15. package/dist/types/auth-storage.d.ts +641 -0
  16. package/dist/types/cli.d.ts +2 -0
  17. package/dist/types/index.d.ts +49 -0
  18. package/dist/types/model-cache.d.ts +17 -0
  19. package/dist/types/model-manager.d.ts +62 -0
  20. package/dist/types/model-thinking.d.ts +71 -0
  21. package/dist/types/models.d.ts +12 -0
  22. package/dist/types/provider-details.d.ts +24 -0
  23. package/dist/types/provider-models/bundled-references.d.ts +4 -0
  24. package/dist/types/provider-models/descriptors.d.ts +48 -0
  25. package/dist/types/provider-models/google.d.ts +20 -0
  26. package/dist/types/provider-models/index.d.ts +5 -0
  27. package/dist/types/provider-models/ollama.d.ts +7 -0
  28. package/dist/types/provider-models/openai-compat.d.ts +237 -0
  29. package/dist/types/provider-models/special.d.ts +16 -0
  30. package/dist/types/providers/amazon-bedrock.d.ts +36 -0
  31. package/dist/types/providers/anthropic-messages-server-schema.d.ts +450 -0
  32. package/dist/types/providers/anthropic-messages-server.d.ts +17 -0
  33. package/dist/types/providers/anthropic.d.ts +188 -0
  34. package/dist/types/providers/aws-credentials.d.ts +43 -0
  35. package/dist/types/providers/aws-eventstream.d.ts +38 -0
  36. package/dist/types/providers/aws-sigv4.d.ts +55 -0
  37. package/dist/types/providers/azure-openai-responses.d.ts +15 -0
  38. package/dist/types/providers/cursor/gen/agent_pb.d.ts +13022 -0
  39. package/dist/types/providers/cursor.d.ts +42 -0
  40. package/dist/types/providers/error-message.d.ts +27 -0
  41. package/dist/types/providers/github-copilot-headers.d.ts +40 -0
  42. package/dist/types/providers/gitlab-duo.d.ts +27 -0
  43. package/dist/types/providers/google-auth.d.ts +24 -0
  44. package/dist/types/providers/google-gemini-cli.d.ts +72 -0
  45. package/dist/types/providers/google-gemini-headers.d.ts +18 -0
  46. package/dist/types/providers/google-shared.d.ts +163 -0
  47. package/dist/types/providers/google-types.d.ts +138 -0
  48. package/dist/types/providers/google-vertex.d.ts +7 -0
  49. package/dist/types/providers/google.d.ts +4 -0
  50. package/dist/types/providers/grammar.d.ts +1 -0
  51. package/dist/types/providers/kimi.d.ts +27 -0
  52. package/dist/types/providers/mock.d.ts +175 -0
  53. package/dist/types/providers/ollama.d.ts +6 -0
  54. package/dist/types/providers/openai-anthropic-shim.d.ts +31 -0
  55. package/dist/types/providers/openai-chat-server-schema.d.ts +814 -0
  56. package/dist/types/providers/openai-chat-server.d.ts +16 -0
  57. package/dist/types/providers/openai-codex/constants.d.ts +26 -0
  58. package/dist/types/providers/openai-codex/request-transformer.d.ts +49 -0
  59. package/dist/types/providers/openai-codex/response-handler.d.ts +17 -0
  60. package/dist/types/providers/openai-codex-responses.d.ts +67 -0
  61. package/dist/types/providers/openai-completions-compat.d.ts +25 -0
  62. package/dist/types/providers/openai-completions.d.ts +33 -0
  63. package/dist/types/providers/openai-responses-server-schema.d.ts +392 -0
  64. package/dist/types/providers/openai-responses-server.d.ts +17 -0
  65. package/dist/types/providers/openai-responses-shared.d.ts +89 -0
  66. package/dist/types/providers/openai-responses.d.ts +32 -0
  67. package/dist/types/providers/pi-native-client.d.ts +13 -0
  68. package/dist/types/providers/pi-native-server.d.ts +68 -0
  69. package/dist/types/providers/register-builtins.d.ts +31 -0
  70. package/dist/types/providers/synthetic.d.ts +26 -0
  71. package/dist/types/providers/transform-messages.d.ts +12 -0
  72. package/dist/types/providers/vision-guard.d.ts +8 -0
  73. package/dist/types/rate-limit-utils.d.ts +19 -0
  74. package/dist/types/stream.d.ts +24 -0
  75. package/dist/types/types.d.ts +746 -0
  76. package/dist/types/usage/claude.d.ts +3 -0
  77. package/dist/types/usage/gemini.d.ts +2 -0
  78. package/dist/types/usage/github-copilot.d.ts +7 -0
  79. package/dist/types/usage/google-antigravity.d.ts +2 -0
  80. package/dist/types/usage/kimi.d.ts +2 -0
  81. package/dist/types/usage/minimax-code.d.ts +2 -0
  82. package/dist/types/usage/openai-codex.d.ts +3 -0
  83. package/dist/types/usage/shared.d.ts +1 -0
  84. package/dist/types/usage/zai.d.ts +2 -0
  85. package/dist/types/usage.d.ts +258 -0
  86. package/dist/types/utils/abort.d.ts +19 -0
  87. package/dist/types/utils/anthropic-auth.d.ts +31 -0
  88. package/dist/types/utils/discovery/antigravity.d.ts +61 -0
  89. package/dist/types/utils/discovery/codex.d.ts +38 -0
  90. package/dist/types/utils/discovery/cursor.d.ts +23 -0
  91. package/dist/types/utils/discovery/gemini.d.ts +25 -0
  92. package/dist/types/utils/discovery/index.d.ts +4 -0
  93. package/dist/types/utils/discovery/openai-compatible.d.ts +72 -0
  94. package/dist/types/utils/event-stream.d.ts +28 -0
  95. package/dist/types/utils/fireworks-model-id.d.ts +10 -0
  96. package/dist/types/utils/foundry.d.ts +1 -0
  97. package/dist/types/utils/h2-fetch.d.ts +22 -0
  98. package/dist/types/utils/http-inspector.d.ts +31 -0
  99. package/dist/types/utils/idle-iterator.d.ts +67 -0
  100. package/dist/types/utils/json-parse.d.ts +10 -0
  101. package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +18 -0
  102. package/dist/types/utils/oauth/anthropic.d.ts +22 -0
  103. package/dist/types/utils/oauth/api-key-login.d.ts +35 -0
  104. package/dist/types/utils/oauth/api-key-validation.d.ts +27 -0
  105. package/dist/types/utils/oauth/callback-server.d.ts +57 -0
  106. package/dist/types/utils/oauth/cerebras.d.ts +1 -0
  107. package/dist/types/utils/oauth/cloudflare-ai-gateway.d.ts +18 -0
  108. package/dist/types/utils/oauth/cursor.d.ts +15 -0
  109. package/dist/types/utils/oauth/deepseek.d.ts +10 -0
  110. package/dist/types/utils/oauth/firepass.d.ts +1 -0
  111. package/dist/types/utils/oauth/fireworks.d.ts +1 -0
  112. package/dist/types/utils/oauth/github-copilot.d.ts +38 -0
  113. package/dist/types/utils/oauth/gitlab-duo.d.ts +3 -0
  114. package/dist/types/utils/oauth/google-antigravity.d.ts +11 -0
  115. package/dist/types/utils/oauth/google-gemini-cli.d.ts +10 -0
  116. package/dist/types/utils/oauth/google-oauth-shared.d.ts +28 -0
  117. package/dist/types/utils/oauth/huggingface.d.ts +19 -0
  118. package/dist/types/utils/oauth/index.d.ts +38 -0
  119. package/dist/types/utils/oauth/kagi.d.ts +17 -0
  120. package/dist/types/utils/oauth/kilo.d.ts +5 -0
  121. package/dist/types/utils/oauth/kimi.d.ts +21 -0
  122. package/dist/types/utils/oauth/litellm.d.ts +18 -0
  123. package/dist/types/utils/oauth/lm-studio.d.ts +17 -0
  124. package/dist/types/utils/oauth/minimax-code.d.ts +28 -0
  125. package/dist/types/utils/oauth/moonshot.d.ts +1 -0
  126. package/dist/types/utils/oauth/nanogpt.d.ts +1 -0
  127. package/dist/types/utils/oauth/nvidia.d.ts +18 -0
  128. package/dist/types/utils/oauth/ollama-cloud.d.ts +2 -0
  129. package/dist/types/utils/oauth/ollama.d.ts +18 -0
  130. package/dist/types/utils/oauth/openai-codex.d.ts +21 -0
  131. package/dist/types/utils/oauth/opencode.d.ts +18 -0
  132. package/dist/types/utils/oauth/parallel.d.ts +17 -0
  133. package/dist/types/utils/oauth/perplexity.d.ts +9 -0
  134. package/dist/types/utils/oauth/pkce.d.ts +8 -0
  135. package/dist/types/utils/oauth/qianfan.d.ts +17 -0
  136. package/dist/types/utils/oauth/qwen-portal.d.ts +19 -0
  137. package/dist/types/utils/oauth/synthetic.d.ts +1 -0
  138. package/dist/types/utils/oauth/tavily.d.ts +17 -0
  139. package/dist/types/utils/oauth/together.d.ts +1 -0
  140. package/dist/types/utils/oauth/types.d.ts +44 -0
  141. package/dist/types/utils/oauth/venice.d.ts +18 -0
  142. package/dist/types/utils/oauth/vercel-ai-gateway.d.ts +18 -0
  143. package/dist/types/utils/oauth/vllm.d.ts +16 -0
  144. package/dist/types/utils/oauth/xiaomi.d.ts +19 -0
  145. package/dist/types/utils/oauth/zai.d.ts +18 -0
  146. package/dist/types/utils/oauth/zenmux.d.ts +1 -0
  147. package/dist/types/utils/overflow.d.ts +54 -0
  148. package/dist/types/utils/parse-bind.d.ts +23 -0
  149. package/dist/types/utils/provider-response.d.ts +3 -0
  150. package/dist/types/utils/retry-after.d.ts +3 -0
  151. package/dist/types/utils/retry.d.ts +26 -0
  152. package/dist/types/utils/schema/adapt.d.ts +24 -0
  153. package/dist/types/utils/schema/compatibility.d.ts +30 -0
  154. package/dist/types/utils/schema/dereference.d.ts +11 -0
  155. package/dist/types/utils/schema/draft.d.ts +10 -0
  156. package/dist/types/utils/schema/equality.d.ts +4 -0
  157. package/dist/types/utils/schema/fields.d.ts +49 -0
  158. package/dist/types/utils/schema/index.d.ts +13 -0
  159. package/dist/types/utils/schema/json-schema-validator.d.ts +12 -0
  160. package/dist/types/utils/schema/meta-validator.d.ts +2 -0
  161. package/dist/types/utils/schema/normalize.d.ts +93 -0
  162. package/dist/types/utils/schema/spill.d.ts +8 -0
  163. package/dist/types/utils/schema/stamps.d.ts +25 -0
  164. package/dist/types/utils/schema/types.d.ts +4 -0
  165. package/dist/types/utils/schema/wire.d.ts +54 -0
  166. package/dist/types/utils/schema/zod-decontaminate.d.ts +31 -0
  167. package/dist/types/utils/sse-debug.d.ts +10 -0
  168. package/dist/types/utils/tool-call-healing.d.ts +71 -0
  169. package/dist/types/utils/tool-choice.d.ts +50 -0
  170. package/dist/types/utils/validation.d.ts +17 -0
  171. package/dist/types/utils.d.ts +28 -0
  172. package/package.json +146 -0
  173. package/src/api-registry.ts +96 -0
  174. package/src/auth-broker/client.ts +358 -0
  175. package/src/auth-broker/index.ts +5 -0
  176. package/src/auth-broker/refresher.ts +127 -0
  177. package/src/auth-broker/remote-store.ts +623 -0
  178. package/src/auth-broker/server.ts +644 -0
  179. package/src/auth-broker/types.ts +127 -0
  180. package/src/auth-broker/wire-schemas.ts +200 -0
  181. package/src/auth-gateway/http.ts +194 -0
  182. package/src/auth-gateway/index.ts +3 -0
  183. package/src/auth-gateway/server.ts +717 -0
  184. package/src/auth-gateway/types.ts +134 -0
  185. package/src/auth-storage.ts +4104 -0
  186. package/src/cli.ts +262 -0
  187. package/src/index.ts +54 -0
  188. package/src/model-cache.ts +129 -0
  189. package/src/model-manager.ts +450 -0
  190. package/src/model-thinking.ts +691 -0
  191. package/src/models.json +73853 -0
  192. package/src/models.json.d.ts +9 -0
  193. package/src/models.ts +56 -0
  194. package/src/prompts/turn-aborted-guidance.md +4 -0
  195. package/src/provider-details.ts +90 -0
  196. package/src/provider-models/bundled-references.ts +38 -0
  197. package/src/provider-models/descriptors.ts +308 -0
  198. package/src/provider-models/google.ts +91 -0
  199. package/src/provider-models/index.ts +5 -0
  200. package/src/provider-models/ollama.ts +153 -0
  201. package/src/provider-models/openai-compat.ts +2275 -0
  202. package/src/provider-models/special.ts +67 -0
  203. package/src/providers/amazon-bedrock.ts +849 -0
  204. package/src/providers/anthropic-messages-server-schema.ts +229 -0
  205. package/src/providers/anthropic-messages-server.ts +677 -0
  206. package/src/providers/anthropic.ts +2696 -0
  207. package/src/providers/aws-credentials.ts +501 -0
  208. package/src/providers/aws-eventstream.ts +185 -0
  209. package/src/providers/aws-sigv4.ts +218 -0
  210. package/src/providers/azure-openai-responses.ts +337 -0
  211. package/src/providers/cursor/gen/agent_pb.ts +15274 -0
  212. package/src/providers/cursor/proto/agent.proto +3526 -0
  213. package/src/providers/cursor/proto/buf.gen.yaml +6 -0
  214. package/src/providers/cursor/proto/buf.yaml +17 -0
  215. package/src/providers/cursor.ts +2561 -0
  216. package/src/providers/error-message.ts +21 -0
  217. package/src/providers/github-copilot-headers.ts +140 -0
  218. package/src/providers/gitlab-duo.ts +372 -0
  219. package/src/providers/google-auth.ts +252 -0
  220. package/src/providers/google-gemini-cli.ts +795 -0
  221. package/src/providers/google-gemini-headers.ts +41 -0
  222. package/src/providers/google-shared.ts +902 -0
  223. package/src/providers/google-types.ts +167 -0
  224. package/src/providers/google-vertex.ts +88 -0
  225. package/src/providers/google.ts +41 -0
  226. package/src/providers/grammar.ts +70 -0
  227. package/src/providers/kimi.ts +52 -0
  228. package/src/providers/mock.ts +500 -0
  229. package/src/providers/ollama.ts +544 -0
  230. package/src/providers/openai-anthropic-shim.ts +138 -0
  231. package/src/providers/openai-chat-server-schema.ts +243 -0
  232. package/src/providers/openai-chat-server.ts +628 -0
  233. package/src/providers/openai-codex/constants.ts +43 -0
  234. package/src/providers/openai-codex/request-transformer.ts +161 -0
  235. package/src/providers/openai-codex/response-handler.ts +81 -0
  236. package/src/providers/openai-codex-responses.ts +2598 -0
  237. package/src/providers/openai-completions-compat.ts +279 -0
  238. package/src/providers/openai-completions.ts +1853 -0
  239. package/src/providers/openai-responses-server-schema.ts +290 -0
  240. package/src/providers/openai-responses-server.ts +1183 -0
  241. package/src/providers/openai-responses-shared.ts +800 -0
  242. package/src/providers/openai-responses.ts +621 -0
  243. package/src/providers/pi-native-client.ts +228 -0
  244. package/src/providers/pi-native-server.ts +210 -0
  245. package/src/providers/register-builtins.ts +412 -0
  246. package/src/providers/synthetic.ts +50 -0
  247. package/src/providers/transform-messages.ts +309 -0
  248. package/src/providers/vision-guard.ts +31 -0
  249. package/src/rate-limit-utils.ts +84 -0
  250. package/src/stream.ts +895 -0
  251. package/src/types.ts +884 -0
  252. package/src/usage/claude.ts +431 -0
  253. package/src/usage/gemini.ts +250 -0
  254. package/src/usage/github-copilot.ts +421 -0
  255. package/src/usage/google-antigravity.ts +201 -0
  256. package/src/usage/kimi.ts +271 -0
  257. package/src/usage/minimax-code.ts +31 -0
  258. package/src/usage/openai-codex.ts +503 -0
  259. package/src/usage/shared.ts +10 -0
  260. package/src/usage/zai.ts +247 -0
  261. package/src/usage.ts +183 -0
  262. package/src/utils/abort.ts +51 -0
  263. package/src/utils/anthropic-auth.ts +87 -0
  264. package/src/utils/discovery/antigravity.ts +261 -0
  265. package/src/utils/discovery/codex.ts +371 -0
  266. package/src/utils/discovery/cursor.ts +306 -0
  267. package/src/utils/discovery/gemini.ts +248 -0
  268. package/src/utils/discovery/index.ts +4 -0
  269. package/src/utils/discovery/openai-compatible.ts +224 -0
  270. package/src/utils/event-stream.ts +142 -0
  271. package/src/utils/fireworks-model-id.ts +30 -0
  272. package/src/utils/foundry.ts +8 -0
  273. package/src/utils/h2-fetch.ts +60 -0
  274. package/src/utils/http-inspector.ts +176 -0
  275. package/src/utils/idle-iterator.ts +250 -0
  276. package/src/utils/json-parse.ts +148 -0
  277. package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
  278. package/src/utils/oauth/anthropic.ts +200 -0
  279. package/src/utils/oauth/api-key-login.ts +87 -0
  280. package/src/utils/oauth/api-key-validation.ts +92 -0
  281. package/src/utils/oauth/callback-server.ts +276 -0
  282. package/src/utils/oauth/cerebras.ts +16 -0
  283. package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
  284. package/src/utils/oauth/cursor.ts +157 -0
  285. package/src/utils/oauth/deepseek.ts +53 -0
  286. package/src/utils/oauth/firepass.ts +24 -0
  287. package/src/utils/oauth/fireworks.ts +15 -0
  288. package/src/utils/oauth/github-copilot.ts +362 -0
  289. package/src/utils/oauth/gitlab-duo.ts +123 -0
  290. package/src/utils/oauth/google-antigravity.ts +200 -0
  291. package/src/utils/oauth/google-gemini-cli.ts +256 -0
  292. package/src/utils/oauth/google-oauth-shared.ts +110 -0
  293. package/src/utils/oauth/huggingface.ts +62 -0
  294. package/src/utils/oauth/index.ts +444 -0
  295. package/src/utils/oauth/kagi.ts +47 -0
  296. package/src/utils/oauth/kilo.ts +87 -0
  297. package/src/utils/oauth/kimi.ts +254 -0
  298. package/src/utils/oauth/litellm.ts +47 -0
  299. package/src/utils/oauth/lm-studio.ts +38 -0
  300. package/src/utils/oauth/minimax-code.ts +78 -0
  301. package/src/utils/oauth/moonshot.ts +16 -0
  302. package/src/utils/oauth/nanogpt.ts +15 -0
  303. package/src/utils/oauth/nvidia.ts +70 -0
  304. package/src/utils/oauth/oauth.html +199 -0
  305. package/src/utils/oauth/ollama-cloud.ts +28 -0
  306. package/src/utils/oauth/ollama.ts +47 -0
  307. package/src/utils/oauth/openai-codex.ts +299 -0
  308. package/src/utils/oauth/opencode.ts +49 -0
  309. package/src/utils/oauth/parallel.ts +46 -0
  310. package/src/utils/oauth/perplexity.ts +206 -0
  311. package/src/utils/oauth/pkce.ts +18 -0
  312. package/src/utils/oauth/qianfan.ts +58 -0
  313. package/src/utils/oauth/qwen-portal.ts +60 -0
  314. package/src/utils/oauth/synthetic.ts +16 -0
  315. package/src/utils/oauth/tavily.ts +46 -0
  316. package/src/utils/oauth/together.ts +16 -0
  317. package/src/utils/oauth/types.ts +94 -0
  318. package/src/utils/oauth/venice.ts +59 -0
  319. package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
  320. package/src/utils/oauth/vllm.ts +40 -0
  321. package/src/utils/oauth/xiaomi.ts +137 -0
  322. package/src/utils/oauth/zai.ts +60 -0
  323. package/src/utils/oauth/zenmux.ts +15 -0
  324. package/src/utils/overflow.ts +137 -0
  325. package/src/utils/parse-bind.ts +54 -0
  326. package/src/utils/provider-response.ts +30 -0
  327. package/src/utils/retry-after.ts +110 -0
  328. package/src/utils/retry.ts +54 -0
  329. package/src/utils/schema/CONSTRAINTS.md +164 -0
  330. package/src/utils/schema/adapt.ts +36 -0
  331. package/src/utils/schema/compatibility.ts +435 -0
  332. package/src/utils/schema/dereference.ts +98 -0
  333. package/src/utils/schema/draft.ts +341 -0
  334. package/src/utils/schema/equality.ts +97 -0
  335. package/src/utils/schema/fields.ts +190 -0
  336. package/src/utils/schema/index.ts +13 -0
  337. package/src/utils/schema/json-schema-validator.ts +577 -0
  338. package/src/utils/schema/meta-validator.ts +167 -0
  339. package/src/utils/schema/normalize.ts +1588 -0
  340. package/src/utils/schema/spill.ts +43 -0
  341. package/src/utils/schema/stamps.ts +97 -0
  342. package/src/utils/schema/types.ts +11 -0
  343. package/src/utils/schema/wire.ts +213 -0
  344. package/src/utils/schema/zod-decontaminate.ts +331 -0
  345. package/src/utils/sse-debug.ts +289 -0
  346. package/src/utils/tool-call-healing.ts +271 -0
  347. package/src/utils/tool-choice.ts +99 -0
  348. package/src/utils/validation.ts +1019 -0
  349. package/src/utils.ts +166 -0
@@ -0,0 +1,621 @@
1
+ import { $env, extractHttpStatusFromError, structuredCloneJSON } from "@gajae-code/utils";
2
+ import OpenAI from "openai";
3
+ import type {
4
+ Tool as OpenAITool,
5
+ ResponseCreateParamsStreaming,
6
+ ResponseInput,
7
+ } from "openai/resources/responses/responses";
8
+ import { getEnvApiKey } from "../stream";
9
+ import type {
10
+ AssistantMessage,
11
+ CacheRetention,
12
+ Context,
13
+ FetchImpl,
14
+ MessageAttribution,
15
+ Model,
16
+ OpenAICompat,
17
+ ProviderSessionState,
18
+ ServiceTier,
19
+ StreamFunction,
20
+ StreamOptions,
21
+ Tool,
22
+ ToolChoice,
23
+ } from "../types";
24
+ import {
25
+ createOpenAIResponsesHistoryPayload,
26
+ getOpenAIResponsesHistoryItems,
27
+ getOpenAIResponsesHistoryPayload,
28
+ normalizeSystemPrompts,
29
+ resolveCacheRetention,
30
+ sanitizeOpenAIResponsesHistoryItemsForReplay,
31
+ } from "../utils";
32
+ import { createAbortSourceTracker } from "../utils/abort";
33
+ import { AssistantMessageEventStream } from "../utils/event-stream";
34
+ import { finalizeErrorMessage, type RawHttpRequestDump, rewriteCopilotError } from "../utils/http-inspector";
35
+ import {
36
+ createWatchdog,
37
+ getOpenAIStreamIdleTimeoutMs,
38
+ getStreamFirstEventTimeoutMs,
39
+ iterateWithIdleTimeout,
40
+ } from "../utils/idle-iterator";
41
+ import { parseGitHubCopilotApiKey } from "../utils/oauth/github-copilot";
42
+ import { notifyProviderResponse } from "../utils/provider-response";
43
+ import { callWithCopilotModelRetry } from "../utils/retry";
44
+ import { adaptSchemaForStrict, NO_STRICT, sanitizeSchemaForOpenAIResponses, toolWireSchema } from "../utils/schema";
45
+ import { wrapFetchForSseDebug } from "../utils/sse-debug";
46
+ import { mapToOpenAIResponsesToolChoice, type OpenAIResponsesToolChoice } from "../utils/tool-choice";
47
+ import {
48
+ buildCopilotDynamicHeaders,
49
+ hasCopilotVisionInput,
50
+ resolveGitHubCopilotBaseUrl,
51
+ } from "./github-copilot-headers";
52
+ import { compactGrammarDefinition } from "./grammar";
53
+ import {
54
+ appendResponsesToolResultMessages,
55
+ applyCommonResponsesSamplingParams,
56
+ applyResponsesReasoningParams,
57
+ collectCustomCallIds,
58
+ collectKnownCallIds,
59
+ convertResponsesAssistantMessage,
60
+ convertResponsesInputContent,
61
+ createInitialResponsesAssistantMessage,
62
+ normalizeResponsesToolCallIdForTransform,
63
+ processResponsesStream,
64
+ repairOrphanResponsesToolOutputs,
65
+ } from "./openai-responses-shared";
66
+ import { transformMessages } from "./transform-messages";
67
+
68
+ /**
69
+ * Get prompt cache retention based on cacheRetention and base URL.
70
+ * Only applies to direct OpenAI API calls (api.openai.com).
71
+ */
72
+ function getPromptCacheRetention(baseUrl: string, cacheRetention: CacheRetention): "24h" | undefined {
73
+ if (cacheRetention !== "long") {
74
+ return undefined;
75
+ }
76
+ if (baseUrl.includes("api.openai.com")) {
77
+ return "24h";
78
+ }
79
+ return undefined;
80
+ }
81
+
82
+ export function normalizeOpenAIResponsesPromptCacheKey(sessionId: string | undefined): string | undefined {
83
+ if (!sessionId || sessionId.length === 0) return undefined;
84
+ const wellFormed = sessionId.toWellFormed();
85
+ if (wellFormed.length <= 64) return wellFormed;
86
+ return `pc_${Bun.hash(wellFormed).toString(36)}`;
87
+ }
88
+
89
+ // OpenAI Responses-specific options
90
+ export interface OpenAIResponsesOptions extends StreamOptions {
91
+ reasoning?: "minimal" | "low" | "medium" | "high" | "xhigh";
92
+ reasoningSummary?: "auto" | "detailed" | "concise" | null;
93
+ serviceTier?: ServiceTier;
94
+ toolChoice?: ToolChoice;
95
+ /**
96
+ * Enforce strict tool call/result pairing when building Responses API inputs.
97
+ * Azure OpenAI and GitHub Copilot Responses paths require tool results to match prior tool calls.
98
+ */
99
+ strictResponsesPairing?: boolean;
100
+ }
101
+
102
+ const OPENAI_RESPONSES_PROVIDER_SESSION_STATE_PREFIX = "openai-responses:";
103
+ const OPENAI_RESPONSES_FIRST_EVENT_TIMEOUT_MESSAGE =
104
+ "OpenAI responses stream timed out while waiting for the first event";
105
+
106
+ const OPENAI_RESPONSES_PROGRESS_EVENT_TYPES = new Set([
107
+ "response.created",
108
+ "response.output_item.added",
109
+ "response.reasoning_summary_part.added",
110
+ "response.reasoning_summary_text.delta",
111
+ "response.reasoning_summary_part.done",
112
+ "response.reasoning_text.delta",
113
+ "response.content_part.added",
114
+ "response.output_text.delta",
115
+ "response.refusal.delta",
116
+ "response.function_call_arguments.delta",
117
+ "response.function_call_arguments.done",
118
+ "response.custom_tool_call_input.delta",
119
+ "response.custom_tool_call_input.done",
120
+ "response.output_item.done",
121
+ "response.completed",
122
+ "response.failed",
123
+ "error",
124
+ ]);
125
+
126
+ function isOpenAIResponsesProgressEvent(event: unknown): boolean {
127
+ if (!event || typeof event !== "object") return false;
128
+ const type = (event as { type?: unknown }).type;
129
+ return typeof type === "string" && OPENAI_RESPONSES_PROGRESS_EVENT_TYPES.has(type);
130
+ }
131
+
132
+ interface OpenAIResponsesProviderSessionState extends ProviderSessionState {
133
+ nativeHistoryReplayWarmed: boolean;
134
+ }
135
+
136
+ function createOpenAIResponsesProviderSessionState(): OpenAIResponsesProviderSessionState {
137
+ const state: OpenAIResponsesProviderSessionState = {
138
+ nativeHistoryReplayWarmed: false,
139
+ close: () => {
140
+ state.nativeHistoryReplayWarmed = false;
141
+ },
142
+ };
143
+ return state;
144
+ }
145
+
146
+ function getOpenAIResponsesProviderSessionStateKey(model: Model<"openai-responses">): string {
147
+ return `${OPENAI_RESPONSES_PROVIDER_SESSION_STATE_PREFIX}${model.provider}`;
148
+ }
149
+
150
+ function getOpenAIResponsesProviderSessionState(
151
+ model: Model<"openai-responses">,
152
+ providerSessionState: Map<string, ProviderSessionState> | undefined,
153
+ ): OpenAIResponsesProviderSessionState | undefined {
154
+ if (!providerSessionState) return undefined;
155
+ const key = getOpenAIResponsesProviderSessionStateKey(model);
156
+ const existing = providerSessionState.get(key) as OpenAIResponsesProviderSessionState | undefined;
157
+ if (existing) return existing;
158
+ const created = createOpenAIResponsesProviderSessionState();
159
+ providerSessionState.set(key, created);
160
+ return created;
161
+ }
162
+
163
+ function canReplayOpenAIResponsesNativeHistory(
164
+ providerSessionState: OpenAIResponsesProviderSessionState | undefined,
165
+ ): boolean {
166
+ return providerSessionState?.nativeHistoryReplayWarmed ?? true;
167
+ }
168
+
169
+ type OpenAIResponsesSamplingParams = ResponseCreateParamsStreaming & {
170
+ top_p?: number;
171
+ top_k?: number;
172
+ min_p?: number;
173
+ presence_penalty?: number;
174
+ repetition_penalty?: number;
175
+ stream_options?: { include_obfuscation?: boolean };
176
+ };
177
+
178
+ /**
179
+ * Generate function for OpenAI Responses API
180
+ */
181
+ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
182
+ model: Model<"openai-responses">,
183
+ context: Context,
184
+ options?: OpenAIResponsesOptions,
185
+ ): AssistantMessageEventStream => {
186
+ const stream = new AssistantMessageEventStream();
187
+
188
+ // Start async processing
189
+ (async () => {
190
+ const startTime = Date.now();
191
+ let firstTokenTime: number | undefined;
192
+
193
+ const output: AssistantMessage = createInitialResponsesAssistantMessage(
194
+ "openai-responses",
195
+ model.provider,
196
+ model.id,
197
+ );
198
+ let rawRequestDump: RawHttpRequestDump | undefined;
199
+ const abortTracker = createAbortSourceTracker(options?.signal);
200
+ const firstEventTimeoutAbortError = new Error(OPENAI_RESPONSES_FIRST_EVENT_TIMEOUT_MESSAGE);
201
+ const { requestAbortController, requestSignal } = abortTracker;
202
+
203
+ try {
204
+ // Keep request headers and prompt-cache routing on the same session-derived value.
205
+ const cacheSessionId = getOpenAIResponsesCacheSessionId(options);
206
+ const apiKey = options?.apiKey || getEnvApiKey(model.provider) || "";
207
+ const { client, copilotPremiumRequests, baseUrl } = createClient(
208
+ model,
209
+ context,
210
+ apiKey,
211
+ options?.headers,
212
+ options?.initiatorOverride,
213
+ cacheSessionId,
214
+ options?.onSseEvent,
215
+ options?.fetch,
216
+ );
217
+ const premiumRequestsTotal = copilotPremiumRequests;
218
+ const providerSessionState = getOpenAIResponsesProviderSessionState(model, options?.providerSessionState);
219
+ const { params } = buildParams(model, context, options, providerSessionState, baseUrl);
220
+ const idleTimeoutMs = options?.streamIdleTimeoutMs ?? getOpenAIStreamIdleTimeoutMs();
221
+ options?.onPayload?.(params);
222
+ rawRequestDump = {
223
+ provider: model.provider,
224
+ api: output.api,
225
+ model: model.id,
226
+ method: "POST",
227
+ url: `${baseUrl ?? "https://api.openai.com/v1"}/responses`,
228
+ body: params,
229
+ };
230
+ const openaiStream = await callWithCopilotModelRetry(
231
+ async () => {
232
+ const { data, response, request_id } = await client.responses
233
+ .create(params, { signal: requestSignal })
234
+ .withResponse();
235
+ await notifyProviderResponse(options, response, model, request_id);
236
+ return data;
237
+ },
238
+ { provider: model.provider, signal: requestSignal },
239
+ );
240
+ const firstEventWatchdog = createWatchdog(
241
+ options?.streamFirstEventTimeoutMs ?? getStreamFirstEventTimeoutMs(idleTimeoutMs),
242
+ () => abortTracker.abortLocally(firstEventTimeoutAbortError),
243
+ );
244
+ if (premiumRequestsTotal !== undefined) output.usage.premiumRequests = premiumRequestsTotal;
245
+ stream.push({ type: "start", partial: output });
246
+
247
+ const nativeOutputItems: Array<Record<string, unknown>> = [];
248
+ await processResponsesStream(
249
+ iterateWithIdleTimeout(openaiStream, {
250
+ idleTimeoutMs,
251
+ watchdog: firstEventWatchdog,
252
+ errorMessage: "OpenAI responses stream stalled while waiting for the next event",
253
+ onIdle: () => requestAbortController.abort(),
254
+ abortSignal: options?.signal,
255
+ isProgressItem: isOpenAIResponsesProgressEvent,
256
+ }),
257
+ output,
258
+ stream,
259
+ model,
260
+ {
261
+ onFirstToken: () => {
262
+ if (!firstTokenTime) firstTokenTime = Date.now();
263
+ },
264
+ onOutputItemDone: item => {
265
+ nativeOutputItems.push(structuredCloneJSON<unknown>(item) as unknown as Record<string, unknown>);
266
+ },
267
+ },
268
+ );
269
+ if (premiumRequestsTotal !== undefined) output.usage.premiumRequests = premiumRequestsTotal;
270
+
271
+ const firstEventTimeoutError = abortTracker.getLocalAbortReason();
272
+ if (firstEventTimeoutError) {
273
+ throw firstEventTimeoutError;
274
+ }
275
+ if (abortTracker.wasCallerAbort()) {
276
+ throw new Error("Request was aborted");
277
+ }
278
+
279
+ if (output.stopReason === "aborted" || output.stopReason === "error") {
280
+ throw new Error(output.errorMessage ?? "An unknown error occurred");
281
+ }
282
+
283
+ output.providerPayload = createOpenAIResponsesHistoryPayload(model.provider, nativeOutputItems);
284
+ if (providerSessionState) providerSessionState.nativeHistoryReplayWarmed = true;
285
+
286
+ output.duration = Date.now() - startTime;
287
+ if (firstTokenTime) output.ttft = firstTokenTime - startTime;
288
+ stream.push({ type: "done", reason: output.stopReason, message: output });
289
+ stream.end();
290
+ } catch (error) {
291
+ for (const block of output.content) delete (block as { index?: number }).index;
292
+ const firstEventTimeoutError = abortTracker.getLocalAbortReason();
293
+ output.stopReason = abortTracker.wasCallerAbort() ? "aborted" : "error";
294
+ output.errorStatus = extractHttpStatusFromError(error);
295
+ output.errorMessage = firstEventTimeoutError?.message ?? (await finalizeErrorMessage(error, rawRequestDump));
296
+ output.errorMessage = rewriteCopilotError(output.errorMessage, error, model.provider);
297
+ output.duration = Date.now() - startTime;
298
+ if (firstTokenTime) output.ttft = firstTokenTime - startTime;
299
+ stream.push({ type: "error", reason: output.stopReason, error: output });
300
+ stream.end();
301
+ }
302
+ })();
303
+
304
+ return stream;
305
+ };
306
+
307
+ function createClient(
308
+ model: Model<"openai-responses">,
309
+ context: Context,
310
+ apiKey?: string,
311
+ extraHeaders?: Record<string, string>,
312
+ initiatorOverride?: MessageAttribution,
313
+ sessionId?: string,
314
+ onSseEvent?: OpenAIResponsesOptions["onSseEvent"],
315
+ fetchOverride?: FetchImpl,
316
+ ): {
317
+ client: OpenAI;
318
+ copilotPremiumRequests: number | undefined;
319
+ baseUrl: string | undefined;
320
+ } {
321
+ if (!apiKey) {
322
+ if (!$env.OPENAI_API_KEY) {
323
+ throw new Error(
324
+ "OpenAI API key is required. Set OPENAI_API_KEY environment variable or pass it as an argument.",
325
+ );
326
+ }
327
+ apiKey = $env.OPENAI_API_KEY;
328
+ }
329
+ const rawApiKey = apiKey;
330
+
331
+ const headers = { ...(model.headers ?? {}), ...(extraHeaders ?? {}) };
332
+ let copilotPremiumRequests: number | undefined;
333
+
334
+ let baseUrl = model.baseUrl;
335
+ if (model.provider === "github-copilot") {
336
+ apiKey = parseGitHubCopilotApiKey(rawApiKey).accessToken;
337
+ const hasImages = hasCopilotVisionInput(context.messages);
338
+ const copilot = buildCopilotDynamicHeaders({
339
+ messages: context.messages,
340
+ hasImages,
341
+ premiumMultiplier: model.premiumMultiplier,
342
+ headers,
343
+ initiatorOverride,
344
+ });
345
+ Object.assign(headers, copilot.headers);
346
+ copilotPremiumRequests = copilot.premiumRequests;
347
+ baseUrl = resolveGitHubCopilotBaseUrl(model.baseUrl, rawApiKey) ?? model.baseUrl;
348
+ }
349
+ if (sessionId && model.provider === "openai" && (baseUrl ?? "").toLowerCase().includes("api.openai.com")) {
350
+ headers.session_id ??= sessionId;
351
+ headers["x-client-request-id"] ??= sessionId;
352
+ }
353
+ const baseFetch = fetchOverride ?? fetch;
354
+ return {
355
+ client: new OpenAI({
356
+ apiKey,
357
+ baseURL: baseUrl,
358
+ dangerouslyAllowBrowser: true,
359
+ maxRetries: 5,
360
+ defaultHeaders: headers,
361
+ fetch: onSseEvent ? wrapFetchForSseDebug(baseFetch, event => onSseEvent(event, model)) : baseFetch,
362
+ }),
363
+ copilotPremiumRequests,
364
+ baseUrl,
365
+ };
366
+ }
367
+
368
+ function getOpenAIResponsesCacheSessionId(
369
+ options: Pick<OpenAIResponsesOptions, "cacheRetention" | "sessionId"> | undefined,
370
+ ): string | undefined {
371
+ return resolveCacheRetention(options?.cacheRetention) === "none"
372
+ ? undefined
373
+ : normalizeOpenAIResponsesPromptCacheKey(options?.sessionId);
374
+ }
375
+
376
+ function buildParams(
377
+ model: Model<"openai-responses">,
378
+ context: Context,
379
+ options: OpenAIResponsesOptions | undefined,
380
+ providerSessionState: OpenAIResponsesProviderSessionState | undefined,
381
+ resolvedBaseUrl?: string,
382
+ ): { conversationMessages: ResponseInput; params: OpenAIResponsesSamplingParams } {
383
+ const strictResponsesPairing =
384
+ options?.strictResponsesPairing ??
385
+ (isAzureOpenAIBaseUrl(model.baseUrl ?? "") || model.provider === "github-copilot");
386
+ const conversationMessages = convertConversationMessages(
387
+ model,
388
+ context,
389
+ strictResponsesPairing,
390
+ providerSessionState,
391
+ );
392
+ const messages: ResponseInput = [...conversationMessages];
393
+
394
+ const systemPrompts = normalizeSystemPrompts(context.systemPrompt);
395
+ let systemInstructions: string | undefined;
396
+ if (systemPrompts.length > 0) {
397
+ const needsDeveloperRole = model.reasoning && supportsDeveloperRole(resolvedBaseUrl ?? model);
398
+ if (needsDeveloperRole) {
399
+ // Reasoning models on known OpenAI-compatible endpoints require the
400
+ // `developer` role. Send all system prompts inline in `input`.
401
+ messages.unshift(
402
+ ...systemPrompts.map(systemPrompt => ({ role: "developer" as const, content: systemPrompt })),
403
+ );
404
+ } else {
405
+ // All other endpoints (including third-party /v1/responses proxies) use
406
+ // the canonical top-level `instructions` field so that proxies that
407
+ // reject `input[{role:"system"}]` work out of the box.
408
+ systemInstructions = systemPrompts.join("\n\n");
409
+ }
410
+ }
411
+
412
+ const cacheRetention = resolveCacheRetention(options?.cacheRetention);
413
+ const promptCacheKey = getOpenAIResponsesCacheSessionId(options);
414
+ const params: OpenAIResponsesSamplingParams = {
415
+ model: model.id,
416
+ input: messages,
417
+ instructions: systemInstructions,
418
+ stream: true,
419
+ prompt_cache_key: promptCacheKey,
420
+ prompt_cache_retention: promptCacheKey ? getPromptCacheRetention(model.baseUrl, cacheRetention) : undefined,
421
+ store: false,
422
+ stream_options: model.provider === "openai" ? { include_obfuscation: false } : undefined,
423
+ };
424
+
425
+ applyCommonResponsesSamplingParams(params, options, model.provider);
426
+ // TODO: openai responses has no top-level `stop`/`stop_sequences`; surface via reasoning.stop?
427
+ // `StreamOptions.stopSequences` is intentionally dropped for this provider.
428
+ // TODO: openai responses has no top-level `frequency_penalty` field as of the current SDK;
429
+ // `StreamOptions.frequencyPenalty` is intentionally dropped for this provider.
430
+
431
+ if (context.tools) {
432
+ params.tools = convertTools(context.tools, supportsStrictMode(model), model);
433
+ if (options?.toolChoice) {
434
+ params.tool_choice = mapOpenAIResponsesToolChoiceForTools(options.toolChoice, context.tools, model);
435
+ }
436
+ // The apply_patch spec §1 marks only `apply_patch` itself as
437
+ // `supports_parallel_tool_calls = false`. OpenAI's Responses API
438
+ // exposes `parallel_tool_calls` as a request-scoped flag, not a
439
+ // per-tool one, so when a custom grammar tool is in the list we
440
+ // disable parallelism for the whole turn. Slightly coarser than
441
+ // the spec requires — but the platform API offers no finer knob.
442
+ if (params.tools.some(t => (t as { type?: string }).type === "custom")) {
443
+ params.parallel_tool_calls = false;
444
+ }
445
+ }
446
+
447
+ applyResponsesReasoningParams(params, model, options, messages, effort =>
448
+ mapReasoningEffort(effort as NonNullable<OpenAIResponsesOptions["reasoning"]>, model.compat?.reasoningEffortMap),
449
+ );
450
+
451
+ return { conversationMessages, params };
452
+ }
453
+
454
+ function mapReasoningEffort(
455
+ effort: NonNullable<OpenAIResponsesOptions["reasoning"]>,
456
+ reasoningEffortMap: OpenAICompat["reasoningEffortMap"] | undefined,
457
+ ): string {
458
+ return reasoningEffortMap?.[effort] ?? effort;
459
+ }
460
+
461
+ function isAzureOpenAIBaseUrl(baseUrl: string): boolean {
462
+ return baseUrl.includes(".openai.azure.com") || baseUrl.includes("azure.com/openai");
463
+ }
464
+
465
+ function supportsStrictMode(model: Model<"openai-responses">): boolean {
466
+ if (model.provider === "openai" || model.provider === "azure" || model.provider === "github-copilot") return true;
467
+
468
+ const baseUrl = model.baseUrl.toLowerCase();
469
+ return (
470
+ baseUrl.includes("api.openai.com") ||
471
+ baseUrl.includes(".openai.azure.com") ||
472
+ baseUrl.includes("models.inference.ai.azure.com")
473
+ );
474
+ }
475
+
476
+ export function supportsDeveloperRole(modelOrBaseUrl: Pick<Model, "provider" | "baseUrl"> | string): boolean {
477
+ const baseUrl =
478
+ typeof modelOrBaseUrl === "string" ? modelOrBaseUrl.toLowerCase() : (modelOrBaseUrl.baseUrl ?? "").toLowerCase();
479
+ return (
480
+ baseUrl.includes("api.openai.com") ||
481
+ baseUrl.includes(".openai.azure.com") ||
482
+ baseUrl.includes("azure.com/openai") ||
483
+ baseUrl.includes("models.inference.ai.azure.com") ||
484
+ baseUrl.includes("githubcopilot.com") ||
485
+ baseUrl.includes("copilot-api.")
486
+ );
487
+ }
488
+
489
+ function convertConversationMessages(
490
+ model: Model<"openai-responses">,
491
+ context: Context,
492
+ strictResponsesPairing: boolean,
493
+ providerSessionState: OpenAIResponsesProviderSessionState | undefined,
494
+ ): ResponseInput {
495
+ const messages: ResponseInput = [];
496
+ let knownCallIds = new Set<string>();
497
+ const customCallIds = new Set<string>();
498
+ const shouldReplayNativeHistory = canReplayOpenAIResponsesNativeHistory(providerSessionState);
499
+ const transformedMessages = transformMessages(context.messages, model, normalizeResponsesToolCallIdForTransform);
500
+
501
+ let msgIndex = 0;
502
+ for (const msg of transformedMessages) {
503
+ if (msg.role === "user" || msg.role === "developer") {
504
+ const providerPayload = (msg as { providerPayload?: AssistantMessage["providerPayload"] }).providerPayload;
505
+ const historyItems = getOpenAIResponsesHistoryItems(providerPayload, model.provider);
506
+ const shouldReplayPayloadItems =
507
+ shouldReplayNativeHistory ||
508
+ (historyItems?.some(item => {
509
+ if (!item || typeof item !== "object") return false;
510
+ const candidate = item as { type?: unknown };
511
+ return candidate.type === "compaction" || candidate.type === "compaction_summary";
512
+ }) ??
513
+ false);
514
+ if (historyItems && shouldReplayPayloadItems) {
515
+ messages.push(...sanitizeOpenAIResponsesHistoryItemsForReplay(historyItems));
516
+ knownCallIds = collectKnownCallIds(messages);
517
+ for (const id of collectCustomCallIds(messages)) customCallIds.add(id);
518
+ msgIndex++;
519
+ continue;
520
+ }
521
+ const content = convertResponsesInputContent(msg.content, model.input.includes("image"));
522
+ if (!content) continue;
523
+ messages.push({ role: "user", content });
524
+ } else if (msg.role === "assistant") {
525
+ const assistantMsg = msg as AssistantMessage;
526
+ const providerPayload = shouldReplayNativeHistory
527
+ ? getOpenAIResponsesHistoryPayload(assistantMsg.providerPayload, model.provider, assistantMsg.provider)
528
+ : undefined;
529
+ const historyItems = providerPayload?.items;
530
+ if (historyItems) {
531
+ const sanitizedHistoryItems = sanitizeOpenAIResponsesHistoryItemsForReplay(historyItems);
532
+ if (providerPayload?.dt) {
533
+ messages.push(...sanitizedHistoryItems);
534
+ } else {
535
+ messages.splice(0, messages.length, ...sanitizedHistoryItems);
536
+ }
537
+ knownCallIds = collectKnownCallIds(messages);
538
+ for (const id of collectCustomCallIds(messages)) customCallIds.add(id);
539
+ msgIndex++;
540
+ continue;
541
+ }
542
+
543
+ const outputItems = convertResponsesAssistantMessage(
544
+ assistantMsg,
545
+ model,
546
+ msgIndex,
547
+ knownCallIds,
548
+ shouldReplayNativeHistory,
549
+ customCallIds,
550
+ );
551
+ if (outputItems.length === 0) continue;
552
+ messages.push(...outputItems);
553
+ } else if (msg.role === "toolResult") {
554
+ appendResponsesToolResultMessages(messages, msg, model, strictResponsesPairing, knownCallIds, customCallIds);
555
+ }
556
+ msgIndex++;
557
+ }
558
+
559
+ return repairOrphanResponsesToolOutputs(messages);
560
+ }
561
+
562
+ /**
563
+ * Whether this model should get the OpenAI custom-tool grammar variant
564
+ * for `apply_patch`. The generated model catalog sets
565
+ * `model.applyPatchToolType` for first-party GPT-5 Responses models; this
566
+ * runtime path only consumes that metadata.
567
+ * @internal Exported for tests.
568
+ */
569
+ export function supportsFreeformApplyPatch(model: Model<"openai-responses">): boolean {
570
+ return model.applyPatchToolType === "freeform";
571
+ }
572
+
573
+ /** @internal Exported for tests. */
574
+ export function mapOpenAIResponsesToolChoiceForTools(
575
+ choice: ToolChoice | undefined,
576
+ tools: Tool[],
577
+ model: Model<"openai-responses">,
578
+ ): OpenAIResponsesToolChoice {
579
+ const mapped = mapToOpenAIResponsesToolChoice(choice);
580
+ if (!mapped || typeof mapped === "string" || mapped.type !== "function" || !supportsFreeformApplyPatch(model)) {
581
+ return mapped;
582
+ }
583
+
584
+ const customTool = tools.find(
585
+ tool => tool.customFormat && (tool.name === mapped.name || tool.customWireName === mapped.name),
586
+ );
587
+ return customTool ? { type: "custom", name: customTool.customWireName ?? customTool.name } : mapped;
588
+ }
589
+
590
+ /** @internal Exported for tests. */
591
+ export function convertTools(tools: Tool[], strictMode: boolean, model: Model<"openai-responses">): OpenAITool[] {
592
+ const allowFreeform = supportsFreeformApplyPatch(model);
593
+ return tools.map(tool => {
594
+ if (allowFreeform && tool.customFormat) {
595
+ return {
596
+ type: "custom",
597
+ // Tool advertises its wire-level name (e.g. `apply_patch`) — the
598
+ // agent-loop dispatcher will match incoming calls by either the
599
+ // internal `name` or `customWireName`.
600
+ name: tool.customWireName ?? tool.name,
601
+ description: tool.description || "",
602
+ format: {
603
+ type: "grammar",
604
+ syntax: tool.customFormat.syntax,
605
+ definition: compactGrammarDefinition(tool.customFormat.syntax, tool.customFormat.definition),
606
+ },
607
+ } as unknown as OpenAITool;
608
+ }
609
+ const strict = !NO_STRICT && strictMode && tool.strict !== false;
610
+ const baseParameters = toolWireSchema(tool);
611
+ const responseParameters = sanitizeSchemaForOpenAIResponses(baseParameters);
612
+ const { schema: parameters, strict: effectiveStrict } = adaptSchemaForStrict(responseParameters, strict);
613
+ return {
614
+ type: "function",
615
+ name: tool.name,
616
+ description: tool.description || "",
617
+ parameters,
618
+ ...(effectiveStrict && { strict: true }),
619
+ } as OpenAITool;
620
+ });
621
+ }