@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.
- package/README.md +12 -1
- package/dist/browser-realtime.d.ts +58 -0
- package/dist/browser-realtime.d.ts.map +1 -0
- package/dist/browser-realtime.js +259 -0
- package/dist/browser-realtime.js.map +1 -0
- package/dist/browser.d.ts +9 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +7 -0
- package/dist/browser.js.map +1 -0
- package/dist/cloud-media.d.ts +89 -0
- package/dist/cloud-media.d.ts.map +1 -0
- package/dist/cloud-media.js +221 -0
- package/dist/cloud-media.js.map +1 -0
- package/dist/cloud-realtime.d.ts +58 -0
- package/dist/cloud-realtime.d.ts.map +1 -0
- package/dist/cloud-realtime.js +123 -0
- package/dist/cloud-realtime.js.map +1 -0
- package/dist/decision.d.ts +69 -0
- package/dist/decision.d.ts.map +1 -0
- package/dist/decision.js +122 -0
- package/dist/decision.js.map +1 -0
- package/dist/embeddings.d.ts +30 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +94 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/index.d.ts +40 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +183 -88
- package/dist/index.js.map +1 -1
- package/dist/inference-audio.d.ts +146 -0
- package/dist/inference-audio.d.ts.map +1 -0
- package/dist/inference-audio.js +256 -0
- package/dist/inference-audio.js.map +1 -0
- package/dist/native-asr.d.ts +52 -0
- package/dist/native-asr.d.ts.map +1 -0
- package/dist/native-asr.js +162 -0
- package/dist/native-asr.js.map +1 -0
- package/dist/rtc-protocol.d.ts +10 -0
- package/dist/rtc-protocol.d.ts.map +1 -0
- package/dist/rtc-protocol.js +10 -0
- package/dist/rtc-protocol.js.map +1 -0
- package/dist/sdp-exchange.d.ts +8 -0
- package/dist/sdp-exchange.d.ts.map +1 -0
- package/dist/sdp-exchange.js +25 -0
- package/dist/sdp-exchange.js.map +1 -0
- package/dist/temporary-keys.d.ts +15 -0
- package/dist/temporary-keys.d.ts.map +1 -0
- package/dist/temporary-keys.js +28 -0
- package/dist/temporary-keys.js.map +1 -0
- package/dist/text-profiles.d.ts +7 -0
- package/dist/text-profiles.d.ts.map +1 -0
- package/dist/text-profiles.js +55 -0
- package/dist/text-profiles.js.map +1 -0
- package/dist/url-path.d.ts +3 -0
- package/dist/url-path.d.ts.map +1 -0
- package/dist/url-path.js +8 -0
- package/dist/url-path.js.map +1 -0
- package/dist/world-rtc.d.ts +30 -0
- package/dist/world-rtc.d.ts.map +1 -0
- package/dist/world-rtc.js +33 -0
- package/dist/world-rtc.js.map +1 -0
- package/dist/worlds.d.ts +134 -0
- package/dist/worlds.d.ts.map +1 -0
- package/dist/worlds.js +196 -0
- package/dist/worlds.js.map +1 -0
- 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))
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1932
|
-
const
|
|
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
|
|
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
|
-
?
|
|
2023
|
-
? { media
|
|
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
|
|
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
|
-
|
|
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) =>
|
|
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
|
|
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) =>
|
|
2403
|
-
|
|
2404
|
-
|
|
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
|
};
|