@tonyclaw/agent-inspector 2.1.12 → 2.1.14
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 +0 -8
- package/.output/nitro.json +1 -1
- package/.output/public/assets/CompareDrawer-DjgjIFx7.js +1 -0
- package/.output/public/assets/ProxyViewerContainer-CWUQZLYy.js +106 -0
- package/.output/public/assets/{ReplayDialog-Cd1VsfRe.js → ReplayDialog-CU0Tbb2c.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-QxSpbmsj.js → RequestAnatomy-BNahe83D.js} +1 -1
- package/.output/public/assets/ResponseView-DSOnGqi6.js +3 -0
- package/.output/public/assets/{StreamingChunkSequence-CwNGqJ5n.js → StreamingChunkSequence-BEKTDklB.js} +1 -1
- package/.output/public/assets/_sessionId-Cif8JZdn.js +1 -0
- package/.output/public/assets/index-DWOkqdCa.js +14 -0
- package/.output/public/assets/index-D_WfwzUi.js +1 -0
- package/.output/public/assets/index-DtLuQrs0.css +1 -0
- package/.output/public/assets/json-viewer-C2JpgcW0.js +1 -0
- package/.output/public/assets/{main-B_EcGyf-.js → main-CSONBwwn.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +20 -20
- package/.output/server/_libs/react-markdown.mjs +90 -2
- package/.output/server/{_sessionId-CmO9read.mjs → _sessionId-CPAa37n5.mjs} +6 -57
- package/.output/server/_ssr/{CompareDrawer-D9wf870o.mjs → CompareDrawer-ceW5VxMo.mjs} +17 -62
- package/.output/server/_ssr/{ProxyViewerContainer-BDLJSawP.mjs → ProxyViewerContainer-CDfEE_w-.mjs} +872 -688
- package/.output/server/_ssr/{ReplayDialog-B2dS4kmP.mjs → ReplayDialog-V0s_eEbR.mjs} +8 -59
- package/.output/server/_ssr/{RequestAnatomy-k49Peshi.mjs → RequestAnatomy-f1ccwR9d.mjs} +6 -57
- package/.output/server/_ssr/{ResponseView-Bk3B1suu.mjs → ResponseView-BIRrqG4H.mjs} +138 -60
- package/.output/server/_ssr/{StreamingChunkSequence-D6MDGCgh.mjs → StreamingChunkSequence-V3JFjCgX.mjs} +13 -58
- package/.output/server/_ssr/{index-CC8KdlTb.mjs → index-DsykulzS.mjs} +6 -57
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/json-viewer-Dcnm0Ivf.mjs +478 -0
- package/.output/server/_ssr/{router-BEmMnVa7.mjs → router-4bdm6Mt2.mjs} +674 -173
- package/.output/server/{_tanstack-start-manifest_v-Bf_4hG4w.mjs → _tanstack-start-manifest_v-Xp4CO64V.mjs} +1 -1
- package/.output/server/index.mjs +70 -56
- package/package.json +1 -1
- package/src/components/ProxyViewerContainer.tsx +25 -17
- package/src/components/providers/ImportWizardDialog.tsx +7 -1
- package/src/components/providers/ProviderCard.tsx +10 -1
- package/src/components/providers/ProviderForm.tsx +157 -41
- package/src/components/providers/ProvidersPanel.tsx +5 -1
- package/src/components/proxy-viewer/AnswerMarkdown.tsx +8 -3
- package/src/components/proxy-viewer/CompareDrawer.tsx +20 -6
- package/src/components/proxy-viewer/ConversationGroup.tsx +11 -15
- package/src/components/proxy-viewer/LogEntry.tsx +31 -11
- package/src/components/proxy-viewer/LogEntryHeader.tsx +111 -40
- package/src/components/proxy-viewer/RequestToolsPanel.tsx +19 -13
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +9 -3
- package/src/components/proxy-viewer/TurnGroup.tsx +53 -7
- package/src/components/proxy-viewer/TurnGroupList.tsx +153 -0
- package/src/components/proxy-viewer/anatomy/sessionContextSummary.ts +74 -2
- package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +13 -7
- package/src/components/proxy-viewer/formats/index.tsx +10 -3
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +185 -17
- package/src/components/proxy-viewer/log-formats/openai.ts +107 -15
- package/src/components/proxy-viewer/logFocus.ts +15 -2
- package/src/components/proxy-viewer/requestTools.ts +7 -3
- package/src/components/proxy-viewer/viewerState.ts +72 -3
- package/src/components/ui/json-viewer-bulk.ts +41 -6
- package/src/components/ui/json-viewer.tsx +9 -8
- package/src/contracts/index.ts +15 -1
- package/src/contracts/openai.ts +94 -0
- package/src/lib/providerContract.ts +1 -0
- package/src/lib/providerModelMetadata.ts +7 -1
- package/src/lib/stopReason.ts +12 -0
- package/src/lib/upstreamUrl.ts +46 -0
- package/src/mcp/previewExtractor.ts +73 -3
- package/src/mcp/server.ts +2 -0
- package/src/mcp/toolHandlers.ts +2 -0
- package/src/proxy/constants.ts +2 -0
- package/src/proxy/formats/openai/handler.ts +40 -9
- package/src/proxy/formats/openai/index.ts +7 -0
- package/src/proxy/formats/openai/schemas.ts +15 -1
- package/src/proxy/formats/openai/stream.ts +529 -135
- package/src/proxy/formats/registry.ts +9 -1
- package/src/proxy/identityProxy.ts +0 -7
- package/src/proxy/providerImporters.ts +45 -5
- package/src/proxy/providers.ts +31 -11
- package/src/proxy/schemas.ts +6 -0
- package/src/proxy/toolSchemaWarnings.ts +25 -3
- package/src/proxy/upstream.ts +18 -37
- package/src/routes/api/providers.$providerId.ts +1 -0
- package/src/routes/api/providers.ts +2 -0
- package/.output/public/assets/CompareDrawer-XTdSOto9.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-Cc944N8i.js +0 -117
- package/.output/public/assets/ResponseView-8GivXCzp.js +0 -1
- package/.output/public/assets/_sessionId-D89uDTQs.js +0 -1
- package/.output/public/assets/index-BsFdgXac.js +0 -1
- package/.output/public/assets/index-CmtfjQPv.css +0 -1
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { FormatHandler } from "./handler";
|
|
2
2
|
import type { ApiFormat } from "../schemas";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
PATH_V1_CHAT_COMPLETIONS,
|
|
5
|
+
PATH_CHAT_COMPLETIONS,
|
|
6
|
+
PATH_V1_RESPONSES,
|
|
7
|
+
PATH_RESPONSES,
|
|
8
|
+
PATH_V1_MESSAGES,
|
|
9
|
+
} from "../constants";
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
12
|
* API format registry maps request paths and API formats to their FormatHandler.
|
|
@@ -73,3 +79,5 @@ export const requestFormatForPath = apiFormatForPath;
|
|
|
73
79
|
formatRegistry.registerPath(PATH_V1_MESSAGES, "anthropic");
|
|
74
80
|
formatRegistry.registerPath(PATH_V1_CHAT_COMPLETIONS, "openai");
|
|
75
81
|
formatRegistry.registerPath(PATH_CHAT_COMPLETIONS, "openai");
|
|
82
|
+
formatRegistry.registerPath(PATH_V1_RESPONSES, "openai");
|
|
83
|
+
formatRegistry.registerPath(PATH_RESPONSES, "openai");
|
|
@@ -139,13 +139,6 @@ async function handleRequest(
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
process.stderr.write(
|
|
143
|
-
`[identity-proxy] req ${req.method} ${req.url} remotePort=${remotePort} pid=${identity?.pid ?? "?"}\n`,
|
|
144
|
-
);
|
|
145
|
-
process.stderr.write(
|
|
146
|
-
`[identity-proxy] forwarding headers: pid=${upstreamHeaders[IDENTITY_HEADERS.pid] ?? "?"} cwd=${upstreamHeaders[IDENTITY_HEADERS.cwd] ?? "?"}\n`,
|
|
147
|
-
);
|
|
148
|
-
|
|
149
142
|
const proxyReq = http.request(
|
|
150
143
|
{
|
|
151
144
|
hostname: options.upstreamHost,
|
|
@@ -12,6 +12,7 @@ export type ExternalProvider = {
|
|
|
12
12
|
format: "anthropic" | "openai";
|
|
13
13
|
anthropicBaseUrl: string;
|
|
14
14
|
openaiBaseUrl: string;
|
|
15
|
+
openaiResponsesBaseUrl: string;
|
|
15
16
|
models: string[];
|
|
16
17
|
sourceTool: "claude-code" | "opencode" | "mimo-code";
|
|
17
18
|
alreadyExists: boolean;
|
|
@@ -20,6 +21,30 @@ export type ExternalProvider = {
|
|
|
20
21
|
const ZHIPU_OPENAI_BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
|
|
21
22
|
const ZHIPU_CODING_OPENAI_BASE_URL = "https://open.bigmodel.cn/api/coding/paas/v4";
|
|
22
23
|
const ZHIPU_CODING_ANTHROPIC_BASE_URL = "https://open.bigmodel.cn/api/anthropic";
|
|
24
|
+
const MINIMAX_OPENAI_BASE_URL = "https://api.minimaxi.com";
|
|
25
|
+
|
|
26
|
+
function defaultOpenAIResponsesBaseUrl(providerId: string): string {
|
|
27
|
+
const normalized = providerId.toLowerCase();
|
|
28
|
+
return normalized === "minimax" || normalized.startsWith("minimax-")
|
|
29
|
+
? MINIMAX_OPENAI_BASE_URL
|
|
30
|
+
: "";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function openAIResponsesBaseUrlMatchesExisting(
|
|
34
|
+
provider: ProviderConfig,
|
|
35
|
+
externalProvider: ExternalProvider,
|
|
36
|
+
): boolean {
|
|
37
|
+
const existingUrl = provider.openaiResponsesBaseUrl ?? "";
|
|
38
|
+
if (existingUrl === externalProvider.openaiResponsesBaseUrl) return true;
|
|
39
|
+
|
|
40
|
+
const defaultUrl = defaultOpenAIResponsesBaseUrl(externalProvider.name);
|
|
41
|
+
if (defaultUrl === "") return false;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
(existingUrl === "" && externalProvider.openaiResponsesBaseUrl === defaultUrl) ||
|
|
45
|
+
(existingUrl === defaultUrl && externalProvider.openaiResponsesBaseUrl === "")
|
|
46
|
+
);
|
|
47
|
+
}
|
|
23
48
|
|
|
24
49
|
function readJsonSafe(filePath: string): Record<string, unknown> | null {
|
|
25
50
|
try {
|
|
@@ -134,6 +159,7 @@ function detectClaudeCodeProviders(): ExternalProvider[] {
|
|
|
134
159
|
format: "anthropic",
|
|
135
160
|
anthropicBaseUrl: baseUrl,
|
|
136
161
|
openaiBaseUrl: "",
|
|
162
|
+
openaiResponsesBaseUrl: "",
|
|
137
163
|
models,
|
|
138
164
|
sourceTool: "claude-code",
|
|
139
165
|
alreadyExists: false, // filled in later
|
|
@@ -210,6 +236,7 @@ function detectOpenCodeProviders(): ExternalProvider[] {
|
|
|
210
236
|
format,
|
|
211
237
|
anthropicBaseUrl: format === "anthropic" ? baseURL : "",
|
|
212
238
|
openaiBaseUrl: format === "openai" ? baseURL : "",
|
|
239
|
+
openaiResponsesBaseUrl: defaultOpenAIResponsesBaseUrl(providerId),
|
|
213
240
|
models,
|
|
214
241
|
sourceTool: "opencode",
|
|
215
242
|
alreadyExists: false,
|
|
@@ -354,6 +381,7 @@ function detectMiMoCodeProviders(): ExternalProvider[] {
|
|
|
354
381
|
format,
|
|
355
382
|
anthropicBaseUrl: format === "anthropic" ? baseURL : "",
|
|
356
383
|
openaiBaseUrl: format === "openai" ? baseURL : "",
|
|
384
|
+
openaiResponsesBaseUrl: defaultOpenAIResponsesBaseUrl(providerId),
|
|
357
385
|
models,
|
|
358
386
|
sourceTool: "mimo-code",
|
|
359
387
|
alreadyExists: false,
|
|
@@ -364,7 +392,12 @@ function detectMiMoCodeProviders(): ExternalProvider[] {
|
|
|
364
392
|
if (isPlainRecord(auth)) {
|
|
365
393
|
const knownDefaults: Record<
|
|
366
394
|
string,
|
|
367
|
-
{
|
|
395
|
+
{
|
|
396
|
+
anthropicBaseUrl: string;
|
|
397
|
+
openaiBaseUrl: string;
|
|
398
|
+
openaiResponsesBaseUrl?: string;
|
|
399
|
+
format: "anthropic" | "openai";
|
|
400
|
+
}
|
|
368
401
|
> = {
|
|
369
402
|
deepseek: {
|
|
370
403
|
anthropicBaseUrl: "https://api.deepseek.com/anthropic",
|
|
@@ -373,12 +406,14 @@ function detectMiMoCodeProviders(): ExternalProvider[] {
|
|
|
373
406
|
},
|
|
374
407
|
minimax: {
|
|
375
408
|
anthropicBaseUrl: "https://api.minimaxi.com/anthropic",
|
|
376
|
-
openaiBaseUrl:
|
|
409
|
+
openaiBaseUrl: MINIMAX_OPENAI_BASE_URL,
|
|
410
|
+
openaiResponsesBaseUrl: MINIMAX_OPENAI_BASE_URL,
|
|
377
411
|
format: "anthropic",
|
|
378
412
|
},
|
|
379
413
|
"minimax-coding-plan": {
|
|
380
414
|
anthropicBaseUrl: "https://api.minimaxi.com/anthropic",
|
|
381
|
-
openaiBaseUrl:
|
|
415
|
+
openaiBaseUrl: MINIMAX_OPENAI_BASE_URL,
|
|
416
|
+
openaiResponsesBaseUrl: MINIMAX_OPENAI_BASE_URL,
|
|
382
417
|
format: "anthropic",
|
|
383
418
|
},
|
|
384
419
|
openai: {
|
|
@@ -471,6 +506,8 @@ function detectMiMoCodeProviders(): ExternalProvider[] {
|
|
|
471
506
|
format: defaults.format,
|
|
472
507
|
anthropicBaseUrl: defaults.anthropicBaseUrl,
|
|
473
508
|
openaiBaseUrl: defaults.openaiBaseUrl,
|
|
509
|
+
openaiResponsesBaseUrl:
|
|
510
|
+
defaults.openaiResponsesBaseUrl ?? defaultOpenAIResponsesBaseUrl(key),
|
|
474
511
|
models: ["default"],
|
|
475
512
|
sourceTool: "mimo-code",
|
|
476
513
|
alreadyExists: false,
|
|
@@ -511,7 +548,7 @@ export function scanExternalProviders(): { providers: ExternalProvider[]; warnin
|
|
|
511
548
|
|
|
512
549
|
// Filter out providers already using the localhost proxy (already switched)
|
|
513
550
|
const filteredProviders = allProviders.filter((p) => {
|
|
514
|
-
const url = p.anthropicBaseUrl || p.openaiBaseUrl;
|
|
551
|
+
const url = p.anthropicBaseUrl || p.openaiBaseUrl || p.openaiResponsesBaseUrl;
|
|
515
552
|
return !/(?:localhost|127\.0\.0\.1)/.test(url);
|
|
516
553
|
});
|
|
517
554
|
|
|
@@ -530,7 +567,10 @@ export function scanExternalProviders(): { providers: ExternalProvider[]; warnin
|
|
|
530
567
|
if (ext.format === "anthropic") {
|
|
531
568
|
return (p.anthropicBaseUrl ?? "") === ext.anthropicBaseUrl;
|
|
532
569
|
}
|
|
533
|
-
return (
|
|
570
|
+
return (
|
|
571
|
+
(p.openaiBaseUrl ?? "") === ext.openaiBaseUrl &&
|
|
572
|
+
openAIResponsesBaseUrlMatchesExisting(p, ext)
|
|
573
|
+
);
|
|
534
574
|
});
|
|
535
575
|
}
|
|
536
576
|
|
package/src/proxy/providers.ts
CHANGED
|
@@ -55,6 +55,7 @@ const LegacyProviderConfigSchema = z.object({
|
|
|
55
55
|
baseUrl: z.string().optional(),
|
|
56
56
|
anthropicBaseUrl: z.string().optional(),
|
|
57
57
|
openaiBaseUrl: z.string().optional(),
|
|
58
|
+
openaiResponsesBaseUrl: z.string().optional(),
|
|
58
59
|
authHeader: z.enum(["bearer", "x-api-key"]).optional(),
|
|
59
60
|
apiDocsUrl: z.string().optional(),
|
|
60
61
|
modelMetadataUrl: z.string().optional(),
|
|
@@ -97,6 +98,7 @@ export function migrateLegacyProvider(
|
|
|
97
98
|
provider.anthropicBaseUrl ?? (format === "anthropic" && baseUrl !== undefined ? baseUrl : "");
|
|
98
99
|
const openaiBaseUrl =
|
|
99
100
|
provider.openaiBaseUrl ?? (format === "openai" && baseUrl !== undefined ? baseUrl : "");
|
|
101
|
+
const openaiResponsesBaseUrl = provider.openaiResponsesBaseUrl ?? "";
|
|
100
102
|
|
|
101
103
|
const candidate: ProviderConfig = {
|
|
102
104
|
id: provider.id ?? randomUUID(),
|
|
@@ -108,6 +110,7 @@ export function migrateLegacyProvider(
|
|
|
108
110
|
baseUrl,
|
|
109
111
|
anthropicBaseUrl,
|
|
110
112
|
openaiBaseUrl,
|
|
113
|
+
openaiResponsesBaseUrl,
|
|
111
114
|
authHeader: provider.authHeader ?? "bearer",
|
|
112
115
|
apiDocsUrl: provider.apiDocsUrl,
|
|
113
116
|
modelMetadataUrl: provider.modelMetadataUrl,
|
|
@@ -235,7 +238,11 @@ migrateFromLegacyConfLocation(store);
|
|
|
235
238
|
* missing counterpart means that API format was intentionally not configured.
|
|
236
239
|
*/
|
|
237
240
|
export function migrateProvider(p: ProviderConfig): ProviderConfig {
|
|
238
|
-
if (
|
|
241
|
+
if (
|
|
242
|
+
p.anthropicBaseUrl !== undefined ||
|
|
243
|
+
p.openaiBaseUrl !== undefined ||
|
|
244
|
+
p.openaiResponsesBaseUrl !== undefined
|
|
245
|
+
) {
|
|
239
246
|
return p;
|
|
240
247
|
}
|
|
241
248
|
if (p.format === undefined || p.baseUrl === undefined || p.baseUrl === "") {
|
|
@@ -243,13 +250,12 @@ export function migrateProvider(p: ProviderConfig): ProviderConfig {
|
|
|
243
250
|
}
|
|
244
251
|
|
|
245
252
|
return p.format === "openai"
|
|
246
|
-
? { ...p, anthropicBaseUrl: "", openaiBaseUrl: p.baseUrl }
|
|
247
|
-
: { ...p, anthropicBaseUrl: p.baseUrl, openaiBaseUrl: "" };
|
|
253
|
+
? { ...p, anthropicBaseUrl: "", openaiBaseUrl: p.baseUrl, openaiResponsesBaseUrl: "" }
|
|
254
|
+
: { ...p, anthropicBaseUrl: p.baseUrl, openaiBaseUrl: "", openaiResponsesBaseUrl: "" };
|
|
248
255
|
}
|
|
249
256
|
|
|
250
257
|
/**
|
|
251
|
-
* Migrates existing provider configs to preserve
|
|
252
|
-
* Old configs had anthropicBaseUrl/openaiBaseUrl, we now keep both.
|
|
258
|
+
* Migrates existing provider configs to preserve Anthropic and OpenAI routing URLs.
|
|
253
259
|
*/
|
|
254
260
|
function migrateProviders(): void {
|
|
255
261
|
const providers = store.get("providers", []);
|
|
@@ -265,7 +271,7 @@ function migrateProviders(): void {
|
|
|
265
271
|
migrateProviders();
|
|
266
272
|
|
|
267
273
|
/**
|
|
268
|
-
* Migrate: merge providers with identical
|
|
274
|
+
* Migrate: merge providers with identical API key and routing URLs.
|
|
269
275
|
* into multi-model providers. Existing single-model providers get their `model`
|
|
270
276
|
* value promoted to `models: [model]`.
|
|
271
277
|
*/
|
|
@@ -282,10 +288,10 @@ function migrateMultiModel(): void {
|
|
|
282
288
|
return { ...p, models };
|
|
283
289
|
});
|
|
284
290
|
|
|
285
|
-
// Step 2: Merge providers with same
|
|
291
|
+
// Step 2: Merge providers with the same API key and routing URLs.
|
|
286
292
|
const groups = new Map<string, ProviderConfig[]>();
|
|
287
293
|
for (const p of promoted) {
|
|
288
|
-
const key = `${p.apiKey}::${p.anthropicBaseUrl ?? ""}::${p.openaiBaseUrl ?? ""}`;
|
|
294
|
+
const key = `${p.apiKey}::${p.anthropicBaseUrl ?? ""}::${p.openaiBaseUrl ?? ""}::${p.openaiResponsesBaseUrl ?? ""}`;
|
|
289
295
|
const group = groups.get(key);
|
|
290
296
|
if (group !== undefined) {
|
|
291
297
|
group.push(p);
|
|
@@ -412,6 +418,7 @@ export function addProvider(
|
|
|
412
418
|
source?: "company" | "personal",
|
|
413
419
|
modelMetadataUrl?: string,
|
|
414
420
|
modelMetadata?: ProviderModelMetadata[],
|
|
421
|
+
openaiResponsesBaseUrl?: string,
|
|
415
422
|
): ProviderConfig {
|
|
416
423
|
const providers = getProviders();
|
|
417
424
|
const normalizedKey = normalizeApiKey(apiKey);
|
|
@@ -419,15 +426,17 @@ export function addProvider(
|
|
|
419
426
|
anthropicBaseUrl ?? (format === "anthropic" && baseUrl !== undefined ? baseUrl : "");
|
|
420
427
|
const resolvedOpenaiUrl =
|
|
421
428
|
openaiBaseUrl ?? (format === "openai" && baseUrl !== undefined ? baseUrl : "");
|
|
429
|
+
const resolvedOpenaiResponsesUrl = openaiResponsesBaseUrl ?? "";
|
|
422
430
|
const now = new Date().toISOString();
|
|
423
431
|
|
|
424
|
-
// If a provider with the same
|
|
432
|
+
// If a provider with the same API key and routing URLs already exists,
|
|
425
433
|
// merge the new models into it instead of creating a duplicate.
|
|
426
434
|
const existing = providers.find(
|
|
427
435
|
(p) =>
|
|
428
436
|
p.apiKey === normalizedKey &&
|
|
429
437
|
(p.anthropicBaseUrl ?? "") === (resolvedAnthropicUrl ?? "") &&
|
|
430
|
-
(p.openaiBaseUrl ?? "") === (resolvedOpenaiUrl ?? "")
|
|
438
|
+
(p.openaiBaseUrl ?? "") === (resolvedOpenaiUrl ?? "") &&
|
|
439
|
+
(p.openaiResponsesBaseUrl ?? "") === (resolvedOpenaiResponsesUrl ?? ""),
|
|
431
440
|
);
|
|
432
441
|
if (existing) {
|
|
433
442
|
const existingIndex = providers.findIndex((p) => p.id === existing.id);
|
|
@@ -457,7 +466,7 @@ export function addProvider(
|
|
|
457
466
|
format ??
|
|
458
467
|
(anthropicBaseUrl !== undefined
|
|
459
468
|
? "anthropic"
|
|
460
|
-
: openaiBaseUrl !== undefined
|
|
469
|
+
: openaiBaseUrl !== undefined || openaiResponsesBaseUrl !== undefined
|
|
461
470
|
? "openai"
|
|
462
471
|
: undefined),
|
|
463
472
|
baseUrl,
|
|
@@ -470,6 +479,7 @@ export function addProvider(
|
|
|
470
479
|
updatedAt: now,
|
|
471
480
|
anthropicBaseUrl: resolvedAnthropicUrl,
|
|
472
481
|
openaiBaseUrl: resolvedOpenaiUrl,
|
|
482
|
+
openaiResponsesBaseUrl: resolvedOpenaiResponsesUrl,
|
|
473
483
|
source,
|
|
474
484
|
},
|
|
475
485
|
now,
|
|
@@ -513,6 +523,10 @@ export function updateProvider(
|
|
|
513
523
|
: existing.anthropicBaseUrl,
|
|
514
524
|
openaiBaseUrl:
|
|
515
525
|
updates.openaiBaseUrl !== undefined ? updates.openaiBaseUrl : existing.openaiBaseUrl,
|
|
526
|
+
openaiResponsesBaseUrl:
|
|
527
|
+
updates.openaiResponsesBaseUrl !== undefined
|
|
528
|
+
? updates.openaiResponsesBaseUrl
|
|
529
|
+
: existing.openaiResponsesBaseUrl,
|
|
516
530
|
source: updates.source !== undefined ? updates.source : existing.source,
|
|
517
531
|
},
|
|
518
532
|
updatedAt,
|
|
@@ -685,6 +699,12 @@ function providerRoutingUrls(provider: ProviderConfig): string[] {
|
|
|
685
699
|
if (provider.openaiBaseUrl !== undefined && provider.openaiBaseUrl.trim() !== "") {
|
|
686
700
|
urls.push(provider.openaiBaseUrl);
|
|
687
701
|
}
|
|
702
|
+
if (
|
|
703
|
+
provider.openaiResponsesBaseUrl !== undefined &&
|
|
704
|
+
provider.openaiResponsesBaseUrl.trim() !== ""
|
|
705
|
+
) {
|
|
706
|
+
urls.push(provider.openaiResponsesBaseUrl);
|
|
707
|
+
}
|
|
688
708
|
if (provider.baseUrl !== undefined && provider.baseUrl.trim() !== "") {
|
|
689
709
|
urls.push(provider.baseUrl);
|
|
690
710
|
}
|
package/src/proxy/schemas.ts
CHANGED
|
@@ -30,10 +30,16 @@ export type {
|
|
|
30
30
|
|
|
31
31
|
export {
|
|
32
32
|
OpenAIRequestSchema,
|
|
33
|
+
OpenAIResponsesRequestSchema,
|
|
34
|
+
OpenAIResponsesResponseSchema,
|
|
35
|
+
OpenAIResponsesSSEventSchema,
|
|
36
|
+
OpenAIResponsesUsageSchema,
|
|
33
37
|
OpenAIResponseSchema,
|
|
34
38
|
OpenAISSERawChunkSchema,
|
|
35
39
|
type OpenAIResponse,
|
|
40
|
+
type OpenAIResponsesResponse,
|
|
36
41
|
type OpenAIToolCall,
|
|
42
|
+
parseOpenAIResponsesResponse,
|
|
37
43
|
parseOpenAIResponse,
|
|
38
44
|
} from "../contracts/openai";
|
|
39
45
|
|
|
@@ -342,12 +342,18 @@ function extractOpenAIToolSchemas(request: unknown): Map<string, ToolSchema> {
|
|
|
342
342
|
const tools = new Map<string, ToolSchema>();
|
|
343
343
|
for (const tool of readArray(request, "tools")) {
|
|
344
344
|
const functionDefinition = readObject(tool, "function");
|
|
345
|
-
|
|
346
|
-
|
|
345
|
+
const name =
|
|
346
|
+
functionDefinition === null
|
|
347
|
+
? readString(tool, "name")
|
|
348
|
+
: (readString(functionDefinition, "name") ?? readString(tool, "name"));
|
|
347
349
|
if (name === null || name === "") continue;
|
|
350
|
+
const parameters =
|
|
351
|
+
functionDefinition === null
|
|
352
|
+
? readObject(tool, "parameters")
|
|
353
|
+
: (readObject(functionDefinition, "parameters") ?? readObject(tool, "parameters"));
|
|
348
354
|
tools.set(name, {
|
|
349
355
|
name,
|
|
350
|
-
schema:
|
|
356
|
+
schema: parameters,
|
|
351
357
|
});
|
|
352
358
|
}
|
|
353
359
|
return tools;
|
|
@@ -431,6 +437,22 @@ function extractOpenAIToolCalls(response: unknown): ToolCall[] {
|
|
|
431
437
|
}
|
|
432
438
|
choiceIndex++;
|
|
433
439
|
}
|
|
440
|
+
|
|
441
|
+
let outputIndex = 0;
|
|
442
|
+
for (const item of readArray(response, "output")) {
|
|
443
|
+
if (readString(item, "type") !== "function_call") {
|
|
444
|
+
outputIndex++;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
const parsedArguments = parseOpenAIArguments(readProperty(item, "arguments"));
|
|
448
|
+
calls.push({
|
|
449
|
+
name: readString(item, "name"),
|
|
450
|
+
arguments: parsedArguments.value,
|
|
451
|
+
argumentIssue: parsedArguments.issue,
|
|
452
|
+
location: `output[${outputIndex}]`,
|
|
453
|
+
});
|
|
454
|
+
outputIndex++;
|
|
455
|
+
}
|
|
434
456
|
return calls;
|
|
435
457
|
}
|
|
436
458
|
|
package/src/proxy/upstream.ts
CHANGED
|
@@ -7,13 +7,18 @@ import {
|
|
|
7
7
|
HEADER_HOST,
|
|
8
8
|
HEADER_X_API_KEY,
|
|
9
9
|
PATH_CHAT_COMPLETIONS,
|
|
10
|
+
PATH_RESPONSES,
|
|
10
11
|
PATH_V1_CHAT_COMPLETIONS,
|
|
12
|
+
PATH_V1_RESPONSES,
|
|
11
13
|
} from "./constants";
|
|
14
|
+
export { buildUpstreamUrl } from "../lib/upstreamUrl";
|
|
12
15
|
|
|
13
16
|
export type ApiRoute = {
|
|
14
17
|
apiPath: string;
|
|
15
18
|
endpointPath: string;
|
|
16
19
|
isChatCompletions: boolean;
|
|
20
|
+
isResponses: boolean;
|
|
21
|
+
isOpenAI: boolean;
|
|
17
22
|
normalizedPath: string;
|
|
18
23
|
};
|
|
19
24
|
|
|
@@ -27,10 +32,11 @@ export function describeApiRoute(apiPath: string): ApiRoute {
|
|
|
27
32
|
const endpointPath = apiPath.split("?")[0] ?? "";
|
|
28
33
|
const isChatCompletions =
|
|
29
34
|
endpointPath === PATH_CHAT_COMPLETIONS || endpointPath === PATH_V1_CHAT_COMPLETIONS;
|
|
30
|
-
const
|
|
31
|
-
|
|
35
|
+
const isResponses = endpointPath === PATH_RESPONSES || endpointPath === PATH_V1_RESPONSES;
|
|
36
|
+
const isOpenAI = isChatCompletions || isResponses;
|
|
37
|
+
const normalizedPath = isOpenAI && !apiPath.startsWith("/v1/") ? `/v1${apiPath}` : apiPath;
|
|
32
38
|
|
|
33
|
-
return { apiPath, endpointPath, isChatCompletions, normalizedPath };
|
|
39
|
+
return { apiPath, endpointPath, isChatCompletions, isResponses, isOpenAI, normalizedPath };
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
export function getProxyApiPath(url: URL): string {
|
|
@@ -39,12 +45,10 @@ export function getProxyApiPath(url: URL): string {
|
|
|
39
45
|
|
|
40
46
|
export function selectUpstreamBase(route: ApiRoute, provider: ProviderConfig | null): string {
|
|
41
47
|
if (provider === null) {
|
|
42
|
-
return route.
|
|
48
|
+
return route.isOpenAI ? DEFAULT_OPENAI_UPSTREAM : DEFAULT_UPSTREAM;
|
|
43
49
|
}
|
|
44
50
|
|
|
45
|
-
const formatSpecificUrl = route
|
|
46
|
-
? provider.openaiBaseUrl
|
|
47
|
-
: provider.anthropicBaseUrl;
|
|
51
|
+
const formatSpecificUrl = selectFormatSpecificUrl(route, provider);
|
|
48
52
|
if (formatSpecificUrl !== undefined && formatSpecificUrl !== "") {
|
|
49
53
|
return formatSpecificUrl;
|
|
50
54
|
}
|
|
@@ -57,39 +61,16 @@ export function selectUpstreamBase(route: ApiRoute, provider: ProviderConfig | n
|
|
|
57
61
|
return provider.format === "openai" ? DEFAULT_OPENAI_UPSTREAM : DEFAULT_UPSTREAM;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (isZhipuVersionedOpenAIBase(base) && isVersionedChatCompletionsPath(normalizedPath)) {
|
|
64
|
-
return base + normalizedPath.slice(3);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Many OpenAI-compatible base URLs already include /v1. Avoid producing /v1/v1.
|
|
68
|
-
if (base.endsWith("/v1") && normalizedPath.startsWith("/v1/")) {
|
|
69
|
-
return base + normalizedPath.slice(3);
|
|
70
|
-
}
|
|
71
|
-
return base + normalizedPath;
|
|
64
|
+
function nonEmptyUrl(value: string | undefined): string | undefined {
|
|
65
|
+
return value !== undefined && value !== "" ? value : undefined;
|
|
72
66
|
}
|
|
73
67
|
|
|
74
|
-
function
|
|
75
|
-
return
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
normalizedPath.startsWith(`${PATH_V1_CHAT_COMPLETIONS}/`)
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function isZhipuVersionedOpenAIBase(base: string): boolean {
|
|
83
|
-
try {
|
|
84
|
-
const parsed = new URL(base);
|
|
85
|
-
const path = parsed.pathname.replace(/\/+$/, "");
|
|
86
|
-
return (
|
|
87
|
-
parsed.hostname === "open.bigmodel.cn" &&
|
|
88
|
-
(path === "/api/paas/v4" || path === "/api/coding/paas/v4")
|
|
89
|
-
);
|
|
90
|
-
} catch {
|
|
91
|
-
return false;
|
|
68
|
+
function selectFormatSpecificUrl(route: ApiRoute, provider: ProviderConfig): string | undefined {
|
|
69
|
+
if (!route.isOpenAI) return provider.anthropicBaseUrl;
|
|
70
|
+
if (route.isResponses) {
|
|
71
|
+
return nonEmptyUrl(provider.openaiResponsesBaseUrl) ?? provider.openaiBaseUrl;
|
|
92
72
|
}
|
|
73
|
+
return provider.openaiBaseUrl;
|
|
93
74
|
}
|
|
94
75
|
|
|
95
76
|
export function setUpstreamHost(headers: Headers, upstreamBase: string): void {
|
|
@@ -18,6 +18,7 @@ const ProviderUpdateSchema = z.object({
|
|
|
18
18
|
authHeader: z.enum(["bearer", "x-api-key"]).optional(),
|
|
19
19
|
anthropicBaseUrl: z.string().optional(),
|
|
20
20
|
openaiBaseUrl: z.string().optional(),
|
|
21
|
+
openaiResponsesBaseUrl: z.string().optional(),
|
|
21
22
|
apiDocsUrl: z.string().optional(),
|
|
22
23
|
modelMetadataUrl: z.string().optional(),
|
|
23
24
|
modelMetadata: z.array(ProviderModelMetadataSchema).optional(),
|
|
@@ -9,6 +9,7 @@ const ProviderInputSchema = z.object({
|
|
|
9
9
|
format: z.enum(["anthropic", "openai"]).optional(),
|
|
10
10
|
anthropicBaseUrl: z.string().optional(),
|
|
11
11
|
openaiBaseUrl: z.string().optional(),
|
|
12
|
+
openaiResponsesBaseUrl: z.string().optional(),
|
|
12
13
|
models: z.array(z.string()).min(1, "At least one model is required"),
|
|
13
14
|
model: z.string().optional(),
|
|
14
15
|
authHeader: z.enum(["bearer", "x-api-key"]).optional().default("bearer"),
|
|
@@ -45,6 +46,7 @@ export const Route = createFileRoute("/api/providers")({
|
|
|
45
46
|
parsed.data.source,
|
|
46
47
|
parsed.data.modelMetadataUrl,
|
|
47
48
|
parsed.data.modelMetadata,
|
|
49
|
+
parsed.data.openaiResponsesBaseUrl,
|
|
48
50
|
);
|
|
49
51
|
return Response.json(newProvider, { status: 201 });
|
|
50
52
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as h,j as t}from"./main-B_EcGyf-.js";import{c as X,g as O,r as P,a as q,X as Y,b as x,B as Z,f as $,R as ee,C as te,M as _,d as J,e as M,h as V,J as N,i as re,j as ne,k as se}from"./ProxyViewerContainer-Cc944N8i.js";const ae=[["line",{x1:"5",x2:"19",y1:"9",y2:"9",key:"1nwqeh"}],["line",{x1:"5",x2:"19",y1:"15",y2:"15",key:"g8yjpy"}]],oe=X("equal",ae),de="";function j(e){if(e.length===0)return de;let r="";for(let n=0;n<e.length;n++){const s=e[n];s!==void 0&&(typeof s=="number"?r+=`[${s}]`:n===0?r+=s:r+=`.${s}`)}return r}function ie(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function D(e){if(typeof e=="string")try{return C(JSON.parse(e))}catch{return{kind:"primitive",value:e}}return C(e)}function C(e){if(e===null)return{kind:"primitive",value:null};if(typeof e=="string")return{kind:"primitive",value:e};if(typeof e=="number")return{kind:"primitive",value:e};if(typeof e=="boolean")return{kind:"primitive",value:e};if(Array.isArray(e))return{kind:"array",value:e.map(r=>C(r))};if(ie(e)){const r={};for(const n of Object.keys(e).sort())r[n]=C(e[n]);return{kind:"object",value:r}}return{kind:"primitive",value:null}}function le(e,r){const n=[];return R([],e,r,n),n}function R(e,r,n,s){const d=j(e);if(E(r,n)){s.push({kind:"equal",path:d,value:r});return}if(r.kind!==n.kind){s.push({kind:"changed",path:d,left:r,right:n});return}if(r.kind==="primitive"&&n.kind==="primitive"){s.push({kind:"changed",path:d,left:r,right:n});return}if(r.kind==="object"&&n.kind==="object"){const o=Object.keys(r.value),a=Object.keys(n.value),m=new Set(a);for(const i of o){const f=r.value[i];if(f!==void 0)if(!m.has(i))s.push({kind:"removed",path:j([...e,i]),value:f});else{const p=n.value[i];if(p===void 0)continue;R([...e,i],f,p,s)}}for(const i of a){if(o.includes(i))continue;const f=n.value[i];f!==void 0&&s.push({kind:"added",path:j([...e,i]),value:f})}return}if(r.kind==="array"&&n.kind==="array"){const o=Math.min(r.value.length,n.value.length);for(let a=0;a<o;a++){const m=r.value[a],i=n.value[a];m===void 0||i===void 0||R([...e,a],m,i,s)}for(let a=o;a<n.value.length;a++){const m=n.value[a];m!==void 0&&s.push({kind:"added",path:j([...e,a]),value:m})}for(let a=o;a<r.value.length;a++){const m=r.value[a];m!==void 0&&s.push({kind:"removed",path:j([...e,a]),value:m})}}}function E(e,r){if(e.kind!==r.kind)return!1;if(e.kind==="primitive"&&r.kind==="primitive")return e.value===r.value;if(e.kind==="array"&&r.kind==="array"){if(e.value.length!==r.value.length)return!1;for(let n=0;n<e.value.length;n++){const s=e.value[n],d=r.value[n];if(s===void 0||d===void 0||!E(s,d))return!1}return!0}if(e.kind==="object"&&r.kind==="object"){const n=Object.keys(e.value),s=Object.keys(r.value);if(n.length!==s.length)return!1;for(const d of n){const o=e.value[d],a=r.value[d];if(o===void 0||a===void 0||!E(o,a))return!1}return!0}return!1}function v(e,r=80){let n;switch(e.kind){case"primitive":n=e.value===null?"null":JSON.stringify(e.value);break;case"array":n=`[… ${e.value.length} items]`;break;case"object":n=`{… ${Object.keys(e.value).length} keys}`;break}return n.length>r&&(n=`${n.slice(0,r-1)}…`),n}function w(e,r=2){return JSON.stringify(S(e),null,r)}function S(e){switch(e.kind){case"primitive":return e.value;case"array":return e.value.map(S);case"object":{const r={};for(const[n,s]of Object.entries(e.value))r[n]=S(s);return r}}}function K(e){if(e==="")return"";for(let r=e.length-1;r>=0;r--){const n=e[r];if(n==="."||n==="[")return e.substring(0,r)}return""}function L(e){return e.kind==="equal"&&(e.value.kind==="object"||e.value.kind==="array")}function ce(e){const r=[];let n=0;for(;n<e.length;){const s=e[n];if(s!==void 0&&L(s)){const d=K(s.path);let o=n+1;for(;o<e.length;){const a=e[o];if(a===void 0||!L(a)||K(a.path)!==d)break;o++}if(o-n>1){const a=[];for(let m=n;m<o;m++){const i=e[m];i!==void 0&&i.kind==="equal"&&a.push(i)}r.push({kind:"equal-run",ops:a}),n=o;continue}}s!==void 0&&r.push({kind:"single",op:s}),n++}return r}const B={added:{icon:J,accent:"text-emerald-600 dark:text-emerald-400",bg:"bg-emerald-500/5 hover:bg-emerald-500/10",border:"border-l-emerald-500",label:"ADDED"},removed:{icon:_,accent:"text-rose-600 dark:text-rose-400",bg:"bg-rose-500/5 hover:bg-rose-500/10",border:"border-l-rose-500",label:"REMOVED"},changed:{icon:M,accent:"text-amber-600 dark:text-amber-400",bg:"bg-amber-500/5 hover:bg-amber-500/10",border:"border-l-amber-500",label:"CHANGED"},equal:{icon:oe,accent:"text-muted-foreground/70",bg:"bg-muted/20 hover:bg-muted/30",border:"border-l-muted-foreground/20",label:"EQUAL"}};function ue({ops:e,expanded:r,onToggle:n}){const s=e[0],d=e[e.length-1];if(s===void 0||d===void 0)return t.jsx("div",{className:"text-muted-foreground/40 text-xs",children:"—"});const o=s.path,a=d.path,m=e.length===1?o:`${o} … ${a}`,i=s.value.kind==="array"?`${e.length} equal arrays`:s.value.kind==="object"?`${e.length} equal objects`:"equal",f=B.equal;return t.jsxs("div",{className:x("border-l-4 rounded-sm",f.border,f.bg),children:[t.jsxs("button",{type:"button",onClick:n,className:"w-full text-left flex items-center gap-2 px-3 py-1.5 text-xs text-muted-foreground cursor-pointer",children:[t.jsx(V,{className:x("size-3 transition-transform shrink-0",r&&"rotate-90")}),t.jsx(f.icon,{className:x("size-3 shrink-0",f.accent)}),t.jsx("span",{className:"font-mono truncate flex-1",title:`${o} … ${a}`,children:m}),t.jsx("span",{className:x("text-[10px] uppercase tracking-wider shrink-0",f.accent),children:f.label}),t.jsxs("span",{className:"text-muted-foreground/60 shrink-0",children:["(",i,")"]})]}),r&&t.jsx("div",{className:"ml-5 mt-1 mb-2 space-y-2 pr-2",children:e.map(p=>t.jsxs("div",{className:"border border-border/50 rounded p-2 bg-muted/20",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-1",children:p.path}),t.jsx(N,{text:w(p.value),defaultExpandDepth:0})]},p.path))})]})}function me({op:e,idx:r,copiedPath:n,onCopyPath:s,expanded:d,onToggle:o}){const a=B[e.kind],m=a.icon,i=e.kind==="added"||e.kind==="removed"?e.value.kind==="object"||e.value.kind==="array":e.kind==="changed"?e.left.kind==="object"||e.left.kind==="array"||e.right.kind==="object"||e.right.kind==="array":!1,f=e.kind==="changed"?[{text:v(e.left,400),tone:"text-rose-700 dark:text-rose-300 line-through"},{text:v(e.right,400),tone:"text-emerald-700 dark:text-emerald-300"}]:e.kind==="removed"?[{text:v(e.value,400),tone:"text-rose-700 dark:text-rose-300 line-through"}]:e.kind==="added"?[{text:v(e.value,400),tone:"text-emerald-700 dark:text-emerald-300"}]:[{text:v(e.value,400),tone:"text-muted-foreground"}],p=n===e.path&&e.path!=="";return t.jsxs("div",{"data-diff-idx":r,"data-diff-kind":e.kind,className:x("border-l-4 rounded-sm px-3 py-2 my-0.5 transition-colors",a.border,a.bg),children:[t.jsxs("button",{type:"button",onClick:o,disabled:!i,className:x("w-full flex items-center gap-2 text-xs text-left rounded-sm",i?"cursor-pointer":"cursor-default"),"aria-expanded":i?d:void 0,"aria-label":i?d?`Collapse ${e.path||"root"}`:`Expand ${e.path||"root"}`:void 0,children:[i?t.jsx(V,{className:x("size-3 shrink-0 transition-transform",a.accent,d&&"rotate-90")}):t.jsx("span",{className:"size-3 shrink-0","aria-hidden":"true"}),t.jsx(m,{className:x("size-3.5 shrink-0",a.accent),strokeWidth:2.5}),t.jsx("span",{className:"font-mono truncate flex-1 min-w-0",title:e.path||"(root)",children:e.path===""?"(root)":e.path}),t.jsx("span",{className:x("text-[9px] font-bold uppercase tracking-wider shrink-0 px-1.5 py-0.5 rounded",a.accent,e.kind==="equal"?"bg-muted/40":"bg-background/60"),children:a.label}),e.path!==""&&t.jsx("span",{role:"button",tabIndex:0,onClick:b=>{b.stopPropagation(),s(e.path)},onKeyDown:b=>{(b.key==="Enter"||b.key===" ")&&(b.stopPropagation(),b.preventDefault(),s(e.path))},className:x("shrink-0 p-1 rounded transition-colors cursor-pointer inline-flex items-center justify-center",p?"text-emerald-500":"text-muted-foreground/50 hover:text-foreground hover:bg-muted"),"aria-label":p?"Copied":"Copy",title:p?"Copied!":"Copy",children:p?t.jsx(re,{className:"size-3"}):t.jsx(ne,{className:"size-3"})})]}),f.map((b,y)=>t.jsx("div",{className:x("font-mono text-xs mt-1 break-all pl-5",b.tone),children:b.text},y)),t.jsx("div",{className:"overflow-hidden transition-all duration-200",style:{maxHeight:d&&i?"2000px":"0"},"aria-hidden":!d,children:d&&i&&e.kind!=="equal"?t.jsx(xe,{op:e}):null})]})}function xe({op:e}){if(e.kind==="added"||e.kind==="removed")return t.jsx("div",{className:"pl-5 mt-2 border border-border/50 rounded p-2 bg-muted/20",children:t.jsx(N,{text:w(e.value),defaultExpandDepth:0})});const r=e.left.kind==="object"||e.left.kind==="array",n=e.right.kind==="object"||e.right.kind==="array";return!r&&!n?t.jsx("div",{className:"pl-5 mt-2 text-xs text-muted-foreground/70 italic",children:"Primitive values are shown inline above."}):t.jsxs("div",{className:"pl-5 mt-2 grid grid-cols-1 md:grid-cols-2 gap-2",children:[t.jsxs("div",{className:"border border-rose-500/30 rounded p-2 bg-rose-500/5",children:[t.jsx("div",{className:"text-[10px] uppercase tracking-wider text-rose-500 mb-1",children:"Old"}),t.jsx(N,{text:w(e.left),defaultExpandDepth:0})]}),t.jsxs("div",{className:"border border-emerald-500/30 rounded p-2 bg-emerald-500/5",children:[t.jsx("div",{className:"text-[10px] uppercase tracking-wider text-emerald-500 mb-1",children:"New"}),t.jsx(N,{text:w(e.right),defaultExpandDepth:0})]})]})}function fe({counts:e,onJumpTo:r}){const n=e.added+e.removed+e.changed;return t.jsxs("div",{className:"px-4 py-2 border-b border-border bg-muted/20 flex items-center gap-2 text-xs flex-wrap",children:[t.jsxs("span",{className:"text-muted-foreground font-medium",children:[n," ",n===1?"change":"changes"]}),t.jsxs("button",{type:"button",onClick:()=>r("removed"),disabled:e.removed===0,className:x("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",e.removed>0?"border-rose-500/40 text-rose-600 dark:text-rose-400 bg-rose-500/10 hover:bg-rose-500/20":"border-border text-muted-foreground/40 cursor-not-allowed"),title:e.removed>0?"Jump to first removed":"No removals",children:[t.jsx(_,{className:"size-3"}),e.removed," removed"]}),t.jsxs("button",{type:"button",onClick:()=>r("added"),disabled:e.added===0,className:x("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",e.added>0?"border-emerald-500/40 text-emerald-600 dark:text-emerald-400 bg-emerald-500/10 hover:bg-emerald-500/20":"border-border text-muted-foreground/40 cursor-not-allowed"),title:e.added>0?"Jump to first added":"No additions",children:[t.jsx(J,{className:"size-3"}),e.added," added"]}),t.jsxs("button",{type:"button",onClick:()=>r("changed"),disabled:e.changed===0,className:x("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border cursor-pointer transition-colors",e.changed>0?"border-amber-500/40 text-amber-600 dark:text-amber-400 bg-amber-500/10 hover:bg-amber-500/20":"border-border text-muted-foreground/40 cursor-not-allowed"),title:e.changed>0?"Jump to first changed":"No changes",children:[t.jsx(M,{className:"size-3"}),e.changed," changed"]})]})}function pe({mode:e,onChange:r}){return t.jsxs("div",{className:"inline-flex rounded-md border border-border overflow-hidden",children:[t.jsxs("button",{type:"button",onClick:()=>r("unified"),"aria-pressed":e==="unified",className:x("flex items-center gap-1 px-2 py-1 text-xs transition-colors cursor-pointer",e==="unified"?"bg-muted text-foreground":"hover:bg-muted/50 text-muted-foreground"),title:"Unified view (single column, emphasized diffs)",children:[t.jsx(ee,{className:"size-3"}),"Unified"]}),t.jsxs("button",{type:"button",onClick:()=>r("split"),"aria-pressed":e==="split",className:x("flex items-center gap-1 px-2 py-1 text-xs transition-colors border-l border-border cursor-pointer",e==="split"?"bg-muted text-foreground":"hover:bg-muted/50 text-muted-foreground"),title:"Split view (path | left | right)",children:[t.jsx(te,{className:"size-3"}),"Split"]})]})}function A({log:e,side:r}){const n=q(e);return t.jsxs("div",{className:"flex-1 min-w-0 space-y-1 text-xs",children:[t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx(Z,{variant:"outline",className:x("text-[10px] px-1.5 py-0 h-5 font-mono shrink-0",r==="left"?"border-rose-500/40 text-rose-400":"border-emerald-500/40 text-emerald-400"),children:r==="left"?"← Left":"Right →"}),t.jsxs("span",{className:"font-mono text-blue-400/80",children:["#",e.id]}),e.model!==null&&t.jsx("span",{className:"font-mono text-muted-foreground truncate",children:e.model})]}),t.jsxs("div",{className:"flex items-center gap-3 text-muted-foreground font-mono",children:[e.cacheCreationInputTokens!==null&&e.cacheCreationInputTokens>0&&t.jsxs("span",{className:"text-emerald-400",children:["KV Cache +",$(e.cacheCreationInputTokens)]}),e.cacheReadInputTokens!==null&&e.cacheReadInputTokens>0&&t.jsxs("span",{className:"text-purple-400",children:["KV Cache ~",$(e.cacheReadInputTokens)]}),t.jsx("span",{className:"truncate",title:e.timestamp,children:e.timestamp})]}),t.jsxs("div",{className:"text-muted-foreground/70 font-mono truncate",title:n,children:["session: ",n]})]})}function ge({left:e,right:r,onClose:n}){const s=h.useMemo(()=>{const l=O(P(e)).analyzeRequest(e.rawRequestBody),c=O(P(r)).analyzeRequest(r.rawRequestBody),u=D(l.comparisonValue),g=D(c.comparisonValue);return le(u,g)},[e.apiFormat,e.path,e.rawRequestBody,r.apiFormat,r.path,r.rawRequestBody]),d=h.useMemo(()=>ce(s),[s]),o=h.useMemo(()=>{let l=0,c=0,u=0;for(const g of d)if(g.kind==="single")switch(g.op.kind){case"added":l++;break;case"removed":c++;break;case"changed":u++;break}return{added:l,removed:c,changed:u}},[d]),[a,m]=h.useState(new Set),i=l=>{m(c=>{const u=new Set(c);return u.has(l)?u.delete(l):u.add(l),u})},[f,p]=h.useState(new Set),b=l=>{p(c=>{const u=new Set(c);return u.has(l)?u.delete(l):u.add(l),u})};h.useEffect(()=>{p(new Set)},[e.id,r.id]);const[y,F]=h.useState("unified"),T=h.useRef(null),[U,z]=h.useState(null),k=h.useRef(null),H=l=>{se(l).then(c=>{c&&(z(l),k.current!==null&&clearTimeout(k.current),k.current=setTimeout(()=>z(null),1500))})};h.useEffect(()=>()=>{k.current!==null&&clearTimeout(k.current)},[]);const G=l=>{const c=d.findIndex(I=>I.kind==="single"&&I.op.kind===l);if(c===-1)return;const u=T.current;if(u===null)return;const g=u.querySelector(`[data-diff-idx="${c}"]`);g!==null&&g.scrollIntoView({behavior:"smooth",block:"center"})};h.useEffect(()=>{const l=u=>{u.key==="Escape"&&n()};document.addEventListener("keydown",l);const c=document.body.style.overflow;return document.body.style.overflow="hidden",()=>{document.removeEventListener("keydown",l),document.body.style.overflow=c}},[n]);const Q=q(e)===q(r),W=s.length===1&&s[0]?.kind==="equal";return t.jsxs("div",{className:"fixed inset-0 z-50 flex justify-end",role:"dialog","aria-modal":"true","aria-label":"Compare two log requests",children:[t.jsx("button",{type:"button",onClick:n,"aria-label":"Close compare drawer",className:"absolute inset-0 bg-black/40 cursor-default",tabIndex:-1}),t.jsxs("div",{className:x("relative bg-background border-l border-border shadow-xl","w-full md:w-[70vw] max-w-[1100px] flex flex-col h-full"),onClick:l=>l.stopPropagation(),onKeyDown:l=>l.stopPropagation(),children:[t.jsxs("div",{className:"flex items-start gap-4 px-4 py-3 border-b border-border",children:[t.jsxs("div",{className:"flex-1 flex gap-4 min-w-0",children:[t.jsx(A,{log:e,side:"left"}),t.jsx(A,{log:r,side:"right"})]}),t.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[t.jsx(pe,{mode:y,onChange:F}),t.jsx("button",{type:"button",onClick:n,"aria-label":"Close",className:"p-1 rounded text-muted-foreground hover:text-foreground hover:bg-muted cursor-pointer",children:t.jsx(Y,{className:"size-4"})})]})]}),!Q&&t.jsx("div",{className:"px-4 py-1.5 text-xs text-amber-400 bg-amber-500/10 border-b border-border",children:"Heads up: the two selected logs are from different sessions."}),W?t.jsx("div",{className:"flex-1 min-h-0 overflow-y-auto flex items-center justify-center text-muted-foreground text-sm",children:"The two Request payloads are identical."}):t.jsxs(t.Fragment,{children:[t.jsx(fe,{counts:o,onJumpTo:G}),t.jsx("div",{ref:T,className:"flex-1 min-h-0 overflow-y-auto",children:y==="unified"?t.jsx("div",{className:"px-3 py-2 space-y-0.5",children:d.map((l,c)=>{if(l.kind==="equal-run")return t.jsx(ue,{ops:l.ops,expanded:a.has(c),onToggle:()=>i(c)},`r${c}`);const u=l.op;return t.jsx(me,{op:u,idx:c,copiedPath:U,onCopyPath:H,expanded:f.has(c),onToggle:()=>b(c)},`o${c}`)})}):t.jsx(he,{grouped:d,left:e,right:r})})]})]})]})}function he({grouped:e,left:r,right:n}){return t.jsxs("div",{className:"grid grid-cols-[200px_1fr_1fr] gap-x-2 gap-y-0.5 px-3 py-2 text-xs",children:[t.jsxs("div",{className:"grid grid-cols-[200px_1fr_1fr] gap-x-2 col-span-3 pb-2 mb-2 border-b border-border text-[10px] uppercase tracking-wider text-muted-foreground",children:[t.jsx("span",{children:"Path"}),t.jsxs("span",{children:["Left (Log #",r.id,")"]}),t.jsxs("span",{children:["Right (Log #",n.id,")"]})]}),e.map((s,d)=>{if(s.kind==="equal-run")return t.jsxs("div",{className:"col-span-3 px-2 py-1 text-xs text-muted-foreground/60",children:[s.ops.length," equal siblings collapsed — switch to Unified to expand"]},d);const o=s.op;return o.kind==="equal"?t.jsxs("div",{className:"col-span-3 grid grid-cols-[200px_1fr_1fr] gap-x-2 px-2 py-0.5 text-muted-foreground",children:[t.jsx("span",{className:"font-mono text-xs truncate",title:o.path,children:o.path}),t.jsx("span",{className:"font-mono text-xs break-all opacity-60",children:v(o.value,200)}),t.jsx("span",{className:"font-mono text-xs break-all opacity-60",children:v(o.value,200)})]},d):o.kind==="added"?t.jsxs("div",{className:"col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-emerald-400/70 bg-emerald-500/5",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-0.5",children:o.path}),t.jsxs("div",{className:"font-mono break-all text-emerald-300/90",children:["+ ",v(o.value,400)]})]},d):o.kind==="removed"?t.jsxs("div",{className:"col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-rose-400/70 bg-rose-500/5",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-0.5",children:o.path}),t.jsxs("div",{className:"font-mono break-all text-rose-300/90 line-through",children:["− ",v(o.value,400)]})]},d):t.jsxs("div",{className:"col-span-3 px-2 py-1 rounded text-xs border-l-2 border-l-amber-400/70 bg-amber-500/5",children:[t.jsx("div",{className:"font-mono text-xs text-muted-foreground mb-1",children:o.path}),t.jsxs("div",{className:"grid grid-cols-2 gap-2",children:[t.jsx("div",{className:"font-mono text-rose-300/90 break-all line-through",children:v(o.left,400)}),t.jsx("div",{className:"font-mono text-emerald-300/90 break-all",children:v(o.right,400)})]})]},d)})]})}export{ge as CompareDrawer};
|