@tonyclaw/agent-inspector 2.1.13 → 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.
Files changed (81) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/CompareDrawer-DjgjIFx7.js +1 -0
  3. package/.output/public/assets/ProxyViewerContainer-CWUQZLYy.js +106 -0
  4. package/.output/public/assets/{ReplayDialog-sBA1KAYD.js → ReplayDialog-CU0Tbb2c.js} +1 -1
  5. package/.output/public/assets/{RequestAnatomy-BjlBT-Cy.js → RequestAnatomy-BNahe83D.js} +1 -1
  6. package/.output/public/assets/ResponseView-DSOnGqi6.js +3 -0
  7. package/.output/public/assets/{StreamingChunkSequence-D2_SMhlE.js → StreamingChunkSequence-BEKTDklB.js} +1 -1
  8. package/.output/public/assets/_sessionId-Cif8JZdn.js +1 -0
  9. package/.output/public/assets/index-DWOkqdCa.js +14 -0
  10. package/.output/public/assets/index-D_WfwzUi.js +1 -0
  11. package/.output/public/assets/index-DtLuQrs0.css +1 -0
  12. package/.output/public/assets/json-viewer-C2JpgcW0.js +1 -0
  13. package/.output/public/assets/{main-Dtspb4Ui.js → main-CSONBwwn.js} +2 -2
  14. package/.output/server/_libs/lucide-react.mjs +20 -20
  15. package/.output/server/_libs/react-markdown.mjs +90 -2
  16. package/.output/server/{_sessionId-CXDcLuvi.mjs → _sessionId-CPAa37n5.mjs} +6 -57
  17. package/.output/server/_ssr/{CompareDrawer-CMoCAoeq.mjs → CompareDrawer-ceW5VxMo.mjs} +17 -62
  18. package/.output/server/_ssr/{ProxyViewerContainer-BW2vVCBN.mjs → ProxyViewerContainer-CDfEE_w-.mjs} +872 -688
  19. package/.output/server/_ssr/{ReplayDialog-ChXL1t8H.mjs → ReplayDialog-V0s_eEbR.mjs} +8 -59
  20. package/.output/server/_ssr/{RequestAnatomy-DtKzIlfU.mjs → RequestAnatomy-f1ccwR9d.mjs} +6 -57
  21. package/.output/server/_ssr/{ResponseView-B5I8drzc.mjs → ResponseView-BIRrqG4H.mjs} +138 -60
  22. package/.output/server/_ssr/{StreamingChunkSequence-DvwjQNcO.mjs → StreamingChunkSequence-V3JFjCgX.mjs} +13 -58
  23. package/.output/server/_ssr/{index-D_ZHtRfl.mjs → index-DsykulzS.mjs} +6 -57
  24. package/.output/server/_ssr/index.mjs +2 -2
  25. package/.output/server/_ssr/json-viewer-Dcnm0Ivf.mjs +478 -0
  26. package/.output/server/_ssr/{router-DhL9Wp3N.mjs → router-4bdm6Mt2.mjs} +674 -173
  27. package/.output/server/{_tanstack-start-manifest_v-BHgoAmxZ.mjs → _tanstack-start-manifest_v-Xp4CO64V.mjs} +1 -1
  28. package/.output/server/index.mjs +76 -62
  29. package/package.json +1 -1
  30. package/src/components/ProxyViewerContainer.tsx +25 -17
  31. package/src/components/providers/ImportWizardDialog.tsx +7 -1
  32. package/src/components/providers/ProviderCard.tsx +10 -1
  33. package/src/components/providers/ProviderForm.tsx +157 -41
  34. package/src/components/providers/ProvidersPanel.tsx +5 -1
  35. package/src/components/proxy-viewer/AnswerMarkdown.tsx +8 -3
  36. package/src/components/proxy-viewer/CompareDrawer.tsx +20 -6
  37. package/src/components/proxy-viewer/ConversationGroup.tsx +11 -15
  38. package/src/components/proxy-viewer/LogEntry.tsx +31 -11
  39. package/src/components/proxy-viewer/LogEntryHeader.tsx +111 -40
  40. package/src/components/proxy-viewer/RequestToolsPanel.tsx +19 -13
  41. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +9 -3
  42. package/src/components/proxy-viewer/TurnGroup.tsx +53 -7
  43. package/src/components/proxy-viewer/TurnGroupList.tsx +153 -0
  44. package/src/components/proxy-viewer/anatomy/sessionContextSummary.ts +74 -2
  45. package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +13 -7
  46. package/src/components/proxy-viewer/formats/index.tsx +10 -3
  47. package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +185 -17
  48. package/src/components/proxy-viewer/log-formats/openai.ts +107 -15
  49. package/src/components/proxy-viewer/logFocus.ts +15 -2
  50. package/src/components/proxy-viewer/requestTools.ts +7 -3
  51. package/src/components/proxy-viewer/viewerState.ts +72 -3
  52. package/src/components/ui/json-viewer-bulk.ts +41 -6
  53. package/src/components/ui/json-viewer.tsx +9 -8
  54. package/src/contracts/index.ts +15 -1
  55. package/src/contracts/openai.ts +94 -0
  56. package/src/lib/providerContract.ts +1 -0
  57. package/src/lib/providerModelMetadata.ts +7 -1
  58. package/src/lib/stopReason.ts +12 -0
  59. package/src/lib/upstreamUrl.ts +46 -0
  60. package/src/mcp/previewExtractor.ts +73 -3
  61. package/src/mcp/server.ts +2 -0
  62. package/src/mcp/toolHandlers.ts +2 -0
  63. package/src/proxy/constants.ts +2 -0
  64. package/src/proxy/formats/openai/handler.ts +40 -9
  65. package/src/proxy/formats/openai/index.ts +7 -0
  66. package/src/proxy/formats/openai/schemas.ts +15 -1
  67. package/src/proxy/formats/openai/stream.ts +529 -135
  68. package/src/proxy/formats/registry.ts +9 -1
  69. package/src/proxy/providerImporters.ts +45 -5
  70. package/src/proxy/providers.ts +31 -11
  71. package/src/proxy/schemas.ts +6 -0
  72. package/src/proxy/toolSchemaWarnings.ts +25 -3
  73. package/src/proxy/upstream.ts +18 -37
  74. package/src/routes/api/providers.$providerId.ts +1 -0
  75. package/src/routes/api/providers.ts +2 -0
  76. package/.output/public/assets/CompareDrawer-Cz_1vIpR.js +0 -1
  77. package/.output/public/assets/ProxyViewerContainer-7QSiluMf.js +0 -117
  78. package/.output/public/assets/ResponseView-PtEKzml9.js +0 -1
  79. package/.output/public/assets/_sessionId-DZfB4ruK.js +0 -1
  80. package/.output/public/assets/index-B-QQLbpz.js +0 -1
  81. 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 { PATH_V1_CHAT_COMPLETIONS, PATH_CHAT_COMPLETIONS, PATH_V1_MESSAGES } from "../constants";
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");
@@ -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
- { anthropicBaseUrl: string; openaiBaseUrl: string; format: "anthropic" | "openai" }
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: "https://api.minimaxi.com",
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: "https://api.minimaxi.com",
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 (p.openaiBaseUrl ?? "") === ext.openaiBaseUrl;
570
+ return (
571
+ (p.openaiBaseUrl ?? "") === ext.openaiBaseUrl &&
572
+ openAIResponsesBaseUrlMatchesExisting(p, ext)
573
+ );
534
574
  });
