@tonyclaw/agent-inspector 2.0.8 → 2.0.10
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/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-Djb4XFZS.js → CompareDrawer-BWMipEjS.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-DIAgurux.js +114 -0
- package/.output/public/assets/{ReplayDialog-DBPMIs2I.js → ReplayDialog-DaSzZsvM.js} +1 -1
- package/.output/public/assets/RequestAnatomy-BfQmPHCd.js +1 -0
- package/.output/public/assets/ResponseView-CblEWDMx.js +1 -0
- package/.output/public/assets/StreamingChunkSequence-B4atnL99.js +1 -0
- package/.output/public/assets/_sessionId-DUzYnZXV.js +1 -0
- package/.output/public/assets/index-CxhRsekH.css +1 -0
- package/.output/public/assets/index-jagRsQaV.js +1 -0
- package/.output/public/assets/{main-CwhqfJqM.js → main-DE6Y4-wV.js} +2 -2
- package/.output/server/{_sessionId-DzBy9gLa.mjs → _sessionId-61HO9rUI.mjs} +3 -3
- package/.output/server/_ssr/{CompareDrawer-C5NBvE2e.mjs → CompareDrawer-CSPqneYm.mjs} +2 -2
- package/.output/server/_ssr/{ProxyViewerContainer-DiZjOTQT.mjs → ProxyViewerContainer-DrSMa5O7.mjs} +159 -14
- package/.output/server/_ssr/{ReplayDialog-EpvTwFvp.mjs → ReplayDialog-Dn3naENv.mjs} +3 -3
- package/.output/server/_ssr/{RequestAnatomy-DaQjhixp.mjs → RequestAnatomy-D1X3f3AX.mjs} +336 -42
- package/.output/server/_ssr/{ResponseView-D8tGHCGY.mjs → ResponseView-CwXAWOoE.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-DGQnb1QE.mjs → StreamingChunkSequence-DRSR2FfN.mjs} +2 -2
- package/.output/server/_ssr/{index-N25hqa7t.mjs → index-D4zn5CAg.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-Bf0m6F0G.mjs → router-CSRaa_tu.mjs} +531 -111
- package/.output/server/{_tanstack-start-manifest_v-DlAyJ5DB.mjs → _tanstack-start-manifest_v-CfvVUYYj.mjs} +1 -1
- package/.output/server/index.mjs +60 -60
- package/bin/agent-inspector.js +2 -0
- package/package.json +3 -2
- package/src/components/providers/ProviderCard.tsx +59 -5
- package/src/components/providers/ProviderForm.tsx +42 -0
- package/src/components/providers/ProvidersPanel.tsx +68 -0
- package/src/components/proxy-viewer/LogEntry.tsx +6 -1
- package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +134 -3
- package/src/components/proxy-viewer/anatomy/contextIntelligence.ts +298 -40
- package/src/lib/providerContract.ts +12 -0
- package/src/lib/providerModelMetadata.ts +327 -0
- package/src/proxy/providers.ts +112 -52
- package/src/routes/api/providers.$providerId.model-metadata.ts +134 -0
- package/src/routes/api/providers.$providerId.ts +3 -0
- package/src/routes/api/providers.ts +2 -0
- package/.output/public/assets/ProxyViewerContainer-CWdkPZsJ.js +0 -114
- package/.output/public/assets/RequestAnatomy-FBfRNQN7.js +0 -1
- package/.output/public/assets/ResponseView-Db4pJL7U.js +0 -1
- package/.output/public/assets/StreamingChunkSequence-BH175O6R.js +0 -1
- package/.output/public/assets/_sessionId-3ipVoQtW.js +0 -1
- package/.output/public/assets/index-Bo1dGS4R.css +0 -1
- package/.output/public/assets/index-CJgaCJo8.js +0 -1
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ProviderConfig, ProviderModelMetadata } from "./providerContract";
|
|
3
|
+
|
|
4
|
+
export const ProviderRegistryModelSchema = z.object({
|
|
5
|
+
id: z.string().min(1),
|
|
6
|
+
aliases: z.array(z.string()).optional().default([]),
|
|
7
|
+
contextWindow: z.number().int().positive().optional(),
|
|
8
|
+
outputLimit: z.number().int().positive().optional(),
|
|
9
|
+
sourceUrl: z.string().optional(),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const ProviderRegistryProviderSchema = z.object({
|
|
13
|
+
name: z.string().min(1).optional(),
|
|
14
|
+
aliases: z.array(z.string()).optional().default([]),
|
|
15
|
+
baseUrls: z.array(z.string()).optional().default([]),
|
|
16
|
+
models: z.array(ProviderRegistryModelSchema).min(1),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const ProviderModelRegistrySchema = z.object({
|
|
20
|
+
version: z.number().int().positive().optional(),
|
|
21
|
+
updatedAt: z.string().optional(),
|
|
22
|
+
providers: z.array(ProviderRegistryProviderSchema).optional().default([]),
|
|
23
|
+
models: z.array(ProviderRegistryModelSchema).optional().default([]),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export type ProviderRegistryModel = z.infer<typeof ProviderRegistryModelSchema>;
|
|
27
|
+
export type ProviderModelRegistry = z.infer<typeof ProviderModelRegistrySchema>;
|
|
28
|
+
|
|
29
|
+
export type ProviderContextWindowMatch = {
|
|
30
|
+
providerName: string;
|
|
31
|
+
model: string;
|
|
32
|
+
tokens: number;
|
|
33
|
+
sourceUrl?: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type ProviderModelRegistryApplyResult = {
|
|
37
|
+
modelMetadata: ProviderModelMetadata[];
|
|
38
|
+
imported: number;
|
|
39
|
+
matchedModels: string[];
|
|
40
|
+
missingModels: string[];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const BUILTIN_MODEL_METADATA_SOURCE_URL = "agent-inspector://builtin/model-metadata";
|
|
44
|
+
|
|
45
|
+
export const BUILTIN_PROVIDER_MODEL_REGISTRY: ProviderModelRegistry = {
|
|
46
|
+
version: 1,
|
|
47
|
+
updatedAt: "2026-06-23T00:00:00.000Z",
|
|
48
|
+
providers: [
|
|
49
|
+
{
|
|
50
|
+
name: "DeepSeek",
|
|
51
|
+
aliases: ["deepseek"],
|
|
52
|
+
baseUrls: ["https://api.deepseek.com"],
|
|
53
|
+
models: [
|
|
54
|
+
{
|
|
55
|
+
id: "deepseek-v4-pro",
|
|
56
|
+
aliases: ["DeepSeek-V4-Pro"],
|
|
57
|
+
contextWindow: 1_000_000,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "deepseek-v4-flash",
|
|
61
|
+
aliases: ["DeepSeek-V4-Flash"],
|
|
62
|
+
contextWindow: 1_000_000,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "MiniMax",
|
|
68
|
+
aliases: ["minimax"],
|
|
69
|
+
baseUrls: ["https://api.minimaxi.com/anthropic"],
|
|
70
|
+
models: [
|
|
71
|
+
{
|
|
72
|
+
id: "MiniMax M3",
|
|
73
|
+
aliases: ["MiniMax-M3", "minimax-m3"],
|
|
74
|
+
contextWindow: 1_000_000,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "MiniMax M2.7",
|
|
78
|
+
aliases: ["MiniMax-M2.7", "minimax-m2.7"],
|
|
79
|
+
contextWindow: 204_800,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: "MiniMax M2.7-highspeed",
|
|
83
|
+
aliases: ["MiniMax-M2.7-highspeed", "minimax-m2.7-highspeed"],
|
|
84
|
+
contextWindow: 204_800,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "MiniMax M2.5",
|
|
88
|
+
aliases: ["MiniMax-M2.5", "minimax-m2.5"],
|
|
89
|
+
contextWindow: 204_800,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "MiniMax M2.5-highspeed",
|
|
93
|
+
aliases: ["MiniMax-M2.5-highspeed", "minimax-m2.5-highspeed"],
|
|
94
|
+
contextWindow: 204_800,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "MiniMax M2.1",
|
|
98
|
+
aliases: ["MiniMax-M2.1", "minimax-m2.1"],
|
|
99
|
+
contextWindow: 204_800,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "MiniMax M2.1-highspeed",
|
|
103
|
+
aliases: ["MiniMax-M2.1-highspeed", "minimax-m2.1-highspeed"],
|
|
104
|
+
contextWindow: 204_800,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
models: [],
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
function normalizeModelName(value: string): string {
|
|
113
|
+
return value.trim().toLowerCase().replace(/\s+/g, "-");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function normalizeProviderName(value: string): string {
|
|
117
|
+
return value.trim().toLowerCase().replace(/\s+/g, "");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normalizeUrl(value: string | undefined): string | null {
|
|
121
|
+
if (value === undefined) return null;
|
|
122
|
+
const trimmed = value.trim();
|
|
123
|
+
if (trimmed === "") return null;
|
|
124
|
+
try {
|
|
125
|
+
const parsed = new URL(trimmed);
|
|
126
|
+
const path = parsed.pathname.replace(/\/+$/, "");
|
|
127
|
+
return `${parsed.origin}${path}`.toLowerCase();
|
|
128
|
+
} catch {
|
|
129
|
+
return trimmed.replace(/\/+$/, "").toLowerCase();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function providerBaseUrls(provider: ProviderConfig): string[] {
|
|
134
|
+
const urls: string[] = [];
|
|
135
|
+
if (provider.anthropicBaseUrl !== undefined && provider.anthropicBaseUrl.trim() !== "") {
|
|
136
|
+
urls.push(provider.anthropicBaseUrl);
|
|
137
|
+
}
|
|
138
|
+
if (provider.openaiBaseUrl !== undefined && provider.openaiBaseUrl.trim() !== "") {
|
|
139
|
+
urls.push(provider.openaiBaseUrl);
|
|
140
|
+
}
|
|
141
|
+
if (provider.baseUrl !== undefined && provider.baseUrl.trim() !== "") {
|
|
142
|
+
urls.push(provider.baseUrl);
|
|
143
|
+
}
|
|
144
|
+
return urls;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function providerModels(provider: ProviderConfig): string[] {
|
|
148
|
+
const models = provider.models.filter((model) => model.trim() !== "");
|
|
149
|
+
if (models.length > 0) return models;
|
|
150
|
+
if (provider.model !== undefined && provider.model.trim() !== "") return [provider.model];
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function registryModelHasUsableMetadata(model: ProviderRegistryModel): boolean {
|
|
155
|
+
return model.contextWindow !== undefined || model.outputLimit !== undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function registryModelMatches(model: string, registryModel: ProviderRegistryModel): boolean {
|
|
159
|
+
const normalized = normalizeModelName(model);
|
|
160
|
+
if (normalized === normalizeModelName(registryModel.id)) return true;
|
|
161
|
+
return registryModel.aliases.some((alias) => normalized === normalizeModelName(alias));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function providerEntryMatches(
|
|
165
|
+
provider: ProviderConfig,
|
|
166
|
+
entry: ProviderModelRegistry["providers"][number],
|
|
167
|
+
): boolean {
|
|
168
|
+
const providerName = normalizeProviderName(provider.name);
|
|
169
|
+
const names = entry.name === undefined ? [...entry.aliases] : [entry.name, ...entry.aliases];
|
|
170
|
+
const nameMatches = names.some((name) => {
|
|
171
|
+
const normalized = normalizeProviderName(name);
|
|
172
|
+
return providerName.includes(normalized) || normalized.includes(providerName);
|
|
173
|
+
});
|
|
174
|
+
if (nameMatches) return true;
|
|
175
|
+
|
|
176
|
+
const normalizedProviderUrls = providerBaseUrls(provider)
|
|
177
|
+
.map((url) => normalizeUrl(url))
|
|
178
|
+
.filter((url): url is string => url !== null);
|
|
179
|
+
const baseUrlMatches = entry.baseUrls.some((url) => {
|
|
180
|
+
const normalized = normalizeUrl(url);
|
|
181
|
+
return normalized !== null && normalizedProviderUrls.includes(normalized);
|
|
182
|
+
});
|
|
183
|
+
if (baseUrlMatches) return true;
|
|
184
|
+
|
|
185
|
+
return providerModels(provider).some((model) =>
|
|
186
|
+
entry.models.some((registryModel) => registryModelMatches(model, registryModel)),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function findRegistryModel(
|
|
191
|
+
provider: ProviderConfig,
|
|
192
|
+
model: string,
|
|
193
|
+
registry: ProviderModelRegistry,
|
|
194
|
+
): ProviderRegistryModel | null {
|
|
195
|
+
for (const providerEntry of registry.providers) {
|
|
196
|
+
if (!providerEntryMatches(provider, providerEntry)) continue;
|
|
197
|
+
const matched = providerEntry.models.find((registryModel) =>
|
|
198
|
+
registryModelMatches(model, registryModel),
|
|
199
|
+
);
|
|
200
|
+
if (matched !== undefined && registryModelHasUsableMetadata(matched)) return matched;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const topLevelMatched = registry.models.find((registryModel) =>
|
|
204
|
+
registryModelMatches(model, registryModel),
|
|
205
|
+
);
|
|
206
|
+
if (topLevelMatched !== undefined && registryModelHasUsableMetadata(topLevelMatched)) {
|
|
207
|
+
return topLevelMatched;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function findProviderModelMetadata(
|
|
214
|
+
provider: ProviderConfig,
|
|
215
|
+
model: string,
|
|
216
|
+
): ProviderModelMetadata | null {
|
|
217
|
+
const normalized = normalizeModelName(model);
|
|
218
|
+
const metadata = provider.modelMetadata ?? [];
|
|
219
|
+
return metadata.find((entry) => normalizeModelName(entry.model) === normalized) ?? null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function providerHasContextMetadata(provider: ProviderConfig): boolean {
|
|
223
|
+
const models = providerModels(provider);
|
|
224
|
+
if (models.length === 0) return false;
|
|
225
|
+
return models.every((model) => {
|
|
226
|
+
const metadata = findProviderModelMetadata(provider, model);
|
|
227
|
+
return metadata !== null && metadata.contextWindow !== undefined;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function resolveProviderContextWindow(
|
|
232
|
+
model: string | null,
|
|
233
|
+
providers: readonly ProviderConfig[],
|
|
234
|
+
): ProviderContextWindowMatch | null {
|
|
235
|
+
if (model === null) return null;
|
|
236
|
+
for (const provider of providers) {
|
|
237
|
+
const metadata = findProviderModelMetadata(provider, model);
|
|
238
|
+
if (metadata !== null && metadata.contextWindow !== undefined) {
|
|
239
|
+
return {
|
|
240
|
+
providerName: provider.name,
|
|
241
|
+
model: metadata.model,
|
|
242
|
+
tokens: metadata.contextWindow,
|
|
243
|
+
sourceUrl: metadata.sourceUrl,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function applyProviderModelRegistry(
|
|
251
|
+
provider: ProviderConfig,
|
|
252
|
+
registry: ProviderModelRegistry,
|
|
253
|
+
sourceUrl: string,
|
|
254
|
+
updatedAt: string,
|
|
255
|
+
mode: "replace" | "fill-missing" = "replace",
|
|
256
|
+
): ProviderModelRegistryApplyResult {
|
|
257
|
+
const existing = new Map<string, ProviderModelMetadata>();
|
|
258
|
+
for (const metadata of provider.modelMetadata ?? []) {
|
|
259
|
+
existing.set(normalizeModelName(metadata.model), metadata);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const matchedModels: string[] = [];
|
|
263
|
+
const missingModels: string[] = [];
|
|
264
|
+
|
|
265
|
+
for (const model of providerModels(provider)) {
|
|
266
|
+
const registryModel = findRegistryModel(provider, model, registry);
|
|
267
|
+
if (registryModel === null) {
|
|
268
|
+
missingModels.push(model);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const normalized = normalizeModelName(model);
|
|
273
|
+
const existingMetadata = existing.get(normalized);
|
|
274
|
+
if (
|
|
275
|
+
mode === "fill-missing" &&
|
|
276
|
+
existingMetadata !== undefined &&
|
|
277
|
+
existingMetadata.contextWindow !== undefined &&
|
|
278
|
+
(existingMetadata.outputLimit !== undefined || registryModel.outputLimit === undefined)
|
|
279
|
+
) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
existing.set(normalized, {
|
|
284
|
+
model,
|
|
285
|
+
contextWindow: existingMetadata?.contextWindow ?? registryModel.contextWindow,
|
|
286
|
+
outputLimit: existingMetadata?.outputLimit ?? registryModel.outputLimit,
|
|
287
|
+
source: existingMetadata?.source ?? "registry",
|
|
288
|
+
sourceUrl: existingMetadata?.sourceUrl ?? registryModel.sourceUrl ?? sourceUrl,
|
|
289
|
+
updatedAt,
|
|
290
|
+
});
|
|
291
|
+
matchedModels.push(model);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const modelMetadata = [...existing.values()].toSorted((a, b) => a.model.localeCompare(b.model));
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
modelMetadata,
|
|
298
|
+
imported: matchedModels.length,
|
|
299
|
+
matchedModels,
|
|
300
|
+
missingModels,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function applyBuiltinProviderModelMetadata(
|
|
305
|
+
provider: ProviderConfig,
|
|
306
|
+
updatedAt: string,
|
|
307
|
+
): ProviderModelRegistryApplyResult {
|
|
308
|
+
return applyProviderModelRegistry(
|
|
309
|
+
provider,
|
|
310
|
+
BUILTIN_PROVIDER_MODEL_REGISTRY,
|
|
311
|
+
BUILTIN_MODEL_METADATA_SOURCE_URL,
|
|
312
|
+
updatedAt,
|
|
313
|
+
"fill-missing",
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function withBuiltinProviderModelMetadata(
|
|
318
|
+
provider: ProviderConfig,
|
|
319
|
+
updatedAt: string,
|
|
320
|
+
): ProviderConfig {
|
|
321
|
+
const result = applyBuiltinProviderModelMetadata(provider, updatedAt);
|
|
322
|
+
if (result.imported === 0) return provider;
|
|
323
|
+
return {
|
|
324
|
+
...provider,
|
|
325
|
+
modelMetadata: result.modelMetadata,
|
|
326
|
+
};
|
|
327
|
+
}
|
package/src/proxy/providers.ts
CHANGED
|
@@ -5,7 +5,12 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { logger } from "./logger";
|
|
7
7
|
import { getDataDir, hasExplicitDataDir } from "./dataDir";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
ProviderConfigSchema,
|
|
10
|
+
ProviderModelMetadataSchema,
|
|
11
|
+
type ProviderConfig,
|
|
12
|
+
} from "../lib/providerContract";
|
|
13
|
+
import { withBuiltinProviderModelMetadata } from "../lib/providerModelMetadata";
|
|
9
14
|
|
|
10
15
|
export { ProviderConfigSchema };
|
|
11
16
|
export type { ProviderConfig };
|
|
@@ -29,6 +34,8 @@ const LegacyProviderConfigSchema = z.object({
|
|
|
29
34
|
openaiBaseUrl: z.string().optional(),
|
|
30
35
|
authHeader: z.enum(["bearer", "x-api-key"]).optional(),
|
|
31
36
|
apiDocsUrl: z.string().optional(),
|
|
37
|
+
modelMetadataUrl: z.string().optional(),
|
|
38
|
+
modelMetadata: z.array(ProviderModelMetadataSchema).optional(),
|
|
32
39
|
source: z.enum(["company", "personal"]).optional(),
|
|
33
40
|
createdAt: z.string().optional(),
|
|
34
41
|
updatedAt: z.string().optional(),
|
|
@@ -80,13 +87,15 @@ export function migrateLegacyProvider(
|
|
|
80
87
|
openaiBaseUrl,
|
|
81
88
|
authHeader: provider.authHeader ?? "bearer",
|
|
82
89
|
apiDocsUrl: provider.apiDocsUrl,
|
|
90
|
+
modelMetadataUrl: provider.modelMetadataUrl,
|
|
91
|
+
modelMetadata: provider.modelMetadata,
|
|
83
92
|
source: provider.source,
|
|
84
93
|
createdAt: provider.createdAt ?? now,
|
|
85
94
|
updatedAt: provider.updatedAt ?? now,
|
|
86
95
|
};
|
|
87
96
|
|
|
88
97
|
const parsed = ProviderConfigSchema.safeParse(candidate);
|
|
89
|
-
return parsed.success ? parsed.data : null;
|
|
98
|
+
return parsed.success ? withBuiltinProviderModelMetadata(parsed.data, now) : null;
|
|
90
99
|
}
|
|
91
100
|
|
|
92
101
|
function migrateFromDataDirConfig(targetStore: Conf<ProvidersStore>, dataDirPath: string): void {
|
|
@@ -148,7 +157,11 @@ function migrateFromLegacyConfLocation(targetStore: Conf<ProvidersStore>): void
|
|
|
148
157
|
});
|
|
149
158
|
const legacyProviders = legacyStore.get("providers", []);
|
|
150
159
|
if (legacyProviders.length === 0) return;
|
|
151
|
-
|
|
160
|
+
const now = new Date().toISOString();
|
|
161
|
+
targetStore.set(
|
|
162
|
+
"providers",
|
|
163
|
+
legacyProviders.map((provider) => withBuiltinProviderModelMetadata(provider, now)),
|
|
164
|
+
);
|
|
152
165
|
logger.info(
|
|
153
166
|
`[providers] Migrated ${legacyProviders.length} provider(s) from ${legacyStore.path} to ${targetStore.path}`,
|
|
154
167
|
);
|
|
@@ -301,6 +314,23 @@ function migrateMultiModel(): void {
|
|
|
301
314
|
|
|
302
315
|
migrateMultiModel();
|
|
303
316
|
|
|
317
|
+
function migrateBuiltinModelMetadata(): void {
|
|
318
|
+
const providers = store.get("providers", []);
|
|
319
|
+
if (providers.length === 0) return;
|
|
320
|
+
|
|
321
|
+
const now = new Date().toISOString();
|
|
322
|
+
const updated = providers.map((provider) => withBuiltinProviderModelMetadata(provider, now));
|
|
323
|
+
const changed = updated.some(
|
|
324
|
+
(provider, index) => JSON.stringify(provider) !== JSON.stringify(providers[index]),
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
if (changed) {
|
|
328
|
+
store.set("providers", updated);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
migrateBuiltinModelMetadata();
|
|
333
|
+
|
|
304
334
|
// Override with JSON env var if provided (for testing)
|
|
305
335
|
const providersJson = process.env["AGENT_INSPECTOR_PROVIDERS_JSON"];
|
|
306
336
|
if (providersJson !== undefined) {
|
|
@@ -308,7 +338,10 @@ if (providersJson !== undefined) {
|
|
|
308
338
|
const parsed = ProviderConfigSchema.array().safeParse(JSON.parse(providersJson));
|
|
309
339
|
if (parsed.success) {
|
|
310
340
|
// Apply migration to JSON providers (e.g., convert baseUrl to openaiBaseUrl/anthropicBaseUrl)
|
|
311
|
-
const
|
|
341
|
+
const now = new Date().toISOString();
|
|
342
|
+
const migrated = parsed.data
|
|
343
|
+
.map(migrateProvider)
|
|
344
|
+
.map((provider) => withBuiltinProviderModelMetadata(provider, now));
|
|
312
345
|
store.set("providers", migrated);
|
|
313
346
|
}
|
|
314
347
|
} catch {
|
|
@@ -343,6 +376,7 @@ export function addProvider(
|
|
|
343
376
|
anthropicBaseUrl?: string,
|
|
344
377
|
openaiBaseUrl?: string,
|
|
345
378
|
source?: "company" | "personal",
|
|
379
|
+
modelMetadataUrl?: string,
|
|
346
380
|
): ProviderConfig {
|
|
347
381
|
const providers = getProviders();
|
|
348
382
|
const normalizedKey = normalizeApiKey(apiKey);
|
|
@@ -361,38 +395,48 @@ export function addProvider(
|
|
|
361
395
|
(p.openaiBaseUrl ?? "") === (resolvedOpenaiUrl ?? ""),
|
|
362
396
|
);
|
|
363
397
|
if (existing) {
|
|
398
|
+
const existingIndex = providers.findIndex((p) => p.id === existing.id);
|
|
364
399
|
const newModels = (models ?? []).filter((m) => m !== "");
|
|
400
|
+
existing.modelMetadataUrl = modelMetadataUrl ?? existing.modelMetadataUrl;
|
|
365
401
|
if (newModels.length > 0) {
|
|
366
402
|
const mergedModels = new Set(existing.models ?? []);
|
|
367
403
|
for (const m of newModels) mergedModels.add(m);
|
|
368
404
|
existing.models = [...mergedModels];
|
|
369
|
-
existing.updatedAt = now;
|
|
370
|
-
store.set("providers", providers);
|
|
371
405
|
}
|
|
372
|
-
|
|
406
|
+
existing.updatedAt = now;
|
|
407
|
+
const enriched = withBuiltinProviderModelMetadata(existing, now);
|
|
408
|
+
if (existingIndex >= 0) {
|
|
409
|
+
providers[existingIndex] = enriched;
|
|
410
|
+
}
|
|
411
|
+
store.set("providers", providers);
|
|
412
|
+
return enriched;
|
|
373
413
|
}
|
|
374
414
|
|
|
375
|
-
const newProvider: ProviderConfig =
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
format
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
415
|
+
const newProvider: ProviderConfig = withBuiltinProviderModelMetadata(
|
|
416
|
+
{
|
|
417
|
+
id: randomUUID(),
|
|
418
|
+
name,
|
|
419
|
+
apiKey: normalizedKey,
|
|
420
|
+
format:
|
|
421
|
+
format ??
|
|
422
|
+
(anthropicBaseUrl !== undefined
|
|
423
|
+
? "anthropic"
|
|
424
|
+
: openaiBaseUrl !== undefined
|
|
425
|
+
? "openai"
|
|
426
|
+
: undefined),
|
|
427
|
+
baseUrl,
|
|
428
|
+
models: models ?? [],
|
|
429
|
+
authHeader: authHeader ?? "bearer",
|
|
430
|
+
apiDocsUrl,
|
|
431
|
+
modelMetadataUrl,
|
|
432
|
+
createdAt: now,
|
|
433
|
+
updatedAt: now,
|
|
434
|
+
anthropicBaseUrl: resolvedAnthropicUrl,
|
|
435
|
+
openaiBaseUrl: resolvedOpenaiUrl,
|
|
436
|
+
source,
|
|
437
|
+
},
|
|
438
|
+
now,
|
|
439
|
+
);
|
|
396
440
|
providers.push(newProvider);
|
|
397
441
|
store.set("providers", providers);
|
|
398
442
|
return newProvider;
|
|
@@ -406,24 +450,36 @@ export function updateProvider(
|
|
|
406
450
|
const existing = providers.find((p) => p.id === id);
|
|
407
451
|
if (!existing) return null;
|
|
408
452
|
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
453
|
+
const updatedAt = new Date().toISOString();
|
|
454
|
+
const updated: ProviderConfig = withBuiltinProviderModelMetadata(
|
|
455
|
+
{
|
|
456
|
+
id: existing.id,
|
|
457
|
+
name: updates.name ?? existing.name,
|
|
458
|
+
apiKey: updates.apiKey !== undefined ? normalizeApiKey(updates.apiKey) : existing.apiKey,
|
|
459
|
+
models: updates.models !== undefined ? updates.models : existing.models,
|
|
460
|
+
format: updates.format ?? existing.format,
|
|
461
|
+
baseUrl: updates.baseUrl !== undefined ? updates.baseUrl : existing.baseUrl,
|
|
462
|
+
authHeader: updates.authHeader ?? existing.authHeader,
|
|
463
|
+
apiDocsUrl: updates.apiDocsUrl ?? existing.apiDocsUrl,
|
|
464
|
+
modelMetadataUrl:
|
|
465
|
+
updates.modelMetadataUrl !== undefined
|
|
466
|
+
? updates.modelMetadataUrl
|
|
467
|
+
: existing.modelMetadataUrl,
|
|
468
|
+
modelMetadata:
|
|
469
|
+
updates.modelMetadata !== undefined ? updates.modelMetadata : existing.modelMetadata,
|
|
470
|
+
createdAt: existing.createdAt,
|
|
471
|
+
updatedAt,
|
|
472
|
+
// Handle format-specific URLs
|
|
473
|
+
anthropicBaseUrl:
|
|
474
|
+
updates.anthropicBaseUrl !== undefined
|
|
475
|
+
? updates.anthropicBaseUrl
|
|
476
|
+
: existing.anthropicBaseUrl,
|
|
477
|
+
openaiBaseUrl:
|
|
478
|
+
updates.openaiBaseUrl !== undefined ? updates.openaiBaseUrl : existing.openaiBaseUrl,
|
|
479
|
+
source: updates.source !== undefined ? updates.source : existing.source,
|
|
480
|
+
},
|
|
481
|
+
updatedAt,
|
|
482
|
+
);
|
|
427
483
|
const index = providers.findIndex((p) => p.id === id);
|
|
428
484
|
providers[index] = updated;
|
|
429
485
|
store.set("providers", providers);
|
|
@@ -519,12 +575,16 @@ export function importProviders(json: string): { imported: number; errors: strin
|
|
|
519
575
|
}
|
|
520
576
|
|
|
521
577
|
// Generate new ID and add
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
578
|
+
const now = new Date().toISOString();
|
|
579
|
+
const newProvider: ProviderConfig = withBuiltinProviderModelMetadata(
|
|
580
|
+
{
|
|
581
|
+
...provider,
|
|
582
|
+
id: randomUUID(),
|
|
583
|
+
createdAt: now,
|
|
584
|
+
updatedAt: now,
|
|
585
|
+
},
|
|
586
|
+
now,
|
|
587
|
+
);
|
|
528
588
|
|
|
529
589
|
newProviders.push(newProvider);
|
|
530
590
|
existingKeys.add(key);
|