imtoagent 0.3.2 → 0.3.4
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/bin/imtoagent-real +2 -2
- package/modules/cli/setup.ts +166 -23
- package/modules/proxy/anthropic-proxy.ts +11 -0
- package/package.json +1 -1
package/bin/imtoagent-real
CHANGED
|
@@ -158,7 +158,7 @@ async function cmdStart() {
|
|
|
158
158
|
const pkgDir = path.resolve(import.meta.dirname, '..');
|
|
159
159
|
const indexFile = path.join(pkgDir, 'index.ts');
|
|
160
160
|
|
|
161
|
-
const child = Bun.spawn([
|
|
161
|
+
const child = Bun.spawn([process.execPath, 'run', indexFile], {
|
|
162
162
|
cwd: dataDir,
|
|
163
163
|
env: { ...process.env, IMTOAGENT_HOME: dataDir },
|
|
164
164
|
stdout: 'pipe',
|
|
@@ -384,7 +384,7 @@ async function cmdDaemon(): Promise<void> {
|
|
|
384
384
|
|
|
385
385
|
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
|
|
386
386
|
|
|
387
|
-
const child = Bun.spawn([
|
|
387
|
+
const child = Bun.spawn([process.execPath, 'run', indexFile], {
|
|
388
388
|
cwd: dataDir,
|
|
389
389
|
env: { ...process.env, IMTOAGENT_HOME: dataDir },
|
|
390
390
|
stdout: 'pipe',
|
package/modules/cli/setup.ts
CHANGED
|
@@ -48,7 +48,6 @@ function readKey(): Promise<string> {
|
|
|
48
48
|
|
|
49
49
|
async function selectMenu(title: string, options: string[]): Promise<number> {
|
|
50
50
|
let idx = 0;
|
|
51
|
-
const linesAbove = options.length + 2;
|
|
52
51
|
|
|
53
52
|
function render() {
|
|
54
53
|
// 清除之前的输出
|
|
@@ -107,7 +106,7 @@ async function promptText(label: string, defaultValue = ''): Promise<string> {
|
|
|
107
106
|
while (true) {
|
|
108
107
|
const key = await readKey();
|
|
109
108
|
|
|
110
|
-
if (key === KEY.ENTER) {
|
|
109
|
+
if (key === KEY.ENTER || key === '\n') {
|
|
111
110
|
break;
|
|
112
111
|
} else if (key === KEY.ESC) {
|
|
113
112
|
process.stdout.write('\x1B[0K\n');
|
|
@@ -119,13 +118,13 @@ async function promptText(label: string, defaultValue = ''): Promise<string> {
|
|
|
119
118
|
}
|
|
120
119
|
} else if (key === KEY.UP || key === KEY.DOWN) {
|
|
121
120
|
// 忽略方向键
|
|
122
|
-
} else if (key.length === 1 && key !== KEY.SPACE) {
|
|
123
|
-
// 普通字符(空格单独处理)
|
|
124
|
-
buf.push(key);
|
|
125
|
-
process.stdout.write(key);
|
|
126
121
|
} else if (key === KEY.SPACE) {
|
|
127
122
|
buf.push(' ');
|
|
128
123
|
process.stdout.write(' ');
|
|
124
|
+
} else if (key.length >= 1 && !key.startsWith('\x1b')) {
|
|
125
|
+
// 普通字符 / 粘贴的多字符块(不含转义序列的文本)
|
|
126
|
+
buf.push(key);
|
|
127
|
+
process.stdout.write(key);
|
|
129
128
|
}
|
|
130
129
|
}
|
|
131
130
|
} finally {
|
|
@@ -171,6 +170,91 @@ async function confirm(label: string, defaultYes = true): Promise<boolean | -1>
|
|
|
171
170
|
}
|
|
172
171
|
}
|
|
173
172
|
|
|
173
|
+
// ================================================================
|
|
174
|
+
// 供应商预设
|
|
175
|
+
// ================================================================
|
|
176
|
+
|
|
177
|
+
interface ProviderPreset {
|
|
178
|
+
name: string;
|
|
179
|
+
baseUrl: string;
|
|
180
|
+
format: 'openai' | 'anthropic';
|
|
181
|
+
models: string[];
|
|
182
|
+
hint?: string; // 额外说明
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const PROVIDER_PRESETS: ProviderPreset[] = [
|
|
186
|
+
{
|
|
187
|
+
name: 'DashScope(阿里百炼)',
|
|
188
|
+
baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
189
|
+
format: 'openai',
|
|
190
|
+
models: ['qwen-max', 'qwen-plus', 'qwen-turbo'],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: 'DeepSeek',
|
|
194
|
+
baseUrl: 'https://api.deepseek.com/v1',
|
|
195
|
+
format: 'openai',
|
|
196
|
+
models: ['deepseek-chat', 'deepseek-reasoner'],
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: '智谱 AI(Zhipu)',
|
|
200
|
+
baseUrl: 'https://open.bigmodel.cn/api/paas/v4',
|
|
201
|
+
format: 'openai',
|
|
202
|
+
models: ['glm-4-plus', 'glm-4-flash', 'glm-4'],
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: 'MiniMax',
|
|
206
|
+
baseUrl: 'https://api.minimax.io/v1',
|
|
207
|
+
format: 'openai',
|
|
208
|
+
models: ['MiniMax-M2.5', 'MiniMax-M1'],
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: '硅基流动(SiliconFlow)',
|
|
212
|
+
baseUrl: 'https://api.siliconflow.cn/v1',
|
|
213
|
+
format: 'openai',
|
|
214
|
+
models: ['Qwen/Qwen2.5-72B-Instruct', 'deepseek-ai/DeepSeek-V3'],
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: 'Moonshot(月之暗面)',
|
|
218
|
+
baseUrl: 'https://api.moonshot.cn/v1',
|
|
219
|
+
format: 'openai',
|
|
220
|
+
models: ['moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k'],
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'OpenAI',
|
|
224
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
225
|
+
format: 'openai',
|
|
226
|
+
models: ['gpt-4o', 'gpt-4o-mini', 'o3', 'o4-mini'],
|
|
227
|
+
hint: '需要代理才能访问',
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'Anthropic',
|
|
231
|
+
baseUrl: 'https://api.anthropic.com',
|
|
232
|
+
format: 'anthropic',
|
|
233
|
+
models: ['claude-sonnet-4-20250514', 'claude-haiku-4-20250514', 'claude-opus-4-20250514'],
|
|
234
|
+
hint: '需要代理才能访问',
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: 'Gemini(Google)',
|
|
238
|
+
baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
239
|
+
format: 'openai',
|
|
240
|
+
models: ['gemini-2.5-pro', 'gemini-2.5-flash'],
|
|
241
|
+
hint: '需要代理才能访问',
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: 'xAI(Grok)',
|
|
245
|
+
baseUrl: 'https://api.x.ai/v1',
|
|
246
|
+
format: 'openai',
|
|
247
|
+
models: ['grok-3', 'grok-3-mini'],
|
|
248
|
+
hint: '需要代理才能访问',
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: 'Ollama(本地)',
|
|
252
|
+
baseUrl: 'http://localhost:11434/v1',
|
|
253
|
+
format: 'openai',
|
|
254
|
+
models: ['qwen2.5', 'llama3.2', 'deepseek-r1'],
|
|
255
|
+
},
|
|
256
|
+
];
|
|
257
|
+
|
|
174
258
|
// ================================================================
|
|
175
259
|
// IM 平台配置定义
|
|
176
260
|
// ================================================================
|
|
@@ -396,29 +480,85 @@ export async function runSetupWizard(): Promise<void> {
|
|
|
396
480
|
console.log(`✅ 已保留 ${Object.keys(providers).length} 个现有供应商\n`);
|
|
397
481
|
}
|
|
398
482
|
|
|
399
|
-
|
|
400
|
-
|
|
483
|
+
// Step 4 外层循环:确保至少有一个供应商(用户明确跳过时退出)
|
|
484
|
+
let step4Loop = true;
|
|
485
|
+
while (step4Loop) {
|
|
486
|
+
let addingProviders = true;
|
|
487
|
+
while (addingProviders) {
|
|
401
488
|
console.log('--- 添加新供应商 ---\n');
|
|
402
|
-
const provName = await promptText('供应商名称 (如 deepseek, dashscope)');
|
|
403
|
-
if ((provName as any) === -1) { addingProviders = false; continue; }
|
|
404
|
-
if (!provName) { addingProviders = false; continue; }
|
|
405
489
|
|
|
406
|
-
|
|
407
|
-
|
|
490
|
+
// 选择预设 or 自定义
|
|
491
|
+
const presetOptions = PROVIDER_PRESETS.map(p => {
|
|
492
|
+
const tag = p.hint ? ` ${p.hint}` : '';
|
|
493
|
+
return `${p.name}${tag}`;
|
|
494
|
+
});
|
|
495
|
+
presetOptions.push('自定义...');
|
|
496
|
+
|
|
497
|
+
const presetIdx = await selectMenu('选择供应商', presetOptions);
|
|
498
|
+
if (presetIdx === -1) { addingProviders = false; continue; }
|
|
499
|
+
|
|
500
|
+
let provName: string, baseUrl: string, format: 'openai' | 'anthropic', models: string[];
|
|
501
|
+
|
|
502
|
+
if (presetIdx < PROVIDER_PRESETS.length) {
|
|
503
|
+
// 使用预设
|
|
504
|
+
const preset = PROVIDER_PRESETS[presetIdx];
|
|
505
|
+
provName = preset.name.split('(')[0].trim().toLowerCase(); // 取简短名称
|
|
506
|
+
baseUrl = preset.baseUrl;
|
|
507
|
+
format = preset.format;
|
|
508
|
+
models = [...preset.models];
|
|
509
|
+
|
|
510
|
+
console.log(`\n✅ 预设已加载:`);
|
|
511
|
+
console.log(` 名称: ${provName}`);
|
|
512
|
+
console.log(` URL: ${preset.baseUrl}`);
|
|
513
|
+
console.log(` 格式: ${preset.format}`);
|
|
514
|
+
console.log(` 模型: ${preset.models.join(', ')}\n`);
|
|
515
|
+
|
|
516
|
+
// 确认/修改简短名称
|
|
517
|
+
const nameEdit = await promptText('供应商名称(留空确认)', provName);
|
|
518
|
+
if ((nameEdit as any) === -1) continue;
|
|
519
|
+
provName = nameEdit || provName;
|
|
520
|
+
|
|
521
|
+
// 确认/修改 Base URL
|
|
522
|
+
const urlEdit = await promptText('Base URL', baseUrl);
|
|
523
|
+
if ((urlEdit as any) === -1) continue;
|
|
524
|
+
baseUrl = urlEdit || baseUrl;
|
|
525
|
+
|
|
526
|
+
// 确认/修改模型列表
|
|
527
|
+
const modelsEdit = await promptText('模型列表(逗号分隔)', models.join(', '));
|
|
528
|
+
if ((modelsEdit as any) === -1) continue;
|
|
529
|
+
if (modelsEdit) models = modelsEdit.split(',').map(s => s.trim()).filter(Boolean);
|
|
530
|
+
|
|
531
|
+
if (providers[provName]) {
|
|
532
|
+
console.log(`⚠️ 供应商 "${provName}" 已存在,将覆盖\n`);
|
|
533
|
+
}
|
|
534
|
+
} else {
|
|
535
|
+
// 自定义
|
|
536
|
+
provName = await promptText('供应商名称 (如 deepseek, dashscope)');
|
|
537
|
+
if ((provName as any) === -1) { addingProviders = false; continue; }
|
|
538
|
+
if (!provName) { addingProviders = false; continue; }
|
|
539
|
+
if (providers[provName]) {
|
|
540
|
+
console.log(`⚠️ 供应商 "${provName}" 已存在,将覆盖\n`);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
baseUrl = await promptText('Base URL (如 https://api.deepseek.com/v1)');
|
|
544
|
+
if ((baseUrl as any) === -1) continue;
|
|
545
|
+
const modelsStr = await promptText('模型列表 (逗号分隔)');
|
|
546
|
+
if ((modelsStr as any) === -1) continue;
|
|
547
|
+
models = (modelsStr || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
548
|
+
|
|
549
|
+
const formatIdx = await selectMenu('API 格式', ['openai', 'anthropic']);
|
|
550
|
+
if (formatIdx === -1) continue;
|
|
551
|
+
format = ['openai', 'anthropic'][formatIdx];
|
|
408
552
|
}
|
|
409
553
|
|
|
410
|
-
|
|
411
|
-
if ((baseUrl as any) === -1) continue;
|
|
554
|
+
// API Key(所有路径都需要)
|
|
412
555
|
const apiKey = await promptText('API Key');
|
|
413
556
|
if ((apiKey as any) === -1) continue;
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
const formatIdx = await selectMenu('API 格式', ['openai', 'anthropic']);
|
|
419
|
-
if (formatIdx === -1) continue;
|
|
420
|
-
const format = ['openai', 'anthropic'][formatIdx];
|
|
557
|
+
if (!apiKey) {
|
|
558
|
+
console.log('⚠️ API Key 为空,当前供应商将暂时无法使用\n');
|
|
559
|
+
}
|
|
421
560
|
|
|
561
|
+
// 价格(可选)
|
|
422
562
|
const priceInput = await promptText('价格 (入/出 每百万 Token,如 0.55,2.19,留空跳过)');
|
|
423
563
|
if ((priceInput as any) === -1) continue;
|
|
424
564
|
|
|
@@ -443,8 +583,11 @@ export async function runSetupWizard(): Promise<void> {
|
|
|
443
583
|
if (Object.keys(providers).length === 0) {
|
|
444
584
|
console.log('\n⚠️ 未配置任何供应商。');
|
|
445
585
|
const r = await confirm('至少配置一个供应商吗?');
|
|
446
|
-
if (r === true)
|
|
586
|
+
if (r === true) continue; // 重新进入 step4Loop
|
|
587
|
+
if (r === -1) { console.log('\n⚠️ 已跳过,你可以稍后配置。\n'); }
|
|
447
588
|
}
|
|
589
|
+
step4Loop = false; // 有供应商或用户明确跳过
|
|
590
|
+
}
|
|
448
591
|
|
|
449
592
|
// ===== Step 5: 选择默认模型 =====
|
|
450
593
|
console.log('\n📌 Step 5: 选择默认模型\n');
|
|
@@ -1007,6 +1007,17 @@ export function startAnthropicProxy(port = 18899): Promise<number> {
|
|
|
1007
1007
|
console.log(`[Proxy] 本地代理启动 http://localhost:${port}/v1/messages`);
|
|
1008
1008
|
console.log(`[Proxy] 当前模型: ${sharedState.activeConfig ? `${sharedState.activeConfig.providerName}/${sharedState.activeConfig.model} (${sharedState.activeConfig.format})` : '未设置'}`);
|
|
1009
1009
|
console.log(`[Proxy] 供应商: ${sharedState.activeConfig?.baseUrl || '无'}`);
|
|
1010
|
+
|
|
1011
|
+
// 检查有空 API Key 的供应商
|
|
1012
|
+
const emptyKeyProviders: string[] = [];
|
|
1013
|
+
providers.forEach((cfg, name) => {
|
|
1014
|
+
if (!cfg.apiKey) emptyKeyProviders.push(name);
|
|
1015
|
+
});
|
|
1016
|
+
if (emptyKeyProviders.length > 0) {
|
|
1017
|
+
console.log(`⚠️ 以下供应商 API Key 为空,可能无法正常工作: ${emptyKeyProviders.join(', ')}`);
|
|
1018
|
+
console.log(' 建议: 运行 "imtoagent setup" 设置必要的参数\n');
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1010
1021
|
resolve(port);
|
|
1011
1022
|
});
|
|
1012
1023
|
});
|