@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.
Files changed (44) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{CompareDrawer-Djb4XFZS.js → CompareDrawer-BWMipEjS.js} +1 -1
  3. package/.output/public/assets/ProxyViewerContainer-DIAgurux.js +114 -0
  4. package/.output/public/assets/{ReplayDialog-DBPMIs2I.js → ReplayDialog-DaSzZsvM.js} +1 -1
  5. package/.output/public/assets/RequestAnatomy-BfQmPHCd.js +1 -0
  6. package/.output/public/assets/ResponseView-CblEWDMx.js +1 -0
  7. package/.output/public/assets/StreamingChunkSequence-B4atnL99.js +1 -0
  8. package/.output/public/assets/_sessionId-DUzYnZXV.js +1 -0
  9. package/.output/public/assets/index-CxhRsekH.css +1 -0
  10. package/.output/public/assets/index-jagRsQaV.js +1 -0
  11. package/.output/public/assets/{main-CwhqfJqM.js → main-DE6Y4-wV.js} +2 -2
  12. package/.output/server/{_sessionId-DzBy9gLa.mjs → _sessionId-61HO9rUI.mjs} +3 -3
  13. package/.output/server/_ssr/{CompareDrawer-C5NBvE2e.mjs → CompareDrawer-CSPqneYm.mjs} +2 -2
  14. package/.output/server/_ssr/{ProxyViewerContainer-DiZjOTQT.mjs → ProxyViewerContainer-DrSMa5O7.mjs} +159 -14
  15. package/.output/server/_ssr/{ReplayDialog-EpvTwFvp.mjs → ReplayDialog-Dn3naENv.mjs} +3 -3
  16. package/.output/server/_ssr/{RequestAnatomy-DaQjhixp.mjs → RequestAnatomy-D1X3f3AX.mjs} +336 -42
  17. package/.output/server/_ssr/{ResponseView-D8tGHCGY.mjs → ResponseView-CwXAWOoE.mjs} +2 -2
  18. package/.output/server/_ssr/{StreamingChunkSequence-DGQnb1QE.mjs → StreamingChunkSequence-DRSR2FfN.mjs} +2 -2
  19. package/.output/server/_ssr/{index-N25hqa7t.mjs → index-D4zn5CAg.mjs} +2 -2
  20. package/.output/server/_ssr/index.mjs +2 -2
  21. package/.output/server/_ssr/{router-Bf0m6F0G.mjs → router-CSRaa_tu.mjs} +531 -111
  22. package/.output/server/{_tanstack-start-manifest_v-DlAyJ5DB.mjs → _tanstack-start-manifest_v-CfvVUYYj.mjs} +1 -1
  23. package/.output/server/index.mjs +60 -60
  24. package/bin/agent-inspector.js +2 -0
  25. package/package.json +3 -2
  26. package/src/components/providers/ProviderCard.tsx +59 -5
  27. package/src/components/providers/ProviderForm.tsx +42 -0
  28. package/src/components/providers/ProvidersPanel.tsx +68 -0
  29. package/src/components/proxy-viewer/LogEntry.tsx +6 -1
  30. package/src/components/proxy-viewer/anatomy/RequestAnatomy.tsx +134 -3
  31. package/src/components/proxy-viewer/anatomy/contextIntelligence.ts +298 -40
  32. package/src/lib/providerContract.ts +12 -0
  33. package/src/lib/providerModelMetadata.ts +327 -0
  34. package/src/proxy/providers.ts +112 -52
  35. package/src/routes/api/providers.$providerId.model-metadata.ts +134 -0
  36. package/src/routes/api/providers.$providerId.ts +3 -0
  37. package/src/routes/api/providers.ts +2 -0
  38. package/.output/public/assets/ProxyViewerContainer-CWdkPZsJ.js +0 -114
  39. package/.output/public/assets/RequestAnatomy-FBfRNQN7.js +0 -1
  40. package/.output/public/assets/ResponseView-Db4pJL7U.js +0 -1
  41. package/.output/public/assets/StreamingChunkSequence-BH175O6R.js +0 -1
  42. package/.output/public/assets/_sessionId-3ipVoQtW.js +0 -1
  43. package/.output/public/assets/index-Bo1dGS4R.css +0 -1
  44. 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
+ }
@@ -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 { ProviderConfigSchema, type ProviderConfig } from "../lib/providerContract";
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
- targetStore.set("providers", legacyProviders);
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 migrated = parsed.data.map(migrateProvider);
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
- return existing;
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
- id: randomUUID(),
377
- name,
378
- apiKey: normalizedKey,
379
- format:
380
- format ??
381
- (anthropicBaseUrl !== undefined
382
- ? "anthropic"
383
- : openaiBaseUrl !== undefined
384
- ? "openai"
385
- : undefined),
386
- baseUrl,
387
- models: models ?? [],
388
- authHeader: authHeader ?? "bearer",
389
- apiDocsUrl,
390
- createdAt: now,
391
- updatedAt: now,
392
- anthropicBaseUrl: resolvedAnthropicUrl,
393
- openaiBaseUrl: resolvedOpenaiUrl,
394
- source,
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 updated: ProviderConfig = {
410
- id: existing.id,
411
- name: updates.name ?? existing.name,
412
- apiKey: updates.apiKey !== undefined ? normalizeApiKey(updates.apiKey) : existing.apiKey,
413
- models: updates.models !== undefined ? updates.models : existing.models,
414
- format: updates.format ?? existing.format,
415
- baseUrl: updates.baseUrl !== undefined ? updates.baseUrl : existing.baseUrl,
416
- authHeader: updates.authHeader ?? existing.authHeader,
417
- apiDocsUrl: updates.apiDocsUrl ?? existing.apiDocsUrl,
418
- createdAt: existing.createdAt,
419
- updatedAt: new Date().toISOString(),
420
- // Handle format-specific URLs
421
- anthropicBaseUrl:
422
- updates.anthropicBaseUrl !== undefined ? updates.anthropicBaseUrl : existing.anthropicBaseUrl,
423
- openaiBaseUrl:
424
- updates.openaiBaseUrl !== undefined ? updates.openaiBaseUrl : existing.openaiBaseUrl,
425
- source: updates.source !== undefined ? updates.source : existing.source,
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 newProvider: ProviderConfig = {
523
- ...provider,
524
- id: randomUUID(),
525
- createdAt: new Date().toISOString(),
526
- updatedAt: new Date().toISOString(),
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);