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,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ollama Provider Configuration
|
|
3
|
+
*
|
|
4
|
+
* Uses local Ollama models via a LiteLLM proxy for Anthropic API compatibility.
|
|
5
|
+
* Ollama only speaks OpenAI format, so LiteLLM translates Anthropic Messages API
|
|
6
|
+
* requests into OpenAI Chat Completions format.
|
|
7
|
+
*
|
|
8
|
+
* Prerequisites:
|
|
9
|
+
* - Ollama installed and running on port 11434
|
|
10
|
+
* - LiteLLM installed (pip install 'litellm[proxy]')
|
|
11
|
+
* - LiteLLM proxy running on port 4000
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { platform } from "os";
|
|
15
|
+
import { providers, ollamaModels } from "../models";
|
|
16
|
+
|
|
17
|
+
export const OLLAMA_PROVIDER = providers.ollama;
|
|
18
|
+
|
|
19
|
+
export interface OllamaConfig {
|
|
20
|
+
provider: "ollama";
|
|
21
|
+
model: string;
|
|
22
|
+
endpoint: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const OLLAMA_ENDPOINT = "http://localhost:4000";
|
|
26
|
+
export const OLLAMA_LITELLM_PORT = 4000;
|
|
27
|
+
export const OLLAMA_PORT = 11434;
|
|
28
|
+
|
|
29
|
+
export function getOllamaConfig(model?: string): OllamaConfig {
|
|
30
|
+
return {
|
|
31
|
+
provider: "ollama",
|
|
32
|
+
model: model || "deepseek-r1:latest",
|
|
33
|
+
endpoint: OLLAMA_ENDPOINT
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getAvailableModels() {
|
|
38
|
+
return ollamaModels;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function findModel(modelId: string) {
|
|
42
|
+
return ollamaModels.find(m => m.id === modelId);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if litellm is installed
|
|
47
|
+
*/
|
|
48
|
+
export async function isLitellmInstalled(): Promise<boolean> {
|
|
49
|
+
try {
|
|
50
|
+
const { exec } = await import("child_process");
|
|
51
|
+
const { promisify } = await import("util");
|
|
52
|
+
const execAsync = promisify(exec);
|
|
53
|
+
|
|
54
|
+
const cmd = platform() === "win32" ? "where litellm" : "which litellm";
|
|
55
|
+
await execAsync(cmd);
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check if ollama is installed
|
|
64
|
+
*/
|
|
65
|
+
export async function isOllamaInstalled(): Promise<boolean> {
|
|
66
|
+
try {
|
|
67
|
+
const { exec } = await import("child_process");
|
|
68
|
+
const { promisify } = await import("util");
|
|
69
|
+
const execAsync = promisify(exec);
|
|
70
|
+
|
|
71
|
+
const cmd = platform() === "win32" ? "where ollama" : "which ollama";
|
|
72
|
+
await execAsync(cmd);
|
|
73
|
+
return true;
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if Ollama is running on port 11434
|
|
81
|
+
*/
|
|
82
|
+
export async function isOllamaRunning(): Promise<boolean> {
|
|
83
|
+
try {
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
86
|
+
const resp = await fetch(`http://localhost:${OLLAMA_PORT}/api/tags`, {
|
|
87
|
+
signal: controller.signal
|
|
88
|
+
});
|
|
89
|
+
clearTimeout(timeout);
|
|
90
|
+
return resp.ok;
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Check if LiteLLM proxy is running on a given port
|
|
98
|
+
*/
|
|
99
|
+
export async function isLitellmProxyRunning(port: number = OLLAMA_LITELLM_PORT): Promise<boolean> {
|
|
100
|
+
try {
|
|
101
|
+
const controller = new AbortController();
|
|
102
|
+
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
103
|
+
const resp = await fetch(`http://localhost:${port}/health`, {
|
|
104
|
+
signal: controller.signal
|
|
105
|
+
});
|
|
106
|
+
clearTimeout(timeout);
|
|
107
|
+
return resp.ok;
|
|
108
|
+
} catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Start LiteLLM proxy for Ollama as a detached background process
|
|
115
|
+
*/
|
|
116
|
+
export async function startLitellmProxy(model: string, port: number = OLLAMA_LITELLM_PORT): Promise<{ success: boolean; error?: string }> {
|
|
117
|
+
try {
|
|
118
|
+
// Already running?
|
|
119
|
+
if (await isLitellmProxyRunning(port)) {
|
|
120
|
+
return { success: true };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const { spawn } = await import("child_process");
|
|
124
|
+
const child = spawn("litellm", ["--model", `ollama/${model}`, "--port", String(port)], {
|
|
125
|
+
detached: true,
|
|
126
|
+
stdio: "ignore",
|
|
127
|
+
shell: false
|
|
128
|
+
});
|
|
129
|
+
child.unref();
|
|
130
|
+
|
|
131
|
+
// Poll health endpoint for up to 5 seconds
|
|
132
|
+
for (let i = 0; i < 10; i++) {
|
|
133
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
134
|
+
if (await isLitellmProxyRunning(port)) {
|
|
135
|
+
return { success: true };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { success: false, error: "LiteLLM proxy did not start within 5 seconds" };
|
|
140
|
+
} catch (error) {
|
|
141
|
+
return {
|
|
142
|
+
success: false,
|
|
143
|
+
error: error instanceof Error ? error.message : "Failed to start LiteLLM proxy"
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenRouter Provider Configuration
|
|
3
|
+
*
|
|
4
|
+
* Configures Claude Code and OpenCode to use OpenRouter
|
|
5
|
+
* with models like Qwen3.6 Plus and OpenRouter Free via Anthropic-compatible API.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { providers, openrouterModels } from "../models";
|
|
9
|
+
|
|
10
|
+
export const OPENROUTER_PROVIDER = providers.openrouter;
|
|
11
|
+
|
|
12
|
+
export interface OpenRouterConfig {
|
|
13
|
+
provider: "openrouter";
|
|
14
|
+
apiKey: string;
|
|
15
|
+
model: string;
|
|
16
|
+
endpoint: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const OPENROUTER_ENDPOINT = "https://openrouter.ai/api/v1";
|
|
20
|
+
|
|
21
|
+
export function getOpenRouterConfig(apiKey: string, model?: string): OpenRouterConfig {
|
|
22
|
+
return {
|
|
23
|
+
provider: "openrouter",
|
|
24
|
+
apiKey,
|
|
25
|
+
model: model || "qwen/qwen3.6-plus:free",
|
|
26
|
+
endpoint: OPENROUTER_ENDPOINT
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get available OpenRouter models
|
|
32
|
+
*/
|
|
33
|
+
export function getAvailableModels() {
|
|
34
|
+
return openrouterModels;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Find a model by ID
|
|
39
|
+
*/
|
|
40
|
+
export function findModel(modelId: string) {
|
|
41
|
+
return openrouterModels.find(m => m.id === modelId);
|
|
42
|
+
}
|
package/src/verify.ts
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key Verification
|
|
3
|
+
*
|
|
4
|
+
* Makes lightweight requests to each provider's API to verify keys are valid.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const TIMEOUT_MS = 5000;
|
|
8
|
+
|
|
9
|
+
export interface VerifyResult {
|
|
10
|
+
provider: string;
|
|
11
|
+
status: "ok" | "invalid" | "missing" | "error" | "skipped";
|
|
12
|
+
message?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function maskKey(key: string): string {
|
|
16
|
+
if (key.length <= 8) return "****";
|
|
17
|
+
return key.slice(0, 4) + "..." + key.slice(-4);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { maskKey };
|
|
21
|
+
|
|
22
|
+
async function fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {
|
|
23
|
+
const controller = new AbortController();
|
|
24
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
25
|
+
try {
|
|
26
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
27
|
+
} finally {
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Verify Alibaba Coding Plan API key with a minimal chat completion request.
|
|
34
|
+
*/
|
|
35
|
+
async function verifyAlibaba(apiKey: string): Promise<VerifyResult> {
|
|
36
|
+
try {
|
|
37
|
+
const res = await fetchWithTimeout(
|
|
38
|
+
"https://dashscope.aliyuncs.com/compatible-mode/v1/models",
|
|
39
|
+
{
|
|
40
|
+
method: "GET",
|
|
41
|
+
headers: {
|
|
42
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
// 200 OK means key works, 401/403 means invalid
|
|
47
|
+
if (res.ok) {
|
|
48
|
+
return { provider: "alibaba", status: "ok", message: "Key valid" };
|
|
49
|
+
}
|
|
50
|
+
if (res.status === 401 || res.status === 403) {
|
|
51
|
+
return { provider: "alibaba", status: "invalid", message: "Authentication failed" };
|
|
52
|
+
}
|
|
53
|
+
return { provider: "alibaba", status: "error", message: `HTTP ${res.status}` };
|
|
54
|
+
} catch {
|
|
55
|
+
return { provider: "alibaba", status: "error", message: "Connection failed" };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Verify OpenRouter API key by listing models.
|
|
61
|
+
*/
|
|
62
|
+
async function verifyOpenRouter(apiKey: string): Promise<VerifyResult> {
|
|
63
|
+
try {
|
|
64
|
+
const res = await fetchWithTimeout(
|
|
65
|
+
"https://openrouter.ai/api/v1/models",
|
|
66
|
+
{
|
|
67
|
+
method: "GET",
|
|
68
|
+
headers: {
|
|
69
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
70
|
+
"Content-Type": "application/json"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (res.ok) {
|
|
76
|
+
return { provider: "openrouter", status: "ok", message: "Key valid" };
|
|
77
|
+
}
|
|
78
|
+
if (res.status === 401 || res.status === 403) {
|
|
79
|
+
return { provider: "openrouter", status: "invalid", message: "Authentication failed" };
|
|
80
|
+
}
|
|
81
|
+
return { provider: "openrouter", status: "error", message: `HTTP ${res.status}` };
|
|
82
|
+
} catch {
|
|
83
|
+
return { provider: "openrouter", status: "error", message: "Connection failed" };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Verify GLM/Z.AI by checking if coding-helper is installed and authenticated.
|
|
89
|
+
* Uses the coding-helper CLI status check rather than a direct API call.
|
|
90
|
+
*/
|
|
91
|
+
async function verifyGLM(): Promise<VerifyResult> {
|
|
92
|
+
try {
|
|
93
|
+
const { exec } = await import("child_process");
|
|
94
|
+
const { promisify } = await import("util");
|
|
95
|
+
const execAsync = promisify(exec);
|
|
96
|
+
|
|
97
|
+
const { platform } = await import("os");
|
|
98
|
+
const checkCmd = platform() === "win32" ? "where coding-helper" : "which coding-helper";
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
await execAsync(checkCmd);
|
|
102
|
+
} catch {
|
|
103
|
+
return { provider: "glm", status: "error", message: "coding-helper not installed" };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Try to ping the GLM API — if env vars are set, the auth should work
|
|
107
|
+
const glmModel = process.env.ZHIPUAI_MODEL || process.env.ZAI_MODEL;
|
|
108
|
+
if (glmModel) {
|
|
109
|
+
return { provider: "glm", status: "ok", message: "coding-helper installed, env vars set" };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { provider: "glm", status: "ok", message: "coding-helper installed" };
|
|
113
|
+
} catch {
|
|
114
|
+
return { provider: "glm", status: "error", message: "Check failed" };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Verify Anthropic API key (optional — uses ANTHROPIC_API_KEY env var).
|
|
120
|
+
*/
|
|
121
|
+
async function verifyAnthropic(apiKey: string): Promise<VerifyResult> {
|
|
122
|
+
try {
|
|
123
|
+
const res = await fetchWithTimeout(
|
|
124
|
+
"https://api.anthropic.com/v1/models",
|
|
125
|
+
{
|
|
126
|
+
method: "GET",
|
|
127
|
+
headers: {
|
|
128
|
+
"x-api-key": apiKey,
|
|
129
|
+
"anthropic-version": "2023-06-01",
|
|
130
|
+
"Content-Type": "application/json"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (res.ok) {
|
|
136
|
+
return { provider: "anthropic", status: "ok", message: "Key valid" };
|
|
137
|
+
}
|
|
138
|
+
if (res.status === 401 || res.status === 403) {
|
|
139
|
+
return { provider: "anthropic", status: "invalid", message: "Authentication failed" };
|
|
140
|
+
}
|
|
141
|
+
return { provider: "anthropic", status: "error", message: `HTTP ${res.status}` };
|
|
142
|
+
} catch {
|
|
143
|
+
return { provider: "anthropic", status: "error", message: "Connection failed" };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Verify all configured API keys in parallel.
|
|
149
|
+
*/
|
|
150
|
+
export async function verifyAllKeys(keys: {
|
|
151
|
+
alibaba?: string;
|
|
152
|
+
openrouter?: string;
|
|
153
|
+
anthropic?: string;
|
|
154
|
+
checkGLM?: boolean;
|
|
155
|
+
checkOllama?: boolean;
|
|
156
|
+
gemini?: string;
|
|
157
|
+
}): Promise<VerifyResult[]> {
|
|
158
|
+
const checks: Promise<VerifyResult>[] = [];
|
|
159
|
+
|
|
160
|
+
if (keys.alibaba) {
|
|
161
|
+
checks.push(verifyAlibaba(keys.alibaba));
|
|
162
|
+
} else {
|
|
163
|
+
checks.push(Promise.resolve({ provider: "alibaba", status: "missing" }));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (keys.openrouter) {
|
|
167
|
+
checks.push(verifyOpenRouter(keys.openrouter));
|
|
168
|
+
} else {
|
|
169
|
+
checks.push(Promise.resolve({ provider: "openrouter", status: "missing" }));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (keys.anthropic) {
|
|
173
|
+
checks.push(verifyAnthropic(keys.anthropic));
|
|
174
|
+
} else {
|
|
175
|
+
checks.push(Promise.resolve({ provider: "anthropic", status: "missing" }));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (keys.checkGLM) {
|
|
179
|
+
checks.push(verifyGLM());
|
|
180
|
+
} else {
|
|
181
|
+
checks.push(Promise.resolve({ provider: "glm", status: "skipped" }));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (keys.checkOllama) {
|
|
185
|
+
checks.push(verifyOllama());
|
|
186
|
+
} else {
|
|
187
|
+
checks.push(Promise.resolve({ provider: "ollama", status: "skipped" }));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (keys.gemini) {
|
|
191
|
+
checks.push(verifyGemini(keys.gemini));
|
|
192
|
+
} else {
|
|
193
|
+
checks.push(Promise.resolve({ provider: "gemini", status: "missing" }));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return Promise.all(checks);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Verify Ollama by checking LiteLLM proxy and Ollama service.
|
|
201
|
+
*/
|
|
202
|
+
async function verifyOllama(): Promise<VerifyResult> {
|
|
203
|
+
try {
|
|
204
|
+
// Check LiteLLM proxy on port 4000
|
|
205
|
+
try {
|
|
206
|
+
const res = await fetchWithTimeout("http://localhost:4000/health", { method: "GET" });
|
|
207
|
+
if (!res.ok) {
|
|
208
|
+
return { provider: "ollama", status: "error", message: "LiteLLM proxy not healthy" };
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
return { provider: "ollama", status: "error", message: "LiteLLM proxy not running on port 4000" };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check Ollama on port 11434
|
|
215
|
+
try {
|
|
216
|
+
const res = await fetchWithTimeout("http://localhost:11434/api/tags", { method: "GET" });
|
|
217
|
+
if (res.ok) {
|
|
218
|
+
return { provider: "ollama", status: "ok", message: "Ollama + LiteLLM proxy running" };
|
|
219
|
+
}
|
|
220
|
+
return { provider: "ollama", status: "error", message: "Ollama not responding" };
|
|
221
|
+
} catch {
|
|
222
|
+
return { provider: "ollama", status: "error", message: "Ollama not running on port 11434" };
|
|
223
|
+
}
|
|
224
|
+
} catch {
|
|
225
|
+
return { provider: "ollama", status: "error", message: "Check failed" };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Verify Gemini API key and LiteLLM proxy.
|
|
231
|
+
*/
|
|
232
|
+
async function verifyGemini(apiKey: string): Promise<VerifyResult> {
|
|
233
|
+
// Verify Gemini API key independently of proxy status
|
|
234
|
+
try {
|
|
235
|
+
const res = await fetchWithTimeout(
|
|
236
|
+
"https://generativelanguage.googleapis.com/v1beta/models",
|
|
237
|
+
{ method: "GET", headers: { "x-goog-api-key": apiKey } }
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
if (res.ok) {
|
|
241
|
+
// Key is valid — also check proxy status for informational purposes
|
|
242
|
+
let proxyMsg = "";
|
|
243
|
+
try {
|
|
244
|
+
const proxyRes = await fetchWithTimeout("http://localhost:4001/health", { method: "GET" });
|
|
245
|
+
proxyMsg = proxyRes.ok ? ", proxy running" : ", proxy not running";
|
|
246
|
+
} catch {
|
|
247
|
+
proxyMsg = ", proxy not running";
|
|
248
|
+
}
|
|
249
|
+
return { provider: "gemini", status: "ok", message: `Key valid${proxyMsg}` };
|
|
250
|
+
}
|
|
251
|
+
if (res.status === 400 || res.status === 401 || res.status === 403) {
|
|
252
|
+
return { provider: "gemini", status: "invalid", message: "Authentication failed" };
|
|
253
|
+
}
|
|
254
|
+
return { provider: "gemini", status: "error", message: `HTTP ${res.status}` };
|
|
255
|
+
} catch {
|
|
256
|
+
return { provider: "gemini", status: "error", message: "Connection failed" };
|
|
257
|
+
}
|
|
258
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|