@zhivex-ai/qwen 0.16.1 → 0.16.3
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 +99 -2
- package/dist/annotations.d.ts +3 -0
- package/dist/annotations.d.ts.map +1 -0
- package/dist/annotations.js +42 -0
- package/dist/annotations.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +266 -62
- package/dist/index.js.map +1 -1
- package/dist/inference-audio.d.ts +2 -0
- package/dist/inference-audio.d.ts.map +1 -1
- package/dist/search-usage.d.ts +3 -0
- package/dist/search-usage.d.ts.map +1 -0
- package/dist/search-usage.js +11 -0
- package/dist/search-usage.js.map +1 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { qwenSearchUsage } from "./search-usage.js";
|
|
2
|
+
import { createAnnotationCollector } from "./annotations.js";
|
|
1
3
|
export { createQwenSDPExchange } from "./sdp-exchange.js";
|
|
2
4
|
export { connectQwenWebRTC, connectQwenAOQ } from "./browser-realtime.js";
|
|
3
5
|
import { createQwenTemporaryKeysClient } from "./temporary-keys.js";
|
|
@@ -683,7 +685,37 @@ const hasHostedTools = (tools) => Object.values(tools ?? {}).some((tool) => !isC
|
|
|
683
685
|
const hasMessagePart = (messages, type) => messages.some((message) => message.parts.some((part) => part.type === type));
|
|
684
686
|
const hasVideoInput = (messages) => messages.some((message) => message.parts.some((part) => part.type === "file" && part.mediaType.toLowerCase().startsWith("video/")));
|
|
685
687
|
const hasNonVideoFileInput = (messages) => messages.some((message) => message.parts.some((part) => part.type === "file" && !part.mediaType.toLowerCase().startsWith("video/")));
|
|
688
|
+
// Only documented Responses families may send a bounded output request.
|
|
689
|
+
const responsesOutputLimitFamilies = new Set([
|
|
690
|
+
"qwen3.8-max", "qwen3.8-flash", "qwen3.8-omni-flash", "qwen3.8-2.4t-a95b", "qwen3.8-27b",
|
|
691
|
+
"qwen3.7-max", "qwen3.7-max-preview", "qwen3.7-plus", "qwen3.7-flash",
|
|
692
|
+
"qwen3.6-plus", "qwen3.6-flash", "qwen3.6-35b-a3b", "qwen3-max",
|
|
693
|
+
"qwen3.5-plus", "qwen3.5-flash", "qwen3.5-397b-a17b", "qwen3.5-122b-a10b", "qwen3.5-27b", "qwen3.5-35b-a3b"
|
|
694
|
+
]);
|
|
695
|
+
const supportsResponsesOutputLimit = (modelId) => responsesOutputLimitFamilies.has(modelId.toLowerCase().replace(/-\d{4}(?:-\d{2}-\d{2})?$/, "")) ||
|
|
696
|
+
thirdPartyProfile(modelId)?.responses === true;
|
|
697
|
+
const responsesOutputLimit = (modelId, input, options) => {
|
|
698
|
+
const value = input.maxTokens ?? options.max_output_tokens;
|
|
699
|
+
if (input.maxTokens !== undefined && options.max_output_tokens !== undefined && input.maxTokens !== options.max_output_tokens) {
|
|
700
|
+
throw new ConfigurationError("Qwen maxTokens and max_output_tokens must agree.");
|
|
701
|
+
}
|
|
702
|
+
if (value === undefined)
|
|
703
|
+
return undefined;
|
|
704
|
+
if (!supportsResponsesOutputLimit(modelId))
|
|
705
|
+
throw new UnsupportedFeatureError(`Qwen Responses output limits are not supported for model "${modelId}".`);
|
|
706
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 16) {
|
|
707
|
+
throw new ConfigurationError("Qwen Responses maxTokens/max_output_tokens must be a safe integer of at least 16.");
|
|
708
|
+
}
|
|
709
|
+
return value;
|
|
710
|
+
};
|
|
686
711
|
const resolveApiMode = (modelId, requestedMode, input, providerOptions) => {
|
|
712
|
+
if (providerOptions.max_tool_calls !== undefined)
|
|
713
|
+
throw new UnsupportedFeatureError("Qwen max_tool_calls is not verified as an effective hosted-tool limit.");
|
|
714
|
+
for (const tool of Object.values(input.tools ?? {})) {
|
|
715
|
+
if (!isCallableToolDefinition(tool) && tool.type === "web_search" && tool.config && typeof tool.config === "object" &&
|
|
716
|
+
("max_tool_calls" in tool.config || "max_uses" in tool.config))
|
|
717
|
+
throw new UnsupportedFeatureError("Qwen web_search tool-call limits are not verified on Responses.");
|
|
718
|
+
}
|
|
687
719
|
if (isQwenTranslation(modelId) || isQwenCharacter(modelId))
|
|
688
720
|
return "chat";
|
|
689
721
|
const videoInput = hasVideoInput(input.messages);
|
|
@@ -700,15 +732,16 @@ const resolveApiMode = (modelId, requestedMode, input, providerOptions) => {
|
|
|
700
732
|
throw new UnsupportedFeatureError('QwenCloud glm-5.2 callable tools require apiMode "chat" or "auto" because Responses function continuation is unavailable.');
|
|
701
733
|
}
|
|
702
734
|
const needsChat = glmCallableTools ||
|
|
703
|
-
input.maxTokens !== undefined ||
|
|
735
|
+
(input.maxTokens !== undefined && (!supportsResponsesOutputLimit(modelId) ||
|
|
736
|
+
(requestedMode === "auto" && !hasHostedTools(input.tools) && !hasNonVideoFileInput(input.messages) && providerOptions.max_output_tokens === undefined))) ||
|
|
704
737
|
input.reasoning?.budgetTokens !== undefined ||
|
|
705
738
|
providerOptions.thinking_budget !== undefined ||
|
|
706
739
|
providerOptions.tool_stream === true ||
|
|
707
740
|
input.structuredOutput?.mode === "native" ||
|
|
708
741
|
(!omni38 && (hasMessagePart(input.messages, "audio") || videoInput));
|
|
709
|
-
const needsResponses = hasHostedTools(input.tools) || hasNonVideoFileInput(input.messages);
|
|
742
|
+
const needsResponses = hasHostedTools(input.tools) || hasNonVideoFileInput(input.messages) || providerOptions.max_output_tokens !== undefined;
|
|
710
743
|
if (needsChat && needsResponses) {
|
|
711
|
-
throw new UnsupportedFeatureError("Qwen cannot combine Responses-only hosted/file inputs with Chat-only maxTokens, audio, video, reasoning budgets, structured output, or tool streaming.");
|
|
744
|
+
throw new UnsupportedFeatureError("Qwen cannot combine Responses-only hosted/file inputs with Chat-only options (including maxTokens on unsupported models), audio, video, reasoning budgets, structured output, or tool streaming.");
|
|
712
745
|
}
|
|
713
746
|
if (isQwenOmniLanguageModel(modelId)) {
|
|
714
747
|
if (requestedMode === "responses" || needsResponses) {
|
|
@@ -895,7 +928,8 @@ const toResponsesInput = (messages, format, modelId) => {
|
|
|
895
928
|
part.provider === "qwen" &&
|
|
896
929
|
part.data &&
|
|
897
930
|
typeof part.data === "object" &&
|
|
898
|
-
typeof part.data.type === "string"
|
|
931
|
+
typeof part.data.type === "string" &&
|
|
932
|
+
!["response.annotations", "hosted-tool-usage"].includes(String(part.data.type))) {
|
|
899
933
|
input.push(part.data);
|
|
900
934
|
}
|
|
901
935
|
break;
|
|
@@ -927,15 +961,72 @@ class QwenResponseError extends Error {
|
|
|
927
961
|
this.name = "QwenResponseError";
|
|
928
962
|
}
|
|
929
963
|
}
|
|
964
|
+
// Never retain provider arguments or parser causes in diagnostics.
|
|
965
|
+
const qwenResponsesToolCallError = (reason, usage, effectsPossible = false) => new ProviderToolCallError({
|
|
966
|
+
provider: "qwen", transport: "responses", diagnosticCode: "QWEN_RESPONSES_TOOL_CALL_INVALID",
|
|
967
|
+
reason, usage, effectsPossible, retryable: false
|
|
968
|
+
});
|
|
969
|
+
class QwenStreamEventError extends Error {
|
|
970
|
+
transport;
|
|
971
|
+
reason;
|
|
972
|
+
provider = "qwen";
|
|
973
|
+
diagnosticCode = "QWEN_SSE_EVENT_INVALID";
|
|
974
|
+
retryable = false;
|
|
975
|
+
constructor(transport, reason) {
|
|
976
|
+
super("Qwen stream event could not be parsed safely.");
|
|
977
|
+
this.transport = transport;
|
|
978
|
+
this.reason = reason;
|
|
979
|
+
this.name = "QwenStreamEventError";
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
const parseQwenStreamEvent = (data, transport) => {
|
|
983
|
+
let event;
|
|
984
|
+
try {
|
|
985
|
+
event = JSON.parse(data);
|
|
986
|
+
}
|
|
987
|
+
catch {
|
|
988
|
+
throw new QwenStreamEventError(transport, "invalid_json");
|
|
989
|
+
}
|
|
990
|
+
if (!event || typeof event !== "object" || Array.isArray(event) || (transport === "responses" && typeof event.type !== "string")) {
|
|
991
|
+
throw new QwenStreamEventError(transport, "invalid_event");
|
|
992
|
+
}
|
|
993
|
+
return event;
|
|
994
|
+
};
|
|
995
|
+
const parseQwenResponsesArguments = (raw, usage, effectsPossible = false) => {
|
|
996
|
+
const failure = (reason) => qwenResponsesToolCallError(reason, usage, effectsPossible);
|
|
997
|
+
if (typeof raw !== "string")
|
|
998
|
+
throw failure("incomplete_arguments");
|
|
999
|
+
if (!raw.trim())
|
|
1000
|
+
throw failure("empty_arguments");
|
|
1001
|
+
let value;
|
|
1002
|
+
try {
|
|
1003
|
+
value = JSON.parse(raw);
|
|
1004
|
+
}
|
|
1005
|
+
catch {
|
|
1006
|
+
throw failure("invalid_json");
|
|
1007
|
+
}
|
|
1008
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1009
|
+
throw failure("invalid_json");
|
|
1010
|
+
return value;
|
|
1011
|
+
};
|
|
930
1012
|
const parseResponsesAssistantMessage = (json, input) => {
|
|
931
1013
|
if (json.status === "failed" || json.error)
|
|
932
1014
|
throw new QwenResponseError();
|
|
933
1015
|
const parts = [];
|
|
934
1016
|
const seenIds = existingToolCallIds(input.messages);
|
|
935
1017
|
const fallbackGeneration = nextFallbackToolCallGeneration("responses", input, seenIds);
|
|
1018
|
+
const usage = mapResponsesUsage(json.usage);
|
|
1019
|
+
const effectsPossible = Object.values(input.tools ?? {}).some(tool => !isCallableToolDefinition(tool)) || (json.output ?? []).some((item) => parseResponsesProviderData(item) !== undefined);
|
|
1020
|
+
if (json.status === "incomplete" && (json.output ?? []).some((item) => item?.type === "function_call")) {
|
|
1021
|
+
throw qwenResponsesToolCallError("response_incomplete", usage, effectsPossible);
|
|
1022
|
+
}
|
|
1023
|
+
const collectAnnotations = createAnnotationCollector();
|
|
936
1024
|
for (const [index, item] of (json.output ?? []).entries()) {
|
|
937
1025
|
if (item?.type === "message") {
|
|
938
|
-
for (const content of item.content ?? []) {
|
|
1026
|
+
for (const [contentIndex, content] of (item.content ?? []).entries()) {
|
|
1027
|
+
const data = collectAnnotations(content, index, contentIndex, item.id);
|
|
1028
|
+
if (data)
|
|
1029
|
+
parts.push(providerDataPart("qwen", data));
|
|
939
1030
|
if (typeof content?.text === "string" && content.text) {
|
|
940
1031
|
parts.push({ type: "text", text: content.text });
|
|
941
1032
|
}
|
|
@@ -943,6 +1034,9 @@ const parseResponsesAssistantMessage = (json, input) => {
|
|
|
943
1034
|
continue;
|
|
944
1035
|
}
|
|
945
1036
|
if (item?.type === "function_call") {
|
|
1037
|
+
if (typeof item.name !== "string" || !item.name.trim()) {
|
|
1038
|
+
throw qwenResponsesToolCallError("inconsistent_metadata", usage, effectsPossible);
|
|
1039
|
+
}
|
|
946
1040
|
const providerCallId = qwenResponsesProviderCallId(item.call_id);
|
|
947
1041
|
const id = resolveToolCallId([item.call_id, item.id], fallbackToolCallId("responses", fallbackGeneration, index), seenIds);
|
|
948
1042
|
parts.push({
|
|
@@ -950,7 +1044,7 @@ const parseResponsesAssistantMessage = (json, input) => {
|
|
|
950
1044
|
toolCall: {
|
|
951
1045
|
id,
|
|
952
1046
|
name: item.name,
|
|
953
|
-
input:
|
|
1047
|
+
input: parseQwenResponsesArguments(item.arguments, usage, effectsPossible),
|
|
954
1048
|
...(providerCallId !== undefined && providerCallId !== id
|
|
955
1049
|
? { providerMetadata: { qwenResponsesCallId: providerCallId } }
|
|
956
1050
|
: {})
|
|
@@ -981,17 +1075,41 @@ const normalizeResponsesFinishReason = (status, hasToolCalls) => {
|
|
|
981
1075
|
if (status === "completed") {
|
|
982
1076
|
return "stop";
|
|
983
1077
|
}
|
|
1078
|
+
if (status === "incomplete")
|
|
1079
|
+
return "length";
|
|
984
1080
|
if (status === "failed") {
|
|
985
1081
|
return "error";
|
|
986
1082
|
}
|
|
987
1083
|
return normalizeFinishReason(status);
|
|
988
1084
|
};
|
|
989
1085
|
const streamResponses = async function* (response, input) {
|
|
1086
|
+
const attemptId = globalThis.crypto.randomUUID();
|
|
1087
|
+
const searchEnabled = Object.values(input.tools ?? {}).some(t => !isCallableToolDefinition(t) && t.type === "web_search");
|
|
1088
|
+
if (searchEnabled)
|
|
1089
|
+
yield { type: "provider-data", provider: "qwen", data: qwenSearchUsage(attemptId) };
|
|
1090
|
+
const collectAnnotations = createAnnotationCollector();
|
|
990
1091
|
const toolBuffers = new Map();
|
|
991
1092
|
const toolBufferAliases = new Map();
|
|
992
1093
|
const seenIds = existingToolCallIds(input.messages);
|
|
993
1094
|
const fallbackGeneration = nextFallbackToolCallGeneration("responses", input, seenIds);
|
|
994
|
-
let
|
|
1095
|
+
let terminal;
|
|
1096
|
+
let effectsPossible = Object.values(input.tools ?? {}).some(tool => !isCallableToolDefinition(tool));
|
|
1097
|
+
let invalidMetadata = false;
|
|
1098
|
+
const mergeMetadata = (buffer, itemId, callId, name) => {
|
|
1099
|
+
const nextItemId = qwenResponsesProviderCallId(itemId);
|
|
1100
|
+
const nextCallId = qwenResponsesProviderCallId(callId);
|
|
1101
|
+
const nextName = typeof name === "string" && name.trim() ? name : undefined;
|
|
1102
|
+
if ((buffer.itemId !== undefined && nextItemId !== undefined && buffer.itemId !== nextItemId) ||
|
|
1103
|
+
(buffer.providerCallId !== undefined && nextCallId !== undefined && buffer.providerCallId !== nextCallId) ||
|
|
1104
|
+
(buffer.name && nextName !== undefined && buffer.name !== nextName) ||
|
|
1105
|
+
(name !== undefined && typeof name !== "string"))
|
|
1106
|
+
invalidMetadata = true;
|
|
1107
|
+
buffer.itemId ??= nextItemId;
|
|
1108
|
+
buffer.providerCallId ??= nextCallId;
|
|
1109
|
+
buffer.name ||= nextName ?? "";
|
|
1110
|
+
// A receipt ID may arrive after the item ID used as the initial fallback.
|
|
1111
|
+
buffer.callId = stableToolCallId(callId) ?? buffer.callId;
|
|
1112
|
+
};
|
|
995
1113
|
const bufferKey = (outputIndex, candidates, fallbackId) => {
|
|
996
1114
|
const stableIds = candidates
|
|
997
1115
|
.map(stableToolCallId)
|
|
@@ -1010,31 +1128,11 @@ const streamResponses = async function* (response, input) {
|
|
|
1010
1128
|
}
|
|
1011
1129
|
return `fallback:${fallbackId}`;
|
|
1012
1130
|
};
|
|
1013
|
-
const emitToolCall = (key) => {
|
|
1014
|
-
const toolCall = toolBuffers.get(key);
|
|
1015
|
-
if (!toolCall || toolCall.emitted || !toolCall.name) {
|
|
1016
|
-
return undefined;
|
|
1017
|
-
}
|
|
1018
|
-
toolCall.emitted = true;
|
|
1019
|
-
sawToolCalls = true;
|
|
1020
|
-
const id = resolveToolCallId([toolCall.callId], toolCall.fallbackId, seenIds);
|
|
1021
|
-
return {
|
|
1022
|
-
type: "tool-call",
|
|
1023
|
-
toolCall: {
|
|
1024
|
-
id,
|
|
1025
|
-
name: toolCall.name,
|
|
1026
|
-
input: JSON.parse(toolCall.args || "{}"),
|
|
1027
|
-
...(toolCall.providerCallId !== undefined && toolCall.providerCallId !== id
|
|
1028
|
-
? { providerMetadata: { qwenResponsesCallId: toolCall.providerCallId } }
|
|
1029
|
-
: {})
|
|
1030
|
-
}
|
|
1031
|
-
};
|
|
1032
|
-
};
|
|
1033
1131
|
for await (const event of streamSSE(response)) {
|
|
1034
1132
|
if (event.data === "[DONE]") {
|
|
1035
|
-
|
|
1133
|
+
break;
|
|
1036
1134
|
}
|
|
1037
|
-
const json =
|
|
1135
|
+
const json = parseQwenStreamEvent(event.data, "responses");
|
|
1038
1136
|
const type = json.type;
|
|
1039
1137
|
if (type === "error" || type === "response.failed" || json.response?.status === "failed" || json.error) {
|
|
1040
1138
|
throw new QwenResponseError();
|
|
@@ -1055,8 +1153,23 @@ const streamResponses = async function* (response, input) {
|
|
|
1055
1153
|
};
|
|
1056
1154
|
continue;
|
|
1057
1155
|
}
|
|
1156
|
+
input.abortSignal?.throwIfAborted();
|
|
1157
|
+
if (type === "response.content_part.done") {
|
|
1158
|
+
const data = collectAnnotations(json.part, json.output_index, json.content_index, json.item_id);
|
|
1159
|
+
if (data)
|
|
1160
|
+
yield { type: "provider-data", provider: "qwen", data };
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1058
1163
|
if (type === "response.output_item.added" || type === "response.output_item.done") {
|
|
1059
1164
|
const item = json.item;
|
|
1165
|
+
if (type === "response.output_item.done" && item?.type === "message" && Array.isArray(item.content)) {
|
|
1166
|
+
for (const [contentIndex, part] of item.content.entries()) {
|
|
1167
|
+
input.abortSignal?.throwIfAborted();
|
|
1168
|
+
const data = collectAnnotations(part, json.output_index, contentIndex, item.id);
|
|
1169
|
+
if (data)
|
|
1170
|
+
yield { type: "provider-data", provider: "qwen", data };
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1060
1173
|
if (item?.type === "function_call") {
|
|
1061
1174
|
const outputIndex = json.output_index ?? toolBuffers.size;
|
|
1062
1175
|
const fallbackId = fallbackToolCallId("responses", fallbackGeneration, outputIndex);
|
|
@@ -1067,23 +1180,24 @@ const streamResponses = async function* (response, input) {
|
|
|
1067
1180
|
fallbackId,
|
|
1068
1181
|
name: item.name ?? "",
|
|
1069
1182
|
args: "",
|
|
1070
|
-
|
|
1183
|
+
done: false
|
|
1071
1184
|
};
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
existing.
|
|
1185
|
+
if (item.arguments !== undefined && typeof item.arguments !== "string")
|
|
1186
|
+
invalidMetadata = true;
|
|
1187
|
+
if (existing.done && typeof item.arguments === "string" && item.arguments !== existing.args)
|
|
1188
|
+
invalidMetadata = true;
|
|
1189
|
+
mergeMetadata(existing, item.id ?? json.item_id, item.call_id, item.name);
|
|
1075
1190
|
if (typeof item.arguments === "string") {
|
|
1076
1191
|
existing.args = item.arguments;
|
|
1077
1192
|
}
|
|
1078
1193
|
toolBuffers.set(key, existing);
|
|
1079
1194
|
if (type === "response.output_item.done") {
|
|
1080
|
-
|
|
1081
|
-
if (emitted) {
|
|
1082
|
-
yield emitted;
|
|
1083
|
-
}
|
|
1195
|
+
existing.done = true;
|
|
1084
1196
|
}
|
|
1085
1197
|
}
|
|
1086
1198
|
const providerData = parseResponsesProviderData(item);
|
|
1199
|
+
if (providerData)
|
|
1200
|
+
effectsPossible = true;
|
|
1087
1201
|
if (providerData && type === "response.output_item.done") {
|
|
1088
1202
|
yield {
|
|
1089
1203
|
type: "provider-data",
|
|
@@ -1103,9 +1217,13 @@ const streamResponses = async function* (response, input) {
|
|
|
1103
1217
|
fallbackId,
|
|
1104
1218
|
name: "",
|
|
1105
1219
|
args: "",
|
|
1106
|
-
|
|
1220
|
+
done: false
|
|
1107
1221
|
};
|
|
1108
|
-
existing.
|
|
1222
|
+
mergeMetadata(existing, json.item_id, json.call_id);
|
|
1223
|
+
if (existing.done)
|
|
1224
|
+
invalidMetadata = true;
|
|
1225
|
+
if (typeof json.delta !== "string")
|
|
1226
|
+
invalidMetadata = true;
|
|
1109
1227
|
existing.args += typeof json.delta === "string" ? json.delta : "";
|
|
1110
1228
|
toolBuffers.set(key, existing);
|
|
1111
1229
|
continue;
|
|
@@ -1120,36 +1238,115 @@ const streamResponses = async function* (response, input) {
|
|
|
1120
1238
|
fallbackId,
|
|
1121
1239
|
name: "",
|
|
1122
1240
|
args: "",
|
|
1123
|
-
|
|
1241
|
+
done: false
|
|
1124
1242
|
};
|
|
1125
|
-
existing.
|
|
1243
|
+
mergeMetadata(existing, json.item_id, json.call_id);
|
|
1244
|
+
if (json.arguments !== undefined && typeof json.arguments !== "string")
|
|
1245
|
+
invalidMetadata = true;
|
|
1246
|
+
if (existing.done && typeof json.arguments === "string" && json.arguments !== existing.args)
|
|
1247
|
+
invalidMetadata = true;
|
|
1126
1248
|
if (typeof json.arguments === "string") {
|
|
1127
1249
|
existing.args = json.arguments;
|
|
1128
1250
|
}
|
|
1251
|
+
existing.done = true;
|
|
1129
1252
|
toolBuffers.set(key, existing);
|
|
1130
|
-
const emitted = emitToolCall(key);
|
|
1131
|
-
if (emitted) {
|
|
1132
|
-
yield emitted;
|
|
1133
|
-
}
|
|
1134
1253
|
continue;
|
|
1135
1254
|
}
|
|
1136
1255
|
if (type === "response.completed" || type === "response.incomplete") {
|
|
1137
|
-
const
|
|
1138
|
-
if (
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1256
|
+
const next = { ...(json.response ?? {}), status: type === "response.incomplete" ? "incomplete" : json.response?.status };
|
|
1257
|
+
if (terminal && JSON.stringify(terminal) !== JSON.stringify(next))
|
|
1258
|
+
invalidMetadata = true;
|
|
1259
|
+
terminal = next;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
// Some servers supply the final items only in the terminal snapshot. When
|
|
1263
|
+
// both forms exist, neither may silently overwrite a contradictory call.
|
|
1264
|
+
if (Array.isArray(terminal?.output)) {
|
|
1265
|
+
const terminalKeys = new Set();
|
|
1266
|
+
for (const [index, item] of terminal.output.entries()) {
|
|
1267
|
+
input.abortSignal?.throwIfAborted();
|
|
1268
|
+
if (item?.type === "message" && Array.isArray(item.content)) {
|
|
1269
|
+
for (const [contentIndex, part] of item.content.entries()) {
|
|
1270
|
+
input.abortSignal?.throwIfAborted();
|
|
1271
|
+
const data = collectAnnotations(part, index, contentIndex, item.id);
|
|
1272
|
+
if (data)
|
|
1273
|
+
yield { type: "provider-data", provider: "qwen", data };
|
|
1274
|
+
}
|
|
1144
1275
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1276
|
+
if (parseResponsesProviderData(item))
|
|
1277
|
+
effectsPossible = true;
|
|
1278
|
+
if (item?.type !== "function_call")
|
|
1279
|
+
continue;
|
|
1280
|
+
const fallbackId = fallbackToolCallId("responses", fallbackGeneration, index);
|
|
1281
|
+
const priorKey = [item.id, item.call_id]
|
|
1282
|
+
.map(stableToolCallId)
|
|
1283
|
+
.filter((id) => id !== undefined)
|
|
1284
|
+
.map(id => toolBufferAliases.get(`id:${id}`) ?? `id:${id}`)
|
|
1285
|
+
.find(key => toolBuffers.has(key));
|
|
1286
|
+
const positionalKey = `output:${index}`;
|
|
1287
|
+
const key = toolBuffers.has(positionalKey) ? positionalKey : priorKey ?? bufferKey(index, [item.id, item.call_id], fallbackId);
|
|
1288
|
+
terminalKeys.add(key);
|
|
1289
|
+
const existing = toolBuffers.get(key);
|
|
1290
|
+
if (existing) {
|
|
1291
|
+
if (existing.done && existing.args !== item.arguments)
|
|
1292
|
+
invalidMetadata = true;
|
|
1293
|
+
mergeMetadata(existing, item.id, item.call_id, item.name);
|
|
1294
|
+
}
|
|
1295
|
+
if (item.arguments !== undefined && typeof item.arguments !== "string")
|
|
1296
|
+
invalidMetadata = true;
|
|
1297
|
+
toolBuffers.set(key, {
|
|
1298
|
+
itemId: existing?.itemId ?? qwenResponsesProviderCallId(item.id),
|
|
1299
|
+
callId: stableToolCallId(item.call_id) ?? existing?.callId ?? stableToolCallId(item.id),
|
|
1300
|
+
providerCallId: qwenResponsesProviderCallId(item.call_id) ?? existing?.providerCallId,
|
|
1301
|
+
fallbackId: existing?.fallbackId ?? fallbackId,
|
|
1302
|
+
name: item.name,
|
|
1303
|
+
args: typeof item.arguments === "string" ? item.arguments : "",
|
|
1304
|
+
done: true
|
|
1305
|
+
});
|
|
1151
1306
|
}
|
|
1152
|
-
|
|
1307
|
+
if ([...toolBuffers.keys()].some(key => !terminalKeys.has(key)))
|
|
1308
|
+
invalidMetadata = true;
|
|
1309
|
+
}
|
|
1310
|
+
// No callable event escapes until the full response batch has been checked.
|
|
1311
|
+
// Text/provider events may already have been observed; hosted effects cannot be undone.
|
|
1312
|
+
input.abortSignal?.throwIfAborted();
|
|
1313
|
+
const usage = mapResponsesUsage(terminal?.usage);
|
|
1314
|
+
const failure = (reason) => qwenResponsesToolCallError(reason, usage, effectsPossible);
|
|
1315
|
+
if (!terminal)
|
|
1316
|
+
throw failure("stream_truncated");
|
|
1317
|
+
if (invalidMetadata)
|
|
1318
|
+
throw failure("inconsistent_metadata");
|
|
1319
|
+
if (searchEnabled || terminal.usage?.x_tools?.web_search !== undefined)
|
|
1320
|
+
yield { type: "provider-data", provider: "qwen", data: qwenSearchUsage(attemptId, terminal) };
|
|
1321
|
+
if (toolBuffers.size && terminal.status !== "completed")
|
|
1322
|
+
throw failure("response_incomplete");
|
|
1323
|
+
const materialized = [];
|
|
1324
|
+
for (const call of toolBuffers.values()) {
|
|
1325
|
+
if (!call.done)
|
|
1326
|
+
throw failure("incomplete_arguments");
|
|
1327
|
+
if (typeof call.name !== "string" || !call.name.trim())
|
|
1328
|
+
throw failure("inconsistent_metadata");
|
|
1329
|
+
const args = parseQwenResponsesArguments(call.args, usage, effectsPossible);
|
|
1330
|
+
const id = resolveToolCallId([call.callId], call.fallbackId, seenIds);
|
|
1331
|
+
materialized.push({ type: "tool-call", toolCall: {
|
|
1332
|
+
id, name: call.name, input: args,
|
|
1333
|
+
...(call.providerCallId !== undefined && call.providerCallId !== id
|
|
1334
|
+
? { providerMetadata: { qwenResponsesCallId: call.providerCallId } } : {})
|
|
1335
|
+
} });
|
|
1336
|
+
}
|
|
1337
|
+
for (const event of materialized) {
|
|
1338
|
+
input.abortSignal?.throwIfAborted();
|
|
1339
|
+
yield event;
|
|
1340
|
+
}
|
|
1341
|
+
if (typeof terminal.id === "string") {
|
|
1342
|
+
yield { type: "provider-data", provider: "qwen", data: { responseId: terminal.id } };
|
|
1343
|
+
}
|
|
1344
|
+
yield {
|
|
1345
|
+
type: "finish",
|
|
1346
|
+
finishReason: normalizeResponsesFinishReason(terminal.status, materialized.length > 0),
|
|
1347
|
+
providerFinishReason: terminal.status,
|
|
1348
|
+
usage
|
|
1349
|
+
};
|
|
1153
1350
|
};
|
|
1154
1351
|
class QwenLanguageModel {
|
|
1155
1352
|
modelId;
|
|
@@ -1163,7 +1360,7 @@ class QwenLanguageModel {
|
|
|
1163
1360
|
this.apiKey = apiKey;
|
|
1164
1361
|
this.baseURL = baseURL;
|
|
1165
1362
|
this.fetcher = fetcher;
|
|
1166
|
-
this.capabilities = { ...qwenLanguageCapabilities(modelId), toolHistory: thirdPartyProfile(modelId)?.thinkingOnly || isQwen38MaxPreview(modelId) || /thinking|qwq/i.test(modelId) ? undefined : "json" };
|
|
1363
|
+
this.capabilities = { ...qwenLanguageCapabilities(modelId), hostedTools: qwenLanguageCapabilities(modelId).webSearch ? [{ route: "responses", tool: "web_search", limit: "unverified", metering: "provider-counter" }] : [], toolHistory: thirdPartyProfile(modelId)?.thinkingOnly || isQwen38MaxPreview(modelId) || /thinking|qwq/i.test(modelId) ? undefined : "json" };
|
|
1167
1364
|
}
|
|
1168
1365
|
async generate(input) {
|
|
1169
1366
|
if (isQwenOmniLanguageModel(this.modelId)) {
|
|
@@ -1184,6 +1381,7 @@ class QwenLanguageModel {
|
|
|
1184
1381
|
}
|
|
1185
1382
|
const apiMode = resolveApiMode(this.modelId, providerOptions.apiMode ?? "auto", input, providerOptions);
|
|
1186
1383
|
delete providerOptions.apiMode;
|
|
1384
|
+
const outputLimit = apiMode === "responses" ? responsesOutputLimit(this.modelId, input, providerOptions) : undefined;
|
|
1187
1385
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1188
1386
|
try {
|
|
1189
1387
|
if (apiMode === "responses") {
|
|
@@ -1201,6 +1399,7 @@ class QwenLanguageModel {
|
|
|
1201
1399
|
signal,
|
|
1202
1400
|
body: JSON.stringify({
|
|
1203
1401
|
...responseProviderOptions,
|
|
1402
|
+
max_output_tokens: outputLimit,
|
|
1204
1403
|
model: this.modelId,
|
|
1205
1404
|
...(previousResponse ? { previous_response_id: previousResponse.responseId } : {}),
|
|
1206
1405
|
...(messages.length ? { input: toResponsesInput(messages, input.toolResultFormat, this.modelId) } : {}),
|
|
@@ -1213,6 +1412,9 @@ class QwenLanguageModel {
|
|
|
1213
1412
|
}), { ...input, abortSignal: signal }, "Qwen");
|
|
1214
1413
|
const json = await parseJson(response);
|
|
1215
1414
|
const assistantMessage = parseResponsesAssistantMessage(json, input);
|
|
1415
|
+
if (Object.values(input.tools ?? {}).some(t => !isCallableToolDefinition(t) && t.type === "web_search") || json.usage?.x_tools?.web_search !== undefined) {
|
|
1416
|
+
assistantMessage.parts.push(providerDataPart("qwen", qwenSearchUsage(globalThis.crypto.randomUUID(), json)));
|
|
1417
|
+
}
|
|
1216
1418
|
const hasToolCalls = assistantMessage.parts.some((part) => part.type === "tool-call");
|
|
1217
1419
|
return {
|
|
1218
1420
|
messages: [assistantMessage],
|
|
@@ -1285,6 +1487,7 @@ class QwenLanguageModel {
|
|
|
1285
1487
|
}
|
|
1286
1488
|
const apiMode = resolveApiMode(this.modelId, providerOptions.apiMode ?? "auto", input, providerOptions);
|
|
1287
1489
|
delete providerOptions.apiMode;
|
|
1490
|
+
const outputLimit = apiMode === "responses" ? responsesOutputLimit(this.modelId, input, providerOptions) : undefined;
|
|
1288
1491
|
const { signal, cleanup } = withTimeoutSignal(input);
|
|
1289
1492
|
if (apiMode === "responses") {
|
|
1290
1493
|
const previousResponse = getProviderResponseId(input.messages);
|
|
@@ -1301,6 +1504,7 @@ class QwenLanguageModel {
|
|
|
1301
1504
|
signal,
|
|
1302
1505
|
body: JSON.stringify({
|
|
1303
1506
|
...responseProviderOptions,
|
|
1507
|
+
max_output_tokens: outputLimit,
|
|
1304
1508
|
model: this.modelId,
|
|
1305
1509
|
...(previousResponse ? { previous_response_id: previousResponse.responseId } : {}),
|
|
1306
1510
|
...(messages.length ? { input: toResponsesInput(messages, input.toolResultFormat, this.modelId) } : {}),
|
|
@@ -1313,7 +1517,7 @@ class QwenLanguageModel {
|
|
|
1313
1517
|
}), { ...input, abortSignal: signal }, "Qwen").catch((error) => { cleanup(); throw error; });
|
|
1314
1518
|
return (async function* () {
|
|
1315
1519
|
try {
|
|
1316
|
-
yield* streamResponses(response, input);
|
|
1520
|
+
yield* streamResponses(response, { ...input, abortSignal: signal });
|
|
1317
1521
|
}
|
|
1318
1522
|
finally {
|
|
1319
1523
|
cleanup();
|
|
@@ -1359,7 +1563,7 @@ class QwenLanguageModel {
|
|
|
1359
1563
|
if (event.data === "[DONE]") {
|
|
1360
1564
|
break;
|
|
1361
1565
|
}
|
|
1362
|
-
const json =
|
|
1566
|
+
const json = parseQwenStreamEvent(event.data, "chat");
|
|
1363
1567
|
if (json.error) {
|
|
1364
1568
|
if (json.usage)
|
|
1365
1569
|
lastUsage = json.usage;
|