@henjicc/ai-sdk 0.6.1 → 0.7.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 (128) hide show
  1. package/CHANGELOG.md +41 -27
  2. package/README.md +59 -46
  3. package/dist/capabilities/client.js +103 -86
  4. package/dist/capabilities/speech-recognition/bailian/realtime/module.js +42 -7
  5. package/dist/capabilities/speech-recognition/bailian/realtime/protocol.js +57 -15
  6. package/dist/capabilities/speech-recognition/volcengine/realtime/module.js +42 -10
  7. package/dist/capabilities/translation/bailian/module.js +41 -13
  8. package/dist/capabilities/translation/bailian/sse.d.ts +1 -1
  9. package/dist/capabilities/translation/bailian/sse.js +82 -54
  10. package/dist/capabilities/types.d.ts +4 -0
  11. package/dist/catalog/audio/tts-common.d.ts +5 -0
  12. package/dist/catalog/audio/tts-common.js +17 -0
  13. package/dist/catalog/bailian/cosyvoice-v3.5.model.d.ts +2 -0
  14. package/dist/catalog/bailian/cosyvoice-v3.5.model.js +40 -0
  15. package/dist/catalog/bailian/minimax-speech-2.8.model.d.ts +2 -0
  16. package/dist/catalog/bailian/minimax-speech-2.8.model.js +38 -0
  17. package/dist/catalog/bailian/qwen-audio-3.1-tts-flash.model.d.ts +2 -0
  18. package/dist/catalog/bailian/qwen-audio-3.1-tts-flash.model.js +35 -0
  19. package/dist/catalog/fal/eleven-v3-tts.model.d.ts +2 -0
  20. package/dist/catalog/fal/eleven-v3-tts.model.js +31 -0
  21. package/dist/catalog/fal/minimax-speech-2.8.model.d.ts +2 -0
  22. package/dist/catalog/fal/minimax-speech-2.8.model.js +39 -0
  23. package/dist/catalog/fal/qwen3-tts-1.7b.model.d.ts +2 -0
  24. package/dist/catalog/fal/qwen3-tts-1.7b.model.js +33 -0
  25. package/dist/catalog/index.js +116 -90
  26. package/dist/catalog/kie/eleven-turbo-2.5-tts.model.d.ts +2 -0
  27. package/dist/catalog/kie/eleven-turbo-2.5-tts.model.js +29 -0
  28. package/dist/catalog/kie/eleven-v3-dialogue.model.d.ts +2 -0
  29. package/dist/catalog/kie/eleven-v3-dialogue.model.js +46 -0
  30. package/dist/catalog/kie/gemini-3.1-flash-tts.model.d.ts +2 -0
  31. package/dist/catalog/kie/gemini-3.1-flash-tts.model.js +40 -0
  32. package/dist/catalog/ppio/minimax-speech.model.js +1 -3
  33. package/dist/catalog/siliconflow/cosyvoice2-tts.model.d.ts +2 -0
  34. package/dist/catalog/siliconflow/cosyvoice2-tts.model.js +27 -0
  35. package/dist/catalog/siliconflow/moss-ttsd-0.5.model.d.ts +2 -0
  36. package/dist/catalog/siliconflow/moss-ttsd-0.5.model.js +28 -0
  37. package/dist/catalog/volcengine-speech/seed-icl-2.0.model.d.ts +2 -0
  38. package/dist/catalog/volcengine-speech/seed-icl-2.0.model.js +26 -0
  39. package/dist/catalog/volcengine-speech/seed-tts-2.0.model.d.ts +2 -0
  40. package/dist/catalog/volcengine-speech/seed-tts-2.0.model.js +25 -0
  41. package/dist/llm/chat.js +6 -1
  42. package/dist/llm/streaming.js +131 -91
  43. package/dist/packs/models/bailian/cosyvoice-v3.5.d.ts +6 -0
  44. package/dist/packs/models/bailian/cosyvoice-v3.5.js +7 -0
  45. package/dist/packs/models/bailian/minimax-speech-2.8.d.ts +6 -0
  46. package/dist/packs/models/bailian/minimax-speech-2.8.js +7 -0
  47. package/dist/packs/models/bailian/qwen-audio-3.1-tts-flash.d.ts +6 -0
  48. package/dist/packs/models/bailian/qwen-audio-3.1-tts-flash.js +7 -0
  49. package/dist/packs/models/fal/eleven-v3-tts.d.ts +6 -0
  50. package/dist/packs/models/fal/eleven-v3-tts.js +7 -0
  51. package/dist/packs/models/fal/minimax-speech-2.8.d.ts +6 -0
  52. package/dist/packs/models/fal/minimax-speech-2.8.js +7 -0
  53. package/dist/packs/models/fal/qwen3-tts-1.7b.d.ts +6 -0
  54. package/dist/packs/models/fal/qwen3-tts-1.7b.js +7 -0
  55. package/dist/packs/models/kie/eleven-turbo-2.5-tts.d.ts +6 -0
  56. package/dist/packs/models/kie/eleven-turbo-2.5-tts.js +7 -0
  57. package/dist/packs/models/kie/eleven-v3-dialogue.d.ts +6 -0
  58. package/dist/packs/models/kie/eleven-v3-dialogue.js +7 -0
  59. package/dist/packs/models/kie/gemini-3.1-flash-tts.d.ts +6 -0
  60. package/dist/packs/models/kie/gemini-3.1-flash-tts.js +7 -0
  61. package/dist/packs/models/siliconflow/cosyvoice2-tts.d.ts +6 -0
  62. package/dist/packs/models/siliconflow/cosyvoice2-tts.js +7 -0
  63. package/dist/packs/models/siliconflow/moss-ttsd-0.5.d.ts +6 -0
  64. package/dist/packs/models/siliconflow/moss-ttsd-0.5.js +7 -0
  65. package/dist/packs/models/volcengine-speech/seed-icl-2.0.d.ts +6 -0
  66. package/dist/packs/models/volcengine-speech/seed-icl-2.0.js +7 -0
  67. package/dist/packs/models/volcengine-speech/seed-tts-2.0.d.ts +6 -0
  68. package/dist/packs/models/volcengine-speech/seed-tts-2.0.js +7 -0
  69. package/dist/packs/provider-adapters/siliconflow.d.ts +3 -0
  70. package/dist/packs/provider-adapters/siliconflow.js +5 -0
  71. package/dist/packs/provider-adapters/volcengine-speech.d.ts +3 -0
  72. package/dist/packs/provider-adapters/volcengine-speech.js +5 -0
  73. package/dist/packs/provider-packs/bailian.d.ts +1 -1
  74. package/dist/packs/provider-packs/bailian.js +6 -3
  75. package/dist/packs/provider-packs/fal.d.ts +1 -1
  76. package/dist/packs/provider-packs/fal.js +41 -38
  77. package/dist/packs/provider-packs/kie.d.ts +1 -1
  78. package/dist/packs/provider-packs/kie.js +33 -30
  79. package/dist/packs/provider-packs/siliconflow.d.ts +4 -0
  80. package/dist/packs/provider-packs/siliconflow.js +7 -0
  81. package/dist/packs/provider-packs/volcengine-speech.d.ts +4 -0
  82. package/dist/packs/provider-packs/volcengine-speech.js +7 -0
  83. package/dist/protocols/sse-events.d.ts +4 -0
  84. package/dist/protocols/sse-events.js +31 -0
  85. package/dist/providers/audio-data.d.ts +2 -0
  86. package/dist/providers/audio-data.js +21 -0
  87. package/dist/providers/bailian.js +30 -2
  88. package/dist/providers/index.js +4 -0
  89. package/dist/providers/metadata.js +8 -0
  90. package/dist/providers/siliconflow.d.ts +3 -0
  91. package/dist/providers/siliconflow.js +34 -0
  92. package/dist/providers/volcengine-speech.d.ts +3 -0
  93. package/dist/providers/volcengine-speech.js +143 -0
  94. package/dist/types/model.d.ts +4 -4
  95. package/dist/types/model.js +3 -1
  96. package/docs/consumers.md +22 -5
  97. package/docs/llm-adaptation/README.md +28 -3
  98. package/docs/llm-adaptation//344/276/233/345/272/224/345/225/206/DeepSeek.md +14 -14
  99. package/docs/llm-adaptation//344/276/233/345/272/224/345/225/206/Kimi.md +3 -3
  100. package/docs/llm-adaptation//344/276/233/345/272/224/345/225/206//345/260/217/347/261/263MiMo.md +42 -42
  101. package/docs/llm-adaptation//344/276/233/345/272/224/345/225/206//346/231/272/350/260/261GLM.md +2 -0
  102. package/docs/model-adaptation/Fun-ASR-Realtime/Fun-ASR-Realtime_/347/231/276/347/202/274.md +22 -2
  103. package/docs/model-adaptation/GLM-5.3-Flash/GLM-5.3-Flash_/346/231/272/350/260/261.md +6 -6
  104. package/docs/model-adaptation/GPT-Image-2.5/GPT-Image-2.5_APIMart.md +66 -66
  105. package/docs/model-adaptation/GPT-Image-2.5/GPT-Image-2.5_Fal.md +55 -55
  106. package/docs/model-adaptation/GPT-Image-2.5/GPT-Image-2.5_Grsai.md +53 -53
  107. package/docs/model-adaptation/GPT-Image-2.5/GPT-Image-2.5_KIE.md +55 -55
  108. package/docs/model-adaptation/HappyHorse-1.1/HappyHorse-1.1_KIE.md +27 -27
  109. package/docs/model-adaptation/IC-Light-v2/IC-Light-v2_Fal.md +14 -14
  110. package/docs/model-adaptation/Qwen-MT-Flash/Qwen-MT-Flash_/347/231/276/347/202/274.md +22 -1
  111. package/docs/model-adaptation/Qwen-MT-Lite/Qwen-MT-Lite_/347/231/276/347/202/274.md +2 -1
  112. package/docs/model-adaptation/Qwen-MT-Plus/Qwen-MT-Plus_/347/231/276/347/202/274.md +2 -1
  113. package/docs/model-adaptation/Qwen3-ASR-Flash-Realtime/Qwen3-ASR-Flash-Realtime_/347/231/276/347/202/274.md +25 -2
  114. package/docs/model-adaptation/README.md +14 -10
  115. package/docs/model-adaptation/SeedASR-2.0-Realtime/SeedASR-2.0-Realtime_/347/201/253/345/261/261/345/274/225/346/223/216.md +6 -2
  116. package/docs/model-adaptation/Seedream-5.0-Flash/Seedream-5.0-Flash_/347/201/253/345/261/261/345/274/225/346/223/216.md +18 -18
  117. package/docs/model-adaptation/TTS-/345/244/232/344/276/233/345/272/224/345/225/206-2026-09.md +55 -0
  118. package/docs/model-adaptation//344/276/233/345/272/224/345/225/206//346/264/276/346/254/247/344/272/221.md +9 -9
  119. package/docs/model-adaptation//344/276/233/345/272/224/345/225/206//347/201/253/345/261/261/345/274/225/346/223/216.md +11 -11
  120. package/docs/model-adaptation//344/276/233/345/272/224/345/225/206//347/231/276/347/202/274.md +31 -31
  121. package/docs/model-adaptation//344/276/233/345/272/224/345/225/206//347/241/205/345/237/272/346/265/201/345/212/250.md +67 -67
  122. package/docs/model-adaptation//346/226/207/346/241/243/351/207/207/351/233/206/346/211/213/345/206/214.md +8 -8
  123. package/examples/form-renderer/package.json +1 -1
  124. package/examples/llm-chat/README.md +8 -8
  125. package/examples/llm-chat/index.ts +2 -2
  126. package/examples/llm-chat/package.json +1 -1
  127. package/examples/minimal-node/package.json +1 -1
  128. package/package.json +258 -258
