@zhivex-ai/qwen 0.10.2 → 0.11.0
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 +22 -6
- package/dist/capabilities.d.ts +9 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +101 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/index.d.ts +2 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +223 -184
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,59 +1,7 @@
|
|
|
1
1
|
import { toJSONSchema } from "zod";
|
|
2
|
+
import { capabilities, embeddingCapabilities, imageGenerationCapabilities, realtimeCapabilities, speechCapabilities, transcriptionCapabilities, videoGenerationCapabilities, } from "./capabilities.js";
|
|
2
3
|
import { CallbackRealtimeSession, ConfigurationError, decodeBase64WithLimit, ProviderHTTPError, ValidationError, assertTrustedEndpoint, readBodyWithLimit, readErrorBodyWithLimit, readJsonWithLimit, resolveAudioResponseLimits, UnsupportedFeatureError, createProviderAdapter, hostedTool, openWebSocketConnection, isCallableToolDefinition, normalizeFinishReason, providerDataPart, streamSSE, toToolSet, withRetry, withTimeoutSignal } from "@zhivex-ai/core";
|
|
3
4
|
export const QWEN_TOKEN_PLAN_BASE_URL = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1";
|
|
4
|
-
const capabilities = {
|
|
5
|
-
streaming: true,
|
|
6
|
-
tools: true,
|
|
7
|
-
structuredOutput: true,
|
|
8
|
-
jsonMode: true,
|
|
9
|
-
toolChoice: true,
|
|
10
|
-
parallelToolCalls: false,
|
|
11
|
-
vision: true,
|
|
12
|
-
files: false,
|
|
13
|
-
audioInput: false,
|
|
14
|
-
audioOutput: false,
|
|
15
|
-
embeddings: true,
|
|
16
|
-
reasoning: false,
|
|
17
|
-
webSearch: true,
|
|
18
|
-
agentCapabilities: {
|
|
19
|
-
supportTier: "tier-b",
|
|
20
|
-
toolChoiceNone: true,
|
|
21
|
-
approvalRequests: false,
|
|
22
|
-
hostedWebSearch: true,
|
|
23
|
-
hostedFileSearch: true,
|
|
24
|
-
remoteMcp: true,
|
|
25
|
-
computerUse: false,
|
|
26
|
-
codeExecution: true,
|
|
27
|
-
webExtraction: true,
|
|
28
|
-
toolsets: false
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
const embeddingCapabilities = {
|
|
32
|
-
streaming: false,
|
|
33
|
-
tools: false,
|
|
34
|
-
structuredOutput: false,
|
|
35
|
-
jsonMode: false,
|
|
36
|
-
toolChoice: false,
|
|
37
|
-
parallelToolCalls: false,
|
|
38
|
-
vision: false,
|
|
39
|
-
files: false,
|
|
40
|
-
audioInput: false,
|
|
41
|
-
audioOutput: false,
|
|
42
|
-
embeddings: true,
|
|
43
|
-
reasoning: false,
|
|
44
|
-
webSearch: false,
|
|
45
|
-
agentCapabilities: {
|
|
46
|
-
supportTier: "tier-c",
|
|
47
|
-
toolChoiceNone: false,
|
|
48
|
-
approvalRequests: false,
|
|
49
|
-
hostedWebSearch: false,
|
|
50
|
-
hostedFileSearch: false,
|
|
51
|
-
remoteMcp: false,
|
|
52
|
-
computerUse: false,
|
|
53
|
-
codeExecution: false,
|
|
54
|
-
toolsets: false
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
5
|
const qwenTaskBaseURLFrom = (baseURL) => {
|
|
58
6
|
const url = new URL(baseURL);
|
|
59
7
|
return `${url.protocol}//${url.host}/api/v1`;
|
|
@@ -375,8 +323,10 @@ const stripResponsesRequestOptions = (providerOptions) => {
|
|
|
375
323
|
};
|
|
376
324
|
const modelFamily = (modelId) => modelId.toLowerCase();
|
|
377
325
|
const isQwen38Max = (modelId) => modelFamily(modelId) === "qwen3.8-max";
|
|
326
|
+
const isQwen38Flash = (modelId) => modelFamily(modelId) === "qwen3.8-flash";
|
|
378
327
|
const isQwen38MaxPreview = (modelId) => modelFamily(modelId) === "qwen3.8-max-preview";
|
|
379
|
-
const
|
|
328
|
+
const isQwen38ProductionModel = (modelId) => isQwen38Max(modelId) || isQwen38Flash(modelId);
|
|
329
|
+
const isQwen38ReasoningModel = (modelId) => isQwen38ProductionModel(modelId) || isQwen38MaxPreview(modelId);
|
|
380
330
|
const isQwenTokenPlanURL = (url) => {
|
|
381
331
|
const path = url.pathname.replace(/\/+$/, "");
|
|
382
332
|
const tokenPlanURL = new URL(QWEN_TOKEN_PLAN_BASE_URL);
|
|
@@ -399,7 +349,7 @@ const validateQwen38BaseURL = (baseURL) => {
|
|
|
399
349
|
throw new ConfigurationError(`Qwen qwen3.8-max-preview is Token Plan only. Configure baseURL with QWEN_TOKEN_PLAN_BASE_URL (${QWEN_TOKEN_PLAN_BASE_URL}); pay-as-you-go and workspace endpoints cannot serve this model.`);
|
|
400
350
|
}
|
|
401
351
|
};
|
|
402
|
-
const
|
|
352
|
+
const validateQwen38ProductionBaseURL = (modelId, baseURL) => {
|
|
403
353
|
let url;
|
|
404
354
|
try {
|
|
405
355
|
url = new URL(baseURL);
|
|
@@ -408,7 +358,7 @@ const validateQwen38MaxBaseURL = (baseURL) => {
|
|
|
408
358
|
return;
|
|
409
359
|
}
|
|
410
360
|
if (isQwenTokenPlanURL(url)) {
|
|
411
|
-
throw new ConfigurationError(
|
|
361
|
+
throw new ConfigurationError(`Qwen ${modelId} uses standard Model Studio credentials and endpoints; QWEN_TOKEN_PLAN_BASE_URL is reserved for qwen3.8-max-preview.`);
|
|
412
362
|
}
|
|
413
363
|
};
|
|
414
364
|
const stripQwen38ReasoningOptions = (providerOptions) => {
|
|
@@ -428,7 +378,7 @@ const supportsQwenVision = (modelId) => {
|
|
|
428
378
|
model.includes("omni") ||
|
|
429
379
|
model.includes("ocr") ||
|
|
430
380
|
model.includes("vision") ||
|
|
431
|
-
|
|
381
|
+
isQwen38ProductionModel(modelId) ||
|
|
432
382
|
isQwen38MaxPreview(modelId) ||
|
|
433
383
|
/^qwen3\.7-plus(?:$|-)/.test(model) ||
|
|
434
384
|
/^qwen3\.6-flash(?:$|-)/.test(model));
|
|
@@ -440,7 +390,7 @@ const qwenLanguageCapabilities = (modelId) => {
|
|
|
440
390
|
const tools = supportsQwenTools(modelId);
|
|
441
391
|
const omni = isQwenOmniLanguageModel(modelId);
|
|
442
392
|
const reasoning = supportsQwenReasoning(modelId);
|
|
443
|
-
const
|
|
393
|
+
const qwen38Production = isQwen38ProductionModel(modelId);
|
|
444
394
|
const qwen38MaxPreview = isQwen38MaxPreview(modelId);
|
|
445
395
|
return {
|
|
446
396
|
...capabilities,
|
|
@@ -449,12 +399,12 @@ const qwenLanguageCapabilities = (modelId) => {
|
|
|
449
399
|
structuredOutput: tools && !omni && !qwen38MaxPreview,
|
|
450
400
|
jsonMode: tools && !omni && !qwen38MaxPreview,
|
|
451
401
|
toolChoice: tools,
|
|
452
|
-
parallelToolCalls:
|
|
402
|
+
parallelToolCalls: qwen38Production,
|
|
453
403
|
webSearch: tools && !omni,
|
|
454
|
-
files: supportsQwenFiles(modelId) ||
|
|
404
|
+
files: supportsQwenFiles(modelId) || qwen38Production,
|
|
455
405
|
audioInput: supportsQwenAudioInput(modelId),
|
|
456
406
|
reasoning,
|
|
457
|
-
reasoningEfforts:
|
|
407
|
+
reasoningEfforts: qwen38Production
|
|
458
408
|
? ["none", "minimal", "low", "medium", "high", "xhigh", "max"]
|
|
459
409
|
: qwen38MaxPreview
|
|
460
410
|
? ["minimal", "low", "medium", "high", "xhigh", "max"]
|
|
@@ -473,53 +423,6 @@ const qwenLanguageCapabilities = (modelId) => {
|
|
|
473
423
|
}
|
|
474
424
|
};
|
|
475
425
|
};
|
|
476
|
-
const transcriptionCapabilities = {
|
|
477
|
-
...embeddingCapabilities,
|
|
478
|
-
audioInput: true
|
|
479
|
-
};
|
|
480
|
-
const speechCapabilities = {
|
|
481
|
-
...embeddingCapabilities,
|
|
482
|
-
audioOutput: true
|
|
483
|
-
};
|
|
484
|
-
const imageGenerationCapabilities = {
|
|
485
|
-
...embeddingCapabilities,
|
|
486
|
-
imageGeneration: true
|
|
487
|
-
};
|
|
488
|
-
const videoGenerationCapabilities = {
|
|
489
|
-
...embeddingCapabilities,
|
|
490
|
-
videoGeneration: true
|
|
491
|
-
};
|
|
492
|
-
const realtimeCapabilities = {
|
|
493
|
-
...capabilities,
|
|
494
|
-
streaming: false,
|
|
495
|
-
structuredOutput: false,
|
|
496
|
-
jsonMode: false,
|
|
497
|
-
toolChoice: false,
|
|
498
|
-
parallelToolCalls: false,
|
|
499
|
-
files: false,
|
|
500
|
-
audioInput: true,
|
|
501
|
-
audioOutput: true,
|
|
502
|
-
embeddings: false,
|
|
503
|
-
reasoning: false,
|
|
504
|
-
webSearch: false,
|
|
505
|
-
agentCapabilities: {
|
|
506
|
-
...capabilities.agentCapabilities,
|
|
507
|
-
toolChoiceNone: true,
|
|
508
|
-
hostedWebSearch: false,
|
|
509
|
-
hostedFileSearch: false,
|
|
510
|
-
remoteMcp: false,
|
|
511
|
-
codeExecution: false,
|
|
512
|
-
webExtraction: false
|
|
513
|
-
},
|
|
514
|
-
realtime: {
|
|
515
|
-
sessions: true,
|
|
516
|
-
audioInput: true,
|
|
517
|
-
audioOutput: true,
|
|
518
|
-
imageInput: true,
|
|
519
|
-
tools: true,
|
|
520
|
-
browserTokens: false
|
|
521
|
-
}
|
|
522
|
-
};
|
|
523
426
|
const reasoningContentFromMessage = (message) => message.parts
|
|
524
427
|
.filter((part) => {
|
|
525
428
|
if (part.type !== "provider-data" || part.provider !== "qwen") {
|
|
@@ -683,16 +586,29 @@ const mapResponsesToolChoice = (toolChoice, tools) => {
|
|
|
683
586
|
tools: [{ type: "function", name: toolChoice.toolName }]
|
|
684
587
|
};
|
|
685
588
|
};
|
|
686
|
-
const mapStructuredOutput = (input) => {
|
|
589
|
+
const mapStructuredOutput = (modelId, input) => {
|
|
687
590
|
if (!input.structuredOutput || input.structuredOutput.mode !== "native") {
|
|
688
591
|
return undefined;
|
|
689
592
|
}
|
|
593
|
+
if (isQwen38Flash(modelId)) {
|
|
594
|
+
return {
|
|
595
|
+
type: "json_schema",
|
|
596
|
+
json_schema: {
|
|
597
|
+
name: input.structuredOutput.name ?? "response",
|
|
598
|
+
strict: true,
|
|
599
|
+
schema: toJSONSchema(input.structuredOutput.schema)
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
}
|
|
690
603
|
return { type: "json_object" };
|
|
691
604
|
};
|
|
692
|
-
const withStructuredOutputMessages = (input) => {
|
|
605
|
+
const withStructuredOutputMessages = (modelId, input) => {
|
|
693
606
|
if (!input.structuredOutput || input.structuredOutput.mode !== "native") {
|
|
694
607
|
return input.messages;
|
|
695
608
|
}
|
|
609
|
+
if (isQwen38Flash(modelId)) {
|
|
610
|
+
return input.messages;
|
|
611
|
+
}
|
|
696
612
|
const schema = JSON.stringify(toJSONSchema(input.structuredOutput.schema));
|
|
697
613
|
return [
|
|
698
614
|
{
|
|
@@ -766,26 +682,28 @@ const validateQwen38Request = (modelId, input, providerOptions) => {
|
|
|
766
682
|
if (sharedBudget !== undefined && sharedBudget > 262_144) {
|
|
767
683
|
throw new ConfigurationError(`Qwen ${modelId} reasoning.budgetTokens cannot exceed 262144.`);
|
|
768
684
|
}
|
|
769
|
-
if (
|
|
685
|
+
if (isQwen38ProductionModel(modelId) &&
|
|
770
686
|
providerOptions.enable_thinking === false &&
|
|
771
687
|
((effort !== undefined && effort !== "none") || budget !== undefined)) {
|
|
772
|
-
throw new ConfigurationError(
|
|
688
|
+
throw new ConfigurationError(`Qwen ${modelId} cannot disable thinking while also configuring a reasoning effort or thinking budget.`);
|
|
773
689
|
}
|
|
774
|
-
if (
|
|
775
|
-
|
|
690
|
+
if (isQwen38ProductionModel(modelId) &&
|
|
691
|
+
providerOptions.enable_thinking === true &&
|
|
692
|
+
effort === "none") {
|
|
693
|
+
throw new ConfigurationError(`Qwen ${modelId} cannot enable thinking with reasoning effort "none".`);
|
|
776
694
|
}
|
|
777
695
|
const thinkingEnabled = isQwen38MaxPreview(modelId) ||
|
|
778
696
|
!(effort === "none" || providerOptions.enable_thinking === false);
|
|
779
697
|
if (thinkingEnabled && (input.toolChoice === "required" || typeof input.toolChoice === "object")) {
|
|
780
698
|
throw new UnsupportedFeatureError(`Qwen ${modelId} supports toolChoice "required" or a named tool only when thinking is disabled.`);
|
|
781
699
|
}
|
|
782
|
-
if (
|
|
700
|
+
if (isQwen38ProductionModel(modelId) &&
|
|
783
701
|
input.messages.some((message) => message.parts.some((part) => part.type === "file" && !part.mediaType.toLowerCase().startsWith("video/")))) {
|
|
784
|
-
throw new UnsupportedFeatureError(
|
|
702
|
+
throw new UnsupportedFeatureError(`Qwen ${modelId} FilePart input is reserved for video/* media. Use ImagePart for images.`);
|
|
785
703
|
}
|
|
786
704
|
};
|
|
787
705
|
const mapChatReasoning = (modelId, input, providerOptions) => {
|
|
788
|
-
if (
|
|
706
|
+
if (isQwen38ReasoningModel(modelId)) {
|
|
789
707
|
const rawEffort = input.reasoning?.effort ?? providerOptions.reasoning_effort;
|
|
790
708
|
const thinkingDisabled = rawEffort === "none" || providerOptions.enable_thinking === false;
|
|
791
709
|
if (thinkingDisabled) {
|
|
@@ -818,7 +736,9 @@ const mapChatReasoning = (modelId, input, providerOptions) => {
|
|
|
818
736
|
};
|
|
819
737
|
const mapResponsesReasoning = (modelId, input, providerOptions) => {
|
|
820
738
|
const effort = input.reasoning?.effort ?? providerOptions.reasoning_effort;
|
|
821
|
-
const qwen38Effort =
|
|
739
|
+
const qwen38Effort = isQwen38ProductionModel(modelId) &&
|
|
740
|
+
providerOptions.enable_thinking === false &&
|
|
741
|
+
effort === undefined
|
|
822
742
|
? "none"
|
|
823
743
|
: effort;
|
|
824
744
|
if (!qwen38Effort) {
|
|
@@ -828,7 +748,7 @@ const mapResponsesReasoning = (modelId, input, providerOptions) => {
|
|
|
828
748
|
reasoning: {
|
|
829
749
|
effort: isQwen38MaxPreview(modelId)
|
|
830
750
|
? mapQwen38ChatReasoningEffort(modelId, qwen38Effort)
|
|
831
|
-
:
|
|
751
|
+
: isQwen38ProductionModel(modelId)
|
|
832
752
|
? qwen38Effort
|
|
833
753
|
: qwen38Effort === "low"
|
|
834
754
|
? "minimal"
|
|
@@ -860,7 +780,7 @@ const hasVideoInput = (messages) => messages.some((message) => message.parts.som
|
|
|
860
780
|
const hasNonVideoFileInput = (messages) => messages.some((message) => message.parts.some((part) => part.type === "file" && !part.mediaType.toLowerCase().startsWith("video/")));
|
|
861
781
|
const resolveApiMode = (modelId, requestedMode, input, providerOptions) => {
|
|
862
782
|
const videoInput = hasVideoInput(input.messages);
|
|
863
|
-
if (videoInput && !
|
|
783
|
+
if (videoInput && !isQwen38ProductionModel(modelId)) {
|
|
864
784
|
throw new UnsupportedFeatureError(`Qwen model "${modelId}" does not support video FilePart input through this adapter.`);
|
|
865
785
|
}
|
|
866
786
|
const needsChat = input.maxTokens !== undefined ||
|
|
@@ -891,25 +811,87 @@ const resolveApiMode = (modelId, requestedMode, input, providerOptions) => {
|
|
|
891
811
|
}
|
|
892
812
|
return needsChat ? "chat" : "responses";
|
|
893
813
|
};
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
814
|
+
const classifyToolCallId = (value) => {
|
|
815
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
816
|
+
return { kind: "transient" };
|
|
817
|
+
}
|
|
818
|
+
if (typeof value !== "string") {
|
|
819
|
+
return { kind: "missing" };
|
|
820
|
+
}
|
|
821
|
+
const trimmed = value.trim();
|
|
822
|
+
if (!trimmed) {
|
|
823
|
+
return { kind: "missing" };
|
|
824
|
+
}
|
|
825
|
+
if (/^\d+$/.test(trimmed)) {
|
|
826
|
+
return { kind: "transient" };
|
|
827
|
+
}
|
|
828
|
+
return { kind: "stable", id: value };
|
|
829
|
+
};
|
|
830
|
+
const stableToolCallId = (value) => {
|
|
831
|
+
const classification = classifyToolCallId(value);
|
|
832
|
+
return classification.kind === "stable" ? classification.id : undefined;
|
|
833
|
+
};
|
|
834
|
+
const qwenResponsesProviderCallId = (value) => typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
835
|
+
const existingToolCallIds = (messages) => new Set(messages.flatMap((message) => message.parts.flatMap((part) => part.type === "tool-call" ? [part.toolCall.id] : [])));
|
|
836
|
+
const nextFallbackToolCallGeneration = (surface, input, seenIds) => {
|
|
837
|
+
const prefix = `qwen-${surface}-tool-`;
|
|
838
|
+
let generation = input.messages.length;
|
|
839
|
+
for (const id of seenIds) {
|
|
840
|
+
if (!id.startsWith(prefix))
|
|
841
|
+
continue;
|
|
842
|
+
const separator = id.indexOf("-", prefix.length);
|
|
843
|
+
if (separator < 0)
|
|
844
|
+
continue;
|
|
845
|
+
const generationText = id.slice(prefix.length, separator);
|
|
846
|
+
if (!generationText)
|
|
847
|
+
continue;
|
|
848
|
+
const retainedGeneration = Number(generationText);
|
|
849
|
+
if (Number.isSafeInteger(retainedGeneration) && retainedGeneration >= generation) {
|
|
850
|
+
generation = retainedGeneration + 1;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return generation;
|
|
854
|
+
};
|
|
855
|
+
const fallbackToolCallId = (surface, generation, index) => `qwen-${surface}-tool-${generation}-${index}`;
|
|
856
|
+
class QwenToolCallIdError extends ValidationError {
|
|
857
|
+
diagnosticCode = "QWEN_DUPLICATE_TOOL_CALL_ID";
|
|
858
|
+
constructor() {
|
|
859
|
+
super("Qwen returned a duplicate tool-call id.");
|
|
860
|
+
this.name = "QwenToolCallIdError";
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
const resolveToolCallId = (candidates, fallback, seenIds) => {
|
|
864
|
+
const candidate = candidates.map(stableToolCallId).find((value) => value !== undefined);
|
|
865
|
+
const id = candidate ?? (typeof fallback === "function" ? fallback() : fallback);
|
|
866
|
+
if (seenIds.has(id)) {
|
|
867
|
+
throw new QwenToolCallIdError();
|
|
868
|
+
}
|
|
869
|
+
seenIds.add(id);
|
|
870
|
+
return id;
|
|
871
|
+
};
|
|
872
|
+
const parseAssistantMessage = (message, input) => {
|
|
873
|
+
const seenIds = existingToolCallIds(input.messages);
|
|
874
|
+
const fallbackGeneration = nextFallbackToolCallGeneration("chat", input, seenIds);
|
|
875
|
+
return {
|
|
876
|
+
role: "assistant",
|
|
877
|
+
parts: [
|
|
878
|
+
...(typeof message.reasoning_content === "string" && message.reasoning_content
|
|
879
|
+
? [providerDataPart("qwen", { type: "reasoning_content", reasoningContent: message.reasoning_content })]
|
|
880
|
+
: []),
|
|
881
|
+
...(typeof message.content === "string" && message.content
|
|
882
|
+
? [{ type: "text", text: message.content }]
|
|
883
|
+
: []),
|
|
884
|
+
...((message.tool_calls ?? []).map((call, index) => ({
|
|
885
|
+
type: "tool-call",
|
|
886
|
+
toolCall: {
|
|
887
|
+
id: resolveToolCallId([call.id], fallbackToolCallId("chat", fallbackGeneration, index), seenIds),
|
|
888
|
+
name: call.function.name,
|
|
889
|
+
input: JSON.parse(call.function.arguments ?? "{}")
|
|
890
|
+
}
|
|
891
|
+
})) ?? [])
|
|
892
|
+
]
|
|
893
|
+
};
|
|
894
|
+
};
|
|
913
895
|
const getProviderResponseId = (messages) => {
|
|
914
896
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
915
897
|
const message = messages[index];
|
|
@@ -932,11 +914,16 @@ const getProviderResponseId = (messages) => {
|
|
|
932
914
|
};
|
|
933
915
|
const serializeResponsesToolOutput = (message) => message.parts
|
|
934
916
|
.filter((part) => part.type === "tool-result")
|
|
935
|
-
.map((part) =>
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
917
|
+
.map((part) => {
|
|
918
|
+
const providerCallId = part.toolResult.providerMetadata?.qwenResponsesCallId;
|
|
919
|
+
return {
|
|
920
|
+
type: "function_call_output",
|
|
921
|
+
call_id: typeof providerCallId === "string" && providerCallId.trim().length > 0
|
|
922
|
+
? providerCallId
|
|
923
|
+
: part.toolResult.toolCallId,
|
|
924
|
+
output: JSON.stringify(part.toolResult.isError ? part.toolResult.error : part.toolResult.output ?? null)
|
|
925
|
+
};
|
|
926
|
+
});
|
|
940
927
|
const toResponsesInput = (messages) => {
|
|
941
928
|
const input = [];
|
|
942
929
|
for (const message of messages) {
|
|
@@ -1003,8 +990,10 @@ const parseResponsesProviderData = (item) => {
|
|
|
1003
990
|
}
|
|
1004
991
|
return item;
|
|
1005
992
|
};
|
|
1006
|
-
const parseResponsesAssistantMessage = (json) => {
|
|
993
|
+
const parseResponsesAssistantMessage = (json, input) => {
|
|
1007
994
|
const parts = [];
|
|
995
|
+
const seenIds = existingToolCallIds(input.messages);
|
|
996
|
+
const fallbackGeneration = nextFallbackToolCallGeneration("responses", input, seenIds);
|
|
1008
997
|
for (const [index, item] of (json.output ?? []).entries()) {
|
|
1009
998
|
if (item?.type === "message") {
|
|
1010
999
|
for (const content of item.content ?? []) {
|
|
@@ -1015,12 +1004,17 @@ const parseResponsesAssistantMessage = (json) => {
|
|
|
1015
1004
|
continue;
|
|
1016
1005
|
}
|
|
1017
1006
|
if (item?.type === "function_call") {
|
|
1007
|
+
const providerCallId = qwenResponsesProviderCallId(item.call_id);
|
|
1008
|
+
const id = resolveToolCallId([item.call_id, item.id], fallbackToolCallId("responses", fallbackGeneration, index), seenIds);
|
|
1018
1009
|
parts.push({
|
|
1019
1010
|
type: "tool-call",
|
|
1020
1011
|
toolCall: {
|
|
1021
|
-
id
|
|
1012
|
+
id,
|
|
1022
1013
|
name: item.name,
|
|
1023
|
-
input: JSON.parse(item.arguments ?? "{}")
|
|
1014
|
+
input: JSON.parse(item.arguments ?? "{}"),
|
|
1015
|
+
...(providerCallId !== undefined && providerCallId !== id
|
|
1016
|
+
? { providerMetadata: { qwenResponsesCallId: providerCallId } }
|
|
1017
|
+
: {})
|
|
1024
1018
|
}
|
|
1025
1019
|
});
|
|
1026
1020
|
continue;
|
|
@@ -1053,9 +1047,30 @@ const normalizeResponsesFinishReason = (status, hasToolCalls) => {
|
|
|
1053
1047
|
}
|
|
1054
1048
|
return normalizeFinishReason(status);
|
|
1055
1049
|
};
|
|
1056
|
-
const streamResponses = async function* (response) {
|
|
1050
|
+
const streamResponses = async function* (response, input) {
|
|
1057
1051
|
const toolBuffers = new Map();
|
|
1052
|
+
const toolBufferAliases = new Map();
|
|
1053
|
+
const seenIds = existingToolCallIds(input.messages);
|
|
1054
|
+
const fallbackGeneration = nextFallbackToolCallGeneration("responses", input, seenIds);
|
|
1058
1055
|
let sawToolCalls = false;
|
|
1056
|
+
const bufferKey = (outputIndex, candidates, fallbackId) => {
|
|
1057
|
+
const stableIds = candidates
|
|
1058
|
+
.map(stableToolCallId)
|
|
1059
|
+
.filter((value) => value !== undefined);
|
|
1060
|
+
if ((typeof outputIndex === "number" && Number.isSafeInteger(outputIndex) && outputIndex >= 0) ||
|
|
1061
|
+
(typeof outputIndex === "string" && outputIndex.trim().length > 0)) {
|
|
1062
|
+
const key = `output:${outputIndex}`;
|
|
1063
|
+
for (const stableId of stableIds) {
|
|
1064
|
+
toolBufferAliases.set(`id:${stableId}`, key);
|
|
1065
|
+
}
|
|
1066
|
+
return key;
|
|
1067
|
+
}
|
|
1068
|
+
for (const stableId of stableIds) {
|
|
1069
|
+
const identityKey = `id:${stableId}`;
|
|
1070
|
+
return toolBufferAliases.get(identityKey) ?? identityKey;
|
|
1071
|
+
}
|
|
1072
|
+
return `fallback:${fallbackId}`;
|
|
1073
|
+
};
|
|
1059
1074
|
const emitToolCall = (key) => {
|
|
1060
1075
|
const toolCall = toolBuffers.get(key);
|
|
1061
1076
|
if (!toolCall || toolCall.emitted || !toolCall.name) {
|
|
@@ -1063,12 +1078,16 @@ const streamResponses = async function* (response) {
|
|
|
1063
1078
|
}
|
|
1064
1079
|
toolCall.emitted = true;
|
|
1065
1080
|
sawToolCalls = true;
|
|
1081
|
+
const id = resolveToolCallId([toolCall.callId], toolCall.fallbackId, seenIds);
|
|
1066
1082
|
return {
|
|
1067
1083
|
type: "tool-call",
|
|
1068
1084
|
toolCall: {
|
|
1069
|
-
id
|
|
1085
|
+
id,
|
|
1070
1086
|
name: toolCall.name,
|
|
1071
|
-
input: JSON.parse(toolCall.args || "{}")
|
|
1087
|
+
input: JSON.parse(toolCall.args || "{}"),
|
|
1088
|
+
...(toolCall.providerCallId !== undefined && toolCall.providerCallId !== id
|
|
1089
|
+
? { providerMetadata: { qwenResponsesCallId: toolCall.providerCallId } }
|
|
1090
|
+
: {})
|
|
1072
1091
|
}
|
|
1073
1092
|
};
|
|
1074
1093
|
};
|
|
@@ -1097,14 +1116,19 @@ const streamResponses = async function* (response) {
|
|
|
1097
1116
|
if (type === "response.output_item.added" || type === "response.output_item.done") {
|
|
1098
1117
|
const item = json.item;
|
|
1099
1118
|
if (item?.type === "function_call") {
|
|
1100
|
-
const
|
|
1119
|
+
const outputIndex = json.output_index ?? toolBuffers.size;
|
|
1120
|
+
const fallbackId = fallbackToolCallId("responses", fallbackGeneration, outputIndex);
|
|
1121
|
+
const key = bufferKey(json.output_index, [item.id, json.item_id, item.call_id], fallbackId);
|
|
1101
1122
|
const existing = toolBuffers.get(key) ?? {
|
|
1102
|
-
callId: item.call_id ??
|
|
1123
|
+
callId: stableToolCallId(item.call_id) ?? stableToolCallId(item.id) ?? stableToolCallId(json.item_id),
|
|
1124
|
+
providerCallId: qwenResponsesProviderCallId(item.call_id),
|
|
1125
|
+
fallbackId,
|
|
1103
1126
|
name: item.name ?? "",
|
|
1104
1127
|
args: "",
|
|
1105
1128
|
emitted: false
|
|
1106
1129
|
};
|
|
1107
|
-
existing.callId = item.call_id ?? existing.callId;
|
|
1130
|
+
existing.callId = stableToolCallId(item.call_id) ?? existing.callId;
|
|
1131
|
+
existing.providerCallId = qwenResponsesProviderCallId(item.call_id) ?? existing.providerCallId;
|
|
1108
1132
|
existing.name ||= item.name ?? "";
|
|
1109
1133
|
if (typeof item.arguments === "string") {
|
|
1110
1134
|
existing.args = item.arguments;
|
|
@@ -1128,25 +1152,35 @@ const streamResponses = async function* (response) {
|
|
|
1128
1152
|
continue;
|
|
1129
1153
|
}
|
|
1130
1154
|
if (type === "response.function_call_arguments.delta") {
|
|
1131
|
-
const
|
|
1155
|
+
const outputIndex = json.output_index ?? toolBuffers.size;
|
|
1156
|
+
const fallbackId = fallbackToolCallId("responses", fallbackGeneration, outputIndex);
|
|
1157
|
+
const key = bufferKey(json.output_index, [json.item_id, json.call_id], fallbackId);
|
|
1132
1158
|
const existing = toolBuffers.get(key) ?? {
|
|
1133
|
-
callId:
|
|
1159
|
+
callId: stableToolCallId(json.call_id) ?? stableToolCallId(json.item_id),
|
|
1160
|
+
providerCallId: qwenResponsesProviderCallId(json.call_id),
|
|
1161
|
+
fallbackId,
|
|
1134
1162
|
name: "",
|
|
1135
1163
|
args: "",
|
|
1136
1164
|
emitted: false
|
|
1137
1165
|
};
|
|
1166
|
+
existing.providerCallId = qwenResponsesProviderCallId(json.call_id) ?? existing.providerCallId;
|
|
1138
1167
|
existing.args += typeof json.delta === "string" ? json.delta : "";
|
|
1139
1168
|
toolBuffers.set(key, existing);
|
|
1140
1169
|
continue;
|
|
1141
1170
|
}
|
|
1142
1171
|
if (type === "response.function_call_arguments.done") {
|
|
1143
|
-
const
|
|
1172
|
+
const outputIndex = json.output_index ?? toolBuffers.size;
|
|
1173
|
+
const fallbackId = fallbackToolCallId("responses", fallbackGeneration, outputIndex);
|
|
1174
|
+
const key = bufferKey(json.output_index, [json.item_id, json.call_id], fallbackId);
|
|
1144
1175
|
const existing = toolBuffers.get(key) ?? {
|
|
1145
|
-
callId:
|
|
1176
|
+
callId: stableToolCallId(json.call_id) ?? stableToolCallId(json.item_id),
|
|
1177
|
+
providerCallId: qwenResponsesProviderCallId(json.call_id),
|
|
1178
|
+
fallbackId,
|
|
1146
1179
|
name: "",
|
|
1147
1180
|
args: "",
|
|
1148
1181
|
emitted: false
|
|
1149
1182
|
};
|
|
1183
|
+
existing.providerCallId = qwenResponsesProviderCallId(json.call_id) ?? existing.providerCallId;
|
|
1150
1184
|
if (typeof json.arguments === "string") {
|
|
1151
1185
|
existing.args = json.arguments;
|
|
1152
1186
|
}
|
|
@@ -1195,11 +1229,11 @@ class QwenLanguageModel {
|
|
|
1195
1229
|
}
|
|
1196
1230
|
const providerOptions = { ...(input.providerOptions ?? {}) };
|
|
1197
1231
|
validateQwenToolStream(this.modelId, providerOptions, false);
|
|
1198
|
-
if (
|
|
1232
|
+
if (isQwen38ReasoningModel(this.modelId)) {
|
|
1199
1233
|
validateQwen38Request(this.modelId, input, providerOptions);
|
|
1200
1234
|
}
|
|
1201
|
-
if (
|
|
1202
|
-
|
|
1235
|
+
if (isQwen38ProductionModel(this.modelId)) {
|
|
1236
|
+
validateQwen38ProductionBaseURL(this.modelId, this.baseURL);
|
|
1203
1237
|
}
|
|
1204
1238
|
if (isQwen38MaxPreview(this.modelId)) {
|
|
1205
1239
|
validateQwen38BaseURL(this.baseURL);
|
|
@@ -1211,7 +1245,7 @@ class QwenLanguageModel {
|
|
|
1211
1245
|
if (apiMode === "responses") {
|
|
1212
1246
|
const previousResponse = getProviderResponseId(input.messages);
|
|
1213
1247
|
const baseResponseProviderOptions = stripResponsesRequestOptions(providerOptions);
|
|
1214
|
-
const responseProviderOptions =
|
|
1248
|
+
const responseProviderOptions = isQwen38ReasoningModel(this.modelId)
|
|
1215
1249
|
? stripQwen38ReasoningOptions(baseResponseProviderOptions)
|
|
1216
1250
|
: baseResponseProviderOptions;
|
|
1217
1251
|
const messages = previousResponse && previousResponse.index < input.messages.length - 1
|
|
@@ -1234,7 +1268,7 @@ class QwenLanguageModel {
|
|
|
1234
1268
|
})
|
|
1235
1269
|
}), input);
|
|
1236
1270
|
const json = await parseJson(response);
|
|
1237
|
-
const assistantMessage = parseResponsesAssistantMessage(json);
|
|
1271
|
+
const assistantMessage = parseResponsesAssistantMessage(json, input);
|
|
1238
1272
|
const hasToolCalls = assistantMessage.parts.some((part) => part.type === "tool-call");
|
|
1239
1273
|
return {
|
|
1240
1274
|
messages: [assistantMessage],
|
|
@@ -1249,7 +1283,7 @@ class QwenLanguageModel {
|
|
|
1249
1283
|
};
|
|
1250
1284
|
}
|
|
1251
1285
|
const baseChatProviderOptions = stripHeaderOptions(providerOptions);
|
|
1252
|
-
const chatProviderOptions =
|
|
1286
|
+
const chatProviderOptions = isQwen38ReasoningModel(this.modelId)
|
|
1253
1287
|
? stripQwen38ReasoningOptions(baseChatProviderOptions)
|
|
1254
1288
|
: baseChatProviderOptions;
|
|
1255
1289
|
const response = await withRetry(() => this.fetcher(`${this.baseURL}/chat/completions`, {
|
|
@@ -1259,13 +1293,13 @@ class QwenLanguageModel {
|
|
|
1259
1293
|
body: JSON.stringify({
|
|
1260
1294
|
...chatProviderOptions,
|
|
1261
1295
|
model: this.modelId,
|
|
1262
|
-
messages: mapMessages(withStructuredOutputMessages(input)),
|
|
1296
|
+
messages: mapMessages(withStructuredOutputMessages(this.modelId, input)),
|
|
1263
1297
|
tools: mapChatTools(input.tools),
|
|
1264
1298
|
tool_choice: mapChatToolChoice(input.toolChoice),
|
|
1265
|
-
response_format: mapStructuredOutput(input),
|
|
1299
|
+
response_format: mapStructuredOutput(this.modelId, input),
|
|
1266
1300
|
temperature: input.temperature,
|
|
1267
|
-
max_tokens:
|
|
1268
|
-
max_completion_tokens:
|
|
1301
|
+
max_tokens: isQwen38ReasoningModel(this.modelId) ? undefined : input.maxTokens,
|
|
1302
|
+
max_completion_tokens: isQwen38ReasoningModel(this.modelId) ? input.maxTokens : undefined,
|
|
1269
1303
|
stream: false,
|
|
1270
1304
|
...mapChatReasoning(this.modelId, input, providerOptions)
|
|
1271
1305
|
})
|
|
@@ -1273,7 +1307,7 @@ class QwenLanguageModel {
|
|
|
1273
1307
|
const json = await parseJson(response);
|
|
1274
1308
|
const choice = json.choices?.[0];
|
|
1275
1309
|
const message = choice?.message ?? {};
|
|
1276
|
-
const assistantMessage = parseAssistantMessage(message);
|
|
1310
|
+
const assistantMessage = parseAssistantMessage(message, input);
|
|
1277
1311
|
return {
|
|
1278
1312
|
messages: [assistantMessage],
|
|
1279
1313
|
text: assistantMessage.parts
|
|
@@ -1293,11 +1327,11 @@ class QwenLanguageModel {
|
|
|
1293
1327
|
async stream(input) {
|
|
1294
1328
|
const providerOptions = { ...(input.providerOptions ?? {}) };
|
|
1295
1329
|
validateQwenToolStream(this.modelId, providerOptions, true);
|
|
1296
|
-
if (
|
|
1330
|
+
if (isQwen38ReasoningModel(this.modelId)) {
|
|
1297
1331
|
validateQwen38Request(this.modelId, input, providerOptions);
|
|
1298
1332
|
}
|
|
1299
|
-
if (
|
|
1300
|
-
|
|
1333
|
+
if (isQwen38ProductionModel(this.modelId)) {
|
|
1334
|
+
validateQwen38ProductionBaseURL(this.modelId, this.baseURL);
|
|
1301
1335
|
}
|
|
1302
1336
|
if (isQwen38MaxPreview(this.modelId)) {
|
|
1303
1337
|
validateQwen38BaseURL(this.baseURL);
|
|
@@ -1308,7 +1342,7 @@ class QwenLanguageModel {
|
|
|
1308
1342
|
if (apiMode === "responses") {
|
|
1309
1343
|
const previousResponse = getProviderResponseId(input.messages);
|
|
1310
1344
|
const baseResponseProviderOptions = stripResponsesRequestOptions(providerOptions);
|
|
1311
|
-
const responseProviderOptions =
|
|
1345
|
+
const responseProviderOptions = isQwen38ReasoningModel(this.modelId)
|
|
1312
1346
|
? stripQwen38ReasoningOptions(baseResponseProviderOptions)
|
|
1313
1347
|
: baseResponseProviderOptions;
|
|
1314
1348
|
const messages = previousResponse && previousResponse.index < input.messages.length - 1
|
|
@@ -1332,7 +1366,7 @@ class QwenLanguageModel {
|
|
|
1332
1366
|
}), input);
|
|
1333
1367
|
return (async function* () {
|
|
1334
1368
|
try {
|
|
1335
|
-
yield* streamResponses(response);
|
|
1369
|
+
yield* streamResponses(response, input);
|
|
1336
1370
|
}
|
|
1337
1371
|
finally {
|
|
1338
1372
|
cleanup();
|
|
@@ -1340,7 +1374,7 @@ class QwenLanguageModel {
|
|
|
1340
1374
|
})();
|
|
1341
1375
|
}
|
|
1342
1376
|
const baseChatProviderOptions = stripHeaderOptions(providerOptions);
|
|
1343
|
-
const chatProviderOptions =
|
|
1377
|
+
const chatProviderOptions = isQwen38ReasoningModel(this.modelId)
|
|
1344
1378
|
? stripQwen38ReasoningOptions(baseChatProviderOptions)
|
|
1345
1379
|
: baseChatProviderOptions;
|
|
1346
1380
|
const response = await withRetry(() => this.fetcher(`${this.baseURL}/chat/completions`, {
|
|
@@ -1351,13 +1385,13 @@ class QwenLanguageModel {
|
|
|
1351
1385
|
...chatProviderOptions,
|
|
1352
1386
|
model: this.modelId,
|
|
1353
1387
|
modalities: isQwenOmniLanguageModel(this.modelId) ? providerOptions.modalities ?? ["text"] : undefined,
|
|
1354
|
-
messages: mapMessages(withStructuredOutputMessages(input)),
|
|
1388
|
+
messages: mapMessages(withStructuredOutputMessages(this.modelId, input)),
|
|
1355
1389
|
tools: mapChatTools(input.tools),
|
|
1356
1390
|
tool_choice: mapChatToolChoice(input.toolChoice),
|
|
1357
|
-
response_format: mapStructuredOutput(input),
|
|
1391
|
+
response_format: mapStructuredOutput(this.modelId, input),
|
|
1358
1392
|
temperature: input.temperature,
|
|
1359
|
-
max_tokens:
|
|
1360
|
-
max_completion_tokens:
|
|
1393
|
+
max_tokens: isQwen38ReasoningModel(this.modelId) ? undefined : input.maxTokens,
|
|
1394
|
+
max_completion_tokens: isQwen38ReasoningModel(this.modelId) ? input.maxTokens : undefined,
|
|
1361
1395
|
stream: true,
|
|
1362
1396
|
stream_options: { include_usage: true },
|
|
1363
1397
|
...mapChatReasoning(this.modelId, input, providerOptions)
|
|
@@ -1366,6 +1400,8 @@ class QwenLanguageModel {
|
|
|
1366
1400
|
return (async function* () {
|
|
1367
1401
|
try {
|
|
1368
1402
|
const toolBuffers = new Map();
|
|
1403
|
+
const seenIds = existingToolCallIds(input.messages);
|
|
1404
|
+
const fallbackGeneration = nextFallbackToolCallGeneration("chat", input, seenIds);
|
|
1369
1405
|
for await (const event of streamSSE(response)) {
|
|
1370
1406
|
if (event.data === "[DONE]") {
|
|
1371
1407
|
return;
|
|
@@ -1389,12 +1425,13 @@ class QwenLanguageModel {
|
|
|
1389
1425
|
for (const toolCall of delta?.tool_calls ?? []) {
|
|
1390
1426
|
const index = Number(toolCall.index ?? 0);
|
|
1391
1427
|
const existing = toolBuffers.get(index) ?? {
|
|
1392
|
-
id: toolCall.id
|
|
1428
|
+
id: stableToolCallId(toolCall.id),
|
|
1429
|
+
fallbackId: fallbackToolCallId("chat", fallbackGeneration, index),
|
|
1393
1430
|
name: toolCall.function?.name ?? "",
|
|
1394
1431
|
args: "",
|
|
1395
1432
|
emitted: false
|
|
1396
1433
|
};
|
|
1397
|
-
existing.id = toolCall.id ?? existing.id;
|
|
1434
|
+
existing.id = stableToolCallId(toolCall.id) ?? existing.id;
|
|
1398
1435
|
existing.name ||= toolCall.function?.name ?? "";
|
|
1399
1436
|
existing.args += toolCall.function?.arguments ?? "";
|
|
1400
1437
|
toolBuffers.set(index, existing);
|
|
@@ -1409,7 +1446,7 @@ class QwenLanguageModel {
|
|
|
1409
1446
|
yield {
|
|
1410
1447
|
type: "tool-call",
|
|
1411
1448
|
toolCall: {
|
|
1412
|
-
id: toolCall.id,
|
|
1449
|
+
id: resolveToolCallId([toolCall.id], toolCall.fallbackId, seenIds),
|
|
1413
1450
|
name: toolCall.name,
|
|
1414
1451
|
input: JSON.parse(toolCall.args || "{}")
|
|
1415
1452
|
}
|
|
@@ -2155,7 +2192,7 @@ class QwenTasksClientImpl {
|
|
|
2155
2192
|
}
|
|
2156
2193
|
}
|
|
2157
2194
|
}
|
|
2158
|
-
const parseRealtimeEvent = (payload) => {
|
|
2195
|
+
const parseRealtimeEvent = (payload, fallbackRealtimeToolCallId, seenToolCallIds) => {
|
|
2159
2196
|
const type = String(payload.type ?? "");
|
|
2160
2197
|
const metadata = payload;
|
|
2161
2198
|
const itemId = typeof payload.item_id === "string" ? payload.item_id : undefined;
|
|
@@ -2196,7 +2233,7 @@ const parseRealtimeEvent = (payload) => {
|
|
|
2196
2233
|
return [{
|
|
2197
2234
|
type: "realtime-tool-call",
|
|
2198
2235
|
toolCall: {
|
|
2199
|
-
id:
|
|
2236
|
+
id: resolveToolCallId([payload.call_id, payload.item_id], fallbackRealtimeToolCallId, seenToolCallIds),
|
|
2200
2237
|
name: String(payload.name ?? ""),
|
|
2201
2238
|
input: JSON.parse(typeof payload.arguments === "string" ? payload.arguments : "{}")
|
|
2202
2239
|
}
|
|
@@ -2290,6 +2327,8 @@ class QwenRealtimeModel {
|
|
|
2290
2327
|
assertQwenRealtimeConfig(config);
|
|
2291
2328
|
const url = appendQuery(this.realtimeURL, { model: this.modelId });
|
|
2292
2329
|
const connection = await (this.connectionFactory ?? openQwenRealtimeConnection)(url, { authorization: `Bearer ${this.apiKey}` }, options);
|
|
2330
|
+
let fallbackRealtimeToolCallIndex = 0;
|
|
2331
|
+
const seenRealtimeToolCallIds = new Set();
|
|
2293
2332
|
const session = new CallbackRealtimeSession({
|
|
2294
2333
|
provider: this.provider,
|
|
2295
2334
|
modelId: this.modelId,
|
|
@@ -2297,7 +2336,7 @@ class QwenRealtimeModel {
|
|
|
2297
2336
|
config,
|
|
2298
2337
|
connection,
|
|
2299
2338
|
callbacks: {
|
|
2300
|
-
parseEvent: parseRealtimeEvent,
|
|
2339
|
+
parseEvent: (payload) => parseRealtimeEvent(payload, () => `qwen-realtime-tool-${fallbackRealtimeToolCallIndex++}`, seenRealtimeToolCallIds),
|
|
2301
2340
|
buildInitialPayloads: (value) => [{ type: "session.update", session: mapRealtimeSession(value) }],
|
|
2302
2341
|
buildAudioPayloads: (frame, sessionConfig) => [
|
|
2303
2342
|
{ type: "input_audio_buffer.append", audio: encodeRealtimeData(frame.data) },
|