@code-yeongyu/senpi-ai 2026.9.30 → 2026.10.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 (274) hide show
  1. package/README.md +189 -19
  2. package/dist/api/anthropic-messages.js +168 -32
  3. package/dist/api/azure-openai-responses.js +23 -10
  4. package/dist/api/bedrock-converse-stream.js +12 -4
  5. package/dist/api/cloudflare-workers-ai-system-one.d.ts +4 -0
  6. package/dist/api/cloudflare-workers-ai-system-one.js +43 -0
  7. package/dist/api/cloudflare-workers-ai-system-one.lazy.d.ts +3 -0
  8. package/dist/api/cloudflare-workers-ai-system-one.lazy.js +4 -0
  9. package/dist/api/cloudflare.d.ts +2 -0
  10. package/dist/api/cloudflare.js +2 -0
  11. package/dist/api/context-room.d.ts +2 -2
  12. package/dist/api/cursor-agent.js +19 -10
  13. package/dist/api/devin-agent/request.d.ts +9 -9
  14. package/dist/api/devin-agent/request.js +14 -10
  15. package/dist/api/google-generative-ai.js +20 -80
  16. package/dist/api/google-shared.d.ts +13 -4
  17. package/dist/api/google-shared.js +54 -4
  18. package/dist/api/google-vertex.js +19 -62
  19. package/dist/api/llama-cpp-classify.d.ts +33 -0
  20. package/dist/api/llama-cpp-classify.js +365 -0
  21. package/dist/api/llama-cpp-classify.lazy.d.ts +3 -0
  22. package/dist/api/llama-cpp-classify.lazy.js +4 -0
  23. package/dist/api/mistral-conversations.d.ts +1 -1
  24. package/dist/api/mistral-conversations.js +36 -29
  25. package/dist/api/openai-codex-responses.d.ts +1 -1
  26. package/dist/api/openai-codex-responses.js +72 -37
  27. package/dist/api/openai-completions.d.ts +3 -2
  28. package/dist/api/openai-completions.js +76 -50
  29. package/dist/api/openai-images-params.d.ts +2 -2
  30. package/dist/api/openai-images.d.ts +1 -1
  31. package/dist/api/openai-responses-shared.d.ts +31 -8
  32. package/dist/api/openai-responses-shared.js +115 -27
  33. package/dist/api/openai-responses.d.ts +1 -1
  34. package/dist/api/openai-responses.js +38 -33
  35. package/dist/api/openrouter-images.d.ts +2 -1
  36. package/dist/api/openrouter-images.js +1 -0
  37. package/dist/api/pi-messages.d.ts +3 -3
  38. package/dist/api/pi-messages.js +3 -2
  39. package/dist/api/simple-options.d.ts +2 -2
  40. package/dist/api/simple-options.js +1 -0
  41. package/dist/api/system-one-shared.d.ts +23 -0
  42. package/dist/api/system-one-shared.js +183 -0
  43. package/dist/api/transform-messages.js +5 -2
  44. package/dist/api/typesafe-system-one.d.ts +4 -0
  45. package/dist/api/typesafe-system-one.js +19 -0
  46. package/dist/api/typesafe-system-one.lazy.d.ts +3 -0
  47. package/dist/api/typesafe-system-one.lazy.js +4 -0
  48. package/dist/api-registry.d.ts +3 -3
  49. package/dist/auth/helpers.js +1 -1
  50. package/dist/auth/oauth/anthropic-callback-listener.js +1 -1
  51. package/dist/auth/oauth/callback-server.d.ts +55 -0
  52. package/dist/auth/oauth/callback-server.js +146 -0
  53. package/dist/auth/oauth/chatgpt-subscription.d.ts +1 -1
  54. package/dist/auth/oauth/chatgpt-subscription.js +20 -124
  55. package/dist/auth/oauth/devin-callback.js +1 -1
  56. package/dist/auth/oauth/load.d.ts +4 -0
  57. package/dist/auth/oauth/load.js +10 -0
  58. package/dist/auth/oauth/meta.d.ts +17 -0
  59. package/dist/auth/oauth/meta.js +190 -0
  60. package/dist/auth/oauth/openai-chatgpt.d.ts +9 -0
  61. package/dist/auth/oauth/openai-chatgpt.js +266 -0
  62. package/dist/auth/oauth/openrouter.d.ts +1 -1
  63. package/dist/auth/oauth/openrouter.js +19 -138
  64. package/dist/auth/oauth/radius.d.ts +1 -1
  65. package/dist/auth/oauth/radius.js +21 -89
  66. package/dist/auth/resolve.d.ts +3 -8
  67. package/dist/auth/resolve.js +3 -18
  68. package/dist/auth/types.d.ts +10 -1
  69. package/dist/bun-oauth.js +4 -0
  70. package/dist/cli.js +3 -1
  71. package/dist/compat.js +17 -14
  72. package/dist/env-api-keys.js +2 -0
  73. package/dist/image-models.d.ts +19 -8
  74. package/dist/image-models.js +14 -13
  75. package/dist/images-api-registry.d.ts +8 -8
  76. package/dist/images.d.ts +7 -2
  77. package/dist/images.js +5 -0
  78. package/dist/index.d.ts +2 -2
  79. package/dist/index.js +2 -2
  80. package/dist/model-catalog.d.ts +27 -9
  81. package/dist/model-catalog.js +24 -2
  82. package/dist/model.d.ts +13 -13
  83. package/dist/models-store.d.ts +3 -2
  84. package/dist/models.d.ts +106 -36
  85. package/dist/models.generated.d.ts +142 -42
  86. package/dist/models.generated.js +142 -42
  87. package/dist/models.js +174 -36
  88. package/dist/providers/alibaba-token-plan.models.d.ts +4 -2
  89. package/dist/providers/alibaba-token-plan.models.js +4 -2
  90. package/dist/providers/all.d.ts +72 -19
  91. package/dist/providers/all.js +28 -22
  92. package/dist/providers/amazon-bedrock.models.d.ts +4 -2
  93. package/dist/providers/amazon-bedrock.models.js +4 -2
  94. package/dist/providers/ant-ling.models.d.ts +4 -2
  95. package/dist/providers/ant-ling.models.js +4 -2
  96. package/dist/providers/anthropic.models.d.ts +4 -2
  97. package/dist/providers/anthropic.models.js +4 -2
  98. package/dist/providers/azure-openai-responses.models.d.ts +4 -2
  99. package/dist/providers/azure-openai-responses.models.js +4 -2
  100. package/dist/providers/bai.models.d.ts +4 -2
  101. package/dist/providers/bai.models.js +4 -2
  102. package/dist/providers/baseten.models.d.ts +4 -2
  103. package/dist/providers/baseten.models.js +4 -2
  104. package/dist/providers/cerebras.models.d.ts +4 -2
  105. package/dist/providers/cerebras.models.js +4 -2
  106. package/dist/providers/chatgpt-subscription.models.d.ts +4 -2
  107. package/dist/providers/chatgpt-subscription.models.js +4 -2
  108. package/dist/providers/cloudflare-ai-gateway.models.d.ts +4 -2
  109. package/dist/providers/cloudflare-ai-gateway.models.js +4 -2
  110. package/dist/providers/cloudflare-stream.d.ts +6 -2
  111. package/dist/providers/cloudflare-stream.js +6 -0
  112. package/dist/providers/cloudflare-workers-ai.js +10 -3
  113. package/dist/providers/cloudflare-workers-ai.models.d.ts +4 -2
  114. package/dist/providers/cloudflare-workers-ai.models.js +4 -2
  115. package/dist/providers/data/.manifest.json +1 -1
  116. package/dist/providers/data/alibaba-token-plan.json +1 -1
  117. package/dist/providers/data/amazon-bedrock.json +1 -1
  118. package/dist/providers/data/ant-ling.json +1 -1
  119. package/dist/providers/data/anthropic.json +1 -1
  120. package/dist/providers/data/azure-openai-responses.json +1 -1
  121. package/dist/providers/data/bai.json +1 -1
  122. package/dist/providers/data/baseten.json +1 -1
  123. package/dist/providers/data/cerebras.json +1 -1
  124. package/dist/providers/data/chatgpt-subscription.json +1 -1
  125. package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  126. package/dist/providers/data/cloudflare-workers-ai.json +1 -1
  127. package/dist/providers/data/deepseek.json +1 -1
  128. package/dist/providers/data/fireworks.json +1 -1
  129. package/dist/providers/data/github-copilot.json +1 -1
  130. package/dist/providers/data/google-vertex.json +1 -1
  131. package/dist/providers/data/google.json +1 -1
  132. package/dist/providers/data/groq.json +1 -1
  133. package/dist/providers/data/huggingface.json +1 -1
  134. package/dist/providers/data/meta.json +1 -0
  135. package/dist/providers/data/minimax-cn.json +1 -1
  136. package/dist/providers/data/minimax.json +1 -1
  137. package/dist/providers/data/mistral.json +1 -1
  138. package/dist/providers/data/moonshotai-cn.json +1 -1
  139. package/dist/providers/data/moonshotai.json +1 -1
  140. package/dist/providers/data/nvidia.json +1 -1
  141. package/dist/providers/data/openai.json +1 -1
  142. package/dist/providers/data/opencode-go.json +1 -1
  143. package/dist/providers/data/opencode.json +1 -1
  144. package/dist/providers/data/opengateway.json +1 -1
  145. package/dist/providers/data/openrouter.json +1 -1
  146. package/dist/providers/data/qwen-token-plan-cn.json +1 -1
  147. package/dist/providers/data/qwen-token-plan-individual.json +1 -1
  148. package/dist/providers/data/qwen-token-plan.json +1 -1
  149. package/dist/providers/data/radius.json +1 -0
  150. package/dist/providers/data/together.json +1 -1
  151. package/dist/providers/data/typesafe.json +1 -0
  152. package/dist/providers/data/venice.json +1 -1
  153. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  154. package/dist/providers/data/xai.json +1 -1
  155. package/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
  156. package/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
  157. package/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
  158. package/dist/providers/data/xiaomi.json +1 -1
  159. package/dist/providers/data/zai-coding-cn.json +1 -1
  160. package/dist/providers/data/zai.json +1 -1
  161. package/dist/providers/deepseek.models.d.ts +4 -2
  162. package/dist/providers/deepseek.models.js +4 -2
  163. package/dist/providers/faux.d.ts +7 -2
  164. package/dist/providers/faux.js +31 -22
  165. package/dist/providers/fireworks.models.d.ts +4 -2
  166. package/dist/providers/fireworks.models.js +4 -2
  167. package/dist/providers/github-copilot.models.d.ts +4 -2
  168. package/dist/providers/github-copilot.models.js +4 -2
  169. package/dist/providers/google-vertex.models.d.ts +4 -2
  170. package/dist/providers/google-vertex.models.js +4 -2
  171. package/dist/providers/google.models.d.ts +4 -2
  172. package/dist/providers/google.models.js +4 -2
  173. package/dist/providers/groq.models.d.ts +4 -2
  174. package/dist/providers/groq.models.js +4 -2
  175. package/dist/providers/huggingface.models.d.ts +4 -2
  176. package/dist/providers/huggingface.models.js +4 -2
  177. package/dist/providers/images/register-builtins.d.ts +2 -2
  178. package/dist/providers/kimi-coding.models.d.ts +54 -7
  179. package/dist/providers/kimi-coding.models.js +34 -7
  180. package/dist/providers/meta.d.ts +3 -0
  181. package/dist/providers/meta.js +24 -0
  182. package/dist/providers/meta.models.d.ts +6 -0
  183. package/dist/providers/meta.models.js +8 -0
  184. package/dist/providers/minimax-cn.models.d.ts +4 -2
  185. package/dist/providers/minimax-cn.models.js +4 -2
  186. package/dist/providers/minimax.models.d.ts +4 -2
  187. package/dist/providers/minimax.models.js +4 -2
  188. package/dist/providers/mistral.models.d.ts +4 -2
  189. package/dist/providers/mistral.models.js +4 -2
  190. package/dist/providers/moonshotai-cn.models.d.ts +4 -2
  191. package/dist/providers/moonshotai-cn.models.js +4 -2
  192. package/dist/providers/moonshotai.models.d.ts +4 -2
  193. package/dist/providers/moonshotai.models.js +4 -2
  194. package/dist/providers/nvidia.models.d.ts +4 -2
  195. package/dist/providers/nvidia.models.js +4 -2
  196. package/dist/providers/openai.js +4 -2
  197. package/dist/providers/openai.models.d.ts +4 -2
  198. package/dist/providers/openai.models.js +4 -2
  199. package/dist/providers/opencode-go.models.d.ts +4 -2
  200. package/dist/providers/opencode-go.models.js +4 -2
  201. package/dist/providers/opencode.d.ts +3 -1
  202. package/dist/providers/opencode.js +5 -2
  203. package/dist/providers/opencode.models.d.ts +4 -2
  204. package/dist/providers/opencode.models.js +4 -2
  205. package/dist/providers/opengateway.models.d.ts +4 -2
  206. package/dist/providers/opengateway.models.js +4 -2
  207. package/dist/providers/openrouter.js +11 -2
  208. package/dist/providers/openrouter.models.d.ts +4 -2
  209. package/dist/providers/openrouter.models.js +4 -2
  210. package/dist/providers/qwen-token-plan-cn.models.d.ts +4 -2
  211. package/dist/providers/qwen-token-plan-cn.models.js +4 -2
  212. package/dist/providers/qwen-token-plan-individual.models.d.ts +4 -2
  213. package/dist/providers/qwen-token-plan-individual.models.js +4 -2
  214. package/dist/providers/qwen-token-plan.models.d.ts +4 -2
  215. package/dist/providers/qwen-token-plan.models.js +4 -2
  216. package/dist/providers/radius.js +19 -5
  217. package/dist/providers/radius.models.d.ts +6 -0
  218. package/dist/providers/radius.models.js +8 -0
  219. package/dist/providers/together.models.d.ts +4 -2
  220. package/dist/providers/together.models.js +4 -2
  221. package/dist/providers/typesafe.d.ts +3 -0
  222. package/dist/providers/typesafe.js +18 -0
  223. package/dist/providers/typesafe.models.d.ts +6 -0
  224. package/dist/providers/typesafe.models.js +8 -0
  225. package/dist/providers/venice.models.d.ts +4 -2
  226. package/dist/providers/venice.models.js +4 -2
  227. package/dist/providers/vercel-ai-gateway.js +5 -2
  228. package/dist/providers/vercel-ai-gateway.models.d.ts +4 -2
  229. package/dist/providers/vercel-ai-gateway.models.js +4 -2
  230. package/dist/providers/xai.models.d.ts +4 -2
  231. package/dist/providers/xai.models.js +4 -2
  232. package/dist/providers/xiaomi-token-plan-ams.models.d.ts +4 -2
  233. package/dist/providers/xiaomi-token-plan-ams.models.js +4 -2
  234. package/dist/providers/xiaomi-token-plan-cn.models.d.ts +4 -2
  235. package/dist/providers/xiaomi-token-plan-cn.models.js +4 -2
  236. package/dist/providers/xiaomi-token-plan-sgp.models.d.ts +4 -2
  237. package/dist/providers/xiaomi-token-plan-sgp.models.js +4 -2
  238. package/dist/providers/xiaomi.models.d.ts +4 -2
  239. package/dist/providers/xiaomi.models.js +4 -2
  240. package/dist/providers/zai-coding-cn.models.d.ts +4 -2
  241. package/dist/providers/zai-coding-cn.models.js +4 -2
  242. package/dist/providers/zai.models.d.ts +4 -2
  243. package/dist/providers/zai.models.js +4 -2
  244. package/dist/tool-call-middleware/context-transformer.d.ts +8 -5
  245. package/dist/tool-call-middleware/context-transformer.js +44 -15
  246. package/dist/types.d.ts +262 -28
  247. package/dist/utils/diagnostics.d.ts +3 -2
  248. package/dist/utils/estimate.d.ts +2 -2
  249. package/dist/utils/estimate.js +22 -30
  250. package/dist/utils/headers.d.ts +1 -1
  251. package/dist/utils/headers.js +10 -8
  252. package/dist/utils/model-operations.d.ts +11 -0
  253. package/dist/utils/model-operations.js +47 -0
  254. package/dist/utils/models-error.d.ts +8 -0
  255. package/dist/utils/models-error.js +18 -0
  256. package/dist/utils/overflow.d.ts +1 -0
  257. package/dist/utils/overflow.js +11 -5
  258. package/dist/utils/prompt-cache-ttl.js +10 -3
  259. package/dist/utils/retry.js +9 -0
  260. package/dist/utils/text.d.ts +9 -1
  261. package/dist/utils/text.js +26 -0
  262. package/dist/utils/transcript.d.ts +85 -0
  263. package/dist/utils/transcript.js +205 -0
  264. package/package.json +3 -4
  265. package/dist/image-models.generated.d.ts +0 -925
  266. package/dist/image-models.generated.js +0 -927
  267. package/dist/images-models.d.ts +0 -95
  268. package/dist/images-models.js +0 -141
  269. package/dist/providers/openai-images.d.ts +0 -3
  270. package/dist/providers/openai-images.js +0 -16
  271. package/dist/providers/openrouter-images.d.ts +0 -3
  272. package/dist/providers/openrouter-images.js +0 -22
  273. /package/dist/{auth/oauth → utils}/oauth-page.d.ts +0 -0
  274. /package/dist/{auth/oauth → utils}/oauth-page.js +0 -0
