@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
@@ -5,6 +5,8 @@ import { headersToRecord } from "../utils/headers.js";
5
5
  import { parseStreamingJson } from "../utils/json-parse.js";
6
6
  import { getPiUserAgent } from "../utils/pi-user-agent.js";
7
7
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
8
+ import { getSystemMessageText, renderSystemMessageUpdate } from "../utils/text.js";
9
+ import { getCurrentTools, resolveTranscript } from "../utils/transcript.js";
8
10
  import { getJsonSchemaToolParameters, resolveJsonSchemaStrictSampling } from "./constrained-sampling.js";
9
11
  import { applyExtraBody, buildBaseOptions, MISTRAL_RESERVED_BODY_KEYS } from "./simple-options.js";
10
12
  import { transformMessages } from "./transform-messages.js";
@@ -15,6 +17,7 @@ const MAX_MISTRAL_ERROR_BODY_CHARS = 4000;
15
17
  */
16
18
  export const stream = (model, context, options) => {
17
19
  const stream = new AssistantMessageEventStream();
20
+ const normalizedContext = resolveTranscript(context, model.compat?.supportsMidConvoSystemMessages);
18
21
  (async () => {
19
22
  const output = createOutput(model);
20
23
  try {
@@ -23,18 +26,20 @@ export const stream = (model, context, options) => {
23
26
  throw new Error(`No API key for provider: ${model.provider}`);
24
27
  }
25
28
  const normalizeMistralToolCallId = createMistralToolCallIdNormalizer();
26
- const preserveThinking = options?.promptMode === "reasoning" || options?.reasoningEffort !== undefined;
27
- const transformedMessages = transformMessages(context.messages, model, (id) => normalizeMistralToolCallId(id), {
29
+ // `reasoning_effort: "none"` is the thinking-off wire form for effort-mapped models, so it must not replay thinking.
30
+ const preserveThinking = options?.promptMode === "reasoning" ||
31
+ (options?.reasoningEffort !== undefined && options.reasoningEffort !== "none");
32
+ const transformedMessages = transformMessages(normalizedContext.messages, model, (id) => normalizeMistralToolCallId(id), {
28
33
  preserveThinking,
29
34
  });
30
- let payload = buildChatPayload(model, context, transformedMessages, options);
35
+ let payload = buildChatPayload(model, normalizedContext, transformedMessages, options);
31
36
  const nextPayload = await options?.onPayload?.(payload, model);
32
37
  if (nextPayload !== undefined) {
33
38
  payload = nextPayload;
34
39
  }
35
40
  const mistralStream = await requestMistralStream(model, payload, apiKey, options);
36
41
  stream.push({ type: "start", partial: output });
37
- await consumeChatStream(model, output, stream, mistralStream);
42
+ await consumeChatStream(model, output, stream, mistralStream, options?.onProviderStreamEvent);
38
43
  if (options?.signal?.aborted) {
39
44
  throw new Error("Request was aborted");
40
45
  }
@@ -74,11 +79,17 @@ export const streamSimple = (model, context, options) => {
74
79
  };
75
80
  const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;
76
81
  const reasoning = clampedReasoning === "off" ? undefined : clampedReasoning;
77
- const shouldUseReasoning = model.reasoning && reasoning !== undefined;
82
+ // Models with a thinking level map use `reasoning_effort`; other reasoning models use `prompt_mode`.
83
+ const effortMap = model.reasoning ? model.thinkingLevelMap : undefined;
84
+ const reasoningEffort = effortMap
85
+ ? reasoning
86
+ ? (effortMap[reasoning] ?? "high")
87
+ : (effortMap.off ?? undefined)
88
+ : undefined;
78
89
  return stream(model, context, {
79
90
  ...base,
80
- promptMode: shouldUseReasoning && usesPromptModeReasoning(model) ? "reasoning" : undefined,
81
- reasoningEffort: shouldUseReasoning && usesReasoningEffort(model) ? mapReasoningEffort(model, reasoning) : undefined,
91
+ promptMode: model.reasoning && !effortMap && reasoning ? "reasoning" : undefined,
92
+ reasoningEffort: reasoningEffort,
82
93
  });
83
94
  };
84
95
  function createOutput(model) {
@@ -357,8 +368,9 @@ function buildChatPayload(model, context, messages, options) {
357
368
  stream: true,
358
369
  messages: toChatMessages(messages, model.input.includes("image")),
359
370
  };
360
- if (context.tools?.length)
361
- payload.tools = toFunctionTools(context.tools);
371
+ const currentTools = getCurrentTools(context.messages);
372
+ if (currentTools.length > 0)
373
+ payload.tools = toFunctionTools(currentTools);
362
374
  if (options?.temperature !== undefined)
363
375
  payload.temperature = options.temperature;
364
376
  if (options?.maxTokens !== undefined)
@@ -371,12 +383,6 @@ function buildChatPayload(model, context, messages, options) {
371
383
  payload.reasoningEffort = options.reasoningEffort;
372
384
  if (shouldUsePromptCaching(options))
373
385
  payload.promptCacheKey = options.sessionId;
374
- if (context.systemPrompt) {
375
- payload.messages.unshift({
376
- role: "system",
377
- content: sanitizeSurrogates(context.systemPrompt),
378
- });
379
- }
380
386
  applyExtraBody(payload, options?.extraBody, MISTRAL_RESERVED_BODY_KEYS);
381
387
  return payload;
382
388
  }
@@ -395,7 +401,7 @@ function getMistralCachedPromptTokens(usage, promptTokens) {
395
401
  const cachedTokens = typeof rawCachedTokens === "number" && Number.isFinite(rawCachedTokens) ? rawCachedTokens : 0;
396
402
  return Math.min(promptTokens, Math.max(0, cachedTokens));
397
403
  }
398
- async function consumeChatStream(model, output, stream, mistralStream) {
404
+ async function consumeChatStream(model, output, stream, mistralStream, onProviderStreamEvent) {
399
405
  let currentBlock = null;
400
406
  const blocks = output.content;
401
407
  const blockIndex = () => blocks.length - 1;
@@ -423,6 +429,7 @@ async function consumeChatStream(model, output, stream, mistralStream) {
423
429
  };
424
430
  for await (const event of mistralStream) {
425
431
  const chunk = event.data;
432
+ await onProviderStreamEvent?.(chunk, model);
426
433
  // Mistral's streamed CompletionChunk carries an id field. Keep the first non-empty one,
427
434
  // mirroring how OpenAI-style streaming exposes a stable response identifier per stream.
428
435
  output.responseId ||= chunk.id;
@@ -455,6 +462,10 @@ async function consumeChatStream(model, output, stream, mistralStream) {
455
462
  for (const item of contentItems) {
456
463
  if (typeof item === "string") {
457
464
  const textDelta = sanitizeSurrogates(item);
465
+ // GLM models on Mistral send empty content deltas around thinking and tool calls.
466
+ // Opening a block for them splits thinking into multiple blocks, which Mistral rejects on replay.
467
+ if (!textDelta)
468
+ continue;
458
469
  if (currentBlock?.type !== "text") {
459
470
  finishCurrentBlock(currentBlock);
460
471
  currentBlock = { type: "text", text: "" };
@@ -495,6 +506,8 @@ async function consumeChatStream(model, output, stream, mistralStream) {
495
506
  }
496
507
  if (item.type === "text") {
497
508
  const textDelta = sanitizeSurrogates(item.text ?? "");
509
+ if (!textDelta)
510
+ continue;
498
511
  if (currentBlock?.type !== "text") {
499
512
  finishCurrentBlock(currentBlock);
500
513
  currentBlock = { type: "text", text: "" };
@@ -601,9 +614,15 @@ function stripSymbolKeys(value) {
601
614
  }
602
615
  function toChatMessages(messages, supportsImages) {
603
616
  const result = [];
604
- for (const msg of messages) {
617
+ for (const [index, msg] of messages.entries()) {
605
618
  if (msg.role === "configurationUpdate")
606
619
  continue;
620
+ if (msg.role === "system") {
621
+ const text = index === 0 ? getSystemMessageText(msg) : renderSystemMessageUpdate(msg);
622
+ if (text.length > 0)
623
+ result.push({ role: "system", content: sanitizeSurrogates(text) });
624
+ continue;
625
+ }
607
626
  if (msg.role === "user") {
608
627
  if (typeof msg.content === "string") {
609
628
  result.push({ role: "user", content: sanitizeSurrogates(msg.content) });
@@ -708,18 +727,6 @@ function buildToolResultText(text, hasImages, supportsImages, isError) {
708
727
  }
709
728
  return isError ? "[tool error] (no tool output)" : "(no tool output)";
710
729
  }
711
- function usesReasoningEffort(model) {
712
- return (model.id === "mistral-small-2603" ||
713
- model.id === "mistral-small-latest" ||
714
- model.id.startsWith("mistral-medium-") ||
715
- model.id === "zai-glm-5-2");
716
- }
717
- function usesPromptModeReasoning(model) {
718
- return model.reasoning && !usesReasoningEffort(model);
719
- }
720
- function mapReasoningEffort(model, level) {
721
- return (model.thinkingLevelMap?.[level] ?? "high");
722
- }
723
730
  function mapToolChoice(choice) {
724
731
  if (!choice)
725
732
  return undefined;
@@ -5,7 +5,7 @@ import { type CodexReasoningSummaryInput } from "./openai-codex-responses/reason
5
5
  export interface OpenAICodexResponsesOptions extends StreamOptions {
6
6
  reasoningEffort?: "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
7
7
  reasoningSummary?: CodexReasoningSummaryInput;
8
- serviceTier?: ResponseCreateParamsStreaming["service_tier"] | "fast";
8
+ serviceTier?: ResponseCreateParamsStreaming["service_tier"] | "fast" | "ultrafast";
9
9
  textVerbosity?: "low" | "medium" | "high";
10
10
  toolChoice?: "auto" | "none" | "required";
11
11
  }
@@ -11,19 +11,20 @@ import { clampThinkingLevel, supportsMax, supportsXhigh } from "../models.js";
11
11
  import { registerSessionResourceCleanup } from "../session-resources.js";
12
12
  import { combineAbortSignals } from "../utils/abort-signals.js";
13
13
  import { extractChatGptSubscriptionAccountId } from "../utils/chatgpt-subscription-auth.js";
14
- import { splitDeferredTools } from "../utils/deferred-tools.js";
15
14
  import { appendAssistantMessageDiagnostic, createAssistantMessageDiagnostic, formatThrownValue, } from "../utils/diagnostics.js";
16
15
  import { formatProviderError, normalizeProviderError } from "../utils/error-body.js";
17
16
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
18
17
  import { headersToRecord } from "../utils/headers.js";
19
18
  import { resolveHttpProxyUrlForTarget } from "../utils/node-http-proxy.js";
20
19
  import { appendRetryAfterMsMarker, extract429RetryAfterMs } from "../utils/retry-hint.js";
20
+ import { getSystemMessageText } from "../utils/text.js";
21
+ import { getDeclaredTools, getInitialSystemMessage, normalizeContext, resolveTranscript } from "../utils/transcript.js";
21
22
  import { uuidv7 } from "../utils/uuid.js";
22
23
  import { createGrammarToolInputProperties } from "./constrained-sampling.js";
23
24
  import { clearWebSocketFallbackState, getOrCreateWebSocketDebugStats, getWebSocketDebugStats, isWebSocketSseFallbackActive, recordWebSocketFailure, recordWebSocketSseFallback, } from "./openai-codex-responses/fallback-state.js";
24
25
  import { buildCodexReasoning } from "./openai-codex-responses/reasoning.js";
25
26
  import { applyChatGptSubscriptionCacheAffinityHeaders, clampOpenAIPromptCacheKey } from "./openai-prompt-cache.js";
26
- import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js";
27
+ import { convertResponsesMessages, convertResponsesTools, processResponsesStream, resolveResponsesDeferredToolsMode, resolveResponsesToolPlacement, } from "./openai-responses-shared.js";
27
28
  import { applyExtraBody, buildBaseOptions, clampMaxForOpenAI, OPENAI_RESPONSES_RESERVED_BODY_KEYS, } from "./simple-options.js";
28
29
  import { startWebSocketLiveness } from "./websocket-liveness.js";
29
30
  import { createWebSocketTransportFailure } from "./websocket-transport-failure.js";
@@ -137,6 +138,7 @@ function copyBytesToArrayBuffer(bytes) {
137
138
  // ============================================================================
138
139
  export const stream = (model, context, options) => {
139
140
  const stream = new AssistantMessageEventStream();
141
+ const normalizedContext = resolveTranscript(context, model.compat?.supportsMidConvoSystemMessages);
140
142
  (async () => {
141
143
  const output = {
142
144
  role: "assistant",
@@ -161,17 +163,18 @@ export const stream = (model, context, options) => {
161
163
  throw new Error(`No API key for provider: ${model.provider}`);
162
164
  }
163
165
  const accountId = extractAccountId(apiKey);
164
- const grammarToolInputProperties = createGrammarToolInputProperties(context.tools, model.compat?.supportsOpenAIGrammarTools ?? false);
166
+ const grammarToolInputProperties = createGrammarToolInputProperties(getDeclaredTools(normalizedContext.messages), model.compat?.supportsOpenAIGrammarTools ?? false);
165
167
  const cacheSessionId = options?.cacheRetention === "none" ? undefined : options?.sessionId;
166
168
  const codexSessionId = clampOpenAIPromptCacheKey(cacheSessionId);
167
- let body = buildRequestBody(model, context, options, codexSessionId, grammarToolInputProperties);
169
+ let body = buildRequestBody(model, normalizedContext, options, codexSessionId, grammarToolInputProperties);
168
170
  const nextBody = await options?.onPayload?.(body, model);
169
171
  if (nextBody !== undefined) {
170
172
  body = nextBody;
171
173
  }
172
174
  const websocketRequestId = codexSessionId || uuidv7();
173
- const sseHeaders = buildSSEHeaders(model.headers, options?.headers, accountId, apiKey, codexSessionId);
174
- const websocketHeaders = buildWebSocketHeaders(model.headers, options?.headers, accountId, apiKey, websocketRequestId);
175
+ const routingHint = buildCodexRoutingHint(body.model, body.service_tier);
176
+ const sseHeaders = buildSSEHeaders(model.headers, options?.headers, accountId, apiKey, routingHint, codexSessionId);
177
+ const websocketHeaders = buildWebSocketHeaders(model.headers, options?.headers, accountId, apiKey, routingHint, websocketRequestId);
175
178
  const bodyJson = JSON.stringify(body);
176
179
  const httpTimeoutMs = normalizeTimeoutMs(options?.timeoutMs);
177
180
  const websocketConnectTimeoutMs = normalizeTimeoutMs(options?.websocketConnectTimeoutMs);
@@ -224,7 +227,7 @@ export const stream = (model, context, options) => {
224
227
  }
225
228
  appendAssistantMessageDiagnostic(output, createAssistantMessageDiagnostic("provider_transport_failure", error, {
226
229
  configuredTransport: transport,
227
- fallbackTransport: websocketStarted ? undefined : "sse",
230
+ ...(websocketStarted ? {} : { fallbackTransport: "sse" }),
228
231
  eventsEmitted: websocketStarted,
229
232
  phase: websocketStarted ? "after_message_stream_start" : "before_message_stream_start",
230
233
  requestBytes: new TextEncoder().encode(bodyJson).byteLength,
@@ -387,7 +390,7 @@ export const streamSimple = (model, context, options) => {
387
390
  // ============================================================================
388
391
  // Request Building
389
392
  // ============================================================================
390
- function buildRequestBody(model, context, options, cacheSessionId, grammarToolInputProperties = createGrammarToolInputProperties(context.tools, model.compat?.supportsOpenAIGrammarTools ?? false)) {
393
+ function buildRequestBody(model, context, options, cacheSessionId, grammarToolInputProperties = createGrammarToolInputProperties(getDeclaredTools(context.messages), model.compat?.supportsOpenAIGrammarTools ?? false)) {
391
394
  const requestedReasoningEffort = options?.reasoningEffort;
392
395
  const mappedReasoningEffort = requestedReasoningEffort === undefined
393
396
  ? undefined
@@ -398,30 +401,26 @@ function buildRequestBody(model, context, options, cacheSessionId, grammarToolIn
398
401
  const reasoningRequested = requestedReasoningEffort !== undefined && requestedReasoningEffort !== "none" && reasoningEffort !== null;
399
402
  const supportsStrictMode = model.compat?.supportsStrictMode ?? true;
400
403
  const supportsOpenAIGrammarTools = model.compat?.supportsOpenAIGrammarTools ?? false;
401
- const deferredToolsMode = model.compat?.supportsAdditionalTools
402
- ? "additional-tools"
403
- : model.compat?.supportsToolSearch
404
- ? "tool-search"
405
- : undefined;
406
- const toolPlacement = splitDeferredTools(context, deferredToolsMode !== undefined);
404
+ const supportsAdditionalTools = model.compat?.supportsAdditionalTools ?? false;
405
+ const supportsToolSearch = model.compat?.supportsToolSearch ?? false;
406
+ const toolPlacement = resolveResponsesToolPlacement(context.messages, resolveResponsesDeferredToolsMode(model.compat) !== undefined);
407
407
  const messages = convertResponsesMessages(model, context, CODEX_TOOL_CALL_PROVIDERS, {
408
408
  includeSystemPrompt: false,
409
409
  preserveThinking: reasoningRequested,
410
410
  preserveTextSignatures: true,
411
411
  grammarToolInputProperties,
412
- deferredTools: toolPlacement.deferred,
413
- deferredToolsMode,
414
- toolOptions: {
415
- strict: null,
416
- supportsStrictMode,
417
- supportsOpenAIGrammarTools,
418
- },
412
+ supportsMidConvoSystemMessages: model.compat?.supportsMidConvoSystemMessages ?? false,
413
+ supportsAdditionalTools,
414
+ supportsToolSearch,
415
+ toolOptions: { strict: null, supportsStrictMode, supportsOpenAIGrammarTools },
419
416
  });
417
+ const initialSystemMessage = getInitialSystemMessage(context.messages);
418
+ const instructions = initialSystemMessage ? getSystemMessageText(initialSystemMessage) : "";
420
419
  const body = {
421
420
  model: model.id,
422
421
  store: false,
423
422
  stream: true,
424
- instructions: context.systemPrompt || "You are a helpful assistant.",
423
+ instructions: instructions || "You are a helpful assistant.",
425
424
  input: messages,
426
425
  text: { verbosity: options?.textVerbosity || "low" },
427
426
  include: ["reasoning.encrypted_content"],
@@ -435,8 +434,8 @@ function buildRequestBody(model, context, options, cacheSessionId, grammarToolIn
435
434
  if (options?.serviceTier !== undefined) {
436
435
  body.service_tier = options.serviceTier;
437
436
  }
438
- if (toolPlacement.immediate.length > 0) {
439
- body.tools = convertResponsesTools(toolPlacement.immediate, {
437
+ if (toolPlacement.requestTools.length > 0) {
438
+ body.tools = convertResponsesTools(toolPlacement.requestTools, {
440
439
  strict: null,
441
440
  supportsStrictMode,
442
441
  supportsOpenAIGrammarTools,
@@ -450,6 +449,10 @@ function buildRequestBody(model, context, options, cacheSessionId, grammarToolIn
450
449
  }
451
450
  function getServiceTierCostMultiplier(model, serviceTier) {
452
451
  switch (serviceTier) {
452
+ case "ultrafast":
453
+ // OpenAI publishes an Ultrafast price for GPT-6 Astra only: 6x Standard on every
454
+ // token class and context tier. Any other model keeps its base rate.
455
+ return model.id === "gpt-6-astra" ? 6 : 1;
453
456
  case "flex":
454
457
  return 0.5;
455
458
  case "priority":
@@ -471,7 +474,10 @@ function applyServiceTierPricing(usage, serviceTier, model) {
471
474
  }
472
475
  function resolveCodexServiceTier(responseServiceTier, requestServiceTier) {
473
476
  if (responseServiceTier === "default" &&
474
- (requestServiceTier === "flex" || requestServiceTier === "priority" || requestServiceTier === "fast")) {
477
+ (requestServiceTier === "flex" ||
478
+ requestServiceTier === "priority" ||
479
+ requestServiceTier === "fast" ||
480
+ requestServiceTier === "ultrafast")) {
475
481
  return requestServiceTier;
476
482
  }
477
483
  return responseServiceTier ?? requestServiceTier;
@@ -497,7 +503,7 @@ function resolveCodexWebSocketUrl(baseUrl) {
497
503
  // Response Processing
498
504
  // ============================================================================
499
505
  async function processStream(response, output, stream, model, grammarToolInputProperties, options) {
500
- await processResponsesStream(mapCodexEvents(parseSSE(response, options?.signal), output), output, stream, model, {
506
+ await processResponsesStream(mapCodexEvents(parseSSE(response, options?.signal), output, model, options?.onProviderStreamEvent), output, stream, model, {
501
507
  serviceTier: options?.serviceTier,
502
508
  grammarToolInputProperties,
503
509
  resolveServiceTier: resolveCodexServiceTier,
@@ -521,8 +527,17 @@ class CodexProtocolError extends Error {
521
527
  this.cause = options?.cause;
522
528
  }
523
529
  }
530
+ class ProviderStreamEventCallbackError extends Error {
531
+ constructor(cause) {
532
+ super(formatThrownValue(cause));
533
+ this.name = "ProviderStreamEventCallbackError";
534
+ this.cause = cause;
535
+ }
536
+ }
524
537
  function isCodexNonTransportError(error) {
525
- return error instanceof CodexApiError || error instanceof CodexProtocolError;
538
+ return (error instanceof CodexApiError ||
539
+ error instanceof CodexProtocolError ||
540
+ error instanceof ProviderStreamEventCallbackError);
526
541
  }
527
542
  function asRecord(value) {
528
543
  return value && typeof value === "object" ? value : null;
@@ -554,8 +569,15 @@ function isPreviousResponseNotFoundError(error) {
554
569
  function isWebSocketConnectionLimitReachedError(error) {
555
570
  return error instanceof CodexApiError && error.code === WEBSOCKET_CONNECTION_LIMIT_REACHED_CODE;
556
571
  }
557
- async function* mapCodexEvents(events, output) {
572
+ async function* mapCodexEvents(events, output, model, onProviderStreamEvent) {
558
573
  for await (const event of events) {
574
+ try {
575
+ await onProviderStreamEvent?.(event, model);
576
+ }
577
+ catch (error) {
578
+ // Keep callback failures out of Codex's WebSocket retry and SSE fallback path.
579
+ throw new ProviderStreamEventCallbackError(error);
580
+ }
559
581
  const type = typeof event.type === "string" ? event.type : undefined;
560
582
  if (!type)
561
583
  continue;
@@ -897,7 +919,15 @@ async function acquireWebSocket(url, headers, sessionId, accountId, signal, conn
897
919
  };
898
920
  }
899
921
  let accountEntries = websocketSessionCache.get(sessionId);
900
- const cached = accountEntries?.get(accountId);
922
+ let cached = accountEntries?.get(accountId);
923
+ const routingHint = headers.get("x-codex-routing-hint");
924
+ if (cached && cached.routingHint !== routingHint) {
925
+ closeWebSocketSilently(cached.socket, 1000, "routing_hint_changed");
926
+ accountEntries?.delete(accountId);
927
+ if (accountEntries?.size === 0)
928
+ websocketSessionCache.delete(sessionId);
929
+ cached = undefined;
930
+ }
901
931
  if (cached) {
902
932
  unparkSessionWebSocket(cached);
903
933
  if (!cached.busy && isWebSocketSessionExpired(cached)) {
@@ -944,7 +974,7 @@ async function acquireWebSocket(url, headers, sessionId, accountId, signal, conn
944
974
  }
945
975
  }
946
976
  const socket = await connectWebSocket(url, headers, signal, connectTimeoutMs, env);
947
- const entry = { socket, busy: true, createdAt: Date.now() };
977
+ const entry = { socket, busy: true, createdAt: Date.now(), routingHint };
948
978
  accountEntries = websocketSessionCache.get(sessionId);
949
979
  if (!accountEntries) {
950
980
  accountEntries = new Map();
@@ -1199,7 +1229,7 @@ async function processWebSocketStream(url, body, headers, output, stream, model,
1199
1229
  recordWebSocketRequestStats(stats, requestBody, reused && !recoveredStalePreviousResponse, useCachedContext);
1200
1230
  try {
1201
1231
  socket.send(JSON.stringify({ type: "response.create", ...requestBody }));
1202
- await processResponsesStream(startWebSocketOutputOnFirstEvent(mapCodexEvents(parseWebSocket(socket, options?.signal, idleTimeoutMs), output), onStart), output, stream, model, {
1232
+ await processResponsesStream(startWebSocketOutputOnFirstEvent(mapCodexEvents(parseWebSocket(socket, options?.signal, idleTimeoutMs), output, model, options?.onProviderStreamEvent), onStart), output, stream, model, {
1203
1233
  serviceTier: options?.serviceTier,
1204
1234
  grammarToolInputProperties,
1205
1235
  resolveServiceTier: resolveCodexServiceTier,
@@ -1225,7 +1255,7 @@ async function processWebSocketStream(url, body, headers, output, stream, model,
1225
1255
  keepConnection = false;
1226
1256
  }
1227
1257
  else if (useCachedContext && entry && output.responseId) {
1228
- const responseItems = convertResponsesMessages(model, { messages: [output] }, CODEX_TOOL_CALL_PROVIDERS, {
1258
+ const responseItems = convertResponsesMessages(model, normalizeContext({ messages: [output] }), CODEX_TOOL_CALL_PROVIDERS, {
1229
1259
  includeSystemPrompt: false,
1230
1260
  grammarToolInputProperties,
1231
1261
  }).filter((item) => item.type !== "function_call_output" && item.type !== "custom_tool_call_output");
@@ -1279,7 +1309,11 @@ async function parseErrorResponse(response) {
1279
1309
  function extractAccountId(token) {
1280
1310
  return extractChatGptSubscriptionAccountId(token);
1281
1311
  }
1282
- function buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token) {
1312
+ /** codex's `x-codex-routing-hint`: `model=<id>`, plus `;tier=<tier>` when the request names a service tier. */
1313
+ function buildCodexRoutingHint(modelId, serviceTier) {
1314
+ return serviceTier ? `model=${modelId};tier=${serviceTier}` : `model=${modelId}`;
1315
+ }
1316
+ function buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token, routingHint) {
1283
1317
  const headers = new Headers(initHeaders);
1284
1318
  for (const [key, value] of Object.entries(additionalHeaders || {})) {
1285
1319
  if (value === null) {
@@ -1300,18 +1334,19 @@ function buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token)
1300
1334
  headers.set("originator", identity);
1301
1335
  const userAgent = _os ? `${identity} (${_os.platform()} ${_os.release()}; ${_os.arch()})` : `${identity} (browser)`;
1302
1336
  headers.set("User-Agent", userAgent);
1337
+ headers.set("x-codex-routing-hint", routingHint);
1303
1338
  return headers;
1304
1339
  }
1305
- function buildSSEHeaders(initHeaders, additionalHeaders, accountId, token, sessionId) {
1306
- const headers = buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token);
1340
+ function buildSSEHeaders(initHeaders, additionalHeaders, accountId, token, routingHint, sessionId) {
1341
+ const headers = buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token, routingHint);
1307
1342
  headers.set("OpenAI-Beta", "responses=experimental");
1308
1343
  headers.set("accept", "text/event-stream");
1309
1344
  headers.set("content-type", "application/json");
1310
1345
  applyChatGptSubscriptionCacheAffinityHeaders(headers, sessionId);
1311
1346
  return headers;
1312
1347
  }
1313
- function buildWebSocketHeaders(initHeaders, additionalHeaders, accountId, token, requestId) {
1314
- const headers = buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token);
1348
+ function buildWebSocketHeaders(initHeaders, additionalHeaders, accountId, token, routingHint, requestId) {
1349
+ const headers = buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token, routingHint);
1315
1350
  headers.delete("accept");
1316
1351
  headers.delete("content-type");
1317
1352
  headers.delete("OpenAI-Beta");
@@ -1,7 +1,8 @@
1
1
  import OpenAI from "openai";
2
2
  import type { ChatCompletionMessageParam } from "openai/resources/chat/completions.js";
3
- import type { Context, Model, SimpleStreamOptions, StreamFunction, StreamOptions, ThinkingBudgets } from "../types.ts";
3
+ import type { Model, SimpleStreamOptions, StreamFunction, StreamOptions, ThinkingBudgets } from "../types.ts";
4
4
  import { type ResolvedOpenAICompletionsCompat } from "../utils/prompt-cache-ttl.ts";
5
+ import { type TranscriptContext } from "../utils/transcript.ts";
5
6
  export { getOpenAICompletionsCompat as getCompat, type ResolvedOpenAICompletionsCompat, } from "../utils/prompt-cache-ttl.ts";
6
7
  export interface OpenAICompletionsOptions extends StreamOptions {
7
8
  toolChoice?: OpenAI.Chat.Completions.ChatCompletionToolChoiceOption;
@@ -15,5 +16,5 @@ export interface ConvertCompletionsMessagesOptions {
15
16
  }
16
17
  export declare const stream: StreamFunction<"openai-completions", OpenAICompletionsOptions>;
17
18
  export declare const streamSimple: StreamFunction<"openai-completions", SimpleStreamOptions>;
18
- export declare function convertMessages(model: Model<"openai-completions">, context: Context, compat: ResolvedOpenAICompletionsCompat, options?: ConvertCompletionsMessagesOptions): ChatCompletionMessageParam[];
19
+ export declare function convertMessages(model: Model<"openai-completions">, context: TranscriptContext, compat: ResolvedOpenAICompletionsCompat, options?: ConvertCompletionsMessagesOptions): ChatCompletionMessageParam[];
19
20
  //# sourceMappingURL=openai-completions.d.ts.map