@tonyclaw/llm-inspector 1.14.0 → 1.14.2

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.
@@ -57,15 +57,23 @@ const ProviderFormatTestResultsSchema = z.object({
57
57
  streaming: ProviderTestStateSchema,
58
58
  });
59
59
 
60
+ const ModelTestResultsSchema = z.object({
61
+ anthropic: ProviderFormatTestResultsSchema,
62
+ openai: ProviderFormatTestResultsSchema,
63
+ });
64
+
60
65
  export const ProviderTestResultsSchema = z.object({
61
66
  anthropic: ProviderFormatTestResultsSchema,
62
67
  openai: ProviderFormatTestResultsSchema,
68
+ models: z.record(z.string(), ModelTestResultsSchema).optional(),
69
+ testedAt: z.string().optional(),
63
70
  });
64
71
 
65
72
  export type ProviderTestErrorType = z.infer<typeof ProviderTestErrorTypeSchema>;
66
73
  export type ProviderTestResult = z.infer<typeof ProviderTestResultSchema>;
67
74
  export type ProviderTestState = z.infer<typeof ProviderTestStateSchema>;
68
75
  export type ProviderTestResults = z.infer<typeof ProviderTestResultsSchema>;
76
+ export type ModelTestResults = z.infer<typeof ModelTestResultsSchema>;
69
77
 
