@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
|
@@ -1,11 +1,88 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
+
export const PiAgentHistoryMessageSchema = z.object({
|
|
4
|
+
role: z.enum(["user", "assistant"]),
|
|
5
|
+
content: z.string().trim().min(1).max(4_000),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export const PiAgentEvidenceSourceSchema = z.enum([
|
|
9
|
+
"log",
|
|
10
|
+
"knowledge",
|
|
11
|
+
"alert",
|
|
12
|
+
"run",
|
|
13
|
+
"group",
|
|
14
|
+
"provider",
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
export const PiAgentCountEntrySchema = z.object({
|
|
18
|
+
value: z.string(),
|
|
19
|
+
count: z.number().int().nonnegative(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const PiAgentSummarySchema = z.object({
|
|
23
|
+
matchedLogs: z.number().int().nonnegative(),
|
|
24
|
+
knowledgeMatched: z.number().int().nonnegative(),
|
|
25
|
+
failures: z.number().int().nonnegative(),
|
|
26
|
+
pending: z.number().int().nonnegative(),
|
|
27
|
+
sessions: z.number().int().nonnegative(),
|
|
28
|
+
models: z.array(PiAgentCountEntrySchema),
|
|
29
|
+
providers: z.array(PiAgentCountEntrySchema),
|
|
30
|
+
latency: z.object({
|
|
31
|
+
count: z.number().int().nonnegative(),
|
|
32
|
+
minMs: z.number().nullable(),
|
|
33
|
+
maxMs: z.number().nullable(),
|
|
34
|
+
averageMs: z.number().nullable(),
|
|
35
|
+
}),
|
|
36
|
+
tokens: z.object({
|
|
37
|
+
input: z.number().int().nonnegative(),
|
|
38
|
+
output: z.number().int().nonnegative(),
|
|
39
|
+
total: z.number().int().nonnegative(),
|
|
40
|
+
}),
|
|
41
|
+
sourceCounts: z.record(PiAgentEvidenceSourceSchema, z.number().int().nonnegative()),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const PiAgentQueryStatsSchema = z.object({
|
|
45
|
+
scannedLogs: z.number().int().nonnegative(),
|
|
46
|
+
hydratedLogs: z.number().int().nonnegative(),
|
|
47
|
+
matchedLogs: z.number().int().nonnegative(),
|
|
48
|
+
scannedKnowledge: z.number().int().nonnegative(),
|
|
49
|
+
matchedKnowledge: z.number().int().nonnegative(),
|
|
50
|
+
returnedEvidence: z.number().int().nonnegative(),
|
|
51
|
+
truncated: z.boolean(),
|
|
52
|
+
durationMs: z.number().nonnegative(),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export const PiAgentEvidenceSchema = z.object({
|
|
56
|
+
source: PiAgentEvidenceSourceSchema,
|
|
57
|
+
id: z.string(),
|
|
58
|
+
logId: z.number().int().positive().optional(),
|
|
59
|
+
knowledgeId: z.string().optional(),
|
|
60
|
+
logIds: z.array(z.number().int().positive()).optional(),
|
|
61
|
+
timestamp: z.string().optional(),
|
|
62
|
+
sessionId: z.string().optional(),
|
|
63
|
+
model: z.string().optional(),
|
|
64
|
+
providerName: z.string().optional(),
|
|
65
|
+
path: z.string().optional(),
|
|
66
|
+
status: z.union([z.number(), z.string()]).nullable().optional(),
|
|
67
|
+
elapsedMs: z.number().optional(),
|
|
68
|
+
inputTokens: z.number().int().nonnegative().optional(),
|
|
69
|
+
outputTokens: z.number().int().nonnegative().optional(),
|
|
70
|
+
score: z.number(),
|
|
71
|
+
matchedFields: z.array(z.string()),
|
|
72
|
+
excerpt: z.string(),
|
|
73
|
+
entityType: z.string().optional(),
|
|
74
|
+
title: z.string().optional(),
|
|
75
|
+
knowledgeType: z.string().optional(),
|
|
76
|
+
tags: z.array(z.string()).optional(),
|
|
77
|
+
});
|
|
78
|
+
|
|
3
79
|
export const PiAgentRequestSchema = z.object({
|
|
4
80
|
question: z.string().trim().min(1).max(4_000),
|
|
5
81
|
sessionId: z.string().trim().min(1).optional(),
|
|
6
82
|
providerId: z.string().trim().min(1).optional(),
|
|
7
83
|
model: z.string().trim().min(1).optional(),
|
|
8
84
|
logLimit: z.number().int().min(1).max(80).optional(),
|
|
85
|
+
history: z.array(PiAgentHistoryMessageSchema).max(12).optional(),
|
|
9
86
|
});
|
|
10
87
|
|
|
11
88
|
export const PiAgentResponseSchema = z.object({
|
|
@@ -15,6 +92,9 @@ export const PiAgentResponseSchema = z.object({
|
|
|
15
92
|
model: z.string(),
|
|
16
93
|
protocol: z.enum(["openaiResponses", "openaiChat", "anthropic"]),
|
|
17
94
|
logCount: z.number().int().nonnegative(),
|
|
95
|
+
evidence: z.array(PiAgentEvidenceSchema).optional(),
|
|
96
|
+
summary: PiAgentSummarySchema.optional(),
|
|
97
|
+
queryStats: PiAgentQueryStatsSchema.optional(),
|
|
18
98
|
});
|
|
19
99
|
|
|
20
100
|
export type PiAgentRequest = z.infer<typeof PiAgentRequestSchema>;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
MAX_PROVIDER_MODEL_METADATA,
|
|
4
|
+
MAX_PROVIDER_MODEL_NAME_CHARS,
|
|
5
|
+
MAX_PROVIDER_MODELS,
|
|
6
|
+
} from "./resourceLimits";
|
|
2
7
|
|
|
3
8
|
/**
|
|
4
9
|
* Validates that a string is a well-formed http(s) URL. Rejects non-HTTP
|
|
@@ -20,8 +25,11 @@ function httpUrl(): z.ZodString {
|
|
|
20
25
|
}, "Must be a valid http(s) URL");
|
|
21
26
|
}
|
|
22
27
|
|
|
28
|
+
export const ProviderModelNameSchema = z.string().max(MAX_PROVIDER_MODEL_NAME_CHARS);
|
|
29
|
+
export const ProviderModelsSchema = z.array(ProviderModelNameSchema).max(MAX_PROVIDER_MODELS);
|
|
30
|
+
|
|
23
31
|
export const ProviderModelMetadataSchema = z.object({
|
|
24
|
-
model:
|
|
32
|
+
model: ProviderModelNameSchema.min(1),
|
|
25
33
|
contextWindow: z.number().int().positive().optional(),
|
|
26
34
|
outputLimit: z.number().int().positive().optional(),
|
|
27
35
|
source: z.enum(["manual", "registry"]).optional(),
|
|
@@ -29,6 +37,10 @@ export const ProviderModelMetadataSchema = z.object({
|
|
|
29
37
|
updatedAt: z.string().optional(),
|
|
30
38
|
});
|
|
31
39
|
|
|
40
|
+
export const ProviderModelMetadataListSchema = z
|
|
41
|
+
.array(ProviderModelMetadataSchema)
|
|
42
|
+
.max(MAX_PROVIDER_MODEL_METADATA);
|
|
43
|
+
|
|
32
44
|
/**
|
|
33
45
|
* Persisted provider shape shared by the server store and browser API clients.
|
|
34
46
|
*
|
|
@@ -39,8 +51,8 @@ export const ProviderConfigSchema = z.object({
|
|
|
39
51
|
id: z.string(),
|
|
40
52
|
name: z.string(),
|
|
41
53
|
apiKey: z.string(),
|
|
42
|
-
model:
|
|
43
|
-
models:
|
|
54
|
+
model: ProviderModelNameSchema.optional(),
|
|
55
|
+
models: ProviderModelsSchema.optional().default([]),
|
|
44
56
|
format: z.enum(["anthropic", "openai"]).optional(),
|
|
45
57
|
baseUrl: httpUrl().optional(),
|
|
46
58
|
anthropicBaseUrl: httpUrl().optional(),
|
|
@@ -49,11 +61,65 @@ export const ProviderConfigSchema = z.object({
|
|
|
49
61
|
authHeader: z.enum(["bearer", "x-api-key"]).optional().default("bearer"),
|
|
50
62
|
apiDocsUrl: z.string().optional(),
|
|
51
63
|
modelMetadataUrl: z.string().optional(),
|
|
52
|
-
modelMetadata:
|
|
64
|
+
modelMetadata: ProviderModelMetadataListSchema.optional(),
|
|
53
65
|
source: z.enum(["company", "personal"]).optional(),
|
|
54
66
|
createdAt: z.string(),
|
|
55
67
|
updatedAt: z.string(),
|
|
56
68
|
});
|
|
57
69
|
|
|
70
|
+
export const REDACTED_PROVIDER_API_KEY = "configured";
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Secret-safe provider shape used at every API and tool output boundary.
|
|
74
|
+
*
|
|
75
|
+
* Keeping the marker in the existing `apiKey` field preserves compatibility
|
|
76
|
+
* with older browser clients while making plaintext provider credentials
|
|
77
|
+
* impossible to represent in a validated response.
|
|
78
|
+
*/
|
|
79
|
+
export const RedactedProviderDtoSchema = ProviderConfigSchema.extend({
|
|
80
|
+
apiKey: z.literal(REDACTED_PROVIDER_API_KEY),
|
|
81
|
+
});
|
|
82
|
+
|
|
58
83
|
export type ProviderModelMetadata = z.infer<typeof ProviderModelMetadataSchema>;
|
|
59
84
|
export type ProviderConfig = z.infer<typeof ProviderConfigSchema>;
|
|
85
|
+
export type RedactedProviderDto = z.infer<typeof RedactedProviderDtoSchema>;
|
|
86
|
+
|
|
87
|
+
export type ProviderModelLimitViolation = {
|
|
88
|
+
resource: "provider-models" | "provider-model-metadata";
|
|
89
|
+
limit: number;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
function arrayFieldLength(value: unknown, key: string): number | null {
|
|
93
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
|
|
94
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
95
|
+
if (descriptor === undefined) return null;
|
|
96
|
+
const field: unknown = descriptor.value;
|
|
97
|
+
return Array.isArray(field) ? field.length : null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function getProviderModelLimitViolation(value: unknown): ProviderModelLimitViolation | null {
|
|
101
|
+
const modelCount = arrayFieldLength(value, "models");
|
|
102
|
+
if (modelCount !== null && modelCount > MAX_PROVIDER_MODELS) {
|
|
103
|
+
return { resource: "provider-models", limit: MAX_PROVIDER_MODELS };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const metadataCount = arrayFieldLength(value, "modelMetadata");
|
|
107
|
+
if (metadataCount !== null && metadataCount > MAX_PROVIDER_MODEL_METADATA) {
|
|
108
|
+
return { resource: "provider-model-metadata", limit: MAX_PROVIDER_MODEL_METADATA };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function toRedactedProviderDto(provider: ProviderConfig): RedactedProviderDto {
|
|
115
|
+
return {
|
|
116
|
+
...provider,
|
|
117
|
+
apiKey: REDACTED_PROVIDER_API_KEY,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function toRedactedProviderDtos(
|
|
122
|
+
providers: readonly ProviderConfig[],
|
|
123
|
+
): RedactedProviderDto[] {
|
|
124
|
+
return providers.map(toRedactedProviderDto);
|
|
125
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const ScannedProviderDtoSchema = z.object({
|
|
4
|
+
selectionId: z.string().uuid(),
|
|
5
|
+
name: z.string(),
|
|
6
|
+
apiKeyHint: z.string(),
|
|
7
|
+
format: z.enum(["anthropic", "openai"]),
|
|
8
|
+
anthropicBaseUrl: z.string(),
|
|
9
|
+
openaiBaseUrl: z.string(),
|
|
10
|
+
openaiResponsesBaseUrl: z.string().optional().default(""),
|
|
11
|
+
models: z.array(z.string()),
|
|
12
|
+
sourceTool: z.enum(["claude-code", "opencode", "mimo-code"]),
|
|
13
|
+
alreadyExists: z.boolean(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const ProviderScanResponseSchema = z.object({
|
|
17
|
+
scanId: z.string().uuid(),
|
|
18
|
+
providers: z.array(ScannedProviderDtoSchema),
|
|
19
|
+
warnings: z.array(z.string()).optional(),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const ProviderScanImportRequestSchema = z.object({
|
|
23
|
+
scanId: z.string().uuid(),
|
|
24
|
+
selectionIds: z.array(z.string().uuid()).min(1).max(100),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export type ScannedProviderDto = z.infer<typeof ScannedProviderDtoSchema>;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { ProviderConfig, ProviderModelMetadata } from "./providerContract";
|
|
3
|
+
import { MAX_PROVIDER_MODEL_NAME_CHARS, MAX_PROVIDER_MODELS } from "./resourceLimits";
|
|
3
4
|
|
|
4
5
|
export const ProviderRegistryModelSchema = z.object({
|
|
5
|
-
id: z.string().min(1),
|
|
6
|
-
aliases: z
|
|
6
|
+
id: z.string().min(1).max(MAX_PROVIDER_MODEL_NAME_CHARS),
|
|
7
|
+
aliases: z
|
|
8
|
+
.array(z.string().max(MAX_PROVIDER_MODEL_NAME_CHARS))
|
|
9
|
+
.max(MAX_PROVIDER_MODELS)
|
|
10
|
+
.optional()
|
|
11
|
+
.default([]),
|
|
7
12
|
contextWindow: z.number().int().positive().optional(),
|
|
8
13
|
outputLimit: z.number().int().positive().optional(),
|
|
9
14
|
sourceUrl: z.string().optional(),
|
|
@@ -11,16 +16,20 @@ export const ProviderRegistryModelSchema = z.object({
|
|
|
11
16
|
|
|
12
17
|
export const ProviderRegistryProviderSchema = z.object({
|
|
13
18
|
name: z.string().min(1).optional(),
|
|
14
|
-
aliases: z.array(z.string()).optional().default([]),
|
|
15
|
-
baseUrls: z.array(z.string()).optional().default([]),
|
|
16
|
-
models: z.array(ProviderRegistryModelSchema).min(1),
|
|
19
|
+
aliases: z.array(z.string()).max(MAX_PROVIDER_MODELS).optional().default([]),
|
|
20
|
+
baseUrls: z.array(z.string()).max(MAX_PROVIDER_MODELS).optional().default([]),
|
|
21
|
+
models: z.array(ProviderRegistryModelSchema).min(1).max(MAX_PROVIDER_MODELS),
|
|
17
22
|
});
|
|
18
23
|
|
|
19
24
|
export const ProviderModelRegistrySchema = z.object({
|
|
20
25
|
version: z.number().int().positive().optional(),
|
|
21
26
|
updatedAt: z.string().optional(),
|
|
22
|
-
providers: z
|
|
23
|
-
|
|
27
|
+
providers: z
|
|
28
|
+
.array(ProviderRegistryProviderSchema)
|
|
29
|
+
.max(MAX_PROVIDER_MODELS)
|
|
30
|
+
.optional()
|
|
31
|
+
.default([]),
|
|
32
|
+
models: z.array(ProviderRegistryModelSchema).max(MAX_PROVIDER_MODELS).optional().default([]),
|
|
24
33
|
});
|
|
25
34
|
|
|
26
35
|
export type ProviderRegistryModel = z.infer<typeof ProviderRegistryModelSchema>;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const MAX_REST_LOG_PAGE_SIZE = 200;
|
|
4
|
+
export const MAX_SEARCH_QUERY_CHARS = 512;
|
|
5
|
+
export const MAX_SEARCH_PROJECT_CHARS = 256;
|
|
6
|
+
export const MAX_PROVIDER_MODELS = 64;
|
|
7
|
+
export const MAX_PROVIDER_MODEL_NAME_CHARS = 256;
|
|
8
|
+
export const MAX_PROVIDER_MODEL_METADATA = 64;
|
|
9
|
+
export const MAX_SSE_CLIENTS = 32;
|
|
10
|
+
export const MAX_ACTIVE_PROVIDER_TESTS = 2;
|
|
11
|
+
export const MAX_PROVIDER_TEST_CONCURRENCY = 4;
|
|
12
|
+
|
|
13
|
+
export const ResourceLimitErrorCodeSchema = z.enum([
|
|
14
|
+
"invalid-page-request",
|
|
15
|
+
"search-limit-exceeded",
|
|
16
|
+
"provider-model-limit-exceeded",
|
|
17
|
+
"sse-capacity-exceeded",
|
|
18
|
+
"provider-test-capacity-exceeded",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
export const ResourceLimitStatusSchema = z.union([
|
|
22
|
+
z.literal(400),
|
|
23
|
+
z.literal(413),
|
|
24
|
+
z.literal(429),
|
|
25
|
+
z.literal(503),
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
export const ResourceLimitErrorSchema = z.object({
|
|
29
|
+
error: z.string().min(1).max(300),
|
|
30
|
+
code: ResourceLimitErrorCodeSchema,
|
|
31
|
+
status: ResourceLimitStatusSchema,
|
|
32
|
+
resource: z.string().min(1).max(80),
|
|
33
|
+
limit: z.number().int().positive(),
|
|
34
|
+
retryable: z.boolean(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export type ResourceLimitErrorCode = z.infer<typeof ResourceLimitErrorCodeSchema>;
|
|
38
|
+
export type ResourceLimitStatus = z.infer<typeof ResourceLimitStatusSchema>;
|
|
39
|
+
export type ResourceLimitError = z.infer<typeof ResourceLimitErrorSchema>;
|
|
40
|
+
|
|
41
|
+
export type ResourceLimitErrorInput = {
|
|
42
|
+
error: string;
|
|
43
|
+
code: ResourceLimitErrorCode;
|
|
44
|
+
status: ResourceLimitStatus;
|
|
45
|
+
resource: string;
|
|
46
|
+
limit: number;
|
|
47
|
+
retryable: boolean;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export function createResourceLimitError(input: ResourceLimitErrorInput): ResourceLimitError {
|
|
51
|
+
return ResourceLimitErrorSchema.parse(input);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function resourceLimitResponse(
|
|
55
|
+
input: ResourceLimitErrorInput & { retryAfterSeconds?: number },
|
|
56
|
+
): Response {
|
|
57
|
+
const body = createResourceLimitError(input);
|
|
58
|
+
const headers = new Headers({ "cache-control": "no-store" });
|
|
59
|
+
if (input.retryAfterSeconds !== undefined) {
|
|
60
|
+
headers.set("retry-after", String(input.retryAfterSeconds));
|
|
61
|
+
}
|
|
62
|
+
return Response.json(body, { status: input.status, headers });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type BoundedIntegerParameterInput = {
|
|
66
|
+
value: string | null;
|
|
67
|
+
parameter: string;
|
|
68
|
+
fallback: number;
|
|
69
|
+
minimum: number;
|
|
70
|
+
maximum: number;
|
|
71
|
+
resource: string;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type BoundedIntegerParameterResult =
|
|
75
|
+
| { ok: true; value: number }
|
|
76
|
+
| { ok: false; response: Response };
|
|
77
|
+
|
|
78
|
+
export function parseBoundedIntegerParameter(
|
|
79
|
+
input: BoundedIntegerParameterInput,
|
|
80
|
+
): BoundedIntegerParameterResult {
|
|
81
|
+
if (input.value === null) return { ok: true, value: input.fallback };
|
|
82
|
+
|
|
83
|
+
const parsed = Number(input.value);
|
|
84
|
+
if (!Number.isSafeInteger(parsed) || parsed < input.minimum || parsed > input.maximum) {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
response: resourceLimitResponse({
|
|
88
|
+
error: `Query parameter "${input.parameter}" must be an integer between ${String(input.minimum)} and ${String(input.maximum)}.`,
|
|
89
|
+
code: "invalid-page-request",
|
|
90
|
+
status: 400,
|
|
91
|
+
resource: input.resource,
|
|
92
|
+
limit: input.maximum,
|
|
93
|
+
retryable: false,
|
|
94
|
+
}),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return { ok: true, value: parsed };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type CapacityLease = {
|
|
102
|
+
release: () => void;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export type CapacityLimiter = {
|
|
106
|
+
tryAcquire: () => CapacityLease | null;
|
|
107
|
+
activeCount: () => number;
|
|
108
|
+
capacity: number;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export function createCapacityLimiter(maxActive: number): CapacityLimiter {
|
|
112
|
+
const capacity = Number.isSafeInteger(maxActive) && maxActive > 0 ? maxActive : 1;
|
|
113
|
+
let active = 0;
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
capacity,
|
|
117
|
+
activeCount: () => active,
|
|
118
|
+
tryAcquire: () => {
|
|
119
|
+
if (active >= capacity) return null;
|
|
120
|
+
active += 1;
|
|
121
|
+
let released = false;
|
|
122
|
+
return {
|
|
123
|
+
release: () => {
|
|
124
|
+
if (released) return;
|
|
125
|
+
released = true;
|
|
126
|
+
active = Math.max(0, active - 1);
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function runWithConcurrencyLimit(
|
|
134
|
+
tasks: readonly (() => Promise<void>)[],
|
|
135
|
+
maxConcurrent: number,
|
|
136
|
+
): Promise<void> {
|
|
137
|
+
if (tasks.length === 0) return;
|
|
138
|
+
const concurrency = Number.isSafeInteger(maxConcurrent) && maxConcurrent > 0 ? maxConcurrent : 1;
|
|
139
|
+
const workerCount = Math.min(concurrency, tasks.length);
|
|
140
|
+
let nextIndex = 0;
|
|
141
|
+
|
|
142
|
+
const runWorker = async (): Promise<void> => {
|
|
143
|
+
while (nextIndex < tasks.length) {
|
|
144
|
+
const index = nextIndex;
|
|
145
|
+
nextIndex += 1;
|
|
146
|
+
const task = tasks[index];
|
|
147
|
+
if (task !== undefined) await task();
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
await Promise.all(Array.from({ length: workerCount }, runWorker));
|
|
152
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function normalizeScope(scope: string): string {
|
|
2
|
+
const normalized = scope
|
|
3
|
+
.toUpperCase()
|
|
4
|
+
.replaceAll(/[^A-Z0-9]+/g, "-")
|
|
5
|
+
.replaceAll(/^-+|-+$/g, "")
|
|
6
|
+
.slice(0, 24);
|
|
7
|
+
return normalized.length > 0 ? normalized : "UNKNOWN";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function errorKind(error: unknown): string {
|
|
11
|
+
if (error instanceof Error) {
|
|
12
|
+
return error.name;
|
|
13
|
+
}
|
|
14
|
+
return typeof error;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function shortHash(value: string): string {
|
|
18
|
+
let hash = 5381;
|
|
19
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
20
|
+
hash = (hash * 33 + value.charCodeAt(index)) % 2_176_782_336;
|
|
21
|
+
}
|
|
22
|
+
return Math.floor(hash).toString(36).toUpperCase().padStart(6, "0");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates an operator-friendly reference without encoding an error message,
|
|
27
|
+
* stack, captured content, headers, or credentials into the displayed value.
|
|
28
|
+
*/
|
|
29
|
+
export function createSafeDiagnosticReference(
|
|
30
|
+
scope: string,
|
|
31
|
+
error: unknown,
|
|
32
|
+
caughtAt: number = Date.now(),
|
|
33
|
+
): string {
|
|
34
|
+
const safeScope = normalizeScope(scope);
|
|
35
|
+
const timePart = Math.max(0, Math.floor(caughtAt)).toString(36).toUpperCase();
|
|
36
|
+
const fingerprint = shortHash(`${safeScope}:${errorKind(error)}:${timePart}`);
|
|
37
|
+
return `AI-${safeScope}-${timePart}-${fingerprint}`;
|
|
38
|
+
}
|
package/src/lib/useProviders.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import useSWR, { type SWRResponse } from "swr";
|
|
2
2
|
import { fetchJson } from "./apiClient";
|
|
3
|
-
import {
|
|
3
|
+
import { RedactedProviderDtoSchema, type RedactedProviderDto } from "./providerContract";
|
|
4
4
|
|
|
5
|
-
async function fetcher(url: string): Promise<
|
|
5
|
+
async function fetcher(url: string): Promise<RedactedProviderDto[]> {
|
|
6
6
|
return fetchJson(
|
|
7
7
|
url,
|
|
8
|
-
|
|
8
|
+
RedactedProviderDtoSchema.array(),
|
|
9
9
|
undefined,
|
|
10
10
|
(response) => `Failed to fetch ${url}: ${response.status}`,
|
|
11
11
|
);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function useProviders() {
|
|
15
|
-
const response: SWRResponse<
|
|
15
|
+
const response: SWRResponse<RedactedProviderDto[], Error> = useSWR<RedactedProviderDto[], Error>(
|
|
16
16
|
"/api/providers",
|
|
17
17
|
fetcher,
|
|
18
18
|
{
|
package/src/mcp/server.ts
CHANGED
|
@@ -35,6 +35,12 @@ import {
|
|
|
35
35
|
import { InspectorRunStatusSchema, UpdateInspectorRunInputSchema } from "../lib/runContract";
|
|
36
36
|
import { getCurrentPort, setCurrentPort } from "../lib/serverPort";
|
|
37
37
|
import { JsonValueSchema } from "../contracts";
|
|
38
|
+
import {
|
|
39
|
+
MAX_PROVIDER_MODEL_NAME_CHARS,
|
|
40
|
+
MAX_PROVIDER_MODELS,
|
|
41
|
+
MAX_SEARCH_PROJECT_CHARS,
|
|
42
|
+
MAX_SEARCH_QUERY_CHARS,
|
|
43
|
+
} from "../lib/resourceLimits";
|
|
38
44
|
import { getCurrentContext } from "./currentContext";
|
|
39
45
|
import { getMcpModeInfo, isMcpWriteEnabled } from "./mode";
|
|
40
46
|
import {
|
|
@@ -81,6 +87,7 @@ import {
|
|
|
81
87
|
getLogImpl,
|
|
82
88
|
getProviderImpl,
|
|
83
89
|
listGroupsImpl,
|
|
90
|
+
LIMIT_HARD_CAP,
|
|
84
91
|
listKnowledgeCandidatesImpl,
|
|
85
92
|
listLogsImpl,
|
|
86
93
|
listModelsImpl,
|
|
@@ -222,9 +229,15 @@ const AddProviderInputSchema = z
|
|
|
222
229
|
model: z
|
|
223
230
|
.string()
|
|
224
231
|
.min(1)
|
|
232
|
+
.max(MAX_PROVIDER_MODEL_NAME_CHARS)
|
|
225
233
|
.optional()
|
|
226
234
|
.describe("Legacy single-model input. Prefer models for new clients."),
|
|
227
|
-
models: z
|
|
235
|
+
models: z
|
|
236
|
+
.array(z.string().min(1).max(MAX_PROVIDER_MODEL_NAME_CHARS))
|
|
237
|
+
.min(1)
|
|
238
|
+
.max(MAX_PROVIDER_MODELS)
|
|
239
|
+
.optional()
|
|
240
|
+
.describe("Provider model names."),
|
|
228
241
|
anthropicBaseUrl: z
|
|
229
242
|
.string()
|
|
230
243
|
.optional()
|
|
@@ -252,8 +265,17 @@ const UpdateProviderInputSchema = z.object({
|
|
|
252
265
|
name: z.string().min(1).optional(),
|
|
253
266
|
apiKey: z.string().min(1).optional(),
|
|
254
267
|
format: z.enum(["anthropic", "openai"]).optional(),
|
|
255
|
-
model: z
|
|
256
|
-
|
|
268
|
+
model: z
|
|
269
|
+
.string()
|
|
270
|
+
.min(1)
|
|
271
|
+
.max(MAX_PROVIDER_MODEL_NAME_CHARS)
|
|
272
|
+
.optional()
|
|
273
|
+
.describe("Legacy single-model input. Prefer models."),
|
|
274
|
+
models: z
|
|
275
|
+
.array(z.string().min(1).max(MAX_PROVIDER_MODEL_NAME_CHARS))
|
|
276
|
+
.max(MAX_PROVIDER_MODELS)
|
|
277
|
+
.optional()
|
|
278
|
+
.describe("Replace provider model names."),
|
|
257
279
|
baseUrl: z
|
|
258
280
|
.string()
|
|
259
281
|
.min(1)
|
|
@@ -694,6 +716,7 @@ function registerTools(server: McpServer): void {
|
|
|
694
716
|
.number()
|
|
695
717
|
.int()
|
|
696
718
|
.positive()
|
|
719
|
+
.max(LIMIT_HARD_CAP)
|
|
697
720
|
.optional()
|
|
698
721
|
.describe("How many summaries to return. Hard-capped at 5."),
|
|
699
722
|
sessionId: z.string().optional().describe("Filter by session/affinity id."),
|
|
@@ -718,6 +741,7 @@ function registerTools(server: McpServer): void {
|
|
|
718
741
|
inputSchema: z.object({
|
|
719
742
|
query: z
|
|
720
743
|
.string()
|
|
744
|
+
.max(MAX_SEARCH_QUERY_CHARS)
|
|
721
745
|
.optional()
|
|
722
746
|
.describe("Search terms. Empty or omitted returns the newest scanned logs."),
|
|
723
747
|
offset: z.number().int().nonnegative().optional().describe("Skip this many matches."),
|
|
@@ -725,6 +749,7 @@ function registerTools(server: McpServer): void {
|
|
|
725
749
|
.number()
|
|
726
750
|
.int()
|
|
727
751
|
.positive()
|
|
752
|
+
.max(LIMIT_HARD_CAP)
|
|
728
753
|
.optional()
|
|
729
754
|
.describe("How many summaries to return. Hard-capped at 5 by the MCP handler."),
|
|
730
755
|
scanLimit: z
|
|
@@ -1401,8 +1426,12 @@ PATCH-style update: only the fields you supply are changed. Use models to update
|
|
|
1401
1426
|
description:
|
|
1402
1427
|
"Searches knowledge through Inspector. OpenClaw backend details are hidden from the agent.",
|
|
1403
1428
|
inputSchema: z.object({
|
|
1404
|
-
query: z.string().min(1).describe("Knowledge search query."),
|
|
1405
|
-
project: z
|
|
1429
|
+
query: z.string().min(1).max(MAX_SEARCH_QUERY_CHARS).describe("Knowledge search query."),
|
|
1430
|
+
project: z
|
|
1431
|
+
.string()
|
|
1432
|
+
.max(MAX_SEARCH_PROJECT_CHARS)
|
|
1433
|
+
.optional()
|
|
1434
|
+
.describe("Optional project filter."),
|
|
1406
1435
|
}),
|
|
1407
1436
|
},
|
|
1408
1437
|
(args) => safeCall(() => searchKnowledgeImpl(callApi, args)),
|
|
@@ -1415,7 +1444,11 @@ PATCH-style update: only the fields you supply are changed. Use models to update
|
|
|
1415
1444
|
description:
|
|
1416
1445
|
"Returns relevant Inspector knowledge for a project so an agent can seed task context without calling OpenClaw directly.",
|
|
1417
1446
|
inputSchema: z.object({
|
|
1418
|
-
project: z
|
|
1447
|
+
project: z
|
|
1448
|
+
.string()
|
|
1449
|
+
.min(1)
|
|
1450
|
+
.max(MAX_SEARCH_PROJECT_CHARS)
|
|
1451
|
+
.describe("Project name or folder."),
|
|
1419
1452
|
}),
|
|
1420
1453
|
},
|
|
1421
1454
|
(args) => safeCall(() => getProjectContextImpl(callApi, args)),
|