@@ -13,8 +13,10 @@ import { awaitProviderTransport, iterateProviderTransport, openAICompatibleProvi
13
13
  import { getProviderEnvValue } from "../utils/provider-env.js";
14
14
  import { retryProviderStreamRequest } from "../utils/provider-retry.js";
15
15
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
16
+ import { getSystemMessageText, renderSystemMessageUpdate } from "../utils/text.js";
16
17
  import { sendWithForcedToolChoiceFallback } from "../utils/tool-choice-fallback.js";
17
18
  import { normalizeToolParametersForMoonshot, normalizeToolParametersForOpenAICompat, } from "../utils/tool-schema-compat.js";
19
+ import { getCurrentTools, getDeclaredTools, resolveTranscript, resolveTranscriptTools, } from "../utils/transcript.js";
18
20
  import { appendGrammarToolInputJsonDelta, createGrammarToolInputProperties, getGrammarToolInput, getJsonSchemaToolParameters, resolveGrammarConstrainedSampling, resolveJsonSchemaStrictSampling, } from "./constrained-sampling.js";
19
21
  import { withGitHubCopilotFailureNote } from "./github-copilot-errors.js";
20
22
  import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
@@ -122,24 +124,26 @@ function hasToolHistory(messages) {
122
124
  }
123
125
  return false;
124
126
  }
