@code-yeongyu/senpi-ai 2026.7.28 → 2026.7.29-4

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 (82) hide show
  1. package/dist/api/openai-client-auth.d.ts +7 -0
  2. package/dist/api/openai-client-auth.d.ts.map +1 -0
  3. package/dist/api/openai-client-auth.js +15 -0
  4. package/dist/api/openai-client-auth.js.map +1 -0
  5. package/dist/api/openai-completions.d.ts.map +1 -1
  6. package/dist/api/openai-completions.js +125 -58
  7. package/dist/api/openai-completions.js.map +1 -1
  8. package/dist/api/openai-responses.d.ts.map +1 -1
  9. package/dist/api/openai-responses.js +9 -22
  10. package/dist/api/openai-responses.js.map +1 -1
  11. package/dist/auth/headers.d.ts +5 -0
  12. package/dist/auth/headers.d.ts.map +1 -0
  13. package/dist/auth/headers.js +41 -0
  14. package/dist/auth/headers.js.map +1 -0
  15. package/dist/index.d.ts +3 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +3 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/providers/data/.manifest.json +1 -1
  20. package/dist/providers/data/openrouter.json +1 -1
  21. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  22. package/dist/tool-call-middleware/context-transformer.d.ts.map +1 -1
  23. package/dist/tool-call-middleware/context-transformer.js +9 -0
  24. package/dist/tool-call-middleware/context-transformer.js.map +1 -1
  25. package/dist/tool-call-middleware/index.d.ts +5 -0
  26. package/dist/tool-call-middleware/index.d.ts.map +1 -1
  27. package/dist/tool-call-middleware/index.js +12 -2
  28. package/dist/tool-call-middleware/index.js.map +1 -1
  29. package/dist/tool-call-middleware/protocols/kimi-xtml/format.d.ts +5 -0
  30. package/dist/tool-call-middleware/protocols/kimi-xtml/format.d.ts.map +1 -0
  31. package/dist/tool-call-middleware/protocols/kimi-xtml/format.js +54 -0
  32. package/dist/tool-call-middleware/protocols/kimi-xtml/format.js.map +1 -0
  33. package/dist/tool-call-middleware/protocols/kimi-xtml/index.d.ts +4 -0
  34. package/dist/tool-call-middleware/protocols/kimi-xtml/index.d.ts.map +1 -0
  35. package/dist/tool-call-middleware/protocols/kimi-xtml/index.js +4 -0
  36. package/dist/tool-call-middleware/protocols/kimi-xtml/index.js.map +1 -0
  37. package/dist/tool-call-middleware/protocols/kimi-xtml/markers.d.ts +10 -0
  38. package/dist/tool-call-middleware/protocols/kimi-xtml/markers.d.ts.map +1 -0
  39. package/dist/tool-call-middleware/protocols/kimi-xtml/markers.js +30 -0
  40. package/dist/tool-call-middleware/protocols/kimi-xtml/markers.js.map +1 -0
  41. package/dist/tool-call-middleware/protocols/kimi-xtml/parse.d.ts +12 -0
  42. package/dist/tool-call-middleware/protocols/kimi-xtml/parse.d.ts.map +1 -0
  43. package/dist/tool-call-middleware/protocols/kimi-xtml/parse.js +95 -0
  44. package/dist/tool-call-middleware/protocols/kimi-xtml/parse.js.map +1 -0
  45. package/dist/tool-call-middleware/protocols/kimi-xtml/recovery-stream.d.ts +5 -0
  46. package/dist/tool-call-middleware/protocols/kimi-xtml/recovery-stream.d.ts.map +1 -0
  47. package/dist/tool-call-middleware/protocols/kimi-xtml/recovery-stream.js +207 -0
  48. package/dist/tool-call-middleware/protocols/kimi-xtml/recovery-stream.js.map +1 -0
  49. package/dist/tool-call-middleware/protocols/kimi-xtml/stream.d.ts +4 -0
  50. package/dist/tool-call-middleware/protocols/kimi-xtml/stream.d.ts.map +1 -0
  51. package/dist/tool-call-middleware/protocols/kimi-xtml/stream.js +181 -0
  52. package/dist/tool-call-middleware/protocols/kimi-xtml/stream.js.map +1 -0
  53. package/dist/tool-call-middleware/recovery-diagnostics.d.ts +2 -1
  54. package/dist/tool-call-middleware/recovery-diagnostics.d.ts.map +1 -1
  55. package/dist/tool-call-middleware/recovery-diagnostics.js +2 -2
  56. package/dist/tool-call-middleware/recovery-diagnostics.js.map +1 -1
  57. package/dist/tool-call-middleware/recovery-native-projection.d.ts +3 -2
  58. package/dist/tool-call-middleware/recovery-native-projection.d.ts.map +1 -1
  59. package/dist/tool-call-middleware/recovery-native-projection.js +3 -2
  60. package/dist/tool-call-middleware/recovery-native-projection.js.map +1 -1
  61. package/dist/tool-call-middleware/recovery-stream-failure.d.ts +6 -1
  62. package/dist/tool-call-middleware/recovery-stream-failure.d.ts.map +1 -1
  63. package/dist/tool-call-middleware/recovery-stream-failure.js +3 -2
  64. package/dist/tool-call-middleware/recovery-stream-failure.js.map +1 -1
  65. package/dist/tool-call-middleware/recovery-stream-wrapper.d.ts +7 -1
  66. package/dist/tool-call-middleware/recovery-stream-wrapper.d.ts.map +1 -1
  67. package/dist/tool-call-middleware/recovery-stream-wrapper.js +12 -5
  68. package/dist/tool-call-middleware/recovery-stream-wrapper.js.map +1 -1
  69. package/dist/tool-call-middleware/recovery-text-projection.d.ts +7 -1
  70. package/dist/tool-call-middleware/recovery-text-projection.d.ts.map +1 -1
  71. package/dist/tool-call-middleware/recovery-text-projection.js +6 -4
  72. package/dist/tool-call-middleware/recovery-text-projection.js.map +1 -1
  73. package/dist/tool-call-middleware/types.d.ts +2 -1
  74. package/dist/tool-call-middleware/types.d.ts.map +1 -1
  75. package/dist/tool-call-middleware/types.js.map +1 -1
  76. package/dist/types.d.ts +1 -1
  77. package/dist/types.js.map +1 -1
  78. package/dist/utils/retry.d.ts +7 -0
  79. package/dist/utils/retry.d.ts.map +1 -1
  80. package/dist/utils/retry.js +29 -0
  81. package/dist/utils/retry.js.map +1 -1
  82. package/package.json +14 -14
