@zhivex-ai/qwen 0.15.3 → 0.16.1

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 +12 -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 +183 -88
  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);
@@ -1154,6 +1171,7 @@ class QwenLanguageModel {
1154
1171
  }
1155
1172
  const providerOptions = { ...(input.providerOptions ?? {}) };
1156
1173
  validateThirdParty(this.modelId, input, providerOptions);
1174
+ validateQwenSpecializedText(this.modelId, input);
1157
1175
  validateQwenToolStream(this.modelId, providerOptions, false);
1158
1176
  if (isQwen38ReasoningModel(this.modelId)) {
1159
1177
  validateQwen38Request(this.modelId, input, providerOptions);
@@ -1254,6 +1272,7 @@ class QwenLanguageModel {
1254
1272
  async stream(input) {
1255
1273
  const providerOptions = { ...(input.providerOptions ?? {}) };
1256
1274
  validateThirdParty(this.modelId, input, providerOptions);
1275
+ validateQwenSpecializedText(this.modelId, input);
1257
1276
  validateQwenToolStream(this.modelId, providerOptions, true);
1258
1277
  if (isQwen38ReasoningModel(this.modelId)) {
1259
1278
  validateQwen38Request(this.modelId, input, providerOptions);
@@ -1325,11 +1344,13 @@ class QwenLanguageModel {
1325
1344
  ...mapChatReasoning(this.modelId, input, providerOptions)
1326
1345
  })
1327
1346
  }), { ...input, abortSignal: signal }, "Qwen").catch((error) => { cleanup(); throw error; });