125
- function getDeferredToolNames(messages) {
127
+ /**
128
+ * Kimi `deferredToolsMode`: a current tool named by any tool result's `addedToolNames` leaves the
129
+ * top-level `tools` field and is loaded by a Kimi tools system message after that result.
130
+ */
131
+ function resolveKimiDeferredTools(messages, compat) {
132
+ const deferred = new Map();
133
+ if (compat.deferredToolsMode !== "kimi")
134
+ return deferred;
126
135
  const names = new Set();
127
136
  for (const message of messages) {
128
- if (message.role === "toolResult") {
129
- for (const name of message.addedToolNames ?? []) {
130
- names.add(name);
131
- }
132
- }
137
+ if (message.role !== "toolResult")
138
+ continue;
139
+ for (const name of message.addedToolNames ?? [])
140
+ names.add(name);
133
141
  }
134
- return names;
135
- }
136
- function getToolsByName(tools, names) {
137
- if (!tools)
138
- return [];
139
- const toolsByName = new Map(tools.map((tool) => [tool.name, tool]));
140
- return Array.from(names)
141
- .map((name) => toolsByName.get(name))
142
- .filter((tool) => tool !== undefined);
142
+ for (const tool of getCurrentTools(messages)) {
143
+ if (names.has(tool.name))
144
+ deferred.set(tool.name, tool);
145
+ }
146
+ return deferred;
143
147
  }
144
148
  function isTextContentBlock(block) {
145
149
  return block.type === "text";
@@ -297,6 +301,7 @@ function resolveCacheRetention(cacheRetention, env) {
297
301
  }
298
302
  export const stream = (model, context, options) => {
299
303
  const stream = new AssistantMessageEventStream();
304
+ const normalizedContext = resolveTranscript(context, getCompat(model).supportsMidConvoSystemMessages);
300
305
  (async () => {
301
306
  const output = {
302
307
  role: "assistant",
@@ -326,11 +331,11 @@ export const stream = (model, context, options) => {
326
331
  try {
327
332
  const clientAuth = resolveOpenAIClientAuth(model.provider, options?.apiKey, options?.headers);
328
333
  const compat = getCompat(model);
329
- const grammarToolInputProperties = createGrammarToolInputProperties(context.tools, compat.supportsOpenAIGrammarTools);
334
+ const grammarToolInputProperties = createGrammarToolInputProperties(getDeclaredTools(normalizedContext.messages), compat.supportsOpenAIGrammarTools);
330
335
  const cacheRetention = resolveCacheRetention(options?.cacheRetention ?? model.cacheRetention, options?.env);
331
336
  const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
332
- const client = createClient(model, context, clientAuth.apiKey, clientAuth.headers, options?.fetch, cacheSessionId, compat);
333
- let params = buildParams(model, context, options, compat, cacheRetention, grammarToolInputProperties);
337
+ const client = createClient(model, normalizedContext, clientAuth.apiKey, clientAuth.headers, options?.fetch, cacheSessionId, compat);
338
+ let params = buildParams(model, normalizedContext, options, compat, cacheRetention, grammarToolInputProperties);
334
339
  const nextParams = await options?.onPayload?.(params, model);
335
340
  if (nextParams !== undefined) {
336
341
  params = nextParams;
@@ -579,6 +584,7 @@ export const stream = (model, context, options) => {
579
584
  }
580
585
  };
581
586
  for await (const chunk of openaiStream) {
587
+ await options?.onProviderStreamEvent?.(chunk, model);
582
588
  if (!chunk || typeof chunk !== "object")
583
589
  continue;
584
590
  // OpenAI documents ChatCompletionChunk.id as the unique chat completion identifier,
@@ -833,7 +839,10 @@ function createClient(model, context, apiKey, optionsHeaders, fetch, sessionId,
833
839
  defaultHeaders: headers,
834
840
  });
835
841
  }
836
- function buildParams(model, context, options, compat = getCompat(model), cacheRetention = resolveCacheRetention(options?.cacheRetention ?? model.cacheRetention, options?.env), grammarToolInputProperties = createGrammarToolInputProperties(context.tools, compat.supportsOpenAIGrammarTools)) {
842
+ function buildParams(model, context, options, compat = getCompat(model), cacheRetention = resolveCacheRetention(options?.cacheRetention ?? model.cacheRetention, options?.env), grammarToolInputProperties = createGrammarToolInputProperties(getDeclaredTools(context.messages), compat.supportsOpenAIGrammarTools)) {
843
+ const transcriptTools = resolveTranscriptTools(context.messages, compat.supportsMidConvoSystemMessages === true && compat.supportsMidConvoToolAdditions === true);
844
+ const kimiDeferredTools = resolveKimiDeferredTools(context.messages, compat);
845
+ const requestTools = transcriptTools.requestTools.filter((tool) => !kimiDeferredTools.has(tool.name));
837
846
  const messages = convertMessages(model, context, compat, {
838
847
  preserveThinking: options?.reasoningEffort !== undefined,
839
848
  grammarToolInputProperties,
@@ -864,6 +873,7 @@ function buildParams(model, context, options, compat = getCompat(model), cacheRe
864
873
  }
865
874
  if (options?.maxTokens) {
866
875
  if (compat.maxTokensField === "max_tokens") {
876
+ // Deprecated by OpenAI, but some OpenAI-compatible providers only accept max_tokens.
867
877
  params.max_tokens = options.maxTokens;
868
878
  }
869
879
  else {
@@ -873,10 +883,8 @@ function buildParams(model, context, options, compat = getCompat(model), cacheRe
873
883
  if (options?.temperature !== undefined) {
874
884
  params.temperature = options.temperature;
875
885
  }
876
- const deferredToolNames = compat.deferredToolsMode === "kimi" ? getDeferredToolNames(context.messages) : new Set();
877
- const activeTools = context.tools?.filter((tool) => !deferredToolNames.has(tool.name));
878
- if (activeTools && activeTools.length > 0) {
879
- params.tools = convertTools(activeTools, compat);
886
+ if (requestTools.length > 0) {
887
+ params.tools = convertTools(requestTools, compat);
880
888
  if (compat.zaiToolStream) {
881
889
  params.tool_stream = true;
882
890
  }
@@ -1040,10 +1048,8 @@ function buildParams(model, context, options, compat = getCompat(model), cacheRe
1040
1048
  }
1041
1049
  }
1042
1050
  applyExtraBody(params, options?.extraBody, OPENAI_COMPLETIONS_RESERVED_BODY_KEYS);
1043
- // Last so custom keys override the named request fields.
1044
- if (options?.samplingParams) {
1045
- Object.assign(params, options.samplingParams);
1046
- }
1051
+ // Last so custom keys override the named request fields. Per-request keys override model defaults.
1052
+ Object.assign(params, model.samplingParams, options?.samplingParams);
1047
1053
  return params;
1048
1054
  }
1049
1055
  function resolveThinkingTokenBudgetField(compat) {
@@ -1173,6 +1179,7 @@ function appendUserMessage(params, content, mergeAdjacentUserMessages) {
1173
1179
  previous.content = [...previousContent, ...currentContent];
1174
1180
  }
1175
1181
  export function convertMessages(model, context, compat, options = {}) {
1182
+ const normalizedContext = resolveTranscript(context, compat.supportsMidConvoSystemMessages);
1176
1183
  const params = [];
1177
1184
  const normalizeToolCallId = (id) => {
1178
1185
  // Handle pipe-separated IDs from OpenAI Responses API
@@ -1202,16 +1209,16 @@ export function convertMessages(model, context, compat, options = {}) {
1202
1209
  const prefix = sanitizedId.slice(0, Math.max(1, 40 - hash.length - 1));
1203
1210
  return `${prefix}_${hash}`;
1204
1211
  };
1205
- const transformedMessages = transformMessages(context.messages, model, (id) => normalizeToolCallId(id), {
1212
+ const transformedMessages = transformMessages(normalizedContext.messages, model, (id) => normalizeToolCallId(id), {
1206
1213
  preserveThinking: options.preserveThinking,
1207
1214
  normalizeSameModelToolCallIds: true,
1208
1215
  });
1209
1216
  const mergeAdjacentUserMessages = !model.baseUrl.includes("api.openai.com");
1210
- if (context.systemPrompt) {
1211
- const useDeveloperRole = model.reasoning && compat.supportsDeveloperRole;
1212
- const role = useDeveloperRole ? "developer" : "system";
1213
- params.push({ role: role, content: sanitizeSurrogates(context.systemPrompt) });
1214
- }
1217
+ const transcriptTools = resolveTranscriptTools(normalizedContext.messages, compat.supportsMidConvoSystemMessages === true && compat.supportsMidConvoToolAdditions === true);
1218
+ const kimiDeferredTools = resolveKimiDeferredTools(normalizedContext.messages, compat);
1219
+ // One ledger for both in-place loading paths: transcript system messages and `addedToolNames`.
1220
+ const loadedToolNames = new Set();
1221
+ const instructionRole = model.reasoning && compat.supportsDeveloperRole ? "developer" : "system";
1215
1222
  let lastRole = null;
1216
1223
  for (let i = 0; i < transformedMessages.length; i++) {
1217
1224
  const msg = transformedMessages[i];
@@ -1223,12 +1230,32 @@ export function convertMessages(model, context, compat, options = {}) {
1223
1230
  content: "I have processed the tool results.",
1224
1231
  });
1225
1232
  }
1226
- if (msg.role === "user") {
1233
+ if (msg.role === "system") {
1234
+ const addedTools = i > 0 && transcriptTools.anchorsAdditions
1235
+ ? (msg.toolsAdded ?? []).filter((tool) => !loadedToolNames.has(tool.name))
1236
+ : [];
1237
+ for (const tool of addedTools)
1238
+ loadedToolNames.add(tool.name);
1239
+ if (addedTools.length > 0) {
1240
+ const kimiToolMessage = {
1241
+ role: "system",
1242
+ tools: convertTools(addedTools, compat),
1243
+ };
1244
+ params.push(kimiToolMessage);
1245
+ }
1246
+ const text = i === 0 ? getSystemMessageText(msg) : renderSystemMessageUpdate(msg);
1247
+ if (text.length > 0) {
1248
+ params.push({ role: instructionRole, content: sanitizeSurrogates(text) });
1249
+ }
1250
+ }
1251
+ else if (msg.role === "user") {
1227
1252
  if (typeof msg.content === "string") {
1228
1253
  appendUserMessage(params, sanitizeSurrogates(msg.content), mergeAdjacentUserMessages);
1229
1254
  }
1230
1255
  else {
1231
- const content = msg.content.map((item) => {
1256
+ const content = msg.content
1257
+ .filter((item) => item.type !== "text" || item.text.length > 0)
1258
+ .map((item) => {
1232
1259
  if (item.type === "text") {
1233
1260
  return {
1234
1261
  type: "text",
@@ -1361,7 +1388,7 @@ export function convertMessages(model, context, compat, options = {}) {
1361
1388
  }
1362
1389
  else if (msg.role === "toolResult") {
1363
1390
  const imageBlocks = [];
1364
- const deferredToolNames = new Set();
1391
+ const deferredTools = [];
1365
1392
  let j = i;
1366
1393
  for (; j < transformedMessages.length && transformedMessages[j].role === "toolResult"; j++) {
1367
1394
  const toolMsg = transformedMessages[j];
@@ -1384,10 +1411,12 @@ export function convertMessages(model, context, compat, options = {}) {
1384
1411
  Object.assign(toolResultMsg, { name: toolMsg.toolName });
1385
1412
  }
1386
1413
  params.push(toolResultMsg);
1387
- if (compat.deferredToolsMode === "kimi") {
1388
- for (const name of toolMsg.addedToolNames ?? []) {
1389
- deferredToolNames.add(name);
1390
- }
1414
+ for (const name of toolMsg.addedToolNames ?? []) {
1415
+ const tool = kimiDeferredTools.get(name);
1416
+ if (!tool || loadedToolNames.has(name))
1417
+ continue;
1418
+ loadedToolNames.add(name);
1419
+ deferredTools.push(tool);
1391
1420
  }
1392
1421
  if (hasImages && model.input.includes("image")) {
1393
1422
  for (const block of toolMsg.content) {
@@ -1425,16 +1454,13 @@ export function convertMessages(model, context, compat, options = {}) {
1425
1454
  else {
1426
1455
  lastRole = "toolResult";
1427
1456
  }
1428
- if (deferredToolNames.size > 0) {
1429
- const deferredTools = getToolsByName(context.tools, deferredToolNames);
1430
- if (deferredTools.length > 0) {
1431
- const kimiToolMessage = {
1432
- role: "system",
1433
- tools: convertTools(deferredTools, compat),
1434
- };
1435
- // Kimi accepts a system message with tools but omits the standard content field.
1436
- params.push(kimiToolMessage);
1437
- }
1457
+ if (deferredTools.length > 0) {
1458
+ const kimiToolMessage = {
1459
+ role: "system",
1460
+ tools: convertTools(deferredTools, compat),
1461
+ };
1462
+ // Kimi accepts a system message with tools but omits the standard content field.
1463
+ params.push(kimiToolMessage);
1438
1464
  }
1439
1465
  continue;
1440
1466
  }
@@ -1,6 +1,6 @@
1
1
  import type { Uploadable } from "openai";
2
2
  import type { ImageGenerateParamsNonStreaming } from "openai/resources/images.js";
3
- import type { ImageContent, ImagesContext, ImagesModel, ImagesOptions } from "../types.ts";
3
+ import type { ImageApi, ImageContent, ImageModel, ImagesContext, ImagesOptions } from "../types.ts";
4
4
  export type OpenAIImageQuality = "auto" | "low" | "medium" | "high" | "xhigh" | "max";
5
5
  export type OpenAIImageSize = "auto" | "1024x1024" | "1536x1024" | "1024x1536" | `${number}x${number}`;
6
6
  export type OpenAIImageBackground = "auto" | "transparent" | "opaque";
@@ -55,6 +55,6 @@ export type OpenAIImageOutputOptions = {
55
55
  * compression is a jpeg/webp-only integer percentage, and a mask edits an image.
56
56
  */
57
57
  export declare function parseOpenAIImageOutputOptions(input: OpenAIImageOutputOptionsInput): OpenAIImageOutputOptions;
58
- export declare function buildParams(model: ImagesModel<"openai-images">, context: ImagesContext, options?: OpenAIImagesOptions): OpenAIImageParams;
58
+ export declare function buildParams(model: ImageModel<ImageApi>, context: ImagesContext, options?: OpenAIImagesOptions): OpenAIImageParams;
59
59
  export declare function isImageParams(value: unknown): value is OpenAIImageParams;
60
60
  //# sourceMappingURL=openai-images-params.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import type { ImagesFunction } from "../types.ts";
2
2
  import { type OpenAIImagesOptions } from "./openai-images-params.ts";
3
3
  export { type OpenAIImageBackground, type OpenAIImageModeration, type OpenAIImageOutputFormat, type OpenAIImageQuality, type OpenAIImageSize, type OpenAIImagesOptions, parseOpenAIImageOutputOptions, parseOpenAIImageSize, } from "./openai-images-params.ts";
4
- export declare const generateImages: ImagesFunction<"openai-images", OpenAIImagesOptions>;
4
+ export declare const generateImages: ImagesFunction<OpenAIImagesOptions>;
5
5
  //# sourceMappingURL=openai-images.d.ts.map
@@ -1,19 +1,22 @@
1
1
  import type { Tool as OpenAITool, ResponseCreateParamsStreaming, ResponseInput, ResponseStreamEvent } from "openai/resources/responses/responses.js";
2
- import type { Api, AssistantMessage, Context, Model, Tool, Usage } from "../types.ts";
2
+ import type { Api, AssistantMessage, Message, Model, StreamOptions, Tool, TranscriptContext, Usage } from "../types.ts";
3
3
  import type { AssistantMessageEventStream } from "../utils/event-stream.ts";
4
4
  export interface OpenAIResponsesStreamOptions {
5
- serviceTier?: ResponseCreateParamsStreaming["service_tier"] | "fast";
5
+ onProviderStreamEvent?: StreamOptions["onProviderStreamEvent"];
6
+ serviceTier?: ResponseCreateParamsStreaming["service_tier"] | "fast" | "ultrafast";
6
7
  grammarToolInputProperties?: ReadonlyMap<string, string>;
7
- resolveServiceTier?: (responseServiceTier: ResponseCreateParamsStreaming["service_tier"] | "fast" | undefined, requestServiceTier: ResponseCreateParamsStreaming["service_tier"] | "fast" | undefined) => ResponseCreateParamsStreaming["service_tier"] | "fast" | undefined;
8
- applyServiceTierPricing?: (usage: Usage, serviceTier: ResponseCreateParamsStreaming["service_tier"] | "fast" | undefined) => void;
8
+ resolveServiceTier?: (responseServiceTier: ResponseCreateParamsStreaming["service_tier"] | "fast" | "ultrafast" | undefined, requestServiceTier: ResponseCreateParamsStreaming["service_tier"] | "fast" | "ultrafast" | undefined) => ResponseCreateParamsStreaming["service_tier"] | "fast" | "ultrafast" | undefined;
9
+ applyServiceTierPricing?: (usage: Usage, serviceTier: ResponseCreateParamsStreaming["service_tier"] | "fast" | "ultrafast" | undefined) => void;
9
10
  }
10
11
  export interface ConvertResponsesMessagesOptions {
11
12
  includeSystemPrompt?: boolean;
12
13
  preserveThinking?: boolean;
13
14
  preserveTextSignatures?: boolean;
14
15
  grammarToolInputProperties?: ReadonlyMap<string, string>;
15
- deferredTools?: ReadonlyMap<string, Tool>;
16
- deferredToolsMode?: "additional-tools" | "tool-search";
16
+ /** Whether later system messages are sent in place; otherwise they are folded into the leading prompt. */
17
+ supportsMidConvoSystemMessages?: boolean;
18
+ supportsAdditionalTools?: boolean;
19
+ supportsToolSearch?: boolean;
17
20
  toolOptions?: ConvertResponsesToolsOptions;
18
21
  /**
19
22
  * Send the system prompt as one `input_text` block carrying an explicit
@@ -28,10 +31,30 @@ export interface ConvertResponsesToolsOptions {
28
31
  strict?: boolean | null;
29
32
  supportsStrictMode?: boolean;
30
33
  supportsOpenAIGrammarTools?: boolean;
31
- deferLoading?: boolean;
34
+ toolSearchResult?: boolean;
32
35
  }
33
36
  export declare const CUSTOM_TOOL_CALL_ITEM_ID_SENTINEL = "custom";
34
- export declare function convertResponsesMessages<TApi extends Api>(model: Model<TApi>, context: Context, allowedToolCallProviders: ReadonlySet<string>, options?: ConvertResponsesMessagesOptions): ResponseInput;
37
+ export type ResponsesDeferredToolsMode = "additional-tools" | "tool-search";
38
+ /** `additional_tools` items win over client tool search when a model supports both. */
39
+ export declare function resolveResponsesDeferredToolsMode(compat: {
40
+ supportsAdditionalTools?: boolean;
41
+ supportsToolSearch?: boolean;
42
+ } | undefined): ResponsesDeferredToolsMode | undefined;
43
+ export interface ResponsesToolPlacement {
44
+ /** Tools sent in the top-level `tools` field. */
45
+ requestTools: Tool[];
46
+ /** Whether later system messages load their own `toolsAdded` in place. */
47
+ anchorsAdditions: boolean;
48
+ /** Tools a tool result loads in place through `addedToolNames`. */
49
+ deferred: ReadonlyMap<string, Tool>;
50
+ }
51
+ /**
52
+ * Upstream transcript placement plus the fork's `addedToolNames` deferral: a current tool first named by a
53
+ * tool result's `addedToolNames` (before any call to it) leaves the top-level `tools` field and is loaded
54
+ * where that result appears, so lazy activation never rewrites the cached prefix.
55
+ */
56
+ export declare function resolveResponsesToolPlacement(messages: readonly Message[], supportsToolAdditions: boolean): ResponsesToolPlacement;
57
+ export declare function convertResponsesMessages<TApi extends Api>(model: Model<TApi>, context: TranscriptContext, allowedToolCallProviders: ReadonlySet<string>, options?: ConvertResponsesMessagesOptions): ResponseInput;
35
58
  export declare function convertResponsesTools(tools: readonly Tool[], options?: ConvertResponsesToolsOptions): OpenAITool[];
36
59
  export declare function processResponsesStream<TApi extends Api>(openaiStream: AsyncIterable<ResponseStreamEvent>, output: AssistantMessage, stream: AssistantMessageEventStream, model: Model<TApi>, options?: OpenAIResponsesStreamOptions): Promise<void>;
37
60
  //# sourceMappingURL=openai-responses-shared.d.ts.map
@@ -1,8 +1,11 @@
1
1
  import { CONTEXT_PROVENANCE_FIELD, contextProvenanceFingerprint, getContextProvenance, } from "../context-provenance.js";
2
2
  import { calculateCost, supportsConfigurationUpdate } from "../models.js";
3
+ import { splitDeferredTools } from "../utils/deferred-tools.js";
3
4
  import { shortHash } from "../utils/hash.js";
4
5
  import { parseStreamingJson } from "../utils/json-parse.js";
5
6
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
7
+ import { getSystemMessageText, renderSystemMessageUpdate } from "../utils/text.js";
8
+ import { getCurrentTools, getDeclaredTools, resolveTranscript, resolveTranscriptTools } from "../utils/transcript.js";
6
9
  import { appendGrammarToolInputJsonDelta, getGrammarToolInput, getJsonSchemaToolParameters, resolveGrammarConstrainedSampling, resolveJsonSchemaStrictSampling, } from "./constrained-sampling.js";
7
10
  import { parsePromptCacheDiagnostics } from "./openai-responses-prompt-cache.js";
8
11
  import { withResponsesCompletionGrace } from "./responses-completion-grace.js";
@@ -113,11 +116,36 @@ function withContextProvenance(item, message, seal) {
113
116
  }
114
117
  return item;
115
118
  }
119
+ /** `additional_tools` items win over client tool search when a model supports both. */
120
+ export function resolveResponsesDeferredToolsMode(compat) {
121
+ if (compat?.supportsAdditionalTools)
122
+ return "additional-tools";
123
+ return compat?.supportsToolSearch ? "tool-search" : undefined;
124
+ }
125
+ /**
126
+ * Upstream transcript placement plus the fork's `addedToolNames` deferral: a current tool first named by a
127
+ * tool result's `addedToolNames` (before any call to it) leaves the top-level `tools` field and is loaded
128
+ * where that result appears, so lazy activation never rewrites the cached prefix.
129
+ */
130
+ export function resolveResponsesToolPlacement(messages, supportsToolAdditions) {
131
+ const transcriptTools = resolveTranscriptTools(messages, supportsToolAdditions);
132
+ const { deferred } = splitDeferredTools({ messages: [...messages], tools: getCurrentTools(messages) }, supportsToolAdditions);
133
+ return {
134
+ requestTools: transcriptTools.requestTools.filter((tool) => !deferred.has(tool.name)),
135
+ anchorsAdditions: transcriptTools.anchorsAdditions,
136
+ deferred,
137
+ };
138
+ }
116
139
  // =============================================================================
117
140
  // Message conversion
118
141
  // =============================================================================
119
142
  export function convertResponsesMessages(model, context, allowedToolCallProviders, options) {
143
+ const normalizedContext = resolveTranscript(context, options?.supportsMidConvoSystemMessages);
120
144
  const messages = [];
145
+ const deferredToolsMode = resolveResponsesDeferredToolsMode(options);
146
+ const toolPlacement = resolveResponsesToolPlacement(normalizedContext.messages, deferredToolsMode !== undefined);
147
+ const declaredTools = getDeclaredTools(normalizedContext.messages);
148
+ // One ledger for both in-place loading paths: transcript system messages and `addedToolNames`.
121
149
  const loadedToolNames = new Set();
122
150
  const normalizeIdPart = (part) => {
123
151
  const sanitized = part.replace(/[^a-zA-Z0-9_-]/g, "_");
@@ -146,25 +174,52 @@ export function convertResponsesMessages(model, context, allowedToolCallProvider
146
174
  }
147
175
  return `${normalizedCallId}|${normalizedItemId}`;
148
176
  };
149
- const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId, {
177
+ const transformedMessages = transformMessages(normalizedContext.messages, model, normalizeToolCallId, {
150
178
  preserveThinking: options?.preserveThinking,
151
179
  preserveTextSignatures: options?.preserveTextSignatures,
152
180
  });
153
- const includeSystemPrompt = options?.includeSystemPrompt ?? true;
154
- if (includeSystemPrompt && context.systemPrompt) {
155
- const compat = model.compat;
156
- const role = model.reasoning && compat?.supportsDeveloperRole !== false ? "developer" : "system";
157
- const text = sanitizeSurrogates(context.systemPrompt);
158
- if (options?.systemPromptCacheBreakpoint === true) {
159
- const block = { type: "input_text", text, prompt_cache_breakpoint: { mode: "explicit" } };
160
- messages.push({ role, content: [block] });
161
- }
162
- else {
163
- messages.push({ role, content: text });
181
+ const appendSystemToolAdditions = (message, seed) => {
182
+ const tools = toolPlacement.anchorsAdditions
183
+ ? (message.toolsAdded ?? []).filter((tool) => !loadedToolNames.has(tool.name))
184
+ : [];
185
+ if (tools.length === 0)
186
+ return;
187
+ for (const tool of tools)
188
+ loadedToolNames.add(tool.name);
189
+ if (options?.supportsAdditionalTools) {
190
+ messages.push({
191
+ type: "additional_tools",
192
+ role: "developer",
193
+ tools: convertResponsesTools(tools, options.toolOptions),
194
+ });
195
+ return;
164
196
  }
165
- }
197
+ if (!options?.supportsToolSearch)
198
+ return;
199
+ const names = tools.map((tool) => tool.name);
200
+ const callId = `pi_tool_load_${shortHash(`${seed}:${names.join(",")}`)}`;
201
+ messages.push({
202
+ type: "tool_search_call",
203
+ call_id: callId,
204
+ execution: "client",
205
+ status: "completed",
206
+ arguments: { query: names.join(" "), limit: names.length },
207
+ });
208
+ messages.push({
209
+ type: "tool_search_output",
210
+ call_id: callId,
211
+ execution: "client",
212
+ status: "completed",
213
+ tools: convertResponsesTools(tools, { ...options.toolOptions, toolSearchResult: true }),
214
+ });
215
+ };
216
+ const includeInitialSystemMessage = options?.includeSystemPrompt ?? true;
217
+ const compat = model.compat;
218
+ const instructionRole = model.reasoning && compat?.supportsDeveloperRole !== false ? "developer" : "system";
166
219
  let msgIndex = 0;
220
+ let sourceIndex = 0;
167
221
  for (const msg of transformedMessages) {
222
+ const isLeadingSystemMessage = sourceIndex++ === 0 && msg.role === "system";
168
223
  if (msg.role === "configurationUpdate") {
169
224
  if (!supportsConfigurationUpdate(model))
170
225
  continue;
@@ -180,7 +235,25 @@ export function convertResponsesMessages(model, context, allowedToolCallProvider
180
235
  }
181
236
  continue;
182
237
  }
183
- if (msg.role === "user") {
238
+ if (msg.role === "system") {
239
+ if (!isLeadingSystemMessage)
240
+ appendSystemToolAdditions(msg, `system:${msgIndex}`);
241
+ if (!isLeadingSystemMessage || includeInitialSystemMessage) {
242
+ const text = isLeadingSystemMessage ? getSystemMessageText(msg) : renderSystemMessageUpdate(msg);
243
+ if (text.length > 0 && isLeadingSystemMessage && options?.systemPromptCacheBreakpoint === true) {
244
+ const block = {
245
+ type: "input_text",
246
+ text: sanitizeSurrogates(text),
247
+ prompt_cache_breakpoint: { mode: "explicit" },
248
+ };
249
+ messages.push({ role: instructionRole, content: [block] });
250
+ }
251
+ else if (text.length > 0) {
252
+ messages.push({ role: instructionRole, content: sanitizeSurrogates(text) });
253
+ }
254
+ }
255
+ }
256
+ else if (msg.role === "user") {
184
257
  if (typeof msg.content === "string") {
185
258
  messages.push(withContextProvenance({
186
259
  role: "user",
@@ -259,7 +332,7 @@ export function convertResponsesMessages(model, context, allowedToolCallProvider
259
332
  const [callId, itemIdRaw] = toolCall.id.split("|");
260
333
  const customInputProperty = options?.grammarToolInputProperties?.get(toolCall.name);
261
334
  const isPersistedFreeform = itemIdRaw === CUSTOM_TOOL_CALL_ITEM_ID_SENTINEL;
262
- const isFreeform = isFreeformToolName(toolCall.name, context.tools) || isPersistedFreeform;
335
+ const isFreeform = isFreeformToolName(toolCall.name, declaredTools) || isPersistedFreeform;
263
336
  let itemId = isPersistedFreeform ? undefined : itemIdRaw;
264
337
  // An active grammar declaration wins over sentinel recovery below: its
265
338
  // named input property is richer than the persisted freeform fallback.
@@ -272,7 +345,7 @@ export function convertResponsesMessages(model, context, allowedToolCallProvider
272
345
  (!isFreeform && customInputProperty === undefined && !itemId?.startsWith("fc_"))) {
273
346
  itemId = undefined;
274
347
  }
275
- const canReplayNamespace = isSameModel || options?.deferredTools?.has(toolCall.name) === true;
348
+ const canReplayNamespace = isSameModel || toolPlacement.deferred.has(toolCall.name);
276
349
  if (customInputProperty !== undefined) {
277
350
  output.push({
278
351
  type: "custom_tool_call",
@@ -326,7 +399,7 @@ export function convertResponsesMessages(model, context, allowedToolCallProvider
326
399
  output,
327
400
  }, msg, options?.sealContextProvenance));
328
401
  }
329
- else if (isFreeformToolName(msg.toolName, context.tools) || isPersistedFreeform) {
402
+ else if (isFreeformToolName(msg.toolName, declaredTools) || isPersistedFreeform) {
330
403
  messages.push(withContextProvenance({
331
404
  type: "custom_tool_call_output",
332
405
  call_id: callId,
@@ -339,20 +412,20 @@ export function convertResponsesMessages(model, context, allowedToolCallProvider
339
412
  }
340
413
  const deferredTools = [];
341
414
  for (const name of msg.addedToolNames ?? []) {
342
- const tool = options?.deferredTools?.get(name);
415
+ const tool = toolPlacement.deferred.get(name);
343
416
  if (!tool || loadedToolNames.has(name))
344
417
  continue;
345
418
  loadedToolNames.add(name);
346
419
  deferredTools.push(tool);
347
420
  }
348
- if (deferredTools.length > 0 && options?.deferredToolsMode === "additional-tools") {
421
+ if (deferredTools.length > 0 && deferredToolsMode === "additional-tools") {
349
422
  messages.push(withContextProvenance({
350
423
  type: "additional_tools",
351
424
  role: "developer",
352
- tools: convertResponsesTools(deferredTools, options.toolOptions),
353
- }, msg, options.sealContextProvenance));
425
+ tools: convertResponsesTools(deferredTools, options?.toolOptions),
426
+ }, msg, options?.sealContextProvenance));
354
427
  }
355
- else if (deferredTools.length > 0 && options?.deferredToolsMode === "tool-search") {
428
+ else if (deferredTools.length > 0 && deferredToolsMode === "tool-search") {
356
429
  const names = deferredTools.map((tool) => tool.name);
357
430
  const searchCallId = `pi_tool_load_${shortHash(`${msg.toolCallId}:${names.join(",")}`)}`;
358
431
  messages.push(withContextProvenance({
@@ -369,12 +442,13 @@ export function convertResponsesMessages(model, context, allowedToolCallProvider
369
442
  status: "completed",
370
443
  tools: convertResponsesTools(deferredTools, {
371
444
  ...options?.toolOptions,
372
- deferLoading: true,
445
+ toolSearchResult: true,
373
446
  }),
374
447
  }, msg, options?.sealContextProvenance));
375
448
  }
376
449
  }
377
- msgIndex++;
450
+ if (!isLeadingSystemMessage)
451
+ msgIndex++;
378
452
  }
379
453
  return messages;
380
454
  }
@@ -397,7 +471,7 @@ export function convertResponsesTools(tools, options) {
397
471
  syntax: grammar.format,
398
472
  definition: grammar.definition,
399
473
  },
400
- ...(options?.deferLoading ? { defer_loading: true } : {}),
474
+ ...(options?.toolSearchResult ? { defer_loading: true } : {}),
401
475
  };
402
476
  }
403
477
  if (tool.freeform) {
@@ -406,7 +480,7 @@ export function convertResponsesTools(tools, options) {
406
480
  name: tool.name,
407
481
  description: tool.description,
408
482
  format: tool.freeform,
409
- ...(options?.deferLoading ? { defer_loading: true } : {}),
483
+ ...(options?.toolSearchResult ? { defer_loading: true } : {}),
410
484
  };
411
485
  }
412
486
  const constrainedStrict = resolveJsonSchemaStrictSampling(tool, supportsStrictMode);
@@ -416,7 +490,7 @@ export function convertResponsesTools(tools, options) {
416
490
  name: tool.name,
417
491
  description: tool.description,
418
492
  parameters: getJsonSchemaToolParameters(tool, strict === true),
419
- ...(options?.deferLoading ? { defer_loading: true } : {}),
493
+ ...(options?.toolSearchResult ? { defer_loading: true } : {}),
420
494
  };
421
495
  if (supportsStrictMode) {
422
496
  functionTool.strict = strict;
@@ -712,6 +786,7 @@ export async function processResponsesStream(openaiStream, output, stream, model
712
786
  }
713
787
  };
714
788
  for await (const event of withResponsesCompletionGrace(openaiStream)) {
789
+ await options?.onProviderStreamEvent?.(event, model);
715
790
  if (event.type === "response.created") {
716
791
  output.responseId = event.response.id;
717
792
  }
@@ -920,6 +995,19 @@ export async function processResponsesStream(openaiStream, output, stream, model
920
995
  if (!sawTerminalResponseEvent && !hasFinalizedToolCall) {
921
996
  throw new Error("OpenAI Responses stream ended before a terminal response event");
922
997
  }
998
+ // The agent runs every tool call in the final message. Refuse to hand over calls whose
999
+ // output_item.done never arrived: their arguments may be cut off or mixed up, e.g. when a
1000
+ // non-compliant server omits output_index. Finished calls have their scratch buffers removed.
1001
+ if (output.stopReason === "toolUse") {
1002
+ for (const block of output.content) {
1003
+ if (block.type !== "toolCall")
1004
+ continue;
1005
+ const toolCall = block;
1006
+ if (toolCall.partialJson !== undefined || toolCall.customInput !== undefined) {
1007
+ throw new Error(`OpenAI Responses stream completed with an unfinished tool call: ${toolCall.name} (${toolCall.id})`);
1008
+ }
1009
+ }
1010
+ }
923
1011
  }
924
1012
  function mapStopReason(status, incompleteReason) {
925
1013
  if (!status)
@@ -17,7 +17,7 @@ export interface CachedWebSocketConnection {
17
17
  export interface OpenAIResponsesOptions extends StreamOptions {
18
18
  reasoningEffort?: "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
19
19
  reasoningSummary?: "auto" | "detailed" | "concise" | null;
20
- serviceTier?: ResponseCreateParamsStreaming["service_tier"] | "fast";
20
+ serviceTier?: ResponseCreateParamsStreaming["service_tier"] | "fast" | "ultrafast";
21
21
  toolChoice?: ResponseCreateParamsStreaming["tool_choice"];
22
22
  }
23
23
  /**