@@ -0,0 +1,40 @@
1
+ import { defineModel } from '../defineModel.js';
2
+ import { speechNumber, speechString, speechText } from '../audio/tts-common.js';
3
+ export const kieGemini31FlashTtsModel = defineModel({
4
+ meta: {
5
+ id: 'kie-gemini-3.1-flash-tts', canonicalModelId: 'gemini-3.1-flash-tts', provider: 'kie', type: 'audio',
6
+ tags: ['text-to-audio', 'provider-kie'],
7
+ polling: { interval: 3000, maxAttempts: 120, expectedAttempts: 20 },
8
+ },
9
+ inputLimits: { images: { max: 0 }, videos: { max: 0 } },
10
+ params: [
11
+ { id: 'kieGeminiTtsVoice', type: 'text', order: 1, default: 'Fenrir' },
12
+ { id: 'kieGeminiTtsScene', type: 'text', order: 2, default: '' },
13
+ { id: 'kieGeminiTtsTemperature', type: 'number', order: 3, default: 1, min: 0, max: 2, step: 0.1 },
14
+ ],
15
+ endpoints: '/api/v1/jobs/createTask',
16
+ request: {
17
+ builder: (params) => {
18
+ const speakers = Array.isArray(params.kieGeminiTtsSpeakers) && params.kieGeminiTtsSpeakers.length > 0
19
+ ? params.kieGeminiTtsSpeakers
20
+ : [{ speaker_id: 'Speaker 1', voice_name: speechString(params.kieGeminiTtsVoice) ?? 'Fenrir' }];
21
+ const dialogueTurns = Array.isArray(params.kieGeminiTtsDialogueTurns) && params.kieGeminiTtsDialogueTurns.length > 0
22
+ ? params.kieGeminiTtsDialogueTurns
23
+ : [{ speaker_id: 'Speaker 1', text: speechText(params) }];
24
+ const input = {
25
+ speakers,
26
+ dialogue_turns: dialogueTurns,
27
+ temperature: speechNumber(params.kieGeminiTtsTemperature) ?? 1,
28
+ };
29
+ const scene = speechString(params.kieGeminiTtsScene);
30
+ if (scene)
31
+ input.scene = scene;
32
+ return { model: 'google/gemini-3-1-flash-tts', input };
33
+ },
34
+ },
35
+ pricing: {
36
+ currency: '$', calculator: () => Number.NaN,
37
+ description: '输入 $0.7/百万 Token、音频输出 $14/百万 Token;输出 Token 无法在提交前准确估算',
38
+ },
39
+ });
40
+ export default kieGemini31FlashTtsModel;
@@ -49,6 +49,7 @@ export const minimaxSpeechModel = defineModel({
49
49
  type: 'dropdown',
50
50
  order: 3,
51
51
  default: '',
52
+ visible: { condition: () => false },
52
53
  options: [
53
54
  { value: '' },
54
55
  { value: 'happy' },
@@ -251,7 +252,6 @@ export const minimaxSpeechModel = defineModel({
251
252
  const audioSpeed = pickNumber(advanced.audioSpeed, params.minimaxAudioSpeed, params.speed);
252
253
  const audioVol = pickNumber(advanced.audioVol, params.minimaxAudioVol, params.vol);
253
254
  const audioPitch = pickNumber(advanced.audioPitch, params.minimaxAudioPitch, params.pitch);
254
- const audioEmotion = pickString(params.minimaxAudioEmotion, params.emotion);
255
255
  const englishNormalization = pickBoolean(advanced.englishNormalization, params.minimaxTextNormalization, params.english_normalization);
256
256
  const continuousSound = pickBoolean(advanced.continuousSound, params.continuous_sound);
257
257
  const sampleRate = pickNumber(advanced.audioSampleRate, params.minimaxAudioSampleRate, params.audio_sample_rate);
@@ -272,8 +272,6 @@ export const minimaxSpeechModel = defineModel({
272
272
  voiceSetting.vol = audioVol;
273
273
  if (audioPitch !== undefined)
274
274
  voiceSetting.pitch = audioPitch;
275
- if (audioEmotion)
276
- voiceSetting.emotion = audioEmotion;
277
275
  if (englishNormalization !== undefined) {
278
276
  voiceSetting.english_normalization = englishNormalization;
279
277
  }
@@ -0,0 +1,2 @@
1
+ export declare const siliconflowCosyVoice2TtsModel: import("../../index.js").ModelRuntimeDefinition;
2
+ export default siliconflowCosyVoice2TtsModel;
@@ -0,0 +1,27 @@
1
+ import { defineModel } from '../defineModel.js';
2
+ import { speechCharacterPrice, speechNumber, speechString, speechText } from '../audio/tts-common.js';
3
+ export const siliconflowCosyVoice2TtsModel = defineModel({
4
+ meta: {
5
+ id: 'siliconflow-cosyvoice2-tts', canonicalModelId: 'cosyvoice2-0.5b', provider: 'siliconflow', type: 'audio',
6
+ tags: ['text-to-audio'],
7
+ },
8
+ inputLimits: { images: { max: 0 }, videos: { max: 0 } },
9
+ params: [
10
+ { id: 'siliconflowCosyVoice', type: 'text', order: 1, default: 'FunAudioLLM/CosyVoice2-0.5B:alex' },
11
+ { id: 'siliconflowCosySpeed', type: 'number', order: 2, default: 1, min: 0.25, max: 4, step: 0.05 },
12
+ { id: 'siliconflowCosyFormat', type: 'dropdown', order: 3, default: 'mp3', options: ['mp3', 'opus', 'wav', 'pcm'].map(value => ({ value })) },
13
+ ],
14
+ endpoints: '/v1/audio/speech',
15
+ request: {
16
+ builder: (params) => ({
17
+ model: 'FunAudioLLM/CosyVoice2-0.5B',
18
+ input: speechText(params),
19
+ voice: speechString(params.siliconflowCosyVoice) ?? 'FunAudioLLM/CosyVoice2-0.5B:alex',
20
+ response_format: speechString(params.siliconflowCosyFormat) ?? 'mp3',
21
+ speed: speechNumber(params.siliconflowCosySpeed) ?? 1,
22
+ stream: false,
23
+ }),
24
+ },
25
+ pricing: { currency: '¥', calculator: params => speechCharacterPrice(params, 0.05), description: '¥0.05/千 UTF-8 字符' },
26
+ });
27
+ export default siliconflowCosyVoice2TtsModel;
@@ -0,0 +1,2 @@
1
+ export declare const siliconflowMossTtsd05Model: import("../../index.js").ModelRuntimeDefinition;
2
+ export default siliconflowMossTtsd05Model;
@@ -0,0 +1,28 @@
1
+ import { defineModel } from '../defineModel.js';
2
+ import { speechCharacterPrice, speechString, speechText } from '../audio/tts-common.js';
3
+ export const siliconflowMossTtsd05Model = defineModel({
4
+ meta: {
5
+ id: 'siliconflow-moss-ttsd-0.5', canonicalModelId: 'moss-ttsd-v0.5', provider: 'siliconflow', type: 'audio',
6
+ tags: ['text-to-audio'],
7
+ },
8
+ inputLimits: { images: { max: 0 }, videos: { max: 0 } },
9
+ params: [
10
+ { id: 'siliconflowMossVoice', type: 'text', order: 1, default: 'fnlp/MOSS-TTSD-v0.5:alex' },
11
+ { id: 'siliconflowMossFormat', type: 'dropdown', order: 2, default: 'mp3', options: ['mp3', 'opus', 'wav', 'pcm'].map(value => ({ value })) },
12
+ ],
13
+ endpoints: '/v1/audio/speech',
14
+ request: {
15
+ builder: (params) => {
16
+ const text = speechText(params);
17
+ return {
18
+ model: 'fnlp/MOSS-TTSD-v0.5',
19
+ input: /\[S[12]\]/.test(text) ? text : `[S1]${text}`,
20
+ voice: speechString(params.siliconflowMossVoice) ?? 'fnlp/MOSS-TTSD-v0.5:alex',
21
+ response_format: speechString(params.siliconflowMossFormat) ?? 'mp3',
22
+ stream: false,
23
+ };
24
+ },
25
+ },
26
+ pricing: { currency: '¥', calculator: params => speechCharacterPrice(params, 0.05), description: '¥0.05/千 UTF-8 字符' },
27
+ });
28
+ export default siliconflowMossTtsd05Model;
@@ -0,0 +1,2 @@
1
+ export declare const volcengineSeedIcl20Model: import("../../index.js").ModelRuntimeDefinition;
2
+ export default volcengineSeedIcl20Model;
@@ -0,0 +1,26 @@
1
+ import { defineModel } from '../defineModel.js';
2
+ import { speechString, speechText } from '../audio/tts-common.js';
3
+ export const volcengineSeedIcl20Model = defineModel({
4
+ meta: {
5
+ id: 'volcengine-seed-icl-2.0', canonicalModelId: 'seed-icl-2.0', provider: 'volcengine-speech', type: 'audio',
6
+ tags: ['text-to-audio', 'provider-volcengine'],
7
+ },
8
+ inputLimits: { images: { max: 0 }, videos: { max: 0 } },
9
+ params: [
10
+ { id: 'volcSeedIclSpeaker', type: 'text', required: true, order: 1, default: '' },
11
+ ],
12
+ endpoints: '/api/v3/tts/unidirectional',
13
+ request: {
14
+ builder: (params) => {
15
+ const speaker = speechString(params.volcSeedIclSpeaker);
16
+ if (!speaker)
17
+ throw new Error('Seed-ICL 2.0 需要已创建的复刻音色 ID');
18
+ return {
19
+ resource_id: 'seed-icl-2.0',
20
+ req_params: { text: speechText(params), model: 'seed-tts-2.0-standard', speaker, audio_params: { format: 'mp3', sample_rate: 24000 } },
21
+ };
22
+ },
23
+ },
24
+ pricing: { currency: '¥', calculator: () => Number.NaN, description: '火山语音按量计费单价未核实;不显示费用预估' },
25
+ });
26
+ export default volcengineSeedIcl20Model;
@@ -0,0 +1,2 @@
1
+ export declare const volcengineSeedTts20Model: import("../../index.js").ModelRuntimeDefinition;
2
+ export default volcengineSeedTts20Model;
@@ -0,0 +1,25 @@
1
+ import { defineModel } from '../defineModel.js';
2
+ import { speechString, speechText } from '../audio/tts-common.js';
3
+ export const volcengineSeedTts20Model = defineModel({
4
+ meta: {
5
+ id: 'volcengine-seed-tts-2.0', canonicalModelId: 'seed-tts-2.0', provider: 'volcengine-speech', type: 'audio',
6
+ tags: ['text-to-audio', 'provider-volcengine'],
7
+ },
8
+ inputLimits: { images: { max: 0 }, videos: { max: 0 } },
9
+ params: [
10
+ { id: 'volcSeedTtsSpeaker', type: 'text', order: 1, default: 'zh_female_vv_uranus_bigtts' },
11
+ ],
12
+ endpoints: '/api/v3/tts/unidirectional',
13
+ request: {
14
+ builder: (params) => ({
15
+ resource_id: 'seed-tts-2.0',
16
+ req_params: {
17
+ text: speechText(params),
18
+ speaker: speechString(params.volcSeedTtsSpeaker) ?? 'zh_female_vv_uranus_bigtts',
19
+ audio_params: { format: 'mp3', sample_rate: 24000 },
20
+ },
21
+ }),
22
+ },
23
+ pricing: { currency: '¥', calculator: () => Number.NaN, description: '火山语音按量计费单价未核实;不显示费用预估' },
24
+ });
25
+ export default volcengineSeedTts20Model;
package/dist/llm/chat.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { preprocessRequestBody } from '../upload/preprocess.js';
2
2
  import { normalizeProviderError } from '../runtime/error-classify.js';
3
+ import { AiRuntimeError } from '../runtime/AiRuntimeError.js';
3
4
  import { cancelTask, clearCancelFlag, isCancelled, registerAbortController, } from '../runtime/task-registry.js';
4
5
  import { buildOpenAiCompatiblePayload, resolveOpenAiCompatibleEndpoint, resolvePpioChatEndpoint, streamOpenAiCompatibleChat, } from './streaming.js';
5
6
  import { countLlmInputChars } from './message-metrics.js';
@@ -109,11 +110,15 @@ export async function runLlmChatStream(request, taskId, emit, runtime, hooks = {
109
110
  })
110
111
  : error;
111
112
  span?.end(normalizedInput);
112
- throw new Error(normalizeLlmChatError(taskId, normalizedInput, {
113
+ const normalized = new Error(normalizeLlmChatError(taskId, normalizedInput, {
113
114
  providerId: request.providerId,
114
115
  modelId: request.modelId,
115
116
  requestId: taskId,
116
117
  }));
118
+ if (error instanceof AiRuntimeError && error.details?.protocol === 'openai-chat-sse') {
119
+ Object.assign(normalized, { details: error.details });
120
+ }
121
+ throw normalized;
117
122
  }
118
123
  finally {
119
124
  execution.signal?.removeEventListener('abort', forwardAbort);
@@ -1,3 +1,5 @@
1
+ import { drainSseEvents } from '../protocols/sse-events.js';
2
+ import { AiRuntimeError } from '../runtime/AiRuntimeError.js';
1
3
  import { fetchProvider } from '../providers/provider-fetch.js';
2
4
  import { applyProviderRequestBodyQuirks, resolveProviderExtraAuthHeaders, } from './providerProtocolCore.js';
3
5
  import { applyProviderReasoningRequestBody } from './providerReasoningRequest.js';
@@ -78,7 +80,7 @@ export async function streamOpenAiCompatibleChat(options) {
78
80
  if (!response.body) {
79
81
  throw new Error('LLM streaming response body is empty');
80
82
  }
81
- return await readSseStream(response.body, options.emit);
83
+ return await readSseStream(response.body, options);
82
84
  }
83
85
  export function serializeMessage(message) {
84
86
  const serialized = { role: message.role };
@@ -138,7 +140,8 @@ function serializeContentPart(part) {
138
140
  }
139
141
  return next;
140
142
  }
141
- async function readSseStream(body, emit) {
143
+ async function readSseStream(body, options) {
144
+ const { emit, signal } = options;
142
145
  const reader = body.getReader();
143
146
  const decoder = createUtf8StreamDecoder();
144
147
  let pending = '';
@@ -147,68 +150,84 @@ async function readSseStream(body, emit) {
147
150
  let usage = null;
148
151
  let finishReason = null;
149
152
  const toolCalls = new Map();
150
- let streamDone = false;
151
- while (!streamDone) {
152
- const result = await reader.read();
153
- if (result.done) {
154
- streamDone = true;
155
- continue;
156
- }
157
- pending += decoder.decode(result.value, { stream: true });
158
- const parsed = drainSseEvents(pending);
159
- pending = parsed.remaining;
160
- for (const event of parsed.events) {
161
- const chunk = parseSseData(event);
162
- if (chunk.done) {
163
- return {
164
- output,
165
- reasoningOutput,
166
- usage,
167
- finishReason,
168
- truncated: isOutputTruncated(finishReason),
169
- toolCalls: [...toolCalls.values()],
170
- };
171
- }
172
- usage = chunk.usage ?? usage;
173
- finishReason = chunk.finishReason ?? finishReason;
174
- mergeToolCallDeltas(toolCalls, chunk.toolCalls);
175
- if (chunk.reasoning) {
176
- reasoningOutput += chunk.reasoning;
177
- emit({ type: 'ReasoningToken', data: chunk.reasoning });
153
+ let eof = false;
154
+ let stage = 'read';
155
+ let cancellation;
156
+ const cancelReader = () => {
157
+ // Cleanup may reject on an already errored stream. It must not replace the
158
+ // original protocol/transport failure or delay settlement on a custom host.
159
+ cancellation ??= reader.cancel().catch(() => undefined);
160
+ };
161
+ const checkAbort = () => {
162
+ if (signal.aborted)
163
+ throw Object.assign(new Error('LLM stream cancelled'), { name: 'AbortError' });
164
+ };
165
+ const complete = () => {
166
+ stage = 'completion';
167
+ checkAbort();
168
+ if (!finishReason)
169
+ throw new AiRuntimeError('STREAM_INCOMPLETE', 'Chat stream ended without finish_reason');
170
+ return { output, reasoningOutput, usage, finishReason,
171
+ truncated: isOutputTruncated(finishReason, options.providerId), toolCalls: [...toolCalls.values()] };
172
+ };
173
+ signal.addEventListener('abort', cancelReader, { once: true });
174
+ try {
175
+ checkAbort();
176
+ for (;;) {
177
+ stage = 'read';
178
+ const result = await reader.read();
179
+ checkAbort();
180
+ if (result.done) {
181
+ eof = true;
182
+ // SSE dispatch requires a blank line. Do not manufacture a final event
183
+ // from an unterminated block at EOF.
184
+ return complete();
178
185
  }
179
- if (chunk.content) {
180
- output += chunk.content;
181
- emit({ type: 'Token', data: chunk.content });
186
+ pending += decoder.decode(result.value, { stream: true });
187
+ const parsed = drainSseEvents(pending);
188
+ pending = parsed.remaining;
189
+ for (const event of parsed.events) {
190
+ checkAbort();
191
+ stage = 'parse';
192
+ const chunk = parseSseData(event, options.providerId);
193
+ if (chunk.done)
194
+ return complete();
195
+ if (finishReason && (chunk.content || chunk.reasoning || chunk.toolCalls?.length
196
+ || (chunk.finishReason && chunk.finishReason !== finishReason))) {
197
+ throw new AiRuntimeError('INVALID_STREAM_RESPONSE', 'Chat result changed after finish_reason');
198
+ }
199
+ usage = chunk.usage ?? usage;
200
+ finishReason = chunk.finishReason ?? finishReason;
201
+ mergeToolCallDeltas(toolCalls, chunk.toolCalls);
202
+ stage = 'emit';
203
+ if (chunk.reasoning) {
204
+ reasoningOutput += chunk.reasoning;
205
+ emit({ type: 'ReasoningToken', data: chunk.reasoning });
206
+ checkAbort();
207
+ }
208
+ if (chunk.content) {
209
+ output += chunk.content;
210
+ emit({ type: 'Token', data: chunk.content });
211
+ }
182
212
  }
183
213
  }
184
214
  }
185
- const flushed = decoder.decode();
186
- if (flushed) {
187
- pending += flushed;
188
- }
189
- const parsed = drainSseEvents(`${pending}\n\n`);
190
- for (const event of parsed.events) {
191
- const chunk = parseSseData(event);
192
- usage = chunk.usage ?? usage;
193
- finishReason = chunk.finishReason ?? finishReason;
194
- mergeToolCallDeltas(toolCalls, chunk.toolCalls);
195
- if (chunk.reasoning) {
196
- reasoningOutput += chunk.reasoning;
197
- emit({ type: 'ReasoningToken', data: chunk.reasoning });
198
- }
199
- if (chunk.content) {
200
- output += chunk.content;
201
- emit({ type: 'Token', data: chunk.content });
215
+ catch (error) {
216
+ if (error instanceof AiRuntimeError) {
217
+ throw new AiRuntimeError(error.code, 'Chat streaming response failed', {
218
+ providerId: options.providerId, modelId: options.payload.model,
219
+ protocol: 'openai-chat-sse', stage, receivedFinish: finishReason !== null,
220
+ ...(error.details?.finishReason === 'network_error' ? { finishReason: 'network_error' } : {}),
221
+ });
202
222
  }
223
+ throw error;
224
+ }
225
+ finally {
226
+ signal.removeEventListener('abort', cancelReader);
227
+ if (!eof)
228
+ cancelReader();
229
+ reader.releaseLock();
203
230
  }
204
- return {
205
- output,
206
- reasoningOutput,
207
- usage,
208
- finishReason,
209
- truncated: isOutputTruncated(finishReason),
210
- toolCalls: [...toolCalls.values()],
211
- };
212
231
  }
213
232
  function applyStructuredOutputRequestBody(request, providerId, body) {
214
233
  const output = request.structuredOutput;
@@ -282,47 +301,68 @@ function resolveMaxOutputTokens(request) {
282
301
  function invalidRequest(code, message) {
283
302
  return Object.assign(new Error(message), { code, statusCode: 400 });
284
303
  }
285
- function isOutputTruncated(finishReason) {
286
- return finishReason === 'length' || finishReason === 'max_tokens' || finishReason === 'max_output_tokens';
287
- }
288
- function drainSseEvents(input) {
289
- const events = [];
290
- let remaining = input;
291
- let hasSeparator = true;
292
- while (hasSeparator) {
293
- const separator = findNextSeparator(remaining);
294
- if (!separator) {
295
- hasSeparator = false;
296
- continue;
297
- }
298
- if (separator) {
299
- events.push(remaining.slice(0, separator.index));
300
- remaining = remaining.slice(separator.index + separator.length);
301
- }
302
- }
303
- return { events, remaining };
304
- }
305
- function findNextSeparator(input) {
306
- const crlf = input.indexOf('\r\n\r\n');
307
- const lf = input.indexOf('\n\n');
308
- if (crlf === -1 && lf === -1)
309
- return undefined;
310
- if (crlf !== -1 && (lf === -1 || crlf < lf))
311
- return { index: crlf, length: 4 };
312
- return { index: lf, length: 2 };
304
+ function isOutputTruncated(finishReason, providerId) {
305
+ return finishReason === 'length' || finishReason === 'max_tokens' || finishReason === 'max_output_tokens'
306
+ || (providerId === 'bigmodel' && finishReason === 'model_context_window_exceeded');
313
307
  }
314
- function parseSseData(event) {
308
+ function parseSseData(event, providerId) {
309
+ const eventName = event.split(/\r\n|\r|\n/)
310
+ .filter(line => line.startsWith('event:')).at(-1)?.slice(6).trim();
315
311
  const data = event
316
- .split(/\r?\n/)
317
- .map((line) => line.trim())
312
+ .split(/\r\n|\r|\n/)
318
313
  .filter((line) => line.startsWith('data:'))
319
314
  .map((line) => line.slice(5).trim())
320
315
  .join('\n');
316
+ if (eventName === 'error' && !data)
317
+ throw new AiRuntimeError('PROVIDER_STREAM_ERROR', 'Server error event');
321
318
  if (!data)
322
319
  return { done: false };
323
- if (data === '[DONE]')
320
+ if (data === '[DONE]' && (!eventName || eventName === 'message'))
324
321
  return { done: true };
325
- const json = JSON.parse(data);
322
+ if (eventName && !['message', 'chat.completion.chunk', 'error'].includes(eventName))
323
+ return { done: false };
324
+ let json;
325
+ try {
326
+ json = JSON.parse(data);
327
+ }
328
+ catch {
329
+ if (eventName === 'error')
330
+ throw new AiRuntimeError('PROVIDER_STREAM_ERROR', 'Server error event');
331
+ throw new AiRuntimeError('INVALID_STREAM_RESPONSE', 'Invalid SSE JSON');
332
+ }
333
+ if (eventName === 'error' || (isRecord(json) && json.error != null)) {
334
+ const error = isRecord(json) && isRecord(json.error) ? json.error : json;
335
+ const code = isRecord(error) && typeof error.code === 'string' && /^[\w.-]{1,80}$/.test(error.code)
336
+ ? error.code : 'PROVIDER_STREAM_ERROR';
337
+ throw new AiRuntimeError(code, 'Server error event');
338
+ }
339
+ // BigModel's documented SDK consumer permits status-only chunks without
340
+ // choices. This does not make them completion evidence or allow wrong types.
341
+ const statusOnlyAllowed = providerId === 'bigmodel';
342
+ if (statusOnlyAllowed && isRecord(json) && json.choices === undefined) {
343
+ return { done: false, usage: readUsage(json) };
344
+ }
345
+ if (!isRecord(json) || !Array.isArray(json.choices)) {
346
+ throw new AiRuntimeError('INVALID_STREAM_RESPONSE', 'Chat chunk has no choices array');
347
+ }
348
+ if (json.choices.length === 0) {
349
+ if (!statusOnlyAllowed && !isRecord(json.usage))
350
+ throw new AiRuntimeError('INVALID_STREAM_RESPONSE', 'Empty choices without usage');
351
+ return { done: false, usage: readUsage(json) };
352
+ }
353
+ const first = json.choices[0];
354
+ if (!isRecord(first) || !isRecord(first.delta)
355
+ || (first.finish_reason != null && (typeof first.finish_reason !== 'string' || !first.finish_reason.trim()))) {
356
+ throw new AiRuntimeError('INVALID_STREAM_RESPONSE', 'Invalid Chat choice');
357
+ }
358
+ if (providerId === 'bigmodel' && first.finish_reason === 'network_error') {
359
+ throw new AiRuntimeError('PROVIDER_STREAM_ERROR', 'Model inference failed', { finishReason: 'network_error' });
360
+ }
361
+ for (const field of ['content', 'reasoning_content', 'reasoning']) {
362
+ if (first.delta[field] != null && typeof first.delta[field] !== 'string') {
363
+ throw new AiRuntimeError('INVALID_STREAM_RESPONSE', 'Invalid Chat text delta');
364
+ }
365
+ }
326
366
  const delta = readDelta(json);
327
367
  return {
328
368
  done: false,
@@ -0,0 +1,6 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/bailian/cosyvoice-v3.5.model.js';
3
+ import type { GenerationClientProviderRegistration, GenerationPack } from '../../../generation/core.js';
4
+ export { model };
5
+ export declare const provider: GenerationClientProviderRegistration;
6
+ export declare const pack: GenerationPack;
@@ -0,0 +1,7 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/bailian/cosyvoice-v3.5.model.js';
3
+ import * as adapter from '../../../providers/bailian.js';
4
+ import { preprocess } from '../../../upload/provider-preprocessors/data-uri.js';
5
+ export { model };
6
+ export const provider = { id: 'bailian', adapter, preprocess };
7
+ export const pack = { models: [model], providers: [provider] };
@@ -0,0 +1,6 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/bailian/minimax-speech-2.8.model.js';
3
+ import type { GenerationClientProviderRegistration, GenerationPack } from '../../../generation/core.js';
4
+ export { model };
5
+ export declare const provider: GenerationClientProviderRegistration;
6
+ export declare const pack: GenerationPack;
@@ -0,0 +1,7 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/bailian/minimax-speech-2.8.model.js';
3
+ import * as adapter from '../../../providers/bailian.js';
4
+ import { preprocess } from '../../../upload/provider-preprocessors/data-uri.js';
5
+ export { model };
6
+ export const provider = { id: 'bailian', adapter, preprocess };
7
+ export const pack = { models: [model], providers: [provider] };
@@ -0,0 +1,6 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/bailian/qwen-audio-3.1-tts-flash.model.js';
3
+ import type { GenerationClientProviderRegistration, GenerationPack } from '../../../generation/core.js';
4
+ export { model };
5
+ export declare const provider: GenerationClientProviderRegistration;
6
+ export declare const pack: GenerationPack;
@@ -0,0 +1,7 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/bailian/qwen-audio-3.1-tts-flash.model.js';
3
+ import * as adapter from '../../../providers/bailian.js';
4
+ import { preprocess } from '../../../upload/provider-preprocessors/data-uri.js';
5
+ export { model };
6
+ export const provider = { id: 'bailian', adapter, preprocess };
7
+ export const pack = { models: [model], providers: [provider] };
@@ -0,0 +1,6 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/fal/eleven-v3-tts.model.js';
3
+ import type { GenerationClientProviderRegistration, GenerationPack } from '../../../generation/core.js';
4
+ export { model };
5
+ export declare const provider: GenerationClientProviderRegistration;
6
+ export declare const pack: GenerationPack;
@@ -0,0 +1,7 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/fal/eleven-v3-tts.model.js';
3
+ import * as adapter from '../../../providers/fal.js';
4
+ import { preprocess } from '../../../upload/provider-preprocessors/fal.js';
5
+ export { model };
6
+ export const provider = { id: 'fal', adapter, preprocess };
7
+ export const pack = { models: [model], providers: [provider] };
@@ -0,0 +1,6 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/fal/minimax-speech-2.8.model.js';
3
+ import type { GenerationClientProviderRegistration, GenerationPack } from '../../../generation/core.js';
4
+ export { model };
5
+ export declare const provider: GenerationClientProviderRegistration;
6
+ export declare const pack: GenerationPack;
@@ -0,0 +1,7 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/fal/minimax-speech-2.8.model.js';
3
+ import * as adapter from '../../../providers/fal.js';
4
+ import { preprocess } from '../../../upload/provider-preprocessors/fal.js';
5
+ export { model };
6
+ export const provider = { id: 'fal', adapter, preprocess };
7
+ export const pack = { models: [model], providers: [provider] };
@@ -0,0 +1,6 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/fal/qwen3-tts-1.7b.model.js';
3
+ import type { GenerationClientProviderRegistration, GenerationPack } from '../../../generation/core.js';
4
+ export { model };
5
+ export declare const provider: GenerationClientProviderRegistration;
6
+ export declare const pack: GenerationPack;
@@ -0,0 +1,7 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/fal/qwen3-tts-1.7b.model.js';
3
+ import * as adapter from '../../../providers/fal.js';
4
+ import { preprocess } from '../../../upload/provider-preprocessors/fal.js';
5
+ export { model };
6
+ export const provider = { id: 'fal', adapter, preprocess };
7
+ export const pack = { models: [model], providers: [provider] };
@@ -0,0 +1,6 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/kie/eleven-turbo-2.5-tts.model.js';
3
+ import type { GenerationClientProviderRegistration, GenerationPack } from '../../../generation/core.js';
4
+ export { model };
5
+ export declare const provider: GenerationClientProviderRegistration;
6
+ export declare const pack: GenerationPack;
@@ -0,0 +1,7 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/kie/eleven-turbo-2.5-tts.model.js';
3
+ import * as adapter from '../../../providers/kie.js';
4
+ import { preprocess } from '../../../upload/provider-preprocessors/kie.js';
5
+ export { model };
6
+ export const provider = { id: 'kie', adapter, preprocess };
7
+ export const pack = { models: [model], providers: [provider] };
@@ -0,0 +1,6 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/kie/eleven-v3-dialogue.model.js';
3
+ import type { GenerationClientProviderRegistration, GenerationPack } from '../../../generation/core.js';
4
+ export { model };
5
+ export declare const provider: GenerationClientProviderRegistration;
6
+ export declare const pack: GenerationPack;
@@ -0,0 +1,7 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/kie/eleven-v3-dialogue.model.js';
3
+ import * as adapter from '../../../providers/kie.js';
4
+ import { preprocess } from '../../../upload/provider-preprocessors/kie.js';
5
+ export { model };
6
+ export const provider = { id: 'kie', adapter, preprocess };
7
+ export const pack = { models: [model], providers: [provider] };
@@ -0,0 +1,6 @@
1
+ /** 由 scripts/generate-catalog-index.cjs 自动生成;`pack` 是完整执行单元,`model` 仅供低层目录用途。 */
2
+ import model from '../../../catalog/kie/gemini-3.1-flash-tts.model.js';
3
+ import type { GenerationClientProviderRegistration, GenerationPack } from '../../../generation/core.js';
4
+ export { model };
5
+ export declare const provider: GenerationClientProviderRegistration;
6
+ export declare const pack: GenerationPack;