@@ -0,0 +1,7 @@
1
+ import type { ProviderHeaders } from "../types.ts";
2
+ export interface OpenAIClientAuth {
3
+ apiKey: string;
4
+ headers?: ProviderHeaders;
5
+ }
6
+ export declare function resolveOpenAIClientAuth(provider: string, apiKey: string | undefined, headers: ProviderHeaders | undefined): OpenAIClientAuth;
7
+ //# sourceMappingURL=openai-client-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-client-auth.d.ts","sourceRoot":"","sources":["../../src/api/openai-client-auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,eAAe,CAAC;CAC1B;AAED,wBAAgB,uBAAuB,CACtC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,eAAe,GAAG,SAAS,GAClC,gBAAgB,CAUlB"}
@@ -0,0 +1,15 @@
1
+ import { hasCredentialHeaders, hasHeader } from "../auth/headers.js";
2
+ export function resolveOpenAIClientAuth(provider, apiKey, headers) {
3
+ if (apiKey)
4
+ return { apiKey, headers };
5
+ if (!hasCredentialHeaders(headers))
6
+ throw new Error(`No API key for provider: ${provider}`);
7
+ if (hasHeader(headers, "authorization") || hasHeader(headers, "cf-aig-authorization")) {
8
+ return { apiKey: "unused", headers };
9
+ }
10
+ return {
11
+ apiKey: "unused",
12
+ headers: { Authorization: null, ...headers },
13
+ };
14
+ }
15
+ //# sourceMappingURL=openai-client-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-client-auth.js","sourceRoot":"","sources":["../../src/api/openai-client-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAQrE,MAAM,UAAU,uBAAuB,CACtC,QAAgB,EAChB,MAA0B,EAC1B,OAAoC;IAEpC,IAAI,MAAM;QAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IACvC,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,EAAE,CAAC,CAAC;IAC5F,IAAI,SAAS,CAAC,OAAO,EAAE,eAAe,CAAC,IAAI,SAAS,CAAC,OAAO,EAAE,sBAAsB,CAAC,EAAE,CAAC;QACvF,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IACtC,CAAC;IACD,OAAO;QACN,MAAM,EAAE,QAAQ;QAChB,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,OAAO,EAAE;KAC5C,CAAC;AACH,CAAC","sourcesContent":["import { hasCredentialHeaders, hasHeader } from \"../auth/headers.ts\";\nimport type { ProviderHeaders } from \"../types.ts\";\n\nexport interface OpenAIClientAuth {\n\tapiKey: string;\n\theaders?: ProviderHeaders;\n}\n\nexport function resolveOpenAIClientAuth(\n\tprovider: string,\n\tapiKey: string | undefined,\n\theaders: ProviderHeaders | undefined,\n): OpenAIClientAuth {\n\tif (apiKey) return { apiKey, headers };\n\tif (!hasCredentialHeaders(headers)) throw new Error(`No API key for provider: ${provider}`);\n\tif (hasHeader(headers, \"authorization\") || hasHeader(headers, \"cf-aig-authorization\")) {\n\t\treturn { apiKey: \"unused\", headers };\n\t}\n\treturn {\n\t\tapiKey: \"unused\",\n\t\theaders: { Authorization: null, ...headers },\n\t};\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"openai-completions.d.ts","sourceRoot":"","sources":["../../src/api/openai-completions.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,KAAK,EAOX,0BAA0B,EAI1B,MAAM,sCAAsC,CAAC;AAG9C,OAAO,KAAK,EAIX,OAAO,EAGP,KAAK,EAIL,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AAMrB,OAAO,EAEN,KAAK,+BAA+B,EACpC,MAAM,8BAA8B,CAAC;AA2BtC,OAAO,EACN,0BAA0B,IAAI,SAAS,EACvC,KAAK,+BAA+B,GACpC,MAAM,8BAA8B,CAAC;AA2NtC,MAAM,WAAW,wBAAyB,SAAQ,aAAa;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,8BAA8B,CAAC;IACpE,eAAe,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;CAC1E;AAED,MAAM,WAAW,iCAAiC;IACjD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,0BAA0B,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzD;AAwCD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,wBAAwB,CAgbjF,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAyBlF,CAAC;AA8XF,wBAAgB,eAAe,CAC9B,KAAK,EAAE,KAAK,CAAC,oBAAoB,CAAC,EAClC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,+BAA+B,EACvC,OAAO,GAAE,iCAAsC,GAC7C,0BAA0B,EAAE,CAgS9B"}
1
+ {"version":3,"file":"openai-completions.d.ts","sourceRoot":"","sources":["../../src/api/openai-completions.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,KAAK,EAOX,0BAA0B,EAI1B,MAAM,sCAAsC,CAAC;AAG9C,OAAO,KAAK,EAIX,OAAO,EAGP,KAAK,EAIL,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAMb,MAAM,aAAa,CAAC;AAMrB,OAAO,EAEN,KAAK,+BAA+B,EACpC,MAAM,8BAA8B,CAAC;AA4BtC,OAAO,EACN,0BAA0B,IAAI,SAAS,EACvC,KAAK,+BAA+B,GACpC,MAAM,8BAA8B,CAAC;AA4MtC,MAAM,WAAW,wBAAyB,SAAQ,aAAa;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,8BAA8B,CAAC;IACpE,eAAe,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;CAC1E;AAED,MAAM,WAAW,iCAAiC;IACjD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,0BAA0B,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzD;AAwCD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,wBAAwB,CA0fjF,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CAyBlF,CAAC;AA8XF,wBAAgB,eAAe,CAC9B,KAAK,EAAE,KAAK,CAAC,oBAAoB,CAAC,EAClC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,+BAA+B,EACvC,OAAO,GAAE,iCAAsC,GAC7C,0BAA0B,EAAE,CAgS9B"}
@@ -13,6 +13,7 @@ import { isForcedToolChoiceUnsupportedError, omitToolChoiceParam } from "../util
13
13
  import { normalizeToolParametersForMoonshot, normalizeToolParametersForOpenAICompat, } from "../utils/tool-schema-compat.js";
14
14
  import { appendGrammarToolInputJsonDelta, createGrammarToolInputProperties, getGrammarToolInput, resolveGrammarConstrainedSampling, resolveJsonSchemaStrictSampling, } from "./constrained-sampling.js";
15
15
  import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
16
+ import { resolveOpenAIClientAuth } from "./openai-client-auth.js";
16
17
  import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.js";
17
18
  import { applyExtraBody, buildBaseOptions, clampMaxForOpenAI, OPENAI_COMPLETIONS_RESERVED_BODY_KEYS, } from "./simple-options.js";
18
19
  import { transformMessages } from "./transform-messages.js";
@@ -103,23 +104,6 @@ function resolveReasoningEffort(thinkingLevelMap, effort) {
103
104
  * This is needed because Anthropic (via proxy) requires the tools param
104
105
  * to be present when messages include tool_calls or tool role messages.
105
106
  */
106
- function hasHeader(headers, name) {
107
- if (!headers)
108
- return false;
109
- const expected = name.toLowerCase();
110
- for (const [key, value] of Object.entries(headers)) {
111
- if (key.toLowerCase() === expected && value !== null && value.trim().length > 0)
112
- return true;
113
- }
114
- return false;
115
- }
116
- function getClientApiKey(provider, apiKey, headers) {
117
- if (apiKey)
118
- return apiKey;
119
- if (hasHeader(headers, "authorization") || hasHeader(headers, "cf-aig-authorization"))
120
- return "unused";
121
- throw new Error(`No API key for provider: ${provider}`);
122
- }
123
107
  function hasToolHistory(messages) {
124
108
  for (const msg of messages) {
125
109
  if (msg.role === "toolResult") {
@@ -217,12 +201,12 @@ export const stream = (model, context, options) => {
217
201
  timestamp: Date.now(),
218
202
  };
219
203
  try {
220
- const apiKey = getClientApiKey(model.provider, options?.apiKey, options?.headers);
204
+ const clientAuth = resolveOpenAIClientAuth(model.provider, options?.apiKey, options?.headers);
221
205
  const compat = getCompat(model);
222
206
  const grammarToolInputProperties = createGrammarToolInputProperties(context.tools, compat.supportsOpenAIGrammarTools);
223
207
  const cacheRetention = resolveCacheRetention(options?.cacheRetention ?? model.cacheRetention, options?.env);
224
208
  const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
225
- const client = createClient(model, context, apiKey, options?.headers, cacheSessionId, compat);
209
+ const client = createClient(model, context, clientAuth.apiKey, clientAuth.headers, cacheSessionId, compat);
226
210
  let params = buildParams(model, context, options, compat, cacheRetention, grammarToolInputProperties);
227
211
  const nextParams = await options?.onPayload?.(params, model);
228
212
  if (nextParams !== undefined) {
@@ -256,7 +240,12 @@ export const stream = (model, context, options) => {
256
240
  stream.push({ type: "start", partial: output });
257
241
  let textBlock = null;
258
242
  let thinkingBlock = null;
243
+ let activeBlock = null;
244
+ let deferMixedEvents = false;
259
245
  let hasFinishReason = false;
246
+ const deferredTextDeltas = [];
247
+ const deferredThinkingDeltas = [];
248
+ const deferredToolCallDeltas = new Map();
260
249
  const toolCallBlocksByIndex = new Map();
261
250
  const toolCallBlocksById = new Map();
262
251
  const pendingReasoningDetailsByToolCallId = new Map();
@@ -326,23 +315,47 @@ export const stream = (model, context, options) => {
326
315
  });
327
316
  }
328
317
  };
318
+ const finishActiveBlock = () => {
319
+ if (!activeBlock)
320
+ return;
321
+ finishBlock(activeBlock);
322
+ activeBlock = null;
323
+ };
329
324
  const ensureTextBlock = () => {
330
325
  if (!textBlock) {
326
+ if (!deferMixedEvents) {
327
+ finishActiveBlock();
328
+ thinkingBlock = null;
329
+ }
331
330
  textBlock = { type: "text", text: "" };
332
331
  blocks.push(textBlock);
333
- stream.push({ type: "text_start", contentIndex: getContentIndex(textBlock), partial: output });
332
+ if (!deferMixedEvents) {
333
+ activeBlock = textBlock;
334
+ stream.push({ type: "text_start", contentIndex: getContentIndex(textBlock), partial: output });
335
+ }
334
336
  }
335
337
  return textBlock;
336
338
  };
337
339
  const ensureThinkingBlock = (thinkingSignature) => {
338
340
  if (!thinkingBlock) {
341
+ if (!deferMixedEvents) {
342
+ finishActiveBlock();
343
+ textBlock = null;
344
+ }
339
345
  thinkingBlock = {
340
346
  type: "thinking",
341
347
  thinking: "",
342
348
  thinkingSignature,
343
349
  };
344
350
  blocks.push(thinkingBlock);
345
- stream.push({ type: "thinking_start", contentIndex: getContentIndex(thinkingBlock), partial: output });
351
+ if (!deferMixedEvents) {
352
+ activeBlock = thinkingBlock;
353
+ stream.push({
354
+ type: "thinking_start",
355
+ contentIndex: getContentIndex(thinkingBlock),
356
+ partial: output,
357
+ });
358
+ }
346
359
  }
347
360
  return thinkingBlock;
348
361
  };
@@ -364,6 +377,11 @@ export const stream = (model, context, options) => {
364
377
  block = toolCallBlocksById.get(toolCall.id);
365
378
  }
366
379
  if (!block) {
380
+ if (!deferMixedEvents) {
381
+ finishActiveBlock();
382
+ textBlock = null;
383
+ thinkingBlock = null;
384
+ }
367
385
  // Note: the "input" fallback here should/must not be taken. in case the LLM makes up
368
386
  // a tool we don't knwo about, we at least have a place to stash our stuff.
369
387
  const customInputProperty = toolCall.custom
@@ -388,11 +406,14 @@ export const stream = (model, context, options) => {
388
406
  toolCallBlocksById.set(toolCall.id, block);
389
407
  }
390
408
  blocks.push(block);
391
- stream.push({
392
- type: "toolcall_start",
393
- contentIndex: getContentIndex(block),
394
- partial: output,
395
- });
409
+ if (!deferMixedEvents) {
410
+ activeBlock = block;
411
+ stream.push({
412
+ type: "toolcall_start",
413
+ contentIndex: getContentIndex(block),
414
+ partial: output,
415
+ });
416
+ }
396
417
  }
397
418
  if (streamIndex !== undefined && block.streamIndex === undefined) {
398
419
  block.streamIndex = streamIndex;
@@ -416,6 +437,30 @@ export const stream = (model, context, options) => {
416
437
  applyPendingReasoningDetail(block);
417
438
  return block;
418
439
  };
440
+ const flushDeferredBlocks = () => {
441
+ for (const block of blocks) {
442
+ const contentIndex = getContentIndex(block);
443
+ if (block.type === "text") {
444
+ stream.push({ type: "text_start", contentIndex, partial: output });
445
+ for (const delta of deferredTextDeltas) {
446
+ stream.push({ type: "text_delta", contentIndex, delta, partial: output });
447
+ }
448
+ }
449
+ else if (block.type === "thinking") {
450
+ stream.push({ type: "thinking_start", contentIndex, partial: output });
451
+ for (const delta of deferredThinkingDeltas) {
452
+ stream.push({ type: "thinking_delta", contentIndex, delta, partial: output });
453
+ }
454
+ }
455
+ else {
456
+ stream.push({ type: "toolcall_start", contentIndex, partial: output });
457
+ for (const delta of deferredToolCallDeltas.get(block) ?? []) {
458
+ stream.push({ type: "toolcall_delta", contentIndex, delta, partial: output });
459
+ }
460
+ }
461
+ finishBlock(block);
462
+ }
463
+ };
419
464
  for await (const chunk of openaiStream) {
420
465
  if (!chunk || typeof chunk !== "object")
421
466
  continue;
@@ -446,22 +491,9 @@ export const stream = (model, context, options) => {
446
491
  hasFinishReason = true;
447
492
  }
448
493
  if (choice.delta) {
449
- if (choice.delta.content !== null &&
450
- choice.delta.content !== undefined &&
451
- choice.delta.content.length > 0) {
452
- const block = ensureTextBlock();
453
- block.text += choice.delta.content;
454
- stream.push({
455
- type: "text_delta",
456
- contentIndex: getContentIndex(block),
457
- delta: choice.delta.content,
458
- partial: output,
459
- });
460
- }
461
- // Some endpoints return reasoning in reasoning_content (llama.cpp),
462
- // or reasoning (other openai compatible endpoints)
463
- // Use the first non-empty reasoning field to avoid duplication
464
- // (e.g., chutes.ai returns both reasoning_content and reasoning with same content)
494
+ const contentDelta = typeof choice.delta.content === "string" && choice.delta.content.length > 0
495
+ ? choice.delta.content
496
+ : null;
465
497
  const reasoningFields = ["reasoning_content", "reasoning", "reasoning_text"];
466
498
  const deltaFields = choice.delta;
467
499
  let foundReasoningField = null;
@@ -472,6 +504,28 @@ export const stream = (model, context, options) => {
472
504
  break;
473
505
  }
474
506
  }
507
+ if (blocks.length === 0 && contentDelta && foundReasoningField) {
508
+ deferMixedEvents = true;
509
+ }
510
+ if (contentDelta) {
511
+ const block = ensureTextBlock();
512
+ block.text += contentDelta;
513
+ if (deferMixedEvents) {
514
+ deferredTextDeltas.push(contentDelta);
515
+ }
516
+ else {
517
+ stream.push({
518
+ type: "text_delta",
519
+ contentIndex: getContentIndex(block),
520
+ delta: contentDelta,
521
+ partial: output,
522
+ });
523
+ }
524
+ }
525
+ // Some endpoints return reasoning in reasoning_content (llama.cpp),
526
+ // or reasoning (other openai compatible endpoints)
527
+ // Use the first non-empty reasoning field to avoid duplication
528
+ // (e.g., chutes.ai returns both reasoning_content and reasoning with same content)
475
529
  if (foundReasoningField) {
476
530
  const delta = deltaFields[foundReasoningField];
477
531
  if (typeof delta === "string" && delta.length > 0) {
@@ -480,12 +534,17 @@ export const stream = (model, context, options) => {
480
534
  : foundReasoningField;
481
535
  const block = ensureThinkingBlock(thinkingSignature);
482
536
  block.thinking += delta;
483
- stream.push({
484
- type: "thinking_delta",
485
- contentIndex: getContentIndex(block),
486
- delta,
487
- partial: output,
488
- });
537
+ if (deferMixedEvents) {
538
+ deferredThinkingDeltas.push(delta);
539
+ }
540
+ else {
541
+ stream.push({
542
+ type: "thinking_delta",
543
+ contentIndex: getContentIndex(block),
544
+ delta,
545
+ partial: output,
546
+ });
547
+ }
489
548
  }
490
549
  }
491
550
  if (choice?.delta?.tool_calls) {
@@ -509,12 +568,19 @@ export const stream = (model, context, options) => {
509
568
  const nextInput = getCustomToolCallInput(block) + toolCall.custom.input;
510
569
  delta = appendCustomToolCallInput(block, nextInput, false) ?? "";
511
570
  }
512
- stream.push({
513
- type: "toolcall_delta",
514
- contentIndex: getContentIndex(block),
515
- delta,
516
- partial: output,
517
- });
571
+ if (deferMixedEvents) {
572
+ const deltas = deferredToolCallDeltas.get(block) ?? [];
573
+ deltas.push(delta);
574
+ deferredToolCallDeltas.set(block, deltas);
575
+ }
576
+ else {
577
+ stream.push({
578
+ type: "toolcall_delta",
579
+ contentIndex: getContentIndex(block),
580
+ delta,
581
+ partial: output,
582
+ });
583
+ }
518
584
  }
519
585
  }
520
586
  const reasoningDetails = choice.delta.reasoning_details;
@@ -534,9 +600,10 @@ export const stream = (model, context, options) => {
534
600
  }
535
601
  }
536
602
  }
537
- for (const block of blocks) {
538
- finishBlock(block);
539
- }
603
+ if (deferMixedEvents)
604
+ flushDeferredBlocks();
605
+ else
606
+ finishActiveBlock();
540
607
  if (options?.signal?.aborted) {
541
608
  throw new Error("Request was aborted");
542
609
  }
@@ -577,7 +644,7 @@ export const stream = (model, context, options) => {
577
644
  return stream;
578
645
  };
579
646
  export const streamSimple = (model, context, options) => {
580
- getClientApiKey(model.provider, options?.apiKey, options?.headers);
647
+ resolveOpenAIClientAuth(model.provider, options?.apiKey, options?.headers);
581
648
  const base = buildBaseOptions(model, context, options, options?.apiKey);
582
649
  const compat = getCompat(model);
583
650
  const thinkingLevelMap = getThinkingLevelMap(model, compat);