@sema-agent/core 1.294.0 → 1.296.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/agents/subagent.d.ts.map +1 -1
  2. package/dist/agents/subagent.js +24 -3
  3. package/dist/agents/subagent.js.map +1 -1
  4. package/dist/bin/sema-tb.d.ts +20 -0
  5. package/dist/bin/sema-tb.d.ts.map +1 -1
  6. package/dist/bin/sema-tb.js +71 -23
  7. package/dist/bin/sema-tb.js.map +1 -1
  8. package/dist/brain/anthropic.d.ts.map +1 -1
  9. package/dist/brain/anthropic.js +17 -3
  10. package/dist/brain/anthropic.js.map +1 -1
  11. package/dist/brain/openai.d.ts +1 -0
  12. package/dist/brain/openai.d.ts.map +1 -1
  13. package/dist/brain/openai.js +328 -298
  14. package/dist/brain/openai.js.map +1 -1
  15. package/dist/brain/stream-engine.d.ts +7 -0
  16. package/dist/brain/stream-engine.d.ts.map +1 -1
  17. package/dist/brain/stream-engine.js +44 -11
  18. package/dist/brain/stream-engine.js.map +1 -1
  19. package/dist/brain/timeout.d.ts +2 -1
  20. package/dist/brain/timeout.d.ts.map +1 -1
  21. package/dist/brain/timeout.js +14 -1
  22. package/dist/brain/timeout.js.map +1 -1
  23. package/dist/core/ask-question.d.ts.map +1 -1
  24. package/dist/core/ask-question.js.map +1 -1
  25. package/dist/core/hooks.d.ts +3 -3
  26. package/dist/core/hooks.d.ts.map +1 -1
  27. package/dist/core/hooks.js +11 -4
  28. package/dist/core/hooks.js.map +1 -1
  29. package/dist/core/present-plan-tool.d.ts.map +1 -1
  30. package/dist/core/present-plan-tool.js +12 -13
  31. package/dist/core/present-plan-tool.js.map +1 -1
  32. package/dist/core/runner/assemble-result.d.ts +1 -0
  33. package/dist/core/runner/assemble-result.d.ts.map +1 -1
  34. package/dist/core/runner/assemble-result.js.map +1 -1
  35. package/dist/core/runner/call-cap.d.ts +4 -1
  36. package/dist/core/runner/call-cap.d.ts.map +1 -1
  37. package/dist/core/runner/call-cap.js +4 -1
  38. package/dist/core/runner/call-cap.js.map +1 -1
  39. package/dist/core/runner/cut-kill.d.ts +11 -0
  40. package/dist/core/runner/cut-kill.d.ts.map +1 -0
  41. package/dist/core/runner/cut-kill.js +38 -0
  42. package/dist/core/runner/cut-kill.js.map +1 -0
  43. package/dist/core/runner/prepare-task.d.ts +5 -0
  44. package/dist/core/runner/prepare-task.d.ts.map +1 -1
  45. package/dist/core/runner/prepare-task.js +70 -23
  46. package/dist/core/runner/prepare-task.js.map +1 -1
  47. package/dist/core/runner/runtask.d.ts.map +1 -1
  48. package/dist/core/runner/runtask.js +22 -6
  49. package/dist/core/runner/runtask.js.map +1 -1
  50. package/dist/core/sensitive-path-policy.d.ts +2 -1
  51. package/dist/core/sensitive-path-policy.d.ts.map +1 -1
  52. package/dist/core/sensitive-path-policy.js +22 -0
  53. package/dist/core/sensitive-path-policy.js.map +1 -1
  54. package/dist/core/task-registry.d.ts +4 -0
  55. package/dist/core/task-registry.d.ts.map +1 -1
  56. package/dist/core/task-registry.js +83 -15
  57. package/dist/core/task-registry.js.map +1 -1
  58. package/dist/core/tool-policy.d.ts +5 -2
  59. package/dist/core/tool-policy.d.ts.map +1 -1
  60. package/dist/core/tool-policy.js +9 -0
  61. package/dist/core/tool-policy.js.map +1 -1
  62. package/dist/core/trace.d.ts +3 -0
  63. package/dist/core/trace.d.ts.map +1 -1
  64. package/dist/core/trace.js.map +1 -1
  65. package/dist/core/types.d.ts +6 -0
  66. package/dist/core/types.d.ts.map +1 -1
  67. package/dist/engine/compaction/utils.d.ts.map +1 -1
  68. package/dist/engine/compaction/utils.js +3 -1
  69. package/dist/engine/compaction/utils.js.map +1 -1
  70. package/dist/engine/execution-env/kill-tree.d.ts +1 -0
  71. package/dist/engine/execution-env/kill-tree.d.ts.map +1 -1
  72. package/dist/engine/execution-env/kill-tree.js +18 -2
  73. package/dist/engine/execution-env/kill-tree.js.map +1 -1
  74. package/dist/engine/execution-env/node-execution-env.d.ts +5 -0
  75. package/dist/engine/execution-env/node-execution-env.d.ts.map +1 -1
  76. package/dist/engine/execution-env/node-execution-env.js +19 -0
  77. package/dist/engine/execution-env/node-execution-env.js.map +1 -1
  78. package/dist/engine/harness/agent-harness.d.ts +1 -0
  79. package/dist/engine/harness/agent-harness.d.ts.map +1 -1
  80. package/dist/engine/harness/agent-harness.js +4 -0
  81. package/dist/engine/harness/agent-harness.js.map +1 -1
  82. package/dist/engine/harness/messages.d.ts +2 -0
  83. package/dist/engine/harness/messages.d.ts.map +1 -1
  84. package/dist/engine/harness/messages.js +52 -3
  85. package/dist/engine/harness/messages.js.map +1 -1
  86. package/dist/engine/harness/types.d.ts +7 -0
  87. package/dist/engine/harness/types.d.ts.map +1 -1
  88. package/dist/engine/harness/types.js.map +1 -1
  89. package/dist/engine/llm/types.d.ts +6 -0
  90. package/dist/engine/llm/types.d.ts.map +1 -1
  91. package/dist/engine/loop/agent-loop.js +3 -0
  92. package/dist/engine/loop/agent-loop.js.map +1 -1
  93. package/dist/engine/loop/types.d.ts +2 -0
  94. package/dist/engine/loop/types.d.ts.map +1 -1
  95. package/dist/engine/session/import-validate.d.ts.map +1 -1
  96. package/dist/engine/session/import-validate.js +64 -0
  97. package/dist/engine/session/import-validate.js.map +1 -1
  98. package/dist/index.d.ts +2 -2
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +1 -1
  101. package/dist/index.js.map +1 -1
  102. package/dist/internal/llm.d.ts +1 -1
  103. package/dist/internal/llm.d.ts.map +1 -1
  104. package/dist/scenarios/full-body.d.ts.map +1 -1
  105. package/dist/scenarios/full-body.js +3 -2
  106. package/dist/scenarios/full-body.js.map +1 -1
  107. package/dist/tools/fs/index.d.ts +8 -1
  108. package/dist/tools/fs/index.d.ts.map +1 -1
  109. package/dist/tools/fs/index.js +92 -30
  110. package/dist/tools/fs/index.js.map +1 -1
  111. package/dist/tools/fs/safety.d.ts +1 -1
  112. package/dist/tools/fs/safety.d.ts.map +1 -1
  113. package/dist/tools/fs/safety.js +3 -1
  114. package/dist/tools/fs/safety.js.map +1 -1
  115. package/dist/tools/fs/search.d.ts +7 -2
  116. package/dist/tools/fs/search.d.ts.map +1 -1
  117. package/dist/tools/fs/search.js +465 -57
  118. package/dist/tools/fs/search.js.map +1 -1
  119. package/dist/tools/monitor.d.ts.map +1 -1
  120. package/dist/tools/monitor.js +12 -1
  121. package/dist/tools/monitor.js.map +1 -1
  122. package/dist/tools/task-list.d.ts.map +1 -1
  123. package/dist/tools/task-list.js.map +1 -1
  124. package/dist/tools/worktree.d.ts.map +1 -1
  125. package/dist/tools/worktree.js +15 -2
  126. package/dist/tools/worktree.js.map +1 -1
  127. package/package.json +1 -1
