@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.
- package/LICENSE +21 -0
- package/dist/browser/index.d.ts +1104 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +7 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/loggers/index.cjs +1 -0
- package/dist/loggers/index.d.ts +151 -0
- package/dist/loggers/index.d.ts.map +1 -0
- package/dist/loggers/index.js +2 -0
- package/dist/loggers/index.js.map +1 -0
- package/dist/node/anthropic/index.cjs +1 -0
- package/dist/node/anthropic/index.d.ts +158 -0
- package/dist/node/anthropic/index.d.ts.map +1 -0
- package/dist/node/anthropic/index.js +1 -0
- package/dist/node/anthropic--1vgLC-e.js +5 -0
- package/dist/node/anthropic--1vgLC-e.js.map +1 -0
- package/dist/node/anthropic-BFQ6DSCP.cjs +4 -0
- package/dist/node/bytedance/index.cjs +1 -0
- package/dist/node/bytedance/index.d.ts +74 -0
- package/dist/node/bytedance/index.d.ts.map +1 -0
- package/dist/node/bytedance/index.js +1 -0
- package/dist/node/bytedance-C_0sF_pJ.js +2 -0
- package/dist/node/bytedance-C_0sF_pJ.js.map +1 -0
- package/dist/node/bytedance-DVPxqEiC.cjs +1 -0
- package/dist/node/chunk-Bmb41Sf3.cjs +1 -0
- package/dist/node/deepseek/index.cjs +1 -0
- package/dist/node/deepseek/index.d.ts +2 -0
- package/dist/node/deepseek/index.js +1 -0
- package/dist/node/deepseek-_8Ixx7rA.js +2 -0
- package/dist/node/deepseek-_8Ixx7rA.js.map +1 -0
- package/dist/node/deepseek-oA2Y6bD0.cjs +1 -0
- package/dist/node/gemini/index.cjs +1 -0
- package/dist/node/gemini/index.d.ts +173 -0
- package/dist/node/gemini/index.d.ts.map +1 -0
- package/dist/node/gemini/index.js +1 -0
- package/dist/node/gemini-Bh2U87MY.js +4 -0
- package/dist/node/gemini-Bh2U87MY.js.map +1 -0
- package/dist/node/gemini-DSaNCxZj.cjs +3 -0
- package/dist/node/gemma/index.cjs +1 -0
- package/dist/node/gemma/index.d.ts +2 -0
- package/dist/node/gemma/index.js +1 -0
- package/dist/node/gemma-Dp_AfCUR.js +2 -0
- package/dist/node/gemma-Dp_AfCUR.js.map +1 -0
- package/dist/node/gemma-G-Pf_PnX.cjs +1 -0
- package/dist/node/google/index.cjs +1 -0
- package/dist/node/google/index.d.ts +14 -0
- package/dist/node/google/index.d.ts.map +1 -0
- package/dist/node/google/index.js +2 -0
- package/dist/node/google/index.js.map +1 -0
- package/dist/node/index-B6PnlDMd.d.ts +82 -0
- package/dist/node/index-B6PnlDMd.d.ts.map +1 -0
- package/dist/node/index-B7UvPJcI.d.ts +315 -0
- package/dist/node/index-B7UvPJcI.d.ts.map +1 -0
- package/dist/node/index-BLPOTNb5.d.ts +98 -0
- package/dist/node/index-BLPOTNb5.d.ts.map +1 -0
- package/dist/node/index-BqixM_XD.d.ts +231 -0
- package/dist/node/index-BqixM_XD.d.ts.map +1 -0
- package/dist/node/index-C3beaqKO.d.ts +231 -0
- package/dist/node/index-C3beaqKO.d.ts.map +1 -0
- package/dist/node/index-Cp2XRh9G.d.ts +82 -0
- package/dist/node/index-Cp2XRh9G.d.ts.map +1 -0
- package/dist/node/index-DSv5xruI.d.ts +98 -0
- package/dist/node/index-DSv5xruI.d.ts.map +1 -0
- package/dist/node/index-w0bV1uaP.d.ts +315 -0
- package/dist/node/index-w0bV1uaP.d.ts.map +1 -0
- package/dist/node/index.cjs +1 -0
- package/dist/node/index.d.ts +8 -0
- package/dist/node/index.js +1 -0
- package/dist/node/openai/index.cjs +1 -0
- package/dist/node/openai/index.d.ts +2 -0
- package/dist/node/openai/index.js +1 -0
- package/dist/node/openai-CRQjg4xF.js +2 -0
- package/dist/node/openai-CRQjg4xF.js.map +1 -0
- package/dist/node/openai-compatible-BYfyY5lb.cjs +1 -0
- package/dist/node/openai-compatible-Dm4Sof9e.js +2 -0
- package/dist/node/openai-compatible-Dm4Sof9e.js.map +1 -0
- package/dist/node/openai-xWC6pY7r.cjs +1 -0
- package/dist/node/qwen/index.cjs +1 -0
- package/dist/node/qwen/index.d.ts +2 -0
- package/dist/node/qwen/index.js +1 -0
- package/dist/node/qwen-ChUZobTL.js +2 -0
- package/dist/node/qwen-ChUZobTL.js.map +1 -0
- package/dist/node/qwen-CjT71vSM.cjs +1 -0
- package/package.json +157 -0
- package/src/anthropic/__tests__/abort-streaming.test.ts +199 -0
- package/src/anthropic/__tests__/model-catalog-refresh.test.ts +92 -0
- package/src/anthropic/__tests__/provider-definition.test.ts +55 -0
- package/src/anthropic/__tests__/provider.test.ts +1357 -0
- package/src/anthropic/__tests__/response-parser.test.ts +326 -0
- package/src/anthropic/index.ts +22 -0
- package/src/anthropic/message-converter.ts +181 -0
- package/src/anthropic/model-catalog-refresh.ts +128 -0
- package/src/anthropic/parsers/response-parser.ts +184 -0
- package/src/anthropic/provider-definition.ts +93 -0
- package/src/anthropic/provider.ts +290 -0
- package/src/anthropic/streaming-handler.ts +204 -0
- package/src/anthropic/types/api-types.ts +158 -0
- package/src/anthropic/types.ts +79 -0
- package/src/bytedance/http-client.test.ts +288 -0
- package/src/bytedance/http-client.ts +163 -0
- package/src/bytedance/index.ts +2 -0
- package/src/bytedance/provider.spec.ts +320 -0
- package/src/bytedance/provider.ts +171 -0
- package/src/bytedance/status-mapper.test.ts +299 -0
- package/src/bytedance/status-mapper.ts +141 -0
- package/src/bytedance/types.ts +68 -0
- package/src/deepseek/defaults.ts +4 -0
- package/src/deepseek/index.ts +22 -0
- package/src/deepseek/model-catalog-refresh.test.ts +57 -0
- package/src/deepseek/model-catalog-refresh.ts +105 -0
- package/src/deepseek/model-catalog.ts +55 -0
- package/src/deepseek/provider-definition.test.ts +109 -0
- package/src/deepseek/provider-definition.ts +132 -0
- package/src/deepseek/provider.test.ts +324 -0
- package/src/deepseek/provider.ts +298 -0
- package/src/deepseek/types.ts +37 -0
- package/src/gemini/execution-helpers.ts +233 -0
- package/src/gemini/genai-transport.test.ts +208 -0
- package/src/gemini/image-operations.test.ts +448 -0
- package/src/gemini/image-operations.ts +261 -0
- package/src/gemini/index.ts +11 -0
- package/src/gemini/message-converter.test.ts +616 -0
- package/src/gemini/message-converter.ts +140 -0
- package/src/gemini/model-catalog-refresh.test.ts +107 -0
- package/src/gemini/model-catalog-refresh.ts +92 -0
- package/src/gemini/provider-definition.test.ts +70 -0
- package/src/gemini/provider-definition.ts +78 -0
- package/src/gemini/provider-extended.test.ts +898 -0
- package/src/gemini/provider.spec.ts +216 -0
- package/src/gemini/provider.ts +279 -0
- package/src/gemini/request-converter.ts +226 -0
- package/src/gemini/tool-schema-converter.ts +78 -0
- package/src/gemini/types/api-types.ts +235 -0
- package/src/gemini/types.ts +121 -0
- package/src/gemma/index.ts +5 -0
- package/src/gemma/message-factory.ts +38 -0
- package/src/gemma/provider-definition.test.ts +43 -0
- package/src/gemma/provider-definition.ts +84 -0
- package/src/gemma/provider-projection.ts +49 -0
- package/src/gemma/provider.test.ts +628 -0
- package/src/gemma/provider.ts +308 -0
- package/src/gemma/pseudo-command-envelope.ts +58 -0
- package/src/gemma/pseudo-tool-call-projector.ts +243 -0
- package/src/gemma/pseudo-tool-call-tag-parser.ts +153 -0
- package/src/gemma/pseudo-tool-call-types.ts +31 -0
- package/src/gemma/reasoning-projector.test.ts +52 -0
- package/src/gemma/reasoning-projector.ts +144 -0
- package/src/gemma/streaming-projection.ts +79 -0
- package/src/gemma/tool-call-argument-parser.ts +126 -0
- package/src/gemma/tool-call-projector.test.ts +227 -0
- package/src/gemma/tool-call-projector.ts +264 -0
- package/src/gemma/types.ts +27 -0
- package/src/google/index.ts +11 -0
- package/src/google/provider-compat.test.ts +19 -0
- package/src/google/provider-definition.ts +6 -0
- package/src/google/provider.ts +10 -0
- package/src/google/types.ts +5 -0
- package/src/index.ts +9 -0
- package/src/openai/adapter.test.ts +494 -0
- package/src/openai/adapter.ts +145 -0
- package/src/openai/chat-completions-chat.ts +189 -0
- package/src/openai/executor-integration.test.ts +206 -0
- package/src/openai/index.ts +21 -0
- package/src/openai/interfaces/payload-logger.ts +48 -0
- package/src/openai/loggers/console-payload-logger.test.ts +173 -0
- package/src/openai/loggers/console-payload-logger.ts +94 -0
- package/src/openai/loggers/console.ts +9 -0
- package/src/openai/loggers/file-payload-logger.test.ts +238 -0
- package/src/openai/loggers/file-payload-logger.ts +112 -0
- package/src/openai/loggers/file.ts +9 -0
- package/src/openai/loggers/index.ts +12 -0
- package/src/openai/loggers/sanitize-openai-log-data.test.ts +89 -0
- package/src/openai/loggers/sanitize-openai-log-data.ts +14 -0
- package/src/openai/message-converter.ts +22 -0
- package/src/openai/model-catalog-refresh.test.ts +92 -0
- package/src/openai/model-catalog-refresh.ts +115 -0
- package/src/openai/openai-request-format.ts +92 -0
- package/src/openai/parsers/response-parser.test.ts +407 -0
- package/src/openai/parsers/response-parser.ts +47 -0
- package/src/openai/provider-definition.test.ts +75 -0
- package/src/openai/provider-definition.ts +132 -0
- package/src/openai/provider.test.ts +1402 -0
- package/src/openai/provider.ts +237 -0
- package/src/openai/responses-chat.ts +258 -0
- package/src/openai/responses-converter.ts +112 -0
- package/src/openai/responses-parser.ts +285 -0
- package/src/openai/responses-stream-utils.ts +45 -0
- package/src/openai/responses-types.ts +195 -0
- package/src/openai/streaming/stream-assembler.ts +3 -0
- package/src/openai/streaming/stream-handler.test.ts +367 -0
- package/src/openai/streaming/stream-handler.ts +119 -0
- package/src/openai/types/api-types.ts +112 -0
- package/src/openai/types.ts +194 -0
- package/src/qwen/defaults.ts +26 -0
- package/src/qwen/index.ts +5 -0
- package/src/qwen/model-catalog-refresh.test.ts +91 -0
- package/src/qwen/model-catalog-refresh.ts +97 -0
- package/src/qwen/provider-capabilities.ts +34 -0
- package/src/qwen/provider-definition.test.ts +139 -0
- package/src/qwen/provider-definition.ts +173 -0
- package/src/qwen/provider-streaming-assembly.ts +40 -0
- package/src/qwen/provider.test.ts +640 -0
- package/src/qwen/provider.ts +293 -0
- package/src/qwen/responses-chat.ts +194 -0
- package/src/qwen/responses-converter.ts +104 -0
- package/src/qwen/responses-parser.ts +299 -0
- package/src/qwen/responses-stream-utils.ts +38 -0
- package/src/qwen/types.ts +228 -0
- package/src/shared/openai-compatible/endpoint-probe.test.ts +52 -0
- package/src/shared/openai-compatible/endpoint-probe.ts +43 -0
- package/src/shared/openai-compatible/index.ts +6 -0
- package/src/shared/openai-compatible/message-converter.test.ts +111 -0
- package/src/shared/openai-compatible/message-converter.ts +84 -0
- package/src/shared/openai-compatible/native-payload-observer.test.ts +43 -0
- package/src/shared/openai-compatible/native-payload-observer.ts +26 -0
- package/src/shared/openai-compatible/response-parser.test.ts +172 -0
- package/src/shared/openai-compatible/response-parser.ts +180 -0
- package/src/shared/openai-compatible/stream-assembler.test.ts +266 -0
- package/src/shared/openai-compatible/stream-assembler.ts +248 -0
- package/src/shared/openai-compatible/types.ts +59 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
|
|
3
|
+
import type { IPayloadLogger } from './interfaces/payload-logger';
|
|
4
|
+
import type { IExecutor, ILogger, TProviderOptionValueBase } from '@robota-sdk/agent-core';
|
|
5
|
+
|
|
6
|
+
export type TOpenAIApiSurface = 'responses' | 'chat-completions';
|
|
7
|
+
|
|
8
|
+
export interface IOpenAIJsonSchemaDefinition {
|
|
9
|
+
name: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
schema?: Record<string, TOpenAIProviderOptionValue>;
|
|
12
|
+
strict?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface IOpenAIResponsesReasoningOptions {
|
|
16
|
+
effort?: 'low' | 'medium' | 'high';
|
|
17
|
+
summary?: 'auto' | 'concise' | 'detailed';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface IOpenAINativeWebToolsOptions {
|
|
21
|
+
webSearch?: boolean;
|
|
22
|
+
webFetch?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Valid provider option value types
|
|
27
|
+
*/
|
|
28
|
+
export type TOpenAIProviderOptionValue =
|
|
29
|
+
| string
|
|
30
|
+
| number
|
|
31
|
+
| boolean
|
|
32
|
+
| undefined
|
|
33
|
+
| null
|
|
34
|
+
| IOpenAIJsonSchemaDefinition
|
|
35
|
+
| IOpenAINativeWebToolsOptions
|
|
36
|
+
| IOpenAIResponsesReasoningOptions
|
|
37
|
+
| OpenAI
|
|
38
|
+
| IPayloadLogger
|
|
39
|
+
| ILogger
|
|
40
|
+
| IExecutor
|
|
41
|
+
| TProviderOptionValueBase
|
|
42
|
+
| TOpenAIProviderOptionValue[]
|
|
43
|
+
| { [key: string]: TOpenAIProviderOptionValue };
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* OpenAI provider options
|
|
47
|
+
*/
|
|
48
|
+
export interface IOpenAIProviderOptions {
|
|
49
|
+
/**
|
|
50
|
+
* Additional provider-specific options
|
|
51
|
+
*/
|
|
52
|
+
[key: string]: TOpenAIProviderOptionValue;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* OpenAI API key (required when client is not provided)
|
|
56
|
+
*/
|
|
57
|
+
apiKey?: string;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* OpenAI organization ID (optional)
|
|
61
|
+
*/
|
|
62
|
+
organization?: string;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* API request timeout (milliseconds)
|
|
66
|
+
*/
|
|
67
|
+
timeout?: number;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* API base URL (default: 'https://api.openai.com/v1')
|
|
71
|
+
*/
|
|
72
|
+
baseURL?: string;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Default model used when chat options do not provide a model.
|
|
76
|
+
*/
|
|
77
|
+
defaultModel?: string;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* API surface to use for direct OpenAI calls.
|
|
81
|
+
*
|
|
82
|
+
* Defaults to Responses for official OpenAI calls. Profiles with baseURL use
|
|
83
|
+
* Chat Completions by default for OpenAI-compatible endpoint compatibility.
|
|
84
|
+
*/
|
|
85
|
+
apiSurface?: TOpenAIApiSurface;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Response format (default: 'text')
|
|
89
|
+
* - 'text': Plain text response
|
|
90
|
+
* - 'json_object': JSON object mode (requires system message)
|
|
91
|
+
* - 'json_schema': Structured Outputs with schema validation
|
|
92
|
+
*/
|
|
93
|
+
responseFormat?: 'text' | 'json_object' | 'json_schema';
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* JSON schema for structured outputs (required when responseFormat is 'json_schema')
|
|
97
|
+
*/
|
|
98
|
+
jsonSchema?: IOpenAIJsonSchemaDefinition;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Responses API reasoning controls. Hidden reasoning is never exposed in message
|
|
102
|
+
* content; only explicit summaries/encrypted items requested here are represented.
|
|
103
|
+
*/
|
|
104
|
+
reasoning?: IOpenAIResponsesReasoningOptions;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Whether OpenAI should store Responses API results. Defaults to OpenAI API behavior.
|
|
108
|
+
*/
|
|
109
|
+
store?: boolean;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Include encrypted reasoning items for stateless reasoning continuation.
|
|
113
|
+
*/
|
|
114
|
+
includeEncryptedReasoning?: boolean;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Opt into strict custom function parameter validation where supported.
|
|
118
|
+
*/
|
|
119
|
+
strictTools?: boolean;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Provider-native hosted web tool request from provider profile options.
|
|
123
|
+
*
|
|
124
|
+
* OpenAI-compatible Chat Completions endpoints do not support this Robota
|
|
125
|
+
* native web contract. The provider rejects unsupported configurations before
|
|
126
|
+
* any model request is sent.
|
|
127
|
+
*/
|
|
128
|
+
nativeWebTools?: IOpenAINativeWebToolsOptions;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* OpenAI client instance (optional: will be created from apiKey if not provided)
|
|
132
|
+
*/
|
|
133
|
+
client?: OpenAI;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Payload logger instance for debugging API requests/responses
|
|
137
|
+
*
|
|
138
|
+
* Use different implementations based on your environment:
|
|
139
|
+
* - FilePayloadLogger: Node.js file-based logging
|
|
140
|
+
* - ConsolePayloadLogger: Browser console-based logging
|
|
141
|
+
* - Custom: Implement IPayloadLogger interface
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* // Node.js
|
|
146
|
+
* import { FilePayloadLogger } from '@robota-sdk/agent-provider/openai/loggers';
|
|
147
|
+
* const provider = new OpenAIProvider({
|
|
148
|
+
* client: openaiClient,
|
|
149
|
+
* payloadLogger: new FilePayloadLogger({ logDir: './logs/openai' })
|
|
150
|
+
* });
|
|
151
|
+
*
|
|
152
|
+
* // Browser
|
|
153
|
+
* import { ConsolePayloadLogger } from '@robota-sdk/agent-provider/openai/loggers';
|
|
154
|
+
* const provider = new OpenAIProvider({
|
|
155
|
+
* client: openaiClient,
|
|
156
|
+
* payloadLogger: new ConsolePayloadLogger()
|
|
157
|
+
* });
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
payloadLogger?: IPayloadLogger;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Optional executor for handling AI requests
|
|
164
|
+
*
|
|
165
|
+
* When provided, the provider will delegate all chat operations to this executor
|
|
166
|
+
* instead of making direct API calls. This enables remote execution capabilities.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```typescript
|
|
170
|
+
* import { LocalExecutor, RemoteExecutor } from '@robota-sdk/agent-core';
|
|
171
|
+
*
|
|
172
|
+
* // Local execution (registers this provider)
|
|
173
|
+
* const localExecutor = new LocalExecutor();
|
|
174
|
+
* localExecutor.registerProvider('openai', new OpenAIProvider({ apiKey: 'sk-...' }));
|
|
175
|
+
*
|
|
176
|
+
* // Remote execution
|
|
177
|
+
* const remoteExecutor = new RemoteExecutor({
|
|
178
|
+
* serverUrl: 'https://api.robota.io',
|
|
179
|
+
* userApiKey: 'user-token-123'
|
|
180
|
+
* });
|
|
181
|
+
*
|
|
182
|
+
* const provider = new OpenAIProvider({
|
|
183
|
+
* executor: remoteExecutor // No direct API key needed
|
|
184
|
+
* });
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
executor?: IExecutor;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Logger instance for internal OpenAI provider logging
|
|
191
|
+
* @defaultValue SilentLogger
|
|
192
|
+
*/
|
|
193
|
+
logger?: ILogger;
|
|
194
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const QWEN_PROVIDER_BASE_URLS = {
|
|
2
|
+
singapore: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1',
|
|
3
|
+
usVirginia: 'https://dashscope-us.aliyuncs.com/compatible-mode/v1',
|
|
4
|
+
beijing: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
5
|
+
hongKong: 'https://cn-hongkong.dashscope.aliyuncs.com/compatible-mode/v1',
|
|
6
|
+
} as const;
|
|
7
|
+
|
|
8
|
+
export type TQwenProviderRegion = keyof typeof QWEN_PROVIDER_BASE_URLS;
|
|
9
|
+
|
|
10
|
+
export const QWEN_PROVIDER_RESPONSES_BASE_URLS = {
|
|
11
|
+
singapore: 'https://dashscope-intl.aliyuncs.com/api/v2/apps/protocols/compatible-mode/v1',
|
|
12
|
+
usVirginia: 'https://dashscope-us.aliyuncs.com/api/v2/apps/protocols/compatible-mode/v1',
|
|
13
|
+
beijing: 'https://dashscope.aliyuncs.com/api/v2/apps/protocols/compatible-mode/v1',
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
export type TQwenProviderResponsesRegion = keyof typeof QWEN_PROVIDER_RESPONSES_BASE_URLS;
|
|
17
|
+
|
|
18
|
+
export const DEFAULT_QWEN_PROVIDER_MODEL = 'qwen-plus';
|
|
19
|
+
export const DEFAULT_QWEN_PROVIDER_API_KEY_ENV = 'DASHSCOPE_API_KEY';
|
|
20
|
+
export const DEFAULT_QWEN_PROVIDER_API_KEY_REFERENCE = `$ENV:${DEFAULT_QWEN_PROVIDER_API_KEY_ENV}`;
|
|
21
|
+
export const DEFAULT_QWEN_PROVIDER_BASE_URL = QWEN_PROVIDER_BASE_URLS.singapore;
|
|
22
|
+
export const DEFAULT_QWEN_PROVIDER_RESPONSES_BASE_URL = QWEN_PROVIDER_RESPONSES_BASE_URLS.singapore;
|
|
23
|
+
|
|
24
|
+
export const QWEN_MODEL_SOURCE_URL =
|
|
25
|
+
'https://www.alibabacloud.com/help/en/model-studio/compatibility-of-openai-with-dashscope';
|
|
26
|
+
export const QWEN_MODEL_LAST_VERIFIED_AT = '2026-05-04';
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { refreshQwenModelCatalog } from './model-catalog-refresh';
|
|
3
|
+
|
|
4
|
+
describe('refreshQwenModelCatalog', () => {
|
|
5
|
+
it('maps the live Qwen /models response into provider model catalog entries', async () => {
|
|
6
|
+
const fetcher = vi.fn(async () => ({
|
|
7
|
+
ok: true,
|
|
8
|
+
status: 200,
|
|
9
|
+
json: async () => ({
|
|
10
|
+
data: [
|
|
11
|
+
{ id: 'qwen-plus', object: 'model', owned_by: 'alibaba-cloud' },
|
|
12
|
+
{ id: 'qwen-max', object: 'model', owned_by: 'alibaba-cloud' },
|
|
13
|
+
{ id: 'qwen-turbo', object: 'model', owned_by: 'alibaba-cloud' },
|
|
14
|
+
],
|
|
15
|
+
}),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const catalog = await refreshQwenModelCatalog(
|
|
19
|
+
{
|
|
20
|
+
baseURL: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1/',
|
|
21
|
+
apiKey: 'test-api-key',
|
|
22
|
+
},
|
|
23
|
+
fetcher,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
expect(fetcher).toHaveBeenCalledWith(
|
|
27
|
+
'https://dashscope-intl.aliyuncs.com/compatible-mode/v1/models',
|
|
28
|
+
{
|
|
29
|
+
headers: {
|
|
30
|
+
Authorization: 'Bearer test-api-key',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
expect(catalog.status).toBe('live');
|
|
35
|
+
expect(catalog.entries?.map((entry) => entry.id)).toEqual([
|
|
36
|
+
'qwen-plus',
|
|
37
|
+
'qwen-max',
|
|
38
|
+
'qwen-turbo',
|
|
39
|
+
]);
|
|
40
|
+
expect(catalog.entries?.[0]).toMatchObject({
|
|
41
|
+
id: 'qwen-plus',
|
|
42
|
+
displayName: 'qwen-plus',
|
|
43
|
+
lifecycle: 'active',
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('uses the default base URL when profile.baseURL is not set', async () => {
|
|
48
|
+
const fetcher = vi.fn(async () => ({
|
|
49
|
+
ok: true,
|
|
50
|
+
status: 200,
|
|
51
|
+
json: async () => ({ data: [{ id: 'qwen-flash' }] }),
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
await refreshQwenModelCatalog({ apiKey: 'test-key' }, fetcher);
|
|
55
|
+
|
|
56
|
+
expect(fetcher).toHaveBeenCalledWith(
|
|
57
|
+
'https://dashscope-intl.aliyuncs.com/compatible-mode/v1/models',
|
|
58
|
+
expect.anything(),
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('omits Authorization header when no apiKey is provided', async () => {
|
|
63
|
+
const fetcher = vi.fn(async () => ({
|
|
64
|
+
ok: true,
|
|
65
|
+
status: 200,
|
|
66
|
+
json: async () => ({ data: [] }),
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
await refreshQwenModelCatalog({}, fetcher);
|
|
70
|
+
|
|
71
|
+
expect(fetcher).toHaveBeenCalledWith(
|
|
72
|
+
'https://dashscope-intl.aliyuncs.com/compatible-mode/v1/models',
|
|
73
|
+
undefined,
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('returns an unavailable catalog when the live model refresh fails', async () => {
|
|
78
|
+
const fetcher = vi.fn(async () => ({
|
|
79
|
+
ok: false,
|
|
80
|
+
status: 401,
|
|
81
|
+
json: async () => ({ data: [] }),
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
const catalog = await refreshQwenModelCatalog({}, fetcher);
|
|
85
|
+
|
|
86
|
+
expect(catalog).toMatchObject({
|
|
87
|
+
status: 'unavailable',
|
|
88
|
+
message: 'Qwen model refresh failed: HTTP 401',
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IProviderModelCatalog,
|
|
3
|
+
IProviderModelCatalogEntry,
|
|
4
|
+
IProviderProfileConfig,
|
|
5
|
+
} from '@robota-sdk/agent-core';
|
|
6
|
+
import {
|
|
7
|
+
DEFAULT_QWEN_PROVIDER_BASE_URL,
|
|
8
|
+
QWEN_MODEL_LAST_VERIFIED_AT,
|
|
9
|
+
QWEN_MODEL_SOURCE_URL,
|
|
10
|
+
} from './defaults';
|
|
11
|
+
|
|
12
|
+
export interface IQwenModelsResponse {
|
|
13
|
+
data?: Array<{
|
|
14
|
+
id?: string;
|
|
15
|
+
object?: string;
|
|
16
|
+
owned_by?: string;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface IQwenFetchInit {
|
|
21
|
+
headers?: Record<string, string>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface IQwenFetchResponse {
|
|
25
|
+
ok: boolean;
|
|
26
|
+
status: number;
|
|
27
|
+
json: () => Promise<IQwenModelsResponse>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type TQwenFetch = (url: string, init?: IQwenFetchInit) => Promise<IQwenFetchResponse>;
|
|
31
|
+
|
|
32
|
+
export async function refreshQwenModelCatalog(
|
|
33
|
+
profile: IProviderProfileConfig,
|
|
34
|
+
fetcher: TQwenFetch = defaultQwenFetch,
|
|
35
|
+
): Promise<IProviderModelCatalog> {
|
|
36
|
+
const baseURL = trimTrailingSlash(profile.baseURL ?? DEFAULT_QWEN_PROVIDER_BASE_URL);
|
|
37
|
+
const url = `${baseURL}/models`;
|
|
38
|
+
const response = await fetcher(url, buildFetchInit(profile.apiKey));
|
|
39
|
+
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
return {
|
|
42
|
+
status: 'unavailable',
|
|
43
|
+
sourceUrl: QWEN_MODEL_SOURCE_URL,
|
|
44
|
+
message: `Qwen model refresh failed: HTTP ${response.status}`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const body = await response.json();
|
|
49
|
+
const entries = (body.data ?? [])
|
|
50
|
+
.map((model) => model.id)
|
|
51
|
+
.filter((id): id is string => typeof id === 'string' && id.length > 0)
|
|
52
|
+
.map(toModelCatalogEntry);
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
status: 'live',
|
|
56
|
+
sourceUrl: QWEN_MODEL_SOURCE_URL,
|
|
57
|
+
lastVerifiedAt: QWEN_MODEL_LAST_VERIFIED_AT,
|
|
58
|
+
entries,
|
|
59
|
+
message: `Loaded ${entries.length} models from Qwen API`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildFetchInit(apiKey: string | undefined): IQwenFetchInit | undefined {
|
|
64
|
+
if (!apiKey) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
headers: {
|
|
69
|
+
Authorization: `Bearer ${apiKey}`,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function toModelCatalogEntry(id: string): IProviderModelCatalogEntry {
|
|
75
|
+
return {
|
|
76
|
+
id,
|
|
77
|
+
displayName: id,
|
|
78
|
+
lifecycle: 'active',
|
|
79
|
+
sourceUrl: QWEN_MODEL_SOURCE_URL,
|
|
80
|
+
lastVerifiedAt: QWEN_MODEL_LAST_VERIFIED_AT,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function trimTrailingSlash(value: string): string {
|
|
85
|
+
return value.replace(/\/$/, '');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function defaultQwenFetch(url: string, init?: IQwenFetchInit): Promise<IQwenFetchResponse> {
|
|
89
|
+
const response = await fetch(url, {
|
|
90
|
+
...(init?.headers !== undefined && { headers: init.headers }),
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
ok: response.ok,
|
|
94
|
+
status: response.status,
|
|
95
|
+
json: () => response.json() as Promise<IQwenModelsResponse>,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { IProviderCapabilities } from '@robota-sdk/agent-core';
|
|
2
|
+
import type { IQwenProviderOptions } from './types';
|
|
3
|
+
|
|
4
|
+
const QWEN_RESPONSES_SOURCE = 'qwen-responses';
|
|
5
|
+
const ENABLE_WEB_SEARCH_REASON = 'Enable builtInWebTools.webSearch or builtInWebTools.webFetch.';
|
|
6
|
+
const ENABLE_WEB_FETCH_REASON = 'Enable builtInWebTools.webFetch.';
|
|
7
|
+
|
|
8
|
+
export function getQwenProviderCapabilities(options: IQwenProviderOptions): IProviderCapabilities {
|
|
9
|
+
const webTools = options.builtInWebTools;
|
|
10
|
+
const webSearchEnabled = webTools?.webSearch === true || webTools?.webFetch === true;
|
|
11
|
+
const webFetchEnabled = webTools?.webFetch === true;
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
functionCalling: { supported: true },
|
|
15
|
+
nativeWebTools: {
|
|
16
|
+
webSearch: webSearchEnabled
|
|
17
|
+
? { supported: true, enabled: true, source: QWEN_RESPONSES_SOURCE }
|
|
18
|
+
: {
|
|
19
|
+
supported: true,
|
|
20
|
+
enabled: false,
|
|
21
|
+
source: QWEN_RESPONSES_SOURCE,
|
|
22
|
+
reason: ENABLE_WEB_SEARCH_REASON,
|
|
23
|
+
},
|
|
24
|
+
webFetch: webFetchEnabled
|
|
25
|
+
? { supported: true, enabled: true, source: QWEN_RESPONSES_SOURCE }
|
|
26
|
+
: {
|
|
27
|
+
supported: true,
|
|
28
|
+
enabled: false,
|
|
29
|
+
source: QWEN_RESPONSES_SOURCE,
|
|
30
|
+
reason: ENABLE_WEB_FETCH_REASON,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
createQwenProviderDefinition,
|
|
4
|
+
DEFAULT_QWEN_PROVIDER_API_KEY_ENV,
|
|
5
|
+
DEFAULT_QWEN_PROVIDER_API_KEY_REFERENCE,
|
|
6
|
+
DEFAULT_QWEN_PROVIDER_BASE_URL,
|
|
7
|
+
DEFAULT_QWEN_PROVIDER_MODEL,
|
|
8
|
+
DEFAULT_QWEN_PROVIDER_RESPONSES_BASE_URL,
|
|
9
|
+
QWEN_PROVIDER_BASE_URLS,
|
|
10
|
+
QWEN_PROVIDER_RESPONSES_BASE_URLS,
|
|
11
|
+
QwenProvider,
|
|
12
|
+
} from './index';
|
|
13
|
+
|
|
14
|
+
vi.mock('openai', () => {
|
|
15
|
+
const MockOpenAI = vi.fn().mockImplementation(() => ({
|
|
16
|
+
chat: {
|
|
17
|
+
completions: {
|
|
18
|
+
create: vi.fn(),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
responses: {
|
|
22
|
+
create: vi.fn(),
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
return { default: MockOpenAI };
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('createQwenProviderDefinition', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
vi.clearAllMocks();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('exposes Qwen defaults and setup steps through the provider-definition contract', () => {
|
|
34
|
+
const definition = createQwenProviderDefinition();
|
|
35
|
+
|
|
36
|
+
expect(definition.type).toBe('qwen');
|
|
37
|
+
expect(definition.requiresApiKey).toBe(true);
|
|
38
|
+
expect(definition.probeProfile).toBeTypeOf('function');
|
|
39
|
+
expect(definition.defaults).toEqual({
|
|
40
|
+
model: DEFAULT_QWEN_PROVIDER_MODEL,
|
|
41
|
+
apiKey: DEFAULT_QWEN_PROVIDER_API_KEY_REFERENCE,
|
|
42
|
+
baseURL: DEFAULT_QWEN_PROVIDER_BASE_URL,
|
|
43
|
+
});
|
|
44
|
+
expect(definition.setupHelpLinks).toEqual([
|
|
45
|
+
{
|
|
46
|
+
kind: 'api-key',
|
|
47
|
+
label: 'Alibaba Cloud Model Studio API keys',
|
|
48
|
+
url: 'https://modelstudio.console.alibabacloud.com/?tab=api#/api-key',
|
|
49
|
+
sourceUrl: 'https://www.alibabacloud.com/help/en/model-studio/get-api-key',
|
|
50
|
+
lastVerifiedAt: '2026-05-08',
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
expect(DEFAULT_QWEN_PROVIDER_API_KEY_ENV).toBe('DASHSCOPE_API_KEY');
|
|
54
|
+
expect(QWEN_PROVIDER_BASE_URLS).toMatchObject({
|
|
55
|
+
singapore: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1',
|
|
56
|
+
usVirginia: 'https://dashscope-us.aliyuncs.com/compatible-mode/v1',
|
|
57
|
+
beijing: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
58
|
+
hongKong: 'https://cn-hongkong.dashscope.aliyuncs.com/compatible-mode/v1',
|
|
59
|
+
});
|
|
60
|
+
expect(DEFAULT_QWEN_PROVIDER_RESPONSES_BASE_URL).toBe(
|
|
61
|
+
'https://dashscope-intl.aliyuncs.com/api/v2/apps/protocols/compatible-mode/v1',
|
|
62
|
+
);
|
|
63
|
+
expect(QWEN_PROVIDER_RESPONSES_BASE_URLS).toMatchObject({
|
|
64
|
+
singapore: 'https://dashscope-intl.aliyuncs.com/api/v2/apps/protocols/compatible-mode/v1',
|
|
65
|
+
usVirginia: 'https://dashscope-us.aliyuncs.com/api/v2/apps/protocols/compatible-mode/v1',
|
|
66
|
+
beijing: 'https://dashscope.aliyuncs.com/api/v2/apps/protocols/compatible-mode/v1',
|
|
67
|
+
});
|
|
68
|
+
expect(definition.setupSteps).toEqual([
|
|
69
|
+
{
|
|
70
|
+
key: 'baseURL',
|
|
71
|
+
title: 'Qwen OpenAI-compatible base URL',
|
|
72
|
+
defaultValue: DEFAULT_QWEN_PROVIDER_BASE_URL,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
key: 'model',
|
|
76
|
+
title: 'Qwen model',
|
|
77
|
+
defaultValue: DEFAULT_QWEN_PROVIDER_MODEL,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: 'apiKey',
|
|
81
|
+
title: 'Qwen Model Studio API key',
|
|
82
|
+
defaultValue: DEFAULT_QWEN_PROVIDER_API_KEY_REFERENCE,
|
|
83
|
+
masked: true,
|
|
84
|
+
},
|
|
85
|
+
]);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('creates a QwenProvider from a resolved provider profile', () => {
|
|
89
|
+
const definition = createQwenProviderDefinition();
|
|
90
|
+
|
|
91
|
+
const provider = definition.createProvider({
|
|
92
|
+
name: 'qwen',
|
|
93
|
+
model: 'qwen-plus',
|
|
94
|
+
apiKey: 'dashscope-key',
|
|
95
|
+
baseURL: QWEN_PROVIDER_BASE_URLS.usVirginia,
|
|
96
|
+
timeout: 12_000,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(provider).toBeInstanceOf(QwenProvider);
|
|
100
|
+
expect(provider.name).toBe('qwen');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('passes Qwen-owned Responses API options from the generic provider options bag', () => {
|
|
104
|
+
const definition = createQwenProviderDefinition();
|
|
105
|
+
|
|
106
|
+
const provider = definition.createProvider({
|
|
107
|
+
name: 'qwen',
|
|
108
|
+
model: 'qwen3.6-plus',
|
|
109
|
+
apiKey: 'dashscope-key',
|
|
110
|
+
options: {
|
|
111
|
+
responsesBaseURL: QWEN_PROVIDER_RESPONSES_BASE_URLS.usVirginia,
|
|
112
|
+
builtInWebTools: {
|
|
113
|
+
webSearch: true,
|
|
114
|
+
webFetch: true,
|
|
115
|
+
enableThinking: true,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const options = (provider as unknown as { options: Record<string, unknown> }).options;
|
|
121
|
+
expect(options['responsesBaseURL']).toBe(QWEN_PROVIDER_RESPONSES_BASE_URLS.usVirginia);
|
|
122
|
+
expect(options['builtInWebTools']).toEqual({
|
|
123
|
+
webSearch: true,
|
|
124
|
+
webFetch: true,
|
|
125
|
+
enableThinking: true,
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('rejects provider creation without an API key', () => {
|
|
130
|
+
const definition = createQwenProviderDefinition();
|
|
131
|
+
|
|
132
|
+
expect(() =>
|
|
133
|
+
definition.createProvider({
|
|
134
|
+
name: 'qwen',
|
|
135
|
+
model: 'qwen-plus',
|
|
136
|
+
}),
|
|
137
|
+
).toThrow('Provider qwen requires apiKey');
|
|
138
|
+
});
|
|
139
|
+
});
|