@tonyclaw/agent-inspector 3.0.15 → 3.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-UaIxykqz.js → CompareDrawer-Lx-WeJNC.js} +1 -1
- package/.output/public/assets/{ProxyViewerContainer-DlpfcX56.js → ProxyViewerContainer-BNehwMI0.js} +31 -31
- package/.output/public/assets/{ReplayDialog-BFFomU1m.js → ReplayDialog-CADaXFPE.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-DKQJBt_c.js → RequestAnatomy-CGoL8ACh.js} +1 -1
- package/.output/public/assets/{ResponseView-D7yotmne.js → ResponseView-BSwM1dCH.js} +2 -2
- package/.output/public/assets/{StreamingChunkSequence-Da-6WNFG.js → StreamingChunkSequence-qhWtSx3D.js} +1 -1
- package/.output/public/assets/{_sessionId-ByTXLS-w.js → _sessionId-2uq_HB8p.js} +1 -1
- package/.output/public/assets/index-BHXN1Fsx.css +1 -0
- package/.output/public/assets/{index-BrM8t5mc.js → index-DPc2wzgW.js} +1 -1
- package/.output/public/assets/index-T0u1foYL.js +1 -0
- package/.output/public/assets/{json-viewer-DyjXQdVu.js → json-viewer-uIwLntt-.js} +1 -1
- package/.output/public/assets/{main-Zv_gjZMb.js → main-DuFBHsus.js} +7 -7
- package/.output/server/_libs/@opentelemetry/semantic-conventions+[...].mjs +2658 -0
- package/.output/server/_libs/agent-base.mjs +234 -0
- package/.output/server/_libs/anthropic-ai__sdk.mjs +6042 -0
- package/.output/server/_libs/atomically.mjs +3 -0
- package/.output/server/_libs/base64-js.mjs +102 -0
- package/.output/server/_libs/bignumber.js.mjs +1353 -0
- package/.output/server/_libs/buffer-equal-constant-time.mjs +38 -0
- package/.output/server/_libs/data-uri-to-buffer.mjs +39 -0
- package/.output/server/_libs/debug.mjs +547 -0
- package/.output/server/_libs/ecdsa-sig-formatter.mjs +162 -0
- package/.output/server/_libs/extend.mjs +2 -1
- package/.output/server/_libs/fetch-blob.mjs +287 -0
- package/.output/server/_libs/formdata-polyfill.mjs +88 -0
- package/.output/server/_libs/gaxios.mjs +927 -0
- package/.output/server/_libs/gcp-metadata.mjs +322 -0
- package/.output/server/_libs/google-auth-library.mjs +6339 -0
- package/.output/server/_libs/google-logging-utils.mjs +385 -0
- package/.output/server/_libs/google__genai.mjs +18034 -0
- package/.output/server/_libs/has-flag.mjs +16 -0
- package/.output/server/_libs/https-proxy-agent.mjs +280 -0
- package/.output/server/_libs/json-bigint.mjs +413 -0
- package/.output/server/_libs/jwa.mjs +233 -0
- package/.output/server/_libs/jws.mjs +298 -0
- package/.output/server/_libs/lucide-react.mjs +261 -226
- package/.output/server/_libs/mariozechner__pi-agent-core.mjs +824 -0
- package/.output/server/_libs/mariozechner__pi-ai.mjs +22889 -0
- package/.output/server/_libs/mistralai__mistralai.mjs +32045 -0
- package/.output/server/_libs/ms.mjs +120 -0
- package/.output/server/_libs/node-domexception.mjs +17 -0
- package/.output/server/_libs/node-fetch.mjs +1578 -0
- package/.output/server/_libs/ohash.mjs +1 -1
- package/.output/server/_libs/openai.mjs +6766 -0
- package/.output/server/_libs/opentelemetry__api.mjs +1678 -0
- package/.output/server/_libs/p-retry.mjs +87 -0
- package/.output/server/_libs/partial-json.mjs +244 -0
- package/.output/server/_libs/react-dom.mjs +5 -5
- package/.output/server/_libs/react-markdown.mjs +2 -2
- package/.output/server/_libs/react.mjs +30 -0
- package/.output/server/_libs/readable-stream.mjs +5 -5
- package/.output/server/_libs/retry.mjs +236 -0
- package/.output/server/_libs/safe-buffer.mjs +63 -1
- package/.output/server/_libs/srvx.mjs +2 -2
- package/.output/server/_libs/supports-color.mjs +108 -0
- package/.output/server/_libs/tanstack__router-core.mjs +1 -1
- package/.output/server/_libs/typebox.mjs +6596 -0
- package/.output/server/_libs/util-deprecate.mjs +2 -2
- package/.output/server/_libs/vfile.mjs +2 -2
- package/.output/server/_libs/web-streams-polyfill.mjs +4282 -0
- package/.output/server/_libs/ws.mjs +3715 -0
- package/.output/server/_libs/zod-to-json-schema.mjs +1207 -0
- package/.output/server/_libs/zod.mjs +597 -9
- package/.output/server/{_sessionId-GcFhWT0i.mjs → _sessionId-DL2JQJwR.mjs} +14 -13
- package/.output/server/_ssr/{CompareDrawer-BV7dw1TN.mjs → CompareDrawer-hPTOTmyU.mjs} +14 -13
- package/.output/server/_ssr/{ProxyViewerContainer-CcgfvaS4.mjs → ProxyViewerContainer-k0mFaufl.mjs} +161 -91
- package/.output/server/_ssr/{ReplayDialog-B7sBKxIO.mjs → ReplayDialog-t6QdwnZh.mjs} +15 -14
- package/.output/server/_ssr/{RequestAnatomy-s_9Veje2.mjs → RequestAnatomy-BnvWxyac.mjs} +14 -13
- package/.output/server/_ssr/{ResponseView-DsOFPCvF.mjs → ResponseView-BhhozRRl.mjs} +14 -13
- package/.output/server/_ssr/{StreamingChunkSequence-CyA1XtiF.mjs → StreamingChunkSequence-Dl_laTbX.mjs} +14 -13
- package/.output/server/_ssr/{index-CDDRt201.mjs → index-D1-t1D8F.mjs} +25 -15
- package/.output/server/_ssr/index.mjs +4 -4
- package/.output/server/_ssr/{json-viewer-BFV-xtRM.mjs → json-viewer-D5Lh2yBU.mjs} +14 -13
- package/.output/server/_ssr/{router-B2Q7vHPM.mjs → router-DaLFEeWW.mjs} +597 -100
- package/.output/server/_tanstack-start-manifest_v-DsSiD2gY.mjs +4 -0
- package/.output/server/index.mjs +77 -77
- package/package.json +4 -1
- package/src/components/ProxyViewer.tsx +12 -4
- package/src/components/pi-agent/PiAgentPanel.tsx +204 -0
- package/src/lib/chunkLoadRecovery.ts +83 -0
- package/src/lib/piAgentContract.ts +21 -0
- package/src/routes/__root.tsx +6 -3
- package/src/routes/api/pi-agent.ts +29 -0
- package/src/routes/index.tsx +25 -1
- package/src/routes/session/$sessionId.tsx +1 -0
- package/src/services/piAgent.ts +390 -0
- package/.output/public/assets/index-CGpPRty8.js +0 -1
- package/.output/public/assets/index-HRfrFVYw.css +0 -1
- package/.output/server/_tanstack-start-manifest_v-Dm_GmuRz.mjs +0 -4
package/src/routes/__root.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/// <reference types="vite/client" />
|
|
2
2
|
import { Outlet, createRootRoute, HeadContent, Scripts } from "@tanstack/react-router";
|
|
3
|
-
import type
|
|
3
|
+
import { useEffect, type ReactNode } from "react";
|
|
4
4
|
import { SWRConfig } from "swr";
|
|
5
5
|
import faviconSvg from "../assets/favicon.svg?url";
|
|
6
6
|
import appCss from "../index.css?url";
|
|
7
|
+
import { installChunkLoadRecovery } from "../lib/chunkLoadRecovery";
|
|
7
8
|
|
|
8
9
|
export const Route = createRootRoute({
|
|
9
10
|
head: () => ({
|
|
@@ -22,6 +23,8 @@ export const Route = createRootRoute({
|
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
function RootComponent() {
|
|
26
|
+
useEffect(() => installChunkLoadRecovery(), []);
|
|
27
|
+
|
|
25
28
|
return (
|
|
26
29
|
<RootDocument>
|
|
27
30
|
<Outlet />
|
|
@@ -57,11 +60,11 @@ function RootNotFoundComponent() {
|
|
|
57
60
|
|
|
58
61
|
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
|
|
59
62
|
return (
|
|
60
|
-
<html lang="en" className="dark">
|
|
63
|
+
<html lang="en" className="dark" suppressHydrationWarning={true}>
|
|
61
64
|
<head>
|
|
62
65
|
<HeadContent />
|
|
63
66
|
</head>
|
|
64
|
-
<body>
|
|
67
|
+
<body suppressHydrationWarning={true}>
|
|
65
68
|
<SWRConfig value={{ revalidateOnFocus: false, revalidateIfStale: false }}>
|
|
66
69
|
{children}
|
|
67
70
|
</SWRConfig>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { PiAgentRequestSchema } from "../../lib/piAgentContract";
|
|
3
|
+
import { runPiAgent } from "../../services/piAgent";
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute("/api/pi-agent")({
|
|
6
|
+
server: {
|
|
7
|
+
handlers: {
|
|
8
|
+
POST: async ({ request }: { request: Request }) => {
|
|
9
|
+
let body: unknown;
|
|
10
|
+
try {
|
|
11
|
+
body = await request.json();
|
|
12
|
+
} catch {
|
|
13
|
+
return Response.json({ error: "Invalid JSON body" }, { status: 400 });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const parsed = PiAgentRequestSchema.safeParse(body);
|
|
17
|
+
if (!parsed.success) {
|
|
18
|
+
return Response.json(
|
|
19
|
+
{ error: "Invalid Pi-agent request", details: parsed.error.format() },
|
|
20
|
+
{ status: 400 },
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const result = await runPiAgent(parsed.data);
|
|
25
|
+
return Response.json(result);
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
package/src/routes/index.tsx
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { useEffect, useState, type JSX } from "react";
|
|
2
3
|
import { ProxyViewerContainer } from "../components/ProxyViewerContainer";
|
|
3
4
|
|
|
4
5
|
export const Route = createFileRoute("/")({
|
|
5
|
-
|
|
6
|
+
ssr: false,
|
|
7
|
+
component: ClientOnlyProxyViewerRoute,
|
|
6
8
|
});
|
|
9
|
+
|
|
10
|
+
function ClientOnlyProxyViewerRoute(): JSX.Element {
|
|
11
|
+
const [mounted, setMounted] = useState(false);
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
setMounted(true);
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
if (!mounted) {
|
|
18
|
+
return (
|
|
19
|
+
<main className="min-h-screen bg-background text-foreground">
|
|
20
|
+
<div className="mx-auto flex min-h-screen w-full max-w-2xl flex-col justify-center px-6 py-16 text-center">
|
|
21
|
+
<div className="rounded-[8px] border border-border bg-card px-4 py-8 shadow-sm">
|
|
22
|
+
<p className="text-sm font-medium text-muted-foreground">Loading Inspector...</p>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</main>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return <ProxyViewerContainer />;
|
|
30
|
+
}
|
|
@@ -9,6 +9,7 @@ import { decodeSessionIdFromPath, encodeSessionIdForPath } from "../../lib/sessi
|
|
|
9
9
|
* and Unicode project paths survive dev-server URL normalization.
|
|
10
10
|
*/
|
|
11
11
|
export const Route = createFileRoute("/session/$sessionId")({
|
|
12
|
+
ssr: false,
|
|
12
13
|
component: SessionViewerRoute,
|
|
13
14
|
parseParams: (params) => ({
|
|
14
15
|
sessionId: decodeSessionIdFromPath(params.sessionId),
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
|
2
|
+
import type { Api, AssistantMessage, Context, Model } from "@mariozechner/pi-ai";
|
|
3
|
+
import type { CapturedLog } from "../contracts";
|
|
4
|
+
import type { PiAgentRequest, PiAgentResponse } from "../lib/piAgentContract";
|
|
5
|
+
import { buildUpstreamUrl } from "../lib/upstreamUrl";
|
|
6
|
+
import { extractStopReason } from "../lib/stopReason";
|
|
7
|
+
import {
|
|
8
|
+
getModelUsageName,
|
|
9
|
+
getProvider,
|
|
10
|
+
getProviders,
|
|
11
|
+
type ProviderConfig,
|
|
12
|
+
} from "../proxy/providers";
|
|
13
|
+
import { AUTH_HEADER_X_API_KEY } from "../proxy/constants";
|
|
14
|
+
import { listLogsPage } from "../proxy/store";
|
|
15
|
+
|
|
16
|
+
type PiAgentProtocol = PiAgentResponse["protocol"];
|
|
17
|
+
type PiModelApi = "openai-responses" | "openai-completions" | "anthropic-messages";
|
|
18
|
+
|
|
19
|
+
type SelectedProvider = {
|
|
20
|
+
provider: ProviderConfig;
|
|
21
|
+
modelName: string;
|
|
22
|
+
protocol: PiAgentProtocol;
|
|
23
|
+
api: PiModelApi;
|
|
24
|
+
baseUrl: string;
|
|
25
|
+
upstreamPath: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const DEFAULT_LOG_LIMIT = 24;
|
|
29
|
+
const MAX_TEXT_SNIPPET = 900;
|
|
30
|
+
const MAX_CONTEXT_CHARS = 18_000;
|
|
31
|
+
const PI_AGENT_MAX_OUTPUT_TOKENS = 900;
|
|
32
|
+
const DEFAULT_CONTEXT_WINDOW = 128_000;
|
|
33
|
+
|
|
34
|
+
function nonEmpty(value: string | undefined): string | undefined {
|
|
35
|
+
return value !== undefined && value.trim() !== "" ? value.trim() : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function firstModel(provider: ProviderConfig): string | undefined {
|
|
39
|
+
const explicitModel = nonEmpty(provider.model);
|
|
40
|
+
if (explicitModel !== undefined) return explicitModel;
|
|
41
|
+
for (const model of provider.models) {
|
|
42
|
+
const trimmed = nonEmpty(model);
|
|
43
|
+
if (trimmed !== undefined) return trimmed;
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function providerCandidates(request: PiAgentRequest): ProviderConfig[] {
|
|
49
|
+
if (request.providerId === undefined) {
|
|
50
|
+
return getProviders().toSorted((left, right) => {
|
|
51
|
+
const leftCompanyRank = left.source === "company" ? 0 : 1;
|
|
52
|
+
const rightCompanyRank = right.source === "company" ? 0 : 1;
|
|
53
|
+
if (leftCompanyRank !== rightCompanyRank) return leftCompanyRank - rightCompanyRank;
|
|
54
|
+
return left.name.localeCompare(right.name);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const provider = getProvider(request.providerId);
|
|
58
|
+
return provider === undefined ? [] : [provider];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function selectProvider(request: PiAgentRequest): SelectedProvider | null {
|
|
62
|
+
for (const provider of providerCandidates(request)) {
|
|
63
|
+
const modelName = request.model ?? firstModel(provider);
|
|
64
|
+
if (modelName === undefined) continue;
|
|
65
|
+
|
|
66
|
+
const responsesBaseUrl = nonEmpty(provider.openaiResponsesBaseUrl);
|
|
67
|
+
if (responsesBaseUrl !== undefined) {
|
|
68
|
+
return {
|
|
69
|
+
provider,
|
|
70
|
+
modelName,
|
|
71
|
+
protocol: "openaiResponses",
|
|
72
|
+
api: "openai-responses",
|
|
73
|
+
baseUrl: responsesBaseUrl,
|
|
74
|
+
upstreamPath: "/v1/responses",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const openaiBaseUrl = nonEmpty(provider.openaiBaseUrl);
|
|
79
|
+
if (openaiBaseUrl !== undefined) {
|
|
80
|
+
return {
|
|
81
|
+
provider,
|
|
82
|
+
modelName,
|
|
83
|
+
protocol: "openaiChat",
|
|
84
|
+
api: "openai-completions",
|
|
85
|
+
baseUrl: openaiBaseUrl,
|
|
86
|
+
upstreamPath: "/v1/chat/completions",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const anthropicBaseUrl = nonEmpty(provider.anthropicBaseUrl);
|
|
91
|
+
if (anthropicBaseUrl !== undefined) {
|
|
92
|
+
return {
|
|
93
|
+
provider,
|
|
94
|
+
modelName,
|
|
95
|
+
protocol: "anthropic",
|
|
96
|
+
api: "anthropic-messages",
|
|
97
|
+
baseUrl: anthropicBaseUrl,
|
|
98
|
+
upstreamPath: "/v1/messages",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function truncate(value: string, maxLength: number): string {
|
|
107
|
+
if (value.length <= maxLength) return value;
|
|
108
|
+
return `${value.slice(0, maxLength)}\n...[truncated ${String(value.length - maxLength)} chars]`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function readProperty(value: unknown, key: string): unknown {
|
|
112
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
113
|
+
return Object.getOwnPropertyDescriptor(value, key)?.value;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function collectStringProperties(
|
|
117
|
+
value: unknown,
|
|
118
|
+
keys: readonly string[],
|
|
119
|
+
output: Set<string>,
|
|
120
|
+
): void {
|
|
121
|
+
if (value === null || typeof value !== "object") return;
|
|
122
|
+
for (const key of keys) {
|
|
123
|
+
const property = readProperty(value, key);
|
|
124
|
+
if (typeof property === "string" && property.trim() !== "") {
|
|
125
|
+
output.add(property.trim());
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function collectToolNamesFromUnknown(value: unknown, output: Set<string>, depth = 0): void {
|
|
131
|
+
if (depth > 5 || value === null || typeof value !== "object") return;
|
|
132
|
+
|
|
133
|
+
collectStringProperties(value, ["name", "tool_name"], output);
|
|
134
|
+
collectStringProperties(readProperty(value, "function"), ["name"], output);
|
|
135
|
+
|
|
136
|
+
if (Array.isArray(value)) {
|
|
137
|
+
for (const item of value) {
|
|
138
|
+
collectToolNamesFromUnknown(item, output, depth + 1);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const candidateKey of ["tools", "tool_calls", "content", "output", "input", "messages"]) {
|
|
144
|
+
collectToolNamesFromUnknown(readProperty(value, candidateKey), output, depth + 1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function collectToolNames(log: CapturedLog): string[] {
|
|
149
|
+
const tools = new Set<string>();
|
|
150
|
+
for (const raw of [log.rawRequestBody, log.responseText]) {
|
|
151
|
+
if (raw === null || raw.trim() === "") continue;
|
|
152
|
+
try {
|
|
153
|
+
const parsed: unknown = JSON.parse(raw);
|
|
154
|
+
collectToolNamesFromUnknown(parsed, tools);
|
|
155
|
+
} catch {
|
|
156
|
+
// Ignore non-JSON payloads; the surrounding summary still includes the status.
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return [...tools].slice(0, 12);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function summarizeLog(log: CapturedLog): string {
|
|
163
|
+
const stopReason = extractStopReason(log);
|
|
164
|
+
const tools = collectToolNames(log);
|
|
165
|
+
const requestSnippet =
|
|
166
|
+
log.rawRequestBody !== null ? truncate(log.rawRequestBody, MAX_TEXT_SNIPPET) : "[not captured]";
|
|
167
|
+
const responseSnippet =
|
|
168
|
+
log.responseText !== null ? truncate(log.responseText, MAX_TEXT_SNIPPET) : "[not captured]";
|
|
169
|
+
const warnings = log.warnings !== undefined && log.warnings.length > 0 ? log.warnings : [];
|
|
170
|
+
|
|
171
|
+
return [
|
|
172
|
+
`Log #${String(log.id)} ${log.timestamp}`,
|
|
173
|
+
`path=${log.path} format=${log.apiFormat} model=${log.model ?? "unknown"} provider=${log.providerName ?? "unknown"}`,
|
|
174
|
+
`session=${log.sessionId ?? "unknown"} pid=${log.clientPid?.toString() ?? "unknown"} status=${log.responseStatus?.toString() ?? "pending"} streaming=${String(log.streaming)}`,
|
|
175
|
+
`elapsedMs=${log.elapsedMs?.toString() ?? "unknown"} firstChunkMs=${log.firstChunkMs?.toString() ?? "unknown"} tokens=${log.inputTokens?.toString() ?? "?"}/${log.outputTokens?.toString() ?? "?"} stop=${stopReason ?? "unknown"}`,
|
|
176
|
+
tools.length > 0 ? `tools=${tools.join(", ")}` : "tools=none detected",
|
|
177
|
+
warnings.length > 0 ? `warnings=${warnings.join(" | ")}` : "warnings=none",
|
|
178
|
+
log.error !== null && log.error !== undefined ? `error=${log.error}` : "error=none",
|
|
179
|
+
`requestSnippet:\n${requestSnippet}`,
|
|
180
|
+
`responseSnippet:\n${responseSnippet}`,
|
|
181
|
+
].join("\n");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function buildInspectorContext(request: PiAgentRequest): Promise<{
|
|
185
|
+
context: string;
|
|
186
|
+
logCount: number;
|
|
187
|
+
}> {
|
|
188
|
+
const limit = request.logLimit ?? DEFAULT_LOG_LIMIT;
|
|
189
|
+
const page = await listLogsPage({
|
|
190
|
+
sessionId: request.sessionId,
|
|
191
|
+
offset: 0,
|
|
192
|
+
limit,
|
|
193
|
+
includeBodies: true,
|
|
194
|
+
});
|
|
195
|
+
const logs = page.logs.slice(-limit);
|
|
196
|
+
const context = truncate(logs.map(summarizeLog).join("\n\n---\n\n"), MAX_CONTEXT_CHARS);
|
|
197
|
+
return { context, logCount: logs.length };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function buildSystemPrompt(): string {
|
|
201
|
+
return [
|
|
202
|
+
"You are Pi Agent, the built-in analysis agent inside TonyClaw Agent Inspector.",
|
|
203
|
+
"Analyze AI coding tool requests, responses, provider behavior, sessions, turns, tools, token usage, and hanging interactions.",
|
|
204
|
+
"Be concrete. Cite log ids when possible. Distinguish evidence from inference.",
|
|
205
|
+
"Give a short diagnosis first, then the likely cause, then practical next actions.",
|
|
206
|
+
"Do not invent raw data that is not present in the Inspector context.",
|
|
207
|
+
].join("\n");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function buildUserPrompt(question: string, context: string): string {
|
|
211
|
+
return [
|
|
212
|
+
`User question:\n${question}`,
|
|
213
|
+
"",
|
|
214
|
+
"Inspector context:",
|
|
215
|
+
context !== "" ? context : "No logs were loaded for this scope.",
|
|
216
|
+
].join("\n");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function stripUrlSuffix(value: string, suffix: string): string | null {
|
|
220
|
+
if (!value.endsWith(suffix)) return null;
|
|
221
|
+
const stripped = value.slice(0, -suffix.length);
|
|
222
|
+
return stripped === "" ? "/" : stripped;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function sdkBaseUrlFromFinalUrl(finalUrl: string, protocol: PiAgentProtocol): string {
|
|
226
|
+
try {
|
|
227
|
+
const url = new URL(finalUrl);
|
|
228
|
+
const pathname = url.pathname.replace(/\/+$/, "");
|
|
229
|
+
const suffixes =
|
|
230
|
+
protocol === "openaiResponses"
|
|
231
|
+
? ["/responses"]
|
|
232
|
+
: protocol === "openaiChat"
|
|
233
|
+
? ["/chat/completions"]
|
|
234
|
+
: ["/v1/messages", "/messages"];
|
|
235
|
+
|
|
236
|
+
for (const suffix of suffixes) {
|
|
237
|
+
const nextPath = stripUrlSuffix(pathname, suffix);
|
|
238
|
+
if (nextPath !== null) {
|
|
239
|
+
url.pathname = nextPath;
|
|
240
|
+
return url.toString().replace(/\/+$/, "");
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
} catch {
|
|
244
|
+
// Fall back to the configured base below.
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return finalUrl.replace(/\/+$/, "");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function buildPiBaseUrl(selected: SelectedProvider): string {
|
|
251
|
+
const finalUrl = buildUpstreamUrl(selected.baseUrl, selected.upstreamPath);
|
|
252
|
+
return sdkBaseUrlFromFinalUrl(finalUrl, selected.protocol);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function modelMetadataFor(
|
|
256
|
+
provider: ProviderConfig,
|
|
257
|
+
modelName: string,
|
|
258
|
+
): {
|
|
259
|
+
contextWindow: number;
|
|
260
|
+
outputLimit: number;
|
|
261
|
+
} {
|
|
262
|
+
const metadata = provider.modelMetadata?.find((item) => item.model === modelName);
|
|
263
|
+
return {
|
|
264
|
+
contextWindow: metadata?.contextWindow ?? DEFAULT_CONTEXT_WINDOW,
|
|
265
|
+
outputLimit: metadata?.outputLimit ?? PI_AGENT_MAX_OUTPUT_TOKENS,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function buildPiHeaders(provider: ProviderConfig): Record<string, string> | undefined {
|
|
270
|
+
if (provider.authHeader !== AUTH_HEADER_X_API_KEY) return undefined;
|
|
271
|
+
return { [AUTH_HEADER_X_API_KEY]: provider.apiKey };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function buildPiModel(selected: SelectedProvider): Model<Api> {
|
|
275
|
+
const metadata = modelMetadataFor(selected.provider, selected.modelName);
|
|
276
|
+
const usageModelName = getModelUsageName(selected.modelName, selected.provider.name);
|
|
277
|
+
return {
|
|
278
|
+
id: usageModelName,
|
|
279
|
+
name: selected.modelName,
|
|
280
|
+
api: selected.api,
|
|
281
|
+
provider: selected.provider.name,
|
|
282
|
+
baseUrl: buildPiBaseUrl(selected),
|
|
283
|
+
reasoning: false,
|
|
284
|
+
input: ["text"],
|
|
285
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
286
|
+
contextWindow: metadata.contextWindow,
|
|
287
|
+
maxTokens: Math.max(metadata.outputLimit, PI_AGENT_MAX_OUTPUT_TOKENS),
|
|
288
|
+
headers: buildPiHeaders(selected.provider),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function textFromAssistantMessage(message: AssistantMessage): string {
|
|
293
|
+
const parts: string[] = [];
|
|
294
|
+
for (const block of message.content) {
|
|
295
|
+
if (block.type === "text" && block.text.trim() !== "") {
|
|
296
|
+
parts.push(block.text);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return parts.join("\n").trim();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function stripReasoningTags(value: string): string {
|
|
303
|
+
const stripped = value.replace(/<think>[\s\S]*?<\/think>/gi, "").trim();
|
|
304
|
+
return stripped !== "" ? stripped : value.trim();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function lastAssistantMessage(messages: readonly AgentMessage[]): AssistantMessage | null {
|
|
308
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
309
|
+
const message = messages[index];
|
|
310
|
+
if (message !== undefined && message.role === "assistant") return message;
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function buildContext(question: string, inspectorContext: string): Context {
|
|
316
|
+
return {
|
|
317
|
+
systemPrompt: buildSystemPrompt(),
|
|
318
|
+
messages: [
|
|
319
|
+
{
|
|
320
|
+
role: "user",
|
|
321
|
+
content: buildUserPrompt(question, inspectorContext),
|
|
322
|
+
timestamp: Date.now(),
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
async function askPi(
|
|
329
|
+
selected: SelectedProvider,
|
|
330
|
+
question: string,
|
|
331
|
+
inspectorContext: string,
|
|
332
|
+
): Promise<string> {
|
|
333
|
+
const { Agent } = await import(/* @vite-ignore */ "@mariozechner/pi-agent-core");
|
|
334
|
+
const model = buildPiModel(selected);
|
|
335
|
+
const agent = new Agent({
|
|
336
|
+
initialState: {
|
|
337
|
+
model,
|
|
338
|
+
systemPrompt: buildSystemPrompt(),
|
|
339
|
+
tools: [],
|
|
340
|
+
},
|
|
341
|
+
getApiKey: () => selected.provider.apiKey,
|
|
342
|
+
sessionId: selected.provider.id,
|
|
343
|
+
maxRetryDelayMs: 10_000,
|
|
344
|
+
});
|
|
345
|
+
const context = buildContext(question, inspectorContext);
|
|
346
|
+
|
|
347
|
+
await agent.prompt(context.messages);
|
|
348
|
+
const assistantMessage = lastAssistantMessage(agent.state.messages);
|
|
349
|
+
if (assistantMessage === null) {
|
|
350
|
+
return agent.state.errorMessage ?? "Pi Agent returned no assistant message.";
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const answer = textFromAssistantMessage(assistantMessage);
|
|
354
|
+
if (answer !== "") return stripReasoningTags(answer);
|
|
355
|
+
return assistantMessage.errorMessage ?? "Pi Agent received an empty provider response.";
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export async function runPiAgent(request: PiAgentRequest): Promise<PiAgentResponse> {
|
|
359
|
+
const selected = selectProvider(request);
|
|
360
|
+
if (selected === null) {
|
|
361
|
+
return {
|
|
362
|
+
answer:
|
|
363
|
+
"Pi Agent needs at least one Provider with OpenAI Responses, OpenAI Chat, or Anthropic Messages configured.",
|
|
364
|
+
providerId: "",
|
|
365
|
+
providerName: "No provider",
|
|
366
|
+
model: "",
|
|
367
|
+
protocol: "openaiChat",
|
|
368
|
+
logCount: 0,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const { context, logCount } = await buildInspectorContext(request);
|
|
373
|
+
let answer: string;
|
|
374
|
+
try {
|
|
375
|
+
answer = await askPi(selected, request.question, context);
|
|
376
|
+
} catch (err) {
|
|
377
|
+
answer = `Pi Agent provider request failed: ${
|
|
378
|
+
err instanceof Error ? err.message : String(err)
|
|
379
|
+
}`;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
answer,
|
|
384
|
+
providerId: selected.provider.id,
|
|
385
|
+
providerName: selected.provider.name,
|
|
386
|
+
model: selected.modelName,
|
|
387
|
+
protocol: selected.protocol,
|
|
388
|
+
logCount,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{P as o}from"./ProxyViewerContainer-DlpfcX56.js";import"./main-Zv_gjZMb.js";const r=o;export{r as component};
|