@@ -258,324 +258,354 @@ function computeUsage(model, raw) {
258
258
  cost: { input: costInput, output: costOutput, cacheRead: 0, cacheWrite: 0, total: costInput + costOutput },
259
259
  };
260
260
  }
261
+ export function inferMaxTokensField(modelId) {
262
+ const id = modelId.toLowerCase();
263
+ if (/^gpt-5/.test(id) || /^chatgpt-4o/.test(id) || /^o[134](?:$|[-.])/.test(id))
264
+ return "max_completion_tokens";
265
+ return "max_tokens";
266
+ }
261
267
  export function createOpenAIBrain(config = {}) {
262
268
  const doFetch = config.fetchImpl ?? fetch;
263
- const stream = (model, context, options) => runStreamingBrain({
264
- model,
265
- doFetch,
266
- signal: options?.signal,
267
- config,
268
- httpLabel: "gateway",
269
- buildRequest: () => {
270
- const apiKey = options?.apiKey ?? config.apiKey;
271
- const root = (model.baseUrl || config.baseUrl || "").replace(/\/+$/, "");
272
- if (!root) {
273
- throw new Error("createOpenAIBrain: no baseUrl configured (set config.baseUrl or model.baseUrl)");
274
- }
275
- const body = {
276
- model: model.id,
277
- messages: toOpenAIMessages(context, config.replayThinking, model),
278
- stream: true,
279
- stream_options: { include_usage: true },
280
- };
281
- const tools = toOpenAITools(context);
282
- if (tools)
283
- body.tools = tools;
284
- if (options?.temperature !== undefined)
285
- body.temperature = options.temperature;
286
- if (options?.maxTokens !== undefined) {
287
- const mtField = (model.compat ?? {}).maxTokensField;
288
- body[mtField ?? "max_tokens"] = options.maxTokens;
289
- }
290
- if (options?.stop)
291
- body.stop = options.stop;
292
- applyThinking(body, model, options?.reasoning);
293
- const headers = {
294
- ...model.headers,
295
- ...config.headers,
296
- ...options?.headers,
297
- };
298
- headers["content-type"] = "application/json";
299
- if (apiKey)
300
- headers["authorization"] = `Bearer ${apiKey}`;
301
- return { url: `${root}/chat/completions`, headers, body: JSON.stringify(applyExtraBody(body, model.extraBody, OPENAI_RESERVED)) };
302
- },
303
- makeParser: (ctrl) => {
304
- const { out, partial } = ctrl;
305
- let accumText = "";
306
- let accumReasoning = "";
307
- let accumRefusal = "";
308
- let degenerate = false;
309
- let degenFace;
310
- let repCut;
311
- const repSpared = new Map();
312
- const pollRepetition = (text) => {
313
- const insp = inspectDegenerate(text);
314
- for (const s of insp.spared) {
315
- const k = `${s.rule} ${s.segment}`;
316
- const prev = repSpared.get(k);
317
- if (prev === undefined)
318
- repSpared.set(k, s);
319
- else if (s.reps > prev.reps)
320
- prev.reps = s.reps;
321
- }
322
- if (insp.degenerate)
323
- repCut = insp.cut;
324
- return insp.degenerate;
325
- };
326
- let walltimeCut = false;
327
- const callDeadlineMs = options?.callDeadlineMs;
328
- let lastDegenCheck = 0;
329
- let lastDegenCheckReasoning = 0;
330
- const detectRep = config.detectRepetition !== false;
331
- let emittedTextStart = false;
332
- let emittedThinkingStart = false;
333
- let thinkingClosed = false;
334
- let textIndex = 0;
335
- const toolAccum = new Map();
336
- let finishReason = null;
337
- let usageRaw;
338
- const thinkingBlock = { type: "thinking", thinking: "" };
339
- const textBlock = { type: "text", text: "" };
340
- const pushReasoningDelta = (delta) => {
341
- if (!delta)
342
- return;
343
- ctrl.sawContentToken();
344
- if (thinkingClosed)
345
- return;
346
- if (!emittedThinkingStart) {
347
- emittedThinkingStart = true;
348
- partial.content = [thinkingBlock];
349
- out.push({ type: "thinking_start", contentIndex: 0, partial: { ...partial } });
350
- }
351
- accumReasoning += delta;
352
- thinkingBlock.thinking = accumReasoning;
353
- out.push({ type: "thinking_delta", contentIndex: 0, delta, partial: { ...partial } });
354
- if (detectRep && !degenerate && accumReasoning.length - lastDegenCheckReasoning >= 64) {
355
- lastDegenCheckReasoning = accumReasoning.length;
356
- if (pollRepetition(accumReasoning)) {
357
- degenerate = true;
358
- degenFace = "reasoning";
359
- ctrl.cancel();
360
- }
269
+ const stream = (model, context, options) => {
270
+ let sentMaxTokens;
271
+ let sentMaxTokensLane = "unset";
272
+ return runStreamingBrain({
273
+ model,
274
+ doFetch,
275
+ signal: options?.signal,
276
+ config,
277
+ httpLabel: "gateway",
278
+ callDeadlineMs: options?.callDeadlineMs,
279
+ stallTimeouts: options?.stallTimeouts,
280
+ buildRequest: () => {
281
+ const apiKey = options?.apiKey ?? config.apiKey;
282
+ const root = (model.baseUrl || config.baseUrl || "").replace(/\/+$/, "");
283
+ if (!root) {
284
+ throw new Error("createOpenAIBrain: no baseUrl configured (set config.baseUrl or model.baseUrl)");
361
285
  }
362
- };
363
- const closeThinking = () => {
364
- if (emittedThinkingStart && !thinkingClosed) {
365
- thinkingClosed = true;
366
- out.push({ type: "thinking_end", contentIndex: 0, content: accumReasoning, partial: { ...partial } });
286
+ const body = {
287
+ model: model.id,
288
+ messages: toOpenAIMessages(context, config.replayThinking, model),
289
+ stream: true,
290
+ stream_options: { include_usage: true },
291
+ };
292
+ const tools = toOpenAITools(context);
293
+ if (tools)
294
+ body.tools = tools;
295
+ if (options?.temperature !== undefined)
296
+ body.temperature = options.temperature;
297
+ const requestedMaxTokens = options?.maxTokens ?? model.maxTokens;
298
+ if (requestedMaxTokens !== undefined) {
299
+ const declared = (model.compat ?? {}).maxTokensField;
300
+ const mtField = declared ?? inferMaxTokensField(model.id);
301
+ const capped = Math.max(1, requestedMaxTokens);
302
+ body[mtField] = capped;
303
+ sentMaxTokens = capped;
304
+ sentMaxTokensLane = options?.maxTokens !== undefined ? "options" : "model";
367
305
  }
368
- };
369
- const pushTextDelta = (delta) => {
370
- if (!delta)
371
- return;
372
- ctrl.sawContentToken();
373
- if (!emittedTextStart) {
374
- closeThinking();
375
- emittedTextStart = true;
376
- textIndex = emittedThinkingStart ? 1 : 0;
377
- partial.content = [textBlock];
378
- out.push({ type: "text_start", contentIndex: textIndex, partial: { ...partial } });
306
+ else {
307
+ sentMaxTokens = undefined;
308
+ sentMaxTokensLane = "unset";
379
309
  }
380
- accumText += delta;
381
- textBlock.text = accumText;
382
- out.push({ type: "text_delta", contentIndex: textIndex, delta, partial: { ...partial } });
383
- if (detectRep && !degenerate && accumText.length - lastDegenCheck >= 64) {
384
- lastDegenCheck = accumText.length;
385
- if (pollRepetition(accumText)) {
386
- degenerate = true;
387
- degenFace = "text";
388
- ctrl.cancel();
310
+ if (options?.stop)
311
+ body.stop = options.stop;
312
+ applyThinking(body, model, options?.reasoning);
313
+ const headers = {
314
+ ...model.headers,
315
+ ...config.headers,
316
+ ...options?.headers,
317
+ };
318
+ headers["content-type"] = "application/json";
319
+ if (apiKey)
320
+ headers["authorization"] = `Bearer ${apiKey}`;
321
+ return { url: `${root}/chat/completions`, headers, body: JSON.stringify(applyExtraBody(body, model.extraBody, OPENAI_RESERVED)) };
322
+ },
323
+ makeParser: (ctrl) => {
324
+ const { out, partial } = ctrl;
325
+ let accumText = "";
326
+ let accumReasoning = "";
327
+ let accumRefusal = "";
328
+ let degenerate = false;
329
+ let degenFace;
330
+ let repCut;
331
+ const repSpared = new Map();
332
+ const pollRepetition = (text) => {
333
+ const insp = inspectDegenerate(text);
334
+ for (const s of insp.spared) {
335
+ const k = `${s.rule} ${s.segment}`;
336
+ const prev = repSpared.get(k);
337
+ if (prev === undefined)
338
+ repSpared.set(k, s);
339
+ else if (s.reps > prev.reps)
340
+ prev.reps = s.reps;
389
341
  }
390
- }
391
- };
392
- return {
393
- snapshot() {
394
- const hasCompletedToolCall = [...toolAccum.values()].some((a) => a.closedTc !== undefined);
395
- const hasSubstantiveText = accumText.trim() !== "" || accumRefusal.trim() !== "";
396
- return {
397
- hasSubstantiveText,
398
- hasCompletedToolCall,
399
- hasOnlyThinking: accumReasoning.length > 0 && !hasSubstantiveText && !hasCompletedToolCall,
400
- };
401
- },
402
- sealForRetry() {
403
- closeThinking();
404
- },
405
- onLine(rawLine) {
406
- if (callDeadlineMs !== undefined && !walltimeCut && !degenerate && Date.now() >= callDeadlineMs) {
407
- walltimeCut = true;
408
- ctrl.cancel();
409
- return;
410
- }
411
- if (walltimeCut)
412
- return;
413
- const line = rawLine.trim();
414
- if (!line.startsWith("data:"))
342
+ if (insp.degenerate)
343
+ repCut = insp.cut;
344
+ return insp.degenerate;
345
+ };
346
+ let walltimeCut = false;
347
+ const callDeadlineMs = options?.callDeadlineMs;
348
+ let lastDegenCheck = 0;
349
+ let lastDegenCheckReasoning = 0;
350
+ const detectRep = config.detectRepetition !== false;
351
+ let emittedTextStart = false;
352
+ let emittedThinkingStart = false;
353
+ let thinkingClosed = false;
354
+ let textIndex = 0;
355
+ const toolAccum = new Map();
356
+ let finishReason = null;
357
+ let usageRaw;
358
+ const thinkingBlock = { type: "thinking", thinking: "" };
359
+ const textBlock = { type: "text", text: "" };
360
+ const pushReasoningDelta = (delta) => {
361
+ if (!delta)
415
362
  return;
416
- const data = line.slice(5).trim();
417
- if (!data || data === "[DONE]")
363
+ ctrl.sawContentToken();
364
+ if (thinkingClosed)
418
365
  return;
419
- let chunk;
420
- try {
421
- chunk = JSON.parse(data);
366
+ if (!emittedThinkingStart) {
367
+ emittedThinkingStart = true;
368
+ partial.content = [thinkingBlock];
369
+ out.push({ type: "thinking_start", contentIndex: 0, partial: { ...partial } });
422
370
  }
423
- catch {
424
- return;
371
+ accumReasoning += delta;
372
+ thinkingBlock.thinking = accumReasoning;
373
+ out.push({ type: "thinking_delta", contentIndex: 0, delta, partial: { ...partial } });
374
+ if (detectRep && !degenerate && accumReasoning.length - lastDegenCheckReasoning >= 64) {
375
+ lastDegenCheckReasoning = accumReasoning.length;
376
+ if (pollRepetition(accumReasoning)) {
377
+ degenerate = true;
378
+ degenFace = "reasoning";
379
+ ctrl.cancel();
380
+ }
381
+ }
382
+ };
383
+ const closeThinking = () => {
384
+ if (emittedThinkingStart && !thinkingClosed) {
385
+ thinkingClosed = true;
386
+ out.push({ type: "thinking_end", contentIndex: 0, content: accumReasoning, partial: { ...partial } });
425
387
  }
426
- if (chunk.usage)
427
- usageRaw = chunk.usage;
428
- const choice = chunk.choices?.[0];
429
- if (!choice)
388
+ };
389
+ const pushTextDelta = (delta) => {
390
+ if (!delta)
430
391
  return;
431
- if (choice.finish_reason)
432
- finishReason = choice.finish_reason;
433
- const delta = choice.delta;
434
- if (delta?.reasoning)
435
- pushReasoningDelta(delta.reasoning);
436
- else if (delta?.reasoning_content)
437
- pushReasoningDelta(delta.reasoning_content);
438
- if (delta?.refusal) {
439
- ctrl.sawContentToken();
440
- accumRefusal += delta.refusal;
392
+ ctrl.sawContentToken();
393
+ if (!emittedTextStart) {
394
+ closeThinking();
395
+ emittedTextStart = true;
396
+ textIndex = emittedThinkingStart ? 1 : 0;
397
+ partial.content = [textBlock];
398
+ out.push({ type: "text_start", contentIndex: textIndex, partial: { ...partial } });
399
+ }
400
+ accumText += delta;
401
+ textBlock.text = accumText;
402
+ out.push({ type: "text_delta", contentIndex: textIndex, delta, partial: { ...partial } });
403
+ if (detectRep && !degenerate && accumText.length - lastDegenCheck >= 64) {
404
+ lastDegenCheck = accumText.length;
405
+ if (pollRepetition(accumText)) {
406
+ degenerate = true;
407
+ degenFace = "text";
408
+ ctrl.cancel();
409
+ }
441
410
  }
442
- if (delta?.content)
443
- pushTextDelta(delta.content);
444
- if (delta?.tool_calls) {
445
- ctrl.sawContentToken();
446
- for (const tc of delta.tool_calls) {
447
- const idx = tc.index ?? (tc.id ? toolAccum.size : Math.max(0, toolAccum.size - 1));
448
- if (!toolAccum.has(idx)) {
449
- for (const [prevIdx, prevAcc] of [...toolAccum.entries()].sort((a, b) => a[0] - b[0])) {
450
- if (prevIdx >= idx || prevAcc.closedTc)
451
- continue;
452
- if (prevAcc.args === "")
453
- continue;
454
- const closed = closeToolCallAccum(prevAcc);
455
- if (closed) {
456
- out.push({ type: "toolcall_end", contentIndex: prevIdx, toolCall: closed, partial: { ...partial } });
411
+ };
412
+ return {
413
+ snapshot() {
414
+ const hasCompletedToolCall = [...toolAccum.values()].some((a) => a.closedTc !== undefined);
415
+ const hasSubstantiveText = accumText.trim() !== "" || accumRefusal.trim() !== "";
416
+ return {
417
+ hasSubstantiveText,
418
+ hasCompletedToolCall,
419
+ hasOnlyThinking: accumReasoning.length > 0 && !hasSubstantiveText && !hasCompletedToolCall,
420
+ };
421
+ },
422
+ sealForRetry() {
423
+ closeThinking();
424
+ },
425
+ onDeadline() {
426
+ if (degenerate)
427
+ return false;
428
+ walltimeCut = true;
429
+ return true;
430
+ },
431
+ onLine(rawLine) {
432
+ if (callDeadlineMs !== undefined && !walltimeCut && !degenerate && Date.now() >= callDeadlineMs) {
433
+ walltimeCut = true;
434
+ ctrl.cancel();
435
+ return;
436
+ }
437
+ if (walltimeCut)
438
+ return;
439
+ const line = rawLine.trim();
440
+ if (!line.startsWith("data:"))
441
+ return;
442
+ const data = line.slice(5).trim();
443
+ if (!data || data === "[DONE]")
444
+ return;
445
+ let chunk;
446
+ try {
447
+ chunk = JSON.parse(data);
448
+ }
449
+ catch {
450
+ return;
451
+ }
452
+ if (chunk.usage)
453
+ usageRaw = chunk.usage;
454
+ const choice = chunk.choices?.[0];
455
+ if (!choice)
456
+ return;
457
+ if (choice.finish_reason)
458
+ finishReason = choice.finish_reason;
459
+ const delta = choice.delta;
460
+ if (delta?.reasoning)
461
+ pushReasoningDelta(delta.reasoning);
462
+ else if (delta?.reasoning_content)
463
+ pushReasoningDelta(delta.reasoning_content);
464
+ if (delta?.refusal) {
465
+ ctrl.sawContentToken();
466
+ accumRefusal += delta.refusal;
467
+ }
468
+ if (delta?.content)
469
+ pushTextDelta(delta.content);
470
+ if (delta?.tool_calls) {
471
+ ctrl.sawContentToken();
472
+ for (const tc of delta.tool_calls) {
473
+ const idx = tc.index ?? (tc.id ? toolAccum.size : Math.max(0, toolAccum.size - 1));
474
+ if (!toolAccum.has(idx)) {
475
+ for (const [prevIdx, prevAcc] of [...toolAccum.entries()].sort((a, b) => a[0] - b[0])) {
476
+ if (prevIdx >= idx || prevAcc.closedTc)
477
+ continue;
478
+ if (prevAcc.args === "")
479
+ continue;
480
+ const closed = closeToolCallAccum(prevAcc);
481
+ if (closed) {
482
+ out.push({ type: "toolcall_end", contentIndex: prevIdx, toolCall: closed, partial: { ...partial } });
483
+ }
457
484
  }
458
485
  }
486
+ const acc = toolAccum.get(idx) ?? { id: "", name: "", args: "" };
487
+ if (tc.id)
488
+ acc.id = tc.id;
489
+ if (tc.function?.name)
490
+ acc.name = tc.function.name;
491
+ if (tc.function?.arguments && !acc.closedTc)
492
+ acc.args += tc.function.arguments;
493
+ toolAccum.set(idx, acc);
459
494
  }
460
- const acc = toolAccum.get(idx) ?? { id: "", name: "", args: "" };
461
- if (tc.id)
462
- acc.id = tc.id;
463
- if (tc.function?.name)
464
- acc.name = tc.function.name;
465
- if (tc.function?.arguments && !acc.closedTc)
466
- acc.args += tc.function.arguments;
467
- toolAccum.set(idx, acc);
468
495
  }
469
- }
470
- },
471
- finalize() {
472
- closeThinking();
473
- if (emittedTextStart) {
474
- out.push({ type: "text_end", contentIndex: textIndex, content: accumText, partial: { ...partial } });
475
- }
476
- const finalContent = [];
477
- if (accumReasoning)
478
- finalContent.push({ type: "thinking", thinking: accumReasoning });
479
- const textFace = degenerate && degenFace === "text" && repCut !== undefined ? trimDegenerateTail(accumText, repCut) : accumText;
480
- if (textFace)
481
- finalContent.push({ type: "text", text: textFace });
482
- const toolCalls = [];
483
- const malformed = [];
484
- for (const acc of [...toolAccum.entries()].sort((a, b) => a[0] - b[0]).map((e) => e[1])) {
485
- if (!acc.name)
486
- continue;
487
- const tc = closeToolCallAccum(acc);
488
- if (tc) {
489
- toolCalls.push(tc);
490
- finalContent.push(tc);
491
- }
492
- else {
493
- malformed.push(`${acc.name}(${acc.args.slice(0, 200)})`);
496
+ },
497
+ finalize() {
498
+ closeThinking();
499
+ if (emittedTextStart) {
500
+ out.push({ type: "text_end", contentIndex: textIndex, content: accumText, partial: { ...partial } });
494
501
  }
495
- }
496
- if (config.repairTextToolCalls && toolCalls.length === 0 && textFace && context.tools && context.tools.length > 0) {
497
- const names = new Set(context.tools.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
498
- const repaired = repairTextToolCalls(textFace, names);
499
- if (repaired.toolCalls.length > 0) {
500
- finalContent.length = 0;
501
- if (accumReasoning)
502
- finalContent.push({ type: "thinking", thinking: accumReasoning });
503
- if (repaired.cleanedText)
504
- finalContent.push({ type: "text", text: repaired.cleanedText });
505
- for (const tc of repaired.toolCalls) {
506
- finalContent.push(tc);
502
+ const finalContent = [];
503
+ if (accumReasoning)
504
+ finalContent.push({ type: "thinking", thinking: accumReasoning });
505
+ const textFace = degenerate && degenFace === "text" && repCut !== undefined ? trimDegenerateTail(accumText, repCut) : accumText;
506
+ if (textFace)
507
+ finalContent.push({ type: "text", text: textFace });
508
+ const toolCalls = [];
509
+ const malformed = [];
510
+ for (const acc of [...toolAccum.entries()].sort((a, b) => a[0] - b[0]).map((e) => e[1])) {
511
+ if (!acc.name)
512
+ continue;
513
+ const tc = closeToolCallAccum(acc);
514
+ if (tc) {
507
515
  toolCalls.push(tc);
516
+ finalContent.push(tc);
517
+ }
518
+ else {
519
+ malformed.push(`${acc.name}(${acc.args.slice(0, 200)})`);
508
520
  }
509
521
  }
510
- }
511
- const toolError = malformed.length > 0
512
- ? `tool call argument(s) not valid JSON (likely truncated, finish_reason="${finishReason ?? "?"}"): ${malformed.join("; ")}`
513
- : undefined;
514
- const noUsableContent = toolCalls.length === 0 && !accumText.trim();
515
- if (toolError !== undefined) {
516
- finalContent.push({
517
- type: "text",
518
- text: `\n[note: ${malformed.length} tool call(s) were truncated (finish_reason="${finishReason ?? "?"}") and dropped — re-issue them next turn: ${malformed.join("; ")}]`,
519
- });
520
- }
521
- const truncatedEmpty = finishReason === "length" && noUsableContent && !toolError;
522
- const dynamicCut = truncatedEmpty && options?.maxTokensDynamic === true;
523
- const safetyCut = finishReason === "content_filter";
524
- const refused = accumRefusal.trim().length > 0;
525
- const emptyNoFinish = noUsableContent && finishReason == null && !degenerate && !walltimeCut;
526
- const errorMessage = walltimeCut
527
- ? WALLTIME_CUTOFF_MESSAGE
528
- : degenerate
529
- ? DEGENERATE_MESSAGE
530
- : safetyCut
531
- ? `response cut by the provider content filter (finish_reason="content_filter") — the output was censored/truncated for policy and is unreliable`
532
- : refused
533
- ? `response refused by the model for policy reasons: ${accumRefusal.trim()}`
534
- : (toolError ??
535
- (truncatedEmpty && !dynamicCut
536
- ? accumReasoning.trim()
537
- ? `response truncated at max_tokens (finish_reason="length") with no answer text — the model spent the whole budget on reasoning (${accumReasoning.length} reasoning chars). Raise max_tokens, or disable thinking for this task.`
538
- : `response truncated at max_tokens (finish_reason="length") with no answer text — raise max_tokens`
539
- : emptyNoFinish
540
- ? `model stream ended with no content and no finish_reason — the response was lost (a torn stream or an in-band provider error frame)`
541
- : undefined));
542
- const errored = walltimeCut ||
543
- degenerate ||
544
- safetyCut ||
545
- refused ||
546
- emptyNoFinish ||
547
- (toolError !== undefined && noUsableContent) ||
548
- (truncatedEmpty && !dynamicCut);
549
- const errorKind = !walltimeCut && !degenerate && !safetyCut && !refused && toolError === undefined && truncatedEmpty && !dynamicCut
550
- ? "length_empty"
551
- : undefined;
552
- const finalMessage = {
553
- ...partial,
554
- content: finalContent.length > 0 ? finalContent : [{ type: "text", text: "" }],
555
- stopReason: errored ? "error" : mapFinishReason(finishReason, toolCalls.length > 0),
556
- usage: computeUsage(model, usageRaw),
557
- ...(usageRaw === undefined ? { usageMissing: true } : {}),
558
- timestamp: Date.now(),
559
- ...(errorMessage ? { errorMessage } : {}),
560
- ...(errorKind ? { errorKind } : {}),
561
- ...(repCut !== undefined || repSpared.size > 0
562
- ? { repetition: { ...(repCut !== undefined ? { cut: repCut } : {}), ...(repSpared.size > 0 ? { spared: [...repSpared.values()] } : {}) } }
563
- : {}),
564
- };
565
- if (errored) {
566
- out.push({ type: "error", reason: "error", error: finalMessage });
567
- }
568
- else {
569
- out.push({
570
- type: "done",
571
- reason: finalMessage.stopReason,
572
- message: finalMessage,
573
- });
574
- }
575
- },
576
- };
577
- },
578
- });
522
+ if (config.repairTextToolCalls && toolCalls.length === 0 && textFace && context.tools && context.tools.length > 0) {
523
+ const names = new Set(context.tools.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
524
+ const repaired = repairTextToolCalls(textFace, names);
525
+ if (repaired.toolCalls.length > 0) {
526
+ finalContent.length = 0;
527
+ if (accumReasoning)
528
+ finalContent.push({ type: "thinking", thinking: accumReasoning });
529
+ if (repaired.cleanedText)
530
+ finalContent.push({ type: "text", text: repaired.cleanedText });
531
+ for (const tc of repaired.toolCalls) {
532
+ finalContent.push(tc);
533
+ toolCalls.push(tc);
534
+ }
535
+ }
536
+ }
537
+ const toolError = malformed.length > 0
538
+ ? `tool call argument(s) not valid JSON (likely truncated, finish_reason="${finishReason ?? "?"}"): ${malformed.join("; ")}`
539
+ : undefined;
540
+ const noUsableContent = toolCalls.length === 0 && !accumText.trim();
541
+ if (toolError !== undefined) {
542
+ finalContent.push({
543
+ type: "text",
544
+ text: `\n[note: ${malformed.length} tool call(s) were truncated (finish_reason="${finishReason ?? "?"}") and dropped — re-issue them next turn: ${malformed.join("; ")}]`,
545
+ });
546
+ }
547
+ const truncatedEmpty = finishReason === "length" && noUsableContent && !toolError;
548
+ const dynamicCut = truncatedEmpty && options?.maxTokensDynamic === true;
549
+ const safetyCut = finishReason === "content_filter";
550
+ const refused = accumRefusal.trim().length > 0;
551
+ const emptyNoFinish = noUsableContent && finishReason == null && !degenerate && !walltimeCut;
552
+ const maxTokensNote = sentMaxTokens !== undefined
553
+ ? `effective max_tokens=${sentMaxTokens} (from ${sentMaxTokensLane === "options" ? "options.maxTokens" : "model.maxTokens"})`
554
+ : "max_tokens not set (neither options.maxTokens nor model.maxTokens — the provider's default cap applied)";
555
+ const errorMessage = walltimeCut
556
+ ? WALLTIME_CUTOFF_MESSAGE
557
+ : degenerate
558
+ ? DEGENERATE_MESSAGE
559
+ : safetyCut
560
+ ? `response cut by the provider content filter (finish_reason="content_filter") the output was censored/truncated for policy and is unreliable`
561
+ : refused
562
+ ? `response refused by the model for policy reasons: ${accumRefusal.trim()}`
563
+ : (toolError ??
564
+ (truncatedEmpty && !dynamicCut
565
+ ? accumReasoning.trim()
566
+ ? `response truncated at max_tokens (finish_reason="length", ${maxTokensNote}) with no answer text — the model spent the whole budget on reasoning (${accumReasoning.length} reasoning chars). Raise max_tokens, or disable thinking for this task.`
567
+ : `response truncated at max_tokens (finish_reason="length", ${maxTokensNote}) with no answer text — raise max_tokens`
568
+ : emptyNoFinish
569
+ ? `model stream ended with no content and no finish_reason — the response was lost (a torn stream or an in-band provider error frame)`
570
+ : undefined));
571
+ const errored = walltimeCut ||
572
+ degenerate ||
573
+ safetyCut ||
574
+ refused ||
575
+ emptyNoFinish ||
576
+ (toolError !== undefined && noUsableContent) ||
577
+ (truncatedEmpty && !dynamicCut);
578
+ const errorKind = !walltimeCut && !degenerate && !safetyCut && !refused && toolError === undefined && truncatedEmpty && !dynamicCut
579
+ ? "length_empty"
580
+ : undefined;
581
+ const finalMessage = {
582
+ ...partial,
583
+ content: finalContent.length > 0 ? finalContent : [{ type: "text", text: "" }],
584
+ stopReason: errored ? "error" : mapFinishReason(finishReason, toolCalls.length > 0),
585
+ usage: computeUsage(model, usageRaw),
586
+ ...(usageRaw === undefined ? { usageMissing: true } : {}),
587
+ timestamp: Date.now(),
588
+ ...(errorMessage ? { errorMessage } : {}),
589
+ ...(errorKind ? { errorKind } : {}),
590
+ ...(repCut !== undefined || repSpared.size > 0
591
+ ? { repetition: { ...(repCut !== undefined ? { cut: repCut } : {}), ...(repSpared.size > 0 ? { spared: [...repSpared.values()] } : {}) } }
592
+ : {}),
593
+ };
594
+ if (errored) {
595
+ out.push({ type: "error", reason: "error", error: finalMessage });
596
+ }
597
+ else {
598
+ out.push({
599
+ type: "done",
600
+ reason: finalMessage.stopReason,
601
+ message: finalMessage,
602
+ });
603
+ }
604
+ },
605
+ };
606
+ },
607
+ });
608
+ };
579
609
  return { stream };
580
610
  }
581
611
  //# sourceMappingURL=openai.js.map