@agentscope-ai/agentscope 0.0.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.
Files changed (136) hide show
  1. package/dist/agent/index.d.mts +234 -0
  2. package/dist/agent/index.d.ts +234 -0
  3. package/dist/agent/index.js +1412 -0
  4. package/dist/agent/index.js.map +1 -0
  5. package/dist/agent/index.mjs +1375 -0
  6. package/dist/agent/index.mjs.map +1 -0
  7. package/dist/base-BOx3UzOl.d.mts +41 -0
  8. package/dist/base-BoIps2RL.d.ts +41 -0
  9. package/dist/base-C7jwyH4Z.d.mts +52 -0
  10. package/dist/base-Cwi4bjze.d.ts +127 -0
  11. package/dist/base-DYlBMCy_.d.mts +127 -0
  12. package/dist/base-NX-knWOv.d.ts +52 -0
  13. package/dist/block-VsnHrllL.d.mts +48 -0
  14. package/dist/block-VsnHrllL.d.ts +48 -0
  15. package/dist/event/index.d.mts +181 -0
  16. package/dist/event/index.d.ts +181 -0
  17. package/dist/event/index.js +58 -0
  18. package/dist/event/index.js.map +1 -0
  19. package/dist/event/index.mjs +33 -0
  20. package/dist/event/index.mjs.map +1 -0
  21. package/dist/formatter/index.d.mts +187 -0
  22. package/dist/formatter/index.d.ts +187 -0
  23. package/dist/formatter/index.js +647 -0
  24. package/dist/formatter/index.js.map +1 -0
  25. package/dist/formatter/index.mjs +616 -0
  26. package/dist/formatter/index.mjs.map +1 -0
  27. package/dist/index-BTJDlKvQ.d.mts +195 -0
  28. package/dist/index-BcatlwXQ.d.ts +195 -0
  29. package/dist/index-CAxQAkiP.d.mts +21 -0
  30. package/dist/index-CAxQAkiP.d.ts +21 -0
  31. package/dist/mcp/index.d.mts +9 -0
  32. package/dist/mcp/index.d.ts +9 -0
  33. package/dist/mcp/index.js +432 -0
  34. package/dist/mcp/index.js.map +1 -0
  35. package/dist/mcp/index.mjs +408 -0
  36. package/dist/mcp/index.mjs.map +1 -0
  37. package/dist/message/index.d.mts +10 -0
  38. package/dist/message/index.d.ts +10 -0
  39. package/dist/message/index.js +67 -0
  40. package/dist/message/index.js.map +1 -0
  41. package/dist/message/index.mjs +37 -0
  42. package/dist/message/index.mjs.map +1 -0
  43. package/dist/message-CkN21KaY.d.mts +99 -0
  44. package/dist/message-CzLeTlua.d.ts +99 -0
  45. package/dist/model/index.d.mts +377 -0
  46. package/dist/model/index.d.ts +377 -0
  47. package/dist/model/index.js +1880 -0
  48. package/dist/model/index.js.map +1 -0
  49. package/dist/model/index.mjs +1849 -0
  50. package/dist/model/index.mjs.map +1 -0
  51. package/dist/storage/index.d.mts +68 -0
  52. package/dist/storage/index.d.ts +68 -0
  53. package/dist/storage/index.js +250 -0
  54. package/dist/storage/index.js.map +1 -0
  55. package/dist/storage/index.mjs +212 -0
  56. package/dist/storage/index.mjs.map +1 -0
  57. package/dist/tool/index.d.mts +311 -0
  58. package/dist/tool/index.d.ts +311 -0
  59. package/dist/tool/index.js +1494 -0
  60. package/dist/tool/index.js.map +1 -0
  61. package/dist/tool/index.mjs +1447 -0
  62. package/dist/tool/index.mjs.map +1 -0
  63. package/dist/toolkit-CEpulFi0.d.ts +99 -0
  64. package/dist/toolkit-CGEZSZPa.d.mts +99 -0
  65. package/jest.config.js +11 -0
  66. package/package.json +92 -0
  67. package/src/_utils/common.ts +104 -0
  68. package/src/_utils/index.ts +1 -0
  69. package/src/agent/agent-base.ts +0 -0
  70. package/src/agent/agent.test.ts +1028 -0
  71. package/src/agent/agent.ts +1032 -0
  72. package/src/agent/index.ts +2 -0
  73. package/src/agent/interfaces.ts +23 -0
  74. package/src/agent/test-compression.ts +72 -0
  75. package/src/event/index.ts +250 -0
  76. package/src/formatter/base.ts +133 -0
  77. package/src/formatter/dashscope-chat-formatter.test.ts +372 -0
  78. package/src/formatter/dashscope-chat-formatter.ts +163 -0
  79. package/src/formatter/deepseek-chat-formatter.ts +130 -0
  80. package/src/formatter/index.ts +5 -0
  81. package/src/formatter/ollama-chat-formatter.ts +67 -0
  82. package/src/formatter/openai-chat-formatter.test.ts +263 -0
  83. package/src/formatter/openai-chat-formatter.ts +301 -0
  84. package/src/formatter/openai.md +767 -0
  85. package/src/mcp/base.ts +114 -0
  86. package/src/mcp/http.test.ts +303 -0
  87. package/src/mcp/http.ts +224 -0
  88. package/src/mcp/index.ts +2 -0
  89. package/src/mcp/stdio.test.ts +91 -0
  90. package/src/mcp/stdio.ts +119 -0
  91. package/src/message/block.ts +60 -0
  92. package/src/message/enums.ts +4 -0
  93. package/src/message/index.ts +12 -0
  94. package/src/message/message.test.ts +80 -0
  95. package/src/message/message.ts +131 -0
  96. package/src/model/base.ts +226 -0
  97. package/src/model/dashscope-model.test.ts +335 -0
  98. package/src/model/dashscope-model.ts +441 -0
  99. package/src/model/deepseek-model.test.ts +279 -0
  100. package/src/model/deepseek-model.ts +401 -0
  101. package/src/model/index.ts +7 -0
  102. package/src/model/ollama-model.test.ts +307 -0
  103. package/src/model/ollama-model.ts +356 -0
  104. package/src/model/openai-model.ts +327 -0
  105. package/src/model/response.ts +22 -0
  106. package/src/model/usage.ts +12 -0
  107. package/src/storage/base.ts +52 -0
  108. package/src/storage/file-system.test.ts +587 -0
  109. package/src/storage/file-system.ts +269 -0
  110. package/src/storage/index.ts +2 -0
  111. package/src/tool/base.ts +23 -0
  112. package/src/tool/bash.test.ts +174 -0
  113. package/src/tool/bash.ts +152 -0
  114. package/src/tool/edit.test.ts +83 -0
  115. package/src/tool/edit.ts +95 -0
  116. package/src/tool/glob.test.ts +63 -0
  117. package/src/tool/glob.ts +166 -0
  118. package/src/tool/grep.test.ts +74 -0
  119. package/src/tool/grep.ts +256 -0
  120. package/src/tool/index.ts +10 -0
  121. package/src/tool/read.test.ts +77 -0
  122. package/src/tool/read.ts +117 -0
  123. package/src/tool/response.ts +82 -0
  124. package/src/tool/task.test.ts +299 -0
  125. package/src/tool/task.ts +399 -0
  126. package/src/tool/toolkit.test.ts +636 -0
  127. package/src/tool/toolkit.ts +601 -0
  128. package/src/tool/write.test.ts +52 -0
  129. package/src/tool/write.ts +57 -0
  130. package/src/type/index.ts +52 -0
  131. package/tsconfig.build.json +4 -0
  132. package/tsconfig.cjs.json +11 -0
  133. package/tsconfig.esm.json +10 -0
  134. package/tsconfig.json +14 -0
  135. package/tsup.config.ts +20 -0
  136. package/typedoc.json +52 -0
