@tonyclaw/agent-inspector 3.0.29 → 3.0.31
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 +791 -191
- package/.output/nitro.json +1 -1
- package/.output/public/assets/CompareDrawer-DB2KggLE.js +1 -0
- package/.output/public/assets/InspectorPet-CfwDw6kD.js +4108 -0
- package/.output/public/assets/ProxyViewerContainer-PcrO96_F.js +126 -0
- package/.output/public/assets/{ReplayDialog-CctnITbt.js → ReplayDialog-3Pl7EKjp.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-BrN0RBbL.js → RequestAnatomy-BcPjcHLX.js} +1 -1
- package/.output/public/assets/ResponseView-DOx51wV3.js +2 -0
- package/.output/public/assets/StreamingChunkSequence-LkYJWM1A.js +1 -0
- package/.output/public/assets/_sessionId-BSg32vC6.js +1 -0
- package/.output/public/assets/{_sessionId-DKM91udC.js → _sessionId-C0CA68QV.js} +1 -1
- package/.output/public/assets/{index-Bq-nqolG.js → index-BpBUqdAB.js} +1 -1
- package/.output/public/assets/index-D93dCgAQ.js +1 -0
- package/.output/public/assets/{index-DPiESBo2.js → index-FV5sxqx3.js} +6 -6
- package/.output/public/assets/index-VUsF4nhf.js +76 -0
- package/.output/public/assets/index-hyQHZeQI.css +1 -0
- package/.output/public/assets/{json-viewer-CfaDJwx8.js → json-viewer-WctIbAh_.js} +1 -1
- package/.output/public/assets/jszip.min-DnSIoTix.js +2 -0
- package/.output/public/assets/qwen-mMn3f5ul.webp +0 -0
- package/.output/server/_libs/jszip.mjs +35 -9
- package/.output/server/_libs/lucide-react.mjs +80 -80
- package/.output/server/_libs/radix-ui__react-dialog.mjs +3 -3
- package/.output/server/_libs/tanstack__react-router.mjs +1 -1
- package/.output/server/{_sessionId-CwGXPD4C.mjs → _sessionId-BHBK3VbF.mjs} +15 -15
- package/.output/server/_sessionId-CyuJ6Kfl.mjs +81 -0
- package/.output/server/_ssr/{CompareDrawer-R_P1-VNr.mjs → CompareDrawer-27amlv_a.mjs} +138 -103
- package/.output/server/_ssr/InspectorPet-CQnGXfLs.mjs +925 -0
- package/.output/server/_ssr/{ProxyViewerContainer-DUgxRNbj.mjs → ProxyViewerContainer-DlAl0uF-.mjs} +1569 -1646
- package/.output/server/_ssr/{ReplayDialog-De9wKKzS.mjs → ReplayDialog-5m2jtlLa.mjs} +18 -18
- package/.output/server/_ssr/{RequestAnatomy-Db4MxUYo.mjs → RequestAnatomy--1WtQNY9.mjs} +58 -58
- package/.output/server/_ssr/{ResponseView-8rDXOiNB.mjs → ResponseView-CgbqS8sr.mjs} +60 -71
- package/.output/server/_ssr/{StreamingChunkSequence-N9teZHkA.mjs → StreamingChunkSequence-BwpnopWx.mjs} +16 -16
- package/.output/server/_ssr/{index-D4DvByuW.mjs → index-CNVi1TzT.mjs} +15 -15
- package/.output/server/_ssr/index-DMCjH_4V.mjs +81 -0
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-BjoHRbjS.mjs → json-viewer-DNGN2uoe.mjs} +58 -58
- package/.output/server/_ssr/{router-BYlePDj8.mjs → router-CgsqLnk5.mjs} +16007 -12632
- package/.output/server/_tanstack-start-manifest_v-BH5f_kWV.mjs +4 -0
- package/.output/server/index.mjs +99 -71
- package/.output/workers/logFinalizer.worker.js +16908 -0
- package/.output/workers/sessionWorkerEntry.js +16904 -0
- package/README.md +21 -5
- package/package.json +6 -2
- package/src/assets/logos/qwen.webp +0 -0
- package/src/cli.ts +85 -10
- package/src/components/OnboardingBanner.tsx +41 -21
- package/src/components/ProxyViewer.tsx +198 -269
- package/src/components/ProxyViewerContainer.tsx +128 -17
- package/src/components/ecosystem/AgentLabDialog.tsx +166 -19
- package/src/components/errors/SafeErrorBoundary.tsx +201 -0
- package/src/components/pi-agent/PiAgentPanel.tsx +209 -35
- package/src/components/pi-agent/piAgentChatLogic.ts +263 -0
- package/src/components/providers/ImportWizardDialog.tsx +18 -42
- package/src/components/providers/ProviderLogo.tsx +1 -1
- package/src/components/providers/ProvidersPanel.tsx +8 -5
- package/src/components/providers/SettingsDialog.tsx +34 -11
- package/src/components/proxy-viewer/ApplicationBar.tsx +63 -0
- package/src/components/proxy-viewer/CompareDrawer.tsx +92 -49
- package/src/components/proxy-viewer/LiveConnectionStatus.tsx +186 -0
- package/src/components/proxy-viewer/LogEntry.tsx +283 -275
- package/src/components/proxy-viewer/RawExportWarning.tsx +77 -0
- package/src/components/proxy-viewer/SessionLoadProgressBar.tsx +61 -0
- package/src/components/proxy-viewer/ThreadConnector.tsx +16 -1
- package/src/components/proxy-viewer/TurnGroup.tsx +13 -2
- package/src/components/proxy-viewer/accessibility.ts +8 -0
- package/src/components/proxy-viewer/lazy.ts +4 -0
- package/src/components/proxy-viewer/liveConnectionState.ts +158 -0
- package/src/components/proxy-viewer/proxyViewerLogic.ts +10 -0
- package/src/components/proxy-viewer/viewerState.ts +2 -6
- package/src/components/ui/select.tsx +1 -1
- package/src/components/ui/tabs.tsx +3 -3
- package/src/components/ui/transient-toast.tsx +7 -6
- package/src/lib/apiClient.ts +17 -2
- package/src/lib/ecosystemContract.ts +34 -0
- package/src/lib/export-logs.ts +1 -1
- package/src/lib/piAgentContract.ts +80 -0
- package/src/lib/providerContract.ts +70 -4
- package/src/lib/providerImportContract.ts +27 -0
- package/src/lib/providerModelMetadata.ts +16 -7
- package/src/lib/resourceLimits.ts +152 -0
- package/src/lib/safeDiagnostic.ts +38 -0
- package/src/lib/useProviders.ts +4 -4
- package/src/mcp/server.ts +39 -6
- package/src/mcp/toolHandlers.ts +131 -4
- package/src/proxy/chunkStorage.ts +20 -6
- package/src/proxy/config.ts +20 -6
- package/src/proxy/dataDir.ts +3 -0
- package/src/proxy/ecosystemExecutionConfirmation.ts +114 -0
- package/src/proxy/ecosystemExecutionRoute.ts +116 -0
- package/src/proxy/ecosystemTasks.ts +48 -0
- package/src/proxy/evidenceExporter.ts +23 -9
- package/src/proxy/groupEvidenceExporter.ts +26 -5
- package/src/proxy/groupStore.ts +13 -3
- package/src/proxy/handler.ts +103 -20
- package/src/proxy/identityProxy.ts +333 -14
- package/src/proxy/logFinalizer.ts +73 -6
- package/src/proxy/logImportUpload.ts +128 -0
- package/src/proxy/logImporter.ts +321 -70
- package/src/proxy/logIndex.ts +16 -6
- package/src/proxy/logger.ts +294 -41
- package/src/proxy/privateDataPath.ts +183 -0
- package/src/proxy/providerScanStore.ts +87 -0
- package/src/proxy/providerSecretStore.ts +58 -19
- package/src/proxy/providers.ts +175 -58
- package/src/proxy/rawStreamCapture.ts +66 -5
- package/src/proxy/runStore.ts +13 -3
- package/src/proxy/runtimeAdmission.ts +52 -0
- package/src/proxy/runtimeHealth.ts +206 -0
- package/src/proxy/runtimeShutdown.ts +75 -0
- package/src/proxy/sessionArchive.ts +15 -2
- package/src/proxy/sessionProcess.ts +19 -0
- package/src/proxy/sessionRuntime.ts +7 -0
- package/src/proxy/shutdownCoordinator.ts +90 -0
- package/src/proxy/sqliteLogIndex.ts +18 -2
- package/src/proxy/store.ts +18 -2
- package/src/routes/__root.tsx +18 -1
- package/src/routes/api/alerts.summary.ts +12 -8
- package/src/routes/api/alerts.ts +27 -9
- package/src/routes/api/ecosystem.packages.$packageId.help.ts +3 -8
- package/src/routes/api/ecosystem.packages.$packageId.install.ts +3 -8
- package/src/routes/api/ecosystem.packages.$packageId.runner-presets.ts +3 -11
- package/src/routes/api/ecosystem.packages.$packageId.upgrade.ts +3 -8
- package/src/routes/api/ecosystem.recipes.$recipeId.run.ts +3 -8
- package/src/routes/api/health.ts +9 -2
- package/src/routes/api/knowledge.project-context.ts +11 -0
- package/src/routes/api/knowledge.search.ts +33 -2
- package/src/routes/api/logs.$id.body.ts +16 -2
- package/src/routes/api/logs.import.ts +7 -18
- package/src/routes/api/logs.stream.ts +147 -69
- package/src/routes/api/logs.ts +55 -14
- package/src/routes/api/providers.$providerId.model-metadata.ts +22 -4
- package/src/routes/api/providers.$providerId.ts +37 -6
- package/src/routes/api/providers.export.ts +34 -16
- package/src/routes/api/providers.import.ts +42 -8
- package/src/routes/api/providers.scan.ts +13 -8
- package/src/routes/api/providers.ts +36 -5
- package/src/routes/api/runs.ts +12 -12
- package/src/routes/api/sessions.ts +15 -8
- package/src/routes/index.tsx +6 -0
- package/src/routes/livez.ts +13 -0
- package/src/routes/readyz.ts +18 -0
- package/src/routes/session/$sessionId.tsx +6 -0
- package/src/services/piAgent.ts +131 -102
- package/src/services/piAgentRetrieval.ts +1180 -0
- package/styles/globals.css +19 -3
- package/.output/public/assets/CompareDrawer-Cb5ccPeX.js +0 -1
- package/.output/public/assets/ProxyViewerContainer-COBe5GTT.js +0 -4234
- package/.output/public/assets/ResponseView-Cqq87lX_.js +0 -3
- package/.output/public/assets/StreamingChunkSequence-CnTa9mCr.js +0 -1
- package/.output/public/assets/index-DTLNVcgc.js +0 -76
- package/.output/public/assets/index-DliqmwUw.css +0 -1
- package/.output/public/assets/qwen-CONDcHqt.png +0 -0
- package/.output/server/_tanstack-start-manifest_v-DMCA8KK3.mjs +0 -4
- package/src/assets/logos/mcp.png +0 -0
- package/src/assets/logos/qwen.png +0 -0
- package/src/components/ui/mcp-logo.tsx +0 -20
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { safeGetOwnProperty } from "../../lib/objectUtils";
|
|
2
|
+
|
|
3
|
+
export type PiAgentChatRole = "user" | "assistant";
|
|
4
|
+
|
|
5
|
+
export type PiAgentHistoryMessage = {
|
|
6
|
+
role: PiAgentChatRole;
|
|
7
|
+
content: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type PiAgentEvidenceView = {
|
|
11
|
+
source: string;
|
|
12
|
+
id: string;
|
|
13
|
+
logId: number | null;
|
|
14
|
+
sessionId: string | null;
|
|
15
|
+
timestamp: string | null;
|
|
16
|
+
model: string | null;
|
|
17
|
+
providerName: string | null;
|
|
18
|
+
path: string | null;
|
|
19
|
+
status: string | number | null;
|
|
20
|
+
elapsedMs: number | null;
|
|
21
|
+
inputTokens: number | null;
|
|
22
|
+
outputTokens: number | null;
|
|
23
|
+
totalTokens: number | null;
|
|
24
|
+
score: number | null;
|
|
25
|
+
matchedFields: string[];
|
|
26
|
+
excerpt: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type PiAgentMetricView = {
|
|
30
|
+
key: string;
|
|
31
|
+
label: string;
|
|
32
|
+
value: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type PiAgentResponseView = {
|
|
36
|
+
answer: string;
|
|
37
|
+
providerId: string;
|
|
38
|
+
providerName: string;
|
|
39
|
+
model: string;
|
|
40
|
+
protocol: string;
|
|
41
|
+
logCount: number;
|
|
42
|
+
evidence: PiAgentEvidenceView[];
|
|
43
|
+
summaryMetrics: PiAgentMetricView[];
|
|
44
|
+
queryMetrics: PiAgentMetricView[];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type PiAgentConversationMessage = PiAgentHistoryMessage & {
|
|
48
|
+
id: string;
|
|
49
|
+
response: PiAgentResponseView | null;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type PiAgentConversationState = {
|
|
53
|
+
messages: PiAgentConversationMessage[];
|
|
54
|
+
question: string;
|
|
55
|
+
error: string | null;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type PiAgentStarter = {
|
|
59
|
+
id: "reliability" | "performance" | "tokens" | "providers" | "tools";
|
|
60
|
+
label: string;
|
|
61
|
+
question: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const PI_AGENT_STARTERS: readonly PiAgentStarter[] = [
|
|
65
|
+
{
|
|
66
|
+
id: "reliability",
|
|
67
|
+
label: "Reliability",
|
|
68
|
+
question: "Which requests failed, stalled, or look most likely to hang, and why?",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: "performance",
|
|
72
|
+
label: "Performance",
|
|
73
|
+
question: "Summarize latency and identify the slowest requests with supporting evidence.",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "tokens",
|
|
77
|
+
label: "Token usage",
|
|
78
|
+
question: "Where are tokens being spent, and which requests have unusually high usage?",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "providers",
|
|
82
|
+
label: "Providers",
|
|
83
|
+
question: "Compare Provider and model reliability, latency, and token usage in this scope.",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "tools",
|
|
87
|
+
label: "Tool activity",
|
|
88
|
+
question: "Which tools were used most, and where did tool calls fail or produce warnings?",
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const MAX_HISTORY_MESSAGES = 10;
|
|
93
|
+
const MAX_HISTORY_CONTENT_CHARS = 4_000;
|
|
94
|
+
const MAX_METRICS = 12;
|
|
95
|
+
|
|
96
|
+
export function createEmptyPiAgentConversation(): PiAgentConversationState {
|
|
97
|
+
return { messages: [], question: "", error: null };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function buildPiAgentHistory(
|
|
101
|
+
messages: readonly PiAgentConversationMessage[],
|
|
102
|
+
): PiAgentHistoryMessage[] {
|
|
103
|
+
return messages
|
|
104
|
+
.filter((message) => message.content.trim().length > 0)
|
|
105
|
+
.slice(-MAX_HISTORY_MESSAGES)
|
|
106
|
+
.map((message) => ({
|
|
107
|
+
role: message.role,
|
|
108
|
+
content: message.content.trim().slice(0, MAX_HISTORY_CONTENT_CHARS),
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function readString(value: unknown, ...keys: string[]): string | null {
|
|
113
|
+
for (const key of keys) {
|
|
114
|
+
const candidate = safeGetOwnProperty(value, key);
|
|
115
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) return candidate;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function readNumber(value: unknown, ...keys: string[]): number | null {
|
|
121
|
+
for (const key of keys) {
|
|
122
|
+
const candidate = safeGetOwnProperty(value, key);
|
|
123
|
+
if (typeof candidate === "number" && Number.isFinite(candidate)) return candidate;
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function readStringArray(value: unknown, key: string): string[] {
|
|
129
|
+
const candidate = safeGetOwnProperty(value, key);
|
|
130
|
+
if (!Array.isArray(candidate)) return [];
|
|
131
|
+
return candidate.filter((item): item is string => typeof item === "string").slice(0, 8);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function evidenceView(value: unknown): PiAgentEvidenceView | null {
|
|
135
|
+
const source = readString(value, "source") ?? "log";
|
|
136
|
+
const id = readString(value, "id") ?? "";
|
|
137
|
+
const logId = readNumber(value, "logId", "id");
|
|
138
|
+
if (id === "" && logId === null) return null;
|
|
139
|
+
if (logId !== null && (!Number.isInteger(logId) || logId < 0)) return null;
|
|
140
|
+
const inputTokens = readNumber(value, "inputTokens");
|
|
141
|
+
const outputTokens = readNumber(value, "outputTokens");
|
|
142
|
+
const explicitTotal = readNumber(value, "totalTokens", "tokenCount");
|
|
143
|
+
const statusNumber = readNumber(value, "responseStatus", "status");
|
|
144
|
+
const statusString = readString(value, "responseStatus", "status");
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
source,
|
|
148
|
+
id: id !== "" ? id : String(logId),
|
|
149
|
+
logId,
|
|
150
|
+
sessionId: readString(value, "sessionId"),
|
|
151
|
+
timestamp: readString(value, "timestamp"),
|
|
152
|
+
model: readString(value, "model"),
|
|
153
|
+
providerName: readString(value, "providerName", "provider"),
|
|
154
|
+
path: readString(value, "path"),
|
|
155
|
+
status: statusNumber ?? statusString,
|
|
156
|
+
elapsedMs: readNumber(value, "elapsedMs", "latencyMs"),
|
|
157
|
+
inputTokens,
|
|
158
|
+
outputTokens,
|
|
159
|
+
totalTokens:
|
|
160
|
+
explicitTotal ??
|
|
161
|
+
(inputTokens === null && outputTokens === null
|
|
162
|
+
? null
|
|
163
|
+
: (inputTokens ?? 0) + (outputTokens ?? 0)),
|
|
164
|
+
score: readNumber(value, "score", "relevanceScore"),
|
|
165
|
+
matchedFields: readStringArray(value, "matchedFields"),
|
|
166
|
+
excerpt: readString(value, "excerpt", "snippet") ?? "No excerpt available.",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function humanizeMetricKey(key: string): string {
|
|
171
|
+
const spaced = key
|
|
172
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
173
|
+
.replace(/[_-]+/g, " ")
|
|
174
|
+
.trim();
|
|
175
|
+
return spaced.length === 0 ? "Metric" : `${spaced[0]?.toUpperCase() ?? ""}${spaced.slice(1)}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function metricValue(value: unknown): string | null {
|
|
179
|
+
if (typeof value === "number" && Number.isFinite(value)) return value.toLocaleString();
|
|
180
|
+
if (typeof value === "string" && value.trim().length > 0) return value;
|
|
181
|
+
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
182
|
+
if (Array.isArray(value)) {
|
|
183
|
+
const items = value
|
|
184
|
+
.filter(
|
|
185
|
+
(item): item is string | number =>
|
|
186
|
+
typeof item === "string" || (typeof item === "number" && Number.isFinite(item)),
|
|
187
|
+
)
|
|
188
|
+
.slice(0, 6);
|
|
189
|
+
return items.length > 0 ? items.join(", ") : null;
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function objectMetricEntries(value: object): [string, unknown][] {
|
|
195
|
+
return Object.getOwnPropertyNames(value).map((key) => [key, safeGetOwnProperty(value, key)]);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function metricViews(value: unknown): PiAgentMetricView[] {
|
|
199
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return [];
|
|
200
|
+
const entries: PiAgentMetricView[] = [];
|
|
201
|
+
|
|
202
|
+
for (const [key, rawValue] of objectMetricEntries(value)) {
|
|
203
|
+
const directValue = metricValue(rawValue);
|
|
204
|
+
if (directValue !== null) {
|
|
205
|
+
entries.push({ key, label: humanizeMetricKey(key), value: directValue });
|
|
206
|
+
if (entries.length >= MAX_METRICS) break;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (rawValue === null || typeof rawValue !== "object" || Array.isArray(rawValue)) continue;
|
|
210
|
+
for (const [nestedKey, nestedValue] of objectMetricEntries(rawValue)) {
|
|
211
|
+
const formatted = metricValue(nestedValue);
|
|
212
|
+
if (formatted === null) continue;
|
|
213
|
+
const metricKey = `${key}.${nestedKey}`;
|
|
214
|
+
entries.push({
|
|
215
|
+
key: metricKey,
|
|
216
|
+
label: `${humanizeMetricKey(key)} ${humanizeMetricKey(nestedKey).toLowerCase()}`,
|
|
217
|
+
value: formatted,
|
|
218
|
+
});
|
|
219
|
+
if (entries.length >= MAX_METRICS) break;
|
|
220
|
+
}
|
|
221
|
+
if (entries.length >= MAX_METRICS) break;
|
|
222
|
+
}
|
|
223
|
+
return entries;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function normalizePiAgentResponse(value: unknown): PiAgentResponseView | null {
|
|
227
|
+
const answer = readString(value, "answer");
|
|
228
|
+
const providerId = readString(value, "providerId");
|
|
229
|
+
const providerName = readString(value, "providerName");
|
|
230
|
+
const model = readString(value, "model");
|
|
231
|
+
const protocol = readString(value, "protocol");
|
|
232
|
+
const logCount = readNumber(value, "logCount");
|
|
233
|
+
if (
|
|
234
|
+
answer === null ||
|
|
235
|
+
providerId === null ||
|
|
236
|
+
providerName === null ||
|
|
237
|
+
model === null ||
|
|
238
|
+
protocol === null ||
|
|
239
|
+
logCount === null
|
|
240
|
+
) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const rawEvidence = safeGetOwnProperty(value, "evidence");
|
|
245
|
+
const evidence = Array.isArray(rawEvidence)
|
|
246
|
+
? rawEvidence
|
|
247
|
+
.map(evidenceView)
|
|
248
|
+
.filter((item): item is PiAgentEvidenceView => item !== null)
|
|
249
|
+
.slice(0, 12)
|
|
250
|
+
: [];
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
answer,
|
|
254
|
+
providerId,
|
|
255
|
+
providerName,
|
|
256
|
+
model,
|
|
257
|
+
protocol,
|
|
258
|
+
logCount,
|
|
259
|
+
evidence,
|
|
260
|
+
summaryMetrics: metricViews(safeGetOwnProperty(value, "summary")),
|
|
261
|
+
queryMetrics: metricViews(safeGetOwnProperty(value, "queryStats")),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
@@ -3,27 +3,14 @@ import { z } from "zod";
|
|
|
3
3
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
|
|
4
4
|
import { Button } from "../ui/button";
|
|
5
5
|
import { fetchJsonWithTimeout } from "../../lib/apiClient";
|
|
6
|
+
import {
|
|
7
|
+
ProviderScanResponseSchema,
|
|
8
|
+
type ScannedProviderDto,
|
|
9
|
+
} from "../../lib/providerImportContract";
|
|
6
10
|
import { ClientLogo } from "../clients/ClientLogo";
|
|
7
11
|
|
|
8
12
|
import { Loader2, Download, AlertCircle } from "lucide-react";
|
|
9
13
|
|
|
10
|
-
const ExternalProviderSchema = z.object({
|
|
11
|
-
name: z.string(),
|
|
12
|
-
apiKey: z.string(),
|
|
13
|
-
format: z.enum(["anthropic", "openai"]),
|
|
14
|
-
anthropicBaseUrl: z.string(),
|
|
15
|
-
openaiBaseUrl: z.string(),
|
|
16
|
-
openaiResponsesBaseUrl: z.string().optional().default(""),
|
|
17
|
-
models: z.array(z.string()),
|
|
18
|
-
sourceTool: z.enum(["claude-code", "opencode", "mimo-code"]),
|
|
19
|
-
alreadyExists: z.boolean(),
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const ScanResponseSchema = z.object({
|
|
23
|
-
providers: z.array(ExternalProviderSchema),
|
|
24
|
-
warnings: z.array(z.string()).optional(),
|
|
25
|
-
});
|
|
26
|
-
|
|
27
14
|
const ImportResponseSchema = z.object({
|
|
28
15
|
success: z.boolean().optional(),
|
|
29
16
|
imported: z.number().optional(),
|
|
@@ -46,7 +33,8 @@ export function ImportWizardDialog({
|
|
|
46
33
|
}: ImportWizardDialogProps): JSX.Element {
|
|
47
34
|
const [scanning, setScanning] = useState(false);
|
|
48
35
|
const [scanError, setScanError] = useState<string | null>(null);
|
|
49
|
-
const [
|
|
36
|
+
const [scanId, setScanId] = useState<string | null>(null);
|
|
37
|
+
const [providers, setProviders] = useState<ScannedProviderDto[]>([]);
|
|
50
38
|
const [warnings, setWarnings] = useState<string[]>([]);
|
|
51
39
|
const [selected, setSelected] = useState<Set<number>>(new Set());
|
|
52
40
|
const [importing, setImporting] = useState(false);
|
|
@@ -61,6 +49,7 @@ export function ImportWizardDialog({
|
|
|
61
49
|
setScanning(true);
|
|
62
50
|
setScanError(null);
|
|
63
51
|
setWarnings([]);
|
|
52
|
+
setScanId(null);
|
|
64
53
|
setProviders([]);
|
|
65
54
|
setSelected(new Set());
|
|
66
55
|
setImportResult(null);
|
|
@@ -68,13 +57,14 @@ export function ImportWizardDialog({
|
|
|
68
57
|
|
|
69
58
|
void fetchJsonWithTimeout(
|
|
70
59
|
"/api/providers/scan",
|
|
71
|
-
|
|
60
|
+
ProviderScanResponseSchema,
|
|
72
61
|
PROVIDER_IMPORT_TIMEOUT_MS,
|
|
73
|
-
|
|
62
|
+
{ method: "POST" },
|
|
74
63
|
(response) => `Scan failed (${String(response.status)})`,
|
|
75
64
|
)
|
|
76
65
|
.then((data) => {
|
|
77
66
|
if (scanRequestIdRef.current !== requestId) return;
|
|
67
|
+
setScanId(data.scanId);
|
|
78
68
|
setProviders(data.providers);
|
|
79
69
|
setWarnings(data.warnings ?? []);
|
|
80
70
|
const indices = new Set<number>();
|
|
@@ -122,7 +112,7 @@ export function ImportWizardDialog({
|
|
|
122
112
|
|
|
123
113
|
const importSelected = useCallback(() => {
|
|
124
114
|
const toImport = providers.filter((_, i) => selected.has(i));
|
|
125
|
-
if (toImport.length === 0) return;
|
|
115
|
+
if (toImport.length === 0 || scanId === null) return;
|
|
126
116
|
|
|
127
117
|
const requestId = importRequestIdRef.current + 1;
|
|
128
118
|
importRequestIdRef.current = requestId;
|
|
@@ -130,21 +120,6 @@ export function ImportWizardDialog({
|
|
|
130
120
|
setImportError(null);
|
|
131
121
|
setImportResult(null);
|
|
132
122
|
|
|
133
|
-
// Convert ExternalProvider[] to ProviderConfig shape for import
|
|
134
|
-
const now = new Date().toISOString();
|
|
135
|
-
const providersPayload = toImport.map((p) => ({
|
|
136
|
-
id: window.crypto.randomUUID(),
|
|
137
|
-
name: p.name,
|
|
138
|
-
apiKey: p.apiKey,
|
|
139
|
-
format: p.format,
|
|
140
|
-
anthropicBaseUrl: p.anthropicBaseUrl,
|
|
141
|
-
openaiBaseUrl: p.openaiBaseUrl,
|
|
142
|
-
openaiResponsesBaseUrl: p.openaiResponsesBaseUrl,
|
|
143
|
-
models: p.models,
|
|
144
|
-
createdAt: now,
|
|
145
|
-
updatedAt: now,
|
|
146
|
-
}));
|
|
147
|
-
|
|
148
123
|
void fetchJsonWithTimeout(
|
|
149
124
|
"/api/providers/import",
|
|
150
125
|
ImportResponseSchema,
|
|
@@ -152,7 +127,10 @@ export function ImportWizardDialog({
|
|
|
152
127
|
{
|
|
153
128
|
method: "POST",
|
|
154
129
|
headers: { "Content-Type": "application/json" },
|
|
155
|
-
body: JSON.stringify({
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
scanId,
|
|
132
|
+
selectionIds: toImport.map((provider) => provider.selectionId),
|
|
133
|
+
}),
|
|
156
134
|
},
|
|
157
135
|
(response) => `Import failed (${String(response.status)})`,
|
|
158
136
|
)
|
|
@@ -183,7 +161,7 @@ export function ImportWizardDialog({
|
|
|
183
161
|
if (importRequestIdRef.current !== requestId) return;
|
|
184
162
|
setImporting(false);
|
|
185
163
|
});
|
|
186
|
-
}, [providers, selected, onImportComplete]);
|
|
164
|
+
}, [providers, scanId, selected, onImportComplete]);
|
|
187
165
|
|
|
188
166
|
const hasSelectable = providers.some((p) => !p.alreadyExists);
|
|
189
167
|
|
|
@@ -226,7 +204,7 @@ export function ImportWizardDialog({
|
|
|
226
204
|
{!scanning &&
|
|
227
205
|
providers.map((p, i) => (
|
|
228
206
|
<label
|
|
229
|
-
key={
|
|
207
|
+
key={p.selectionId}
|
|
230
208
|
className={`flex items-start gap-3 p-3 border rounded-md ${p.alreadyExists ? "" : "cursor-pointer hover:bg-muted/50"}`}
|
|
231
209
|
>
|
|
232
210
|
{!p.alreadyExists && (
|
|
@@ -257,9 +235,7 @@ export function ImportWizardDialog({
|
|
|
257
235
|
: p.openaiBaseUrl}
|
|
258
236
|
</div>
|
|
259
237
|
<div className="text-xs text-muted-foreground mt-0.5 font-mono">
|
|
260
|
-
{p.
|
|
261
|
-
? `${p.apiKey.slice(0, 4)}••••${p.apiKey.slice(-4)}`
|
|
262
|
-
: "••••"}
|
|
238
|
+
{p.apiKeyHint}
|
|
263
239
|
</div>
|
|
264
240
|
</div>
|
|
265
241
|
</label>
|
|
@@ -5,7 +5,7 @@ import OpenAILogoSvg from "../../assets/logos/openai.svg";
|
|
|
5
5
|
import DeepSeekLogoSvg from "../../assets/logos/deepseek.svg";
|
|
6
6
|
import MiniMaxLogoSvg from "../../assets/logos/minimax.jpeg";
|
|
7
7
|
import AlibabaLogoSvg from "../../assets/logos/alibaba.svg";
|
|
8
|
-
import QwenLogoSvg from "../../assets/logos/qwen.
|
|
8
|
+
import QwenLogoSvg from "../../assets/logos/qwen.webp";
|
|
9
9
|
import ZhipuAILogoSvg from "../../assets/logos/zhipuai.svg";
|
|
10
10
|
|
|
11
11
|
export type Provider =
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "../../lib/apiClient";
|
|
16
16
|
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
17
17
|
import {
|
|
18
|
-
|
|
18
|
+
RedactedProviderDtoSchema,
|
|
19
19
|
type ProviderConfig,
|
|
20
20
|
type ProviderModelMetadata,
|
|
21
21
|
} from "../../lib/providerContract";
|
|
@@ -41,7 +41,7 @@ const ImportResponseSchema = z.object({
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
const ModelMetadataRefreshResponseSchema = z.object({
|
|
44
|
-
provider:
|
|
44
|
+
provider: RedactedProviderDtoSchema,
|
|
45
45
|
imported: z.number(),
|
|
46
46
|
matchedModels: z.array(z.string()),
|
|
47
47
|
missingModels: z.array(z.string()),
|
|
@@ -385,7 +385,7 @@ export function ProvidersPanel({
|
|
|
385
385
|
try {
|
|
386
386
|
const newProvider = await fetchJsonWithTimeout(
|
|
387
387
|
"/api/providers",
|
|
388
|
-
|
|
388
|
+
RedactedProviderDtoSchema,
|
|
389
389
|
PROVIDER_WRITE_TIMEOUT_MS,
|
|
390
390
|
{
|
|
391
391
|
method: "POST",
|
|
@@ -413,7 +413,7 @@ export function ProvidersPanel({
|
|
|
413
413
|
try {
|
|
414
414
|
const updated = await fetchJsonWithTimeout(
|
|
415
415
|
`/api/providers/${editingProvider.id}`,
|
|
416
|
-
|
|
416
|
+
RedactedProviderDtoSchema,
|
|
417
417
|
PROVIDER_WRITE_TIMEOUT_MS,
|
|
418
418
|
{
|
|
419
419
|
method: "PUT",
|
|
@@ -563,7 +563,10 @@ export function ProvidersPanel({
|
|
|
563
563
|
|
|
564
564
|
return (
|
|
565
565
|
<div className="space-y-4">
|
|
566
|
-
<div
|
|
566
|
+
<div
|
|
567
|
+
data-provider-toolbar="true"
|
|
568
|
+
className="sticky top-0 z-10 flex min-w-0 flex-wrap items-center justify-start gap-2 rounded-[8px] bg-background/95 px-1 py-1 backdrop-blur sm:justify-end"
|
|
569
|
+
>
|
|
567
570
|
<Button
|
|
568
571
|
variant="outline"
|
|
569
572
|
size="sm"
|
|
@@ -17,6 +17,29 @@ import {
|
|
|
17
17
|
type TimeDisplayFormat,
|
|
18
18
|
} from "../../lib/runtimeConfig";
|
|
19
19
|
|
|
20
|
+
export const SETTINGS_SECTIONS = [
|
|
21
|
+
{ value: "providers", label: "Providers" },
|
|
22
|
+
{ value: "proxy", label: "Proxy" },
|
|
23
|
+
{ value: "storage", label: "Storage" },
|
|
24
|
+
{ value: "mcp", label: "MCP" },
|
|
25
|
+
{ value: "onboarding", label: "Onboarding" },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export function ResponsiveSettingsTabs(): JSX.Element {
|
|
29
|
+
return (
|
|
30
|
+
<TabsList
|
|
31
|
+
aria-label="Settings sections"
|
|
32
|
+
className="grid w-full grid-cols-3 justify-stretch gap-1 bg-muted/35 p-1 group-data-[orientation=horizontal]/tabs:h-auto sm:grid-cols-5"
|
|
33
|
+
>
|
|
34
|
+
{SETTINGS_SECTIONS.map((section) => (
|
|
35
|
+
<TabsTrigger key={section.value} value={section.value} className="min-w-0 w-full">
|
|
36
|
+
{section.label}
|
|
37
|
+
</TabsTrigger>
|
|
38
|
+
))}
|
|
39
|
+
</TabsList>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
20
43
|
export function SettingsDialog(): JSX.Element {
|
|
21
44
|
const [open, setOpen] = useState(false);
|
|
22
45
|
const [activeTab, setActiveTab] = useState("providers");
|
|
@@ -74,21 +97,19 @@ export function SettingsDialog(): JSX.Element {
|
|
|
74
97
|
<span className="sr-only">Settings</span>
|
|
75
98
|
</Button>
|
|
76
99
|
</DialogTrigger>
|
|
77
|
-
<DialogContent className="flex max-h-[
|
|
100
|
+
<DialogContent className="flex max-h-[calc(100vh-1rem)] w-[calc(100%-1rem)] max-w-2xl flex-col overflow-hidden p-4 sm:max-h-[80vh] sm:p-6">
|
|
78
101
|
<DialogHeader>
|
|
79
102
|
<DialogTitle>Settings</DialogTitle>
|
|
80
103
|
</DialogHeader>
|
|
81
104
|
|
|
82
|
-
<Tabs
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<TabsTrigger value="onboarding">Onboarding</TabsTrigger>
|
|
89
|
-
</TabsList>
|
|
105
|
+
<Tabs
|
|
106
|
+
value={activeTab}
|
|
107
|
+
onValueChange={setActiveTab}
|
|
108
|
+
className="min-h-0 min-w-0 flex-1 overflow-hidden"
|
|
109
|
+
>
|
|
110
|
+
<ResponsiveSettingsTabs />
|
|
90
111
|
|
|
91
|
-
<div className="mt-4 flex-1 overflow-x-hidden overflow-y-auto pr-2">
|
|
112
|
+
<div className="mt-4 min-w-0 flex-1 overflow-x-hidden overflow-y-auto pr-1 sm:pr-2">
|
|
92
113
|
<TabsContent value="providers">
|
|
93
114
|
<ProvidersPanel
|
|
94
115
|
externalProviders={providers}
|
|
@@ -283,7 +304,9 @@ function CopyableSetupValue({
|
|
|
283
304
|
<div className="min-w-0 rounded-md border border-border/45 bg-muted/15 px-3 py-2 transition-colors hover:bg-muted/25">
|
|
284
305
|
<div className="mb-1 text-xs font-medium text-muted-foreground">{label}</div>
|
|
285
306
|
<div className="flex min-w-0 items-center gap-2">
|
|
286
|
-
<code className="min-w-0 flex-1 truncate font-mono text-xs text-foreground"
|
|
307
|
+
<code className="min-w-0 flex-1 truncate font-mono text-xs text-foreground" title={value}>
|
|
308
|
+
{value}
|
|
309
|
+
</code>
|
|
287
310
|
<Button
|
|
288
311
|
type="button"
|
|
289
312
|
variant="ghost"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type JSX, type ReactNode } from "react";
|
|
2
|
+
import packageJson from "../../../package.json";
|
|
3
|
+
import { CrabLogo } from "../ui/crab-logo";
|
|
4
|
+
import { LiveConnectionBadge } from "./LiveConnectionStatus";
|
|
5
|
+
import type { LiveConnectionState } from "./liveConnectionState";
|
|
6
|
+
|
|
7
|
+
export function ApplicationBar({
|
|
8
|
+
liveConnection,
|
|
9
|
+
alertsAction,
|
|
10
|
+
settingsAction,
|
|
11
|
+
}: {
|
|
12
|
+
liveConnection: LiveConnectionState | undefined;
|
|
13
|
+
alertsAction: ReactNode;
|
|
14
|
+
settingsAction: ReactNode;
|
|
15
|
+
}): JSX.Element {
|
|
16
|
+
return (
|
|
17
|
+
<header
|
|
18
|
+
data-application-bar="true"
|
|
19
|
+
className="border-border/70 bg-background/95 sticky top-0 z-50 shrink-0 border-b px-3 py-2 shadow-[0_10px_30px_rgba(0,0,0,0.14)] backdrop-blur lg:px-4"
|
|
20
|
+
>
|
|
21
|
+
<div className="grid min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 gap-y-2 sm:grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)]">
|
|
22
|
+
<a
|
|
23
|
+
href="/"
|
|
24
|
+
aria-label="Agent Inspector home"
|
|
25
|
+
className="flex min-w-0 items-center gap-2 rounded-md focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none"
|
|
26
|
+
>
|
|
27
|
+
<CrabLogo className="size-7 shrink-0 text-amber-500" />
|
|
28
|
+
<span className="min-w-0">
|
|
29
|
+
<span className="block truncate text-sm font-bold leading-4">Agent Inspector</span>
|
|
30
|
+
<span className="hidden truncate text-[10px] font-medium text-muted-foreground md:block">
|
|
31
|
+
Local First. Evidence First.
|
|
32
|
+
</span>
|
|
33
|
+
</span>
|
|
34
|
+
<span className="hidden shrink-0 font-mono text-[10px] text-muted-foreground lg:inline">
|
|
35
|
+
v{packageJson.version}
|
|
36
|
+
</span>
|
|
37
|
+
</a>
|
|
38
|
+
|
|
39
|
+
<div className="order-3 col-span-2 flex min-w-0 justify-start sm:order-2 sm:col-span-1 sm:justify-center">
|
|
40
|
+
{liveConnection === undefined ? (
|
|
41
|
+
<span
|
|
42
|
+
data-connection-state="stored"
|
|
43
|
+
className="border-border/70 bg-muted/50 inline-flex h-7 max-w-full items-center gap-1.5 rounded-full border px-2.5 text-xs font-medium text-muted-foreground"
|
|
44
|
+
>
|
|
45
|
+
<span className="size-1.5 shrink-0 rounded-full bg-slate-400" />
|
|
46
|
+
Stored session
|
|
47
|
+
</span>
|
|
48
|
+
) : (
|
|
49
|
+
<LiveConnectionBadge state={liveConnection} />
|
|
50
|
+
)}
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<nav
|
|
54
|
+
aria-label="Application actions"
|
|
55
|
+
className="order-2 flex shrink-0 items-center justify-end gap-1 sm:order-3"
|
|
56
|
+
>
|
|
57
|
+
{alertsAction}
|
|
58
|
+
{settingsAction}
|
|
59
|
+
</nav>
|
|
60
|
+
</div>
|
|
61
|
+
</header>
|
|
62
|
+
);
|
|
63
|
+
}
|