claude-ai-switcher 1.1.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/AGENTS.md +265 -0
- package/ARCHITECTURE.md +162 -0
- package/CLAUDE.md +267 -0
- package/LICENSE +21 -0
- package/QWEN.md +429 -0
- package/README.md +833 -0
- package/dist/clients/claude-code.d.ts +92 -0
- package/dist/clients/claude-code.d.ts.map +1 -0
- package/dist/clients/claude-code.js +312 -0
- package/dist/clients/claude-code.js.map +1 -0
- package/dist/clients/opencode.d.ts +71 -0
- package/dist/clients/opencode.d.ts.map +1 -0
- package/dist/clients/opencode.js +604 -0
- package/dist/clients/opencode.js.map +1 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +122 -0
- package/dist/config.js.map +1 -0
- package/dist/display.d.ts +51 -0
- package/dist/display.d.ts.map +1 -0
- package/dist/display.js +118 -0
- package/dist/display.js.map +1 -0
- package/dist/hooks/index.d.ts +60 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +223 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/token-tracker.js +280 -0
- package/dist/hooks/visual-enhancements.js +364 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1091 -0
- package/dist/index.js.map +1 -0
- package/dist/models.d.ts +34 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +343 -0
- package/dist/models.js.map +1 -0
- package/dist/providers/alibaba.d.ts +25 -0
- package/dist/providers/alibaba.d.ts.map +1 -0
- package/dist/providers/alibaba.js +37 -0
- package/dist/providers/alibaba.js.map +1 -0
- package/dist/providers/anthropic.d.ts +14 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +19 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/gemini.d.ts +44 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +156 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/glm.d.ts +25 -0
- package/dist/providers/glm.d.ts.map +1 -0
- package/dist/providers/glm.js +89 -0
- package/dist/providers/glm.js.map +1 -0
- package/dist/providers/ollama.d.ts +48 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +174 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/openrouter.d.ts +24 -0
- package/dist/providers/openrouter.d.ts.map +1 -0
- package/dist/providers/openrouter.js +36 -0
- package/dist/providers/openrouter.js.map +1 -0
- package/dist/verify.d.ts +24 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +262 -0
- package/dist/verify.js.map +1 -0
- package/package.json +57 -0
- package/scripts/copy-hooks.js +15 -0
- package/src/clients/claude-code.ts +340 -0
- package/src/clients/opencode.ts +618 -0
- package/src/config.ts +101 -0
- package/src/display.ts +151 -0
- package/src/hooks/index.ts +208 -0
- package/src/hooks/token-tracker.js +280 -0
- package/src/hooks/visual-enhancements.js +364 -0
- package/src/index.ts +1263 -0
- package/src/models.ts +366 -0
- package/src/providers/alibaba.ts +43 -0
- package/src/providers/anthropic.ts +23 -0
- package/src/providers/gemini.ts +136 -0
- package/src/providers/glm.ts +60 -0
- package/src/providers/ollama.ts +146 -0
- package/src/providers/openrouter.ts +42 -0
- package/src/verify.ts +258 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,618 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Client Handler
|
|
3
|
+
*
|
|
4
|
+
* Manages ~/.config/opencode/opencode.json for OpenCode
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from "fs-extra";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import * as os from "os";
|
|
10
|
+
|
|
11
|
+
export interface OpenCodeSettings {
|
|
12
|
+
$schema?: string;
|
|
13
|
+
provider?: Record<string, any>;
|
|
14
|
+
mcpServers?: Record<string, any>;
|
|
15
|
+
agents?: Record<string, any>;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the OpenCode config path
|
|
21
|
+
* Priority: ~/.config/opencode/opencode.json
|
|
22
|
+
*/
|
|
23
|
+
export function getOpenCodeConfigPath(): string {
|
|
24
|
+
return path.join(os.homedir(), ".config", "opencode", "opencode.json");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if OpenCode settings file exists
|
|
29
|
+
*/
|
|
30
|
+
export function opencodeSettingsExists(): boolean {
|
|
31
|
+
const configPath = getOpenCodeConfigPath();
|
|
32
|
+
return fs.existsSync(configPath);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Read current OpenCode settings
|
|
37
|
+
*/
|
|
38
|
+
export async function readOpenCodeSettings(): Promise<OpenCodeSettings> {
|
|
39
|
+
const configPath = getOpenCodeConfigPath();
|
|
40
|
+
|
|
41
|
+
if (!fs.existsSync(configPath)) {
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const content = await fs.readFile(configPath, "utf-8");
|
|
46
|
+
return JSON.parse(content);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Write OpenCode settings with backup
|
|
51
|
+
*/
|
|
52
|
+
export async function writeOpenCodeSettings(settings: OpenCodeSettings): Promise<void> {
|
|
53
|
+
const configPath = getOpenCodeConfigPath();
|
|
54
|
+
const configDir = path.dirname(configPath);
|
|
55
|
+
|
|
56
|
+
// Ensure directory exists
|
|
57
|
+
await fs.ensureDir(configDir);
|
|
58
|
+
|
|
59
|
+
// Backup existing settings if they exist
|
|
60
|
+
if (opencodeSettingsExists()) {
|
|
61
|
+
const backupPath = `${configPath}.backup.${Date.now()}`;
|
|
62
|
+
await fs.copyFile(configPath, backupPath);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Write new settings
|
|
66
|
+
await fs.writeFile(configPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Configure OpenCode for Alibaba Coding Plan
|
|
71
|
+
* Writes the full provider configuration with all models
|
|
72
|
+
*/
|
|
73
|
+
export async function configureAlibaba(apiKey: string): Promise<void> {
|
|
74
|
+
const settings = await readOpenCodeSettings();
|
|
75
|
+
|
|
76
|
+
// Set schema
|
|
77
|
+
settings.$schema = "https://opencode.ai/config.json";
|
|
78
|
+
|
|
79
|
+
// Configure bailian-coding-plan provider with all models
|
|
80
|
+
settings.provider = settings.provider || {};
|
|
81
|
+
settings.provider["bailian-coding-plan"] = {
|
|
82
|
+
npm: "@ai-sdk/anthropic",
|
|
83
|
+
name: "Model Studio Coding Plan",
|
|
84
|
+
options: {
|
|
85
|
+
baseURL: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1",
|
|
86
|
+
apiKey: apiKey
|
|
87
|
+
},
|
|
88
|
+
models: {
|
|
89
|
+
"qwen3.7-plus": {
|
|
90
|
+
name: "Qwen3.7 Plus",
|
|
91
|
+
modalities: {
|
|
92
|
+
input: ["text", "image"],
|
|
93
|
+
output: ["text"]
|
|
94
|
+
},
|
|
95
|
+
options: {
|
|
96
|
+
thinking: {
|
|
97
|
+
type: "enabled",
|
|
98
|
+
budgetTokens: 8192
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
limit: {
|
|
102
|
+
context: 1000000,
|
|
103
|
+
output: 65536
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"qwen3.6-plus": {
|
|
107
|
+
name: "Qwen3.6 Plus",
|
|
108
|
+
modalities: {
|
|
109
|
+
input: ["text", "image"],
|
|
110
|
+
output: ["text"]
|
|
111
|
+
},
|
|
112
|
+
options: {
|
|
113
|
+
thinking: {
|
|
114
|
+
type: "enabled",
|
|
115
|
+
budgetTokens: 8192
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
limit: {
|
|
119
|
+
context: 1000000,
|
|
120
|
+
output: 65536
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"qwen3-max-2026-01-23": {
|
|
124
|
+
name: "Qwen3 Max 2026-01-23",
|
|
125
|
+
modalities: {
|
|
126
|
+
input: ["text"],
|
|
127
|
+
output: ["text"]
|
|
128
|
+
},
|
|
129
|
+
limit: {
|
|
130
|
+
context: 262144,
|
|
131
|
+
output: 32768
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"qwen3-coder-next": {
|
|
135
|
+
name: "Qwen3 Coder Next",
|
|
136
|
+
modalities: {
|
|
137
|
+
input: ["text"],
|
|
138
|
+
output: ["text"]
|
|
139
|
+
},
|
|
140
|
+
limit: {
|
|
141
|
+
context: 262144,
|
|
142
|
+
output: 65536
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"qwen3-coder-plus": {
|
|
146
|
+
name: "Qwen3 Coder Plus",
|
|
147
|
+
modalities: {
|
|
148
|
+
input: ["text"],
|
|
149
|
+
output: ["text"]
|
|
150
|
+
},
|
|
151
|
+
limit: {
|
|
152
|
+
context: 1000000,
|
|
153
|
+
output: 65536
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
"MiniMax-M2.5": {
|
|
157
|
+
name: "MiniMax M2.5",
|
|
158
|
+
modalities: {
|
|
159
|
+
input: ["text"],
|
|
160
|
+
output: ["text"]
|
|
161
|
+
},
|
|
162
|
+
options: {
|
|
163
|
+
thinking: {
|
|
164
|
+
type: "enabled",
|
|
165
|
+
budgetTokens: 8192
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
limit: {
|
|
169
|
+
context: 200000,
|
|
170
|
+
output: 24576
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"glm-5": {
|
|
174
|
+
name: "GLM-5",
|
|
175
|
+
modalities: {
|
|
176
|
+
input: ["text"],
|
|
177
|
+
output: ["text"]
|
|
178
|
+
},
|
|
179
|
+
options: {
|
|
180
|
+
thinking: {
|
|
181
|
+
type: "enabled",
|
|
182
|
+
budgetTokens: 8192
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
limit: {
|
|
186
|
+
context: 200000,
|
|
187
|
+
output: 16384
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"glm-4.7": {
|
|
191
|
+
name: "GLM-4.7",
|
|
192
|
+
modalities: {
|
|
193
|
+
input: ["text"],
|
|
194
|
+
output: ["text"]
|
|
195
|
+
},
|
|
196
|
+
options: {
|
|
197
|
+
thinking: {
|
|
198
|
+
type: "enabled",
|
|
199
|
+
budgetTokens: 8192
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
limit: {
|
|
203
|
+
context: 256000,
|
|
204
|
+
output: 16384
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
"kimi-k2.5": {
|
|
208
|
+
name: "Kimi K2.5",
|
|
209
|
+
modalities: {
|
|
210
|
+
input: ["text", "image"],
|
|
211
|
+
output: ["text"]
|
|
212
|
+
},
|
|
213
|
+
options: {
|
|
214
|
+
thinking: {
|
|
215
|
+
type: "enabled",
|
|
216
|
+
budgetTokens: 8192
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
limit: {
|
|
220
|
+
context: 200000,
|
|
221
|
+
output: 32768
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
await writeOpenCodeSettings(settings);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Configure OpenCode for Anthropic (default)
|
|
232
|
+
* Removes bailian-coding-plan provider to use native Anthropic
|
|
233
|
+
*/
|
|
234
|
+
export async function configureAnthropic(): Promise<void> {
|
|
235
|
+
const settings = await readOpenCodeSettings();
|
|
236
|
+
|
|
237
|
+
// Remove bailian-coding-plan provider
|
|
238
|
+
if (settings.provider?.["bailian-coding-plan"]) {
|
|
239
|
+
delete settings.provider["bailian-coding-plan"];
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Remove openrouter provider
|
|
243
|
+
if (settings.provider?.["openrouter"]) {
|
|
244
|
+
delete settings.provider["openrouter"];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Remove ollama provider
|
|
248
|
+
if (settings.provider?.["ollama"]) {
|
|
249
|
+
delete settings.provider["ollama"];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Remove gemini provider
|
|
253
|
+
if (settings.provider?.["gemini"]) {
|
|
254
|
+
delete settings.provider["gemini"];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Remove glm provider
|
|
258
|
+
if (settings.provider?.["glm"]) {
|
|
259
|
+
delete settings.provider["glm"];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Clean up empty provider object
|
|
263
|
+
if (settings.provider && Object.keys(settings.provider).length === 0) {
|
|
264
|
+
delete settings.provider;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
await writeOpenCodeSettings(settings);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Configure OpenCode for GLM/Z.AI
|
|
272
|
+
* Auth is managed by coding-helper — reads baseURL and apiKey from Claude settings
|
|
273
|
+
*/
|
|
274
|
+
export async function configureGLM(baseURL: string, apiKey: string): Promise<void> {
|
|
275
|
+
const settings = await readOpenCodeSettings();
|
|
276
|
+
|
|
277
|
+
settings.$schema = "https://opencode.ai/config.json";
|
|
278
|
+
|
|
279
|
+
settings.provider = settings.provider || {};
|
|
280
|
+
settings.provider["glm"] = {
|
|
281
|
+
npm: "@ai-sdk/anthropic",
|
|
282
|
+
name: "GLM/Z.AI",
|
|
283
|
+
options: {
|
|
284
|
+
baseURL,
|
|
285
|
+
apiKey
|
|
286
|
+
},
|
|
287
|
+
models: {
|
|
288
|
+
"glm-5.1": {
|
|
289
|
+
name: "GLM-5.1",
|
|
290
|
+
modalities: {
|
|
291
|
+
input: ["text"],
|
|
292
|
+
output: ["text"]
|
|
293
|
+
},
|
|
294
|
+
options: {
|
|
295
|
+
thinking: {
|
|
296
|
+
type: "enabled",
|
|
297
|
+
budgetTokens: 8192
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
limit: {
|
|
301
|
+
context: 200000,
|
|
302
|
+
output: 16384
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
"glm-5v-turbo": {
|
|
306
|
+
name: "GLM-5V-Turbo",
|
|
307
|
+
modalities: {
|
|
308
|
+
input: ["text", "image"],
|
|
309
|
+
output: ["text"]
|
|
310
|
+
},
|
|
311
|
+
options: {
|
|
312
|
+
thinking: {
|
|
313
|
+
type: "enabled",
|
|
314
|
+
budgetTokens: 8192
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
limit: {
|
|
318
|
+
context: 200000,
|
|
319
|
+
output: 16384
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
"glm-5-turbo": {
|
|
323
|
+
name: "GLM-5-Turbo",
|
|
324
|
+
modalities: {
|
|
325
|
+
input: ["text"],
|
|
326
|
+
output: ["text"]
|
|
327
|
+
},
|
|
328
|
+
options: {
|
|
329
|
+
thinking: {
|
|
330
|
+
type: "enabled",
|
|
331
|
+
budgetTokens: 8192
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
limit: {
|
|
335
|
+
context: 200000,
|
|
336
|
+
output: 16384
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
"glm-4.7": {
|
|
340
|
+
name: "GLM-4.7",
|
|
341
|
+
modalities: {
|
|
342
|
+
input: ["text"],
|
|
343
|
+
output: ["text"]
|
|
344
|
+
},
|
|
345
|
+
options: {
|
|
346
|
+
thinking: {
|
|
347
|
+
type: "enabled",
|
|
348
|
+
budgetTokens: 8192
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
limit: {
|
|
352
|
+
context: 256000,
|
|
353
|
+
output: 16384
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
"glm-4.7-flash": {
|
|
357
|
+
name: "GLM-4.7-Flash",
|
|
358
|
+
modalities: {
|
|
359
|
+
input: ["text"],
|
|
360
|
+
output: ["text"]
|
|
361
|
+
},
|
|
362
|
+
limit: {
|
|
363
|
+
context: 256000,
|
|
364
|
+
output: 16384
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
await writeOpenCodeSettings(settings);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Configure OpenCode for OpenRouter
|
|
375
|
+
* Writes the openrouter provider with available models
|
|
376
|
+
*/
|
|
377
|
+
export async function configureOpenRouter(apiKey: string): Promise<void> {
|
|
378
|
+
const settings = await readOpenCodeSettings();
|
|
379
|
+
|
|
380
|
+
// Set schema
|
|
381
|
+
settings.$schema = "https://opencode.ai/config.json";
|
|
382
|
+
|
|
383
|
+
// Configure openrouter provider with models
|
|
384
|
+
settings.provider = settings.provider || {};
|
|
385
|
+
settings.provider["openrouter"] = {
|
|
386
|
+
npm: "@ai-sdk/openai",
|
|
387
|
+
name: "OpenRouter",
|
|
388
|
+
options: {
|
|
389
|
+
baseURL: "https://openrouter.ai/api/v1",
|
|
390
|
+
apiKey: apiKey
|
|
391
|
+
},
|
|
392
|
+
models: {
|
|
393
|
+
"qwen/qwen3.6-plus:free": {
|
|
394
|
+
name: "Qwen3.6 Plus (Free)",
|
|
395
|
+
modalities: {
|
|
396
|
+
input: ["text"],
|
|
397
|
+
output: ["text"]
|
|
398
|
+
},
|
|
399
|
+
limit: {
|
|
400
|
+
context: 131072,
|
|
401
|
+
output: 32768
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
"openrouter/free": {
|
|
405
|
+
name: "OpenRouter Free",
|
|
406
|
+
modalities: {
|
|
407
|
+
input: ["text"],
|
|
408
|
+
output: ["text"]
|
|
409
|
+
},
|
|
410
|
+
limit: {
|
|
411
|
+
context: 131072,
|
|
412
|
+
output: 32768
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
await writeOpenCodeSettings(settings);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Configure OpenCode for Ollama (via LiteLLM proxy on port 4000)
|
|
423
|
+
*/
|
|
424
|
+
export async function configureOllama(): Promise<void> {
|
|
425
|
+
const settings = await readOpenCodeSettings();
|
|
426
|
+
|
|
427
|
+
settings.$schema = "https://opencode.ai/config.json";
|
|
428
|
+
|
|
429
|
+
settings.provider = settings.provider || {};
|
|
430
|
+
settings.provider["ollama"] = {
|
|
431
|
+
npm: "@ai-sdk/openai",
|
|
432
|
+
name: "Ollama (Local)",
|
|
433
|
+
options: {
|
|
434
|
+
baseURL: "http://localhost:4000/v1",
|
|
435
|
+
apiKey: "ollama"
|
|
436
|
+
},
|
|
437
|
+
models: {
|
|
438
|
+
"deepseek-r1:latest": {
|
|
439
|
+
name: "DeepSeek R1",
|
|
440
|
+
modalities: {
|
|
441
|
+
input: ["text"],
|
|
442
|
+
output: ["text"]
|
|
443
|
+
},
|
|
444
|
+
limit: {
|
|
445
|
+
context: 128000,
|
|
446
|
+
output: 32768
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
"qwen2.5-coder:latest": {
|
|
450
|
+
name: "Qwen 2.5 Coder",
|
|
451
|
+
modalities: {
|
|
452
|
+
input: ["text"],
|
|
453
|
+
output: ["text"]
|
|
454
|
+
},
|
|
455
|
+
limit: {
|
|
456
|
+
context: 128000,
|
|
457
|
+
output: 32768
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
"llama3.1:latest": {
|
|
461
|
+
name: "Llama 3.1",
|
|
462
|
+
modalities: {
|
|
463
|
+
input: ["text", "image"],
|
|
464
|
+
output: ["text"]
|
|
465
|
+
},
|
|
466
|
+
limit: {
|
|
467
|
+
context: 128000,
|
|
468
|
+
output: 32768
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
"codellama:latest": {
|
|
472
|
+
name: "Code Llama",
|
|
473
|
+
modalities: {
|
|
474
|
+
input: ["text"],
|
|
475
|
+
output: ["text"]
|
|
476
|
+
},
|
|
477
|
+
limit: {
|
|
478
|
+
context: 100000,
|
|
479
|
+
output: 32768
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
await writeOpenCodeSettings(settings);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Configure OpenCode for Gemini (via LiteLLM proxy on port 4001)
|
|
490
|
+
*/
|
|
491
|
+
export async function configureGemini(apiKey: string): Promise<void> {
|
|
492
|
+
const settings = await readOpenCodeSettings();
|
|
493
|
+
|
|
494
|
+
settings.$schema = "https://opencode.ai/config.json";
|
|
495
|
+
|
|
496
|
+
settings.provider = settings.provider || {};
|
|
497
|
+
settings.provider["gemini"] = {
|
|
498
|
+
npm: "@ai-sdk/openai",
|
|
499
|
+
name: "Gemini (Google)",
|
|
500
|
+
options: {
|
|
501
|
+
baseURL: "http://localhost:4001/v1",
|
|
502
|
+
apiKey: apiKey
|
|
503
|
+
},
|
|
504
|
+
models: {
|
|
505
|
+
"gemini-2.5-pro": {
|
|
506
|
+
name: "Gemini 2.5 Pro",
|
|
507
|
+
modalities: {
|
|
508
|
+
input: ["text", "image"],
|
|
509
|
+
output: ["text"]
|
|
510
|
+
},
|
|
511
|
+
limit: {
|
|
512
|
+
context: 1000000,
|
|
513
|
+
output: 65536
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
"gemini-2.5-flash": {
|
|
517
|
+
name: "Gemini 2.5 Flash",
|
|
518
|
+
modalities: {
|
|
519
|
+
input: ["text", "image"],
|
|
520
|
+
output: ["text"]
|
|
521
|
+
},
|
|
522
|
+
limit: {
|
|
523
|
+
context: 1000000,
|
|
524
|
+
output: 65536
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
"gemini-2.5-flash-lite": {
|
|
528
|
+
name: "Gemini 2.5 Flash Lite",
|
|
529
|
+
modalities: {
|
|
530
|
+
input: ["text"],
|
|
531
|
+
output: ["text"]
|
|
532
|
+
},
|
|
533
|
+
limit: {
|
|
534
|
+
context: 1000000,
|
|
535
|
+
output: 65536
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
await writeOpenCodeSettings(settings);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Remove a specific provider from OpenCode settings
|
|
546
|
+
* Only removes the named provider, preserving others
|
|
547
|
+
*/
|
|
548
|
+
export async function removeProvider(providerKey: string): Promise<void> {
|
|
549
|
+
const settings = await readOpenCodeSettings();
|
|
550
|
+
|
|
551
|
+
if (settings.provider?.[providerKey]) {
|
|
552
|
+
delete settings.provider[providerKey];
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Clean up empty provider object
|
|
556
|
+
if (settings.provider && Object.keys(settings.provider).length === 0) {
|
|
557
|
+
delete settings.provider;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
await writeOpenCodeSettings(settings);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Get current provider from OpenCode settings
|
|
565
|
+
*/
|
|
566
|
+
export async function getCurrentProvider(): Promise<{
|
|
567
|
+
provider: string;
|
|
568
|
+
model?: string;
|
|
569
|
+
endpoint?: string;
|
|
570
|
+
} | null> {
|
|
571
|
+
if (!opencodeSettingsExists()) {
|
|
572
|
+
return { provider: "anthropic" };
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const settings = await readOpenCodeSettings();
|
|
576
|
+
|
|
577
|
+
// Check for bailian-coding-plan (Alibaba) configuration
|
|
578
|
+
if (settings.provider?.["bailian-coding-plan"]) {
|
|
579
|
+
return {
|
|
580
|
+
provider: "alibaba",
|
|
581
|
+
endpoint: settings.provider["bailian-coding-plan"].options?.baseURL
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Check for openrouter configuration
|
|
586
|
+
if (settings.provider?.["openrouter"]) {
|
|
587
|
+
return {
|
|
588
|
+
provider: "openrouter",
|
|
589
|
+
endpoint: "https://openrouter.ai/api/v1"
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Check for ollama configuration
|
|
594
|
+
if (settings.provider?.["ollama"]) {
|
|
595
|
+
return {
|
|
596
|
+
provider: "ollama",
|
|
597
|
+
endpoint: "http://localhost:4000/v1"
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Check for gemini configuration
|
|
602
|
+
if (settings.provider?.["gemini"]) {
|
|
603
|
+
return {
|
|
604
|
+
provider: "gemini",
|
|
605
|
+
endpoint: "http://localhost:4001/v1"
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Check for glm configuration
|
|
610
|
+
if (settings.provider?.["glm"]) {
|
|
611
|
+
return {
|
|
612
|
+
provider: "glm",
|
|
613
|
+
endpoint: settings.provider["glm"].options?.baseURL
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return { provider: "anthropic" };
|
|
618
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages API keys and settings in ~/.claude-ai-switcher/config.json
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from "fs-extra";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import * as os from "os";
|
|
10
|
+
|
|
11
|
+
const CONFIG_DIR = path.join(os.homedir(), ".claude-ai-switcher");
|
|
12
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
13
|
+
|
|
14
|
+
export interface UserConfig {
|
|
15
|
+
alibabaApiKey?: string;
|
|
16
|
+
openrouterApiKey?: string;
|
|
17
|
+
geminiApiKey?: string;
|
|
18
|
+
defaultProvider?: string;
|
|
19
|
+
defaultModel?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Ensure config directory exists
|
|
24
|
+
*/
|
|
25
|
+
async function ensureConfigDir(): Promise<void> {
|
|
26
|
+
await fs.ensureDir(CONFIG_DIR);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Read user configuration
|
|
31
|
+
*/
|
|
32
|
+
export async function readConfig(): Promise<UserConfig> {
|
|
33
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const content = await fs.readFile(CONFIG_FILE, "utf-8");
|
|
38
|
+
return JSON.parse(content);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Write user configuration
|
|
43
|
+
*/
|
|
44
|
+
export async function writeConfig(config: UserConfig): Promise<void> {
|
|
45
|
+
await ensureConfigDir();
|
|
46
|
+
await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get API key for a provider
|
|
51
|
+
*/
|
|
52
|
+
export async function getApiKey(provider: string): Promise<string | undefined> {
|
|
53
|
+
const config = await readConfig();
|
|
54
|
+
|
|
55
|
+
switch (provider) {
|
|
56
|
+
case "alibaba":
|
|
57
|
+
return config.alibabaApiKey;
|
|
58
|
+
case "openrouter":
|
|
59
|
+
return config.openrouterApiKey;
|
|
60
|
+
case "gemini":
|
|
61
|
+
return config.geminiApiKey;
|
|
62
|
+
default:
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Set API key for a provider
|
|
69
|
+
*/
|
|
70
|
+
export async function setApiKey(provider: string, apiKey: string): Promise<void> {
|
|
71
|
+
const config = await readConfig();
|
|
72
|
+
|
|
73
|
+
switch (provider) {
|
|
74
|
+
case "alibaba":
|
|
75
|
+
config.alibabaApiKey = apiKey;
|
|
76
|
+
break;
|
|
77
|
+
case "openrouter":
|
|
78
|
+
config.openrouterApiKey = apiKey;
|
|
79
|
+
break;
|
|
80
|
+
case "gemini":
|
|
81
|
+
config.geminiApiKey = apiKey;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await writeConfig(config);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check if API key is set for a provider
|
|
90
|
+
*/
|
|
91
|
+
export async function hasApiKey(provider: string): Promise<boolean> {
|
|
92
|
+
const apiKey = await getApiKey(provider);
|
|
93
|
+
return !!apiKey;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get config file path
|
|
98
|
+
*/
|
|
99
|
+
export function getConfigPath(): string {
|
|
100
|
+
return CONFIG_FILE;
|
|
101
|
+
}
|