@@ -0,0 +1,226 @@
1
+ import { z } from 'zod';
2
+
3
+ import { ChatResponse, StructuredResponse } from './response';
4
+ import { FormatterBase } from '../formatter';
5
+ import { getTextContent, Msg } from '../message';
6
+ import { ToolChoice, ToolInputSchema, ToolSchema } from '../type';
7
+
8
+ export interface ChatModelOptions {
9
+ modelName: string;
10
+ stream?: boolean;
11
+ maxRetries?: number;
12
+ fallbackModelName?: string;
13
+ formatter?: FormatterBase;
14
+ }
15
+
16
+ // The chat model call options interface
17
+ export interface ChatModelCallOptions {
18
+ messages: Msg[];
19
+ tools?: ToolSchema[];
20
+ toolChoice?: ToolChoice;
21
+
22
+ // The additional options can be added as needed
23
+ [key: string]: unknown;
24
+ }
25
+
26
+ export interface ChatModelCallStructuredOptions {
27
+ messages: Msg[];
28
+ schema: z.ZodObject;
29
+ }
30
+
31
+ // Internal API request options after formatting
32
+ export interface ChatModelRequestOptions<T> {
33
+ messages: T[];
34
+ tools?: ToolSchema[];
35
+ toolChoice?: ToolChoice;
36
+
37
+ // The additional options can be added as needed
38
+ [key: string]: unknown;
39
+ }
40
+
41
+ /**
42
+ * The base class for chat models.
43
+ */
44
+ export abstract class ChatModelBase {
45
+ public modelName: string;
46
+ public stream: boolean;
47
+ public maxRetries: number;
48
+ public fallbackModelName?: string;
49
+ public formatter?: FormatterBase;
50
+ /**
51
+ * Initializes a new instance of the ChatModelBase class.
52
+ *
53
+ * @param options - The chat model options, including model name, streaming option, max retries, fallback
54
+ * model name, and formatter.
55
+ *
56
+ * @param options.modelName
57
+ * @param options.stream
58
+ * @param options.maxRetries
59
+ * @param options.fallbackModelName
60
+ * @param options.formatter
61
+ */
62
+ protected constructor({
63
+ modelName,
64
+ stream,
65
+ maxRetries,
66
+ fallbackModelName,
67
+ formatter,
68
+ }: ChatModelOptions) {
69
+ this.modelName = modelName;
70
+ this.stream = stream ?? true;
71
+ this.maxRetries = maxRetries ?? 0;
72
+ this.fallbackModelName = fallbackModelName;
73
+ this.formatter = formatter;
74
+ }
75
+
76
+ /**
77
+ * Calls the chat model with the given messages.
78
+ * This is the main method to interact with the model.
79
+ *
80
+ * @param options - The chat model call options.
81
+ * @returns A promise that resolves to the model's response.
82
+ */
83
+ async call(
84
+ options: ChatModelCallOptions
85
+ ): Promise<ChatResponse | AsyncGenerator<ChatResponse>> {
86
+ // Format messages using the formatter if available
87
+ let formattedMessages: unknown[];
88
+ if (this.formatter) {
89
+ formattedMessages = await this.formatter.format({ msgs: options.messages });
90
+ } else {
91
+ // If no formatter is provided, pass messages as-is
92
+ formattedMessages = options.messages as unknown[];
93
+ }
94
+
95
+ const requestOptions: ChatModelRequestOptions<unknown> = {
96
+ ...options,
97
+ messages: formattedMessages,
98
+ };
99
+
100
+ let lastError: unknown;
101
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
102
+ try {
103
+ return await this._callAPI(this.modelName, requestOptions);
104
+ } catch (error) {
105
+ lastError = error;
106
+ if (attempt === this.maxRetries) {
107
+ throw error;
108
+ } else {
109
+ console.log(
110
+ `Attempt ${attempt + 1} failed for model ${this.modelName}. Retrying...`
111
+ );
112
+ }
113
+ }
114
+ }
115
+
116
+ // Use the fallback model if specified
117
+ if (this.fallbackModelName) {
118
+ console.log(
119
+ `Using fallback model ${this.fallbackModelName} after ${this.maxRetries} failed attempts.`
120
+ );
121
+ return await this._callAPI(this.fallbackModelName, requestOptions);
122
+ }
123
+
124
+ // This line should never be reached, but it ensures TypeScript knows the function always returns
125
+ throw lastError;
126
+ }
127
+
128
+ /**
129
+ * Abstract method to call the underlying API with the given parameters.
130
+ */
131
+ protected abstract _callAPI(
132
+ modelName: string,
133
+ options: ChatModelRequestOptions<unknown>
134
+ ): Promise<ChatResponse | AsyncGenerator<ChatResponse>>;
135
+
136
+ /**
137
+ * Format the AgentScope tool choice parameter to the expected API format.
138
+ *
139
+ * @param toolChoice - The tool choice option.
140
+ * @returns The formatted tool choice.
141
+ */
142
+ abstract _formatToolChoice(toolChoice: ToolChoice): unknown;
143
+
144
+ /**
145
+ * A heuristic method to count the number of the tokens
146
+ * Note the multimodal content is ignored in the token counting
147
+ * @param options
148
+ * @param options.messages
149
+ * @param options.tools
150
+ * @returns The estimated number of tokens in the input messages and tools.
151
+ */
152
+ async countTokens(options: { messages: Msg[]; tools?: ToolSchema[] }): Promise<number> {
153
+ let accText: string = '';
154
+ for (const msg of options.messages) {
155
+ accText += getTextContent(msg) || '';
156
+ }
157
+ if (options.tools) {
158
+ accText += JSON.stringify(options.tools);
159
+ }
160
+ const chineseMatches =
161
+ accText.match(/[\u4e00-\u9fff\u3400-\u4dbf\u{20000}-\u{2a6df}]/gu)?.length ?? 0;
162
+ const englishMatches = accText.match(/[a-zA-Z]+/g)?.length ?? 0;
163
+
164
+ return chineseMatches * 2 + englishMatches * 1.5;
165
+ }
166
+
167
+ /**
168
+ * Format the tool schemas to the expected API format.
169
+ * @param tools
170
+ * @returns The formatted tool schemas.
171
+ */
172
+ abstract _formatToolSchemas(tools: ToolSchema[]): unknown[];
173
+
174
+ /**
175
+ * A default implementation of the structured call method. For those supporting structured output, the model should
176
+ * override this method.
177
+ * @param options
178
+ * @returns The structured response from the model, which should conform to the provided Zod schema.
179
+ */
180
+ async callStructured(options: ChatModelCallStructuredOptions): Promise<StructuredResponse> {
181
+ // Prepare a tool schema that wraps the provided Zod schema
182
+ const toolSchema: ToolSchema = {
183
+ type: 'function',
184
+ function: {
185
+ name: 'GenerateStructuredResponse',
186
+ description: 'Generate required structured response by this toll.',
187
+ parameters: options.schema.toJSONSchema({
188
+ target: 'openapi-3.0',
189
+ }) as ToolInputSchema,
190
+ },
191
+ };
192
+
193
+ const res = await this.call({
194
+ messages: options.messages,
195
+ tools: [toolSchema],
196
+ toolChoice: 'GenerateStructuredResponse',
197
+ });
198
+
199
+ let completedResponse: ChatResponse;
200
+ if (this.stream) {
201
+ while (true) {
202
+ const { value, done } = await (res as AsyncGenerator<ChatResponse>).next();
203
+ if (done) {
204
+ completedResponse = value;
205
+ break;
206
+ }
207
+ }
208
+ } else {
209
+ completedResponse = res as ChatResponse;
210
+ }
211
+
212
+ // Find the tool call
213
+ for (const block of completedResponse.content) {
214
+ if (block.type === 'tool_call' && block.name === 'GenerateStructuredResponse') {
215
+ const structuredContent = JSON.parse(block.input);
216
+ return {
217
+ ...completedResponse,
218
+ content: structuredContent,
219
+ type: 'structured',
220
+ } as StructuredResponse;
221
+ }
222
+ }
223
+
224
+ throw new Error(`Failed to generate the structured response`);
225
+ }
226
+ }
@@ -0,0 +1,335 @@
1
+ import { DashScopeChatModel } from './dashscope-model';
2
+ import { ChatResponse } from './response';
3
+ import { createMsg } from '../message';
4
+
5
+ // Mock fetch for streaming responses
6
+ global.fetch = jest.fn();
7
+
8
+ describe('DashScopeChatModel', () => {
9
+ beforeEach(() => {
10
+ jest.clearAllMocks();
11
+ });
12
+
13
+ test('Test stream generation with delta output', async () => {
14
+ // Mock streaming response with multiple chunks
15
+ // Chunk 1: First part of thinking
16
+ // Chunk 2: Second part of thinking
17
+ // Chunk 3: Tool call metadata (id, name) + first part of arguments
18
+ // Chunk 4: Second part of arguments
19
+ // Chunk 5: Usage info
20
+ const mockStreamChunks = [
21
+ 'data:{"output":{"choices":[{"message":{"reasoning_content":"Mock thinking: Analyzing"}}]}}\n\n',
22
+ 'data:{"output":{"choices":[{"message":{"reasoning_content":" the weather query for Beijing"}}]}}\n\n',
23
+ 'data:{"output":{"choices":[{"message":{"tool_calls":[{"index":0,"id":"call-123","function":{"name":"get_current_weather","arguments":"{\\"location\\""}}]}}]}}\n\n',
24
+ 'data:{"output":{"choices":[{"message":{"tool_calls":[{"index":0,"function":{"arguments":":\\"Beijing\\"}"}}]}}]}}\n\n',
25
+ 'data:{"output":{"choices":[{"message":{}}]},"usage":{"input_tokens":100,"output_tokens":50}}\n\n',
26
+ ];
27
+
28
+ const mockReadableStream = new ReadableStream({
29
+ start(controller) {
30
+ mockStreamChunks.forEach(chunk =>
31
+ controller.enqueue(new TextEncoder().encode(chunk))
32
+ );
33
+ controller.close();
34
+ },
35
+ });
36
+
37
+ (global.fetch as jest.Mock).mockResolvedValue({
38
+ ok: true,
39
+ body: mockReadableStream,
40
+ });
41
+
42
+ const model = new DashScopeChatModel({
43
+ modelName: 'qwen3.5-plus',
44
+ apiKey: 'test-api-key',
45
+ stream: true,
46
+ thinkingConfig: {
47
+ enableThinking: true,
48
+ },
49
+ });
50
+
51
+ const res = await model.call({
52
+ messages: [
53
+ createMsg({
54
+ name: 'user',
55
+ role: 'user',
56
+ content: [
57
+ {
58
+ id: crypto.randomUUID(),
59
+ type: 'text',
60
+ text: "How's the weather today in Beijing?",
61
+ },
62
+ ],
63
+ }),
64
+ ],
65
+ tools: [
66
+ {
67
+ type: 'function',
68
+ function: {
69
+ name: 'get_current_weather',
70
+ description: 'Get the current weather in a given location',
71
+ parameters: {
72
+ type: 'object',
73
+ properties: {
74
+ location: {
75
+ type: 'string',
76
+ description: 'The city and state, e.g. San Francisco, CA',
77
+ },
78
+ },
79
+ required: ['location'],
80
+ },
81
+ },
82
+ },
83
+ ],
84
+ toolChoice: 'get_current_weather',
85
+ });
86
+
87
+ const generator = res as AsyncGenerator<ChatResponse, ChatResponse>;
88
+ let completeResponse: ChatResponse | undefined;
89
+ const yieldedChunks: ChatResponse[] = [];
90
+
91
+ // Manual iteration to capture both yielded and returned values
92
+ while (true) {
93
+ const result = await generator.next();
94
+ if (result.done) {
95
+ completeResponse = result.value;
96
+ break;
97
+ }
98
+ yieldedChunks.push(result.value);
99
+ }
100
+
101
+ // Verify we received 5 yielded chunks (before the final return)
102
+ expect(yieldedChunks.length).toBe(5);
103
+
104
+ // Chunk 1: First part of thinking
105
+ expect(yieldedChunks[0].content.length).toBe(1);
106
+ expect(yieldedChunks[0].content[0]).toMatchObject({
107
+ type: 'thinking',
108
+ thinking: 'Mock thinking: Analyzing',
109
+ });
110
+
111
+ // Chunk 2: Second part of thinking (delta)
112
+ expect(yieldedChunks[1].content.length).toBe(1);
113
+ expect(yieldedChunks[1].content[0]).toMatchObject({
114
+ type: 'thinking',
115
+ thinking: ' the weather query for Beijing',
116
+ });
117
+
118
+ // Chunk 3: Tool call with first part of arguments
119
+ expect(yieldedChunks[2].content.length).toBe(1);
120
+ expect(yieldedChunks[2].content[0]).toMatchObject({
121
+ type: 'tool_call',
122
+ name: 'get_current_weather',
123
+ id: 'call-123',
124
+ input: '{"location"',
125
+ });
126
+
127
+ // Chunk 4: Tool call with second part of arguments (delta)
128
+ expect(yieldedChunks[3].content.length).toBe(1);
129
+ expect(yieldedChunks[3].content[0]).toMatchObject({
130
+ type: 'tool_call',
131
+ name: 'get_current_weather',
132
+ id: 'call-123',
133
+ input: ':"Beijing"}',
134
+ });
135
+
136
+ // Chunk 5: Empty content with usage info
137
+ expect(yieldedChunks[4].content.length).toBe(0);
138
+ expect(yieldedChunks[4].usage).toBeDefined();
139
+ expect(yieldedChunks[4].usage?.inputTokens).toBe(100);
140
+ expect(yieldedChunks[4].usage?.outputTokens).toBe(50);
141
+
142
+ // Verify the final complete response has correct structure
143
+ expect(completeResponse.content.length).toBe(2);
144
+
145
+ // Check thinking block - should be complete after accumulation
146
+ const thinkingBlock = completeResponse.content.find(b => b.type === 'thinking');
147
+ expect(thinkingBlock).toBeDefined();
148
+ expect(thinkingBlock).toMatchObject({
149
+ type: 'thinking',
150
+ thinking: 'Mock thinking: Analyzing the weather query for Beijing',
151
+ });
152
+
153
+ // Check tool_call block - input should be complete after accumulation
154
+ const toolCallBlock = completeResponse.content.find(b => b.type === 'tool_call');
155
+ expect(toolCallBlock).toBeDefined();
156
+ expect(toolCallBlock).toMatchObject({
157
+ type: 'tool_call',
158
+ name: 'get_current_weather',
159
+ id: 'call-123',
160
+ input: '{"location":"Beijing"}',
161
+ });
162
+
163
+ // Verify usage
164
+ expect(completeResponse.usage).toBeDefined();
165
+ expect(completeResponse.usage?.inputTokens).toBe(100);
166
+ expect(completeResponse.usage?.outputTokens).toBe(50);
167
+ }, 10000);
168
+
169
+ test('Test non-streaming generation', async () => {
170
+ // Mock non-streaming response
171
+ const mockResponse = {
172
+ output: {
173
+ choices: [
174
+ {
175
+ message: {
176
+ reasoning_content:
177
+ 'Mock thinking: Analyzing the weather query for Beijing',
178
+ tool_calls: [
179
+ {
180
+ index: 0,
181
+ id: 'call-123',
182
+ function: {
183
+ name: 'get_current_weather',
184
+ arguments: '{"location":"Beijing"}',
185
+ },
186
+ },
187
+ ],
188
+ },
189
+ },
190
+ ],
191
+ },
192
+ usage: {
193
+ input_tokens: 100,
194
+ output_tokens: 50,
195
+ },
196
+ };
197
+
198
+ (global.fetch as jest.Mock).mockResolvedValue({
199
+ ok: true,
200
+ json: async () => mockResponse,
201
+ });
202
+
203
+ const model = new DashScopeChatModel({
204
+ modelName: 'qwen3.5-plus',
205
+ apiKey: 'test-api-key',
206
+ stream: false,
207
+ thinkingConfig: {
208
+ enableThinking: true,
209
+ },
210
+ });
211
+
212
+ const res = await model.call({
213
+ messages: [
214
+ createMsg({
215
+ name: 'user',
216
+ role: 'user',
217
+ content: [
218
+ {
219
+ id: crypto.randomUUID(),
220
+ type: 'text',
221
+ text: "How's the weather today in Beijing?",
222
+ },
223
+ ],
224
+ }),
225
+ ],
226
+ tools: [
227
+ {
228
+ type: 'function',
229
+ function: {
230
+ name: 'get_current_weather',
231
+ description: 'Get the current weather in a given location',
232
+ parameters: {
233
+ type: 'object',
234
+ properties: {
235
+ location: {
236
+ type: 'string',
237
+ description: 'The city and state, e.g. San Francisco, CA',
238
+ },
239
+ },
240
+ required: ['location'],
241
+ },
242
+ },
243
+ },
244
+ ],
245
+ toolChoice: 'get_current_weather',
246
+ });
247
+
248
+ const completeResponse = res as ChatResponse;
249
+
250
+ // Verify complete response structure
251
+ expect(completeResponse.content.length).toBe(2);
252
+
253
+ // Check thinking block
254
+ const thinkingBlock = completeResponse.content.find(b => b.type === 'thinking');
255
+ expect(thinkingBlock).toBeDefined();
256
+ expect(thinkingBlock).toMatchObject({
257
+ type: 'thinking',
258
+ thinking: 'Mock thinking: Analyzing the weather query for Beijing',
259
+ });
260
+
261
+ // Check tool_call block
262
+ const toolCallBlock = completeResponse.content.find(b => b.type === 'tool_call');
263
+ expect(toolCallBlock).toBeDefined();
264
+ expect(toolCallBlock).toMatchObject({
265
+ type: 'tool_call',
266
+ name: 'get_current_weather',
267
+ id: 'call-123',
268
+ input: '{"location":"Beijing"}',
269
+ });
270
+
271
+ // Verify usage
272
+ expect(completeResponse.usage).toBeDefined();
273
+ expect(completeResponse.usage?.inputTokens).toBe(100);
274
+ expect(completeResponse.usage?.outputTokens).toBe(50);
275
+ }, 10000);
276
+
277
+ test('Test formatToolChoice function', () => {
278
+ const model = new DashScopeChatModel({
279
+ modelName: 'qwen3.5-plus',
280
+ apiKey: 'test-api-key',
281
+ stream: false,
282
+ });
283
+
284
+ // Test 'auto' case
285
+ expect(model['_formatToolChoice']('auto')).toBe('auto');
286
+
287
+ // Test 'none' case
288
+ expect(model['_formatToolChoice']('none')).toBe('none');
289
+
290
+ // Test specific function name case
291
+ expect(model['_formatToolChoice']('get_current_weather')).toEqual({
292
+ type: 'function',
293
+ function: {
294
+ name: 'get_current_weather',
295
+ },
296
+ });
297
+
298
+ // Test undefined case (should default to 'auto')
299
+ expect(model['_formatToolChoice'](undefined)).toBe('auto');
300
+ });
301
+
302
+ test('Test formatToolSchemas function', () => {
303
+ const model = new DashScopeChatModel({
304
+ modelName: 'qwen3.5-plus',
305
+ apiKey: 'test-api-key',
306
+ stream: false,
307
+ });
308
+
309
+ const toolSchemas = [
310
+ {
311
+ type: 'function' as const,
312
+ function: {
313
+ name: 'get_current_weather',
314
+ description: 'Get the current weather in a given location',
315
+ parameters: {
316
+ type: 'object' as const,
317
+ properties: {
318
+ location: {
319
+ type: 'string',
320
+ description: 'The city and state, e.g. San Francisco, CA',
321
+ },
322
+ },
323
+ required: ['location'],
324
+ },
325
+ },
326
+ },
327
+ ];
328
+
329
+ // Test with tool schemas
330
+ expect(model['_formatToolSchemas'](toolSchemas)).toEqual(toolSchemas);
331
+
332
+ // Test with undefined (should return empty array)
333
+ expect(model['_formatToolSchemas'](undefined)).toEqual([]);
334
+ });
335
+ });