@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.
- package/README.md +46 -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 +365 -144
- 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);
|
|
@@ -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:
|
|
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
|
|
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
|
-
|
|
1088
|
+
break;
|
|
1019
1089
|
}
|
|
1020
|
-
const json =
|
|
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
|
-
|
|
1123
|
+
done: false
|
|
1054
1124
|
};
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
existing.
|
|
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
|
-
|
|
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
|
-
|
|
1160
|
+
done: false
|
|
1090
1161
|
};
|
|
1091
|
-
existing.
|
|
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
|
-
|
|
1181
|
+
done: false
|
|
1107
1182
|
};
|
|
1108
|
-
existing.
|
|
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
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
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
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
1932
|
-
const
|
|
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
|
|
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
|
-
?
|
|
2023
|
-
? { media
|
|
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
|
|
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
|
-
|
|
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) =>
|
|
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
|
|
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) =>
|
|
2403
|
-
|
|
2404
|
-
|
|
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
|
};
|