@tonyclaw/llm-inspector 1.7.3 → 1.7.5
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/.output/cli.js +22 -1
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{index-Bf_WGooQ.js → index-BkNsdDqV.js} +2 -2
- package/.output/public/assets/{main-CpIX1ZHy.js → main-CC-HN8LQ.js} +1 -1
- package/.output/server/_ssr/{index-D0iGQ1Zd.mjs → index-DosJndBx.mjs} +3 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-BWDeDWmr.mjs → router-4NfH7bm_.mjs} +91 -72
- package/.output/server/{_tanstack-start-manifest_v-b6u6g-Cr.mjs → _tanstack-start-manifest_v-CdS0WV2N.mjs} +1 -1
- package/.output/server/index.mjs +23 -23
- package/package.json +1 -1
- package/src/cli.ts +24 -1
- package/src/components/providers/ProviderLogo.tsx +2 -1
- package/src/proxy/providers.ts +74 -64
- package/src/routes/api/providers.$providerId.ts +2 -0
package/src/proxy/providers.ts
CHANGED
|
@@ -31,82 +31,70 @@ type ProvidersStore = z.infer<typeof ProvidersStoreSchema>;
|
|
|
31
31
|
|
|
32
32
|
// Using conf for storage - works in any Node.js environment without Electron
|
|
33
33
|
// Note: conf stores data in plain JSON. For production, consider additional encryption.
|
|
34
|
+
const configPath = process.env["LLM_INSPECTOR_CONFIG_PATH"];
|
|
34
35
|
export const store = new Conf<ProvidersStore>({
|
|
35
36
|
projectName: "llm-inspector",
|
|
36
37
|
defaults: {
|
|
37
38
|
providers: [],
|
|
38
39
|
},
|
|
40
|
+
...(configPath !== undefined ? { path: configPath } : {}),
|
|
39
41
|
});
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
|
-
* Migrates
|
|
43
|
-
* Old configs had anthropicBaseUrl/openaiBaseUrl, we now keep both.
|
|
44
|
+
* Migrates a single provider config to preserve both Anthropic and OpenAI URLs.
|
|
44
45
|
*/
|
|
45
|
-
function
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (desc !== undefined && typeof desc.value === "string") {
|
|
55
|
-
return desc.value;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return "";
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const oldAnthropicBaseUrl = getOldUrl(p, "anthropicBaseUrl");
|
|
62
|
-
const oldOpenaiBaseUrl = getOldUrl(p, "openaiBaseUrl");
|
|
63
|
-
const currentAnthropicUrl = p.anthropicBaseUrl ?? "";
|
|
64
|
-
const currentOpenaiUrl = p.openaiBaseUrl ?? "";
|
|
65
|
-
|
|
66
|
-
// If already migrated (has format field and at least one URL), skip
|
|
67
|
-
if (p.format !== undefined && (currentAnthropicUrl !== "" || currentOpenaiUrl !== "")) {
|
|
68
|
-
return p;
|
|
69
|
-
}
|
|
46
|
+
export function migrateProvider(p: ProviderConfig): ProviderConfig {
|
|
47
|
+
const currentAnthropicUrl = p.anthropicBaseUrl ?? "";
|
|
48
|
+
const currentOpenaiUrl = p.openaiBaseUrl ?? "";
|
|
49
|
+
|
|
50
|
+
// If already migrated (has at least one URL set), skip
|
|
51
|
+
// format may be undefined but if URL is set, it's already migrated
|
|
52
|
+
if (currentAnthropicUrl !== "" || currentOpenaiUrl !== "") {
|
|
53
|
+
return p;
|
|
54
|
+
}
|
|
70
55
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
baseUrl
|
|
93
|
-
if (format === "openai") {
|
|
94
|
-
return { ...p, format, baseUrl, anthropicBaseUrl: "", openaiBaseUrl: p.baseUrl };
|
|
95
|
-
} else {
|
|
96
|
-
return { ...p, format, baseUrl, anthropicBaseUrl: p.baseUrl, openaiBaseUrl: "" };
|
|
97
|
-
}
|
|
56
|
+
// Determine primary format based on which URL is set
|
|
57
|
+
let format: "anthropic" | "openai" | undefined;
|
|
58
|
+
let baseUrl: string | undefined;
|
|
59
|
+
|
|
60
|
+
if (currentAnthropicUrl !== "" && currentOpenaiUrl !== "") {
|
|
61
|
+
// Both URLs set - prefer anthropic as default, use baseUrl for backward compat
|
|
62
|
+
format = p.format ?? "anthropic";
|
|
63
|
+
baseUrl = p.baseUrl !== undefined && p.baseUrl !== "" ? p.baseUrl : currentAnthropicUrl;
|
|
64
|
+
} else if (currentOpenaiUrl !== "") {
|
|
65
|
+
format = "openai";
|
|
66
|
+
baseUrl = currentOpenaiUrl;
|
|
67
|
+
} else if (currentAnthropicUrl !== "") {
|
|
68
|
+
format = "anthropic";
|
|
69
|
+
baseUrl = currentAnthropicUrl;
|
|
70
|
+
} else if (p.format !== undefined && p.baseUrl !== undefined && p.baseUrl !== "") {
|
|
71
|
+
// Only baseUrl is set (legacy config) - migrate based on format
|
|
72
|
+
format = p.format;
|
|
73
|
+
baseUrl = p.baseUrl;
|
|
74
|
+
if (format === "openai") {
|
|
75
|
+
return { ...p, format, baseUrl, anthropicBaseUrl: "", openaiBaseUrl: p.baseUrl };
|
|
76
|
+
} else {
|
|
77
|
+
return { ...p, format, baseUrl, anthropicBaseUrl: p.baseUrl, openaiBaseUrl: "" };
|
|
98
78
|
}
|
|
79
|
+
}
|
|
99
80
|
|
|
100
|
-
|
|
81
|
+
return {
|
|
82
|
+
...p,
|
|
83
|
+
format,
|
|
84
|
+
baseUrl,
|
|
85
|
+
anthropicBaseUrl: currentAnthropicUrl,
|
|
86
|
+
openaiBaseUrl: currentOpenaiUrl,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
101
89
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Migrates existing provider configs to preserve both Anthropic and OpenAI URLs.
|
|
92
|
+
* Old configs had anthropicBaseUrl/openaiBaseUrl, we now keep both.
|
|
93
|
+
*/
|
|
94
|
+
function migrateProviders(): void {
|
|
95
|
+
const providers = store.get("providers", []);
|
|
96
|
+
const updated = providers.map(migrateProvider);
|
|
97
|
+
const migrated = updated.some((p, i) => JSON.stringify(p) !== JSON.stringify(providers[i]));
|
|
110
98
|
|
|
111
99
|
if (migrated) {
|
|
112
100
|
store.set("providers", updated);
|
|
@@ -116,6 +104,21 @@ function migrateProviders(): void {
|
|
|
116
104
|
// Run migration on module load
|
|
117
105
|
migrateProviders();
|
|
118
106
|
|
|
107
|
+
// Override with JSON env var if provided (for testing)
|
|
108
|
+
const providersJson = process.env["LLM_INSPECTOR_PROVIDERS_JSON"];
|
|
109
|
+
if (providersJson !== undefined) {
|
|
110
|
+
try {
|
|
111
|
+
const parsed = ProviderConfigSchema.array().safeParse(JSON.parse(providersJson));
|
|
112
|
+
if (parsed.success) {
|
|
113
|
+
// Apply migration to JSON providers (e.g., convert baseUrl to openaiBaseUrl/anthropicBaseUrl)
|
|
114
|
+
const migrated = parsed.data.map(migrateProvider);
|
|
115
|
+
store.set("providers", migrated);
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
// Ignore invalid JSON
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
119
122
|
export function getProviders(): ProviderConfig[] {
|
|
120
123
|
return store.get("providers", []);
|
|
121
124
|
}
|
|
@@ -128,7 +131,7 @@ export function getProvider(id: string): ProviderConfig | undefined {
|
|
|
128
131
|
/**
|
|
129
132
|
* Normalizes an API key by stripping "Bearer " prefix if present.
|
|
130
133
|
*/
|
|
131
|
-
function normalizeApiKey(apiKey: string): string {
|
|
134
|
+
export function normalizeApiKey(apiKey: string): string {
|
|
132
135
|
return apiKey.replace(/^Bearer\s+/i, "").trim();
|
|
133
136
|
}
|
|
134
137
|
|
|
@@ -152,6 +155,8 @@ export function addProvider(
|
|
|
152
155
|
authHeader: authHeader ?? "bearer",
|
|
153
156
|
createdAt: now,
|
|
154
157
|
updatedAt: now,
|
|
158
|
+
anthropicBaseUrl: format === "anthropic" && baseUrl !== undefined ? baseUrl : "",
|
|
159
|
+
openaiBaseUrl: format === "openai" && baseUrl !== undefined ? baseUrl : "",
|
|
155
160
|
};
|
|
156
161
|
providers.push(newProvider);
|
|
157
162
|
store.set("providers", providers);
|
|
@@ -176,6 +181,11 @@ export function updateProvider(
|
|
|
176
181
|
authHeader: updates.authHeader ?? existing.authHeader,
|
|
177
182
|
createdAt: existing.createdAt,
|
|
178
183
|
updatedAt: new Date().toISOString(),
|
|
184
|
+
// Handle format-specific URLs
|
|
185
|
+
anthropicBaseUrl:
|
|
186
|
+
updates.anthropicBaseUrl !== undefined ? updates.anthropicBaseUrl : existing.anthropicBaseUrl,
|
|
187
|
+
openaiBaseUrl:
|
|
188
|
+
updates.openaiBaseUrl !== undefined ? updates.openaiBaseUrl : existing.openaiBaseUrl,
|
|
179
189
|
};
|
|
180
190
|
const index = providers.findIndex((p) => p.id === id);
|
|
181
191
|
providers[index] = updated;
|
|
@@ -9,6 +9,8 @@ const ProviderUpdateSchema = z.object({
|
|
|
9
9
|
baseUrl: z.string().min(1, "Base URL is required").optional(),
|
|
10
10
|
model: z.string().min(1, "Model is required").optional(),
|
|
11
11
|
authHeader: z.enum(["bearer", "x-api-key"]).optional(),
|
|
12
|
+
anthropicBaseUrl: z.string().optional(),
|
|
13
|
+
openaiBaseUrl: z.string().optional(),
|
|
12
14
|
});
|
|
13
15
|
|
|
14
16
|
export const Route = createFileRoute("/api/providers/$providerId")({
|