@tonyclaw/agent-inspector 3.0.7 → 3.0.8
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 +245 -171
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-C0Kbsm-Q.js → CompareDrawer-Bth_Ywyt.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-C73EU9Jq.js +106 -0
- package/.output/public/assets/ReplayDialog-C2YYAuTe.js +1 -0
- package/.output/public/assets/{RequestAnatomy-fa0JXy5p.js → RequestAnatomy-8k9dAX2j.js} +1 -1
- package/.output/public/assets/ResponseView-DEL6JcXm.js +3 -0
- package/.output/public/assets/StreamingChunkSequence-DngmoR9j.js +1 -0
- package/.output/public/assets/{_sessionId-D5fYH2C0.js → _sessionId-4vk9RMZ5.js} +1 -1
- package/.output/public/assets/index-DBEB1rHe.css +1 -0
- package/.output/public/assets/{index-BEsqvywM.js → index-DINLvnm9.js} +1 -1
- package/.output/public/assets/index-LPIGiUMP.js +1 -0
- package/.output/public/assets/{json-viewer-B0QRbMSf.js → json-viewer-CH5dvS2Y.js} +1 -1
- package/.output/public/assets/{main-CuKZD7n0.js → main-GWkZn4xy.js} +2 -2
- package/.output/server/{_sessionId-DdBTK3qm.mjs → _sessionId-EgsjdkN7.mjs} +3 -3
- package/.output/server/_ssr/{CompareDrawer-m82cVZ73.mjs → CompareDrawer-CaM8FYuD.mjs} +2 -2
- package/.output/server/_ssr/{ProxyViewerContainer-7geuf78f.mjs → ProxyViewerContainer-EJIogcGd.mjs} +545 -309
- package/.output/server/_ssr/{ReplayDialog-B8RlWLLH.mjs → ReplayDialog-BPpJ2-q8.mjs} +58 -23
- package/.output/server/_ssr/{RequestAnatomy-BE7pmwpv.mjs → RequestAnatomy-DeQAWnZx.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-D-sDsdQr.mjs → ResponseView-Dshm8iQ7.mjs} +2 -2
- package/.output/server/_ssr/{StreamingChunkSequence-DU_hThJr.mjs → StreamingChunkSequence-32yIU2hn.mjs} +30 -20
- package/.output/server/_ssr/{index-D0sV8UbE.mjs → index-XRcx9blU.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-CaDh1zrw.mjs → json-viewer-C0aX5rqC.mjs} +2 -2
- package/.output/server/_ssr/{router-Zh2iZ_u0.mjs → router-BI-Mvxz9.mjs} +441 -172
- package/.output/server/_tanstack-start-manifest_v-jKScklL_.mjs +4 -0
- package/.output/server/index.mjs +71 -71
- package/package.json +1 -1
- package/src/assets/IDE/opencode.svg +1 -1
- package/src/assets/IDE/xiaomimimo.svg +1 -1
- package/src/cli/detect-tools.ts +2 -27
- package/src/cli/templates/codex-skill-onboard.ts +1 -1
- package/src/cli/templates/skill-onboard.ts +2 -3
- package/src/cli.ts +9 -86
- package/src/components/ProxyViewer.tsx +55 -8
- package/src/components/ProxyViewerContainer.tsx +30 -19
- package/src/components/clients/ClientLogo.tsx +22 -2
- package/src/components/ecosystem/AgentLabDialog.tsx +204 -3
- package/src/components/groups/GroupsDialog.tsx +28 -40
- package/src/components/providers/ImportWizardDialog.tsx +51 -101
- package/src/components/providers/ProvidersPanel.tsx +82 -68
- package/src/components/providers/SettingsDialog.tsx +11 -12
- package/src/components/proxy-viewer/AgentTraceSummary.tsx +15 -23
- package/src/components/proxy-viewer/ReplayDialog.tsx +65 -22
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +33 -20
- package/src/lib/apiClient.ts +43 -10
- package/src/lib/ecosystemContract.ts +32 -0
- package/src/lib/export-logs.ts +37 -3
- package/src/proxy/ecosystemRegistry.ts +244 -0
- package/src/proxy/ecosystemTasks.ts +153 -0
- package/src/proxy/platformCommands.ts +212 -0
- package/src/proxy/socketTracker.ts +84 -143
- package/src/routes/api/ecosystem.packages.$packageId.help.ts +16 -0
- package/src/routes/api/ecosystem.packages.$packageId.install.ts +16 -0
- package/src/routes/api/ecosystem.packages.$packageId.runner-presets.ts +19 -0
- package/src/routes/api/ecosystem.packages.$packageId.upgrade.ts +16 -0
- package/src/routes/api/ecosystem.packages.ts +1 -248
- package/src/routes/api/ecosystem.tasks.$taskId.ts +16 -0
- package/src/routes/api/ecosystem.tasks.ts +12 -0
- package/.output/public/assets/ProxyViewerContainer-p-rpYyiF.js +0 -106
- package/.output/public/assets/ReplayDialog-CM3dhP8v.js +0 -1
- package/.output/public/assets/ResponseView-Bq6sC7Ay.js +0 -3
- package/.output/public/assets/StreamingChunkSequence-CGOun3Df.js +0 -1
- package/.output/public/assets/index-C29Olfzt.js +0 -1
- package/.output/public/assets/index-DgANE0r3.css +0 -1
- package/.output/server/_tanstack-start-manifest_v-LCE-2AaF.mjs +0 -4
|
@@ -1,66 +1,12 @@
|
|
|
1
|
-
import { type JSX, useState, useCallback, useEffect } from "react";
|
|
1
|
+
import { type JSX, useState, useCallback, useEffect, useRef } from "react";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
|
|
4
4
|
import { Button } from "../ui/button";
|
|
5
|
+
import { fetchJsonWithTimeout } from "../../lib/apiClient";
|
|
6
|
+
import { ClientLogo } from "../clients/ClientLogo";
|
|
5
7
|
|
|
6
8
|
import { Loader2, Download, AlertCircle } from "lucide-react";
|
|
7
9
|
|
|
8
|
-
function ClaudeCodeIcon(): JSX.Element {
|
|
9
|
-
return (
|
|
10
|
-
<svg
|
|
11
|
-
fill="currentColor"
|
|
12
|
-
fillRule="evenodd"
|
|
13
|
-
viewBox="0 0 24 24"
|
|
14
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
15
|
-
className="size-6 shrink-0"
|
|
16
|
-
>
|
|
17
|
-
<title>Claude Code</title>
|
|
18
|
-
<path
|
|
19
|
-
clipRule="evenodd"
|
|
20
|
-
d="M20.998 10.949H24v3.102h-3v3.028h-1.487V20H18v-2.921h-1.487V20H15v-2.921H9V20H7.488v-2.921H6V20H4.487v-2.921H3V14.05H0V10.95h3V5h17.998v5.949zM6 10.949h1.488V8.102H6v2.847zm10.51 0H18V8.102h-1.49v2.847z"
|
|
21
|
-
/>
|
|
22
|
-
</svg>
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function OpenCodeIcon(): JSX.Element {
|
|
27
|
-
return (
|
|
28
|
-
<svg
|
|
29
|
-
fill="currentColor"
|
|
30
|
-
fillRule="evenodd"
|
|
31
|
-
viewBox="0 0 24 24"
|
|
32
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
33
|
-
className="size-6 shrink-0"
|
|
34
|
-
>
|
|
35
|
-
<title>OpenCode</title>
|
|
36
|
-
<path d="M16 6H8v12h8V6zm4 16H4V2h16v20z" />
|
|
37
|
-
</svg>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function MiMoCodeIcon(): JSX.Element {
|
|
42
|
-
return (
|
|
43
|
-
<svg
|
|
44
|
-
fill="currentColor"
|
|
45
|
-
viewBox="0 0 24 24"
|
|
46
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
47
|
-
className="size-6 shrink-0"
|
|
48
|
-
>
|
|
49
|
-
<title>MiMo Code</title>
|
|
50
|
-
<text
|
|
51
|
-
x="12"
|
|
52
|
-
y="18"
|
|
53
|
-
textAnchor="middle"
|
|
54
|
-
fontSize="16"
|
|
55
|
-
fontWeight="800"
|
|
56
|
-
fontFamily="system-ui, sans-serif"
|
|
57
|
-
>
|
|
58
|
-
M
|
|
59
|
-
</text>
|
|
60
|
-
</svg>
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
10
|
const ExternalProviderSchema = z.object({
|
|
65
11
|
name: z.string(),
|
|
66
12
|
apiKey: z.string(),
|
|
@@ -85,11 +31,7 @@ const ImportResponseSchema = z.object({
|
|
|
85
31
|
errors: z.array(z.string()).optional(),
|
|
86
32
|
});
|
|
87
33
|
|
|
88
|
-
const
|
|
89
|
-
"claude-code": ClaudeCodeIcon,
|
|
90
|
-
opencode: OpenCodeIcon,
|
|
91
|
-
"mimo-code": MiMoCodeIcon,
|
|
92
|
-
};
|
|
34
|
+
const PROVIDER_IMPORT_TIMEOUT_MS = 20_000;
|
|
93
35
|
|
|
94
36
|
type ImportWizardDialogProps = {
|
|
95
37
|
open: boolean;
|
|
@@ -110,8 +52,12 @@ export function ImportWizardDialog({
|
|
|
110
52
|
const [importing, setImporting] = useState(false);
|
|
111
53
|
const [importResult, setImportResult] = useState<string | null>(null);
|
|
112
54
|
const [importError, setImportError] = useState<string | null>(null);
|
|
55
|
+
const scanRequestIdRef = useRef(0);
|
|
56
|
+
const importRequestIdRef = useRef(0);
|
|
113
57
|
|
|
114
58
|
const scan = useCallback(() => {
|
|
59
|
+
const requestId = scanRequestIdRef.current + 1;
|
|
60
|
+
scanRequestIdRef.current = requestId;
|
|
115
61
|
setScanning(true);
|
|
116
62
|
setScanError(null);
|
|
117
63
|
setWarnings([]);
|
|
@@ -120,33 +66,29 @@ export function ImportWizardDialog({
|
|
|
120
66
|
setImportResult(null);
|
|
121
67
|
setImportError(null);
|
|
122
68
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
setWarnings(parsed.data.warnings ?? []);
|
|
138
|
-
// Pre-select non-existing providers
|
|
139
|
-
const indices = new Set<number>();
|
|
140
|
-
parsed.data.providers.forEach((p, i) => {
|
|
141
|
-
if (!p.alreadyExists) indices.add(i);
|
|
142
|
-
});
|
|
143
|
-
setSelected(indices);
|
|
69
|
+
void fetchJsonWithTimeout(
|
|
70
|
+
"/api/providers/scan",
|
|
71
|
+
ScanResponseSchema,
|
|
72
|
+
PROVIDER_IMPORT_TIMEOUT_MS,
|
|
73
|
+
undefined,
|
|
74
|
+
(response) => `Scan failed (${String(response.status)})`,
|
|
75
|
+
)
|
|
76
|
+
.then((data) => {
|
|
77
|
+
if (scanRequestIdRef.current !== requestId) return;
|
|
78
|
+
setProviders(data.providers);
|
|
79
|
+
setWarnings(data.warnings ?? []);
|
|
80
|
+
const indices = new Set<number>();
|
|
81
|
+
data.providers.forEach((provider, index) => {
|
|
82
|
+
if (!provider.alreadyExists) indices.add(index);
|
|
144
83
|
});
|
|
84
|
+
setSelected(indices);
|
|
145
85
|
})
|
|
146
86
|
.catch((err: unknown) => {
|
|
87
|
+
if (scanRequestIdRef.current !== requestId) return;
|
|
147
88
|
setScanError(err instanceof Error ? err.message : String(err));
|
|
148
89
|
})
|
|
149
90
|
.finally(() => {
|
|
91
|
+
if (scanRequestIdRef.current !== requestId) return;
|
|
150
92
|
setScanning(false);
|
|
151
93
|
});
|
|
152
94
|
}, []);
|
|
@@ -158,6 +100,14 @@ export function ImportWizardDialog({
|
|
|
158
100
|
}
|
|
159
101
|
}, [open, scan]);
|
|
160
102
|
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (open) return;
|
|
105
|
+
scanRequestIdRef.current += 1;
|
|
106
|
+
importRequestIdRef.current += 1;
|
|
107
|
+
setScanning(false);
|
|
108
|
+
setImporting(false);
|
|
109
|
+
}, [open]);
|
|
110
|
+
|
|
161
111
|
const toggleProvider = useCallback((index: number) => {
|
|
162
112
|
setSelected((prev) => {
|
|
163
113
|
const next = new Set(prev);
|
|
@@ -174,6 +124,8 @@ export function ImportWizardDialog({
|
|
|
174
124
|
const toImport = providers.filter((_, i) => selected.has(i));
|
|
175
125
|
if (toImport.length === 0) return;
|
|
176
126
|
|
|
127
|
+
const requestId = importRequestIdRef.current + 1;
|
|
128
|
+
importRequestIdRef.current = requestId;
|
|
177
129
|
setImporting(true);
|
|
178
130
|
setImportError(null);
|
|
179
131
|
setImportResult(null);
|
|
@@ -193,19 +145,19 @@ export function ImportWizardDialog({
|
|
|
193
145
|
updatedAt: now,
|
|
194
146
|
}));
|
|
195
147
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
148
|
+
void fetchJsonWithTimeout(
|
|
149
|
+
"/api/providers/import",
|
|
150
|
+
ImportResponseSchema,
|
|
151
|
+
PROVIDER_IMPORT_TIMEOUT_MS,
|
|
152
|
+
{
|
|
153
|
+
method: "POST",
|
|
154
|
+
headers: { "Content-Type": "application/json" },
|
|
155
|
+
body: JSON.stringify({ providers: providersPayload }),
|
|
156
|
+
},
|
|
157
|
+
(response) => `Import failed (${String(response.status)})`,
|
|
158
|
+
)
|
|
159
|
+
.then((result) => {
|
|
160
|
+
if (importRequestIdRef.current !== requestId) return;
|
|
209
161
|
// Mark imported providers as already existing so they can't be re-selected
|
|
210
162
|
setProviders((prev) =>
|
|
211
163
|
prev.map((p, i) => (selected.has(i) ? { ...p, alreadyExists: true } : p)),
|
|
@@ -224,9 +176,11 @@ export function ImportWizardDialog({
|
|
|
224
176
|
onImportComplete();
|
|
225
177
|
})
|
|
226
178
|
.catch((err: unknown) => {
|
|
179
|
+
if (importRequestIdRef.current !== requestId) return;
|
|
227
180
|
setImportError(err instanceof Error ? err.message : String(err));
|
|
228
181
|
})
|
|
229
182
|
.finally(() => {
|
|
183
|
+
if (importRequestIdRef.current !== requestId) return;
|
|
230
184
|
setImporting(false);
|
|
231
185
|
});
|
|
232
186
|
}, [providers, selected, onImportComplete]);
|
|
@@ -286,11 +240,7 @@ export function ImportWizardDialog({
|
|
|
286
240
|
)}
|
|
287
241
|
<div className="flex-1 min-w-0">
|
|
288
242
|
<div className="flex items-center justify-between">
|
|
289
|
-
{
|
|
290
|
-
const LogoComponent = sourceLogoMap[p.sourceTool];
|
|
291
|
-
if (LogoComponent === undefined) return null;
|
|
292
|
-
return <LogoComponent />;
|
|
293
|
-
})()}
|
|
243
|
+
<ClientLogo client={p.sourceTool} className="size-7 rounded-[8px]" />
|
|
294
244
|
{p.alreadyExists && (
|
|
295
245
|
<span className="text-[10px] text-muted-foreground">Already added</span>
|
|
296
246
|
)}
|
|
@@ -7,7 +7,13 @@ import { ImportWizardDialog } from "./ImportWizardDialog";
|
|
|
7
7
|
import { ConfirmDialog } from "../ui/confirm-dialog";
|
|
8
8
|
import { ProviderCard } from "./ProviderCard";
|
|
9
9
|
import { ProviderForm } from "./ProviderForm";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
ApiTimeoutError,
|
|
12
|
+
fetchJsonWithTimeout,
|
|
13
|
+
fetchWithTimeout,
|
|
14
|
+
parseJsonResponse,
|
|
15
|
+
readApiError,
|
|
16
|
+
} from "../../lib/apiClient";
|
|
11
17
|
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
12
18
|
import {
|
|
13
19
|
ProviderConfigSchema,
|
|
@@ -44,6 +50,13 @@ const ModelMetadataRefreshResponseSchema = z.object({
|
|
|
44
50
|
});
|
|
45
51
|
|
|
46
52
|
const NETWORK_ERROR_MESSAGE = "Network error: could not reach the server. Is the proxy running?";
|
|
53
|
+
const CONFIG_PATH_FETCH_TIMEOUT_MS = 5_000;
|
|
54
|
+
const PROVIDER_TEST_LOG_TIMEOUT_MS = 5_000;
|
|
55
|
+
const PROVIDER_WRITE_TIMEOUT_MS = 15_000;
|
|
56
|
+
const PROVIDER_DELETE_TIMEOUT_MS = 10_000;
|
|
57
|
+
const PROVIDER_EXPORT_TIMEOUT_MS = 20_000;
|
|
58
|
+
const PROVIDER_IMPORT_TIMEOUT_MS = 20_000;
|
|
59
|
+
const PROVIDER_METADATA_TIMEOUT_MS = 30_000;
|
|
47
60
|
|
|
48
61
|
type ProviderFormData = {
|
|
49
62
|
name: string;
|
|
@@ -88,7 +101,7 @@ async function persistProviderTestLog(providerId: string, results: TestResults):
|
|
|
88
101
|
return;
|
|
89
102
|
}
|
|
90
103
|
try {
|
|
91
|
-
await
|
|
104
|
+
await fetchWithTimeout(`/api/providers/${providerId}/test/log`, PROVIDER_TEST_LOG_TIMEOUT_MS, {
|
|
92
105
|
method: "POST",
|
|
93
106
|
headers: { "Content-Type": "application/json" },
|
|
94
107
|
body: JSON.stringify(results),
|
|
@@ -176,11 +189,12 @@ export function ProvidersPanel({
|
|
|
176
189
|
useEffect(() => {
|
|
177
190
|
void (async () => {
|
|
178
191
|
try {
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
192
|
+
const data = await fetchJsonWithTimeout(
|
|
193
|
+
"/api/config/paths",
|
|
194
|
+
ConfigPathsResponseSchema,
|
|
195
|
+
CONFIG_PATH_FETCH_TIMEOUT_MS,
|
|
196
|
+
);
|
|
197
|
+
setConfigPath(data.providerConfig);
|
|
184
198
|
} catch {
|
|
185
199
|
// Ignore
|
|
186
200
|
}
|
|
@@ -270,34 +284,33 @@ export function ProvidersPanel({
|
|
|
270
284
|
}, 1000);
|
|
271
285
|
|
|
272
286
|
try {
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
`HTTP ${res.status}: ${res.statusText}`,
|
|
286
|
-
"server_error",
|
|
287
|
-
),
|
|
288
|
-
);
|
|
289
|
-
}
|
|
287
|
+
const results = await fetchJsonWithTimeout(
|
|
288
|
+
`/api/providers/${providerId}/test`,
|
|
289
|
+
ProviderTestResultsSchema,
|
|
290
|
+
timeoutSeconds * 1000 + 1000,
|
|
291
|
+
{
|
|
292
|
+
method: "POST",
|
|
293
|
+
signal: controller.signal,
|
|
294
|
+
},
|
|
295
|
+
(response) => `HTTP ${response.status}: ${response.statusText}`,
|
|
296
|
+
);
|
|
297
|
+
updateTestResults(providerId, results);
|
|
298
|
+
await persistProviderTestLog(providerId, results);
|
|
290
299
|
} catch (err) {
|
|
291
300
|
// Check if this was an abort (timeout)
|
|
292
301
|
const isAbort = err instanceof Error && err.name === "AbortError";
|
|
293
|
-
|
|
302
|
+
const isTimeout = err instanceof ApiTimeoutError;
|
|
303
|
+
if (isAbort || isTimeout) {
|
|
294
304
|
updateTestResults(
|
|
295
305
|
providerId,
|
|
296
306
|
createFailedProviderTestResults("Request timed out", "timeout"),
|
|
297
307
|
);
|
|
308
|
+
} else if (err instanceof Error) {
|
|
309
|
+
updateTestResults(
|
|
310
|
+
providerId,
|
|
311
|
+
createFailedProviderTestResults(err.message, "server_error"),
|
|
312
|
+
);
|
|
298
313
|
}
|
|
299
|
-
// If it's not an abort error, the test results won't be updated
|
|
300
|
-
// which means the previous results will persist (or it will show "Not configured" reset state)
|
|
301
314
|
} finally {
|
|
302
315
|
// Always clear the countdown and testing state, even if an error occurs
|
|
303
316
|
clearInterval(intervalId);
|
|
@@ -323,18 +336,17 @@ export function ProvidersPanel({
|
|
|
323
336
|
});
|
|
324
337
|
try {
|
|
325
338
|
const payload = url !== undefined && url.trim() !== "" ? { url } : {};
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const data = await parseJsonResponse(res, ModelMetadataRefreshResponseSchema);
|
|
339
|
+
const data = await fetchJsonWithTimeout(
|
|
340
|
+
`/api/providers/${providerId}/model-metadata`,
|
|
341
|
+
ModelMetadataRefreshResponseSchema,
|
|
342
|
+
PROVIDER_METADATA_TIMEOUT_MS,
|
|
343
|
+
{
|
|
344
|
+
method: "POST",
|
|
345
|
+
headers: { "Content-Type": "application/json" },
|
|
346
|
+
body: JSON.stringify(payload),
|
|
347
|
+
},
|
|
348
|
+
() => "Failed to refresh model metadata",
|
|
349
|
+
);
|
|
338
350
|
if (data.imported === 0 && showError) {
|
|
339
351
|
setError("No matching model metadata found for this provider.");
|
|
340
352
|
} else {
|
|
@@ -343,9 +355,9 @@ export function ProvidersPanel({
|
|
|
343
355
|
triggerHighlight(data.provider.id);
|
|
344
356
|
refreshProviders();
|
|
345
357
|
return data.imported > 0;
|
|
346
|
-
} catch {
|
|
358
|
+
} catch (err) {
|
|
347
359
|
if (showError) {
|
|
348
|
-
setError(NETWORK_ERROR_MESSAGE);
|
|
360
|
+
setError(err instanceof Error ? err.message : NETWORK_ERROR_MESSAGE);
|
|
349
361
|
}
|
|
350
362
|
return false;
|
|
351
363
|
} finally {
|
|
@@ -362,16 +374,17 @@ export function ProvidersPanel({
|
|
|
362
374
|
function handleAddProvider(data: ProviderFormData): void {
|
|
363
375
|
void (async () => {
|
|
364
376
|
try {
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
377
|
+
const newProvider = await fetchJsonWithTimeout(
|
|
378
|
+
"/api/providers",
|
|
379
|
+
ProviderConfigSchema,
|
|
380
|
+
PROVIDER_WRITE_TIMEOUT_MS,
|
|
381
|
+
{
|
|
382
|
+
method: "POST",
|
|
383
|
+
headers: { "Content-Type": "application/json" },
|
|
384
|
+
body: JSON.stringify(createProviderPayload(data)),
|
|
385
|
+
},
|
|
386
|
+
() => "Failed to add provider",
|
|
387
|
+
);
|
|
375
388
|
setShowForm(false);
|
|
376
389
|
triggerHighlight(newProvider.id);
|
|
377
390
|
if (data.modelMetadataUrl !== undefined && data.modelMetadataUrl.trim() !== "") {
|
|
@@ -379,8 +392,8 @@ export function ProvidersPanel({
|
|
|
379
392
|
}
|
|
380
393
|
refreshProviders();
|
|
381
394
|
await runTest(newProvider.id);
|
|
382
|
-
} catch {
|
|
383
|
-
setError(NETWORK_ERROR_MESSAGE);
|
|
395
|
+
} catch (err) {
|
|
396
|
+
setError(err instanceof Error ? err.message : NETWORK_ERROR_MESSAGE);
|
|
384
397
|
}
|
|
385
398
|
})();
|
|
386
399
|
}
|
|
@@ -389,16 +402,17 @@ export function ProvidersPanel({
|
|
|
389
402
|
if (!editingProvider) return;
|
|
390
403
|
void (async () => {
|
|
391
404
|
try {
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
405
|
+
const updated = await fetchJsonWithTimeout(
|
|
406
|
+
`/api/providers/${editingProvider.id}`,
|
|
407
|
+
ProviderConfigSchema,
|
|
408
|
+
PROVIDER_WRITE_TIMEOUT_MS,
|
|
409
|
+
{
|
|
410
|
+
method: "PUT",
|
|
411
|
+
headers: { "Content-Type": "application/json" },
|
|
412
|
+
body: JSON.stringify(createProviderPayload(data)),
|
|
413
|
+
},
|
|
414
|
+
() => "Failed to update provider",
|
|
415
|
+
);
|
|
402
416
|
setEditingProvider(undefined);
|
|
403
417
|
triggerHighlight(updated.id);
|
|
404
418
|
if (data.modelMetadataUrl !== undefined && data.modelMetadataUrl.trim() !== "") {
|
|
@@ -415,8 +429,8 @@ export function ProvidersPanel({
|
|
|
415
429
|
if (criticalFieldsChanged) {
|
|
416
430
|
await runTest(updated.id);
|
|
417
431
|
}
|
|
418
|
-
} catch {
|
|
419
|
-
setError(NETWORK_ERROR_MESSAGE);
|
|
432
|
+
} catch (err) {
|
|
433
|
+
setError(err instanceof Error ? err.message : NETWORK_ERROR_MESSAGE);
|
|
420
434
|
}
|
|
421
435
|
})();
|
|
422
436
|
}
|
|
@@ -432,7 +446,7 @@ export function ProvidersPanel({
|
|
|
432
446
|
void (async () => {
|
|
433
447
|
let res: Response;
|
|
434
448
|
try {
|
|
435
|
-
res = await
|
|
449
|
+
res = await fetchWithTimeout(`/api/providers/${providerId}`, PROVIDER_DELETE_TIMEOUT_MS, {
|
|
436
450
|
method: "DELETE",
|
|
437
451
|
});
|
|
438
452
|
} catch {
|
|
@@ -453,7 +467,7 @@ export function ProvidersPanel({
|
|
|
453
467
|
const url = `/api/providers/export${includeKeys ? "?includeKeys=true" : ""}`;
|
|
454
468
|
void (async () => {
|
|
455
469
|
try {
|
|
456
|
-
const res = await
|
|
470
|
+
const res = await fetchWithTimeout(url, PROVIDER_EXPORT_TIMEOUT_MS);
|
|
457
471
|
if (!res.ok) {
|
|
458
472
|
setError("Failed to export providers");
|
|
459
473
|
return;
|
|
@@ -485,7 +499,7 @@ export function ProvidersPanel({
|
|
|
485
499
|
void (async () => {
|
|
486
500
|
try {
|
|
487
501
|
const text = await file.text();
|
|
488
|
-
const res = await
|
|
502
|
+
const res = await fetchWithTimeout("/api/providers/import", PROVIDER_IMPORT_TIMEOUT_MS, {
|
|
489
503
|
method: "POST",
|
|
490
504
|
headers: { "Content-Type": "application/json" },
|
|
491
505
|
body: JSON.stringify(text),
|
|
@@ -9,6 +9,7 @@ import { ProvidersPanel } from "./ProvidersPanel";
|
|
|
9
9
|
import { useProviders } from "../../lib/useProviders";
|
|
10
10
|
import { useStripConfig } from "../../lib/useStripConfig";
|
|
11
11
|
import { copyTextToClipboard } from "../../lib/clipboard";
|
|
12
|
+
import { fetchJsonWithTimeout } from "../../lib/apiClient";
|
|
12
13
|
import {
|
|
13
14
|
MAX_PROVIDER_TEST_TIMEOUT_SECONDS,
|
|
14
15
|
MAX_SLOW_RESPONSE_THRESHOLD_SECONDS,
|
|
@@ -148,6 +149,8 @@ const LogStorageStatsSchema = z.object({
|
|
|
148
149
|
sessionArchiveBytes: z.number(),
|
|
149
150
|
});
|
|
150
151
|
|
|
152
|
+
const STORAGE_STATS_FETCH_TIMEOUT_MS = 10_000;
|
|
153
|
+
|
|
151
154
|
function formatBytes(bytes: number): string {
|
|
152
155
|
if (bytes < 1024) return `${bytes} B`;
|
|
153
156
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
@@ -165,18 +168,14 @@ function StorageSettingsTab(): JSX.Element {
|
|
|
165
168
|
setLoading(true);
|
|
166
169
|
setError(null);
|
|
167
170
|
try {
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
setError("Storage stats response was not valid.");
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
setStats(parsed.data);
|
|
171
|
+
const data = await fetchJsonWithTimeout(
|
|
172
|
+
"/api/logs?stats=1",
|
|
173
|
+
LogStorageStatsSchema,
|
|
174
|
+
STORAGE_STATS_FETCH_TIMEOUT_MS,
|
|
175
|
+
undefined,
|
|
176
|
+
(response) => `Failed to load storage stats: HTTP ${String(response.status)}`,
|
|
177
|
+
);
|
|
178
|
+
setStats(data);
|
|
180
179
|
} catch (err) {
|
|
181
180
|
setError(err instanceof Error ? err.message : String(err));
|
|
182
181
|
} finally {
|
|
@@ -23,7 +23,7 @@ import { z } from "zod";
|
|
|
23
23
|
import type { TimeDisplayFormat } from "../../lib/runtimeConfig";
|
|
24
24
|
import { formatTimestampRange } from "../../lib/timeDisplay";
|
|
25
25
|
import { cn, formatTokens } from "../../lib/utils";
|
|
26
|
-
import {
|
|
26
|
+
import { fetchJsonWithTimeout, fetchWithTimeout } from "../../lib/apiClient";
|
|
27
27
|
import {
|
|
28
28
|
KnowledgeCandidateSchema,
|
|
29
29
|
type KnowledgeCandidate,
|
|
@@ -64,6 +64,10 @@ type CandidateLoadState =
|
|
|
64
64
|
| { status: "ready"; error: null }
|
|
65
65
|
| { status: "failed"; error: string };
|
|
66
66
|
|
|
67
|
+
const CANDIDATE_GENERATE_TIMEOUT_MS = 30_000;
|
|
68
|
+
const CANDIDATE_PROMOTE_TIMEOUT_MS = 15_000;
|
|
69
|
+
const CANDIDATE_UPDATE_TIMEOUT_MS = 15_000;
|
|
70
|
+
|
|
67
71
|
type TraceInsightKind = "session" | "tool" | "slow" | "failure" | "candidate";
|
|
68
72
|
|
|
69
73
|
type TraceInsight = {
|
|
@@ -655,19 +659,13 @@ export function AgentTraceSummary({
|
|
|
655
659
|
setCandidateState({ status: "loading", error: null });
|
|
656
660
|
void (async () => {
|
|
657
661
|
try {
|
|
658
|
-
const
|
|
662
|
+
const parsed = await fetchJsonWithTimeout(
|
|
659
663
|
`/api/knowledge/sessions/${encodeURIComponent(scopeId)}/candidates`,
|
|
664
|
+
CandidateResponseSchema,
|
|
665
|
+
CANDIDATE_GENERATE_TIMEOUT_MS,
|
|
660
666
|
{ method: "POST" },
|
|
667
|
+
(response) => `Candidate generation failed with ${String(response.status)}`,
|
|
661
668
|
);
|
|
662
|
-
if (!response.ok) {
|
|
663
|
-
const message = await readApiError(
|
|
664
|
-
response,
|
|
665
|
-
`Candidate generation failed with ${String(response.status)}`,
|
|
666
|
-
);
|
|
667
|
-
setCandidateState({ status: "failed", error: message });
|
|
668
|
-
return;
|
|
669
|
-
}
|
|
670
|
-
const parsed = await parseJsonResponse(response, CandidateResponseSchema);
|
|
671
669
|
setCandidates(parsed.candidates);
|
|
672
670
|
setCandidatesExpanded(parsed.candidates.length > 0);
|
|
673
671
|
setCandidateState(
|
|
@@ -694,8 +692,9 @@ export function AgentTraceSummary({
|
|
|
694
692
|
|
|
695
693
|
void (async () => {
|
|
696
694
|
try {
|
|
697
|
-
const response = await
|
|
695
|
+
const response = await fetchWithTimeout(
|
|
698
696
|
`/api/knowledge/candidates/${encodeURIComponent(candidateId)}/promote`,
|
|
697
|
+
CANDIDATE_PROMOTE_TIMEOUT_MS,
|
|
699
698
|
{ method: "POST" },
|
|
700
699
|
);
|
|
701
700
|
const raw: unknown = await response.json().catch(() => null);
|
|
@@ -749,24 +748,17 @@ export function AgentTraceSummary({
|
|
|
749
748
|
});
|
|
750
749
|
|
|
751
750
|
try {
|
|
752
|
-
const
|
|
751
|
+
const parsed = await fetchJsonWithTimeout(
|
|
753
752
|
`/api/knowledge/candidates/${encodeURIComponent(candidateId)}`,
|
|
753
|
+
CandidateUpdateResponseSchema,
|
|
754
|
+
CANDIDATE_UPDATE_TIMEOUT_MS,
|
|
754
755
|
{
|
|
755
756
|
method: "PATCH",
|
|
756
757
|
headers: { "Content-Type": "application/json" },
|
|
757
758
|
body: JSON.stringify(update),
|
|
758
759
|
},
|
|
760
|
+
(response) => `Candidate update failed with ${String(response.status)}`,
|
|
759
761
|
);
|
|
760
|
-
if (!response.ok) {
|
|
761
|
-
const message = await readApiError(
|
|
762
|
-
response,
|
|
763
|
-
`Candidate update failed with ${String(response.status)}`,
|
|
764
|
-
);
|
|
765
|
-
setCandidateState({ status: "failed", error: message });
|
|
766
|
-
return false;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
const parsed = await parseJsonResponse(response, CandidateUpdateResponseSchema);
|
|
770
762
|
setCandidates((current) =>
|
|
771
763
|
current.map((candidate) =>
|
|
772
764
|
candidate.id === parsed.candidate.id ? parsed.candidate : candidate,
|