@robota-sdk/agent-provider 3.0.0-beta.64

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 (220) hide show
  1. package/LICENSE +21 -0
  2. package/dist/browser/index.d.ts +1104 -0
  3. package/dist/browser/index.d.ts.map +1 -0
  4. package/dist/browser/index.js +7 -0
  5. package/dist/browser/index.js.map +1 -0
  6. package/dist/loggers/index.cjs +1 -0
  7. package/dist/loggers/index.d.ts +151 -0
  8. package/dist/loggers/index.d.ts.map +1 -0
  9. package/dist/loggers/index.js +2 -0
  10. package/dist/loggers/index.js.map +1 -0
  11. package/dist/node/anthropic/index.cjs +1 -0
  12. package/dist/node/anthropic/index.d.ts +158 -0
  13. package/dist/node/anthropic/index.d.ts.map +1 -0
  14. package/dist/node/anthropic/index.js +1 -0
  15. package/dist/node/anthropic--1vgLC-e.js +5 -0
  16. package/dist/node/anthropic--1vgLC-e.js.map +1 -0
  17. package/dist/node/anthropic-BFQ6DSCP.cjs +4 -0
  18. package/dist/node/bytedance/index.cjs +1 -0
  19. package/dist/node/bytedance/index.d.ts +74 -0
  20. package/dist/node/bytedance/index.d.ts.map +1 -0
  21. package/dist/node/bytedance/index.js +1 -0
  22. package/dist/node/bytedance-C_0sF_pJ.js +2 -0
  23. package/dist/node/bytedance-C_0sF_pJ.js.map +1 -0
  24. package/dist/node/bytedance-DVPxqEiC.cjs +1 -0
  25. package/dist/node/chunk-Bmb41Sf3.cjs +1 -0
  26. package/dist/node/deepseek/index.cjs +1 -0
  27. package/dist/node/deepseek/index.d.ts +2 -0
  28. package/dist/node/deepseek/index.js +1 -0
  29. package/dist/node/deepseek-_8Ixx7rA.js +2 -0
  30. package/dist/node/deepseek-_8Ixx7rA.js.map +1 -0
  31. package/dist/node/deepseek-oA2Y6bD0.cjs +1 -0
  32. package/dist/node/gemini/index.cjs +1 -0
  33. package/dist/node/gemini/index.d.ts +173 -0
  34. package/dist/node/gemini/index.d.ts.map +1 -0
  35. package/dist/node/gemini/index.js +1 -0
  36. package/dist/node/gemini-Bh2U87MY.js +4 -0
  37. package/dist/node/gemini-Bh2U87MY.js.map +1 -0
  38. package/dist/node/gemini-DSaNCxZj.cjs +3 -0
  39. package/dist/node/gemma/index.cjs +1 -0
  40. package/dist/node/gemma/index.d.ts +2 -0
  41. package/dist/node/gemma/index.js +1 -0
  42. package/dist/node/gemma-Dp_AfCUR.js +2 -0
  43. package/dist/node/gemma-Dp_AfCUR.js.map +1 -0
  44. package/dist/node/gemma-G-Pf_PnX.cjs +1 -0
  45. package/dist/node/google/index.cjs +1 -0
  46. package/dist/node/google/index.d.ts +14 -0
  47. package/dist/node/google/index.d.ts.map +1 -0
  48. package/dist/node/google/index.js +2 -0
  49. package/dist/node/google/index.js.map +1 -0
  50. package/dist/node/index-B6PnlDMd.d.ts +82 -0
  51. package/dist/node/index-B6PnlDMd.d.ts.map +1 -0
  52. package/dist/node/index-B7UvPJcI.d.ts +315 -0
  53. package/dist/node/index-B7UvPJcI.d.ts.map +1 -0
  54. package/dist/node/index-BLPOTNb5.d.ts +98 -0
  55. package/dist/node/index-BLPOTNb5.d.ts.map +1 -0
  56. package/dist/node/index-BqixM_XD.d.ts +231 -0
  57. package/dist/node/index-BqixM_XD.d.ts.map +1 -0
  58. package/dist/node/index-C3beaqKO.d.ts +231 -0
  59. package/dist/node/index-C3beaqKO.d.ts.map +1 -0
  60. package/dist/node/index-Cp2XRh9G.d.ts +82 -0
  61. package/dist/node/index-Cp2XRh9G.d.ts.map +1 -0
  62. package/dist/node/index-DSv5xruI.d.ts +98 -0
  63. package/dist/node/index-DSv5xruI.d.ts.map +1 -0
  64. package/dist/node/index-w0bV1uaP.d.ts +315 -0
  65. package/dist/node/index-w0bV1uaP.d.ts.map +1 -0
  66. package/dist/node/index.cjs +1 -0
  67. package/dist/node/index.d.ts +8 -0
  68. package/dist/node/index.js +1 -0
  69. package/dist/node/openai/index.cjs +1 -0
  70. package/dist/node/openai/index.d.ts +2 -0
  71. package/dist/node/openai/index.js +1 -0
  72. package/dist/node/openai-CRQjg4xF.js +2 -0
  73. package/dist/node/openai-CRQjg4xF.js.map +1 -0
  74. package/dist/node/openai-compatible-BYfyY5lb.cjs +1 -0
  75. package/dist/node/openai-compatible-Dm4Sof9e.js +2 -0
  76. package/dist/node/openai-compatible-Dm4Sof9e.js.map +1 -0
  77. package/dist/node/openai-xWC6pY7r.cjs +1 -0
  78. package/dist/node/qwen/index.cjs +1 -0
  79. package/dist/node/qwen/index.d.ts +2 -0
  80. package/dist/node/qwen/index.js +1 -0
  81. package/dist/node/qwen-ChUZobTL.js +2 -0
  82. package/dist/node/qwen-ChUZobTL.js.map +1 -0
  83. package/dist/node/qwen-CjT71vSM.cjs +1 -0
  84. package/package.json +157 -0
  85. package/src/anthropic/__tests__/abort-streaming.test.ts +199 -0
  86. package/src/anthropic/__tests__/model-catalog-refresh.test.ts +92 -0
  87. package/src/anthropic/__tests__/provider-definition.test.ts +55 -0
  88. package/src/anthropic/__tests__/provider.test.ts +1357 -0
  89. package/src/anthropic/__tests__/response-parser.test.ts +326 -0
  90. package/src/anthropic/index.ts +22 -0
  91. package/src/anthropic/message-converter.ts +181 -0
  92. package/src/anthropic/model-catalog-refresh.ts +128 -0
  93. package/src/anthropic/parsers/response-parser.ts +184 -0
  94. package/src/anthropic/provider-definition.ts +93 -0
  95. package/src/anthropic/provider.ts +290 -0
  96. package/src/anthropic/streaming-handler.ts +204 -0
  97. package/src/anthropic/types/api-types.ts +158 -0
  98. package/src/anthropic/types.ts +79 -0
  99. package/src/bytedance/http-client.test.ts +288 -0
  100. package/src/bytedance/http-client.ts +163 -0
  101. package/src/bytedance/index.ts +2 -0
  102. package/src/bytedance/provider.spec.ts +320 -0
  103. package/src/bytedance/provider.ts +171 -0
  104. package/src/bytedance/status-mapper.test.ts +299 -0
  105. package/src/bytedance/status-mapper.ts +141 -0
  106. package/src/bytedance/types.ts +68 -0
  107. package/src/deepseek/defaults.ts +4 -0
  108. package/src/deepseek/index.ts +22 -0
  109. package/src/deepseek/model-catalog-refresh.test.ts +57 -0
  110. package/src/deepseek/model-catalog-refresh.ts +105 -0
  111. package/src/deepseek/model-catalog.ts +55 -0
  112. package/src/deepseek/provider-definition.test.ts +109 -0
  113. package/src/deepseek/provider-definition.ts +132 -0
  114. package/src/deepseek/provider.test.ts +324 -0
  115. package/src/deepseek/provider.ts +298 -0
  116. package/src/deepseek/types.ts +37 -0
  117. package/src/gemini/execution-helpers.ts +233 -0
  118. package/src/gemini/genai-transport.test.ts +208 -0
  119. package/src/gemini/image-operations.test.ts +448 -0
  120. package/src/gemini/image-operations.ts +261 -0
  121. package/src/gemini/index.ts +11 -0
  122. package/src/gemini/message-converter.test.ts +616 -0
  123. package/src/gemini/message-converter.ts +140 -0
  124. package/src/gemini/model-catalog-refresh.test.ts +107 -0
  125. package/src/gemini/model-catalog-refresh.ts +92 -0
  126. package/src/gemini/provider-definition.test.ts +70 -0
  127. package/src/gemini/provider-definition.ts +78 -0
  128. package/src/gemini/provider-extended.test.ts +898 -0
  129. package/src/gemini/provider.spec.ts +216 -0
  130. package/src/gemini/provider.ts +279 -0
  131. package/src/gemini/request-converter.ts +226 -0
  132. package/src/gemini/tool-schema-converter.ts +78 -0
  133. package/src/gemini/types/api-types.ts +235 -0
  134. package/src/gemini/types.ts +121 -0
  135. package/src/gemma/index.ts +5 -0
  136. package/src/gemma/message-factory.ts +38 -0
  137. package/src/gemma/provider-definition.test.ts +43 -0
  138. package/src/gemma/provider-definition.ts +84 -0
  139. package/src/gemma/provider-projection.ts +49 -0
  140. package/src/gemma/provider.test.ts +628 -0
  141. package/src/gemma/provider.ts +308 -0
  142. package/src/gemma/pseudo-command-envelope.ts +58 -0
  143. package/src/gemma/pseudo-tool-call-projector.ts +243 -0
  144. package/src/gemma/pseudo-tool-call-tag-parser.ts +153 -0
  145. package/src/gemma/pseudo-tool-call-types.ts +31 -0
  146. package/src/gemma/reasoning-projector.test.ts +52 -0
  147. package/src/gemma/reasoning-projector.ts +144 -0
  148. package/src/gemma/streaming-projection.ts +79 -0
  149. package/src/gemma/tool-call-argument-parser.ts +126 -0
  150. package/src/gemma/tool-call-projector.test.ts +227 -0
  151. package/src/gemma/tool-call-projector.ts +264 -0
  152. package/src/gemma/types.ts +27 -0
  153. package/src/google/index.ts +11 -0
  154. package/src/google/provider-compat.test.ts +19 -0
  155. package/src/google/provider-definition.ts +6 -0
  156. package/src/google/provider.ts +10 -0
  157. package/src/google/types.ts +5 -0
  158. package/src/index.ts +9 -0
  159. package/src/openai/adapter.test.ts +494 -0
  160. package/src/openai/adapter.ts +145 -0
  161. package/src/openai/chat-completions-chat.ts +189 -0
  162. package/src/openai/executor-integration.test.ts +206 -0
  163. package/src/openai/index.ts +21 -0
  164. package/src/openai/interfaces/payload-logger.ts +48 -0
  165. package/src/openai/loggers/console-payload-logger.test.ts +173 -0
  166. package/src/openai/loggers/console-payload-logger.ts +94 -0
  167. package/src/openai/loggers/console.ts +9 -0
  168. package/src/openai/loggers/file-payload-logger.test.ts +238 -0
  169. package/src/openai/loggers/file-payload-logger.ts +112 -0
  170. package/src/openai/loggers/file.ts +9 -0
  171. package/src/openai/loggers/index.ts +12 -0
  172. package/src/openai/loggers/sanitize-openai-log-data.test.ts +89 -0
  173. package/src/openai/loggers/sanitize-openai-log-data.ts +14 -0
  174. package/src/openai/message-converter.ts +22 -0
  175. package/src/openai/model-catalog-refresh.test.ts +92 -0
  176. package/src/openai/model-catalog-refresh.ts +115 -0
  177. package/src/openai/openai-request-format.ts +92 -0
  178. package/src/openai/parsers/response-parser.test.ts +407 -0
  179. package/src/openai/parsers/response-parser.ts +47 -0
  180. package/src/openai/provider-definition.test.ts +75 -0
  181. package/src/openai/provider-definition.ts +132 -0
  182. package/src/openai/provider.test.ts +1402 -0
  183. package/src/openai/provider.ts +237 -0
  184. package/src/openai/responses-chat.ts +258 -0
  185. package/src/openai/responses-converter.ts +112 -0
  186. package/src/openai/responses-parser.ts +285 -0
  187. package/src/openai/responses-stream-utils.ts +45 -0
  188. package/src/openai/responses-types.ts +195 -0
  189. package/src/openai/streaming/stream-assembler.ts +3 -0
  190. package/src/openai/streaming/stream-handler.test.ts +367 -0
  191. package/src/openai/streaming/stream-handler.ts +119 -0
  192. package/src/openai/types/api-types.ts +112 -0
  193. package/src/openai/types.ts +194 -0
  194. package/src/qwen/defaults.ts +26 -0
  195. package/src/qwen/index.ts +5 -0
  196. package/src/qwen/model-catalog-refresh.test.ts +91 -0
  197. package/src/qwen/model-catalog-refresh.ts +97 -0
  198. package/src/qwen/provider-capabilities.ts +34 -0
  199. package/src/qwen/provider-definition.test.ts +139 -0
  200. package/src/qwen/provider-definition.ts +173 -0
  201. package/src/qwen/provider-streaming-assembly.ts +40 -0
  202. package/src/qwen/provider.test.ts +640 -0
  203. package/src/qwen/provider.ts +293 -0
  204. package/src/qwen/responses-chat.ts +194 -0
  205. package/src/qwen/responses-converter.ts +104 -0
  206. package/src/qwen/responses-parser.ts +299 -0
  207. package/src/qwen/responses-stream-utils.ts +38 -0
  208. package/src/qwen/types.ts +228 -0
  209. package/src/shared/openai-compatible/endpoint-probe.test.ts +52 -0
  210. package/src/shared/openai-compatible/endpoint-probe.ts +43 -0
  211. package/src/shared/openai-compatible/index.ts +6 -0
  212. package/src/shared/openai-compatible/message-converter.test.ts +111 -0
  213. package/src/shared/openai-compatible/message-converter.ts +84 -0
  214. package/src/shared/openai-compatible/native-payload-observer.test.ts +43 -0
  215. package/src/shared/openai-compatible/native-payload-observer.ts +26 -0
  216. package/src/shared/openai-compatible/response-parser.test.ts +172 -0
  217. package/src/shared/openai-compatible/response-parser.ts +180 -0
  218. package/src/shared/openai-compatible/stream-assembler.test.ts +266 -0
  219. package/src/shared/openai-compatible/stream-assembler.ts +248 -0
  220. package/src/shared/openai-compatible/types.ts +59 -0