1347
+ const cumulativeTranslation = /^(?:qwen-mt-plus|qwen-mt-turbo)$/.test(this.modelId);
1328
1348
  return (async function* () {
1329
1349
  try {
1330
1350
  const toolBuffers = new Map();
1331
1351
  const seenIds = existingToolCallIds(input.messages);
1332
1352
  const fallbackGeneration = nextFallbackToolCallGeneration("chat", input, seenIds);
1353
+ let translationText = "";
1333
1354
  let lastFinishReason;
1334
1355
  let lastUsage;
1335
1356
  const failure = (reason) => new ProviderToolCallError({ provider: "qwen", transport: "chat", diagnosticCode: "QWEN_CHAT_TOOL_CALL_INVALID",
@@ -1357,7 +1378,13 @@ class QwenLanguageModel {
1357
1378
  };
1358
1379
  }
1359
1380
  if (delta?.content) {
1360
- yield { type: "text-delta", textDelta: delta.content };
1381
+ const content = String(delta.content);
1382
+ if (cumulativeTranslation && !content.startsWith(translationText))
1383
+ throw new ValidationError("Qwen translation replaced already streamed text.");
1384
+ const textDelta = cumulativeTranslation ? content.slice(translationText.length) : content;
1385
+ translationText = content;
1386
+ if (textDelta)
1387
+ yield { type: "text-delta", textDelta };
1361
1388
  }
1362
1389
  for (const toolCall of delta?.tool_calls ?? []) {
1363
1390
  const index = Number(toolCall.index ?? 0);
@@ -1418,52 +1445,6 @@ class QwenLanguageModel {
1418
1445
  })();
1419
1446
  }
1420
1447
  }
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
1448
  const toMultimodalEmbeddingContent = (value) => {
1468
1449
  if (typeof value === "string") {
1469
1450
  return { text: value };
@@ -1919,7 +1900,6 @@ class QwenImageGenerationModel {
1919
1900
  this.fetcher = fetcher;
1920
1901
  }
1921
1902
  async generateImage(input) {
1922
- const { signal, cleanup } = withTimeoutSignal(input);
1923
1903
  const providerOptions = { ...(input.providerOptions ?? {}) };
1924
1904
  delete providerOptions.endpoint;
1925
1905
  const nestedParameters = providerOptions.parameters && typeof providerOptions.parameters === "object"
@@ -1928,18 +1908,41 @@ class QwenImageGenerationModel {
1928
1908
  delete providerOptions.parameters;
1929
1909
  delete providerOptions.model;
1930
1910
  delete providerOptions.input;
1931
- const endpoint = `${this.taskBaseURL}/services/aigc/multimodal-generation/generation`;
1932
- const sizeFromAspectRatio = {
1911
+ const vidu = this.modelId === "vidu/vidu-image_reference2image";
1912
+ const endpoint = `${this.taskBaseURL}/services/aigc/${vidu ? "image-generation" : "multimodal-generation"}/generation`;
1913
+ const sizeFromAspectRatio = vidu ? {
1914
+ "1:1": "1024*1024", "16:9": "1920*1088", "9:16": "1088*1920", "4:3": "1024*768", "3:4": "768*1024"
1915
+ } : {
1933
1916
  "1:1": "2048*2048",
1934
1917
  "16:9": "1664*928",
1935
1918
  "9:16": "928*1664",
1936
1919
  "4:3": "1472*1104",
1937
1920
  "3:4": "1104*1472"
1938
1921
  };
1922
+ const parameters = { ...providerOptions, ...nestedParameters };
1923
+ if (input.count !== undefined)
1924
+ parameters.n = input.count;
1925
+ if (input.size !== undefined)
1926
+ parameters.size = input.size;
1927
+ else if (input.aspectRatio) {
1928
+ if (!sizeFromAspectRatio[input.aspectRatio])
1929
+ throw new ConfigurationError("Unsupported image aspect ratio.");
1930
+ parameters.size = sizeFromAspectRatio[input.aspectRatio];
1931
+ }
1932
+ if (input.negativePrompt !== undefined)
1933
+ parameters.negative_prompt = input.negativePrompt;
1934
+ if (/^qwen-image-3\.0(?:$|-)/.test(this.modelId))
1935
+ validateQwenImage30(input, parameters);
1936
+ if (vidu) {
1937
+ parameters.n ??= 1;
1938
+ validateViduImage(input, parameters);
1939
+ }
1940
+ const { signal, cleanup } = withTimeoutSignal(input);
1939
1941
  try {
1940
1942
  const response = await withRetry(() => this.fetcher(endpoint, {
1941
1943
  method: "POST",
1942
- headers: jsonHeaders(this.apiKey),
1944
+ headers: { ...jsonHeaders(this.apiKey), ...(vidu ? { "X-DashScope-Async": "enable" } : {}) },
1945
+ redirect: "error",
1943
1946
  signal,
1944
1947
  body: JSON.stringify({
1945
1948
  model: this.modelId,
@@ -1954,24 +1957,25 @@ class QwenImageGenerationModel {
1954
1957
  }
1955
1958
  ]
1956
1959
  },
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
- }
1960
+ parameters
1964
1961
  })
1965
- }), input);
1962
+ }), { ...input, maxRetries: input.maxRetries ?? 0 });
1966
1963
  const json = await parseJson(response);
1964
+ if (json.code || json.output?.task_status === "FAILED")
1965
+ throw new ProviderHTTPError("Qwen media generation failed.", response.status, { responseBody: { code: json.code ?? json.output?.code } });
1967
1966
  const data = json.output?.choices?.flatMap((choice) => choice.message?.content ?? []) ??
1968
1967
  json.output?.results ??
1969
1968
  json.output?.images ??
1970
1969
  json.data ??
1971
1970
  [];
1971
+ if (vidu && (typeof json.output?.task_id !== "string" || !json.output.task_id))
1972
+ throw new ValidationError("Vidu image submission returned no task ID.");
1973
+ if (/^qwen-image-3\.0(?:$|-)/.test(this.modelId) && (!Array.isArray(data) || !data.length))
1974
+ throw new ValidationError("Qwen Image 3.0 returned no images.");
1972
1975
  return {
1973
1976
  images: data.map((item) => normalizeGeneratedMedia(item, input.outputMimeType ?? "image/png")),
1974
1977
  text: json.output?.text,
1978
+ ...(vidu ? { operationName: json.output?.task_id } : {}),
1975
1979
  rawResponse: json
1976
1980
  };
1977
1981
  }
@@ -1994,7 +1998,6 @@ class QwenVideoGenerationModel {
1994
1998
  this.fetcher = fetcher;
1995
1999
  }
1996
2000
  async generateVideo(input) {
1997
- const { signal, cleanup } = withTimeoutSignal(input);
1998
2001
  const providerOptions = { ...(input.providerOptions ?? {}) };
1999
2002
  delete providerOptions.endpoint;
2000
2003
  const nestedInput = providerOptions.input && typeof providerOptions.input === "object"
@@ -2006,38 +2009,60 @@ class QwenVideoGenerationModel {
2006
2009
  delete providerOptions.input;
2007
2010
  delete providerOptions.parameters;
2008
2011
  delete providerOptions.model;
2009
- const isWan27 = /^wan2\.7(?:$|-)/i.test(this.modelId);
2012
+ const happyHorseEdit = /^happyhorse-.*-video-edit$/.test(this.modelId);
2013
+ const media = input.image
2014
+ ? happyHorseEdit
2015
+ ? [...(Array.isArray(nestedInput.media) ? nestedInput.media : []), { type: "reference_image", url: mediaValue(input.image) }]
2016
+ : [{ type: this.modelId.startsWith("vidu/") ? "image" : "first_frame", url: mediaValue(input.image) }]
2017
+ : nestedInput.media;
2018
+ let parameters = { ...providerOptions, ...nestedParameters };
2019
+ if (input.aspectRatio !== undefined)
2020
+ parameters.ratio = input.aspectRatio;
2021
+ if (input.durationSeconds !== undefined)
2022
+ parameters.duration = input.durationSeconds;
2023
+ if (input.count !== undefined)
2024
+ parameters.n = input.count;
2025
+ if (input.outputStorageUri !== undefined)
2026
+ parameters.output_storage_uri = input.outputStorageUri;
2027
+ const usesMedia = /^(?:wan(?:2\.7|3\.0)(?:$|-)|happyhorse-|vidu\/)/i.test(this.modelId);
2028
+ if (/^happyhorse-/.test(this.modelId))
2029
+ validateHappyHorse(this.modelId, media, parameters);
2030
+ if (/^wan3\.0-/.test(this.modelId))
2031
+ validateWan30(media, parameters);
2032
+ if (this.modelId.startsWith("vidu/"))
2033
+ parameters = prepareViduVideo(this.modelId, media, parameters);
2034
+ const { signal, cleanup } = withTimeoutSignal(input);
2010
2035
  try {
2011
2036
  const response = await withRetry(() => this.fetcher(`${this.taskBaseURL}/services/aigc/video-generation/video-synthesis`, {
2012
2037
  method: "POST",
2013
2038
  headers: { ...jsonHeaders(this.apiKey), "X-DashScope-Async": "enable" },
2039
+ redirect: "error",
2014
2040
  signal,
2015
2041
  body: JSON.stringify({
2016
2042
  model: this.modelId,
2017
2043
  input: {
2018
2044
  ...nestedInput,
2019
2045
  prompt: input.prompt,
2020
- negative_prompt: input.negativePrompt,
2046
+ negative_prompt: input.negativePrompt ?? nestedInput.negative_prompt,
2021
2047
  ...(input.image
2022
- ? isWan27
2023
- ? { media: [{ type: "first_frame", url: mediaValue(input.image) }] }
2048
+ ? usesMedia
2049
+ ? { media }
2024
2050
  : { img_url: mediaValue(input.image) }
2025
2051
  : {})
2026
2052
  },
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
- }
2053
+ parameters
2035
2054
  })
2036
- }), input);
2055
+ }), { ...input, maxRetries: input.maxRetries ?? 0 });
2037
2056
  const json = await parseJson(response);
