@tonyclaw/agent-inspector 3.1.7 → 3.1.9
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/backend/nitro.json +1 -1
- package/.output/cli.js +14 -4
- package/.output/server/_ssr/index.mjs +1 -1
- package/.output/server/_ssr/{router-6IijwoYv.mjs → router-Lc1aOs3g.mjs} +270 -3
- package/.output/server/index.mjs +1 -1
- package/.output/ui/assets/{CompareDrawer-CBqIDEv5.js → CompareDrawer-YdfnAMiC.js} +1 -1
- package/.output/ui/assets/{InspectorPet-Cx21mu-r.js → InspectorPet-Bt0vxGL8.js} +1 -1
- package/.output/ui/assets/ProxyViewerContainer-D4_GM20S.js +59 -0
- package/.output/ui/assets/{ReplayDialog-5h3GQEg_.js → ReplayDialog-9BWotxhQ.js} +1 -1
- package/.output/ui/assets/{RequestAnatomy-CAlwQcTB.js → RequestAnatomy-0VrsjZmK.js} +1 -1
- package/.output/ui/assets/ResponseView-mmW2Z9sl.js +2 -0
- package/.output/ui/assets/StreamingChunkSequence-ZC90PIBc.js +1 -0
- package/.output/ui/assets/{_sessionId-1G3bJYx7.js → _sessionId-C84un6eF.js} +1 -1
- package/.output/ui/assets/{_sessionId-BtmyX4Vb.js → _sessionId-CB7Eytuo.js} +1 -1
- package/.output/ui/assets/{index--SNPe17S.js → index-2feCKPew.js} +2 -2
- package/.output/ui/assets/{index-CtSb9B-7.js → index-7oLdSt17.js} +1 -1
- package/.output/ui/assets/{index-CfAo7qLu.js → index-BswExFrv.js} +1 -1
- package/.output/ui/assets/index-IBEDJoV_.css +1 -0
- package/.output/ui/assets/{index-DPCSM3Ig.js → index-YEgoJRUl.js} +1 -1
- package/.output/ui/assets/{json-viewer-BC3R5htL.js → json-viewer-CJ96a0P_.js} +1 -1
- package/.output/ui/assets/{jszip.min-DEdQuE_J.js → jszip.min-BDO1ePnu.js} +1 -1
- package/.output/ui/index.html +2 -2
- package/.output/workers/logFinalizer.worker.js +10 -0
- package/.output/workers/sessionWorkerEntry.js +10 -0
- package/package.json +4 -4
- package/src/components/proxy-viewer/AnswerMarkdown.tsx +37 -7
- package/src/components/proxy-viewer/LogEntry.tsx +82 -32
- package/src/components/proxy-viewer/ResponseView.tsx +33 -3
- package/src/components/proxy-viewer/StreamingChunkSequence.tsx +60 -3
- package/src/components/proxy-viewer/viewerState.ts +11 -169
- package/src/contracts/index.ts +2 -0
- package/src/contracts/log.ts +13 -0
- package/src/lib/toolTrace.ts +290 -0
- package/src/proxy/logIndex.ts +4 -0
- package/src/proxy/schemas.ts +2 -0
- package/src/proxy/sqliteLogIndex.ts +24 -2
- package/src/proxy/store.ts +3 -0
- package/.output/ui/assets/ProxyViewerContainer-Btek1Lgk.js +0 -59
- package/.output/ui/assets/ResponseView-B0QKCR9M.js +0 -2
- package/.output/ui/assets/StreamingChunkSequence-DXbDSW9D.js +0 -1
- package/.output/ui/assets/index-BvXp42al.css +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AlertTriangle, Zap } from "lucide-react";
|
|
2
2
|
import type { JSX } from "react";
|
|
3
|
-
import { memo } from "react";
|
|
3
|
+
import { memo, useEffect, useState } from "react";
|
|
4
4
|
import { useMemo } from "react";
|
|
5
5
|
import { cn, formatTokens, getStatusCategory, type StatusCategory } from "../../lib/utils";
|
|
6
6
|
import type { ApiFormat } from "../../contracts";
|
|
@@ -66,6 +66,8 @@ function MarkdownFallbackView({ text }: { text: string }): JSX.Element {
|
|
|
66
66
|
return <AnswerMarkdown text={text} />;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
const RESPONSE_AUTO_PARSE_MAX_CHARS = 1024 * 1024;
|
|
70
|
+
|
|
69
71
|
// eslint-disable-next-line @typescript-eslint/no-shadow -- named function gives React DevTools a readable displayName
|
|
70
72
|
export const ResponseView = memo(function ResponseView({
|
|
71
73
|
responseText,
|
|
@@ -81,11 +83,23 @@ export const ResponseView = memo(function ResponseView({
|
|
|
81
83
|
toolFocusNonce = 0,
|
|
82
84
|
}: ResponseViewProps): JSX.Element {
|
|
83
85
|
const resolvedFormat = apiFormat ?? "unknown";
|
|
86
|
+
const [parseLargeResponse, setParseLargeResponse] = useState(false);
|
|
87
|
+
const shouldDeferStructuredParse =
|
|
88
|
+
responseText !== null && responseText.length > RESPONSE_AUTO_PARSE_MAX_CHARS;
|
|
89
|
+
const responseTextForStructuredParse =
|
|
90
|
+
responseText === null || (shouldDeferStructuredParse && !parseLargeResponse)
|
|
91
|
+
? null
|
|
92
|
+
: responseText;
|
|
84
93
|
const parsed = useMemo(
|
|
85
|
-
() =>
|
|
86
|
-
|
|
94
|
+
() =>
|
|
95
|
+
getLogFormatAdapter(resolvedFormat).analyzeResponse(responseTextForStructuredParse).parsed,
|
|
96
|
+
[resolvedFormat, responseTextForStructuredParse],
|
|
87
97
|
);
|
|
88
98
|
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
setParseLargeResponse(false);
|
|
101
|
+
}, [responseText]);
|
|
102
|
+
|
|
89
103
|
if (responseText === null && error === undefined) {
|
|
90
104
|
return (
|
|
91
105
|
<div className="flex items-center gap-2 py-3">
|
|
@@ -166,6 +180,22 @@ export const ResponseView = memo(function ResponseView({
|
|
|
166
180
|
</span>
|
|
167
181
|
)}
|
|
168
182
|
</div>
|
|
183
|
+
{shouldDeferStructuredParse && !parseLargeResponse && (
|
|
184
|
+
<div className="rounded-md border border-amber-300/20 bg-amber-300/[0.06] px-3 py-2 text-xs text-muted-foreground">
|
|
185
|
+
<div className="mb-2">
|
|
186
|
+
This response is {responseText.length.toLocaleString()} characters, so structured
|
|
187
|
+
parsing is deferred to keep the UI responsive. Response and SSE remain separate; open
|
|
188
|
+
SSE for the event stream.
|
|
189
|
+
</div>
|
|
190
|
+
<button
|
|
191
|
+
type="button"
|
|
192
|
+
className="rounded-md border border-border bg-background px-3 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
|
193
|
+
onClick={() => setParseLargeResponse(true)}
|
|
194
|
+
>
|
|
195
|
+
Parse full structured response
|
|
196
|
+
</button>
|
|
197
|
+
</div>
|
|
198
|
+
)}
|
|
169
199
|
<MarkdownFallbackView text={responseText ?? ""} />
|
|
170
200
|
</div>
|
|
171
201
|
);
|
|
@@ -29,6 +29,10 @@ const StreamingChunksResponseSchema = z.object({
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
const CHUNK_FETCH_TIMEOUT_MS = 10_000;
|
|
32
|
+
const INITIAL_VISIBLE_GROUPS = 80;
|
|
33
|
+
const GROUP_PAGE_SIZE = 80;
|
|
34
|
+
const INITIAL_VISIBLE_CHUNKS_PER_GROUP = 40;
|
|
35
|
+
const CHUNK_PAGE_SIZE = 40;
|
|
32
36
|
|
|
33
37
|
// eslint-disable-next-line @typescript-eslint/no-shadow -- named function gives React DevTools a readable displayName
|
|
34
38
|
export const StreamingChunkSequence = memo(function StreamingChunkSequence({
|
|
@@ -38,8 +42,18 @@ export const StreamingChunkSequence = memo(function StreamingChunkSequence({
|
|
|
38
42
|
const [containerExpanded, setContainerExpanded] = useState(false);
|
|
39
43
|
const [chunkState, setChunkState] = useState<ChunkState>({ status: "idle" });
|
|
40
44
|
const [expandedIndices, setExpandedIndices] = useState<Set<number>>(new Set());
|
|
45
|
+
const [visibleGroupCount, setVisibleGroupCount] = useState(INITIAL_VISIBLE_GROUPS);
|
|
46
|
+
const [visibleChunksByGroup, setVisibleChunksByGroup] = useState<Map<number, number>>(new Map());
|
|
41
47
|
const loadedLogIdRef = useRef<number | null>(null);
|
|
42
48
|
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
setExpandedIndices(new Set());
|
|
51
|
+
setVisibleGroupCount(INITIAL_VISIBLE_GROUPS);
|
|
52
|
+
setVisibleChunksByGroup(new Map());
|
|
53
|
+
setChunkState({ status: "idle" });
|
|
54
|
+
loadedLogIdRef.current = null;
|
|
55
|
+
}, [logId]);
|
|
56
|
+
|
|
43
57
|
useEffect(() => {
|
|
44
58
|
if (!containerExpanded) return;
|
|
45
59
|
if (loadedLogIdRef.current === logId) return;
|
|
@@ -90,6 +104,9 @@ export const StreamingChunkSequence = memo(function StreamingChunkSequence({
|
|
|
90
104
|
.sort((a, b) => a.index - b.index);
|
|
91
105
|
}, [chunkState]);
|
|
92
106
|
|
|
107
|
+
const visibleGroups = groups.slice(0, visibleGroupCount);
|
|
108
|
+
const hiddenGroupCount = Math.max(groups.length - visibleGroups.length, 0);
|
|
109
|
+
|
|
93
110
|
const toggleIndex = (index: number) => {
|
|
94
111
|
setExpandedIndices((prev) => {
|
|
95
112
|
const next = new Set(prev);
|
|
@@ -102,6 +119,19 @@ export const StreamingChunkSequence = memo(function StreamingChunkSequence({
|
|
|
102
119
|
});
|
|
103
120
|
};
|
|
104
121
|
|
|
122
|
+
const showMoreGroups = () => {
|
|
123
|
+
setVisibleGroupCount((count) => Math.min(count + GROUP_PAGE_SIZE, groups.length));
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const showMoreChunks = (group: ChunkGroup) => {
|
|
127
|
+
setVisibleChunksByGroup((prev) => {
|
|
128
|
+
const next = new Map(prev);
|
|
129
|
+
const current = next.get(group.index) ?? INITIAL_VISIBLE_CHUNKS_PER_GROUP;
|
|
130
|
+
next.set(group.index, Math.min(current + CHUNK_PAGE_SIZE, group.chunks.length));
|
|
131
|
+
return next;
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
|
|
105
135
|
function renderBody(): JSX.Element {
|
|
106
136
|
if (chunkState.status === "idle" || chunkState.status === "loading") {
|
|
107
137
|
return (
|
|
@@ -121,8 +151,12 @@ export const StreamingChunkSequence = memo(function StreamingChunkSequence({
|
|
|
121
151
|
<div className="text-[10px] text-muted-foreground font-mono mb-2">
|
|
122
152
|
{groups.length} index group{groups.length !== 1 ? "s" : ""} available
|
|
123
153
|
</div>
|
|
124
|
-
{
|
|
154
|
+
{visibleGroups.map((group) => {
|
|
125
155
|
const isExpanded = expandedIndices.has(group.index);
|
|
156
|
+
const visibleChunkCount =
|
|
157
|
+
visibleChunksByGroup.get(group.index) ?? INITIAL_VISIBLE_CHUNKS_PER_GROUP;
|
|
158
|
+
const visibleChunks = group.chunks.slice(0, visibleChunkCount);
|
|
159
|
+
const hiddenChunkCount = Math.max(group.chunks.length - visibleChunks.length, 0);
|
|
126
160
|
return (
|
|
127
161
|
<div key={group.index} className="rounded border border-border bg-background">
|
|
128
162
|
<button
|
|
@@ -144,8 +178,11 @@ export const StreamingChunkSequence = memo(function StreamingChunkSequence({
|
|
|
144
178
|
</button>
|
|
145
179
|
{isExpanded && (
|
|
146
180
|
<div className="px-2 pb-2 space-y-1">
|
|
147
|
-
{
|
|
148
|
-
<div
|
|
181
|
+
{visibleChunks.map((chunk, chunkOrdinal) => (
|
|
182
|
+
<div
|
|
183
|
+
key={`${chunk.index}:${chunk.timestamp}:${chunk.type}:${chunkOrdinal}`}
|
|
184
|
+
className="rounded border border-border bg-muted/20 p-2"
|
|
185
|
+
>
|
|
149
186
|
<div className="flex items-center gap-2 mb-1">
|
|
150
187
|
<span className="text-[10px] text-muted-foreground font-mono">
|
|
151
188
|
+{chunk.timestamp}ms
|
|
@@ -163,11 +200,31 @@ export const StreamingChunkSequence = memo(function StreamingChunkSequence({
|
|
|
163
200
|
</Suspense>
|
|
164
201
|
</div>
|
|
165
202
|
))}
|
|
203
|
+
{hiddenChunkCount > 0 && (
|
|
204
|
+
<button
|
|
205
|
+
type="button"
|
|
206
|
+
className="mt-1 rounded-md border border-border bg-background px-2 py-1 text-[10px] font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
|
207
|
+
onClick={() => showMoreChunks(group)}
|
|
208
|
+
>
|
|
209
|
+
Show {Math.min(CHUNK_PAGE_SIZE, hiddenChunkCount)} more chunk
|
|
210
|
+
{Math.min(CHUNK_PAGE_SIZE, hiddenChunkCount) !== 1 ? "s" : ""}
|
|
211
|
+
</button>
|
|
212
|
+
)}
|
|
166
213
|
</div>
|
|
167
214
|
)}
|
|
168
215
|
</div>
|
|
169
216
|
);
|
|
170
217
|
})}
|
|
218
|
+
{hiddenGroupCount > 0 && (
|
|
219
|
+
<button
|
|
220
|
+
type="button"
|
|
221
|
+
className="rounded-md border border-border bg-background px-2 py-1 text-[10px] font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
|
222
|
+
onClick={showMoreGroups}
|
|
223
|
+
>
|
|
224
|
+
Show {Math.min(GROUP_PAGE_SIZE, hiddenGroupCount)} more SSE group
|
|
225
|
+
{Math.min(GROUP_PAGE_SIZE, hiddenGroupCount) !== 1 ? "s" : ""}
|
|
226
|
+
</button>
|
|
227
|
+
)}
|
|
171
228
|
</div>
|
|
172
229
|
);
|
|
173
230
|
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { extractStopReason, isTurnBoundary, type StopReason } from "../../lib/stopReason";
|
|
2
|
-
import { safeGetOwnProperty } from "../../lib/objectUtils";
|
|
3
2
|
import type { CapturedLog } from "../../contracts";
|
|
3
|
+
import {
|
|
4
|
+
extractToolTraceEvents as extractSharedToolTraceEvents,
|
|
5
|
+
type ToolTraceEvent,
|
|
6
|
+
} from "../../lib/toolTrace";
|
|
4
7
|
import { resolveLogFormat } from "./log-formats";
|
|
5
8
|
|
|
9
|
+
export type { ToolTraceEvent };
|
|
10
|
+
|
|
6
11
|
export type TurnEntry = {
|
|
7
12
|
log: CapturedLog;
|
|
8
13
|
stopReason: StopReason;
|
|
@@ -35,39 +40,14 @@ export type TraceSummary = {
|
|
|
35
40
|
knowledgeCandidateCount: number;
|
|
36
41
|
};
|
|
37
42
|
|
|
38
|
-
export type ToolTraceEvent = {
|
|
39
|
-
id: string;
|
|
40
|
-
logId: number;
|
|
41
|
-
index: number;
|
|
42
|
-
provider: "anthropic" | "openai";
|
|
43
|
-
name: string;
|
|
44
|
-
argumentsText: string | null;
|
|
45
|
-
argumentsPreview: string | null;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
43
|
type ToolTraceCacheEntry = {
|
|
49
44
|
apiFormat: CapturedLog["apiFormat"];
|
|
50
45
|
responseText: string | null;
|
|
46
|
+
toolTraceEvents: CapturedLog["toolTraceEvents"];
|
|
51
47
|
events: ToolTraceEvent[];
|
|
52
48
|
};
|
|
53
49
|
|
|
54
|
-
const PREVIEW_LIMIT = 180;
|
|
55
50
|
const toolTraceCache = new WeakMap<CapturedLog, ToolTraceCacheEntry>();
|
|
56
|
-
type ResolvedLogFormat = ReturnType<typeof resolveLogFormat>;
|
|
57
|
-
|
|
58
|
-
function responseMayContainToolTrace(log: CapturedLog, format: ResolvedLogFormat): boolean {
|
|
59
|
-
const responseText = log.responseText;
|
|
60
|
-
if (responseText === null) return false;
|
|
61
|
-
|
|
62
|
-
switch (format) {
|
|
63
|
-
case "anthropic":
|
|
64
|
-
return responseText.includes("tool_use");
|
|
65
|
-
case "openai":
|
|
66
|
-
return responseText.includes("tool_calls") || responseText.includes("function_call");
|
|
67
|
-
case "unknown":
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
51
|
|
|
72
52
|
export function isTurnCollapsible(entryCount: number): boolean {
|
|
73
53
|
return entryCount > 1;
|
|
@@ -112,160 +92,22 @@ export function buildValidPredecessors(groups: ConversationLike[]): Map<number,
|
|
|
112
92
|
return predecessors;
|
|
113
93
|
}
|
|
114
94
|
|
|
115
|
-
function parseJsonResponse(responseText: string | null): unknown {
|
|
116
|
-
if (responseText === null) return null;
|
|
117
|
-
try {
|
|
118
|
-
const parsed: unknown = JSON.parse(responseText);
|
|
119
|
-
if (typeof parsed === "string") {
|
|
120
|
-
return JSON.parse(parsed);
|
|
121
|
-
}
|
|
122
|
-
return parsed;
|
|
123
|
-
} catch {
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function previewValue(value: unknown): string | null {
|
|
129
|
-
if (value === undefined || value === null) return null;
|
|
130
|
-
const raw = typeof value === "string" ? value : JSON.stringify(value);
|
|
131
|
-
if (raw === undefined) return null;
|
|
132
|
-
const normalized = raw.replace(/\s+/g, " ").trim();
|
|
133
|
-
if (normalized.length === 0) return null;
|
|
134
|
-
return normalized.length > PREVIEW_LIMIT
|
|
135
|
-
? `${normalized.slice(0, PREVIEW_LIMIT - 1)}...`
|
|
136
|
-
: normalized;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function copyValue(value: unknown): string | null {
|
|
140
|
-
if (value === undefined || value === null) return null;
|
|
141
|
-
if (typeof value === "string") return value.length > 0 ? value : null;
|
|
142
|
-
const raw = JSON.stringify(value, null, 2);
|
|
143
|
-
return raw === undefined || raw.length === 0 ? null : raw;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function extractAnthropicToolTraceEvents(log: CapturedLog): ToolTraceEvent[] {
|
|
147
|
-
const parsed = parseJsonResponse(log.responseText);
|
|
148
|
-
const content = safeGetOwnProperty(parsed, "content");
|
|
149
|
-
if (!Array.isArray(content)) return [];
|
|
150
|
-
|
|
151
|
-
const events: ToolTraceEvent[] = [];
|
|
152
|
-
for (const block of content) {
|
|
153
|
-
const type = safeGetOwnProperty(block, "type");
|
|
154
|
-
if (type !== "tool_use") continue;
|
|
155
|
-
const name = safeGetOwnProperty(block, "name");
|
|
156
|
-
if (typeof name !== "string" || name.length === 0) continue;
|
|
157
|
-
const input = safeGetOwnProperty(block, "input");
|
|
158
|
-
events.push({
|
|
159
|
-
id: `${String(log.id)}-anthropic-tool-${String(events.length)}`,
|
|
160
|
-
logId: log.id,
|
|
161
|
-
index: events.length,
|
|
162
|
-
provider: "anthropic",
|
|
163
|
-
name,
|
|
164
|
-
argumentsText: copyValue(input),
|
|
165
|
-
argumentsPreview: previewValue(input),
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
return events;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function extractOpenAIToolTraceEvents(log: CapturedLog): ToolTraceEvent[] {
|
|
172
|
-
const parsed = parseJsonResponse(log.responseText);
|
|
173
|
-
const choices = safeGetOwnProperty(parsed, "choices");
|
|
174
|
-
if (!Array.isArray(choices)) return [];
|
|
175
|
-
|
|
176
|
-
const events: ToolTraceEvent[] = [];
|
|
177
|
-
for (const choice of choices) {
|
|
178
|
-
const message = safeGetOwnProperty(choice, "message");
|
|
179
|
-
const toolCalls = safeGetOwnProperty(message, "tool_calls");
|
|
180
|
-
if (Array.isArray(toolCalls)) {
|
|
181
|
-
for (const call of toolCalls) {
|
|
182
|
-
const fn = safeGetOwnProperty(call, "function");
|
|
183
|
-
const name = safeGetOwnProperty(fn, "name");
|
|
184
|
-
if (typeof name !== "string" || name.length === 0) continue;
|
|
185
|
-
const args = safeGetOwnProperty(fn, "arguments");
|
|
186
|
-
events.push({
|
|
187
|
-
id: `${String(log.id)}-openai-tool-${String(events.length)}`,
|
|
188
|
-
logId: log.id,
|
|
189
|
-
index: events.length,
|
|
190
|
-
provider: "openai",
|
|
191
|
-
name,
|
|
192
|
-
argumentsText: copyValue(args),
|
|
193
|
-
argumentsPreview: previewValue(args),
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
const functionCall = safeGetOwnProperty(message, "function_call");
|
|
198
|
-
const functionCallName = safeGetOwnProperty(functionCall, "name");
|
|
199
|
-
if (typeof functionCallName === "string" && functionCallName.length > 0) {
|
|
200
|
-
const args = safeGetOwnProperty(functionCall, "arguments");
|
|
201
|
-
events.push({
|
|
202
|
-
id: `${String(log.id)}-openai-tool-${String(events.length)}`,
|
|
203
|
-
logId: log.id,
|
|
204
|
-
index: events.length,
|
|
205
|
-
provider: "openai",
|
|
206
|
-
name: functionCallName,
|
|
207
|
-
argumentsText: copyValue(args),
|
|
208
|
-
argumentsPreview: previewValue(args),
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
const output = safeGetOwnProperty(parsed, "output");
|
|
213
|
-
if (Array.isArray(output)) {
|
|
214
|
-
for (const item of output) {
|
|
215
|
-
if (safeGetOwnProperty(item, "type") !== "function_call") continue;
|
|
216
|
-
const name = safeGetOwnProperty(item, "name");
|
|
217
|
-
if (typeof name !== "string" || name.length === 0) continue;
|
|
218
|
-
const args = safeGetOwnProperty(item, "arguments");
|
|
219
|
-
events.push({
|
|
220
|
-
id: `${String(log.id)}-openai-tool-${String(events.length)}`,
|
|
221
|
-
logId: log.id,
|
|
222
|
-
index: events.length,
|
|
223
|
-
provider: "openai",
|
|
224
|
-
name,
|
|
225
|
-
argumentsText: copyValue(args),
|
|
226
|
-
argumentsPreview: previewValue(args),
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
return events;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
95
|
export function extractToolTraceEvents(log: CapturedLog): ToolTraceEvent[] {
|
|
234
96
|
const cached = toolTraceCache.get(log);
|
|
235
97
|
if (
|
|
236
98
|
cached !== undefined &&
|
|
237
99
|
cached.apiFormat === log.apiFormat &&
|
|
238
|
-
cached.responseText === log.responseText
|
|
100
|
+
cached.responseText === log.responseText &&
|
|
101
|
+
cached.toolTraceEvents === log.toolTraceEvents
|
|
239
102
|
) {
|
|
240
103
|
return cached.events;
|
|
241
104
|
}
|
|
242
105
|
|
|
243
|
-
const
|
|
244
|
-
if (!responseMayContainToolTrace(log, format)) {
|
|
245
|
-
const events: ToolTraceEvent[] = [];
|
|
246
|
-
toolTraceCache.set(log, {
|
|
247
|
-
apiFormat: log.apiFormat,
|
|
248
|
-
responseText: log.responseText,
|
|
249
|
-
events,
|
|
250
|
-
});
|
|
251
|
-
return events;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
let events: ToolTraceEvent[];
|
|
255
|
-
switch (format) {
|
|
256
|
-
case "anthropic":
|
|
257
|
-
events = extractAnthropicToolTraceEvents(log);
|
|
258
|
-
break;
|
|
259
|
-
case "openai":
|
|
260
|
-
events = extractOpenAIToolTraceEvents(log);
|
|
261
|
-
break;
|
|
262
|
-
case "unknown":
|
|
263
|
-
events = [];
|
|
264
|
-
break;
|
|
265
|
-
}
|
|
106
|
+
const events = extractSharedToolTraceEvents(log);
|
|
266
107
|
toolTraceCache.set(log, {
|
|
267
108
|
apiFormat: log.apiFormat,
|
|
268
109
|
responseText: log.responseText,
|
|
110
|
+
toolTraceEvents: log.toolTraceEvents,
|
|
269
111
|
events,
|
|
270
112
|
});
|
|
271
113
|
return events;
|
package/src/contracts/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export {
|
|
|
9
9
|
LogBodyPartSchema,
|
|
10
10
|
parseLogId,
|
|
11
11
|
StreamingChunkSchema,
|
|
12
|
+
ToolTraceEventSchema,
|
|
12
13
|
} from "./log";
|
|
13
14
|
export type {
|
|
14
15
|
ApiFormat,
|
|
@@ -18,6 +19,7 @@ export type {
|
|
|
18
19
|
LogId,
|
|
19
20
|
LogBodyPart,
|
|
20
21
|
StreamingChunk,
|
|
22
|
+
ToolTraceEvent,
|
|
21
23
|
TokenUsage,
|
|
22
24
|
} from "./log";
|
|
23
25
|
|
package/src/contracts/log.ts
CHANGED
|
@@ -42,6 +42,18 @@ export const StreamingChunkSchema = z.object({
|
|
|
42
42
|
|
|
43
43
|
export type StreamingChunk = z.infer<typeof StreamingChunkSchema>;
|
|
44
44
|
|
|
45
|
+
export const ToolTraceEventSchema = z.object({
|
|
46
|
+
id: z.string(),
|
|
47
|
+
logId: LogIdSchema,
|
|
48
|
+
index: z.number().int().nonnegative(),
|
|
49
|
+
provider: z.enum(["anthropic", "openai"]),
|
|
50
|
+
name: z.string(),
|
|
51
|
+
argumentsText: z.string().nullable(),
|
|
52
|
+
argumentsPreview: z.string().nullable(),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export type ToolTraceEvent = z.infer<typeof ToolTraceEventSchema>;
|
|
56
|
+
|
|
45
57
|
const StreamingChunksArraySchema = z.object({
|
|
46
58
|
chunks: z.array(StreamingChunkSchema),
|
|
47
59
|
truncated: z.boolean().optional().default(false),
|
|
@@ -107,6 +119,7 @@ export const CapturedLogSchema = z.object({
|
|
|
107
119
|
droppedBytes: z.number().int().nonnegative().optional(),
|
|
108
120
|
captureIncompleteReason: CaptureIncompleteReasonSchema.nullable().optional(),
|
|
109
121
|
warnings: z.array(z.string()).optional(),
|
|
122
|
+
toolTraceEvents: z.array(ToolTraceEventSchema).optional(),
|
|
110
123
|
/** Error message from streaming response (e.g., SSE error event) */
|
|
111
124
|
error: z.string().nullable().optional(),
|
|
112
125
|
});
|