@@ -0,0 +1,293 @@
1
+ import OpenAI from 'openai';
2
+ import { AbstractAIProvider, SilentLogger } from '@robota-sdk/agent-core';
3
+ import type { IChatOptions, TTextDeltaCallback, TUniversalMessage } from '@robota-sdk/agent-core';
4
+ import {
5
+ assembleOpenAICompatibleStream,
6
+ convertToOpenAICompatibleMessages,
7
+ convertToOpenAICompatibleTools,
8
+ observeProviderNativeRawPayloadStream,
9
+ OpenAICompatibleResponseParser,
10
+ } from '../shared/openai-compatible/index.js';
11
+ import type { IOpenAICompatibleError } from '../shared/openai-compatible/index.js';
12
+ import {
13
+ DEFAULT_QWEN_PROVIDER_BASE_URL,
14
+ DEFAULT_QWEN_PROVIDER_RESPONSES_BASE_URL,
15
+ } from './defaults';
16
+ import { hasQwenBuiltInWebTools } from './responses-converter';
17
+ import { chatStreamWithQwenResponsesApi, chatWithQwenResponsesApi } from './responses-chat';
18
+ import { getQwenProviderCapabilities } from './provider-capabilities';
19
+ import { qwenChatWithStreamingAssembly } from './provider-streaming-assembly';
20
+ import type { IQwenProviderOptions } from './types';
21
+
22
+ export class QwenProvider extends AbstractAIProvider {
23
+ override readonly name = 'qwen';
24
+ override readonly version = '1.0.0';
25
+
26
+ private readonly client?: OpenAI;
27
+ private readonly responsesClient?: OpenAI;
28
+ private readonly options: IQwenProviderOptions;
29
+ private readonly responseParser: OpenAICompatibleResponseParser;
30
+
31
+ onTextDelta?: TTextDeltaCallback;
32
+
33
+ constructor(options: IQwenProviderOptions) {
34
+ super(options.logger || SilentLogger);
35
+ this.options = options;
36
+
37
+ if (options.executor) {
38
+ this.executor = options.executor;
39
+ }
40
+
41
+ if (!this.executor) {
42
+ if (options.client) {
43
+ this.client = options.client;
44
+ this.responsesClient = options.client;
45
+ } else if (options.apiKey) {
46
+ this.client = new OpenAI({
47
+ apiKey: options.apiKey,
48
+ baseURL: options.baseURL ?? DEFAULT_QWEN_PROVIDER_BASE_URL,
49
+ ...(options.timeout !== undefined && { timeout: options.timeout }),
50
+ });
51
+ this.responsesClient = new OpenAI({
52
+ apiKey: options.apiKey,
53
+ baseURL: options.responsesBaseURL ?? DEFAULT_QWEN_PROVIDER_RESPONSES_BASE_URL,
54
+ ...(options.timeout !== undefined && { timeout: options.timeout }),
55
+ });
56
+ } else {
57
+ throw new Error('Either Qwen client, apiKey, or executor is required');
58
+ }
59
+ }
60
+
61
+ this.responseParser = new OpenAICompatibleResponseParser({ logger: this.logger });
62
+ }
63
+
64
+ override async chat(
65
+ messages: TUniversalMessage[],
66
+ options?: IChatOptions,
67
+ ): Promise<TUniversalMessage> {
68
+ this.validateMessages(messages);
69
+ this.validateNativeWebTools(options?.nativeWebTools);
70
+
71
+ if (this.executor) {
72
+ return this.chatViaExecutor(messages, options);
73
+ }
74
+
75
+ if (this.shouldUseResponsesApi()) {
76
+ return this.chatViaResponsesApi(messages, options, this.getResponsesClient());
77
+ }
78
+
79
+ return this.chatViaChatCompletions(messages, options, this.getClient());
80
+ }
81
+
82
+ private async chatViaExecutor(
83
+ messages: TUniversalMessage[],
84
+ options: IChatOptions | undefined,
85
+ ): Promise<TUniversalMessage> {
86
+ try {
87
+ return await this.executeViaExecutorOrDirect(messages, options);
88
+ } catch (error) {
89
+ this.logger.error(
90
+ 'Qwen Provider executor chat error:',
91
+ error instanceof Error ? error.message : String(error),
92
+ );
93
+ throw error;
94
+ }
95
+ }
96
+
97
+ private async chatViaResponsesApi(
98
+ messages: TUniversalMessage[],
99
+ options: IChatOptions | undefined,
100
+ client: OpenAI,
101
+ ): Promise<TUniversalMessage> {
102
+ this.validateTools(options?.tools);
103
+ return chatWithQwenResponsesApi({
104
+ client,
105
+ messages,
106
+ chatOptions: options,
107
+ defaultModel: this.options.defaultModel,
108
+ builtInWebTools: this.options.builtInWebTools,
109
+ onTextDelta: this.onTextDelta,
110
+ });
111
+ }
112
+
113
+ private async chatViaChatCompletions(
114
+ messages: TUniversalMessage[],
115
+ options: IChatOptions | undefined,
116
+ client: OpenAI,
117
+ ): Promise<TUniversalMessage> {
118
+ try {
119
+ const requestParams = this.buildRequestParams(messages, options);
120
+ const textDeltaCb = options?.onTextDelta ?? this.onTextDelta;
121
+ if (textDeltaCb) {
122
+ return qwenChatWithStreamingAssembly(
123
+ client,
124
+ { ...requestParams, stream: true },
125
+ { ...options, onTextDelta: textDeltaCb },
126
+ );
127
+ }
128
+
129
+ options?.onProviderNativeRawPayload?.({
130
+ provider: 'qwen',
131
+ apiSurface: 'chat-completions',
132
+ payloadKind: 'request',
133
+ payload: requestParams,
134
+ });
135
+ const response = await client.chat.completions.create(requestParams);
136
+ options?.onProviderNativeRawPayload?.({
137
+ provider: 'qwen',
138
+ apiSurface: 'chat-completions',
139
+ payloadKind: 'response',
140
+ payload: response,
141
+ });
142
+ return this.responseParser.parseResponse(response);
143
+ } catch (error) {
144
+ const qwenError = error as IOpenAICompatibleError;
145
+ const errorMessage = qwenError.message || 'Qwen API request failed';
146
+ throw new Error(`Qwen chat failed: ${errorMessage}`);
147
+ }
148
+ }
149
+
150
+ override async *chatStream(
151
+ messages: TUniversalMessage[],
152
+ options?: IChatOptions,
153
+ ): AsyncIterable<TUniversalMessage> {
154
+ this.validateMessages(messages);
155
+ this.validateNativeWebTools(options?.nativeWebTools);
156
+
157
+ if (this.executor) {
158
+ try {
159
+ yield* this.executeStreamViaExecutorOrDirect(messages, options);
160
+ return;
161
+ } catch (error) {
162
+ this.logger.error(
163
+ 'Qwen Provider executor stream error:',
164
+ error instanceof Error ? error.message : String(error),
165
+ );
166
+ throw error;
167
+ }
168
+ }
169
+
170
+ if (!this.client) {
171
+ throw new Error(
172
+ 'Qwen client not available. Either provide a client/apiKey or use an executor.',
173
+ );
174
+ }
175
+
176
+ if (this.shouldUseResponsesApi()) {
177
+ this.validateTools(options?.tools);
178
+ yield* chatStreamWithQwenResponsesApi({
179
+ client: this.responsesClient,
180
+ messages,
181
+ chatOptions: options,
182
+ defaultModel: this.options.defaultModel,
183
+ builtInWebTools: this.options.builtInWebTools,
184
+ onTextDelta: this.onTextDelta,
185
+ });
186
+ return;
187
+ }
188
+
189
+ try {
190
+ const requestParams = this.buildStreamingRequestParams(messages, options);
191
+ options?.onProviderNativeRawPayload?.({
192
+ provider: 'qwen',
193
+ apiSurface: 'chat-completions',
194
+ payloadKind: 'request',
195
+ payload: requestParams,
196
+ });
197
+ const stream = await this.client.chat.completions.create(requestParams);
198
+
199
+ const observedStream = observeProviderNativeRawPayloadStream(stream, {
200
+ provider: 'qwen',
201
+ apiSurface: 'chat-completions',
202
+ onProviderNativeRawPayload: options?.onProviderNativeRawPayload,
203
+ });
204
+
205
+ for await (const chunk of this.streamWithAbort(observedStream, options?.signal)) {
206
+ const universalMessage = this.responseParser.parseStreamingChunk(chunk);
207
+ if (universalMessage) {
208
+ yield universalMessage;
209
+ }
210
+ }
211
+ } catch (error) {
212
+ const qwenError = error as IOpenAICompatibleError;
213
+ const errorMessage = qwenError.message || 'Qwen API request failed';
214
+ throw new Error(`Qwen stream failed: ${errorMessage}`);
215
+ }
216
+ }
217
+
218
+ override supportsTools(): boolean {
219
+ return true;
220
+ }
221
+
222
+ override getCapabilities() {
223
+ return getQwenProviderCapabilities(this.options);
224
+ }
225
+
226
+ override validateConfig(): boolean {
227
+ return (
228
+ !!this.client && !!this.options && (!this.shouldUseResponsesApi() || !!this.responsesClient)
229
+ );
230
+ }
231
+
232
+ override async dispose(): Promise<void> {
233
+ // OpenAI-compatible Qwen clients do not need explicit cleanup.
234
+ }
235
+
236
+ private buildRequestParams(
237
+ messages: TUniversalMessage[],
238
+ options: IChatOptions | undefined,
239
+ ): OpenAI.Chat.ChatCompletionCreateParamsNonStreaming {
240
+ this.validateTools(options?.tools);
241
+ const model = options?.model ?? this.options.defaultModel;
242
+ if (!model) {
243
+ throw new Error(
244
+ 'Model is required in chat options. Please specify a model in defaultModel configuration.',
245
+ );
246
+ }
247
+
248
+ const requestParams = {
249
+ model,
250
+ messages: convertToOpenAICompatibleMessages(messages),
251
+ ...(options?.temperature !== undefined && { temperature: options.temperature }),
252
+ ...(options?.maxTokens !== undefined && { max_tokens: options.maxTokens }),
253
+ ...(options?.tools && {
254
+ tools: convertToOpenAICompatibleTools(options.tools),
255
+ tool_choice: 'auto' as const,
256
+ }),
257
+ };
258
+
259
+ return requestParams as OpenAI.Chat.ChatCompletionCreateParamsNonStreaming;
260
+ }
261
+
262
+ private buildStreamingRequestParams(
263
+ messages: TUniversalMessage[],
264
+ options: IChatOptions | undefined,
265
+ ): OpenAI.Chat.ChatCompletionCreateParamsStreaming {
266
+ return {
267
+ ...this.buildRequestParams(messages, options),
268
+ stream: true,
269
+ } as OpenAI.Chat.ChatCompletionCreateParamsStreaming;
270
+ }
271
+
272
+ private shouldUseResponsesApi(): boolean {
273
+ return hasQwenBuiltInWebTools(this.options.builtInWebTools);
274
+ }
275
+
276
+ private getClient(): OpenAI {
277
+ if (!this.client) {
278
+ throw new Error(
279
+ 'Qwen client not available. Either provide a client/apiKey or use an executor.',
280
+ );
281
+ }
282
+
283
+ return this.client;
284
+ }
285
+
286
+ private getResponsesClient(): OpenAI {
287
+ if (!this.responsesClient) {
288
+ throw new Error('Qwen Responses client not available for built-in web tools.');
289
+ }
290
+
291
+ return this.responsesClient;
292
+ }
293
+ }
@@ -0,0 +1,194 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import type OpenAI from 'openai';
3
+ import type { IChatOptions, TTextDeltaCallback, TUniversalMessage } from '@robota-sdk/agent-core';
4
+ import {
5
+ observeProviderNativeRawPayloadStream,
6
+ type IOpenAICompatibleError,
7
+ } from '../shared/openai-compatible/index.js';
8
+ import {
9
+ buildQwenResponsesTools,
10
+ convertToQwenResponsesInput,
11
+ getQwenBuiltInWebToolNames,
12
+ } from './responses-converter';
13
+ import { assembleQwenResponsesStream, parseQwenResponsesResponse } from './responses-parser';
14
+ import type {
15
+ IQwenBuiltInWebToolsOptions,
16
+ IQwenResponsesRequestNonStreaming,
17
+ IQwenResponsesRequestStreaming,
18
+ TQwenResponsesStreamEvent,
19
+ } from './types';
20
+
21
+ export interface IQwenResponsesChatOptions {
22
+ client?: OpenAI;
23
+ messages: TUniversalMessage[];
24
+ chatOptions?: IChatOptions;
25
+ defaultModel?: string;
26
+ builtInWebTools?: IQwenBuiltInWebToolsOptions;
27
+ onTextDelta?: TTextDeltaCallback;
28
+ }
29
+
30
+ export async function chatWithQwenResponsesApi(
31
+ input: IQwenResponsesChatOptions,
32
+ ): Promise<TUniversalMessage> {
33
+ const textDeltaCb = input.chatOptions?.onTextDelta ?? input.onTextDelta;
34
+ if (textDeltaCb) {
35
+ return chatWithQwenResponsesStreamingAssembly({
36
+ ...input,
37
+ chatOptions: {
38
+ ...input.chatOptions,
39
+ onTextDelta: textDeltaCb,
40
+ },
41
+ });
42
+ }
43
+
44
+ if (!input.client) {
45
+ throw new Error('Qwen Responses client not available for built-in web tools.');
46
+ }
47
+
48
+ try {
49
+ const requestParams = buildResponsesRequestParams(input);
50
+ input.chatOptions?.onProviderNativeRawPayload?.({
51
+ provider: 'qwen',
52
+ apiSurface: 'responses',
53
+ payloadKind: 'request',
54
+ payload: requestParams,
55
+ });
56
+ const response = await input.client.responses.create(
57
+ requestParams as OpenAI.Responses.ResponseCreateParamsNonStreaming,
58
+ input.chatOptions?.signal ? { signal: input.chatOptions.signal } : undefined,
59
+ );
60
+ input.chatOptions?.onProviderNativeRawPayload?.({
61
+ provider: 'qwen',
62
+ apiSurface: 'responses',
63
+ payloadKind: 'response',
64
+ payload: response,
65
+ });
66
+ return parseQwenResponsesResponse(response, {
67
+ enabledBuiltInTools: getQwenBuiltInWebToolNames(input.builtInWebTools),
68
+ });
69
+ } catch (error) {
70
+ const qwenError = error as IOpenAICompatibleError;
71
+ const errorMessage = qwenError.message || 'Qwen Responses API request failed';
72
+ throw new Error(`Qwen responses failed: ${errorMessage}`);
73
+ }
74
+ }
75
+
76
+ export async function* chatStreamWithQwenResponsesApi(
77
+ input: IQwenResponsesChatOptions,
78
+ ): AsyncIterable<TUniversalMessage> {
79
+ const deltas: TUniversalMessage[] = [];
80
+ const result = await chatWithQwenResponsesStreamingAssembly({
81
+ ...input,
82
+ chatOptions: {
83
+ ...input.chatOptions,
84
+ onTextDelta: (delta) => {
85
+ input.chatOptions?.onTextDelta?.(delta);
86
+ deltas.push(createStreamDeltaMessage(delta));
87
+ },
88
+ },
89
+ });
90
+
91
+ for (const delta of deltas) {
92
+ yield delta;
93
+ }
94
+ yield {
95
+ ...result,
96
+ content: '',
97
+ metadata: {
98
+ ...result.metadata,
99
+ isStreamChunk: true,
100
+ isComplete: true,
101
+ },
102
+ };
103
+ }
104
+
105
+ async function chatWithQwenResponsesStreamingAssembly(
106
+ input: IQwenResponsesChatOptions,
107
+ ): Promise<TUniversalMessage> {
108
+ if (!input.client) {
109
+ throw new Error('Qwen Responses client not available for built-in web tools.');
110
+ }
111
+
112
+ try {
113
+ const requestParams = buildResponsesStreamingRequestParams(input);
114
+ input.chatOptions?.onProviderNativeRawPayload?.({
115
+ provider: 'qwen',
116
+ apiSurface: 'responses',
117
+ payloadKind: 'request',
118
+ payload: requestParams,
119
+ });
120
+ const stream = await input.client.responses.create(
121
+ requestParams as OpenAI.Responses.ResponseCreateParamsStreaming,
122
+ input.chatOptions?.signal ? { signal: input.chatOptions.signal } : undefined,
123
+ );
124
+ return assembleQwenResponsesStream({
125
+ stream: observeProviderNativeRawPayloadStream(
126
+ stream as AsyncIterable<TQwenResponsesStreamEvent>,
127
+ {
128
+ provider: 'qwen',
129
+ apiSurface: 'responses',
130
+ onProviderNativeRawPayload: input.chatOptions?.onProviderNativeRawPayload,
131
+ },
132
+ ),
133
+ enabledBuiltInTools: getQwenBuiltInWebToolNames(input.builtInWebTools),
134
+ onTextDelta: input.chatOptions?.onTextDelta,
135
+ signal: input.chatOptions?.signal,
136
+ });
137
+ } catch (error) {
138
+ const qwenError = error as IOpenAICompatibleError;
139
+ const errorMessage = qwenError.message || 'Qwen Responses streaming request failed';
140
+ throw new Error(`Qwen responses stream failed: ${errorMessage}`);
141
+ }
142
+ }
143
+
144
+ function buildResponsesRequestParams(
145
+ input: IQwenResponsesChatOptions,
146
+ ): IQwenResponsesRequestNonStreaming {
147
+ const model = input.chatOptions?.model ?? input.defaultModel;
148
+ if (!model) {
149
+ throw new Error(
150
+ 'Model is required in chat options. Please specify a model in defaultModel configuration.',
151
+ );
152
+ }
153
+
154
+ const enabledBuiltInTools = getQwenBuiltInWebToolNames(input.builtInWebTools);
155
+ const tools = buildQwenResponsesTools(enabledBuiltInTools, input.chatOptions?.tools);
156
+
157
+ return {
158
+ model,
159
+ input: convertToQwenResponsesInput(input.messages),
160
+ ...(tools !== undefined && { tools }),
161
+ ...(input.chatOptions?.temperature !== undefined && {
162
+ temperature: input.chatOptions.temperature,
163
+ }),
164
+ ...(input.chatOptions?.maxTokens !== undefined && {
165
+ max_output_tokens: input.chatOptions.maxTokens,
166
+ }),
167
+ ...(input.builtInWebTools?.enableThinking !== undefined && {
168
+ enable_thinking: input.builtInWebTools.enableThinking,
169
+ }),
170
+ };
171
+ }
172
+
173
+ function buildResponsesStreamingRequestParams(
174
+ input: IQwenResponsesChatOptions,
175
+ ): IQwenResponsesRequestStreaming {
176
+ return {
177
+ ...buildResponsesRequestParams(input),
178
+ stream: true,
179
+ };
180
+ }
181
+
182
+ function createStreamDeltaMessage(delta: string): TUniversalMessage {
183
+ return {
184
+ id: randomUUID(),
185
+ role: 'assistant',
186
+ content: delta,
187
+ state: 'complete',
188
+ timestamp: new Date(),
189
+ metadata: {
190
+ isStreamChunk: true,
191
+ isComplete: false,
192
+ },
193
+ };
194
+ }
@@ -0,0 +1,104 @@
1
+ import type { IAssistantMessage, IToolSchema, TUniversalMessage } from '@robota-sdk/agent-core';
2
+ import type {
3
+ IQwenBuiltInWebToolsOptions,
4
+ IQwenResponsesFunctionTool,
5
+ IQwenResponsesMessageInput,
6
+ TQwenBuiltInWebToolName,
7
+ TQwenResponsesInputItem,
8
+ TQwenResponsesTool,
9
+ } from './types';
10
+
11
+ export function getQwenBuiltInWebToolNames(
12
+ options: IQwenBuiltInWebToolsOptions | undefined,
13
+ ): TQwenBuiltInWebToolName[] {
14
+ const tools: TQwenBuiltInWebToolName[] = [];
15
+ if (options?.webSearch === true || options?.webFetch === true) {
16
+ tools.push('web_search');
17
+ }
18
+ if (options?.webFetch === true) {
19
+ tools.push('web_extractor');
20
+ }
21
+ return tools;
22
+ }
23
+
24
+ export function hasQwenBuiltInWebTools(options: IQwenBuiltInWebToolsOptions | undefined): boolean {
25
+ return getQwenBuiltInWebToolNames(options).length > 0;
26
+ }
27
+
28
+ export function convertToQwenResponsesInput(
29
+ messages: TUniversalMessage[],
30
+ ): TQwenResponsesInputItem[] {
31
+ return messages.flatMap((message) => convertMessage(message));
32
+ }
33
+
34
+ export function buildQwenResponsesTools(
35
+ builtInWebTools: readonly TQwenBuiltInWebToolName[],
36
+ localTools: IToolSchema[] | undefined,
37
+ ): TQwenResponsesTool[] | undefined {
38
+ const tools: TQwenResponsesTool[] = [
39
+ ...builtInWebTools.map((type) => ({ type })),
40
+ ...convertToQwenResponsesFunctionTools(localTools),
41
+ ];
42
+ return tools.length > 0 ? tools : undefined;
43
+ }
44
+
45
+ function convertToQwenResponsesFunctionTools(
46
+ tools: IToolSchema[] | undefined,
47
+ ): IQwenResponsesFunctionTool[] {
48
+ return (
49
+ tools?.map((tool) => ({
50
+ type: 'function',
51
+ name: tool.name,
52
+ description: tool.description,
53
+ parameters: tool.parameters,
54
+ })) ?? []
55
+ );
56
+ }
57
+
58
+ function convertMessage(message: TUniversalMessage): TQwenResponsesInputItem[] {
59
+ if (message.role === 'user') {
60
+ return [createMessageInput('user', message.content)];
61
+ }
62
+ if (message.role === 'system') {
63
+ return [createMessageInput('system', message.content)];
64
+ }
65
+ if (message.role === 'tool') {
66
+ return [
67
+ {
68
+ type: 'function_call_output',
69
+ call_id: message.toolCallId,
70
+ output: message.content || '',
71
+ },
72
+ ];
73
+ }
74
+ return convertAssistantMessage(message);
75
+ }
76
+
77
+ function convertAssistantMessage(message: IAssistantMessage): TQwenResponsesInputItem[] {
78
+ const items: TQwenResponsesInputItem[] = [];
79
+ if (message.content && message.content.length > 0) {
80
+ items.push(createMessageInput('assistant', message.content));
81
+ }
82
+ for (const toolCall of message.toolCalls ?? []) {
83
+ items.push({
84
+ type: 'function_call',
85
+ call_id: toolCall.id,
86
+ name: toolCall.function.name,
87
+ arguments: toolCall.function.arguments,
88
+ });
89
+ }
90
+ if (items.length === 0) {
91
+ items.push(createMessageInput('assistant', ''));
92
+ }
93
+ return items;
94
+ }
95
+
96
+ function createMessageInput(
97
+ role: IQwenResponsesMessageInput['role'],
98
+ content: string | null,
99
+ ): IQwenResponsesMessageInput {
100
+ return {
101
+ role,
102
+ content: content ?? '',
103
+ };
104
+ }