@acosmi/sdk-ts 2.19.2 → 2.19.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.
package/dist/index.mjs CHANGED
@@ -405,272 +405,15 @@ var init_errors = __esm({
405
405
  }
406
406
  });
407
407
 
408
- // src/models/betas.ts
409
- function buildBetas(caps, req) {
410
- const betas = [];
411
- if (caps.supports_isp) {
412
- betas.push(betaInterleavedThinking);
413
- betas.push(betaContextManagement);
414
- }
415
- if (caps.supports_redact_thinking && req.thinking && req.thinking.display === "summary") {
416
- betas.push(betaRedactThinking);
417
- }
418
- if (caps.supports_1m_context) {
419
- betas.push(betaContext1M);
420
- }
421
- const hasStructuredOutput = caps.supports_structured_output && req.outputConfig != null;
422
- if (hasStructuredOutput) {
423
- betas.push(betaStructuredOutputs);
424
- } else if (caps.supports_token_efficient) {
425
- betas.push(betaTokenEfficientTools);
426
- }
427
- if (caps.supports_tool_search) {
428
- betas.push(betaAdvancedToolUse);
429
- }
430
- const needsEffort = req.effort != null || req.thinking != null && req.thinking.level && req.thinking.level !== ThinkingOff;
431
- if (caps.supports_effort && needsEffort) {
432
- betas.push(betaEffort);
433
- }
434
- if (caps.supports_fast_mode && req.speed === "fast") {
435
- betas.push(betaFastMode);
436
- }
437
- if (caps.supports_prompt_cache) {
438
- betas.push(betaPromptCachingScope);
439
- }
440
- return uniqueMerge(betas, req.betas ?? []);
441
- }
442
- function uniqueMerge(base, extra) {
443
- if (extra.length === 0) return base;
444
- const seen = new Set(base);
445
- for (const s of extra) {
446
- if (!seen.has(s)) {
447
- base.push(s);
448
- seen.add(s);
449
- }
450
- }
451
- return base;
452
- }
453
- var betaInterleavedThinking, betaContext1M, betaContextManagement, betaStructuredOutputs, betaAdvancedToolUse, betaEffort, betaPromptCachingScope, betaFastMode, betaRedactThinking, betaTokenEfficientTools;
454
- var init_betas = __esm({
455
- "src/models/betas.ts"() {
456
- init_types();
457
- betaInterleavedThinking = "interleaved-thinking-2025-05-14";
458
- betaContext1M = "context-1m-2025-08-07";
459
- betaContextManagement = "context-management-2025-06-27";
460
- betaStructuredOutputs = "structured-outputs-2025-11-13";
461
- betaAdvancedToolUse = "advanced-tool-use-2025-11-20";
462
- betaEffort = "effort-2025-11-24";
463
- betaPromptCachingScope = "prompt-caching-scope-2026-01-05";
464
- betaFastMode = "fast-mode-2026-02-01";
465
- betaRedactThinking = "redact-thinking-2026-02-12";
466
- betaTokenEfficientTools = "token-efficient-tools-2025-02-19";
467
- }
468
- });
469
-
470
- // src/models/adapters/anthropic.ts
471
- function resolveThinkingLevel(body, req, caps) {
472
- const level = req.thinking?.level ?? "";
473
- if (level === ThinkingOff) {
474
- body["thinking"] = { type: "disabled" };
475
- return;
476
- }
477
- if (!caps.supports_adaptive_thinking && !caps.supports_thinking) {
478
- return;
479
- }
480
- let maxTokens = req.max_tokens ?? 0;
481
- if (maxTokens <= 0) {
482
- maxTokens = ThinkingHighMinMaxTokens;
483
- }
484
- if (level === ThinkingMax) {
485
- let modelMax = caps.max_output_tokens;
486
- if (modelMax <= 0) modelMax = ThinkingMaxFallbackMaxTokens;
487
- if (maxTokens < modelMax) maxTokens = modelMax;
488
- } else {
489
- if (maxTokens < ThinkingHighMinMaxTokens) maxTokens = ThinkingHighMinMaxTokens;
490
- }
491
- body["max_tokens"] = maxTokens;
492
- if (caps.supports_adaptive_thinking) {
493
- const thinking = { type: "adaptive" };
494
- if (req.thinking?.display) thinking["display"] = req.thinking.display;
495
- body["thinking"] = thinking;
496
- } else if (caps.supports_thinking) {
497
- let budget = maxTokens - 1;
498
- if (budget < 1024) budget = 1024;
499
- const thinking = {
500
- type: "enabled",
501
- budget_tokens: budget
502
- };
503
- if (req.thinking?.display) thinking["display"] = req.thinking.display;
504
- body["thinking"] = thinking;
505
- }
506
- if (caps.supports_effort) {
507
- let effortLevel = "high";
508
- if (level === "low") effortLevel = "low";
509
- if (level === "medium" || level === "xhigh") effortLevel = level;
510
- if (level === ThinkingMax && caps.supports_max_effort) {
511
- effortLevel = "max";
512
- }
513
- body["effort"] = { level: effortLevel };
514
- }
515
- delete body["temperature"];
516
- }
517
- var ANTHROPIC_SDK_MANAGED_BODY_KEYS, AnthropicAdapter;
518
- var init_anthropic = __esm({
519
- "src/models/adapters/anthropic.ts"() {
520
- init_types();
521
- init_errors();
522
- init_betas();
523
- init_adapters();
524
- ANTHROPIC_SDK_MANAGED_BODY_KEYS = /* @__PURE__ */ new Set([
525
- "thinking",
526
- "effort",
527
- "max_tokens",
528
- "temperature",
529
- "betas"
530
- ]);
531
- AnthropicAdapter = class {
532
- format() {
533
- return 0 /* Anthropic */;
534
- }
535
- endpointSuffix() {
536
- return "/anthropic";
537
- }
538
- /**
539
- * 构建 Anthropic 格式请求体
540
- * 逻辑等同于原 buildChatRequest, 包含完整的 betas/tools/serverTools/extraBody 处理
541
- */
542
- buildRequestBody(caps, req) {
543
- const body = {};
544
- if (req.rawMessages != null) {
545
- body["messages"] = req.rawMessages;
546
- } else if ((req.messages?.length ?? 0) > 0) {
547
- body["messages"] = req.messages;
548
- }
549
- body["stream"] = req.stream === true;
550
- if (req.max_tokens && req.max_tokens > 0) {
551
- body["max_tokens"] = req.max_tokens;
552
- }
553
- if (req.system != null) {
554
- body["system"] = req.system;
555
- }
556
- if (req.temperature != null) {
557
- body["temperature"] = req.temperature;
558
- }
559
- if (req.thinking && req.thinking.level && req.thinking.level !== "") {
560
- resolveThinkingLevel(body, req, caps);
561
- } else {
562
- if (req.thinking) {
563
- body["thinking"] = req.thinking;
564
- }
565
- if (req.effort) {
566
- body["effort"] = req.effort;
567
- }
568
- }
569
- if (req.metadata || req.endUserId && req.endUserId !== "") {
570
- const meta = {};
571
- if (req.metadata) {
572
- for (const [k, v] of Object.entries(req.metadata)) {
573
- meta[k] = v;
574
- }
575
- }
576
- if (req.endUserId && req.endUserId !== "" && meta["user_id"] === void 0) {
577
- meta["user_id"] = req.endUserId;
578
- }
579
- body["metadata"] = meta;
580
- }
581
- const allTools = [];
582
- if (req.tools != null) {
583
- try {
584
- const parsed = JSON.parse(JSON.stringify(req.tools));
585
- if (Array.isArray(parsed)) {
586
- for (const t of parsed) allTools.push(t);
587
- }
588
- } catch {
589
- }
590
- }
591
- for (const st of req.serverTools ?? []) {
592
- const schema = {
593
- type: st.type,
594
- name: st.name
595
- };
596
- if (st.config) {
597
- for (const [k, v] of Object.entries(st.config)) {
598
- schema[k] = v;
599
- }
600
- }
601
- allTools.push(schema);
602
- }
603
- if (allTools.length > 0) {
604
- body["tools"] = allTools;
605
- }
606
- if (req.speed && req.speed !== "") {
607
- body["speed"] = req.speed;
608
- }
609
- if (req.outputConfig) {
610
- body["output_config"] = req.outputConfig;
611
- }
612
- const betas = buildBetas(caps, req);
613
- if (betas.length > 0) {
614
- body["betas"] = betas;
615
- }
616
- if (req.extraBody) {
617
- for (const [k, v] of Object.entries(req.extraBody)) {
618
- if (ANTHROPIC_SDK_MANAGED_BODY_KEYS.has(k)) {
619
- console.warn(
620
- `acosmi-sdk: extraBody key "${k}" is SDK-managed and was ignored (use the dedicated request field instead).`
621
- );
622
- continue;
623
- }
624
- body[k] = v;
625
- }
626
- }
627
- return body;
628
- }
629
- /**
630
- * 解析 Anthropic 格式同步响应
631
- * 兼容 APIResponse 包装 {"code":0,"data":{...}} 和裸 Anthropic JSON 两种格式
632
- */
633
- parseResponse(bodyInput) {
634
- const bodyStr = typeof bodyInput === "string" ? bodyInput : new TextDecoder().decode(bodyInput);
635
- let raw = bodyStr;
636
- try {
637
- const wrapper = JSON.parse(bodyStr);
638
- if (wrapper.data != null && wrapper.data !== null) {
639
- if ((wrapper.code ?? 0) !== 0) {
640
- throw new BusinessError(wrapper.code ?? 0, wrapper.message ?? "");
641
- }
642
- raw = JSON.stringify(wrapper.data);
643
- }
644
- } catch (e) {
645
- if (e instanceof BusinessError) throw e;
646
- }
647
- let resp;
648
- try {
649
- resp = JSON.parse(raw);
650
- } catch (e) {
651
- throw new Error(
652
- `decode anthropic response: ${e instanceof Error ? e.message : String(e)}`
653
- );
654
- }
655
- resp.tokenRemaining = -1;
656
- resp.callRemaining = -1;
657
- resp.modelTokenRemaining = -1;
658
- resp.modelTokenRemainingETU = -1;
659
- return resp;
660
- }
661
- /**
662
- * 解析 Anthropic SSE 行
663
- *
664
- * Anthropic 原生协议无 [DONE] (message_stop 后上游关闭连接),
665
- * 但 Nexus Gateway 的 ChatStream 路径会追加 [DONE] 哨兵, 此处一并处理。
666
- */
667
- parseStreamLine(eventType, data) {
668
- if (data === "[DONE]") {
669
- return { event: { event: "", data: "" }, done: true };
670
- }
671
- return { event: { event: eventType, data }, done: false };
672
- }
673
- };
408
+ // src/models/adapters/format.ts
409
+ var ProviderFormat;
410
+ var init_format = __esm({
411
+ "src/models/adapters/format.ts"() {
412
+ ProviderFormat = /* @__PURE__ */ ((ProviderFormat2) => {
413
+ ProviderFormat2[ProviderFormat2["Anthropic"] = 0] = "Anthropic";
414
+ ProviderFormat2[ProviderFormat2["OpenAI"] = 1] = "OpenAI";
415
+ return ProviderFormat2;
416
+ })(ProviderFormat || {});
674
417
  }
675
418
  });
676
419
 
@@ -743,16 +486,19 @@ function convertOpenAIToChatResponse(oai) {
743
486
  role: "assistant",
744
487
  content: [],
745
488
  stop_reason: "",
489
+ // [W-SDK-OPENAI-PARITY] 缺 usage 的响应按 0 计数, 而不是让 `oai.usage.prompt_tokens`
490
+ // 抛 TypeError 把整条响应打死 —— 上游不计量是常见形态, 正文照样是有效的。
491
+ // 与 Go 侧同名函数逐字同语义 (那里 Usage 是值类型, 缺席即零值)。
746
492
  usage: {
747
- input_tokens: oai.usage.prompt_tokens,
748
- output_tokens: oai.usage.completion_tokens
493
+ input_tokens: oai.usage?.prompt_tokens ?? 0,
494
+ output_tokens: oai.usage?.completion_tokens ?? 0
749
495
  },
750
496
  tokenRemaining: -1,
751
497
  callRemaining: -1,
752
498
  modelTokenRemaining: -1,
753
499
  modelTokenRemainingETU: -1
754
500
  };
755
- if (oai.choices.length > 0) {
501
+ if (Array.isArray(oai.choices) && oai.choices.length > 0) {
756
502
  const choice = oai.choices[0];
757
503
  switch (choice.finish_reason) {
758
504
  case "stop":
@@ -826,12 +572,14 @@ function parseOpenAIResponseToAnthropic(raw) {
826
572
  content: [],
827
573
  model: oaiResp.model,
828
574
  stop_reason: "",
575
+ // [W-SDK-OPENAI-PARITY] 同 convertOpenAIToChatResponse: 缺 usage 按 0 计数, 不抛。
576
+ // 两处都要改 —— 只改一处等于「非流式路径修好了一半」, 而两条路径是同一族上游响应。
829
577
  usage: {
830
- input_tokens: oaiResp.usage.prompt_tokens,
831
- output_tokens: oaiResp.usage.completion_tokens
578
+ input_tokens: oaiResp.usage?.prompt_tokens ?? 0,
579
+ output_tokens: oaiResp.usage?.completion_tokens ?? 0
832
580
  }
833
581
  };
834
- if (oaiResp.choices.length > 0) {
582
+ if (Array.isArray(oaiResp.choices) && oaiResp.choices.length > 0) {
835
583
  const choice = oaiResp.choices[0];
836
584
  switch (choice.finish_reason) {
837
585
  case "stop":
@@ -869,6 +617,21 @@ function parseOpenAIResponseToAnthropic(raw) {
869
617
  }
870
618
  return resp;
871
619
  }
620
+ function readStreamUsage(chunk) {
621
+ const raw = chunk.usage;
622
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
623
+ return null;
624
+ }
625
+ const { prompt_tokens: promptTokens, completion_tokens: completionTokens } = raw;
626
+ const usage = {};
627
+ if (typeof promptTokens === "number" && Number.isFinite(promptTokens)) {
628
+ usage.input_tokens = promptTokens;
629
+ }
630
+ if (typeof completionTokens === "number" && Number.isFinite(completionTokens)) {
631
+ usage.output_tokens = completionTokens;
632
+ }
633
+ return usage;
634
+ }
872
635
  function newOpenAIStreamConverter() {
873
636
  return new OpenAIStreamConverter();
874
637
  }
@@ -877,7 +640,7 @@ var init_openai = __esm({
877
640
  "src/models/adapters/openai.ts"() {
878
641
  init_types();
879
642
  init_errors();
880
- init_adapters();
643
+ init_format();
881
644
  OpenAIAdapter = class {
882
645
  format() {
883
646
  return 1 /* OpenAI */;
@@ -1002,8 +765,18 @@ var init_openai = __esm({
1002
765
  toolArgsAccum = /* @__PURE__ */ new Map();
1003
766
  /** 上一次解析出的 tool 键,供缺 index 且缺 id 的后续增量沿用。 */
1004
767
  lastToolKey = null;
1005
- /** 已发出 message_delta/message_stop,避免 finish_reason 与 `[DONE]` 各收一次。 */
768
+ /** 已发出 message_delta/message_stop,避免 finish_reason、usage 尾帧、`[DONE]` 与
769
+ * {@link flush} 重复收口 —— 整条流恰好一个 message_stop。 */
1006
770
  messageClosed = false;
771
+ /** 已为仍打开的块发出 content_block_stop。与 {@link messageClosed} 分开记:块在
772
+ * finish_reason 帧上就关,message_delta/message_stop 却可能推迟到之后的帧。 */
773
+ blocksClosed = false;
774
+ /** [W-QUAD-CHAIN-20260914] finish_reason 已到、但 message_delta/message_stop 因等待
775
+ * usage 尾帧而推迟时记下的 stop_reason;没有推迟中的收尾时为 null。 */
776
+ pendingStopReason = null;
777
+ /** [W-QUAD-CHAIN-20260914] 最近一次带 usage 对象的帧搬出的 usage(后到覆盖先到);
778
+ * 整条流从未出现 usage 对象时为 null,收尾 message_delta 据此不写 usage 键。 */
779
+ usage = null;
1007
780
  /**
1008
781
  * 解析一条 tool_call delta 归属的块键。
1009
782
  *
@@ -1028,15 +801,18 @@ var init_openai = __esm({
1028
801
  return 0;
1029
802
  }
1030
803
  /**
1031
- * 关闭仍打开的 text / thinking / tool 块,并收口 message。
804
+ * 关闭仍打开的 text / thinking / tool 块(整条流只关一次)。
1032
805
  *
1033
806
  * 由 `finish_reason` 分支与 `[DONE]` 分支共用:上游断流或只发 `[DONE]` 而不发
1034
807
  * `finish_reason` 时,此前一个 `content_block_stop` 都不会发,下游拿到的是一个
1035
808
  * 永不闭合的 tool_use 块。
809
+ *
810
+ * [W-QUAD-CHAIN-20260914] 此前关块与 message_delta/message_stop 是同一个方法里同一时刻
811
+ * 的事;usage 尾帧要求收尾推迟,于是拆成本方法与 {@link emitMessageEnd} 两段,各自防重。
1036
812
  */
1037
- closeOpenBlocks(events, stopReason) {
1038
- if (this.messageClosed) return;
1039
- this.messageClosed = true;
813
+ closeContentBlocks(events) {
814
+ if (this.blocksClosed) return;
815
+ this.blocksClosed = true;
1040
816
  if (this.textStarted) {
1041
817
  events.push({
1042
818
  event: "content_block_stop",
@@ -1056,10 +832,23 @@ var init_openai = __esm({
1056
832
  data: JSON.stringify({ type: "content_block_stop", index: idx })
1057
833
  });
1058
834
  }
1059
- events.push({
1060
- event: "message_delta",
1061
- data: JSON.stringify({ type: "message_delta", delta: { stop_reason: stopReason } })
1062
- });
835
+ }
836
+ /**
837
+ * 发出 message_delta + message_stop,整条流只发一次;同时清掉推迟中的收尾。
838
+ *
839
+ * 见过 usage 对象就把它放进 message_delta:usage 必须出现在唯一的 message_stop 之前,
840
+ * message_stop 之后下游已无处安放用量。整条流从未出现 usage 对象时不写 usage 键 ——
841
+ * 缺席表示「上游没给」,不是 0。
842
+ */
843
+ emitMessageEnd(events, stopReason) {
844
+ if (this.messageClosed) return;
845
+ this.messageClosed = true;
846
+ this.pendingStopReason = null;
847
+ const messageDelta = { type: "message_delta", delta: { stop_reason: stopReason } };
848
+ if (this.usage !== null) {
849
+ messageDelta.usage = this.usage;
850
+ }
851
+ events.push({ event: "message_delta", data: JSON.stringify(messageDelta) });
1063
852
  events.push({
1064
853
  event: "message_stop",
1065
854
  data: JSON.stringify({ type: "message_stop" })
@@ -1072,8 +861,11 @@ var init_openai = __esm({
1072
861
  convert(data) {
1073
862
  if (data === "[DONE]") {
1074
863
  const events2 = [];
1075
- if (this.messageStarted) {
1076
- this.closeOpenBlocks(events2, "end_turn");
864
+ if (this.pendingStopReason !== null) {
865
+ this.emitMessageEnd(events2, this.pendingStopReason);
866
+ } else if (this.messageStarted) {
867
+ this.closeContentBlocks(events2);
868
+ this.emitMessageEnd(events2, "end_turn");
1077
869
  }
1078
870
  return { events: events2, done: true };
1079
871
  }
@@ -1084,10 +876,18 @@ var init_openai = __esm({
1084
876
  throw new Error(`parse openai stream chunk: ${e instanceof Error ? e.message : String(e)}`);
1085
877
  }
1086
878
  const events = [];
1087
- if (chunk.choices.length === 0) {
879
+ const frameUsage = readStreamUsage(chunk);
880
+ if (frameUsage !== null) {
881
+ this.usage = frameUsage;
882
+ }
883
+ if (!Array.isArray(chunk.choices) || chunk.choices.length === 0) {
884
+ if (frameUsage !== null && this.pendingStopReason !== null) {
885
+ this.emitMessageEnd(events, this.pendingStopReason);
886
+ }
1088
887
  return { events, done: false };
1089
888
  }
1090
889
  const choice = chunk.choices[0];
890
+ const delta = choice.delta ?? {};
1091
891
  if (!this.messageStarted) {
1092
892
  this.messageStarted = true;
1093
893
  const msgJSON = JSON.stringify({
@@ -1102,8 +902,17 @@ var init_openai = __esm({
1102
902
  });
1103
903
  events.push({ event: "message_start", data: msgJSON });
1104
904
  }
1105
- if (choice.delta.reasoning_content && choice.delta.reasoning_content !== "") {
905
+ if (delta.reasoning_content && delta.reasoning_content !== "") {
1106
906
  if (!this.thinkingStarted) {
907
+ if (this.textStarted) {
908
+ const stopJSON = JSON.stringify({
909
+ type: "content_block_stop",
910
+ index: this.blockIndex
911
+ });
912
+ events.push({ event: "content_block_stop", data: stopJSON });
913
+ this.blockIndex++;
914
+ this.textStarted = false;
915
+ }
1107
916
  this.thinkingStarted = true;
1108
917
  this.thinkingBlockIndex = this.blockIndex;
1109
918
  const blockJSON = JSON.stringify({
@@ -1116,11 +925,11 @@ var init_openai = __esm({
1116
925
  const deltaJSON = JSON.stringify({
1117
926
  type: "content_block_delta",
1118
927
  index: this.thinkingBlockIndex,
1119
- delta: { type: "thinking_delta", thinking: choice.delta.reasoning_content }
928
+ delta: { type: "thinking_delta", thinking: delta.reasoning_content }
1120
929
  });
1121
930
  events.push({ event: "content_block_delta", data: deltaJSON });
1122
931
  }
1123
- if (choice.delta.content && choice.delta.content !== "") {
932
+ if (delta.content && delta.content !== "") {
1124
933
  if (this.thinkingStarted && !this.thinkingStopped) {
1125
934
  this.thinkingStopped = true;
1126
935
  const stopJSON = JSON.stringify({
@@ -1142,11 +951,11 @@ var init_openai = __esm({
1142
951
  const deltaJSON = JSON.stringify({
1143
952
  type: "content_block_delta",
1144
953
  index: this.blockIndex,
1145
- delta: { type: "text_delta", text: choice.delta.content }
954
+ delta: { type: "text_delta", text: delta.content }
1146
955
  });
1147
956
  events.push({ event: "content_block_delta", data: deltaJSON });
1148
957
  }
1149
- for (const tc of choice.delta.tool_calls ?? []) {
958
+ for (const tc of delta.tool_calls ?? []) {
1150
959
  const fn = tc.function;
1151
960
  const toolKey = this.resolveToolKey(tc);
1152
961
  if (!this.toolBlockIndex.has(toolKey)) {
@@ -1221,67 +1030,39 @@ var init_openai = __esm({
1221
1030
  default:
1222
1031
  stopReason = choice.finish_reason;
1223
1032
  }
1224
- this.closeOpenBlocks(events, stopReason);
1033
+ if (!this.messageClosed && this.pendingStopReason === null) {
1034
+ this.closeContentBlocks(events);
1035
+ if (this.usage !== null) {
1036
+ this.emitMessageEnd(events, stopReason);
1037
+ } else {
1038
+ this.pendingStopReason = stopReason;
1039
+ }
1040
+ }
1041
+ }
1042
+ if (frameUsage !== null && this.pendingStopReason !== null) {
1043
+ this.emitMessageEnd(events, this.pendingStopReason);
1225
1044
  }
1226
1045
  return { events, done: false };
1227
1046
  }
1047
+ /**
1048
+ * 流在**没有** `[DONE]` 的情况下结束 (EOF) 时, 由驱动方在读循环结束后调用一次。
1049
+ *
1050
+ * 只补发「finish_reason 已到、仅因等待 usage 尾帧而推迟」的 message_delta + message_stop。
1051
+ * 从未收到 finish_reason 的流是被截断的流, 这里刻意**不**替它伪造正常结束 —— 一个
1052
+ * `end_turn` 的 message_stop 会把被截断的回答当成完整回答交给下游。
1053
+ * 已经收口 (usage 帧 / `[DONE]` / 上一次 flush) 后再调用返回空数组, 不会发出第二个 message_stop。
1054
+ */
1055
+ flush() {
1056
+ const events = [];
1057
+ if (this.pendingStopReason !== null) {
1058
+ this.emitMessageEnd(events, this.pendingStopReason);
1059
+ }
1060
+ return events;
1061
+ }
1228
1062
  };
1229
1063
  }
1230
1064
  });
1231
1065
 
1232
- // src/models/adapters/index.ts
1233
- function getAdapter(provider) {
1234
- const a = adapterRegistry[provider.toLowerCase()];
1235
- if (a) return a;
1236
- return defaultOpenAIAdapter;
1237
- }
1238
- function getAdapterForModel(m) {
1239
- let hasAnthropic = false;
1240
- let hasOpenAI = false;
1241
- for (const f of m.supported_formats ?? []) {
1242
- switch (f.trim().toLowerCase()) {
1243
- case "anthropic":
1244
- hasAnthropic = true;
1245
- break;
1246
- case "openai":
1247
- hasOpenAI = true;
1248
- break;
1249
- }
1250
- }
1251
- const declared = hasAnthropic || hasOpenAI;
1252
- const pref = (m.preferred_format ?? "").trim().toLowerCase();
1253
- switch (pref) {
1254
- case "anthropic":
1255
- if (!declared || hasAnthropic) return new AnthropicAdapter();
1256
- break;
1257
- case "openai":
1258
- if (!declared || hasOpenAI) return new OpenAIAdapter();
1259
- break;
1260
- }
1261
- if (hasAnthropic) return new AnthropicAdapter();
1262
- if (hasOpenAI) return new OpenAIAdapter();
1263
- return getAdapter((m.provider ?? "").toLowerCase());
1264
- }
1265
- var ProviderFormat, adapterRegistry, defaultOpenAIAdapter;
1266
- var init_adapters = __esm({
1267
- "src/models/adapters/index.ts"() {
1268
- init_anthropic();
1269
- init_openai();
1270
- init_openai();
1271
- ProviderFormat = /* @__PURE__ */ ((ProviderFormat2) => {
1272
- ProviderFormat2[ProviderFormat2["Anthropic"] = 0] = "Anthropic";
1273
- ProviderFormat2[ProviderFormat2["OpenAI"] = 1] = "OpenAI";
1274
- return ProviderFormat2;
1275
- })(ProviderFormat || {});
1276
- adapterRegistry = {
1277
- anthropic: new AnthropicAdapter(),
1278
- /** Acosmi 自有模型走 Anthropic 格式 */
1279
- acosmi: new AnthropicAdapter()
1280
- };
1281
- defaultOpenAIAdapter = new OpenAIAdapter();
1282
- }
1283
- });
1284
-
1285
1066
  // src/core/credentials.ts
1286
1067
  var uuid = () => globalThis.crypto.randomUUID();
1287
1068
  var pause = () => new Promise((resolve) => setTimeout(resolve, 25));
@@ -2672,8 +2453,308 @@ function computeBackoff(p, attempt, err) {
2672
2453
  return d;
2673
2454
  }
2674
2455
 
2675
- // src/core/client.ts
2676
- init_adapters();
2456
+ // src/models/adapters/anthropic.ts
2457
+ init_types();
2458
+ init_errors();
2459
+
2460
+ // src/models/betas.ts
2461
+ init_types();
2462
+ var betaInterleavedThinking = "interleaved-thinking-2025-05-14";
2463
+ var betaContext1M = "context-1m-2025-08-07";
2464
+ var betaContextManagement = "context-management-2025-06-27";
2465
+ var betaStructuredOutputs = "structured-outputs-2025-11-13";
2466
+ var betaAdvancedToolUse = "advanced-tool-use-2025-11-20";
2467
+ var betaEffort = "effort-2025-11-24";
2468
+ var betaPromptCachingScope = "prompt-caching-scope-2026-01-05";
2469
+ var betaFastMode = "fast-mode-2026-02-01";
2470
+ var betaRedactThinking = "redact-thinking-2026-02-12";
2471
+ var betaTokenEfficientTools = "token-efficient-tools-2025-02-19";
2472
+ function buildBetas(caps, req) {
2473
+ const betas = [];
2474
+ if (caps.supports_isp) {
2475
+ betas.push(betaInterleavedThinking);
2476
+ betas.push(betaContextManagement);
2477
+ }
2478
+ if (caps.supports_redact_thinking && req.thinking && req.thinking.display === "summary") {
2479
+ betas.push(betaRedactThinking);
2480
+ }
2481
+ if (caps.supports_1m_context) {
2482
+ betas.push(betaContext1M);
2483
+ }
2484
+ const hasStructuredOutput = caps.supports_structured_output && req.outputConfig != null;
2485
+ if (hasStructuredOutput) {
2486
+ betas.push(betaStructuredOutputs);
2487
+ } else if (caps.supports_token_efficient) {
2488
+ betas.push(betaTokenEfficientTools);
2489
+ }
2490
+ if (caps.supports_tool_search) {
2491
+ betas.push(betaAdvancedToolUse);
2492
+ }
2493
+ const needsEffort = req.effort != null || req.thinking != null && req.thinking.level && req.thinking.level !== ThinkingOff;
2494
+ if (caps.supports_effort && needsEffort) {
2495
+ betas.push(betaEffort);
2496
+ }
2497
+ if (caps.supports_fast_mode && req.speed === "fast") {
2498
+ betas.push(betaFastMode);
2499
+ }
2500
+ if (caps.supports_prompt_cache) {
2501
+ betas.push(betaPromptCachingScope);
2502
+ }
2503
+ return uniqueMerge(betas, req.betas ?? []);
2504
+ }
2505
+ function uniqueMerge(base, extra) {
2506
+ if (extra.length === 0) return base;
2507
+ const seen = new Set(base);
2508
+ for (const s of extra) {
2509
+ if (!seen.has(s)) {
2510
+ base.push(s);
2511
+ seen.add(s);
2512
+ }
2513
+ }
2514
+ return base;
2515
+ }
2516
+
2517
+ // src/models/adapters/anthropic.ts
2518
+ init_format();
2519
+ var ANTHROPIC_SDK_MANAGED_BODY_KEYS = /* @__PURE__ */ new Set([
2520
+ "thinking",
2521
+ "effort",
2522
+ "max_tokens",
2523
+ "temperature",
2524
+ "betas"
2525
+ ]);
2526
+ var AnthropicAdapter = class {
2527
+ format() {
2528
+ return 0 /* Anthropic */;
2529
+ }
2530
+ endpointSuffix() {
2531
+ return "/anthropic";
2532
+ }
2533
+ /**
2534
+ * 构建 Anthropic 格式请求体
2535
+ * 逻辑等同于原 buildChatRequest, 包含完整的 betas/tools/serverTools/extraBody 处理
2536
+ */
2537
+ buildRequestBody(caps, req) {
2538
+ const body = {};
2539
+ if (req.rawMessages != null) {
2540
+ body["messages"] = req.rawMessages;
2541
+ } else if ((req.messages?.length ?? 0) > 0) {
2542
+ body["messages"] = req.messages;
2543
+ }
2544
+ body["stream"] = req.stream === true;
2545
+ if (req.max_tokens && req.max_tokens > 0) {
2546
+ body["max_tokens"] = req.max_tokens;
2547
+ }
2548
+ if (req.system != null) {
2549
+ body["system"] = req.system;
2550
+ }
2551
+ if (req.temperature != null) {
2552
+ body["temperature"] = req.temperature;
2553
+ }
2554
+ if (req.thinking && req.thinking.level && req.thinking.level !== "") {
2555
+ resolveThinkingLevel(body, req, caps);
2556
+ } else {
2557
+ if (req.thinking) {
2558
+ body["thinking"] = req.thinking;
2559
+ }
2560
+ if (req.effort) {
2561
+ body["effort"] = req.effort;
2562
+ }
2563
+ }
2564
+ if (req.metadata || req.endUserId && req.endUserId !== "") {
2565
+ const meta = {};
2566
+ if (req.metadata) {
2567
+ for (const [k, v] of Object.entries(req.metadata)) {
2568
+ meta[k] = v;
2569
+ }
2570
+ }
2571
+ if (req.endUserId && req.endUserId !== "" && meta["user_id"] === void 0) {
2572
+ meta["user_id"] = req.endUserId;
2573
+ }
2574
+ body["metadata"] = meta;
2575
+ }
2576
+ const allTools = [];
2577
+ if (req.tools != null) {
2578
+ try {
2579
+ const parsed = JSON.parse(JSON.stringify(req.tools));
2580
+ if (Array.isArray(parsed)) {
2581
+ for (const t of parsed) allTools.push(t);
2582
+ }
2583
+ } catch {
2584
+ }
2585
+ }
2586
+ for (const st of req.serverTools ?? []) {
2587
+ const schema = {
2588
+ type: st.type,
2589
+ name: st.name
2590
+ };
2591
+ if (st.config) {
2592
+ for (const [k, v] of Object.entries(st.config)) {
2593
+ schema[k] = v;
2594
+ }
2595
+ }
2596
+ allTools.push(schema);
2597
+ }
2598
+ if (allTools.length > 0) {
2599
+ body["tools"] = allTools;
2600
+ }
2601
+ if (req.speed && req.speed !== "") {
2602
+ body["speed"] = req.speed;
2603
+ }
2604
+ if (req.outputConfig) {
2605
+ body["output_config"] = req.outputConfig;
2606
+ }
2607
+ const betas = buildBetas(caps, req);
2608
+ if (betas.length > 0) {
2609
+ body["betas"] = betas;
2610
+ }
2611
+ if (req.extraBody) {
2612
+ for (const [k, v] of Object.entries(req.extraBody)) {
2613
+ if (ANTHROPIC_SDK_MANAGED_BODY_KEYS.has(k)) {
2614
+ console.warn(
2615
+ `acosmi-sdk: extraBody key "${k}" is SDK-managed and was ignored (use the dedicated request field instead).`
2616
+ );
2617
+ continue;
2618
+ }
2619
+ body[k] = v;
2620
+ }
2621
+ }
2622
+ return body;
2623
+ }
2624
+ /**
2625
+ * 解析 Anthropic 格式同步响应
2626
+ * 兼容 APIResponse 包装 {"code":0,"data":{...}} 和裸 Anthropic JSON 两种格式
2627
+ */
2628
+ parseResponse(bodyInput) {
2629
+ const bodyStr = typeof bodyInput === "string" ? bodyInput : new TextDecoder().decode(bodyInput);
2630
+ let raw = bodyStr;
2631
+ try {
2632
+ const wrapper = JSON.parse(bodyStr);
2633
+ if (wrapper.data != null && wrapper.data !== null) {
2634
+ if ((wrapper.code ?? 0) !== 0) {
2635
+ throw new BusinessError(wrapper.code ?? 0, wrapper.message ?? "");
2636
+ }
2637
+ raw = JSON.stringify(wrapper.data);
2638
+ }
2639
+ } catch (e) {
2640
+ if (e instanceof BusinessError) throw e;
2641
+ }
2642
+ let resp;
2643
+ try {
2644
+ resp = JSON.parse(raw);
2645
+ } catch (e) {
2646
+ throw new Error(
2647
+ `decode anthropic response: ${e instanceof Error ? e.message : String(e)}`
2648
+ );
2649
+ }
2650
+ resp.tokenRemaining = -1;
2651
+ resp.callRemaining = -1;
2652
+ resp.modelTokenRemaining = -1;
2653
+ resp.modelTokenRemainingETU = -1;
2654
+ return resp;
2655
+ }
2656
+ /**
2657
+ * 解析 Anthropic SSE 行
2658
+ *
2659
+ * Anthropic 原生协议无 [DONE] (message_stop 后上游关闭连接),
2660
+ * 但 Nexus Gateway 的 ChatStream 路径会追加 [DONE] 哨兵, 此处一并处理。
2661
+ */
2662
+ parseStreamLine(eventType, data) {
2663
+ if (data === "[DONE]") {
2664
+ return { event: { event: "", data: "" }, done: true };
2665
+ }
2666
+ return { event: { event: eventType, data }, done: false };
2667
+ }
2668
+ };
2669
+ function resolveThinkingLevel(body, req, caps) {
2670
+ const level = req.thinking?.level ?? "";
2671
+ if (level === ThinkingOff) {
2672
+ body["thinking"] = { type: "disabled" };
2673
+ return;
2674
+ }
2675
+ if (!caps.supports_adaptive_thinking && !caps.supports_thinking) {
2676
+ return;
2677
+ }
2678
+ let maxTokens = req.max_tokens ?? 0;
2679
+ if (maxTokens <= 0) {
2680
+ maxTokens = ThinkingHighMinMaxTokens;
2681
+ }
2682
+ if (level === ThinkingMax) {
2683
+ let modelMax = caps.max_output_tokens;
2684
+ if (modelMax <= 0) modelMax = ThinkingMaxFallbackMaxTokens;
2685
+ if (maxTokens < modelMax) maxTokens = modelMax;
2686
+ } else {
2687
+ if (maxTokens < ThinkingHighMinMaxTokens) maxTokens = ThinkingHighMinMaxTokens;
2688
+ }
2689
+ body["max_tokens"] = maxTokens;
2690
+ if (caps.supports_adaptive_thinking) {
2691
+ const thinking = { type: "adaptive" };
2692
+ if (req.thinking?.display) thinking["display"] = req.thinking.display;
2693
+ body["thinking"] = thinking;
2694
+ } else if (caps.supports_thinking) {
2695
+ let budget = maxTokens - 1;
2696
+ if (budget < 1024) budget = 1024;
2697
+ const thinking = {
2698
+ type: "enabled",
2699
+ budget_tokens: budget
2700
+ };
2701
+ if (req.thinking?.display) thinking["display"] = req.thinking.display;
2702
+ body["thinking"] = thinking;
2703
+ }
2704
+ if (caps.supports_effort) {
2705
+ let effortLevel = "high";
2706
+ if (level === "low") effortLevel = "low";
2707
+ if (level === "medium" || level === "xhigh") effortLevel = level;
2708
+ if (level === ThinkingMax && caps.supports_max_effort) {
2709
+ effortLevel = "max";
2710
+ }
2711
+ body["effort"] = { level: effortLevel };
2712
+ }
2713
+ delete body["temperature"];
2714
+ }
2715
+
2716
+ // src/models/adapters/index.ts
2717
+ init_openai();
2718
+ init_format();
2719
+ init_openai();
2720
+ var adapterRegistry = {
2721
+ anthropic: new AnthropicAdapter(),
2722
+ /** Acosmi 自有模型走 Anthropic 格式 */
2723
+ acosmi: new AnthropicAdapter()
2724
+ };
2725
+ var defaultOpenAIAdapter = new OpenAIAdapter();
2726
+ function getAdapter(provider) {
2727
+ const a = adapterRegistry[provider.toLowerCase()];
2728
+ if (a) return a;
2729
+ return defaultOpenAIAdapter;
2730
+ }
2731
+ function getAdapterForModel(m) {
2732
+ let hasAnthropic = false;
2733
+ let hasOpenAI = false;
2734
+ for (const f of m.supported_formats ?? []) {
2735
+ switch (f.trim().toLowerCase()) {
2736
+ case "anthropic":
2737
+ hasAnthropic = true;
2738
+ break;
2739
+ case "openai":
2740
+ hasOpenAI = true;
2741
+ break;
2742
+ }
2743
+ }
2744
+ const declared = hasAnthropic || hasOpenAI;
2745
+ const pref = (m.preferred_format ?? "").trim().toLowerCase();
2746
+ switch (pref) {
2747
+ case "anthropic":
2748
+ if (!declared || hasAnthropic) return new AnthropicAdapter();
2749
+ break;
2750
+ case "openai":
2751
+ if (!declared || hasOpenAI) return new OpenAIAdapter();
2752
+ break;
2753
+ }
2754
+ if (hasAnthropic) return new AnthropicAdapter();
2755
+ if (hasOpenAI) return new OpenAIAdapter();
2756
+ return getAdapter((m.provider ?? "").toLowerCase());
2757
+ }
2677
2758
 
2678
2759
  // src/models/stream-meta.ts
2679
2760
  function extractAnthropicBlockMeta(eventType, data, blockTypeMap) {
@@ -4355,18 +4436,23 @@ var Client = class _Client {
4355
4436
  }
4356
4437
  if (adapter.format() === 1 /* OpenAI */) {
4357
4438
  const converter = newOpenAIStreamConverter();
4439
+ let currentEvent = "";
4358
4440
  for await (const line of iterSSELines(resp.body)) {
4359
4441
  notifyUpstreamActivity(onUpstreamActivity);
4360
4442
  if (isSSECommentLine(line)) continue;
4361
4443
  if (line.startsWith("event:")) {
4362
- line.slice("event:".length).trim();
4444
+ currentEvent = line.slice("event:".length).trim();
4363
4445
  } else if (line.startsWith("data:")) {
4364
4446
  const data = line.slice("data:".length).trim();
4447
+ if (currentEvent === "failed" || currentEvent === "error") {
4448
+ throw parseStreamError(data);
4449
+ }
4365
4450
  const { events, done } = converter.convert(data);
4366
4451
  for (const ev of events) yield ev;
4367
4452
  if (done) return;
4368
4453
  }
4369
4454
  }
4455
+ for (const ev of converter.flush()) yield ev;
4370
4456
  } else {
4371
4457
  const blockTypeMap = /* @__PURE__ */ new Map();
4372
4458
  let currentEvent = "";
@@ -5284,10 +5370,6 @@ function findDesktopVisualUnderstandingModel(models) {
5284
5370
  return candidates[0];
5285
5371
  }
5286
5372
 
5287
- // src/models/index.ts
5288
- init_adapters();
5289
- init_betas();
5290
-
5291
5373
  // src/billing/entitlements.ts
5292
5374
  Client.prototype.getBalance = async function(signal) {
5293
5375
  const resp = await this.doJSON(