@zhivex-ai/qwen 0.15.3 → 0.16.2

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 (66) hide show
  1. package/README.md +46 -1
  2. package/dist/browser-realtime.d.ts +58 -0
  3. package/dist/browser-realtime.d.ts.map +1 -0
  4. package/dist/browser-realtime.js +259 -0
  5. package/dist/browser-realtime.js.map +1 -0
  6. package/dist/browser.d.ts +9 -0
  7. package/dist/browser.d.ts.map +1 -0
  8. package/dist/browser.js +7 -0
  9. package/dist/browser.js.map +1 -0
  10. package/dist/cloud-media.d.ts +89 -0
  11. package/dist/cloud-media.d.ts.map +1 -0
  12. package/dist/cloud-media.js +221 -0
  13. package/dist/cloud-media.js.map +1 -0
  14. package/dist/cloud-realtime.d.ts +58 -0
  15. package/dist/cloud-realtime.d.ts.map +1 -0
  16. package/dist/cloud-realtime.js +123 -0
  17. package/dist/cloud-realtime.js.map +1 -0
  18. package/dist/decision.d.ts +69 -0
  19. package/dist/decision.d.ts.map +1 -0
  20. package/dist/decision.js +122 -0
  21. package/dist/decision.js.map +1 -0
  22. package/dist/embeddings.d.ts +30 -0
  23. package/dist/embeddings.d.ts.map +1 -0
  24. package/dist/embeddings.js +94 -0
  25. package/dist/embeddings.js.map +1 -0
  26. package/dist/index.d.ts +40 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +365 -144
  29. package/dist/index.js.map +1 -1
  30. package/dist/inference-audio.d.ts +146 -0
  31. package/dist/inference-audio.d.ts.map +1 -0
  32. package/dist/inference-audio.js +256 -0
  33. package/dist/inference-audio.js.map +1 -0
  34. package/dist/native-asr.d.ts +52 -0
  35. package/dist/native-asr.d.ts.map +1 -0
  36. package/dist/native-asr.js +162 -0
  37. package/dist/native-asr.js.map +1 -0
  38. package/dist/rtc-protocol.d.ts +10 -0
  39. package/dist/rtc-protocol.d.ts.map +1 -0
  40. package/dist/rtc-protocol.js +10 -0
  41. package/dist/rtc-protocol.js.map +1 -0
  42. package/dist/sdp-exchange.d.ts +8 -0
  43. package/dist/sdp-exchange.d.ts.map +1 -0
  44. package/dist/sdp-exchange.js +25 -0
  45. package/dist/sdp-exchange.js.map +1 -0
  46. package/dist/temporary-keys.d.ts +15 -0
  47. package/dist/temporary-keys.d.ts.map +1 -0
  48. package/dist/temporary-keys.js +28 -0
  49. package/dist/temporary-keys.js.map +1 -0
  50. package/dist/text-profiles.d.ts +7 -0
  51. package/dist/text-profiles.d.ts.map +1 -0
  52. package/dist/text-profiles.js +55 -0
  53. package/dist/text-profiles.js.map +1 -0
  54. package/dist/url-path.d.ts +3 -0
  55. package/dist/url-path.d.ts.map +1 -0
  56. package/dist/url-path.js +8 -0
  57. package/dist/url-path.js.map +1 -0
  58. package/dist/world-rtc.d.ts +30 -0
  59. package/dist/world-rtc.d.ts.map +1 -0
  60. package/dist/world-rtc.js +33 -0
  61. package/dist/world-rtc.js.map +1 -0
  62. package/dist/worlds.d.ts +134 -0
  63. package/dist/worlds.d.ts.map +1 -0
  64. package/dist/worlds.js +196 -0
  65. package/dist/worlds.js.map +1 -0
  66. package/package.json +6 -2
package/dist/index.js CHANGED
@@ -1,9 +1,23 @@
1
+ export { createQwenSDPExchange } from "./sdp-exchange.js";
2
+ export { connectQwenWebRTC, connectQwenAOQ } from "./browser-realtime.js";
3
+ import { createQwenTemporaryKeysClient } from "./temporary-keys.js";
4
+ import { createQwenNativeTranscriptionModel, createQwenFileTranscriptionModel } from "./native-asr.js";
5
+ export { createQwenWorldRTC } from "./world-rtc.js";
6
+ import { createQwenImageTranslationModel, validateWan30, validateQwenImage30, validateHappyHorse, prepareViduVideo, validateViduImage } from "./cloud-media.js";
7
+ import { createQwenWorldsClient } from "./worlds.js";
8
+ import { QwenCloudSpeechModel, QwenCloudStreamingASRModel, isQwenCloudSpeech, isQwenCloudStreamingASR } from "./inference-audio.js";
9
+ import { isQwenCloudRealtime, isQwenAudioRealtime, isQwenOmni38Realtime, mapQwenCloudRealtimeSession, validateQwenCloudAudioFrame, qwenCloudAudioEvent, extendQwenCloudRealtimeSession } from "./cloud-realtime.js";
10
+ import { createQwenDecisionModel } from "./decision.js";
11
+ import { assertQwenLanguageModel, isQwenTranslation, isQwenCharacter, isQwen38Open, validateQwenSpecializedText } from "./text-profiles.js";
12
+ import { createQwenTextEmbeddingModel } from "./embeddings.js";
1
13
  import { createQwenVoicesClient } from "./voices.js";
2
14
  import { isLiveTranslate38, liveTranslateSession } from "./live-translate.js";
3
15
  import { thirdPartyProfile, thirdPartyCapabilities, validateThirdParty, thirdPartyReasoning } from "./third-party.js";
4
16
  import { toJSONSchema } from "zod";
5
17
  import { capabilities, embeddingCapabilities, imageGenerationCapabilities, realtimeCapabilities, speechCapabilities, transcriptionCapabilities, videoGenerationCapabilities, } from "./capabilities.js";
6
18
  import { imageInputToDataUrl, CallbackRealtimeSession, ConfigurationError, decodeBase64WithLimit, ProviderHTTPError, ProviderToolCallError, ValidationError, assertTrustedEndpoint, readBodyWithLimit, readErrorBodyWithLimit, readJsonWithLimit, resolveAudioResponseLimits, UnsupportedFeatureError, createProviderAdapter, hostedTool, openWebSocketConnection, isCallableToolDefinition, normalizeFinishReason, providerDataPart, streamSSE, toolResultPayload, toToolSet, withRetry, withResponseRetry, withTimeoutSignal } from "@zhivex-ai/core/provider";
19
+ export const QWEN_CLOUD_BASE_URL = "https://maas.qwencloudapi.com/compatible-mode/v1";
20
+ export const QWEN_DECISION_PREVIEW_BASE_URL = "https://trial.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1";
7
21
  export const QWEN_TOKEN_PLAN_BASE_URL = "https://token-plan.maas.qwencloudapi.com/compatible-mode/v1";