535
575
  }
536
576
 
@@ -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 (p.anthropicBaseUrl !== undefined || p.openaiBaseUrl !== undefined) {
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 both Anthropic and OpenAI URLs.
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 (apiKey, anthropicBaseUrl, openaiBaseUrl)
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 (apiKey, anthropicBaseUrl, openaiBaseUrl)
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 (apiKey, anthropicBaseUrl, openaiBaseUrl) already exists,
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
  }
@@ -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
- if (functionDefinition === null) continue;
346
- const name = readString(functionDefinition, "name");
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: readObject(functionDefinition, "parameters"),
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
 
@@ -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 normalizedPath =
31
- isChatCompletions && !apiPath.startsWith("/v1/") ? `/v1${apiPath}` : apiPath;
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.isChatCompletions ? DEFAULT_OPENAI_UPSTREAM : DEFAULT_UPSTREAM;
48
+ return route.isOpenAI ? DEFAULT_OPENAI_UPSTREAM : DEFAULT_UPSTREAM;
43
49
  }
44
50
 
45
- const formatSpecificUrl = route.isChatCompletions
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
- export function buildUpstreamUrl(upstreamBase: string, normalizedPath: string): string {
61
- const base = upstreamBase.endsWith("/") ? upstreamBase.slice(0, -1) : upstreamBase;
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 isVersionedChatCompletionsPath(normalizedPath: string): boolean {
75
- return (
76
- normalizedPath === PATH_V1_CHAT_COMPLETIONS ||
77
- normalizedPath.startsWith(`${PATH_V1_CHAT_COMPLETIONS}?`) ||
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-Dtspb4Ui.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-7QSiluMf.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};