2057
+ if (json.code || json.output?.task_status === "FAILED")
2058
+ throw new ProviderHTTPError("Qwen media generation failed.", response.status, { responseBody: { code: json.code ?? json.output?.code } });
2038
2059
  const items = json.output?.results ??
2039
2060
  json.output?.videos ??
2040
2061
  (json.output?.video_url ? [{ url: json.output.video_url }] : []);
2062
+ if (/^(?:wan3\.0-|happyhorse-|vidu\/)/.test(this.modelId) && !items.length &&
2063
+ (typeof (json.output?.task_id ?? json.task_id) !== "string" || !(json.output?.task_id ?? json.task_id))) {
2064
+ throw new ValidationError("Qwen video submission returned neither output nor a task ID.");
2065
+ }
2041
2066
  return {
2042
2067
  videos: items.map((item) => normalizeGeneratedMedia(item, "video/mp4")),
2043
2068
  operationName: json.output?.task_id ?? json.task_id,
@@ -2270,6 +2295,12 @@ class QwenRealtimeModel {
2270
2295
  connectionFactory;
2271
2296
  provider = "qwen";
2272
2297
  get capabilities() {
2298
+ if (isQwenCloudRealtime(this.modelId))
2299
+ return {
2300
+ ...realtimeCapabilities, vision: !isQwenAudioRealtime(this.modelId), webSearch: true,
2301
+ agentCapabilities: { ...realtimeCapabilities.agentCapabilities, hostedWebSearch: true, remoteMcp: isQwenOmni38Realtime(this.modelId) },
2302
+ realtime: { ...realtimeCapabilities.realtime, imageInput: !isQwenAudioRealtime(this.modelId) }
2303
+ };
2273
2304
  if (!isLiveTranslate38(this.modelId))
2274
2305
  return realtimeCapabilities;
2275
2306
  return { ...realtimeCapabilities, tools: false, realtime: { ...realtimeCapabilities.realtime, tools: false } };
@@ -2282,9 +2313,14 @@ class QwenRealtimeModel {
2282
2313
  }
2283
2314
  async connect(config = {}, options) {
2284
2315
  const liveTranslate = isLiveTranslate38(this.modelId);
2285
- const mapSession = liveTranslate ? liveTranslateSession : mapRealtimeSession;
2316
+ const cloudRealtime = isQwenCloudRealtime(this.modelId);
2317
+ const mapSession = liveTranslate ? liveTranslateSession : cloudRealtime
2318
+ ? (value) => mapQwenCloudRealtimeSession(this.modelId, value, mapRealtimeSession(value))
2319
+ : mapRealtimeSession;
2286
2320
  mapSession(config);
2287
2321
  let audioSent = false;
2322
+ let responseActive = false;
2323
+ const pendingMcpApprovals = new Set();
2288
2324
  const imageTimes = [];
2289
2325
  const unsupported = () => { throw new UnsupportedFeatureError("LiveTranslate accepts streaming audio and images, not conversation commands."); };
2290
2326
  const url = appendQuery(this.realtimeURL, { model: this.modelId });
@@ -2297,15 +2333,35 @@ class QwenRealtimeModel {
2297
2333
  capabilities: this.capabilities,
2298
2334
  config,
2299
2335
  connection,
2300
- initializationTimeoutMs: liveTranslate ? (options?.timeoutMs ?? 15000) : undefined,
2336
+ initializationTimeoutMs: liveTranslate || cloudRealtime ? (options?.timeoutMs ?? 15000) : undefined,
2301
2337
  callbacks: {
2302
2338
  ...(liveTranslate ? {
2303
2339
  isReadyPayload: (payload) => payload.type === "session.updated",
2304
2340
  isCloseAcknowledgementPayload: (payload) => payload.type === "session.finished"
2305
2341
  } : {}),
2306
- parseEvent: (payload) => parseRealtimeEvent(payload, () => `qwen-realtime-tool-${fallbackRealtimeToolCallIndex++}`, seenRealtimeToolCallIds, liveTranslate),
2342
+ ...(cloudRealtime ? { isReadyPayload: (payload) => payload.type === "session.updated" } : {}),
2343
+ parseEvent: (payload) => {
2344
+ if (payload.type === "response.created")
2345
+ responseActive = true;
2346
+ if (payload.type === "response.done")
2347
+ responseActive = false;
2348
+ const item = payload.item;
2349
+ if (cloudRealtime && payload.type === "conversation.item.created" && item?.type === "mcp_approval_request" && typeof item.id === "string")
2350
+ pendingMcpApprovals.add(item.id);
2351
+ return (cloudRealtime ? qwenCloudAudioEvent(payload, session.config) : undefined) ?? parseRealtimeEvent(payload, () => `qwen-realtime-tool-${fallbackRealtimeToolCallIndex++}`, seenRealtimeToolCallIds, liveTranslate || cloudRealtime);
2352
+ },
2307
2353
  buildInitialPayloads: (value) => [{ type: "session.update", session: mapSession(value) }],
2308
2354
  buildAudioPayloads: (frame, sessionConfig) => {
2355
+ if (cloudRealtime) {
2356
+ validateQwenCloudAudioFrame(frame, sessionConfig);
2357
+ audioSent = true;
2358
+ const manual = sessionConfig.turnDetection === null;
2359
+ return [
2360
+ { type: "input_audio_buffer.append", audio: encodeRealtimeData(frame.data) },
2361
+ ...(frame.isFinal && manual ? [{ type: "input_audio_buffer.commit" }] : []),
2362
+ ...(frame.isFinal && manual && (sessionConfig.autoResponse ?? true) ? [{ type: "response.create" }] : [])
2363
+ ];
2364
+ }
2309
2365
  if (liveTranslate) {
2310
2366
  if (frame.mediaType !== "audio/pcm" || (frame.sampleRateHz !== undefined && frame.sampleRateHz !== 16000) || (frame.channels !== undefined && frame.channels !== 1))
2311
2367
  throw new ConfigurationError("LiveTranslate input must be mono 16000 Hz PCM16.");
@@ -2323,6 +2379,8 @@ class QwenRealtimeModel {
2323
2379
  ];
2324
2380
  },
2325
2381
  buildMediaPayloads: (frame) => {
2382
+ if (isQwenAudioRealtime(this.modelId))
2383
+ throw new UnsupportedFeatureError("Qwen Audio Realtime does not accept image or video frames.");
2326
2384
  if (!/^image\/(jpeg|jpg)$/i.test(frame.mediaType)) {
2327
2385
  throw new UnsupportedFeatureError("Qwen realtime media frames must be JPEG images.");
2328
2386
  }
@@ -2348,13 +2406,25 @@ class QwenRealtimeModel {
2348
2406
  { type: "conversation.item.create", item: { type: "function_call_output", call_id: result.toolCallId, output: JSON.stringify(result.isError ? result.error : result.output ?? null) } },
2349
2407
  ...((sessionConfig.autoResponse ?? true) ? [{ type: "response.create" }] : [])
2350
2408
  ],
2351
- buildUpdatePayloads: (value) => [{ type: "session.update", session: mapSession(value) }],
2409
+ buildUpdatePayloads: (value) => {
2410
+ if (cloudRealtime && responseActive && JSON.stringify(value.providerOptions?.mcpServers) !== JSON.stringify(session.config.providerOptions?.mcpServers))
2411
+ throw new ConfigurationError("MCP configuration cannot change during an active response.");
2412
+ if (cloudRealtime && audioSent && (JSON.stringify(value.turnDetection) !== JSON.stringify(session.config.turnDetection) ||
2413
+ JSON.stringify(value.providerOptions?.audio) !== JSON.stringify(session.config.providerOptions?.audio) ||
2414
+ JSON.stringify(value.providerOptions?.video) !== JSON.stringify(session.config.providerOptions?.video) ||
2415
+ value.channels !== session.config.channels || value.inputSampleRateHz !== session.config.inputSampleRateHz ||
2416
+ value.outputSampleRateHz !== session.config.outputSampleRateHz || value.outputAudioMediaType !== session.config.outputAudioMediaType))
2417
+ throw new ConfigurationError("Qwen realtime audio/video formats and turn detection must be set before audio starts.");
2418
+ if (isQwenAudioRealtime(this.modelId) && value.voice !== session.config.voice)
2419
+ throw new ConfigurationError("Qwen Audio Realtime voice is fixed after the initial session update.");
2420
+ return [{ type: "session.update", session: mapSession(value) }];
2421
+ },
2352
2422
  buildInterruptPayloads: () => liveTranslate ? unsupported() : [{ type: "response.cancel" }],
2353
2423
  buildClosePayloads: () => liveTranslate ? [{ type: "session.finish" }] : []
2354
2424
  }
2355
2425
  });
2356
2426
  await session.initialize();
2357
- return session;
2427
+ return extendQwenCloudRealtimeSession(session, connection, pendingMcpApprovals);
2358
2428
  }
2359
2429
  }
2360
2430
  export const createQwen = (options = {}) => {
@@ -2378,7 +2448,7 @@ export const createQwen = (options = {}) => {
2378
2448
  }).toString().replace(/\/+$/, "");
2379
2449
  const realtimeURL = assertTrustedEndpoint(options.realtimeURL ??
2380
2450
  workspaceURLs?.realtimeURL ??
2381
- "wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime", {
2451
+ `${new URL(baseURL).protocol === "https:" ? "wss:" : "ws:"}//${new URL(baseURL).host}/api-ws/v1/realtime`, {
2382
2452
  label: "Qwen realtimeURL",
2383
2453
  protocols: ["wss"],
2384
2454
  allowedHosts: options.realtimeURL
@@ -2386,6 +2456,13 @@ export const createQwen = (options = {}) => {
2386
2456
  : [new URL(baseURL).hostname, "dashscope-intl.aliyuncs.com"],
2387
2457
  allowUnsafe: options.allowUnsafeEndpoints
2388
2458
  }).toString();
2459
+ const realtimeEndpoint = new URL(realtimeURL);
2460
+ const inferenceURL = assertTrustedEndpoint(options.inferenceURL ??
2461
+ (/\/realtime$/.test(realtimeEndpoint.pathname)
2462
+ ? `${realtimeEndpoint.origin}${realtimeEndpoint.pathname.replace(/\/realtime$/, "/inference")}`
2463
+ : `${realtimeEndpoint.origin}/api-ws/v1/inference`), {
2464
+ label: "Qwen inferenceURL", protocols: ["wss"], allowedHosts: [new URL(baseURL).hostname], allowUnsafe: options.allowUnsafeEndpoints
2465
+ }).toString();
2389
2466
  const fetcher = options.fetch ?? globalThis.fetch;
2390
2467
  const responseLimits = resolveAudioResponseLimits(options.responseLimits);
2391
2468
  const speechAudioMaxRedirects = options.speechAudioMaxRedirects ?? 3;
@@ -2395,13 +2472,28 @@ export const createQwen = (options = {}) => {
2395
2472
  return createProviderAdapter({
2396
2473
  name: "qwen",
2397
2474
  languageModel: (modelId) => {
2475
+ assertQwenLanguageModel(modelId);
2398
2476
  if (isLiveTranslate38(modelId))
2399
2477
  throw new UnsupportedFeatureError("Qwen 3.8 LiveTranslate requires realtimeModel(), not languageModel().");
2400
2478
  return new QwenLanguageModel(modelId, apiKey, baseURL, fetcher);
2401
2479
  },
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),
2480
+ embeddingModel: (modelId) => createQwenTextEmbeddingModel(modelId, apiKey, baseURL, taskBaseURL, fetcher),
2481
+ decisionModel: (modelId = "decision-model-preview") => createQwenDecisionModel(modelId, { apiKey, baseURL: options.decisionBaseURL ?? baseURL, fetch: fetcher, allowUnsafeEndpoints: options.allowUnsafeEndpoints }),
2482
+ textEmbeddingModel: (modelId) => createQwenTextEmbeddingModel(modelId, apiKey, baseURL, taskBaseURL, fetcher),
2483
+ fileTranscriptionModel: (modelId) => createQwenFileTranscriptionModel(modelId, { apiKey, taskBaseURL, fetch: fetcher, allowUnsafeEndpoints: options.allowUnsafeEndpoints }),
2484
+ transcriptionModel: (modelId) => {
2485
+ if (/filetrans/.test(modelId))
2486
+ throw new UnsupportedFeatureError("Use fileTranscriptionModel() for asynchronous file transcription.");
2487
+ if (modelId === "qwen-audio-3.0-asr-flash")
2488
+ return createQwenNativeTranscriptionModel(modelId, { apiKey, taskBaseURL, fetch: fetcher, allowUnsafeEndpoints: options.allowUnsafeEndpoints });
2489
+ return isQwenCloudStreamingASR(modelId)
2490
+ ? new QwenCloudStreamingASRModel(modelId, apiKey, inferenceURL, options.realtimeConnectionFactory)
2491
+ : new QwenTranscriptionModel(modelId, apiKey, baseURL, fetcher, responseLimits);
2492
+ },
2493
+ streamingASRModel: (modelId) => new QwenCloudStreamingASRModel(modelId, apiKey, inferenceURL, options.realtimeConnectionFactory),
2494
+ speechModel: (modelId) => isQwenCloudSpeech(modelId)
2495
+ ? new QwenCloudSpeechModel(modelId, apiKey, inferenceURL, options.realtimeConnectionFactory)
2496
+ : new QwenSpeechModel(modelId, apiKey, taskBaseURL, fetcher, responseLimits, options.speechAudioURLValidator, speechAudioMaxRedirects),
2405
2497
  imageGenerationModel: (modelId) => new QwenImageGenerationModel(modelId, apiKey, taskBaseURL, fetcher),
2406
2498
  videoGenerationModel: (modelId) => new QwenVideoGenerationModel(modelId, apiKey, taskBaseURL, fetcher),
2407
2499
  realtimeModel: (modelId) => new QwenRealtimeModel(modelId, apiKey, realtimeURL, options.realtimeConnectionFactory),
@@ -2411,6 +2503,9 @@ export const createQwen = (options = {}) => {
2411
2503
  multimodalEmbeddingModel: (modelId) => new QwenMultimodalEmbeddingModelImpl(modelId, apiKey, taskBaseURL, fetcher),
2412
2504
  tasks: new QwenTasksClientImpl(apiKey, taskBaseURL, fetcher),
2413
2505
  voices: createQwenVoicesClient(apiKey, taskBaseURL, fetcher),
2506
+ imageTranslationModel: (modelId = "qwen-mt-image-2.0") => createQwenImageTranslationModel(modelId, apiKey, taskBaseURL, fetcher),
2507
+ worlds: (modelId) => createQwenWorldsClient(modelId, apiKey, taskBaseURL, fetcher),
2508
+ temporaryKeys: createQwenTemporaryKeysClient(apiKey, taskBaseURL, fetcher),
2414
2509
  rawFetch: fetcher
2415
2510
  });
2416
2511
  };