8
22
  const QWEN_TOKEN_PLAN_ORIGINS = new Set([
9
23
  new URL(QWEN_TOKEN_PLAN_BASE_URL).origin,
@@ -155,7 +169,7 @@ const stripResponsesRequestOptions = (providerOptions) => {
155
169
  };
156
170
  const modelFamily = (modelId) => {
157
171
  const normalized = modelId.toLowerCase();
158
- return normalized.replace(/^(qwen3\.8-(?:max|flash))-\d{4}$/, "$1");
172
+ return normalized.replace(/^(qwen3\.8-(?:max|flash))-(?:\d{4}|\d{4}-\d{2}-\d{2})$/, "$1");
159
173
  };
160
174
  const isQwen38OmniFlash = (modelId) => modelId.toLowerCase() === "qwen3.8-omni-flash";
161
175
  const isQwen38Max = (modelId) => modelFamily(modelId) === "qwen3.8-max";
@@ -216,10 +230,11 @@ const supportsQwenVision = (modelId) => {
216
230
  model.includes("vision") ||
217
231
  isQwen38ProductionModel(modelId) ||
218
232
  isQwen38MaxPreview(modelId) ||
219
- /^qwen3\.7-plus(?:$|-)/.test(model) ||
233
+ isQwen38Open(modelId) ||
234
+ /^qwen3\.7-(?:plus|flash)(?:$|-)/.test(model) ||
220
235
  /^qwen3\.6-flash(?:$|-)/.test(model));
221
236
  };
222
- const supportsQwenTools = (modelId) => !/(embedding|rerank|asr|tts|image|realtime|ocr|translation|character|long-context)/.test(modelFamily(modelId));
237
+ const supportsQwenTools = (modelId) => !isQwenTranslation(modelId) && !/(embedding|rerank|asr|tts|image|realtime|ocr|translation|character|long-context)/.test(modelFamily(modelId));
223
238
  const supportsQwenFiles = (modelId) => /^qwen3\.5-ocr(?:$|-)/.test(modelFamily(modelId));
224
239
  const supportsQwenAudioInput = (modelId) => /(omni|audio|asr)/.test(modelFamily(modelId));
225
240
  const qwenLanguageCapabilities = (modelId) => {
@@ -241,8 +256,8 @@ const qwenLanguageCapabilities = (modelId) => {
241
256
  jsonMode: tools && !omni && !omni38 && !qwen38MaxPreview,
242
257
  toolChoice: tools,
243
258
  parallelToolCalls: qwen38Production && !omni38,
244
- webSearch: tools && !omni,
245
- files: supportsQwenFiles(modelId) || qwen38Production,
259
+ webSearch: tools && !omni || ["qwen-plus-character", "qwen-flash-character"].includes(modelId),
260
+ files: supportsQwenFiles(modelId) || qwen38Production || isQwen38Open(modelId),
246
261
  audioInput: supportsQwenAudioInput(modelId),
247
262
  reasoning,
248
263
  reasoningEfforts: qwen38Production
@@ -669,8 +684,10 @@ const hasMessagePart = (messages, type) => messages.some((message) => message.pa
669
684
  const hasVideoInput = (messages) => messages.some((message) => message.parts.some((part) => part.type === "file" && part.mediaType.toLowerCase().startsWith("video/")));
670
685
  const hasNonVideoFileInput = (messages) => messages.some((message) => message.parts.some((part) => part.type === "file" && !part.mediaType.toLowerCase().startsWith("video/")));
671
686
  const resolveApiMode = (modelId, requestedMode, input, providerOptions) => {
687
+ if (isQwenTranslation(modelId) || isQwenCharacter(modelId))
688
+ return "chat";
672
689
  const videoInput = hasVideoInput(input.messages);
673
- if (videoInput && !isQwen38ProductionModel(modelId)) {
690
+ if (videoInput && !isQwen38ProductionModel(modelId) && !isQwen38Open(modelId)) {
674
691
  throw new UnsupportedFeatureError(`Qwen model "${modelId}" does not support video FilePart input through this adapter.`);
675
692
  }
676
693
  const omni38 = isQwen38OmniFlash(modelId);
@@ -910,12 +927,65 @@ class QwenResponseError extends Error {
910
927
  this.name = "QwenResponseError";
911
928
  }
912
929
  }
930
+ // Never retain provider arguments or parser causes in diagnostics.
931
+ const qwenResponsesToolCallError = (reason, usage, effectsPossible = false) => new ProviderToolCallError({
932
+ provider: "qwen", transport: "responses", diagnosticCode: "QWEN_RESPONSES_TOOL_CALL_INVALID",
933
+ reason, usage, effectsPossible, retryable: false
934
+ });
935
+ class QwenStreamEventError extends Error {
936
+ transport;
937
+ reason;
938
+ provider = "qwen";
939
+ diagnosticCode = "QWEN_SSE_EVENT_INVALID";
940
+ retryable = false;
941
+ constructor(transport, reason) {
942
+ super("Qwen stream event could not be parsed safely.");
943
+ this.transport = transport;
944
+ this.reason = reason;
945
+ this.name = "QwenStreamEventError";
946
+ }
947
+ }
948
+ const parseQwenStreamEvent = (data, transport) => {
949
+ let event;
950
+ try {
951
+ event = JSON.parse(data);
952
+ }
953
+ catch {
954
+ throw new QwenStreamEventError(transport, "invalid_json");
955
+ }
956
+ if (!event || typeof event !== "object" || Array.isArray(event) || (transport === "responses" && typeof event.type !== "string")) {
957
+ throw new QwenStreamEventError(transport, "invalid_event");
958
+ }
959
+ return event;
960
+ };
961
+ const parseQwenResponsesArguments = (raw, usage, effectsPossible = false) => {
962
+ const failure = (reason) => qwenResponsesToolCallError(reason, usage, effectsPossible);
963
+ if (typeof raw !== "string")
964
+ throw failure("incomplete_arguments");
965
+ if (!raw.trim())
966
+ throw failure("empty_arguments");
967
+ let value;
968
+ try {
969
+ value = JSON.parse(raw);
970
+ }
971
+ catch {
972
+ throw failure("invalid_json");
973
+ }
974
+ if (!value || typeof value !== "object" || Array.isArray(value))
975
+ throw failure("invalid_json");
976
+ return value;
977
+ };
913
978
  const parseResponsesAssistantMessage = (json, input) => {
914
979
  if (json.status === "failed" || json.error)
915
980
  throw new QwenResponseError();
916
981
  const parts = [];
917
982
  const seenIds = existingToolCallIds(input.messages);
918
983
  const fallbackGeneration = nextFallbackToolCallGeneration("responses", input, seenIds);
984
+ const usage = mapResponsesUsage(json.usage);
985
+ const effectsPossible = Object.values(input.tools ?? {}).some(tool => !isCallableToolDefinition(tool)) || (json.output ?? []).some((item) => parseResponsesProviderData(item) !== undefined);
986
+ if (json.status === "incomplete" && (json.output ?? []).some((item) => item?.type === "function_call")) {
987
+ throw qwenResponsesToolCallError("response_incomplete", usage, effectsPossible);
988
+ }
919
989
  for (const [index, item] of (json.output ?? []).entries()) {
920
990
  if (item?.type === "message") {
921
991
  for (const content of item.content ?? []) {
@@ -926,6 +996,9 @@ const parseResponsesAssistantMessage = (json, input) => {
926
996
  continue;
927
997
  }
928
998
  if (item?.type === "function_call") {
999
+ if (typeof item.name !== "string" || !item.name.trim()) {
1000
+ throw qwenResponsesToolCallError("inconsistent_metadata", usage, effectsPossible);
1001
+ }
929
1002
  const providerCallId = qwenResponsesProviderCallId(item.call_id);
930
1003
  const id = resolveToolCallId([item.call_id, item.id], fallbackToolCallId("responses", fallbackGeneration, index), seenIds);
931
1004
  parts.push({
@@ -933,7 +1006,7 @@ const parseResponsesAssistantMessage = (json, input) => {
933
1006
  toolCall: {
934
1007
  id,
935
1008
  name: item.name,
936
- input: JSON.parse(item.arguments ?? "{}"),
1009
+ input: parseQwenResponsesArguments(item.arguments, usage, effectsPossible),
937
1010
  ...(providerCallId !== undefined && providerCallId !== id
938
1011
  ? { providerMetadata: { qwenResponsesCallId: providerCallId } }
939
1012
  : {})
@@ -974,7 +1047,24 @@ const streamResponses = async function* (response, input) {
974
1047
  const toolBufferAliases = new Map();
975
1048
  const seenIds = existingToolCallIds(input.messages);
976
1049
  const fallbackGeneration = nextFallbackToolCallGeneration("responses", input, seenIds);
977
- let sawToolCalls = false;
1050
+ let terminal;
1051
+ let effectsPossible = Object.values(input.tools ?? {}).some(tool => !isCallableToolDefinition(tool));
1052
+ let invalidMetadata = false;
1053
+ const mergeMetadata = (buffer, itemId, callId, name) => {
1054
+ const nextItemId = qwenResponsesProviderCallId(itemId);
1055
+ const nextCallId = qwenResponsesProviderCallId(callId);
1056
+ const nextName = typeof name === "string" && name.trim() ? name : undefined;
1057
+ if ((buffer.itemId !== undefined && nextItemId !== undefined && buffer.itemId !== nextItemId) ||
1058
+ (buffer.providerCallId !== undefined && nextCallId !== undefined && buffer.providerCallId !== nextCallId) ||
1059
+ (buffer.name && nextName !== undefined && buffer.name !== nextName) ||
1060
+ (name !== undefined && typeof name !== "string"))
1061
+ invalidMetadata = true;
1062
+ buffer.itemId ??= nextItemId;
1063
+ buffer.providerCallId ??= nextCallId;
1064
+ buffer.name ||= nextName ?? "";
1065
+ // A receipt ID may arrive after the item ID used as the initial fallback.
1066
+ buffer.callId = stableToolCallId(callId) ?? buffer.callId;
1067
+ };
978
1068
  const bufferKey = (outputIndex, candidates, fallbackId) => {
979
1069
  const stableIds = candidates
980
1070
  .map(stableToolCallId)
@@ -993,31 +1083,11 @@ const streamResponses = async function* (response, input) {
993
1083
  }
994
1084
  return `fallback:${fallbackId}`;
995
1085
  };
996
- const emitToolCall = (key) => {
997
- const toolCall = toolBuffers.get(key);
998
- if (!toolCall || toolCall.emitted || !toolCall.name) {
999
- return undefined;
1000
- }
1001
- toolCall.emitted = true;
1002
- sawToolCalls = true;
1003
- const id = resolveToolCallId([toolCall.callId], toolCall.fallbackId, seenIds);
1004
- return {
1005
- type: "tool-call",
1006
- toolCall: {
1007
- id,
1008
- name: toolCall.name,
1009
- input: JSON.parse(toolCall.args || "{}"),
1010
- ...(toolCall.providerCallId !== undefined && toolCall.providerCallId !== id
1011
- ? { providerMetadata: { qwenResponsesCallId: toolCall.providerCallId } }
1012
- : {})
1013
- }
1014
- };
1015
- };
1016
1086
  for await (const event of streamSSE(response)) {
1017
1087
  if (event.data === "[DONE]") {
1018
- return;
1088
+ break;
1019
1089
  }
1020
- const json = JSON.parse(event.data);
1090
+ const json = parseQwenStreamEvent(event.data, "responses");
1021
1091
  const type = json.type;
1022
1092
  if (type === "error" || type === "response.failed" || json.response?.status === "failed" || json.error) {
1023
1093
  throw new QwenResponseError();
@@ -1050,23 +1120,24 @@ const streamResponses = async function* (response, input) {
1050
1120
  fallbackId,
1051
1121
  name: item.name ?? "",
1052
1122
  args: "",
1053
- emitted: false
1123
+ done: false
1054
1124
  };
1055
- existing.callId = stableToolCallId(item.call_id) ?? existing.callId;
1056
- existing.providerCallId = qwenResponsesProviderCallId(item.call_id) ?? existing.providerCallId;
1057
- existing.name ||= item.name ?? "";
1125
+ if (item.arguments !== undefined && typeof item.arguments !== "string")
1126
+ invalidMetadata = true;
1127
+ if (existing.done && typeof item.arguments === "string" && item.arguments !== existing.args)
1128
+ invalidMetadata = true;
1129
+ mergeMetadata(existing, item.id ?? json.item_id, item.call_id, item.name);
1058
1130
  if (typeof item.arguments === "string") {
1059
1131
  existing.args = item.arguments;
1060
1132
  }
1061
1133
  toolBuffers.set(key, existing);
1062
1134
  if (type === "response.output_item.done") {
1063
- const emitted = emitToolCall(key);
1064
- if (emitted) {
1065
- yield emitted;
1066
- }
1135
+ existing.done = true;
1067
1136
  }
1068
1137
  }
1069
1138
  const providerData = parseResponsesProviderData(item);
1139
+ if (providerData)
1140
+ effectsPossible = true;
1070
1141
  if (providerData && type === "response.output_item.done") {
1071
1142
  yield {
1072
1143
  type: "provider-data",
@@ -1086,9 +1157,13 @@ const streamResponses = async function* (response, input) {
1086
1157
  fallbackId,
1087
1158
  name: "",
1088
1159
  args: "",
1089
- emitted: false
1160
+ done: false
1090
1161
  };
1091
- existing.providerCallId = qwenResponsesProviderCallId(json.call_id) ?? existing.providerCallId;
1162
+ mergeMetadata(existing, json.item_id, json.call_id);
1163
+ if (existing.done)
1164
+ invalidMetadata = true;
1165
+ if (typeof json.delta !== "string")
1166
+ invalidMetadata = true;
1092
1167
  existing.args += typeof json.delta === "string" ? json.delta : "";
1093
1168
  toolBuffers.set(key, existing);
1094
1169
  continue;
@@ -1103,36 +1178,104 @@ const streamResponses = async function* (response, input) {
1103
1178
  fallbackId,
1104
1179
  name: "",
1105
1180
  args: "",
1106
- emitted: false
1181
+ done: false
1107
1182
  };
1108
- existing.providerCallId = qwenResponsesProviderCallId(json.call_id) ?? existing.providerCallId;
1183
+ mergeMetadata(existing, json.item_id, json.call_id);
1184
+ if (json.arguments !== undefined && typeof json.arguments !== "string")
1185
+ invalidMetadata = true;
1186
+ if (existing.done && typeof json.arguments === "string" && json.arguments !== existing.args)
1187
+ invalidMetadata = true;
1109
1188
  if (typeof json.arguments === "string") {
1110
1189
  existing.args = json.arguments;
1111
1190
  }
1191
+ existing.done = true;
1112
1192
  toolBuffers.set(key, existing);
1113
- const emitted = emitToolCall(key);
1114
- if (emitted) {
1115
- yield emitted;
1116
- }
1117
1193
  continue;
1118
1194
  }
1119
1195
  if (type === "response.completed" || type === "response.incomplete") {
1120
- const responseData = json.response ?? {};
1121
- if (typeof responseData.id === "string") {
1122
- yield {
1123
- type: "provider-data",
1124
- provider: "qwen",
1125
- data: { responseId: responseData.id }
1126
- };
1196
+ if (terminal)
1197
+ invalidMetadata = true;
1198
+ terminal = json.response ?? {};
1199
+ terminal = { ...terminal, status: type === "response.incomplete" ? "incomplete" : terminal.status };
1200
+ }
1201
+ }
1202
+ // Some servers supply the final items only in the terminal snapshot. When
1203
+ // both forms exist, neither may silently overwrite a contradictory call.
1204
+ if (Array.isArray(terminal?.output)) {
1205
+ const terminalKeys = new Set();
1206
+ for (const [index, item] of terminal.output.entries()) {
1207
+ if (parseResponsesProviderData(item))
1208
+ effectsPossible = true;
1209
+ if (item?.type !== "function_call")
1210
+ continue;
1211
+ const fallbackId = fallbackToolCallId("responses", fallbackGeneration, index);
1212
+ const priorKey = [item.id, item.call_id]
1213
+ .map(stableToolCallId)
1214
+ .filter((id) => id !== undefined)
1215
+ .map(id => toolBufferAliases.get(`id:${id}`) ?? `id:${id}`)
1216
+ .find(key => toolBuffers.has(key));
1217
+ const positionalKey = `output:${index}`;
1218
+ const key = toolBuffers.has(positionalKey) ? positionalKey : priorKey ?? bufferKey(index, [item.id, item.call_id], fallbackId);
1219
+ terminalKeys.add(key);
1220
+ const existing = toolBuffers.get(key);
1221
+ if (existing) {
1222
+ if (existing.done && existing.args !== item.arguments)
1223
+ invalidMetadata = true;
1224
+ mergeMetadata(existing, item.id, item.call_id, item.name);
1127
1225
  }
1128
- yield {
1129
- type: "finish",
1130
- finishReason: normalizeResponsesFinishReason(responseData.status, sawToolCalls),
1131
- providerFinishReason: responseData.status,
1132
- usage: mapResponsesUsage(responseData.usage)
1133
- };
1226
+ if (item.arguments !== undefined && typeof item.arguments !== "string")
1227
+ invalidMetadata = true;
1228
+ toolBuffers.set(key, {
1229
+ itemId: existing?.itemId ?? qwenResponsesProviderCallId(item.id),
1230
+ callId: stableToolCallId(item.call_id) ?? existing?.callId ?? stableToolCallId(item.id),
1231
+ providerCallId: qwenResponsesProviderCallId(item.call_id) ?? existing?.providerCallId,
1232
+ fallbackId: existing?.fallbackId ?? fallbackId,
1233
+ name: item.name,
1234
+ args: typeof item.arguments === "string" ? item.arguments : "",
1235
+ done: true
1236
+ });
1134
1237
  }
1135
- }
1238
+ if ([...toolBuffers.keys()].some(key => !terminalKeys.has(key)))
1239
+ invalidMetadata = true;
1240
+ }
1241
+ // No callable event escapes until the full response batch has been checked.
1242
+ // Text/provider events may already have been observed; hosted effects cannot be undone.
1243
+ input.abortSignal?.throwIfAborted();
1244
+ const usage = mapResponsesUsage(terminal?.usage);
1245
+ const failure = (reason) => qwenResponsesToolCallError(reason, usage, effectsPossible);
1246
+ if (!terminal)
1247
+ throw failure("stream_truncated");
1248
+ if (invalidMetadata)
1249
+ throw failure("inconsistent_metadata");
1250
+ if (toolBuffers.size && terminal.status !== "completed")
1251
+ throw failure("response_incomplete");
1252
+ const materialized = [];
1253
+ for (const call of toolBuffers.values()) {
1254
+ if (!call.done)
1255
+ throw failure("incomplete_arguments");
1256
+ if (typeof call.name !== "string" || !call.name.trim())
1257
+ throw failure("inconsistent_metadata");
1258
+ const args = parseQwenResponsesArguments(call.args, usage, effectsPossible);
1259
+ const id = resolveToolCallId([call.callId], call.fallbackId, seenIds);
1260
+ materialized.push({ type: "tool-call", toolCall: {
1261
+ id, name: call.name, input: args,
1262
+ ...(call.providerCallId !== undefined && call.providerCallId !== id
1263
+ ? { providerMetadata: { qwenResponsesCallId: call.providerCallId } } : {})
1264
+ } });
1265
+ }
1266
+ for (const event of materialized) {
1267
+ input.abortSignal?.throwIfAborted();
1268
+ yield event;
1269
+ }
1270
+ if (typeof terminal.id === "string") {
1271
+ yield { type: "provider-data", provider: "qwen", data: { responseId: terminal.id } };
1272
+ }
1273
+ yield {
1274
+ type: "finish",
1275
+ finishReason: normalizeResponsesFinishReason(terminal.status, materialized.length > 0),
1276
+ providerFinishReason: terminal.status,
1277
+ usage
1278
+ };
1136
1279
  };
1137
1280
  class QwenLanguageModel {
1138
1281
  modelId;
@@ -1154,6 +1297,7 @@ class QwenLanguageModel {
1154
1297
  }
1155
1298
  const providerOptions = { ...(input.providerOptions ?? {}) };
1156
1299
  validateThirdParty(this.modelId, input, providerOptions);
1300
+ validateQwenSpecializedText(this.modelId, input);
1157
1301
  validateQwenToolStream(this.modelId, providerOptions, false);
1158
1302
  if (isQwen38ReasoningModel(this.modelId)) {
1159
1303
  validateQwen38Request(this.modelId, input, providerOptions);
@@ -1254,6 +1398,7 @@ class QwenLanguageModel {
1254
1398
  async stream(input) {
1255
1399
  const providerOptions = { ...(input.providerOptions ?? {}) };
1256
1400
  validateThirdParty(this.modelId, input, providerOptions);
1401
+ validateQwenSpecializedText(this.modelId, input);
1257
1402
  validateQwenToolStream(this.modelId, providerOptions, true);
1258
1403
  if (isQwen38ReasoningModel(this.modelId)) {
1259
1404
  validateQwen38Request(this.modelId, input, providerOptions);
@@ -1294,7 +1439,7 @@ class QwenLanguageModel {
1294
1439
  }), { ...input, abortSignal: signal }, "Qwen").catch((error) => { cleanup(); throw error; });
1295
1440
  return (async function* () {
1296
1441
  try {
1297
- yield* streamResponses(response, input);
1442
+ yield* streamResponses(response, { ...input, abortSignal: signal });
1298
1443
  }
1299
1444
  finally {
1300
1445
  cleanup();
@@ -1325,11 +1470,13 @@ class QwenLanguageModel {
1325
1470
  ...mapChatReasoning(this.modelId, input, providerOptions)
1326
1471
  })
1327
1472
  }), { ...input, abortSignal: signal }, "Qwen").catch((error) => { cleanup(); throw error; });
1473
+ const cumulativeTranslation = /^(?:qwen-mt-plus|qwen-mt-turbo)$/.test(this.modelId);
1328
1474
  return (async function* () {
1329
1475
  try {
1330
1476
  const toolBuffers = new Map();
1331
1477
  const seenIds = existingToolCallIds(input.messages);
1332
1478
  const fallbackGeneration = nextFallbackToolCallGeneration("chat", input, seenIds);
1479
+ let translationText = "";
1333
1480
  let lastFinishReason;
1334
1481
  let lastUsage;
1335
1482
  const failure = (reason) => new ProviderToolCallError({ provider: "qwen", transport: "chat", diagnosticCode: "QWEN_CHAT_TOOL_CALL_INVALID",
@@ -1338,7 +1485,7 @@ class QwenLanguageModel {
1338
1485
  if (event.data === "[DONE]") {
1339
1486
  break;
1340
1487
  }
1341
- const json = JSON.parse(event.data);
1488
+ const json = parseQwenStreamEvent(event.data, "chat");
1342
1489
  if (json.error) {
1343
1490
  if (json.usage)
1344
1491
  lastUsage = json.usage;
@@ -1357,7 +1504,13 @@ class QwenLanguageModel {
1357
1504
  };
1358
1505
  }
1359
1506
  if (delta?.content) {
1360
- yield { type: "text-delta", textDelta: delta.content };
1507
+ const content = String(delta.content);
1508
+ if (cumulativeTranslation && !content.startsWith(translationText))
1509
+ throw new ValidationError("Qwen translation replaced already streamed text.");
1510
+ const textDelta = cumulativeTranslation ? content.slice(translationText.length) : content;
1511
+ translationText = content;
1512
+ if (textDelta)
1513
+ yield { type: "text-delta", textDelta };
1361
1514
  }
1362
1515
  for (const toolCall of delta?.tool_calls ?? []) {
1363
1516
  const index = Number(toolCall.index ?? 0);
@@ -1418,52 +1571,6 @@ class QwenLanguageModel {
1418
1571
  })();
1419
1572
  }
1420
1573
  }
1421
- class QwenEmbeddingModel {
1422
- modelId;
1423
- apiKey;
1424
- baseURL;
1425
- fetcher;
1426
- provider = "qwen";
1427
- capabilities = embeddingCapabilities;
1428
- constructor(modelId, apiKey, baseURL, fetcher) {
1429
- this.modelId = modelId;
1430
- this.apiKey = apiKey;
1431
- this.baseURL = baseURL;
1432
- this.fetcher = fetcher;
1433
- }
1434
- async embed(input) {
1435
- const { signal, cleanup } = withTimeoutSignal(input);
1436
- const values = input.values.map((value) => {
1437
- if (typeof value !== "string") {
1438
- throw new UnsupportedFeatureError('Provider "qwen" does not support multimodal embedding values.');
1439
- }
1440
- return value;
1441
- });
1442
- try {
1443
- const response = await withRetry(() => this.fetcher(`${this.baseURL}/embeddings`, {
1444
- method: "POST",
1445
- headers: jsonHeaders(this.apiKey),
1446
- signal,
1447
- body: JSON.stringify({
1448
- model: this.modelId,
1449
- input: values
1450
- })
1451
- }), input);
1452
- const json = await parseJson(response);
1453
- return {
1454
- embeddings: json.data.map((entry) => entry.embedding),
1455
- usage: {
1456
- inputTokens: json.usage?.prompt_tokens,
1457
- totalTokens: json.usage?.total_tokens
1458
- },
1459
- rawResponse: json
1460
- };
1461
- }
1462
- finally {
1463
- cleanup();
1464
- }
1465
- }
1466
- }
1467
1574
  const toMultimodalEmbeddingContent = (value) => {
1468
1575
  if (typeof value === "string") {
1469
1576
  return { text: value };
@@ -1919,7 +2026,6 @@ class QwenImageGenerationModel {
1919
2026
  this.fetcher = fetcher;
1920
2027
  }
1921
2028
  async generateImage(input) {
1922
- const { signal, cleanup } = withTimeoutSignal(input);
1923
2029
  const providerOptions = { ...(input.providerOptions ?? {}) };
1924
2030
  delete providerOptions.endpoint;
1925
2031
  const nestedParameters = providerOptions.parameters && typeof providerOptions.parameters === "object"
@@ -1928,18 +2034,41 @@ class QwenImageGenerationModel {
1928
2034
  delete providerOptions.parameters;
1929
2035
  delete providerOptions.model;
1930
2036
  delete providerOptions.input;
1931
- const endpoint = `${this.taskBaseURL}/services/aigc/multimodal-generation/generation`;
1932
- const sizeFromAspectRatio = {
2037
+ const vidu = this.modelId === "vidu/vidu-image_reference2image";
2038
+ const endpoint = `${this.taskBaseURL}/services/aigc/${vidu ? "image-generation" : "multimodal-generation"}/generation`;
2039
+ const sizeFromAspectRatio = vidu ? {
2040
+ "1:1": "1024*1024", "16:9": "1920*1088", "9:16": "1088*1920", "4:3": "1024*768", "3:4": "768*1024"
2041
+ } : {
1933
2042
  "1:1": "2048*2048",
1934
2043
  "16:9": "1664*928",
1935
2044
  "9:16": "928*1664",
1936
2045
  "4:3": "1472*1104",
1937
2046
  "3:4": "1104*1472"
1938
2047
  };
2048
+ const parameters = { ...providerOptions, ...nestedParameters };
2049
+ if (input.count !== undefined)
2050
+ parameters.n = input.count;
2051
+ if (input.size !== undefined)
2052
+ parameters.size = input.size;
2053
+ else if (input.aspectRatio) {
2054
+ if (!sizeFromAspectRatio[input.aspectRatio])
2055
+ throw new ConfigurationError("Unsupported image aspect ratio.");
2056
+ parameters.size = sizeFromAspectRatio[input.aspectRatio];
2057
+ }
2058
+ if (input.negativePrompt !== undefined)
2059
+ parameters.negative_prompt = input.negativePrompt;
2060
+ if (/^qwen-image-3\.0(?:$|-)/.test(this.modelId))
2061
+ validateQwenImage30(input, parameters);
2062
+ if (vidu) {
2063
+ parameters.n ??= 1;
2064
+ validateViduImage(input, parameters);
2065
+ }
2066
+ const { signal, cleanup } = withTimeoutSignal(input);
1939
2067
  try {
1940
2068
  const response = await withRetry(() => this.fetcher(endpoint, {
1941
2069
  method: "POST",
1942
- headers: jsonHeaders(this.apiKey),
2070
+ headers: { ...jsonHeaders(this.apiKey), ...(vidu ? { "X-DashScope-Async": "enable" } : {}) },
2071
+ redirect: "error",
1943
2072
  signal,
1944
2073
  body: JSON.stringify({
1945
2074
  model: this.modelId,
@@ -1954,24 +2083,25 @@ class QwenImageGenerationModel {
1954
2083
  }
1955
2084
  ]
1956
2085
  },
1957
- parameters: {
1958
- ...providerOptions,
1959
- ...nestedParameters,
1960
- n: input.count,
1961
- size: input.size ?? (input.aspectRatio ? sizeFromAspectRatio[input.aspectRatio] : undefined),
1962
- negative_prompt: input.negativePrompt
1963
- }
2086
+ parameters
1964
2087
  })
1965
- }), input);
2088
+ }), { ...input, maxRetries: input.maxRetries ?? 0 });
1966
2089
  const json = await parseJson(response);
2090
+ if (json.code || json.output?.task_status === "FAILED")
2091
+ throw new ProviderHTTPError("Qwen media generation failed.", response.status, { responseBody: { code: json.code ?? json.output?.code } });
1967
2092
  const data = json.output?.choices?.flatMap((choice) => choice.message?.content ?? []) ??
1968
2093
  json.output?.results ??
1969
2094
  json.output?.images ??
1970
2095
  json.data ??
1971
2096
  [];
2097
+ if (vidu && (typeof json.output?.task_id !== "string" || !json.output.task_id))
2098
+ throw new ValidationError("Vidu image submission returned no task ID.");
2099
+ if (/^qwen-image-3\.0(?:$|-)/.test(this.modelId) && (!Array.isArray(data) || !data.length))
2100
+ throw new ValidationError("Qwen Image 3.0 returned no images.");
1972
2101
  return {
1973
2102
  images: data.map((item) => normalizeGeneratedMedia(item, input.outputMimeType ?? "image/png")),
1974
2103
  text: json.output?.text,
2104
+ ...(vidu ? { operationName: json.output?.task_id } : {}),
1975
2105
  rawResponse: json
1976
2106
  };
1977
2107
  }
@@ -1994,7 +2124,6 @@ class QwenVideoGenerationModel {
1994
2124
  this.fetcher = fetcher;
1995
2125
  }
1996
2126
  async generateVideo(input) {
1997
- const { signal, cleanup } = withTimeoutSignal(input);
1998
2127
  const providerOptions = { ...(input.providerOptions ?? {}) };
1999
2128
  delete providerOptions.endpoint;
2000
2129
  const nestedInput = providerOptions.input && typeof providerOptions.input === "object"
@@ -2006,38 +2135,60 @@ class QwenVideoGenerationModel {
2006
2135
  delete providerOptions.input;
2007
2136
  delete providerOptions.parameters;
2008
2137
  delete providerOptions.model;
2009
- const isWan27 = /^wan2\.7(?:$|-)/i.test(this.modelId);
2138
+ const happyHorseEdit = /^happyhorse-.*-video-edit$/.test(this.modelId);
2139
+ const media = input.image
2140
+ ? happyHorseEdit
2141
+ ? [...(Array.isArray(nestedInput.media) ? nestedInput.media : []), { type: "reference_image", url: mediaValue(input.image) }]
2142
+ : [{ type: this.modelId.startsWith("vidu/") ? "image" : "first_frame", url: mediaValue(input.image) }]
2143
+ : nestedInput.media;
2144
+ let parameters = { ...providerOptions, ...nestedParameters };
2145
+ if (input.aspectRatio !== undefined)
2146
+ parameters.ratio = input.aspectRatio;
2147
+ if (input.durationSeconds !== undefined)
2148
+ parameters.duration = input.durationSeconds;
2149
+ if (input.count !== undefined)
2150
+ parameters.n = input.count;
2151
+ if (input.outputStorageUri !== undefined)
2152
+ parameters.output_storage_uri = input.outputStorageUri;
2153
+ const usesMedia = /^(?:wan(?:2\.7|3\.0)(?:$|-)|happyhorse-|vidu\/)/i.test(this.modelId);
2154
+ if (/^happyhorse-/.test(this.modelId))
2155
+ validateHappyHorse(this.modelId, media, parameters);
2156
+ if (/^wan3\.0-/.test(this.modelId))
2157
+ validateWan30(media, parameters);
2158
+ if (this.modelId.startsWith("vidu/"))
2159
+ parameters = prepareViduVideo(this.modelId, media, parameters);
2160
+ const { signal, cleanup } = withTimeoutSignal(input);
2010
2161
  try {
2011
2162
  const response = await withRetry(() => this.fetcher(`${this.taskBaseURL}/services/aigc/video-generation/video-synthesis`, {
2012
2163
  method: "POST",
2013
2164
  headers: { ...jsonHeaders(this.apiKey), "X-DashScope-Async": "enable" },
2165
+ redirect: "error",
2014
2166
  signal,
2015
2167
  body: JSON.stringify({
2016
2168
  model: this.modelId,
2017
2169
  input: {
2018
2170
  ...nestedInput,
2019
2171
  prompt: input.prompt,
2020
- negative_prompt: input.negativePrompt,
2172
+ negative_prompt: input.negativePrompt ?? nestedInput.negative_prompt,
2021
2173
  ...(input.image
2022
- ? isWan27
2023
- ? { media: [{ type: "first_frame", url: mediaValue(input.image) }] }
2174
+ ? usesMedia
2175
+ ? { media }
2024
2176
  : { img_url: mediaValue(input.image) }
2025
2177
  : {})
2026
2178
  },
2027
- parameters: {
2028
- ...providerOptions,
2029
- ...nestedParameters,
2030
- ratio: input.aspectRatio,
2031
- duration: input.durationSeconds,
2032
- n: input.count,
2033
- output_storage_uri: input.outputStorageUri
2034
- }
2179
+ parameters
2035
2180
  })
2036
- }), input);
2181
+ }), { ...input, maxRetries: input.maxRetries ?? 0 });
2037
2182
  const json = await parseJson(response);
2183
+ if (json.code || json.output?.task_status === "FAILED")
2184
+ throw new ProviderHTTPError("Qwen media generation failed.", response.status, { responseBody: { code: json.code ?? json.output?.code } });
2038
2185
  const items = json.output?.results ??
2039
2186
  json.output?.videos ??
2040
2187
  (json.output?.video_url ? [{ url: json.output.video_url }] : []);
2188
+ if (/^(?:wan3\.0-|happyhorse-|vidu\/)/.test(this.modelId) && !items.length &&
2189
+ (typeof (json.output?.task_id ?? json.task_id) !== "string" || !(json.output?.task_id ?? json.task_id))) {
2190
+ throw new ValidationError("Qwen video submission returned neither output nor a task ID.");
2191
+ }
2041
2192
  return {
2042
2193
  videos: items.map((item) => normalizeGeneratedMedia(item, "video/mp4")),
2043
2194
  operationName: json.output?.task_id ?? json.task_id,
@@ -2270,6 +2421,12 @@ class QwenRealtimeModel {
2270
2421
  connectionFactory;
2271
2422
  provider = "qwen";
2272
2423
  get capabilities() {
2424
+ if (isQwenCloudRealtime(this.modelId))
2425
+ return {
2426
+ ...realtimeCapabilities, vision: !isQwenAudioRealtime(this.modelId), webSearch: true,
2427
+ agentCapabilities: { ...realtimeCapabilities.agentCapabilities, hostedWebSearch: true, remoteMcp: isQwenOmni38Realtime(this.modelId) },
2428
+ realtime: { ...realtimeCapabilities.realtime, imageInput: !isQwenAudioRealtime(this.modelId) }
2429
+ };
2273
2430
  if (!isLiveTranslate38(this.modelId))
2274
2431
  return realtimeCapabilities;
2275
2432
  return { ...realtimeCapabilities, tools: false, realtime: { ...realtimeCapabilities.realtime, tools: false } };
@@ -2282,9 +2439,14 @@ class QwenRealtimeModel {
2282
2439
  }
2283
2440
  async connect(config = {}, options) {
2284
2441
  const liveTranslate = isLiveTranslate38(this.modelId);
2285
- const mapSession = liveTranslate ? liveTranslateSession : mapRealtimeSession;
2442
+ const cloudRealtime = isQwenCloudRealtime(this.modelId);
2443
+ const mapSession = liveTranslate ? liveTranslateSession : cloudRealtime
2444
+ ? (value) => mapQwenCloudRealtimeSession(this.modelId, value, mapRealtimeSession(value))
2445
+ : mapRealtimeSession;
2286
2446
  mapSession(config);
2287
2447
  let audioSent = false;
2448
+ let responseActive = false;
2449
+ const pendingMcpApprovals = new Set();
2288
2450
  const imageTimes = [];
2289
2451
  const unsupported = () => { throw new UnsupportedFeatureError("LiveTranslate accepts streaming audio and images, not conversation commands."); };
2290
2452
  const url = appendQuery(this.realtimeURL, { model: this.modelId });
@@ -2297,15 +2459,35 @@ class QwenRealtimeModel {
2297
2459
  capabilities: this.capabilities,
2298
2460
  config,
2299
2461
  connection,
2300
- initializationTimeoutMs: liveTranslate ? (options?.timeoutMs ?? 15000) : undefined,
2462
+ initializationTimeoutMs: liveTranslate || cloudRealtime ? (options?.timeoutMs ?? 15000) : undefined,
2301
2463
  callbacks: {
2302
2464
  ...(liveTranslate ? {
2303
2465
  isReadyPayload: (payload) => payload.type === "session.updated",
2304
2466
  isCloseAcknowledgementPayload: (payload) => payload.type === "session.finished"
2305
2467
  } : {}),
2306
- parseEvent: (payload) => parseRealtimeEvent(payload, () => `qwen-realtime-tool-${fallbackRealtimeToolCallIndex++}`, seenRealtimeToolCallIds, liveTranslate),
2468
+ ...(cloudRealtime ? { isReadyPayload: (payload) => payload.type === "session.updated" } : {}),
2469
+ parseEvent: (payload) => {
2470
+ if (payload.type === "response.created")
2471
+ responseActive = true;
2472
+ if (payload.type === "response.done")
2473
+ responseActive = false;
2474
+ const item = payload.item;
2475
+ if (cloudRealtime && payload.type === "conversation.item.created" && item?.type === "mcp_approval_request" && typeof item.id === "string")
2476
+ pendingMcpApprovals.add(item.id);
2477
+ return (cloudRealtime ? qwenCloudAudioEvent(payload, session.config) : undefined) ?? parseRealtimeEvent(payload, () => `qwen-realtime-tool-${fallbackRealtimeToolCallIndex++}`, seenRealtimeToolCallIds, liveTranslate || cloudRealtime);
2478
+ },
2307
2479
  buildInitialPayloads: (value) => [{ type: "session.update", session: mapSession(value) }],
2308
2480
  buildAudioPayloads: (frame, sessionConfig) => {
2481
+ if (cloudRealtime) {
2482
+ validateQwenCloudAudioFrame(frame, sessionConfig);
2483
+ audioSent = true;
2484
+ const manual = sessionConfig.turnDetection === null;
2485
+ return [
2486
+ { type: "input_audio_buffer.append", audio: encodeRealtimeData(frame.data) },
2487
+ ...(frame.isFinal && manual ? [{ type: "input_audio_buffer.commit" }] : []),
2488
+ ...(frame.isFinal && manual && (sessionConfig.autoResponse ?? true) ? [{ type: "response.create" }] : [])
2489
+ ];
2490
+ }
2309
2491
  if (liveTranslate) {
2310
2492
  if (frame.mediaType !== "audio/pcm" || (frame.sampleRateHz !== undefined && frame.sampleRateHz !== 16000) || (frame.channels !== undefined && frame.channels !== 1))
2311
2493
  throw new ConfigurationError("LiveTranslate input must be mono 16000 Hz PCM16.");
@@ -2323,6 +2505,8 @@ class QwenRealtimeModel {
2323
2505
  ];
2324
2506
  },
2325
2507
  buildMediaPayloads: (frame) => {
2508
+ if (isQwenAudioRealtime(this.modelId))
2509
+ throw new UnsupportedFeatureError("Qwen Audio Realtime does not accept image or video frames.");
2326
2510
  if (!/^image\/(jpeg|jpg)$/i.test(frame.mediaType)) {
2327
2511
  throw new UnsupportedFeatureError("Qwen realtime media frames must be JPEG images.");
2328
2512
  }
@@ -2348,13 +2532,25 @@ class QwenRealtimeModel {
2348
2532
  { type: "conversation.item.create", item: { type: "function_call_output", call_id: result.toolCallId, output: JSON.stringify(result.isError ? result.error : result.output ?? null) } },
2349
2533
  ...((sessionConfig.autoResponse ?? true) ? [{ type: "response.create" }] : [])
2350
2534
  ],
2351
- buildUpdatePayloads: (value) => [{ type: "session.update", session: mapSession(value) }],
2535
+ buildUpdatePayloads: (value) => {
2536
+ if (cloudRealtime && responseActive && JSON.stringify(value.providerOptions?.mcpServers) !== JSON.stringify(session.config.providerOptions?.mcpServers))
2537
+ throw new ConfigurationError("MCP configuration cannot change during an active response.");
2538
+ if (cloudRealtime && audioSent && (JSON.stringify(value.turnDetection) !== JSON.stringify(session.config.turnDetection) ||
2539
+ JSON.stringify(value.providerOptions?.audio) !== JSON.stringify(session.config.providerOptions?.audio) ||
2540
+ JSON.stringify(value.providerOptions?.video) !== JSON.stringify(session.config.providerOptions?.video) ||
2541
+ value.channels !== session.config.channels || value.inputSampleRateHz !== session.config.inputSampleRateHz ||
2542
+ value.outputSampleRateHz !== session.config.outputSampleRateHz || value.outputAudioMediaType !== session.config.outputAudioMediaType))
2543
+ throw new ConfigurationError("Qwen realtime audio/video formats and turn detection must be set before audio starts.");
2544
+ if (isQwenAudioRealtime(this.modelId) && value.voice !== session.config.voice)
2545
+ throw new ConfigurationError("Qwen Audio Realtime voice is fixed after the initial session update.");
2546
+ return [{ type: "session.update", session: mapSession(value) }];
2547
+ },
2352
2548
  buildInterruptPayloads: () => liveTranslate ? unsupported() : [{ type: "response.cancel" }],
2353
2549
  buildClosePayloads: () => liveTranslate ? [{ type: "session.finish" }] : []
2354
2550
  }
2355
2551
  });
2356
2552
  await session.initialize();
2357
- return session;
2553
+ return extendQwenCloudRealtimeSession(session, connection, pendingMcpApprovals);
2358
2554
  }
2359
2555
  }
2360
2556
  export const createQwen = (options = {}) => {
@@ -2378,7 +2574,7 @@ export const createQwen = (options = {}) => {
2378
2574
  }).toString().replace(/\/+$/, "");
2379
2575
  const realtimeURL = assertTrustedEndpoint(options.realtimeURL ??
2380
2576
  workspaceURLs?.realtimeURL ??
2381
- "wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime", {
2577
+ `${new URL(baseURL).protocol === "https:" ? "wss:" : "ws:"}//${new URL(baseURL).host}/api-ws/v1/realtime`, {
2382
2578
  label: "Qwen realtimeURL",
2383
2579
  protocols: ["wss"],
2384
2580
  allowedHosts: options.realtimeURL
@@ -2386,6 +2582,13 @@ export const createQwen = (options = {}) => {
2386
2582
  : [new URL(baseURL).hostname, "dashscope-intl.aliyuncs.com"],
2387
2583
  allowUnsafe: options.allowUnsafeEndpoints
2388
2584
  }).toString();
2585
+ const realtimeEndpoint = new URL(realtimeURL);
2586
+ const inferenceURL = assertTrustedEndpoint(options.inferenceURL ??
2587
+ (/\/realtime$/.test(realtimeEndpoint.pathname)
2588
+ ? `${realtimeEndpoint.origin}${realtimeEndpoint.pathname.replace(/\/realtime$/, "/inference")}`
2589
+ : `${realtimeEndpoint.origin}/api-ws/v1/inference`), {
2590
+ label: "Qwen inferenceURL", protocols: ["wss"], allowedHosts: [new URL(baseURL).hostname], allowUnsafe: options.allowUnsafeEndpoints
2591
+ }).toString();
2389
2592
  const fetcher = options.fetch ?? globalThis.fetch;
2390
2593
  const responseLimits = resolveAudioResponseLimits(options.responseLimits);
2391
2594
  const speechAudioMaxRedirects = options.speechAudioMaxRedirects ?? 3;
@@ -2395,13 +2598,28 @@ export const createQwen = (options = {}) => {
2395
2598
  return createProviderAdapter({
2396
2599
  name: "qwen",
2397
2600
  languageModel: (modelId) => {
2601
+ assertQwenLanguageModel(modelId);
2398
2602
  if (isLiveTranslate38(modelId))
2399
2603
  throw new UnsupportedFeatureError("Qwen 3.8 LiveTranslate requires realtimeModel(), not languageModel().");
2400
2604
  return new QwenLanguageModel(modelId, apiKey, baseURL, fetcher);
2401
2605
  },
2402
- embeddingModel: (modelId) => new QwenEmbeddingModel(modelId, apiKey, baseURL, fetcher),
2403
- transcriptionModel: (modelId) => new QwenTranscriptionModel(modelId, apiKey, baseURL, fetcher, responseLimits),
2404
- speechModel: (modelId) => new QwenSpeechModel(modelId, apiKey, taskBaseURL, fetcher, responseLimits, options.speechAudioURLValidator, speechAudioMaxRedirects),
2606
+ embeddingModel: (modelId) => createQwenTextEmbeddingModel(modelId, apiKey, baseURL, taskBaseURL, fetcher),
2607
+ decisionModel: (modelId = "decision-model-preview") => createQwenDecisionModel(modelId, { apiKey, baseURL: options.decisionBaseURL ?? baseURL, fetch: fetcher, allowUnsafeEndpoints: options.allowUnsafeEndpoints }),
2608
+ textEmbeddingModel: (modelId) => createQwenTextEmbeddingModel(modelId, apiKey, baseURL, taskBaseURL, fetcher),
2609
+ fileTranscriptionModel: (modelId) => createQwenFileTranscriptionModel(modelId, { apiKey, taskBaseURL, fetch: fetcher, allowUnsafeEndpoints: options.allowUnsafeEndpoints }),
2610
+ transcriptionModel: (modelId) => {
2611
+ if (/filetrans/.test(modelId))
2612
+ throw new UnsupportedFeatureError("Use fileTranscriptionModel() for asynchronous file transcription.");
2613
+ if (modelId === "qwen-audio-3.0-asr-flash")
2614
+ return createQwenNativeTranscriptionModel(modelId, { apiKey, taskBaseURL, fetch: fetcher, allowUnsafeEndpoints: options.allowUnsafeEndpoints });
2615
+ return isQwenCloudStreamingASR(modelId)
2616
+ ? new QwenCloudStreamingASRModel(modelId, apiKey, inferenceURL, options.realtimeConnectionFactory)
2617
+ : new QwenTranscriptionModel(modelId, apiKey, baseURL, fetcher, responseLimits);
2618
+ },
2619
+ streamingASRModel: (modelId) => new QwenCloudStreamingASRModel(modelId, apiKey, inferenceURL, options.realtimeConnectionFactory),
2620
+ speechModel: (modelId) => isQwenCloudSpeech(modelId)
2621
+ ? new QwenCloudSpeechModel(modelId, apiKey, inferenceURL, options.realtimeConnectionFactory)
2622
+ : new QwenSpeechModel(modelId, apiKey, taskBaseURL, fetcher, responseLimits, options.speechAudioURLValidator, speechAudioMaxRedirects),
2405
2623
  imageGenerationModel: (modelId) => new QwenImageGenerationModel(modelId, apiKey, taskBaseURL, fetcher),
2406
2624
  videoGenerationModel: (modelId) => new QwenVideoGenerationModel(modelId, apiKey, taskBaseURL, fetcher),
2407
2625
  realtimeModel: (modelId) => new QwenRealtimeModel(modelId, apiKey, realtimeURL, options.realtimeConnectionFactory),
@@ -2411,6 +2629,9 @@ export const createQwen = (options = {}) => {
2411
2629
  multimodalEmbeddingModel: (modelId) => new QwenMultimodalEmbeddingModelImpl(modelId, apiKey, taskBaseURL, fetcher),
2412
2630
  tasks: new QwenTasksClientImpl(apiKey, taskBaseURL, fetcher),
2413
2631
  voices: createQwenVoicesClient(apiKey, taskBaseURL, fetcher),
2632
+ imageTranslationModel: (modelId = "qwen-mt-image-2.0") => createQwenImageTranslationModel(modelId, apiKey, taskBaseURL, fetcher),
2633
+ worlds: (modelId) => createQwenWorldsClient(modelId, apiKey, taskBaseURL, fetcher),
2634
+ temporaryKeys: createQwenTemporaryKeysClient(apiKey, taskBaseURL, fetcher),
2414
2635
  rawFetch: fetcher
2415
2636
  });
2416
2637
  };