@tonyclaw/agent-inspector 2.0.40 → 2.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/CompareDrawer-Cfqxlo-U.js +1 -0
- package/.output/public/assets/ProxyViewerContainer-CE9pAX4c.js +117 -0
- package/.output/public/assets/{ReplayDialog-TTTVCMe9.js → ReplayDialog-daRidZo_.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-c8ru0pS-.js → RequestAnatomy-CJ7EPQGQ.js} +1 -1
- package/.output/public/assets/ResponseView-xBmr54hB.js +1 -0
- package/.output/public/assets/StreamingChunkSequence-nK-0LpbM.js +1 -0
- package/.output/public/assets/_sessionId-CbWEG5ej.js +1 -0
- package/.output/public/assets/index-D1MkoT4l.js +1 -0
- package/.output/public/assets/{main-CKYe6UUv.js → main-DOy_Q96H.js} +2 -2
- package/.output/server/{_sessionId-CkwhOzdS.mjs → _sessionId-DtYRZHlM.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-BHDiuYMw.mjs → CompareDrawer-fy1ARwtx.mjs} +4 -3
- package/.output/server/_ssr/{ProxyViewerContainer-0c2_DBMX.mjs → ProxyViewerContainer-9GJtCWCq.mjs} +242 -45
- package/.output/server/_ssr/{ReplayDialog-CTjwzDDH.mjs → ReplayDialog-BrS7syOE.mjs} +3 -3
- package/.output/server/_ssr/{RequestAnatomy-D36oujv2.mjs → RequestAnatomy-BtWt1cWY.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-ClZ3oODE.mjs → ResponseView-CkKGYE6m.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-Ck5l4p1m.mjs → StreamingChunkSequence-C7rB3osr.mjs} +2 -2
- package/.output/server/_ssr/{index-DKBYH0TC.mjs → index-D24WforP.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{router-D4I3ZyVS.mjs → router-CVqVjBzJ.mjs} +1312 -467
- package/.output/server/{_tanstack-start-manifest_v-D4vSn_wD.mjs → _tanstack-start-manifest_v-DSikvwru.mjs} +1 -1
- package/.output/server/index.mjs +59 -59
- package/README.md +36 -12
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +5 -2
- package/src/components/providers/ProviderCard.tsx +6 -3
- package/src/components/providers/ProviderForm.tsx +6 -3
- package/src/components/providers/ProvidersPanel.tsx +6 -1
- package/src/components/providers/SettingsDialog.tsx +142 -2
- package/src/components/proxy-viewer/CompareDrawer.tsx +3 -1
- package/src/components/proxy-viewer/LogEntry.tsx +8 -5
- package/src/components/proxy-viewer/useCopyFeedback.ts +3 -1
- package/src/components/ui/json-viewer.tsx +7 -2
- package/src/lib/clipboard.ts +67 -0
- package/src/lib/runContract.ts +3 -0
- package/src/mcp/currentContext.ts +65 -0
- package/src/mcp/mode.ts +56 -0
- package/src/mcp/server.ts +466 -15
- package/src/mcp/toolHandlers.ts +372 -0
- package/src/proxy/evidenceExporter.ts +1 -0
- package/src/proxy/runStore.ts +74 -22
- package/.output/public/assets/CompareDrawer-p0uYxW7_.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-BGqeiYKD.js +0 -115
- package/.output/public/assets/ResponseView-D59jTMy4.js +0 -1
- package/.output/public/assets/StreamingChunkSequence-UlAMVF9j.js +0 -1
- package/.output/public/assets/_sessionId-EHPexdPl.js +0 -1
- package/.output/public/assets/index-CZP-XfpB.js +0 -1
package/src/mcp/toolHandlers.ts
CHANGED
|
@@ -16,10 +16,18 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { Buffer } from "node:buffer";
|
|
19
|
+
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { dirname, join } from "node:path";
|
|
21
|
+
import JSZip from "jszip";
|
|
19
22
|
import { z } from "zod";
|
|
20
23
|
import { getCurrentPort } from "../lib/serverPort";
|
|
21
24
|
import { LoopbackTimeoutError, type CallApiOptions } from "./loopback";
|
|
22
25
|
import { extractLastUserMessagePreview, extractResponsePreview } from "./previewExtractor";
|
|
26
|
+
import {
|
|
27
|
+
getCurrentContext,
|
|
28
|
+
setCurrentContext,
|
|
29
|
+
type InspectorCurrentContextPatch,
|
|
30
|
+
} from "./currentContext";
|
|
23
31
|
import {
|
|
24
32
|
type AddInspectorGroupSessionInput,
|
|
25
33
|
type CreateInspectorGroupInput,
|
|
@@ -36,6 +44,7 @@ import {
|
|
|
36
44
|
exportInspectorGroupEvidence,
|
|
37
45
|
getInspectorGroup,
|
|
38
46
|
listInspectorGroups,
|
|
47
|
+
readInspectorGroupEvidence,
|
|
39
48
|
updateInspectorGroup,
|
|
40
49
|
} from "../services/groups";
|
|
41
50
|
import {
|
|
@@ -43,6 +52,7 @@ import {
|
|
|
43
52
|
exportInspectorRunEvidence,
|
|
44
53
|
getInspectorRun,
|
|
45
54
|
listInspectorRecentFailures,
|
|
55
|
+
readInspectorRunEvidence,
|
|
46
56
|
updateInspectorRun,
|
|
47
57
|
} from "../services/runs";
|
|
48
58
|
|
|
@@ -423,6 +433,227 @@ export async function exportGroupEvidenceImpl(args: ExportGroupEvidenceArgs): Pr
|
|
|
423
433
|
return textJson(exported.value);
|
|
424
434
|
}
|
|
425
435
|
|
|
436
|
+
const SESSION_TIMELINE_DEFAULT_LIMIT = 20;
|
|
437
|
+
const SESSION_TIMELINE_LIMIT_MAX = 50;
|
|
438
|
+
|
|
439
|
+
export type SessionTimelineArgs = {
|
|
440
|
+
sessionId: string;
|
|
441
|
+
limit?: number;
|
|
442
|
+
offset?: number;
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
function clampTimelineLimit(input: number | undefined): number {
|
|
446
|
+
if (input === undefined) return SESSION_TIMELINE_DEFAULT_LIMIT;
|
|
447
|
+
if (!Number.isFinite(input) || input < 1) return 1;
|
|
448
|
+
return Math.min(Math.floor(input), SESSION_TIMELINE_LIMIT_MAX);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export function buildSessionTimeline(
|
|
452
|
+
sessionId: string,
|
|
453
|
+
logs: CapturedLog[],
|
|
454
|
+
total: number,
|
|
455
|
+
limit: number,
|
|
456
|
+
): Record<string, unknown> {
|
|
457
|
+
const sortedLogs = [...logs].sort((left, right) => left.id - right.id);
|
|
458
|
+
const events = sortedLogs.map((log, index) => ({
|
|
459
|
+
sequence: index + 1,
|
|
460
|
+
kind: "request",
|
|
461
|
+
logId: log.id,
|
|
462
|
+
...buildLogSummary(log),
|
|
463
|
+
}));
|
|
464
|
+
const first = sortedLogs[0];
|
|
465
|
+
const last = sortedLogs[sortedLogs.length - 1];
|
|
466
|
+
const errorCount = events.filter((event) => event.hasError).length;
|
|
467
|
+
const models = [...new Set(sortedLogs.map((log) => log.model).filter((model) => model !== null))];
|
|
468
|
+
const providers = [
|
|
469
|
+
...new Set(
|
|
470
|
+
sortedLogs.map((log) => log.providerName ?? null).filter((provider) => provider !== null),
|
|
471
|
+
),
|
|
472
|
+
];
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
sessionId,
|
|
476
|
+
logCoverage: "bounded",
|
|
477
|
+
total,
|
|
478
|
+
limit,
|
|
479
|
+
eventCount: events.length,
|
|
480
|
+
requestCount: events.length,
|
|
481
|
+
errorCount,
|
|
482
|
+
startedAt: first?.timestamp ?? null,
|
|
483
|
+
updatedAt: last?.timestamp ?? null,
|
|
484
|
+
models,
|
|
485
|
+
providers,
|
|
486
|
+
events,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export async function getSessionTimelineImpl(
|
|
491
|
+
callApi: CallApiFn,
|
|
492
|
+
args: SessionTimelineArgs,
|
|
493
|
+
): Promise<ToolResult> {
|
|
494
|
+
const limit = clampTimelineLimit(args.limit);
|
|
495
|
+
const offset = args.offset ?? 0;
|
|
496
|
+
const params = new URLSearchParams({
|
|
497
|
+
offset: String(offset),
|
|
498
|
+
limit: String(limit),
|
|
499
|
+
compact: "1",
|
|
500
|
+
sessionId: args.sessionId,
|
|
501
|
+
});
|
|
502
|
+
const path = `/api/logs?${params.toString()}`;
|
|
503
|
+
const res = await callApi(path);
|
|
504
|
+
if (!res.ok) return toolError(`GET ${path} returned ${res.status}`);
|
|
505
|
+
const rawBody: unknown = await res.json();
|
|
506
|
+
const parsed = LogsListResponseSchema.safeParse(rawBody);
|
|
507
|
+
if (!parsed.success) return toolError("GET /api/logs returned an unparseable timeline shape");
|
|
508
|
+
const logs = parsed.data.logs ?? [];
|
|
509
|
+
return textJson(
|
|
510
|
+
buildSessionTimeline(args.sessionId, logs, parsed.data.total ?? logs.length, limit),
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
type EvidenceFileKind = "json" | "markdown" | "html" | "zip";
|
|
515
|
+
|
|
516
|
+
type EvidenceFileSummary = {
|
|
517
|
+
kind: EvidenceFileKind;
|
|
518
|
+
path: string;
|
|
519
|
+
exists: boolean;
|
|
520
|
+
bytes: number | null;
|
|
521
|
+
included?: boolean;
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
type EvidencePaths = {
|
|
525
|
+
jsonPath: string;
|
|
526
|
+
markdownPath: string;
|
|
527
|
+
htmlPath: string;
|
|
528
|
+
exportedAt: string;
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
function fileBytes(path: string): number | null {
|
|
532
|
+
if (!existsSync(path)) return null;
|
|
533
|
+
try {
|
|
534
|
+
return statSync(path).size;
|
|
535
|
+
} catch {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function evidenceFileSummary(kind: EvidenceFileKind, path: string): EvidenceFileSummary {
|
|
541
|
+
return {
|
|
542
|
+
kind,
|
|
543
|
+
path,
|
|
544
|
+
exists: existsSync(path),
|
|
545
|
+
bytes: fileBytes(path),
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function evidenceFiles(evidence: EvidencePaths): EvidenceFileSummary[] {
|
|
550
|
+
return [
|
|
551
|
+
evidenceFileSummary("json", evidence.jsonPath),
|
|
552
|
+
evidenceFileSummary("markdown", evidence.markdownPath),
|
|
553
|
+
evidenceFileSummary("html", evidence.htmlPath),
|
|
554
|
+
];
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function readEvidenceFile(path: string): Buffer | null {
|
|
558
|
+
if (!existsSync(path)) return null;
|
|
559
|
+
try {
|
|
560
|
+
return readFileSync(path);
|
|
561
|
+
} catch {
|
|
562
|
+
return null;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
async function exportEvidenceZip(
|
|
567
|
+
ownerKind: "run" | "group",
|
|
568
|
+
ownerId: string,
|
|
569
|
+
evidence: EvidencePaths,
|
|
570
|
+
): Promise<Record<string, unknown>> {
|
|
571
|
+
const zip = new JSZip();
|
|
572
|
+
const sourceFiles = evidenceFiles(evidence);
|
|
573
|
+
const includedFiles: EvidenceFileSummary[] = [];
|
|
574
|
+
|
|
575
|
+
for (const file of sourceFiles) {
|
|
576
|
+
const content = readEvidenceFile(file.path);
|
|
577
|
+
const included = content !== null;
|
|
578
|
+
includedFiles.push({ ...file, included });
|
|
579
|
+
if (included) {
|
|
580
|
+
zip.file(`${file.kind}.${file.kind === "markdown" ? "md" : file.kind}`, content);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
zip.file(
|
|
585
|
+
"manifest.json",
|
|
586
|
+
`${JSON.stringify(
|
|
587
|
+
{
|
|
588
|
+
ownerKind,
|
|
589
|
+
ownerId,
|
|
590
|
+
exportedAt: evidence.exportedAt,
|
|
591
|
+
bundledAt: new Date().toISOString(),
|
|
592
|
+
files: includedFiles,
|
|
593
|
+
},
|
|
594
|
+
null,
|
|
595
|
+
2,
|
|
596
|
+
)}\n`,
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
const zipPath = join(
|
|
600
|
+
dirname(evidence.jsonPath),
|
|
601
|
+
ownerKind === "run" ? "evidence.zip" : "group-evidence.zip",
|
|
602
|
+
);
|
|
603
|
+
const buffer = await zip.generateAsync({ type: "nodebuffer" });
|
|
604
|
+
writeFileSync(zipPath, buffer);
|
|
605
|
+
const zipSummary = evidenceFileSummary("zip", zipPath);
|
|
606
|
+
return {
|
|
607
|
+
ownerKind,
|
|
608
|
+
ownerId,
|
|
609
|
+
bundlePath: zipPath,
|
|
610
|
+
bytes: zipSummary.bytes,
|
|
611
|
+
files: [...includedFiles, zipSummary],
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export function getEvidenceFilesImpl(args: { runId: string }): ToolResult {
|
|
616
|
+
const evidence = readInspectorRunEvidence(args.runId);
|
|
617
|
+
if (!evidence.ok) return toolError(evidence.message);
|
|
618
|
+
return textJson({
|
|
619
|
+
runId: args.runId,
|
|
620
|
+
exportedAt: evidence.value.evidence.exportedAt,
|
|
621
|
+
files: evidenceFiles(evidence.value.evidence),
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
export async function exportEvidenceBundleImpl(args: { runId: string }): Promise<ToolResult> {
|
|
626
|
+
const evidence = readInspectorRunEvidence(args.runId);
|
|
627
|
+
if (!evidence.ok) return toolError(evidence.message);
|
|
628
|
+
return textJson(await exportEvidenceZip("run", args.runId, evidence.value.evidence));
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
export function getGroupEvidenceFilesImpl(args: { groupId: string }): ToolResult {
|
|
632
|
+
const evidence = readInspectorGroupEvidence(args.groupId);
|
|
633
|
+
if (!evidence.ok) return toolError(evidence.message);
|
|
634
|
+
return textJson({
|
|
635
|
+
groupId: args.groupId,
|
|
636
|
+
exportedAt: evidence.value.evidence.exportedAt,
|
|
637
|
+
files: evidenceFiles(evidence.value.evidence),
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export async function exportGroupEvidenceBundleImpl(args: {
|
|
642
|
+
groupId: string;
|
|
643
|
+
}): Promise<ToolResult> {
|
|
644
|
+
const evidence = readInspectorGroupEvidence(args.groupId);
|
|
645
|
+
if (!evidence.ok) return toolError(evidence.message);
|
|
646
|
+
return textJson(await exportEvidenceZip("group", args.groupId, evidence.value.evidence));
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
export function getCurrentContextImpl(): ToolResult {
|
|
650
|
+
return textJson(getCurrentContext());
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
export function setCurrentContextImpl(args: InspectorCurrentContextPatch): ToolResult {
|
|
654
|
+
return textJson(setCurrentContext(args));
|
|
655
|
+
}
|
|
656
|
+
|
|
426
657
|
export async function listModelsImpl(callApi: CallApiFn): Promise<ToolResult> {
|
|
427
658
|
const res = await callApi("/api/models");
|
|
428
659
|
if (!res.ok) return toolError(`GET /api/models returned ${res.status}`);
|
|
@@ -540,6 +771,147 @@ export async function testProviderImpl(callApi: CallApiFn, id: string): Promise<
|
|
|
540
771
|
return textJson(await res.json());
|
|
541
772
|
}
|
|
542
773
|
|
|
774
|
+
type ProviderDiagnosticIssue = {
|
|
775
|
+
path: string;
|
|
776
|
+
status: number | null;
|
|
777
|
+
error: string | null;
|
|
778
|
+
durationMs: number | null;
|
|
779
|
+
endpoint: string | null;
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
783
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function stringField(record: Record<string, unknown>, key: string): string | null {
|
|
787
|
+
const value = record[key];
|
|
788
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function numberField(record: Record<string, unknown>, key: string): number | null {
|
|
792
|
+
const value = record[key];
|
|
793
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function firstStringField(record: Record<string, unknown>, keys: string[]): string | null {
|
|
797
|
+
for (const key of keys) {
|
|
798
|
+
const value = stringField(record, key);
|
|
799
|
+
if (value !== null) return value;
|
|
800
|
+
}
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function firstNumberField(record: Record<string, unknown>, keys: string[]): number | null {
|
|
805
|
+
for (const key of keys) {
|
|
806
|
+
const value = numberField(record, key);
|
|
807
|
+
if (value !== null) return value;
|
|
808
|
+
}
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function collectProviderDiagnosticIssues(
|
|
813
|
+
value: unknown,
|
|
814
|
+
path: string,
|
|
815
|
+
issues: ProviderDiagnosticIssue[],
|
|
816
|
+
): void {
|
|
817
|
+
if (Array.isArray(value)) {
|
|
818
|
+
value.forEach((item, index) =>
|
|
819
|
+
collectProviderDiagnosticIssues(item, `${path}[${String(index)}]`, issues),
|
|
820
|
+
);
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (!isRecord(value)) return;
|
|
824
|
+
|
|
825
|
+
const success = value["success"];
|
|
826
|
+
const status = firstNumberField(value, ["status", "responseStatus", "statusCode"]);
|
|
827
|
+
const error = firstStringField(value, ["error", "message", "errorMessage"]);
|
|
828
|
+
if (success === false || error !== null || (status !== null && status >= 400)) {
|
|
829
|
+
issues.push({
|
|
830
|
+
path,
|
|
831
|
+
status,
|
|
832
|
+
error,
|
|
833
|
+
durationMs: firstNumberField(value, ["durationMs", "elapsedMs", "latencyMs"]),
|
|
834
|
+
endpoint: firstStringField(value, ["endpoint", "url", "requestUrl", "baseUrl"]),
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
for (const [key, child] of Object.entries(value)) {
|
|
839
|
+
if (typeof child === "object" && child !== null) {
|
|
840
|
+
collectProviderDiagnosticIssues(child, `${path}.${key}`, issues);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function stringifyUnknown(value: unknown): string {
|
|
846
|
+
try {
|
|
847
|
+
const text = JSON.stringify(value);
|
|
848
|
+
return text === undefined ? "" : text.toLowerCase();
|
|
849
|
+
} catch {
|
|
850
|
+
return "";
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function providerDiagnosticHints(result: unknown): string[] {
|
|
855
|
+
const text = stringifyUnknown(result);
|
|
856
|
+
const hints: string[] = [];
|
|
857
|
+
if (text.includes("timeout") || text.includes("aborted")) {
|
|
858
|
+
hints.push(
|
|
859
|
+
"Provider test timed out or was aborted. Increase Settings > Proxy > Provider test timeout when the provider or model is slow.",
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
if (text.includes("localhost") || text.includes("127.0.0.1")) {
|
|
863
|
+
hints.push(
|
|
864
|
+
"The upstream URL contains localhost. If the agent or provider runs in another container, replace localhost with a reachable host/IP.",
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
if (text.includes("econnrefused") || text.includes("enotfound") || text.includes("network")) {
|
|
868
|
+
hints.push(
|
|
869
|
+
"This looks like a network or DNS failure. Verify the Inspector container/host can reach the provider base URL.",
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
if (
|
|
873
|
+
text.includes("401") ||
|
|
874
|
+
text.includes("403") ||
|
|
875
|
+
text.includes("unauthorized") ||
|
|
876
|
+
text.includes("forbidden")
|
|
877
|
+
) {
|
|
878
|
+
hints.push(
|
|
879
|
+
"This looks like an authentication failure. Check the provider API key and auth header mode.",
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
if (text.includes("404") || text.includes("not found")) {
|
|
883
|
+
hints.push(
|
|
884
|
+
"This looks like a base URL or path mismatch. Check whether the provider endpoint already includes /v1 or needs a vendor-specific path.",
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
if (text.includes("nonstream") || text.includes("non-stream")) {
|
|
888
|
+
hints.push(
|
|
889
|
+
"The failure mentions non-streaming. Compare the OpenAI/Anthropic non-streaming path and payload with the streaming request.",
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
return [...new Set(hints)];
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
export async function diagnoseProviderTestImpl(
|
|
896
|
+
callApi: CallApiFn,
|
|
897
|
+
id: string,
|
|
898
|
+
): Promise<ToolResult> {
|
|
899
|
+
const res = await callApi(`/api/providers/${encodeURIComponent(id)}/test`, {
|
|
900
|
+
method: "POST",
|
|
901
|
+
});
|
|
902
|
+
if (!res.ok) return toolError(`POST /api/providers/${id}/test returned ${res.status}`);
|
|
903
|
+
const result: unknown = await res.json();
|
|
904
|
+
const issues: ProviderDiagnosticIssue[] = [];
|
|
905
|
+
collectProviderDiagnosticIssues(result, "result", issues);
|
|
906
|
+
return textJson({
|
|
907
|
+
providerId: id,
|
|
908
|
+
issueCount: issues.length,
|
|
909
|
+
issues,
|
|
910
|
+
hints: providerDiagnosticHints(result),
|
|
911
|
+
result,
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
|
|
543
915
|
export type CreateSessionKnowledgeArgs = {
|
|
544
916
|
sessionId: string;
|
|
545
917
|
};
|
|
@@ -48,6 +48,7 @@ function buildMarkdown(document: EvidenceDocument): string {
|
|
|
48
48
|
"",
|
|
49
49
|
`- Run ID: ${document.run.id}`,
|
|
50
50
|
`- Session ID: ${document.run.sessionId}`,
|
|
51
|
+
`- Group ID: ${document.run.groupId ?? "n/a"}`,
|
|
51
52
|
`- Status: ${document.run.status}`,
|
|
52
53
|
`- Project: ${document.run.project ?? "n/a"}`,
|
|
53
54
|
`- Agent: ${document.run.agent ?? "n/a"}`,
|
package/src/proxy/runStore.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type UpdateInspectorRunInput,
|
|
13
13
|
} from "../lib/runContract";
|
|
14
14
|
import { getDataDir } from "./dataDir";
|
|
15
|
+
import { addGroupSession, getGroup } from "./groupStore";
|
|
15
16
|
|
|
16
17
|
const RunsFileSchema = z.object({
|
|
17
18
|
runs: z.array(InspectorRunSchema),
|
|
@@ -48,6 +49,51 @@ function normalizeSessionId(input: string | undefined, runId: string): string {
|
|
|
48
49
|
return input.trim();
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
function normalizeGroupId(input: string | null | undefined): string | null {
|
|
53
|
+
if (input === undefined || input === null) return null;
|
|
54
|
+
const normalized = stableId(input);
|
|
55
|
+
return normalized.length > 0 ? normalized : null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function metadataText(metadata: InspectorRun["metadata"], key: string): string | undefined {
|
|
59
|
+
const value = metadata[key];
|
|
60
|
+
if (typeof value !== "string") return undefined;
|
|
61
|
+
const trimmed = value.trim();
|
|
62
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function optionalText(value: string | null): string | undefined {
|
|
66
|
+
return value ?? undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function autoGroupMemberId(runId: string): string {
|
|
70
|
+
return stableId(`run-${runId}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function runHasGroupMember(run: InspectorRun): boolean {
|
|
74
|
+
if (run.groupId === null) return false;
|
|
75
|
+
const group = getGroup(run.groupId);
|
|
76
|
+
if (group === null) return false;
|
|
77
|
+
return group.members.some((member) => member.id === autoGroupMemberId(run.id));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function syncRunGroupMember(run: InspectorRun, createIfMissing: boolean): void {
|
|
81
|
+
if (run.groupId === null) return;
|
|
82
|
+
if (!createIfMissing && !runHasGroupMember(run)) return;
|
|
83
|
+
|
|
84
|
+
addGroupSession(run.groupId, {
|
|
85
|
+
memberId: autoGroupMemberId(run.id),
|
|
86
|
+
sessionId: run.sessionId,
|
|
87
|
+
runId: run.id,
|
|
88
|
+
label: run.title,
|
|
89
|
+
agent: optionalText(run.agent),
|
|
90
|
+
provider: metadataText(run.metadata, "provider") ?? metadataText(run.metadata, "providerName"),
|
|
91
|
+
model: metadataText(run.metadata, "model") ?? metadataText(run.metadata, "modelName"),
|
|
92
|
+
status: run.status,
|
|
93
|
+
metadata: run.metadata,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
51
97
|
function readRuns(): InspectorRun[] {
|
|
52
98
|
const filePath = runsFilePath();
|
|
53
99
|
if (!existsSync(filePath)) return [];
|
|
@@ -83,9 +129,11 @@ export function createRun(input: CreateInspectorRunInput): InspectorRun {
|
|
|
83
129
|
|
|
84
130
|
const timestamp = nowIso();
|
|
85
131
|
const sessionId = normalizeSessionId(parsed?.sessionId, runId);
|
|
132
|
+
const groupId = normalizeGroupId(parsed?.groupId);
|
|
86
133
|
const run: InspectorRun = {
|
|
87
134
|
id: runId,
|
|
88
135
|
sessionId,
|
|
136
|
+
groupId,
|
|
89
137
|
title: parsed?.title ?? `Inspector Run ${runId}`,
|
|
90
138
|
task: parsed?.task ?? null,
|
|
91
139
|
project: parsed?.project ?? null,
|
|
@@ -99,6 +147,7 @@ export function createRun(input: CreateInspectorRunInput): InspectorRun {
|
|
|
99
147
|
};
|
|
100
148
|
|
|
101
149
|
writeRuns([...readRuns(), run]);
|
|
150
|
+
syncRunGroupMember(run, parsed?.sessionId !== undefined);
|
|
102
151
|
return run;
|
|
103
152
|
}
|
|
104
153
|
|
|
@@ -125,32 +174,35 @@ export function updateRunEvidence(
|
|
|
125
174
|
export function updateRun(runId: string, input: UpdateInspectorRunInput): InspectorRun | null {
|
|
126
175
|
const parsed = UpdateInspectorRunInputSchema.parse(input);
|
|
127
176
|
const runs = readRuns();
|
|
128
|
-
|
|
177
|
+
const run = runs.find((candidate) => candidate.id === runId) ?? null;
|
|
129
178
|
const updatedAt = nowIso();
|
|
130
179
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
metadata:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return updatedRun;
|
|
150
|
-
});
|
|
180
|
+
if (run === null) return null;
|
|
181
|
+
|
|
182
|
+
const updatedRun: InspectorRun = {
|
|
183
|
+
...run,
|
|
184
|
+
sessionId:
|
|
185
|
+
parsed.sessionId === undefined ? run.sessionId : normalizeSessionId(parsed.sessionId, run.id),
|
|
186
|
+
groupId: parsed.groupId === undefined ? run.groupId : normalizeGroupId(parsed.groupId),
|
|
187
|
+
title: parsed.title ?? run.title,
|
|
188
|
+
task: parsed.task === undefined ? run.task : parsed.task,
|
|
189
|
+
project: parsed.project === undefined ? run.project : parsed.project,
|
|
190
|
+
agent: parsed.agent === undefined ? run.agent : parsed.agent,
|
|
191
|
+
status: parsed.status ?? run.status,
|
|
192
|
+
tags: parsed.tags ?? run.tags,
|
|
193
|
+
metadata:
|
|
194
|
+
parsed.metadata === undefined ? run.metadata : { ...run.metadata, ...parsed.metadata },
|
|
195
|
+
updatedAt,
|
|
196
|
+
};
|
|
197
|
+
const updatedRuns = runs.map((candidate) => (candidate.id === runId ? updatedRun : candidate));
|
|
151
198
|
|
|
152
|
-
if (updatedRun === null) return null;
|
|
153
199
|
writeRuns(updatedRuns);
|
|
200
|
+
syncRunGroupMember(
|
|
201
|
+
updatedRun,
|
|
202
|
+
parsed.sessionId !== undefined ||
|
|
203
|
+
parsed.groupId !== undefined ||
|
|
204
|
+
updatedRun.sessionId !== updatedRun.id,
|
|
205
|
+
);
|
|
154
206
|
return updatedRun;
|
|
155
207
|
}
|
|
156
208
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as h,j as t}from"./main-CKYe6UUv.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}from"./ProxyViewerContainer-BGqeiYKD.js";const se=[["line",{x1:"5",x2:"19",y1:"9",y2:"9",key:"1nwqeh"}],["line",{x1:"5",x2:"19",y1:"15",y2:"15",key:"g8yjpy"}]],ae=X("equal",se),oe="";function j(e){if(e.length===0)return oe;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 de(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(de(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 ie(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 le(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:ae,accent:"text-muted-foreground/70",bg:"bg-muted/20 hover:bg-muted/30",border:"border-l-muted-foreground/20",label:"EQUAL"}};function ce({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 ue({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(me,{op:e}):null})]})}function me({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 xe({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 fe({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 ve({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 ie(u,g)},[e.apiFormat,e.path,e.rawRequestBody,r.apiFormat,r.path,r.rawRequestBody]),d=h.useMemo(()=>le(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=>{window.navigator.clipboard.writeText(l).then(()=>{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(fe,{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(xe,{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(ce,{ops:l.ops,expanded:a.has(c),onToggle:()=>i(c)},`r${c}`);const u=l.op;return t.jsx(ue,{op:u,idx:c,copiedPath:U,onCopyPath:H,expanded:f.has(c),onToggle:()=>b(c)},`o${c}`)})}):t.jsx(pe,{grouped:d,left:e,right:r})})]})]})]})}function pe({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{ve as CompareDrawer};
|