@renxqoo/renx-code 0.0.6 → 0.0.8
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 +40 -45
- package/bin/renx.cjs +12 -7
- package/package.json +16 -4
- package/src/files/workspace-files.ts +4 -6
- package/vendor/agent-root/src/agent/tool/base-tool.ts +1 -1
- package/vendor/agent-root/src/agent/tool/bash.ts +1 -1
- package/vendor/agent-root/src/agent/tool/file-edit-tool.ts +1 -1
- package/vendor/agent-root/src/agent/tool/file-history-list.ts +1 -1
- package/vendor/agent-root/src/agent/tool/file-history-restore.ts +1 -1
- package/vendor/agent-root/src/agent/tool/file-read-tool.ts +1 -1
- package/vendor/agent-root/src/agent/tool/glob.ts +1 -1
- package/vendor/agent-root/src/agent/tool/grep.ts +2 -2
- package/vendor/agent-root/src/agent/tool/lsp.ts +1 -1
- package/vendor/agent-root/src/agent/tool/skill-tool.ts +1 -1
- package/vendor/agent-root/src/agent/tool/tool-manager.ts +8 -9
- package/vendor/agent-root/src/agent/tool/web-fetch.ts +1 -1
- package/vendor/agent-root/src/agent/tool/web-search.ts +1 -1
- package/vendor/agent-root/src/config/__tests__/load-config-to-env.test.ts +109 -0
- package/vendor/agent-root/src/config/__tests__/loader.test.ts +114 -0
- package/vendor/agent-root/src/config/index.ts +1 -0
- package/vendor/agent-root/src/config/loader.ts +67 -4
- package/vendor/agent-root/src/config/types.ts +26 -0
- package/vendor/agent-root/src/providers/__tests__/registry.test.ts +82 -8
- package/vendor/agent-root/src/providers/index.ts +1 -1
- package/vendor/agent-root/src/providers/registry/model-config.ts +291 -44
- package/vendor/agent-root/src/providers/registry/provider-factory.ts +8 -4
- package/vendor/agent-root/src/providers/registry.ts +8 -8
- package/vendor/agent-root/src/providers/types/index.ts +1 -1
- package/vendor/agent-root/src/providers/types/registry.ts +10 -30
|
@@ -1,21 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* 集中管理所有模型的配置信息,可从外部加载
|
|
2
|
+
* Central model configuration storage.
|
|
5
3
|
*/
|
|
6
4
|
|
|
7
|
-
import type { ModelConfig, ModelId } from '../types';
|
|
5
|
+
import type { ModelConfig, ModelId, BuiltinModelId, ProviderType } from '../types';
|
|
6
|
+
|
|
7
|
+
const CUSTOM_MODELS_ENV_VAR = 'RENX_CUSTOM_MODELS_JSON';
|
|
8
|
+
|
|
9
|
+
export type ModelDefinition = Omit<ModelConfig, 'apiKey'>;
|
|
10
|
+
type PartialModelDefinition = Partial<ModelDefinition>;
|
|
11
|
+
|
|
12
|
+
const VALID_PROVIDERS: ProviderType[] = [
|
|
13
|
+
'anthropic',
|
|
14
|
+
'kimi',
|
|
15
|
+
'deepseek',
|
|
16
|
+
'glm',
|
|
17
|
+
'minimax',
|
|
18
|
+
'openai',
|
|
19
|
+
'openrouter',
|
|
20
|
+
'qwen',
|
|
21
|
+
];
|
|
8
22
|
|
|
9
23
|
/**
|
|
10
|
-
*
|
|
24
|
+
* Built-in model definitions.
|
|
11
25
|
*/
|
|
12
|
-
export const MODEL_DEFINITIONS: Record<
|
|
13
|
-
// Anthropic 系列
|
|
26
|
+
export const MODEL_DEFINITIONS: Record<BuiltinModelId, ModelDefinition> = {
|
|
14
27
|
'claude-opus-4.6': {
|
|
15
28
|
id: 'claude-opus-4.6',
|
|
16
29
|
provider: 'anthropic',
|
|
17
30
|
name: 'Claude Opus 4.6',
|
|
18
|
-
baseURL: '',
|
|
31
|
+
baseURL: 'https://api.anthropic.com',
|
|
19
32
|
endpointPath: '/v1/messages',
|
|
20
33
|
envApiKey: 'ANTHROPIC_API_KEY',
|
|
21
34
|
envBaseURL: 'ANTHROPIC_API_BASE',
|
|
@@ -25,8 +38,6 @@ export const MODEL_DEFINITIONS: Record<ModelId, Omit<ModelConfig, 'apiKey'>> = {
|
|
|
25
38
|
features: ['streaming', 'function-calling', 'vision'],
|
|
26
39
|
modalities: { image: true },
|
|
27
40
|
},
|
|
28
|
-
|
|
29
|
-
// GLM 系列
|
|
30
41
|
'glm-4.7': {
|
|
31
42
|
id: 'glm-4.7',
|
|
32
43
|
provider: 'glm',
|
|
@@ -41,7 +52,6 @@ export const MODEL_DEFINITIONS: Record<ModelId, Omit<ModelConfig, 'apiKey'>> = {
|
|
|
41
52
|
features: ['streaming', 'function-calling', 'vision'],
|
|
42
53
|
modalities: { image: true },
|
|
43
54
|
},
|
|
44
|
-
// GLM 系列
|
|
45
55
|
'glm-5': {
|
|
46
56
|
id: 'glm-5',
|
|
47
57
|
provider: 'glm',
|
|
@@ -56,7 +66,6 @@ export const MODEL_DEFINITIONS: Record<ModelId, Omit<ModelConfig, 'apiKey'>> = {
|
|
|
56
66
|
features: ['streaming', 'function-calling', 'vision'],
|
|
57
67
|
modalities: { image: true },
|
|
58
68
|
},
|
|
59
|
-
// MiniMax 系列
|
|
60
69
|
'minimax-2.5': {
|
|
61
70
|
id: 'minimax-2.5',
|
|
62
71
|
provider: 'minimax',
|
|
@@ -70,7 +79,6 @@ export const MODEL_DEFINITIONS: Record<ModelId, Omit<ModelConfig, 'apiKey'>> = {
|
|
|
70
79
|
LLMMAX_TOKENS: 200 * 1000,
|
|
71
80
|
features: ['streaming', 'function-calling'],
|
|
72
81
|
},
|
|
73
|
-
// Kimi 系列
|
|
74
82
|
'kimi-k2.5': {
|
|
75
83
|
id: 'kimi-k2.5',
|
|
76
84
|
provider: 'kimi',
|
|
@@ -86,7 +94,6 @@ export const MODEL_DEFINITIONS: Record<ModelId, Omit<ModelConfig, 'apiKey'>> = {
|
|
|
86
94
|
temperature: 0.6,
|
|
87
95
|
thinking: false,
|
|
88
96
|
},
|
|
89
|
-
// DeepSeek 系列
|
|
90
97
|
'deepseek-reasoner': {
|
|
91
98
|
id: 'deepseek-reasoner',
|
|
92
99
|
provider: 'deepseek',
|
|
@@ -100,12 +107,11 @@ export const MODEL_DEFINITIONS: Record<ModelId, Omit<ModelConfig, 'apiKey'>> = {
|
|
|
100
107
|
LLMMAX_TOKENS: 128 * 1000,
|
|
101
108
|
features: ['streaming', 'function-calling'],
|
|
102
109
|
},
|
|
103
|
-
// Qwen 系列
|
|
104
110
|
'qwen3.5-plus': {
|
|
105
111
|
id: 'qwen3.5-plus',
|
|
106
112
|
provider: 'qwen',
|
|
107
113
|
name: 'Qwen 3.5 Plus',
|
|
108
|
-
baseURL: 'https://
|
|
114
|
+
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
109
115
|
endpointPath: '/chat/completions',
|
|
110
116
|
envApiKey: 'QWEN_API_KEY',
|
|
111
117
|
envBaseURL: 'QWEN_API_BASE',
|
|
@@ -115,12 +121,11 @@ export const MODEL_DEFINITIONS: Record<ModelId, Omit<ModelConfig, 'apiKey'>> = {
|
|
|
115
121
|
features: ['streaming', 'function-calling'],
|
|
116
122
|
modalities: { image: true },
|
|
117
123
|
},
|
|
118
|
-
// Qwen 系列
|
|
119
124
|
'qwen3.5-max': {
|
|
120
125
|
id: 'qwen3.5-max',
|
|
121
126
|
provider: 'qwen',
|
|
122
127
|
name: 'Qwen 3.5 Max',
|
|
123
|
-
baseURL: 'https://
|
|
128
|
+
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
124
129
|
endpointPath: '/chat/completions',
|
|
125
130
|
envApiKey: 'QWEN_API_KEY',
|
|
126
131
|
envBaseURL: 'QWEN_API_BASE',
|
|
@@ -133,12 +138,12 @@ export const MODEL_DEFINITIONS: Record<ModelId, Omit<ModelConfig, 'apiKey'>> = {
|
|
|
133
138
|
id: 'qwen-kimi-k2.5',
|
|
134
139
|
provider: 'qwen',
|
|
135
140
|
name: 'qwen kimi k2.5',
|
|
136
|
-
baseURL: 'https://
|
|
141
|
+
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
137
142
|
endpointPath: '/chat/completions',
|
|
138
143
|
envApiKey: 'QWEN_API_KEY',
|
|
139
144
|
envBaseURL: 'QWEN_API_BASE',
|
|
140
145
|
model: 'kimi-k2.5',
|
|
141
|
-
max_tokens:
|
|
146
|
+
max_tokens: 1000 * 32,
|
|
142
147
|
LLMMAX_TOKENS: 200 * 1000,
|
|
143
148
|
features: ['streaming', 'function-calling'],
|
|
144
149
|
},
|
|
@@ -146,12 +151,12 @@ export const MODEL_DEFINITIONS: Record<ModelId, Omit<ModelConfig, 'apiKey'>> = {
|
|
|
146
151
|
id: 'qwen-glm-5',
|
|
147
152
|
provider: 'qwen',
|
|
148
153
|
name: 'Qwen GLM 5',
|
|
149
|
-
baseURL: 'https://
|
|
154
|
+
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
150
155
|
endpointPath: '/chat/completions',
|
|
151
156
|
envApiKey: 'QWEN_API_KEY',
|
|
152
157
|
envBaseURL: 'QWEN_API_BASE',
|
|
153
158
|
model: 'glm-5',
|
|
154
|
-
max_tokens:
|
|
159
|
+
max_tokens: 1000 * 32,
|
|
155
160
|
LLMMAX_TOKENS: 200 * 1000,
|
|
156
161
|
features: ['streaming', 'function-calling'],
|
|
157
162
|
},
|
|
@@ -159,40 +164,26 @@ export const MODEL_DEFINITIONS: Record<ModelId, Omit<ModelConfig, 'apiKey'>> = {
|
|
|
159
164
|
id: 'qwen-minimax-2.5',
|
|
160
165
|
provider: 'qwen',
|
|
161
166
|
name: 'Qwen MiniMax 2.5',
|
|
162
|
-
baseURL: 'https://
|
|
167
|
+
baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
163
168
|
endpointPath: '/chat/completions',
|
|
164
169
|
envApiKey: 'QWEN_API_KEY',
|
|
165
170
|
envBaseURL: 'QWEN_API_BASE',
|
|
166
171
|
model: 'MiniMax-M2.5',
|
|
167
|
-
max_tokens:
|
|
172
|
+
max_tokens: 1000 * 32,
|
|
168
173
|
LLMMAX_TOKENS: 200 * 1000,
|
|
169
174
|
features: ['streaming', 'function-calling'],
|
|
170
175
|
},
|
|
171
|
-
// 'claude-4.6': {
|
|
172
|
-
// id: 'wr-claude-4.6',
|
|
173
|
-
// provider: 'openai',
|
|
174
|
-
// name: 'Claude Opus 4.6',
|
|
175
|
-
// baseURL: '',
|
|
176
|
-
// endpointPath: '/chat/completions',
|
|
177
|
-
// envApiKey: 'ANTHROPIC_API_KEY',
|
|
178
|
-
// envBaseURL: 'ANTHROPIC_API_BASE',
|
|
179
|
-
// model: 'claude-opus-4-6',
|
|
180
|
-
// max_tokens: 16384,
|
|
181
|
-
// LLMMAX_TOKENS: 1000 * 1000,
|
|
182
|
-
// features: ['streaming', 'function-calling', 'vision'],
|
|
183
|
-
// modalities: { image: true },
|
|
184
|
-
// },
|
|
185
176
|
'gpt-5.3': {
|
|
186
177
|
id: 'gpt-5.3',
|
|
187
178
|
provider: 'openai',
|
|
188
179
|
name: 'GPT-5.3',
|
|
189
|
-
baseURL: 'https://
|
|
180
|
+
baseURL: 'https://api.openai.com/v1',
|
|
190
181
|
endpointPath: '/responses',
|
|
191
182
|
envApiKey: 'OPENAI_API_KEY',
|
|
192
183
|
envBaseURL: 'OPENAI_API_BASE',
|
|
193
184
|
model: 'gpt-5.3-codex',
|
|
194
|
-
max_tokens:
|
|
195
|
-
LLMMAX_TOKENS:
|
|
185
|
+
max_tokens: 1000 * 32,
|
|
186
|
+
LLMMAX_TOKENS: 258 * 1000,
|
|
196
187
|
model_reasoning_effort: 'high',
|
|
197
188
|
features: ['streaming', 'function-calling', 'reasoning'],
|
|
198
189
|
modalities: { image: true },
|
|
@@ -201,12 +192,12 @@ export const MODEL_DEFINITIONS: Record<ModelId, Omit<ModelConfig, 'apiKey'>> = {
|
|
|
201
192
|
id: 'gpt-5.4',
|
|
202
193
|
provider: 'openai',
|
|
203
194
|
name: 'GPT-5.4',
|
|
204
|
-
baseURL: 'https://
|
|
195
|
+
baseURL: 'https://api.openai.com/v1',
|
|
205
196
|
endpointPath: '/responses',
|
|
206
197
|
envApiKey: 'OPENAI_API_KEY',
|
|
207
198
|
envBaseURL: 'OPENAI_API_BASE',
|
|
208
199
|
model: 'gpt-5.4',
|
|
209
|
-
max_tokens:
|
|
200
|
+
max_tokens: 1000 * 32,
|
|
210
201
|
LLMMAX_TOKENS: 200 * 1000,
|
|
211
202
|
model_reasoning_effort: 'high',
|
|
212
203
|
features: ['streaming', 'function-calling'],
|
|
@@ -221,10 +212,266 @@ export const MODEL_DEFINITIONS: Record<ModelId, Omit<ModelConfig, 'apiKey'>> = {
|
|
|
221
212
|
envApiKey: 'OPENROUTER_API_KEY',
|
|
222
213
|
envBaseURL: 'OPENROUTER_API_BASE',
|
|
223
214
|
model: 'openrouter/hunter-alpha',
|
|
224
|
-
max_tokens:
|
|
215
|
+
max_tokens: 1000 * 32,
|
|
225
216
|
LLMMAX_TOKENS: 200 * 1000,
|
|
226
217
|
model_reasoning_effort: 'high',
|
|
227
218
|
features: ['streaming', 'function-calling'],
|
|
228
219
|
modalities: { image: true },
|
|
229
220
|
},
|
|
230
221
|
};
|
|
222
|
+
|
|
223
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
224
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function isValidProvider(value: unknown): value is ProviderType {
|
|
228
|
+
return typeof value === 'string' && VALID_PROVIDERS.includes(value as ProviderType);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function isValidNumber(value: unknown): value is number {
|
|
232
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function isValidReasoningEffort(
|
|
236
|
+
value: unknown
|
|
237
|
+
): value is NonNullable<ModelDefinition['model_reasoning_effort']> {
|
|
238
|
+
return value === 'low' || value === 'medium' || value === 'high';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function sanitizeModalities(value: unknown): ModelDefinition['modalities'] | undefined {
|
|
242
|
+
if (value === undefined) {
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!isPlainObject(value)) {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const result: NonNullable<ModelDefinition['modalities']> = {};
|
|
251
|
+
|
|
252
|
+
if (typeof value.image === 'boolean') {
|
|
253
|
+
result.image = value.image;
|
|
254
|
+
}
|
|
255
|
+
if (typeof value.audio === 'boolean') {
|
|
256
|
+
result.audio = value.audio;
|
|
257
|
+
}
|
|
258
|
+
if (typeof value.video === 'boolean') {
|
|
259
|
+
result.video = value.video;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return Object.keys(result).length > 0 ? result : {};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function sanitizePartialModelDefinition(
|
|
266
|
+
modelId: string,
|
|
267
|
+
value: unknown
|
|
268
|
+
): PartialModelDefinition | null {
|
|
269
|
+
if (!isPlainObject(value)) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const sanitized: PartialModelDefinition = { id: modelId as ModelId };
|
|
274
|
+
|
|
275
|
+
if (value.provider !== undefined) {
|
|
276
|
+
if (!isValidProvider(value.provider)) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
sanitized.provider = value.provider;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (value.name !== undefined) {
|
|
283
|
+
if (typeof value.name !== 'string' || value.name.trim() === '') {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
sanitized.name = value.name;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (value.endpointPath !== undefined) {
|
|
290
|
+
if (typeof value.endpointPath !== 'string' || value.endpointPath.trim() === '') {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
sanitized.endpointPath = value.endpointPath;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (value.envApiKey !== undefined) {
|
|
297
|
+
if (typeof value.envApiKey !== 'string' || value.envApiKey.trim() === '') {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
sanitized.envApiKey = value.envApiKey;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (value.envBaseURL !== undefined) {
|
|
304
|
+
if (typeof value.envBaseURL !== 'string' || value.envBaseURL.trim() === '') {
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
sanitized.envBaseURL = value.envBaseURL;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (value.baseURL !== undefined) {
|
|
311
|
+
if (typeof value.baseURL !== 'string' || value.baseURL.trim() === '') {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
sanitized.baseURL = value.baseURL;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (value.model !== undefined) {
|
|
318
|
+
if (typeof value.model !== 'string' || value.model.trim() === '') {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
sanitized.model = value.model;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (value.max_tokens !== undefined) {
|
|
325
|
+
if (!isValidNumber(value.max_tokens)) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
sanitized.max_tokens = value.max_tokens;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (value.LLMMAX_TOKENS !== undefined) {
|
|
332
|
+
if (!isValidNumber(value.LLMMAX_TOKENS)) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
sanitized.LLMMAX_TOKENS = value.LLMMAX_TOKENS;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (value.features !== undefined) {
|
|
339
|
+
if (!Array.isArray(value.features) || value.features.some((feature) => typeof feature !== 'string')) {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
sanitized.features = [...value.features];
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (value.modalities !== undefined) {
|
|
346
|
+
const modalities = sanitizeModalities(value.modalities);
|
|
347
|
+
if (modalities === undefined) {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
sanitized.modalities = modalities;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (value.temperature !== undefined) {
|
|
354
|
+
if (typeof value.temperature !== 'number' || !Number.isFinite(value.temperature)) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
sanitized.temperature = value.temperature;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (value.tool_stream !== undefined) {
|
|
361
|
+
if (typeof value.tool_stream !== 'boolean') {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
sanitized.tool_stream = value.tool_stream;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (value.thinking !== undefined) {
|
|
368
|
+
if (typeof value.thinking !== 'boolean') {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
sanitized.thinking = value.thinking;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (value.timeout !== undefined) {
|
|
375
|
+
if (!isValidNumber(value.timeout)) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
sanitized.timeout = value.timeout;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (value.model_reasoning_effort !== undefined) {
|
|
382
|
+
if (!isValidReasoningEffort(value.model_reasoning_effort)) {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
sanitized.model_reasoning_effort = value.model_reasoning_effort;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return sanitized;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function isCompleteModelDefinition(value: PartialModelDefinition): value is ModelDefinition {
|
|
392
|
+
return (
|
|
393
|
+
typeof value.id === 'string' &&
|
|
394
|
+
isValidProvider(value.provider) &&
|
|
395
|
+
typeof value.name === 'string' &&
|
|
396
|
+
typeof value.endpointPath === 'string' &&
|
|
397
|
+
typeof value.envApiKey === 'string' &&
|
|
398
|
+
typeof value.envBaseURL === 'string' &&
|
|
399
|
+
typeof value.baseURL === 'string' &&
|
|
400
|
+
typeof value.model === 'string' &&
|
|
401
|
+
isValidNumber(value.max_tokens) &&
|
|
402
|
+
isValidNumber(value.LLMMAX_TOKENS) &&
|
|
403
|
+
Array.isArray(value.features) &&
|
|
404
|
+
value.features.every((feature) => typeof feature === 'string')
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function mergeModelDefinition(
|
|
409
|
+
modelId: string,
|
|
410
|
+
base: PartialModelDefinition,
|
|
411
|
+
override: PartialModelDefinition
|
|
412
|
+
): ModelDefinition | null {
|
|
413
|
+
const merged: PartialModelDefinition = {
|
|
414
|
+
...base,
|
|
415
|
+
...override,
|
|
416
|
+
id: modelId as ModelId,
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
if (base.modalities || override.modalities) {
|
|
420
|
+
merged.modalities = {
|
|
421
|
+
...(base.modalities ?? {}),
|
|
422
|
+
...(override.modalities ?? {}),
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (!isCompleteModelDefinition(merged)) {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return merged;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export function readCustomModelDefinitionsFromEnv(
|
|
434
|
+
env: NodeJS.ProcessEnv = process.env
|
|
435
|
+
): Record<string, PartialModelDefinition> {
|
|
436
|
+
const raw = env[CUSTOM_MODELS_ENV_VAR];
|
|
437
|
+
if (!raw) {
|
|
438
|
+
return {};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
443
|
+
if (!isPlainObject(parsed)) {
|
|
444
|
+
return {};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const result: Record<string, PartialModelDefinition> = {};
|
|
448
|
+
for (const [modelId, modelConfig] of Object.entries(parsed)) {
|
|
449
|
+
const sanitized = sanitizePartialModelDefinition(modelId, modelConfig);
|
|
450
|
+
if (sanitized) {
|
|
451
|
+
result[modelId] = sanitized;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return result;
|
|
456
|
+
} catch {
|
|
457
|
+
return {};
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export function getResolvedModelDefinitions(
|
|
462
|
+
env: NodeJS.ProcessEnv = process.env
|
|
463
|
+
): Record<ModelId, ModelDefinition> {
|
|
464
|
+
const customDefinitions = readCustomModelDefinitionsFromEnv(env);
|
|
465
|
+
const resolved: Record<string, ModelDefinition> = { ...MODEL_DEFINITIONS };
|
|
466
|
+
|
|
467
|
+
for (const [modelId, customDefinition] of Object.entries(customDefinitions)) {
|
|
468
|
+
const baseDefinition = resolved[modelId] ?? ({ id: modelId as ModelId } as PartialModelDefinition);
|
|
469
|
+
const mergedDefinition = mergeModelDefinition(modelId, baseDefinition, customDefinition);
|
|
470
|
+
|
|
471
|
+
if (mergedDefinition) {
|
|
472
|
+
resolved[modelId] = mergedDefinition;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return resolved as Record<ModelId, ModelDefinition>;
|
|
477
|
+
}
|
|
@@ -9,7 +9,7 @@ import { AnthropicAdapter } from '../adapters/anthropic';
|
|
|
9
9
|
import { OpenAICompatibleProvider, OpenAICompatibleConfig } from '../openai-compatible';
|
|
10
10
|
import type { BaseProviderConfig, ModelId } from '../types';
|
|
11
11
|
import type { BaseAPIAdapter } from '../adapters/base';
|
|
12
|
-
import {
|
|
12
|
+
import { getResolvedModelDefinitions } from './model-config';
|
|
13
13
|
import { KimiAdapter } from '../adapters/kimi';
|
|
14
14
|
import { ResponsesAdapter } from '../adapters/responses';
|
|
15
15
|
|
|
@@ -32,7 +32,7 @@ export class ProviderFactory {
|
|
|
32
32
|
throw new Error('ModelId is required.');
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
const modelConfig =
|
|
35
|
+
const modelConfig = getResolvedModelDefinitions()[modelId];
|
|
36
36
|
if (!modelConfig) {
|
|
37
37
|
throw new Error(`Unknown model: ${modelId}`);
|
|
38
38
|
}
|
|
@@ -73,7 +73,7 @@ export class ProviderFactory {
|
|
|
73
73
|
* @returns OpenAI Compatible Provider 实例
|
|
74
74
|
*/
|
|
75
75
|
static create(modelId: ModelId, config: BaseProviderConfig): OpenAICompatibleProvider {
|
|
76
|
-
const modelConfig =
|
|
76
|
+
const modelConfig = getResolvedModelDefinitions()[modelId];
|
|
77
77
|
if (!modelConfig) {
|
|
78
78
|
throw new Error(`Unknown model: ${modelId}`);
|
|
79
79
|
}
|
|
@@ -92,7 +92,11 @@ export class ProviderFactory {
|
|
|
92
92
|
modelId: ModelId,
|
|
93
93
|
logger?: OpenAICompatibleConfig['logger']
|
|
94
94
|
): BaseAPIAdapter {
|
|
95
|
-
const modelConfig =
|
|
95
|
+
const modelConfig = getResolvedModelDefinitions()[modelId];
|
|
96
|
+
if (!modelConfig) {
|
|
97
|
+
throw new Error(`Unknown model: ${modelId}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
96
100
|
if (modelConfig.provider === 'anthropic') {
|
|
97
101
|
return new AnthropicAdapter({
|
|
98
102
|
defaultModel: modelConfig.model,
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { ProviderFactory } from './registry/provider-factory';
|
|
9
|
-
import { MODEL_DEFINITIONS } from './registry/model-config';
|
|
9
|
+
import { getResolvedModelDefinitions, MODEL_DEFINITIONS } from './registry/model-config';
|
|
10
10
|
import type { ModelConfig, ModelId, ProviderType } from './types';
|
|
11
11
|
|
|
12
12
|
// 导出类型
|
|
13
|
-
export type { ProviderType, ModelId, ModelConfig } from './types';
|
|
13
|
+
export type { ProviderType, BuiltinModelId, ModelId, ModelConfig } from './types';
|
|
14
14
|
|
|
15
15
|
// 导出模型配置
|
|
16
16
|
export { MODEL_DEFINITIONS as MODEL_CONFIGS } from './registry/model-config';
|
|
@@ -47,7 +47,7 @@ export class ProviderRegistry {
|
|
|
47
47
|
* 获取所有模型配置
|
|
48
48
|
*/
|
|
49
49
|
static listModels(): ModelConfig[] {
|
|
50
|
-
return Object.values(
|
|
50
|
+
return Object.values(getResolvedModelDefinitions()).map((config) => ({
|
|
51
51
|
...config,
|
|
52
52
|
apiKey: undefined,
|
|
53
53
|
}));
|
|
@@ -57,7 +57,7 @@ export class ProviderRegistry {
|
|
|
57
57
|
* 获取指定厂商的所有模型
|
|
58
58
|
*/
|
|
59
59
|
static listModelsByProvider(provider: ProviderType): ModelConfig[] {
|
|
60
|
-
return Object.values(
|
|
60
|
+
return Object.values(getResolvedModelDefinitions())
|
|
61
61
|
.filter((m) => m.provider === provider)
|
|
62
62
|
.map((config) => ({
|
|
63
63
|
...config,
|
|
@@ -69,14 +69,14 @@ export class ProviderRegistry {
|
|
|
69
69
|
* 获取所有模型 ID
|
|
70
70
|
*/
|
|
71
71
|
static getModelIds(): ModelId[] {
|
|
72
|
-
return Object.keys(
|
|
72
|
+
return Object.keys(getResolvedModelDefinitions()) as ModelId[];
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
76
|
* 获取指定模型的配置
|
|
77
77
|
*/
|
|
78
78
|
static getModelConfig(modelId: ModelId): ModelConfig {
|
|
79
|
-
const config =
|
|
79
|
+
const config = getResolvedModelDefinitions()[modelId];
|
|
80
80
|
if (!config) {
|
|
81
81
|
throw new Error(`Unknown model: ${modelId}`);
|
|
82
82
|
}
|
|
@@ -87,7 +87,7 @@ export class ProviderRegistry {
|
|
|
87
87
|
* 获取模型显示名称
|
|
88
88
|
*/
|
|
89
89
|
static getModelName(modelId: ModelId): string {
|
|
90
|
-
return
|
|
90
|
+
return getResolvedModelDefinitions()[modelId]?.name || modelId;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/**
|
|
@@ -95,7 +95,7 @@ export class ProviderRegistry {
|
|
|
95
95
|
*/
|
|
96
96
|
static getProviders(): ProviderType[] {
|
|
97
97
|
const providers = new Set<ProviderType>();
|
|
98
|
-
Object.values(
|
|
98
|
+
Object.values(getResolvedModelDefinitions()).forEach((m) => providers.add(m.provider));
|
|
99
99
|
return Array.from(providers);
|
|
100
100
|
}
|
|
101
101
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Registry
|
|
3
|
-
*
|
|
4
|
-
* Provider Registry 相关的类型定义
|
|
2
|
+
* Registry related type definitions.
|
|
5
3
|
*/
|
|
6
4
|
|
|
7
5
|
/**
|
|
8
|
-
*
|
|
6
|
+
* Supported provider types.
|
|
9
7
|
*/
|
|
10
8
|
export type ProviderType =
|
|
11
9
|
| 'anthropic'
|
|
@@ -18,22 +16,14 @@ export type ProviderType =
|
|
|
18
16
|
| 'qwen';
|
|
19
17
|
|
|
20
18
|
/**
|
|
21
|
-
*
|
|
19
|
+
* Built-in model IDs shipped with the CLI.
|
|
22
20
|
*/
|
|
23
|
-
export type
|
|
24
|
-
// Anthropic 系列
|
|
21
|
+
export type BuiltinModelId =
|
|
25
22
|
| 'claude-opus-4.6'
|
|
26
|
-
// GLM 系列
|
|
27
23
|
| 'glm-4.7'
|
|
28
|
-
// MiniMax 系列
|
|
29
24
|
| 'minimax-2.5'
|
|
30
|
-
// Kimi 系列
|
|
31
25
|
| 'kimi-k2.5'
|
|
32
|
-
// DeepSeek 系列
|
|
33
|
-
// | 'deepseek-chat'
|
|
34
|
-
// GLM 5.0 系列
|
|
35
26
|
| 'glm-5'
|
|
36
|
-
// Qwen 系列
|
|
37
27
|
| 'qwen3.5-plus'
|
|
38
28
|
| 'qwen-kimi-k2.5'
|
|
39
29
|
| 'qwen-glm-5'
|
|
@@ -45,42 +35,32 @@ export type ModelId =
|
|
|
45
35
|
| 'openrouter/hunter-alpha';
|
|
46
36
|
|
|
47
37
|
/**
|
|
48
|
-
*
|
|
38
|
+
* Model IDs can include built-ins and user-defined config.json entries.
|
|
39
|
+
*/
|
|
40
|
+
export type ModelId = BuiltinModelId | (string & {});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Model configuration.
|
|
49
44
|
*/
|
|
50
45
|
export interface ModelConfig {
|
|
51
|
-
/** 模型唯一标识 */
|
|
52
46
|
id: ModelId;
|
|
53
|
-
/** 所属厂商 */
|
|
54
47
|
provider: ProviderType;
|
|
55
|
-
/** 显示名称 */
|
|
56
48
|
name: string;
|
|
57
|
-
/** API 端点路径 */
|
|
58
49
|
endpointPath: string;
|
|
59
|
-
/** API Key 环境变量名 */
|
|
60
50
|
envApiKey: string;
|
|
61
|
-
/** Base URL 环境变量名 */
|
|
62
51
|
envBaseURL: string;
|
|
63
|
-
/** API 基础 URL */
|
|
64
52
|
baseURL: string;
|
|
65
|
-
/** API 模型名称 */
|
|
66
53
|
model: string;
|
|
67
|
-
/** 最大输出 token 数 */
|
|
68
54
|
max_tokens: number;
|
|
69
|
-
/** 最大上下文 token 数 */
|
|
70
55
|
LLMMAX_TOKENS: number;
|
|
71
|
-
/** 支持的特性 */
|
|
72
56
|
features: string[];
|
|
73
|
-
/** 多模态输入能力 */
|
|
74
57
|
modalities?: {
|
|
75
58
|
image?: boolean;
|
|
76
59
|
audio?: boolean;
|
|
77
60
|
video?: boolean;
|
|
78
61
|
};
|
|
79
|
-
/** API 密钥(可选) */
|
|
80
62
|
apiKey?: string;
|
|
81
|
-
/** 温度(可选) */
|
|
82
63
|
temperature?: number;
|
|
83
|
-
/** 默认工具流式输出(可选) */
|
|
84
64
|
tool_stream?: boolean;
|
|
85
65
|
thinking?: boolean;
|
|
86
66
|
timeout?: number;
|