70
78
  export function createPendingProviderTestResults(): ProviderTestResults {
71
79
  return {
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import Conf from "conf";
3
3
  import { randomUUID } from "crypto";
4
- import { mkdirSync } from "node:fs";
4
+ import { 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";
@@ -119,6 +119,79 @@ function migrateProviders(): void {
119
119
  // Run migration on module load
120
120
  migrateProviders();
121
121
 
122
+ /**
123
+ * Migrate: merge providers with identical (apiKey, anthropicBaseUrl, openaiBaseUrl)
124
+ * into multi-model providers. Existing single-model providers get their `model`
125
+ * value promoted to `models: [model]`.
126
+ */
127
+ function migrateMultiModel(): void {
128
+ const providers = store.get("providers", []);
129
+ if (providers.length === 0) return;
130
+ let changed = false;
131
+
132
+ // Step 1: Promote single model to models array for any provider missing models
133
+ const promoted = providers.map((p) => {
134
+ if (p.models !== undefined && p.models.length > 0) return p;
135
+ const models = p.model !== undefined && p.model !== "" ? [p.model] : [];
136
+ changed = true;
137
+ return { ...p, models };
138
+ });
139
+
140
+ // Step 2: Merge providers with same (apiKey, anthropicBaseUrl, openaiBaseUrl)
141
+ const groups = new Map<string, ProviderConfig[]>();
142
+ for (const p of promoted) {
143
+ const key = `${p.apiKey}::${p.anthropicBaseUrl ?? ""}::${p.openaiBaseUrl ?? ""}`;
144
+ const group = groups.get(key);
145
+ if (group !== undefined) {
146
+ group.push(p);
147
+ } else {
148
+ groups.set(key, [p]);
149
+ }
150
+ }
151
+
152
+ const merged: ProviderConfig[] = [];
153
+ for (const group of groups.values()) {
154
+ const first = group[0];
155
+ if (group.length === 1 && first !== undefined) {
156
+ merged.push(first);
157
+ continue;
158
+ }
159
+ changed = true;
160
+ // Merge: keep the earliest provider's metadata, combine all models (deduplicated)
161
+ const sorted = group.toSorted(
162
+ (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),
163
+ );
164
+ const earliest = sorted[0];
165
+ if (earliest === undefined) continue;
166
+ const allModels = new Set<string>();
167
+ for (const p of sorted) {
168
+ for (const m of p.models ?? []) {
169
+ if (m !== "") allModels.add(m);
170
+ }
171
+ }
172
+ // Take first non-empty source
173
+ const mergedSource = sorted.find((p) => p.source !== undefined)?.source;
174
+ merged.push({
175
+ ...earliest,
176
+ models: [...allModels],
177
+ source: mergedSource,
178
+ updatedAt: new Date().toISOString(),
179
+ });
180
+ }
181
+
182
+ if (changed && merged.length > 0) {
183
+ // Safety backup before writing
184
+ try {
185
+ writeFileSync(`${store.path}.bak`, readFileSync(store.path, "utf-8"));
186
+ } catch {
187
+ // best-effort, ignore
188
+ }
189
+ store.set("providers", merged);
190
+ }
191
+ }
192
+
193
+ migrateMultiModel();
194
+
122
195
  // Override with JSON env var if provided (for testing)
123
196
  const providersJson = process.env["LLM_INSPECTOR_PROVIDERS_JSON"];
124
197
  if (providersJson !== undefined) {
@@ -153,27 +226,63 @@ export function normalizeApiKey(apiKey: string): string {
153
226
  export function addProvider(
154
227
  name: string,
155
228
  apiKey: string,
156
- format: "anthropic" | "openai",
229
+ format?: "anthropic" | "openai",
157
230
  baseUrl?: string,
158
- model?: string,
231
+ models?: string[],
159
232
  authHeader?: "bearer" | "x-api-key",
160
233
  apiDocsUrl?: string,
234
+ anthropicBaseUrl?: string,
235
+ openaiBaseUrl?: string,
236
+ source?: "company" | "personal",
161
237
  ): ProviderConfig {
162
238
  const providers = getProviders();
239
+ const normalizedKey = normalizeApiKey(apiKey);
240
+ const resolvedAnthropicUrl =
241
+ anthropicBaseUrl ?? (format === "anthropic" && baseUrl !== undefined ? baseUrl : "");
242
+ const resolvedOpenaiUrl =
243
+ openaiBaseUrl ?? (format === "openai" && baseUrl !== undefined ? baseUrl : "");
163
244
  const now = new Date().toISOString();
245
+
246
+ // If a provider with the same (apiKey, anthropicBaseUrl, openaiBaseUrl) already exists,
247
+ // merge the new models into it instead of creating a duplicate.
248
+ const existing = providers.find(
249
+ (p) =>
250
+ p.apiKey === normalizedKey &&
251
+ (p.anthropicBaseUrl ?? "") === (resolvedAnthropicUrl ?? "") &&
252
+ (p.openaiBaseUrl ?? "") === (resolvedOpenaiUrl ?? ""),
253
+ );
254
+ if (existing) {
255
+ const newModels = (models ?? []).filter((m) => m !== "");
256
+ if (newModels.length > 0) {
257
+ const mergedModels = new Set(existing.models ?? []);
258
+ for (const m of newModels) mergedModels.add(m);
259
+ existing.models = [...mergedModels];
260
+ existing.updatedAt = now;
261
+ store.set("providers", providers);
262
+ }
263
+ return existing;
264
+ }
265
+
164
266
  const newProvider: ProviderConfig = {
165
267
  id: randomUUID(),
166
268
  name,
167
- apiKey: normalizeApiKey(apiKey),
168
- format,
269
+ apiKey: normalizedKey,
270
+ format:
271
+ format ??
272
+ (anthropicBaseUrl !== undefined
273
+ ? "anthropic"
274
+ : openaiBaseUrl !== undefined
275
+ ? "openai"
276
+ : undefined),
169
277
  baseUrl,
170
- model,
278
+ models: models ?? [],
171
279
  authHeader: authHeader ?? "bearer",
172
280
  apiDocsUrl,
173
281
  createdAt: now,
174
282
  updatedAt: now,
175
- anthropicBaseUrl: format === "anthropic" && baseUrl !== undefined ? baseUrl : "",
176
- openaiBaseUrl: format === "openai" && baseUrl !== undefined ? baseUrl : "",
283
+ anthropicBaseUrl: resolvedAnthropicUrl,
284
+ openaiBaseUrl: resolvedOpenaiUrl,
285
+ source,
177
286
  };
178
287
  providers.push(newProvider);
179
288
  store.set("providers", providers);
@@ -192,7 +301,7 @@ export function updateProvider(
192
301
  id: existing.id,
193
302
  name: updates.name ?? existing.name,
194
303
  apiKey: updates.apiKey !== undefined ? normalizeApiKey(updates.apiKey) : existing.apiKey,
195
- model: updates.model !== undefined ? updates.model : existing.model,
304
+ models: updates.models !== undefined ? updates.models : existing.models,
196
305
  format: updates.format ?? existing.format,
197
306
  baseUrl: updates.baseUrl !== undefined ? updates.baseUrl : existing.baseUrl,
198
307
  authHeader: updates.authHeader ?? existing.authHeader,
@@ -204,6 +313,7 @@ export function updateProvider(
204
313
  updates.anthropicBaseUrl !== undefined ? updates.anthropicBaseUrl : existing.anthropicBaseUrl,
205
314
  openaiBaseUrl:
206
315
  updates.openaiBaseUrl !== undefined ? updates.openaiBaseUrl : existing.openaiBaseUrl,
316
+ source: updates.source !== undefined ? updates.source : existing.source,
207
317
  };
208
318
  const index = providers.findIndex((p) => p.id === id);
209
319
  providers[index] = updated;
@@ -359,21 +469,21 @@ export function findProviderByModel(model: string): ProviderConfig | null {
359
469
  if (modelLower.startsWith(providerPrefix)) {
360
470
  return provider;
361
471
  }
362
- // Strategy 2: match against provider.model field with normalization
363
- if (
364
- provider.model !== undefined &&
365
- provider.model !== "" &&
366
- modelNormalized === normalizeModelName(provider.model)
367
- ) {
368
- return provider;
472
+ // Strategy 2: match against provider.models array with normalization
473
+ const modelList = provider.models ?? (provider.model !== undefined ? [provider.model] : []);
474
+ for (const m of modelList) {
475
+ if (m !== "" && modelNormalized === normalizeModelName(m)) {
476
+ return provider;
477
+ }
369
478
  }
370
479
  // Strategy 3: fallback - concatenate provider name with model part and compare
371
- // This handles cases like "MiniMax" + "M3" → "MiniMax-M3" when case differs
372
- if (provider.model !== undefined && provider.model !== "") {
373
- const modelPart = modelNormalized.replace(normalizeModelName(provider.name), "");
374
- const concatenated = normalizeModelName(provider.name) + modelPart;
375
- if (modelNormalized === concatenated) {
376
- return provider;
480
+ for (const m of modelList) {
481
+ if (m !== "") {
482
+ const modelPart = modelNormalized.replace(normalizeModelName(provider.name), "");
483
+ const concatenated = normalizeModelName(provider.name) + modelPart;
484
+ if (modelNormalized === concatenated) {
485
+ return provider;
486
+ }
377
487
  }
378
488
  }
379
489
  }
@@ -0,0 +1,293 @@
1
+ import { createFileRoute } from "@tanstack/react-router";
2
+ import { getProvider, getModelUsageName } from "../../proxy/providers";
3
+ import { appendLogEntry } from "../../proxy/logger";
4
+ import { addTestLogEntry } from "../../proxy/store";
5
+ import {
6
+ ProviderTestResultsSchema,
7
+ type ProviderTestResult as TestResult,
8
+ type ProviderTestState,
9
+ } from "../../lib/providerTestContract";
10
+
11
+ function hasSuccessField(result: ProviderTestState): result is TestResult {
12
+ return Object.prototype.hasOwnProperty.call(result, "success");
13
+ }
14
+
15
+ function createTestLogEntry(
16
+ providerName: string,
17
+ path: string,
18
+ body: string,
19
+ upstreamUrl: string,
20
+ result: TestResult,
21
+ isTest: boolean,
22
+ ): Record<string, unknown> {
23
+ return {
24
+ timestamp: new Date().toISOString(),
25
+ id: `test-${Date.now()}`,
26
+ method: "POST",
27
+ path,
28
+ model: isTest ? result.model : undefined,
29
+ sessionId: null,
30
+ rawRequestBody: body,
31
+ responseStatus: result.success ? 200 : 500,
32
+ responseText: result.rawResponse ?? JSON.stringify(result),
33
+ inputTokens: result.inputTokens ?? null,
34
+ outputTokens: result.outputTokens ?? null,
35
+ elapsedMs: result.latencyMs ?? 0,
36
+ streaming: result.streaming ?? false,
37
+ userAgent: "provider-test",
38
+ origin: null,
39
+ upstreamUrl,
40
+ error: result.success ? null : (result.error?.message ?? String(result.error)),
41
+ isTest: true,
42
+ providerName,
43
+ headers: result.requestHeaders ?? {},
44
+ };
45
+ }
46
+
47
+ async function logModelResults(
48
+ displayName: string,
49
+ providerName: string,
50
+ anthropicUrl: string | undefined,
51
+ openaiUrl: string | undefined,
52
+ modelResults: {
53
+ anthropic: { nonStreaming: ProviderTestState; streaming: ProviderTestState };
54
+ openai: { nonStreaming: ProviderTestState; streaming: ProviderTestState };
55
+ },
56
+ ): Promise<void> {
57
+ const usageModel = getModelUsageName(displayName, providerName);
58
+
59
+ if (anthropicUrl !== undefined) {
60
+ const nsResult = modelResults.anthropic.nonStreaming;
61
+ const sResult = modelResults.anthropic.streaming;
62
+ if (hasSuccessField(nsResult) && hasSuccessField(sResult)) {
63
+ const nonStreamingResult = nsResult;
64
+ const streamingResult = sResult;
65
+
66
+ const requestBody = JSON.stringify({
67
+ model: usageModel,
68
+ messages: [{ role: "user", content: "say hello and briefly introduce yourself" }],
69
+ max_tokens: 1024,
70
+ });
71
+ const upstreamUrl = `${anthropicUrl}/v1/messages`;
72
+
73
+ await addTestLogEntry({
74
+ timestamp: new Date().toISOString(),
75
+ method: "POST",
76
+ path: "/v1/messages",
77
+ model: nonStreamingResult.model ?? displayName,
78
+ sessionId: null,
79
+ rawRequestBody: requestBody,
80
+ responseStatus: nonStreamingResult.success ? 200 : 500,
81
+ responseText: nonStreamingResult.rawResponse ?? JSON.stringify(nonStreamingResult),
82
+ inputTokens: nonStreamingResult.inputTokens ?? null,
83
+ outputTokens: nonStreamingResult.outputTokens ?? null,
84
+ cacheCreationInputTokens: nonStreamingResult.cacheCreationInputTokens ?? null,
85
+ cacheReadInputTokens: nonStreamingResult.cacheReadInputTokens ?? null,
86
+ elapsedMs: nonStreamingResult.latencyMs ?? 0,
87
+ streaming: false,
88
+ userAgent: "provider-test",
89
+ origin: null,
90
+ apiFormat: "anthropic",
91
+ isTest: true,
92
+ providerName,
93
+ headers: nonStreamingResult.requestHeaders ?? {},
94
+ });
95
+
96
+ appendLogEntry(
97
+ createTestLogEntry(
98
+ providerName,
99
+ "/v1/messages",
100
+ requestBody,
101
+ upstreamUrl,
102
+ nonStreamingResult,
103
+ true,
104
+ ),
105
+ );
106
+
107
+ const streamingRequestBody = JSON.stringify({
108
+ model: usageModel,
109
+ messages: [{ role: "user", content: "say hello" }],
110
+ max_tokens: 256,
111
+ stream: true,
112
+ });
113
+
114
+ await addTestLogEntry({
115
+ timestamp: new Date().toISOString(),
116
+ method: "POST",
117
+ path: "/v1/messages",
118
+ model: streamingResult.model ?? displayName,
119
+ sessionId: null,
120
+ rawRequestBody: streamingRequestBody,
121
+ responseStatus: streamingResult.success ? 200 : 500,
122
+ responseText: streamingResult.rawResponse ?? JSON.stringify(streamingResult),
123
+ inputTokens: streamingResult.inputTokens ?? null,
124
+ outputTokens: streamingResult.outputTokens ?? null,
125
+ cacheCreationInputTokens: streamingResult.cacheCreationInputTokens ?? null,
126
+ cacheReadInputTokens: streamingResult.cacheReadInputTokens ?? null,
127
+ elapsedMs: streamingResult.latencyMs ?? 0,
128
+ streaming: true,
129
+ streamingChunks: streamingResult.streamingChunks,
130
+ userAgent: "provider-test",
131
+ origin: null,
132
+ apiFormat: "anthropic",
133
+ isTest: true,
134
+ providerName,
135
+ headers: streamingResult.requestHeaders ?? {},
136
+ });
137
+
138
+ appendLogEntry(
139
+ createTestLogEntry(
140
+ providerName,
141
+ "/v1/messages",
142
+ streamingRequestBody,
143
+ upstreamUrl,
144
+ streamingResult,
145
+ true,
146
+ ),
147
+ );
148
+ }
149
+ }
150
+
151
+ if (openaiUrl !== undefined) {
152
+ const nsResult = modelResults.openai.nonStreaming;
153
+ const sResult = modelResults.openai.streaming;
154
+ if (hasSuccessField(nsResult) && hasSuccessField(sResult)) {
155
+ const nonStreamingResult = nsResult;
156
+ const streamingResult = sResult;
157
+
158
+ const requestBody = JSON.stringify({
159
+ model: usageModel,
160
+ messages: [{ role: "user", content: "say hello and briefly introduce yourself" }],
161
+ max_tokens: 1024,
162
+ });
163
+ const upstreamUrl = `${openaiUrl}/v1/chat/completions`;
164
+
165
+ await addTestLogEntry({
166
+ timestamp: new Date().toISOString(),
167
+ method: "POST",
168
+ path: "/v1/chat/completions",
169
+ model: nonStreamingResult.model ?? displayName,
170
+ sessionId: null,
171
+ rawRequestBody: requestBody,
172
+ responseStatus: nonStreamingResult.success ? 200 : 500,
173
+ responseText: nonStreamingResult.rawResponse ?? JSON.stringify(nonStreamingResult),
174
+ inputTokens: nonStreamingResult.inputTokens ?? null,
175
+ outputTokens: nonStreamingResult.outputTokens ?? null,
176
+ cacheCreationInputTokens: null,
177
+ cacheReadInputTokens: null,
178
+ elapsedMs: nonStreamingResult.latencyMs ?? 0,
179
+ streaming: false,
180
+ userAgent: "provider-test",
181
+ origin: null,
182
+ apiFormat: "openai",
183
+ isTest: true,
184
+ providerName,
185
+ headers: nonStreamingResult.requestHeaders ?? {},
186
+ });
187
+
188
+ appendLogEntry(
189
+ createTestLogEntry(
190
+ providerName,
191
+ "/v1/chat/completions",
192
+ requestBody,
193
+ upstreamUrl,
194
+ nonStreamingResult,
195
+ true,
196
+ ),
197
+ );
198
+
199
+ const streamingRequestBody = JSON.stringify({
200
+ model: usageModel,
201
+ messages: [{ role: "user", content: "say hello" }],
202
+ max_tokens: 256,
203
+ stream: true,
204
+ });
205
+
206
+ await addTestLogEntry({
207
+ timestamp: new Date().toISOString(),
208
+ method: "POST",
209
+ path: "/v1/chat/completions",
210
+ model: streamingResult.model ?? displayName,
211
+ sessionId: null,
212
+ rawRequestBody: streamingRequestBody,
213
+ responseStatus: streamingResult.success ? 200 : 500,
214
+ responseText: streamingResult.rawResponse ?? JSON.stringify(streamingResult),
215
+ inputTokens: streamingResult.inputTokens ?? null,
216
+ outputTokens: streamingResult.outputTokens ?? null,
217
+ cacheCreationInputTokens: null,
218
+ cacheReadInputTokens: null,
219
+ elapsedMs: streamingResult.latencyMs ?? 0,
220
+ streaming: true,
221
+ streamingChunks: streamingResult.streamingChunks,
222
+ userAgent: "provider-test",
223
+ origin: null,
224
+ apiFormat: "openai",
225
+ isTest: true,
226
+ providerName,
227
+ headers: streamingResult.requestHeaders ?? {},
228
+ });
229
+
230
+ appendLogEntry(
231
+ createTestLogEntry(
232
+ providerName,
233
+ "/v1/chat/completions",
234
+ streamingRequestBody,
235
+ upstreamUrl,
236
+ streamingResult,
237
+ true,
238
+ ),
239
+ );
240
+ }
241
+ }
242
+ }
243
+
244
+ export const Route = createFileRoute("/api/providers/$providerId/test/log")({
245
+ server: {
246
+ handlers: {
247
+ POST: async ({ params, request }: { params: { providerId: string }; request: Request }) => {
248
+ const provider = getProvider(params.providerId);
249
+ if (!provider) {
250
+ return Response.json({ error: "Provider not found" }, { status: 404 });
251
+ }
252
+
253
+ let body: unknown;
254
+ try {
255
+ body = await request.json();
256
+ } catch {
257
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
258
+ }
259
+
260
+ const parsed = ProviderTestResultsSchema.safeParse(body);
261
+ if (!parsed.success) {
262
+ return Response.json(
263
+ { error: `Invalid test results: ${parsed.error.message}` },
264
+ { status: 400 },
265
+ );
266
+ }
267
+
268
+ const results = parsed.data;
269
+ const anthropicUrl =
270
+ provider.anthropicBaseUrl !== undefined && provider.anthropicBaseUrl.length > 0
271
+ ? provider.anthropicBaseUrl
272
+ : undefined;
273
+ const openaiUrl =
274
+ provider.openaiBaseUrl !== undefined && provider.openaiBaseUrl.length > 0
275
+ ? provider.openaiBaseUrl
276
+ : undefined;
277
+
278
+ const entries: Promise<void>[] = [];
279
+ if (results.models !== undefined) {
280
+ for (const [modelName, modelResult] of Object.entries(results.models)) {
281
+ entries.push(
282
+ logModelResults(modelName, provider.name, anthropicUrl, openaiUrl, modelResult),
283
+ );
284
+ }
285
+ }
286
+
287
+ await Promise.all(entries);
288
+
289
+ return Response.json({ logged: entries.length });
290
+ },
291
+ },
292
+ },
293
+ });
@@ -7,11 +7,13 @@ const ProviderUpdateSchema = z.object({
7
7
  apiKey: z.string().min(1, "API key is required").optional(),
8
8
  format: z.enum(["anthropic", "openai"]).optional(),
9
9
  baseUrl: z.string().min(1, "Base URL is required").optional(),
10
- model: z.string().min(1, "Model is required").optional(),
10
+ model: z.string().optional(),
11
+ models: z.array(z.string()).optional(),
11
12
  authHeader: z.enum(["bearer", "x-api-key"]).optional(),
12
13
  anthropicBaseUrl: z.string().optional(),
13
14
  openaiBaseUrl: z.string().optional(),
14
15
  apiDocsUrl: z.string().optional(),
16
+ source: z.enum(["company", "personal"]).optional(),
15
17
  });
16
18
 
17
19
  export const Route = createFileRoute("/api/providers/$providerId")({
@@ -5,12 +5,14 @@ import { getProviders, addProvider } from "../../proxy/providers";
5
5
  const ProviderInputSchema = z.object({
6
6
  name: z.string().min(1, "Name is required"),
7
7
  apiKey: z.string().min(1, "API key is required"),
8
- format: z.enum(["anthropic", "openai"]),
8
+ format: z.enum(["anthropic", "openai"]).optional(),
9
9
  anthropicBaseUrl: z.string().optional(),
10
10
  openaiBaseUrl: z.string().optional(),
11
- model: z.string().min(1, "Model is required"),
11
+ models: z.array(z.string()).min(1, "At least one model is required"),
12
+ model: z.string().optional(),
12
13
  authHeader: z.enum(["bearer", "x-api-key"]).optional().default("bearer"),
13
14
  apiDocsUrl: z.string().optional(),
15
+ source: z.enum(["company", "personal"]).optional(),
14
16
  });
15
17
 
16
18
  export const Route = createFileRoute("/api/providers")({
@@ -28,12 +30,13 @@ export const Route = createFileRoute("/api/providers")({
28
30
  parsed.data.name,
29
31
  parsed.data.apiKey,
30
32
  parsed.data.format,
31
- parsed.data.format === "anthropic"
32
- ? parsed.data.anthropicBaseUrl
33
- : parsed.data.openaiBaseUrl,
34
- parsed.data.model,
33
+ undefined, // baseUrl (legacy) — use format-specific URLs instead
34
+ parsed.data.models,
35
35
  parsed.data.authHeader,
36
36
  parsed.data.apiDocsUrl,
37
+ parsed.data.anthropicBaseUrl,
38
+ parsed.data.openaiBaseUrl,
39
+ parsed.data.source,
37
40
  );
38
41
  return Response.json(newProvider, { status: 201 });
39
42
  },