@prestyj/ai 4.2.77 → 4.3.34

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.
package/dist/index.js CHANGED
@@ -58,44 +58,62 @@ var EventStream = class {
58
58
  }
59
59
  };
60
60
  var StreamResult = class {
61
- events;
62
61
  response;
62
+ buffer = [];
63
+ done = false;
64
+ error = null;
63
65
  resolveResponse;
64
66
  rejectResponse;
65
- hasConsumer = false;
66
- constructor() {
67
- this.events = new EventStream();
67
+ resolveWait = null;
68
+ constructor(generator) {
68
69
  this.response = new Promise((resolve, reject) => {
69
70
  this.resolveResponse = resolve;
70
71
  this.rejectResponse = reject;
71
72
  });
73
+ this.pump(generator);
72
74
  }
73
- push(event) {
74
- this.events.push(event);
75
- }
76
- complete(response) {
77
- this.events.close();
78
- this.resolveResponse(response);
79
- }
80
- abort(error) {
81
- this.events.abort(error);
82
- this.rejectResponse(error);
75
+ async pump(generator) {
76
+ try {
77
+ let next = await generator.next();
78
+ while (!next.done) {
79
+ this.buffer.push(next.value);
80
+ this.resolveWait?.();
81
+ this.resolveWait = null;
82
+ next = await generator.next();
83
+ }
84
+ this.done = true;
85
+ this.resolveResponse(next.value);
86
+ this.resolveWait?.();
87
+ this.resolveWait = null;
88
+ } catch (err) {
89
+ const error = err instanceof Error ? err : new Error(String(err));
90
+ this.error = error;
91
+ this.done = true;
92
+ this.rejectResponse(error);
93
+ this.resolveWait?.();
94
+ this.resolveWait = null;
95
+ }
83
96
  }
84
- [Symbol.asyncIterator]() {
85
- this.hasConsumer = true;
86
- return this.events[Symbol.asyncIterator]();
97
+ async *[Symbol.asyncIterator]() {
98
+ let index = 0;
99
+ while (true) {
100
+ while (index < this.buffer.length) {
101
+ yield this.buffer[index++];
102
+ }
103
+ if (this.error) throw this.error;
104
+ if (this.done) return;
105
+ await new Promise((r) => {
106
+ this.resolveWait = r;
107
+ if (this.buffer.length > index || this.done || this.error) {
108
+ this.resolveWait = null;
109
+ r();
110
+ }
111
+ });
112
+ }
87
113
  }
88
114
  then(onfulfilled, onrejected) {
89
- this.drainEvents().catch(() => {
90
- });
91
115
  return this.response.then(onfulfilled, onrejected);
92
116
  }
93
- async drainEvents() {
94
- if (this.hasConsumer) return;
95
- this.hasConsumer = true;
96
- for await (const _ of this.events) {
97
- }
98
- }
99
117
  };
100
118
 
101
119
  // src/providers/anthropic.ts
@@ -169,6 +187,7 @@ function toAnthropicMessages(messages, cacheControl) {
169
187
  if (part.type === "raw") return part.data;
170
188
  return null;
171
189
  }).filter(Boolean);
190
+ if (Array.isArray(content) && content.length === 0) continue;
172
191
  out.push({ role: "assistant", content });
173
192
  continue;
174
193
  }
@@ -247,7 +266,7 @@ function toAnthropicToolChoice(choice) {
247
266
  return { type: "tool", name: choice.name };
248
267
  }
249
268
  function supportsAdaptiveThinking(model) {
250
- return /opus-4-6|sonnet-4-6/.test(model);
269
+ return /opus-4-7|opus-4-6|sonnet-4-6/.test(model);
251
270
  }
252
271
  function toAnthropicThinking(level, maxTokens, model) {
253
272
  if (supportsAdaptiveThinking(model)) {
@@ -274,10 +293,10 @@ function toAnthropicThinking(level, maxTokens, model) {
274
293
  };
275
294
  }
276
295
  function remapToolCallId(id, idMap) {
277
- if (id.startsWith("call_")) return id;
296
+ if (!id.startsWith("toolu_")) return id;
278
297
  const existing = idMap.get(id);
279
298
  if (existing) return existing;
280
- const mapped = `call_${id.replace(/^toolu_/, "")}`;
299
+ const mapped = `call_${id.slice(5)}`;
281
300
  idMap.set(id, mapped);
282
301
  return mapped;
283
302
  }
@@ -332,13 +351,18 @@ function toOpenAIMessages(messages, options) {
332
351
  ) : void 0;
333
352
  const textParts = typeof msg.content !== "string" ? msg.content.filter((p) => p.type === "text").map((p) => p.text).join("") : void 0;
334
353
  const thinkingParts = typeof msg.content !== "string" ? msg.content.filter((p) => p.type === "thinking").map((p) => p.text).join("") : void 0;
354
+ const contentValue = parts || textParts || null;
355
+ const hasToolCalls = toolCalls && toolCalls.length > 0;
356
+ if (!contentValue && !hasToolCalls) continue;
335
357
  const assistantMsg = {
336
358
  role: "assistant",
337
- content: parts || textParts || null,
338
- ...toolCalls?.length ? { tool_calls: toolCalls } : {}
359
+ content: contentValue,
360
+ ...hasToolCalls ? { tool_calls: toolCalls } : {}
339
361
  };
340
- if (thinkingParts || toolCalls?.length) {
341
- assistantMsg.reasoning_content = thinkingParts || " ";
362
+ if (thinkingParts) {
363
+ assistantMsg.reasoning_content = thinkingParts;
364
+ } else if (options?.thinking && hasToolCalls && options.provider !== "glm") {
365
+ assistantMsg.reasoning_content = " ";
342
366
  }
343
367
  out.push(assistantMsg);
344
368
  continue;
@@ -404,17 +428,16 @@ function normalizeOpenAIStopReason(reason) {
404
428
  }
405
429
 
406
430
  // src/providers/anthropic.ts
407
- function streamAnthropic(options) {
408
- const result = new StreamResult();
409
- runStream(options, result).catch((err) => result.abort(toError(err)));
410
- return result;
411
- }
412
- async function runStream(options, result) {
431
+ function createClient(options) {
413
432
  const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
414
- const client = new Anthropic({
433
+ return new Anthropic({
415
434
  ...isOAuth ? { apiKey: null, authToken: options.apiKey } : { apiKey: options.apiKey },
416
435
  ...options.baseUrl ? { baseURL: options.baseUrl } : {},
417
436
  ...options.fetch ? { fetch: options.fetch } : {},
437
+ // Allow SDK retries for connection-level failures (socket hang up, 500s,
438
+ // connection refused). Our stall detection handles abort-initiated retries
439
+ // separately — SDK retries only fire on genuine transport errors.
440
+ maxRetries: 2,
418
441
  ...isOAuth ? {
419
442
  defaultHeaders: {
420
443
  "user-agent": "claude-cli/2.1.75",
@@ -422,6 +445,14 @@ async function runStream(options, result) {
422
445
  }
423
446
  } : {}
424
447
  });
448
+ }
449
+ function streamAnthropic(options) {
450
+ return new StreamResult(runStream(options));
451
+ }
452
+ async function* runStream(options) {
453
+ const client = createClient(options);
454
+ const isOAuth = options.apiKey?.startsWith("sk-ant-oat");
455
+ const useStreaming = options.streaming !== false;
425
456
  const cacheControl = toAnthropicCacheControl(options.cacheRetention, options.baseUrl);
426
457
  const { system: rawSystem, messages } = toAnthropicMessages(options.messages, cacheControl);
427
458
  const system = isOAuth ? [
@@ -467,128 +498,323 @@ async function runStream(options, result) {
467
498
  ];
468
499
  return contextEdits.length ? { context_management: { edits: contextEdits } } : {};
469
500
  })(),
470
- stream: true
501
+ stream: useStreaming
471
502
  };
503
+ const hasAdaptiveThinking = options.model.includes("opus-4-7") || options.model.includes("opus-4.7") || options.model.includes("opus-4-6") || options.model.includes("opus-4.6") || options.model.includes("sonnet-4-6") || options.model.includes("sonnet-4.6");
472
504
  const betaHeaders = [
473
505
  ...isOAuth ? ["claude-code-20250219", "oauth-2025-04-20"] : [],
474
506
  ...options.compaction ? ["compact-2026-01-12"] : [],
475
- ...options.clearToolUses ? ["context-management-2025-06-27"] : []
507
+ ...options.clearToolUses ? ["context-management-2025-06-27"] : [],
508
+ "fine-grained-tool-streaming-2025-05-14",
509
+ ...!hasAdaptiveThinking ? ["interleaved-thinking-2025-05-14"] : []
476
510
  ];
477
- const stream2 = client.messages.stream(params, {
511
+ const requestOptions = {
478
512
  signal: options.signal ?? void 0,
479
513
  ...betaHeaders.length ? { headers: { "anthropic-beta": betaHeaders.join(",") } } : {}
480
- });
514
+ };
515
+ if (!useStreaming) {
516
+ try {
517
+ const message = await client.messages.create(
518
+ { ...params, stream: false },
519
+ requestOptions
520
+ );
521
+ yield* synthesizeEventsFromMessage(message);
522
+ return messageToResponse(message);
523
+ } catch (err) {
524
+ throw toError(err);
525
+ }
526
+ }
527
+ const stream2 = client.messages.stream(params, requestOptions);
481
528
  const contentParts = [];
482
- let currentToolId = "";
483
- let currentToolName = "";
484
- stream2.on("text", (text) => {
485
- result.push({ type: "text_delta", text });
486
- });
487
- stream2.on("thinking", (thinkingDelta) => {
488
- result.push({ type: "thinking_delta", text: thinkingDelta });
489
- });
490
- stream2.on("streamEvent", (event) => {
491
- if (event.type === "content_block_start") {
492
- if (event.content_block.type === "tool_use") {
493
- currentToolId = event.content_block.id;
494
- currentToolName = event.content_block.name;
495
- }
496
- if (event.content_block.type === "server_tool_use") {
497
- currentToolId = event.content_block.id;
498
- currentToolName = event.content_block.name;
529
+ const blocks = /* @__PURE__ */ new Map();
530
+ let inputTokens = 0;
531
+ let outputTokens = 0;
532
+ let cacheRead;
533
+ let cacheWrite;
534
+ let stopReason = null;
535
+ const keepalive = { type: "keepalive" };
536
+ try {
537
+ for await (const event of stream2) {
538
+ switch (event.type) {
539
+ case "message_start": {
540
+ const usage = event.message.usage;
541
+ inputTokens = usage.input_tokens;
542
+ const usageAny = usage;
543
+ if (usageAny.cache_read_input_tokens != null) {
544
+ cacheRead = usageAny.cache_read_input_tokens;
545
+ }
546
+ if (usageAny.cache_creation_input_tokens != null) {
547
+ cacheWrite = usageAny.cache_creation_input_tokens;
548
+ }
549
+ yield keepalive;
550
+ break;
551
+ }
552
+ case "content_block_start": {
553
+ const block = event.content_block;
554
+ const idx = event.index;
555
+ const accum = {
556
+ type: block.type,
557
+ text: "",
558
+ thinking: "",
559
+ signature: "",
560
+ toolId: "",
561
+ toolName: "",
562
+ argsJson: "",
563
+ input: void 0,
564
+ raw: null
565
+ };
566
+ if (block.type === "tool_use") {
567
+ accum.toolId = block.id;
568
+ accum.toolName = block.name;
569
+ } else if (block.type === "server_tool_use") {
570
+ accum.toolId = block.id;
571
+ accum.toolName = block.name;
572
+ accum.input = block.input;
573
+ } else if (block.type === "redacted_thinking") {
574
+ accum.raw = block;
575
+ }
576
+ blocks.set(idx, accum);
577
+ yield keepalive;
578
+ break;
579
+ }
580
+ case "content_block_delta": {
581
+ const accum = blocks.get(event.index);
582
+ if (!accum) break;
583
+ const delta = event.delta;
584
+ const deltaType = delta.type;
585
+ if (deltaType === "text_delta") {
586
+ const text = delta.text;
587
+ accum.text += text;
588
+ yield { type: "text_delta", text };
589
+ } else if (deltaType === "thinking_delta") {
590
+ const text = delta.thinking;
591
+ accum.thinking += text;
592
+ yield { type: "thinking_delta", text };
593
+ } else if (deltaType === "input_json_delta") {
594
+ const partialJson = delta.partial_json;
595
+ accum.argsJson += partialJson;
596
+ yield {
597
+ type: "toolcall_delta",
598
+ id: accum.toolId,
599
+ name: accum.toolName,
600
+ argsJson: partialJson
601
+ };
602
+ } else if (deltaType === "signature_delta") {
603
+ accum.signature = delta.signature;
604
+ }
605
+ break;
606
+ }
607
+ case "content_block_stop": {
608
+ const accum = blocks.get(event.index);
609
+ if (!accum) break;
610
+ if (accum.type === "text") {
611
+ contentParts.push({ type: "text", text: accum.text });
612
+ } else if (accum.type === "thinking") {
613
+ contentParts.push({
614
+ type: "thinking",
615
+ text: accum.thinking,
616
+ signature: accum.signature
617
+ });
618
+ yield keepalive;
619
+ } else if (accum.type === "tool_use") {
620
+ let args = {};
621
+ try {
622
+ args = JSON.parse(accum.argsJson);
623
+ } catch {
624
+ }
625
+ const tc = {
626
+ type: "tool_call",
627
+ id: accum.toolId,
628
+ name: accum.toolName,
629
+ args
630
+ };
631
+ contentParts.push(tc);
632
+ yield {
633
+ type: "toolcall_done",
634
+ id: tc.id,
635
+ name: tc.name,
636
+ args: tc.args
637
+ };
638
+ } else if (accum.type === "server_tool_use") {
639
+ const stc = {
640
+ type: "server_tool_call",
641
+ id: accum.toolId,
642
+ name: accum.toolName,
643
+ input: accum.input
644
+ };
645
+ contentParts.push(stc);
646
+ yield {
647
+ type: "server_toolcall",
648
+ id: stc.id,
649
+ name: stc.name,
650
+ input: stc.input
651
+ };
652
+ } else if (accum.type === "redacted_thinking" && accum.raw) {
653
+ contentParts.push({ type: "raw", data: accum.raw });
654
+ yield keepalive;
655
+ } else {
656
+ const msg = stream2.currentMessage;
657
+ const rawBlock = msg?.content[event.index];
658
+ if (rawBlock) {
659
+ const blockType = rawBlock.type;
660
+ if (blockType === "web_search_tool_result") {
661
+ const str = {
662
+ type: "server_tool_result",
663
+ toolUseId: rawBlock.tool_use_id,
664
+ resultType: blockType,
665
+ data: rawBlock
666
+ };
667
+ contentParts.push(str);
668
+ yield {
669
+ type: "server_toolresult",
670
+ toolUseId: str.toolUseId,
671
+ resultType: str.resultType,
672
+ data: str.data
673
+ };
674
+ } else {
675
+ contentParts.push({ type: "raw", data: rawBlock });
676
+ }
677
+ }
678
+ }
679
+ blocks.delete(event.index);
680
+ break;
681
+ }
682
+ case "message_delta": {
683
+ const delta = event.delta;
684
+ if (delta.stop_reason) {
685
+ stopReason = delta.stop_reason;
686
+ }
687
+ const usage = event.usage;
688
+ if (usage?.output_tokens != null) {
689
+ outputTokens = usage.output_tokens;
690
+ }
691
+ yield keepalive;
692
+ break;
693
+ }
694
+ // message_stop — loop exits naturally
695
+ default:
696
+ yield keepalive;
697
+ break;
499
698
  }
500
699
  }
501
- });
502
- stream2.on("inputJson", (delta) => {
503
- result.push({
504
- type: "toolcall_delta",
505
- id: currentToolId,
506
- name: currentToolName,
507
- argsJson: delta
508
- });
509
- });
510
- stream2.on("contentBlock", (block) => {
511
- if (block.type === "text") {
512
- contentParts.push({ type: "text", text: block.text });
513
- } else if (block.type === "thinking") {
514
- contentParts.push({ type: "thinking", text: block.thinking, signature: block.signature });
515
- } else if (block.type === "tool_use") {
516
- const tc = {
517
- type: "tool_call",
518
- id: block.id,
519
- name: block.name,
520
- args: block.input
700
+ } catch (err) {
701
+ throw toError(err);
702
+ }
703
+ const normalizedStop = normalizeAnthropicStopReason(stopReason);
704
+ const response = {
705
+ message: {
706
+ role: "assistant",
707
+ content: contentParts.length > 0 ? contentParts : ""
708
+ },
709
+ stopReason: normalizedStop,
710
+ usage: {
711
+ inputTokens,
712
+ outputTokens,
713
+ ...cacheRead != null && { cacheRead },
714
+ ...cacheWrite != null && { cacheWrite }
715
+ }
716
+ };
717
+ yield { type: "done", stopReason: normalizedStop };
718
+ return response;
719
+ }
720
+ function* synthesizeEventsFromMessage(message) {
721
+ for (const block of message.content) {
722
+ const blk = block;
723
+ const type = blk.type;
724
+ if (type === "text") {
725
+ const text = blk.text;
726
+ if (text) yield { type: "text_delta", text };
727
+ } else if (type === "thinking") {
728
+ const text = blk.thinking;
729
+ if (text) yield { type: "thinking_delta", text };
730
+ } else if (type === "tool_use") {
731
+ const argsJson = JSON.stringify(blk.input ?? {});
732
+ yield {
733
+ type: "toolcall_delta",
734
+ id: blk.id,
735
+ name: blk.name,
736
+ argsJson
521
737
  };
522
- contentParts.push(tc);
523
- result.push({
738
+ yield {
524
739
  type: "toolcall_done",
525
- id: tc.id,
526
- name: tc.name,
527
- args: tc.args
528
- });
529
- } else if (block.type === "server_tool_use") {
530
- const stc = {
531
- type: "server_tool_call",
532
- id: block.id,
533
- name: block.name,
534
- input: block.input
740
+ id: blk.id,
741
+ name: blk.name,
742
+ args: blk.input ?? {}
535
743
  };
536
- contentParts.push(stc);
537
- result.push({
744
+ } else if (type === "server_tool_use") {
745
+ yield {
538
746
  type: "server_toolcall",
539
- id: stc.id,
540
- name: stc.name,
541
- input: stc.input
747
+ id: blk.id,
748
+ name: blk.name,
749
+ input: blk.input
750
+ };
751
+ } else if (type === "web_search_tool_result") {
752
+ yield {
753
+ type: "server_toolresult",
754
+ toolUseId: blk.tool_use_id,
755
+ resultType: type,
756
+ data: blk
757
+ };
758
+ }
759
+ }
760
+ yield { type: "done", stopReason: normalizeAnthropicStopReason(message.stop_reason) };
761
+ }
762
+ function messageToResponse(message) {
763
+ const contentParts = [];
764
+ for (const block of message.content) {
765
+ const blk = block;
766
+ const type = blk.type;
767
+ if (type === "text") {
768
+ contentParts.push({ type: "text", text: blk.text });
769
+ } else if (type === "thinking") {
770
+ contentParts.push({
771
+ type: "thinking",
772
+ text: blk.thinking,
773
+ signature: blk.signature ?? ""
774
+ });
775
+ } else if (type === "tool_use") {
776
+ contentParts.push({
777
+ type: "tool_call",
778
+ id: blk.id,
779
+ name: blk.name,
780
+ args: blk.input ?? {}
781
+ });
782
+ } else if (type === "server_tool_use") {
783
+ contentParts.push({
784
+ type: "server_tool_call",
785
+ id: blk.id,
786
+ name: blk.name,
787
+ input: blk.input
788
+ });
789
+ } else if (type === "web_search_tool_result") {
790
+ contentParts.push({
791
+ type: "server_tool_result",
792
+ toolUseId: blk.tool_use_id,
793
+ resultType: type,
794
+ data: blk
542
795
  });
543
796
  } else {
544
- const raw = block;
545
- const blockType = raw.type;
546
- if (blockType === "web_search_tool_result") {
547
- const str = {
548
- type: "server_tool_result",
549
- toolUseId: raw.tool_use_id,
550
- resultType: blockType,
551
- data: raw
552
- };
553
- contentParts.push(str);
554
- result.push({
555
- type: "server_toolresult",
556
- toolUseId: str.toolUseId,
557
- resultType: str.resultType,
558
- data: str.data
559
- });
560
- } else {
561
- contentParts.push({ type: "raw", data: raw });
562
- }
797
+ contentParts.push({ type: "raw", data: blk });
563
798
  }
564
- });
565
- try {
566
- const finalMessage = await stream2.finalMessage();
567
- const stopReason = normalizeAnthropicStopReason(finalMessage.stop_reason);
568
- const response = {
569
- message: {
570
- role: "assistant",
571
- content: contentParts.length > 0 ? contentParts : ""
572
- },
573
- stopReason,
574
- usage: {
575
- inputTokens: finalMessage.usage.input_tokens,
576
- outputTokens: finalMessage.usage.output_tokens,
577
- ...finalMessage.usage.cache_read_input_tokens != null && {
578
- cacheRead: finalMessage.usage.cache_read_input_tokens
579
- },
580
- ...finalMessage.usage.cache_creation_input_tokens != null && {
581
- cacheWrite: finalMessage.usage.cache_creation_input_tokens
582
- }
583
- }
584
- };
585
- result.push({ type: "done", stopReason });
586
- result.complete(response);
587
- } catch (err) {
588
- const error = toError(err);
589
- result.push({ type: "error", error });
590
- result.abort(error);
591
799
  }
800
+ const usage = message.usage;
801
+ const inputTokens = usage.input_tokens ?? 0;
802
+ const outputTokens = usage.output_tokens ?? 0;
803
+ const cacheRead = usage.cache_read_input_tokens;
804
+ const cacheWrite = usage.cache_creation_input_tokens;
805
+ return {
806
+ message: {
807
+ role: "assistant",
808
+ content: contentParts.length > 0 ? contentParts : ""
809
+ },
810
+ stopReason: normalizeAnthropicStopReason(message.stop_reason),
811
+ usage: {
812
+ inputTokens,
813
+ outputTokens,
814
+ ...cacheRead != null && { cacheRead },
815
+ ...cacheWrite != null && { cacheWrite }
816
+ }
817
+ };
592
818
  }
593
819
  function toError(err) {
594
820
  if (err instanceof Anthropic.APIError) {
@@ -605,49 +831,85 @@ function toError(err) {
605
831
 
606
832
  // src/providers/openai.ts
607
833
  import OpenAI from "openai";
608
- function streamOpenAI(options) {
609
- const result = new StreamResult();
610
- const providerName = options.provider ?? "openai";
611
- runStream2(options, result).catch((err) => result.abort(toError2(err, providerName)));
612
- return result;
613
- }
614
- async function runStream2(options, result) {
615
- const client = new OpenAI({
834
+ function createClient2(options) {
835
+ return new OpenAI({
616
836
  apiKey: options.apiKey,
617
837
  ...options.baseUrl ? { baseURL: options.baseUrl } : {},
618
838
  ...options.fetch ? { fetch: options.fetch } : {}
619
839
  });
620
- const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot";
621
- const messages = toOpenAIMessages(options.messages, { provider: options.provider });
840
+ }
841
+ function streamOpenAI(options) {
842
+ return new StreamResult(runStream2(options));
843
+ }
844
+ async function* runStream2(options) {
845
+ const providerName = options.provider ?? "openai";
846
+ const useStreaming = options.streaming !== false;
847
+ const client = createClient2(options);
848
+ const usesThinkingParam = options.provider === "glm" || options.provider === "moonshot" || options.provider === "xiaomi";
849
+ const messages = toOpenAIMessages(options.messages, {
850
+ provider: options.provider,
851
+ thinking: !!options.thinking
852
+ });
622
853
  const defaultTemp = options.provider === "glm" ? 0.6 : void 0;
623
854
  const effectiveTemp = options.temperature ?? defaultTemp;
624
855
  const params = {
625
856
  model: options.model,
626
857
  messages,
627
- stream: true,
628
- ...options.maxTokens ? { max_tokens: options.maxTokens } : {},
858
+ stream: useStreaming,
859
+ ...options.maxTokens ? { max_completion_tokens: options.maxTokens } : {},
629
860
  ...effectiveTemp != null && !options.thinking ? { temperature: effectiveTemp } : {},
630
861
  ...options.topP != null ? { top_p: options.topP } : {},
631
862
  ...options.stop ? { stop: options.stop } : {},
632
863
  ...options.thinking && !usesThinkingParam ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking) } : {},
633
864
  ...options.tools?.length ? { tools: toOpenAITools(options.tools) } : {},
634
865
  ...options.toolChoice && options.tools?.length ? { tool_choice: toOpenAIToolChoice(options.toolChoice) } : {},
635
- stream_options: { include_usage: true }
866
+ ...useStreaming ? { stream_options: { include_usage: true } } : {}
636
867
  };
637
- if (options.webSearch) {
638
- if (options.provider === "moonshot") {
639
- const raw = params;
640
- const tools = (raw.tools ?? []).slice();
641
- tools.push({ type: "builtin_function", function: { name: "$web_search" } });
642
- raw.tools = tools;
868
+ if (options.provider === "openai" || options.provider === "moonshot") {
869
+ const paramsAny = params;
870
+ paramsAny.prompt_cache_key = "ezcoder";
871
+ const retention = options.cacheRetention ?? "short";
872
+ if (retention === "long") {
873
+ paramsAny.prompt_cache_retention = "24h";
643
874
  }
644
875
  }
645
876
  if (usesThinkingParam) {
646
- params.thinking = options.thinking ? { type: "enabled" } : { type: "disabled" };
877
+ if (options.thinking) {
878
+ params.thinking = { type: "enabled" };
879
+ } else {
880
+ params.thinking = { type: "disabled" };
881
+ }
882
+ }
883
+ if (globalThis.process && globalThis.process.env?.GGAI_DUMP_REQUEST) {
884
+ const fs = await import("fs");
885
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
886
+ const dumpPath = `/tmp/ggai-request-${ts}.json`;
887
+ fs.writeFileSync(dumpPath, JSON.stringify(params, null, 2));
888
+ fs.appendFileSync(
889
+ "/tmp/ggai-requests.log",
890
+ `[${ts}] ${dumpPath} messages=${params.messages.length}
891
+ `
892
+ );
893
+ }
894
+ if (!useStreaming) {
895
+ try {
896
+ const completion = await client.chat.completions.create(params, {
897
+ signal: options.signal ?? void 0
898
+ });
899
+ yield* synthesizeEventsFromCompletion(completion, !!options.thinking);
900
+ return completionToResponse(completion);
901
+ } catch (err) {
902
+ throw toError2(err, providerName);
903
+ }
904
+ }
905
+ let stream2;
906
+ try {
907
+ stream2 = await client.chat.completions.create(params, {
908
+ signal: options.signal ?? void 0
909
+ });
910
+ } catch (err) {
911
+ throw toError2(err, providerName);
647
912
  }
648
- const stream2 = await client.chat.completions.create(params, {
649
- signal: options.signal ?? void 0
650
- });
651
913
  const contentParts = [];
652
914
  const toolCallAccum = /* @__PURE__ */ new Map();
653
915
  let textAccum = "";
@@ -664,6 +926,10 @@ async function runStream2(options, result) {
664
926
  if (details?.cached_tokens) {
665
927
  cacheRead = details.cached_tokens;
666
928
  }
929
+ const usageAny = chunk.usage;
930
+ if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
931
+ cacheRead = usageAny.cached_tokens;
932
+ }
667
933
  inputTokens = chunk.usage.prompt_tokens - cacheRead;
668
934
  }
669
935
  if (!choice) continue;
@@ -674,11 +940,13 @@ async function runStream2(options, result) {
674
940
  const reasoningContent = delta.reasoning_content;
675
941
  if (typeof reasoningContent === "string" && reasoningContent) {
676
942
  thinkingAccum += reasoningContent;
677
- result.push({ type: "thinking_delta", text: reasoningContent });
943
+ if (options.thinking) {
944
+ yield { type: "thinking_delta", text: reasoningContent };
945
+ }
678
946
  }
679
947
  if (delta.content) {
680
948
  textAccum += delta.content;
681
- result.push({ type: "text_delta", text: delta.content });
949
+ yield { type: "text_delta", text: delta.content };
682
950
  }
683
951
  if (delta.tool_calls) {
684
952
  for (const tc of delta.tool_calls) {
@@ -695,12 +963,12 @@ async function runStream2(options, result) {
695
963
  if (tc.function?.name) accum.name = tc.function.name;
696
964
  if (tc.function?.arguments) {
697
965
  accum.argsJson += tc.function.arguments;
698
- result.push({
966
+ yield {
699
967
  type: "toolcall_delta",
700
968
  id: accum.id,
701
969
  name: accum.name,
702
970
  argsJson: tc.function.arguments
703
- });
971
+ };
704
972
  }
705
973
  }
706
974
  }
@@ -724,12 +992,12 @@ async function runStream2(options, result) {
724
992
  args
725
993
  };
726
994
  contentParts.push(toolCall);
727
- result.push({
995
+ yield {
728
996
  type: "toolcall_done",
729
997
  id: tc.id,
730
998
  name: tc.name,
731
999
  args
732
- });
1000
+ };
733
1001
  }
734
1002
  const stopReason = normalizeOpenAIStopReason(finishReason);
735
1003
  const response = {
@@ -740,14 +1008,116 @@ async function runStream2(options, result) {
740
1008
  stopReason,
741
1009
  usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
742
1010
  };
743
- result.push({ type: "done", stopReason });
744
- result.complete(response);
1011
+ yield { type: "done", stopReason };
1012
+ return response;
1013
+ }
1014
+ function* synthesizeEventsFromCompletion(completion, thinkingEnabled) {
1015
+ const choice = completion.choices?.[0];
1016
+ if (!choice) {
1017
+ yield { type: "done", stopReason: normalizeOpenAIStopReason(null) };
1018
+ return;
1019
+ }
1020
+ const msg = choice.message;
1021
+ const reasoning = msg.reasoning_content;
1022
+ if (typeof reasoning === "string" && reasoning && thinkingEnabled) {
1023
+ yield { type: "thinking_delta", text: reasoning };
1024
+ }
1025
+ if (typeof msg.content === "string" && msg.content) {
1026
+ yield { type: "text_delta", text: msg.content };
1027
+ }
1028
+ const toolCalls = msg.tool_calls;
1029
+ if (toolCalls) {
1030
+ for (const tc of toolCalls) {
1031
+ const argsJson = tc.function?.arguments ?? "";
1032
+ if (argsJson) {
1033
+ yield {
1034
+ type: "toolcall_delta",
1035
+ id: tc.id,
1036
+ name: tc.function?.name ?? "",
1037
+ argsJson
1038
+ };
1039
+ }
1040
+ let args = {};
1041
+ try {
1042
+ args = JSON.parse(argsJson);
1043
+ } catch {
1044
+ }
1045
+ yield {
1046
+ type: "toolcall_done",
1047
+ id: tc.id,
1048
+ name: tc.function?.name ?? "",
1049
+ args
1050
+ };
1051
+ }
1052
+ }
1053
+ yield { type: "done", stopReason: normalizeOpenAIStopReason(choice.finish_reason ?? null) };
1054
+ }
1055
+ function completionToResponse(completion) {
1056
+ const choice = completion.choices?.[0];
1057
+ const contentParts = [];
1058
+ let textAccum = "";
1059
+ if (choice) {
1060
+ const msg = choice.message;
1061
+ const reasoning = msg.reasoning_content;
1062
+ if (typeof reasoning === "string" && reasoning) {
1063
+ contentParts.push({ type: "thinking", text: reasoning });
1064
+ }
1065
+ if (typeof msg.content === "string" && msg.content) {
1066
+ textAccum = msg.content;
1067
+ contentParts.push({ type: "text", text: msg.content });
1068
+ }
1069
+ const toolCalls = msg.tool_calls;
1070
+ if (toolCalls) {
1071
+ for (const tc of toolCalls) {
1072
+ let args = {};
1073
+ try {
1074
+ args = JSON.parse(tc.function?.arguments ?? "{}");
1075
+ } catch {
1076
+ }
1077
+ const toolCall = {
1078
+ type: "tool_call",
1079
+ id: tc.id,
1080
+ name: tc.function?.name ?? "",
1081
+ args
1082
+ };
1083
+ contentParts.push(toolCall);
1084
+ }
1085
+ }
1086
+ }
1087
+ let inputTokens = 0;
1088
+ let outputTokens = 0;
1089
+ let cacheRead = 0;
1090
+ if (completion.usage) {
1091
+ outputTokens = completion.usage.completion_tokens;
1092
+ const details = completion.usage.prompt_tokens_details;
1093
+ if (details?.cached_tokens) cacheRead = details.cached_tokens;
1094
+ const usageAny = completion.usage;
1095
+ if (!cacheRead && typeof usageAny.cached_tokens === "number" && usageAny.cached_tokens > 0) {
1096
+ cacheRead = usageAny.cached_tokens;
1097
+ }
1098
+ inputTokens = completion.usage.prompt_tokens - cacheRead;
1099
+ }
1100
+ const stopReason = normalizeOpenAIStopReason(choice?.finish_reason ?? null);
1101
+ return {
1102
+ message: {
1103
+ role: "assistant",
1104
+ content: contentParts.length > 0 ? contentParts : textAccum
1105
+ },
1106
+ stopReason,
1107
+ usage: { inputTokens, outputTokens, ...cacheRead > 0 && { cacheRead } }
1108
+ };
745
1109
  }
746
1110
  function toError2(err, provider = "openai") {
747
1111
  if (err instanceof OpenAI.APIError) {
748
1112
  let msg = err.message;
749
1113
  const body = err.error;
750
1114
  if (body) {
1115
+ const modelName = body.model || "";
1116
+ const _code = body.code || "";
1117
+ const message = body.message || "";
1118
+ if (modelName === "codex-mini-latest" || message.includes("codex-mini-latest")) {
1119
+ msg = `codex-mini-latest requires an OpenAI Pro or Max subscription. You currently have access to GPT-5.4 and GPT-5.4 Mini with your account.`;
1120
+ }
751
1121
  msg += ` | body: ${JSON.stringify(body)}`;
752
1122
  }
753
1123
  return new ProviderError(provider, msg, {
@@ -765,11 +1135,9 @@ function toError2(err, provider = "openai") {
765
1135
  import os from "os";
766
1136
  var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
767
1137
  function streamOpenAICodex(options) {
768
- const result = new StreamResult();
769
- runStream3(options, result).catch((err) => result.abort(toError3(err)));
770
- return result;
1138
+ return new StreamResult(runStream3(options));
771
1139
  }
772
- async function runStream3(options, result) {
1140
+ async function* runStream3(options) {
773
1141
  const baseUrl = (options.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
774
1142
  const url = `${baseUrl}/codex/responses`;
775
1143
  const { system, input } = toCodexInput(options.messages);
@@ -819,6 +1187,11 @@ async function runStream3(options, result) {
819
1187
  message += `
820
1188
 
821
1189
  Hint: Codex models require a ChatGPT Plus ($20/mo) or Pro ($200/mo) subscription. The "codex-spark" variants require ChatGPT Pro. Ensure your account has an active subscription at https://chatgpt.com/settings`;
1190
+ }
1191
+ if (response.status === 404 && text.includes("does not exist")) {
1192
+ message += `
1193
+
1194
+ Hint: codex-mini-latest requires an OpenAI Pro ($200/mo) or Max subscription. GPT-5.4 and GPT-5.4 Mini work with any active ChatGPT plan.`;
822
1195
  }
823
1196
  throw new ProviderError("openai", message, {
824
1197
  statusCode: response.status
@@ -846,11 +1219,11 @@ Hint: Codex models require a ChatGPT Plus ($20/mo) or Pro ($200/mo) subscription
846
1219
  if (type === "response.output_text.delta") {
847
1220
  const delta = event.delta;
848
1221
  textAccum += delta;
849
- result.push({ type: "text_delta", text: delta });
1222
+ yield { type: "text_delta", text: delta };
850
1223
  }
851
1224
  if (type === "response.reasoning_summary_text.delta") {
852
1225
  const delta = event.delta;
853
- result.push({ type: "thinking_delta", text: delta });
1226
+ yield { type: "thinking_delta", text: delta };
854
1227
  }
855
1228
  if (type === "response.output_item.added") {
856
1229
  const item = event.item;
@@ -868,12 +1241,12 @@ Hint: Codex models require a ChatGPT Plus ($20/mo) or Pro ($200/mo) subscription
868
1241
  for (const [key, tc] of toolCalls) {
869
1242
  if (key.endsWith(`|${itemId}`)) {
870
1243
  tc.argsJson += delta;
871
- result.push({
1244
+ yield {
872
1245
  type: "toolcall_delta",
873
1246
  id: tc.id,
874
1247
  name: tc.name,
875
1248
  argsJson: delta
876
- });
1249
+ };
877
1250
  break;
878
1251
  }
879
1252
  }
@@ -901,12 +1274,12 @@ Hint: Codex models require a ChatGPT Plus ($20/mo) or Pro ($200/mo) subscription
901
1274
  args = JSON.parse(tc.argsJson);
902
1275
  } catch {
903
1276
  }
904
- result.push({
1277
+ yield {
905
1278
  type: "toolcall_done",
906
1279
  id: tc.id,
907
1280
  name: tc.name,
908
1281
  args
909
- });
1282
+ };
910
1283
  }
911
1284
  }
912
1285
  }
@@ -946,8 +1319,8 @@ Hint: Codex models require a ChatGPT Plus ($20/mo) or Pro ($200/mo) subscription
946
1319
  stopReason,
947
1320
  usage: { inputTokens, outputTokens }
948
1321
  };
949
- result.push({ type: "done", stopReason });
950
- result.complete(streamResponse);
1322
+ yield { type: "done", stopReason };
1323
+ return streamResponse;
951
1324
  }
952
1325
  async function* parseSSE(body) {
953
1326
  const reader = body.getReader();
@@ -1061,13 +1434,6 @@ function toCodexTools(tools) {
1061
1434
  strict: null
1062
1435
  }));
1063
1436
  }
1064
- function toError3(err) {
1065
- if (err instanceof ProviderError) return err;
1066
- if (err instanceof Error) {
1067
- return new ProviderError("openai", err.message, { cause: err });
1068
- }
1069
- return new ProviderError("openai", String(err));
1070
- }
1071
1437
 
1072
1438
  // src/provider-registry.ts
1073
1439
  var ProviderRegistryImpl = class {
@@ -1107,10 +1473,16 @@ var providerRegistry = new ProviderRegistryImpl();
1107
1473
 
1108
1474
  // src/stream.ts
1109
1475
  var GLM_CODING_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
1110
- var GLM_REGULAR_BASE_URL = "https://api.z.ai/api/paas/v4";
1111
1476
  providerRegistry.register("anthropic", {
1112
1477
  stream: (options) => streamAnthropic(options)
1113
1478
  });
1479
+ providerRegistry.register("xiaomi", {
1480
+ stream: (options) => streamOpenAI({
1481
+ ...options,
1482
+ baseUrl: options.baseUrl ?? "https://token-plan-sgp.xiaomimimo.com/v1",
1483
+ webSearch: false
1484
+ })
1485
+ });
1114
1486
  providerRegistry.register("openai", {
1115
1487
  stream: (options) => {
1116
1488
  if (options.accountId) {
@@ -1120,10 +1492,10 @@ providerRegistry.register("openai", {
1120
1492
  }
1121
1493
  });
1122
1494
  providerRegistry.register("glm", {
1123
- stream: (options) => {
1124
- if (options.baseUrl) return streamOpenAI(options);
1125
- return streamGLMWithFallback(options);
1126
- }
1495
+ stream: (options) => streamOpenAI({
1496
+ ...options,
1497
+ baseUrl: options.baseUrl ?? GLM_CODING_BASE_URL
1498
+ })
1127
1499
  });
1128
1500
  providerRegistry.register("moonshot", {
1129
1501
  stream: (options) => streamOpenAI({
@@ -1131,6 +1503,24 @@ providerRegistry.register("moonshot", {
1131
1503
  baseUrl: options.baseUrl ?? "https://api.moonshot.ai/v1"
1132
1504
  })
1133
1505
  });
1506
+ providerRegistry.register("openrouter", {
1507
+ stream: (options) => streamOpenAI({
1508
+ ...options,
1509
+ baseUrl: options.baseUrl ?? "https://openrouter.ai/api/v1"
1510
+ })
1511
+ });
1512
+ providerRegistry.register("minimax", {
1513
+ stream: (options) => streamAnthropic({
1514
+ ...options,
1515
+ baseUrl: options.baseUrl ?? "https://api.minimax.io/anthropic",
1516
+ // MiniMax's Anthropic-compatible API does not support Anthropic-specific
1517
+ // server tools (web_search), context_management, or server-side tools.
1518
+ webSearch: false,
1519
+ compaction: false,
1520
+ clearToolUses: false,
1521
+ serverTools: void 0
1522
+ })
1523
+ });
1134
1524
  function stream(options) {
1135
1525
  const entry = providerRegistry.get(options.provider);
1136
1526
  if (!entry) {
@@ -1140,32 +1530,6 @@ function stream(options) {
1140
1530
  }
1141
1531
  return entry.stream(options);
1142
1532
  }
1143
- function streamGLMWithFallback(options) {
1144
- const result = new StreamResult();
1145
- runGLMWithFallback(options, result).catch((err) => {
1146
- result.abort(err instanceof Error ? err : new Error(String(err)));
1147
- });
1148
- return result;
1149
- }
1150
- async function runGLMWithFallback(options, result) {
1151
- const codingResult = streamOpenAI({ ...options, baseUrl: GLM_CODING_BASE_URL });
1152
- try {
1153
- for await (const event of codingResult) {
1154
- result.push(event);
1155
- }
1156
- result.complete(await codingResult.response);
1157
- } catch {
1158
- const regularResult = streamOpenAI({ ...options, baseUrl: GLM_REGULAR_BASE_URL });
1159
- try {
1160
- for await (const event of regularResult) {
1161
- result.push(event);
1162
- }
1163
- result.complete(await regularResult.response);
1164
- } catch (fallbackErr) {
1165
- result.abort(fallbackErr instanceof Error ? fallbackErr : new Error(String(fallbackErr)));
1166
- }
1167
- }
1168
- }
1169
1533
 
1170
1534
  // src/providers/palsu.ts
1171
1535
  function palsuText(text) {
@@ -1193,31 +1557,29 @@ function chunkText(text, size) {
1193
1557
  }
1194
1558
  return chunks.length > 0 ? chunks : [""];
1195
1559
  }
1196
- function simulateStream(message, stopReason, result, signal, cacheUsage) {
1560
+ async function* simulateStream(message, stopReason, signal, cacheUsage) {
1197
1561
  if (signal?.aborted) {
1198
- result.abort(new Error("aborted"));
1199
- return;
1562
+ throw new Error("aborted");
1200
1563
  }
1201
1564
  const content = typeof message.content === "string" ? message.content ? [{ type: "text", text: message.content }] : [] : message.content;
1202
1565
  let outputChars = 0;
1203
1566
  for (const part of content) {
1204
1567
  if (signal?.aborted) {
1205
- result.abort(new Error("aborted"));
1206
- return;
1568
+ throw new Error("aborted");
1207
1569
  }
1208
1570
  if (part.type === "text") {
1209
1571
  const chunks = chunkText(part.text, DEFAULT_CHUNK_SIZE);
1210
1572
  for (const chunk of chunks) {
1211
- result.push({ type: "text_delta", text: chunk });
1573
+ yield { type: "text_delta", text: chunk };
1212
1574
  outputChars += chunk.length;
1213
1575
  }
1214
1576
  } else if (part.type === "thinking") {
1215
- result.push({ type: "thinking_delta", text: part.text });
1577
+ yield { type: "thinking_delta", text: part.text };
1216
1578
  outputChars += part.text.length;
1217
1579
  } else if (part.type === "tool_call") {
1218
1580
  const argsJson = JSON.stringify(part.args);
1219
- result.push({ type: "toolcall_delta", id: part.id, name: part.name, argsJson });
1220
- result.push({ type: "toolcall_done", id: part.id, name: part.name, args: part.args });
1581
+ yield { type: "toolcall_delta", id: part.id, name: part.name, argsJson };
1582
+ yield { type: "toolcall_done", id: part.id, name: part.name, args: part.args };
1221
1583
  outputChars += argsJson.length;
1222
1584
  }
1223
1585
  }
@@ -1228,8 +1590,8 @@ function simulateStream(message, stopReason, result, signal, cacheUsage) {
1228
1590
  ...cacheUsage?.cacheRead ? { cacheRead: cacheUsage.cacheRead } : {},
1229
1591
  ...cacheUsage?.cacheWrite ? { cacheWrite: cacheUsage.cacheWrite } : {}
1230
1592
  };
1231
- result.push({ type: "done", stopReason });
1232
- result.complete({ message, stopReason, usage });
1593
+ yield { type: "done", stopReason };
1594
+ return { message, stopReason, usage };
1233
1595
  }
1234
1596
  function computeCacheUsage(current, previous) {
1235
1597
  if (!previous) {
@@ -1301,24 +1663,21 @@ function registerPalsuProvider(config) {
1301
1663
  state.callCount++;
1302
1664
  const ms = modelStates.get(options.model);
1303
1665
  const responseDef = (ms && ms.responses.length > 0 ? ms.responses.shift() : void 0) ?? (responses.length > 0 ? responses.shift() : void 0) ?? ms?.defaultResponse ?? defaultResponse;
1304
- const result = new StreamResult();
1305
1666
  let cacheUsage;
1306
1667
  if (enableCache) {
1307
1668
  const serialized = JSON.stringify(options.messages);
1308
1669
  cacheUsage = computeCacheUsage(serialized, lastMessagesSerialized);
1309
1670
  lastMessagesSerialized = serialized;
1310
1671
  }
1311
- const rawMessage = typeof responseDef === "function" ? responseDef(options.messages, options, state) : responseDef;
1312
- Promise.resolve(rawMessage).then(
1313
- (message) => {
1314
- const hasToolCalls = Array.isArray(message.content) && message.content.some((p) => p.type === "tool_call");
1315
- const explicitStop = message._stopReason;
1316
- const stopReason = explicitStop ?? (hasToolCalls ? "tool_use" : "end_turn");
1317
- simulateStream(message, stopReason, result, options.signal, cacheUsage);
1318
- },
1319
- (err) => result.abort(err instanceof Error ? err : new Error(String(err)))
1320
- );
1321
- return result;
1672
+ const gen = (async function* () {
1673
+ const rawMessage = typeof responseDef === "function" ? responseDef(options.messages, options, state) : responseDef;
1674
+ const message = await Promise.resolve(rawMessage);
1675
+ const hasToolCalls = Array.isArray(message.content) && message.content.some((p) => p.type === "tool_call");
1676
+ const explicitStop = message._stopReason;
1677
+ const stopReason = explicitStop ?? (hasToolCalls ? "tool_use" : "end_turn");
1678
+ return yield* simulateStream(message, stopReason, options.signal, cacheUsage);
1679
+ })();
1680
+ return new StreamResult(gen);
1322
1681
  }
1323
1682
  });
1324
1683
  return handle;