@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
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import type { ApiFormat, CapturedLog, StreamingChunk, ToolTraceEvent } from "../contracts";
|
|
2
|
+
import { apiFormatForPath } from "./apiFormat";
|
|
3
|
+
import { safeGetOwnProperty } from "./objectUtils";
|
|
4
|
+
|
|
5
|
+
export type { ToolTraceEvent };
|
|
6
|
+
|
|
7
|
+
const PREVIEW_LIMIT = 180;
|
|
8
|
+
|
|
9
|
+
type ResolvedLogFormat = ApiFormat;
|
|
10
|
+
|
|
11
|
+
function resolveLogFormat(log: Pick<CapturedLog, "path" | "apiFormat">): ResolvedLogFormat {
|
|
12
|
+
const pathFormat = apiFormatForPath(log.path);
|
|
13
|
+
return pathFormat === "unknown" ? log.apiFormat : pathFormat;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function responseMayContainToolTrace(log: CapturedLog, format: ResolvedLogFormat): boolean {
|
|
17
|
+
const responseText = log.responseText;
|
|
18
|
+
if (responseText === null) return false;
|
|
19
|
+
|
|
20
|
+
switch (format) {
|
|
21
|
+
case "anthropic":
|
|
22
|
+
return responseText.includes("tool_use");
|
|
23
|
+
case "openai":
|
|
24
|
+
return responseText.includes("tool_calls") || responseText.includes("function_call");
|
|
25
|
+
case "unknown":
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseJsonResponse(responseText: string | null): unknown {
|
|
31
|
+
if (responseText === null) return null;
|
|
32
|
+
try {
|
|
33
|
+
const parsed: unknown = JSON.parse(responseText);
|
|
34
|
+
if (typeof parsed === "string") {
|
|
35
|
+
return JSON.parse(parsed);
|
|
36
|
+
}
|
|
37
|
+
return parsed;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function previewValue(value: unknown): string | null {
|
|
44
|
+
if (value === undefined || value === null) return null;
|
|
45
|
+
const raw = typeof value === "string" ? value : JSON.stringify(value);
|
|
46
|
+
if (raw === undefined) return null;
|
|
47
|
+
const normalized = raw.replace(/\s+/g, " ").trim();
|
|
48
|
+
if (normalized.length === 0) return null;
|
|
49
|
+
return normalized.length > PREVIEW_LIMIT
|
|
50
|
+
? `${normalized.slice(0, PREVIEW_LIMIT - 1)}...`
|
|
51
|
+
: normalized;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function copyValue(value: unknown): string | null {
|
|
55
|
+
if (value === undefined || value === null) return null;
|
|
56
|
+
if (typeof value === "string") return value.length > 0 ? value : null;
|
|
57
|
+
const raw = JSON.stringify(value, null, 2);
|
|
58
|
+
return raw === undefined || raw.length === 0 ? null : raw;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function makeEvent({
|
|
62
|
+
log,
|
|
63
|
+
provider,
|
|
64
|
+
index,
|
|
65
|
+
name,
|
|
66
|
+
input,
|
|
67
|
+
}: {
|
|
68
|
+
log: Pick<CapturedLog, "id">;
|
|
69
|
+
provider: "anthropic" | "openai";
|
|
70
|
+
index: number;
|
|
71
|
+
name: string;
|
|
72
|
+
input: unknown;
|
|
73
|
+
}): ToolTraceEvent {
|
|
74
|
+
return {
|
|
75
|
+
id: `${String(log.id)}-${provider}-tool-${String(index)}`,
|
|
76
|
+
logId: log.id,
|
|
77
|
+
index,
|
|
78
|
+
provider,
|
|
79
|
+
name,
|
|
80
|
+
argumentsText: copyValue(input),
|
|
81
|
+
argumentsPreview: previewValue(input),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function extractAnthropicResponseToolTraceEvents(log: CapturedLog): ToolTraceEvent[] {
|
|
86
|
+
const parsed = parseJsonResponse(log.responseText);
|
|
87
|
+
const content = safeGetOwnProperty(parsed, "content");
|
|
88
|
+
if (!Array.isArray(content)) return [];
|
|
89
|
+
|
|
90
|
+
const events: ToolTraceEvent[] = [];
|
|
91
|
+
for (const block of content) {
|
|
92
|
+
const type = safeGetOwnProperty(block, "type");
|
|
93
|
+
if (type !== "tool_use") continue;
|
|
94
|
+
const name = safeGetOwnProperty(block, "name");
|
|
95
|
+
if (typeof name !== "string" || name.length === 0) continue;
|
|
96
|
+
const input = safeGetOwnProperty(block, "input");
|
|
97
|
+
events.push(makeEvent({ log, provider: "anthropic", index: events.length, name, input }));
|
|
98
|
+
}
|
|
99
|
+
return events;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function extractOpenAIResponseToolTraceEvents(log: CapturedLog): ToolTraceEvent[] {
|
|
103
|
+
const parsed = parseJsonResponse(log.responseText);
|
|
104
|
+
const choices = safeGetOwnProperty(parsed, "choices");
|
|
105
|
+
const events: ToolTraceEvent[] = [];
|
|
106
|
+
|
|
107
|
+
if (Array.isArray(choices)) {
|
|
108
|
+
for (const choice of choices) {
|
|
109
|
+
const message = safeGetOwnProperty(choice, "message");
|
|
110
|
+
const toolCalls = safeGetOwnProperty(message, "tool_calls");
|
|
111
|
+
if (Array.isArray(toolCalls)) {
|
|
112
|
+
for (const call of toolCalls) {
|
|
113
|
+
const fn = safeGetOwnProperty(call, "function");
|
|
114
|
+
const name = safeGetOwnProperty(fn, "name");
|
|
115
|
+
if (typeof name !== "string" || name.length === 0) continue;
|
|
116
|
+
const args = safeGetOwnProperty(fn, "arguments");
|
|
117
|
+
events.push(
|
|
118
|
+
makeEvent({ log, provider: "openai", index: events.length, name, input: args }),
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const functionCall = safeGetOwnProperty(message, "function_call");
|
|
123
|
+
const functionCallName = safeGetOwnProperty(functionCall, "name");
|
|
124
|
+
if (typeof functionCallName === "string" && functionCallName.length > 0) {
|
|
125
|
+
const args = safeGetOwnProperty(functionCall, "arguments");
|
|
126
|
+
events.push(
|
|
127
|
+
makeEvent({
|
|
128
|
+
log,
|
|
129
|
+
provider: "openai",
|
|
130
|
+
index: events.length,
|
|
131
|
+
name: functionCallName,
|
|
132
|
+
input: args,
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const output = safeGetOwnProperty(parsed, "output");
|
|
140
|
+
if (Array.isArray(output)) {
|
|
141
|
+
for (const item of output) {
|
|
142
|
+
if (safeGetOwnProperty(item, "type") !== "function_call") continue;
|
|
143
|
+
const name = safeGetOwnProperty(item, "name");
|
|
144
|
+
if (typeof name !== "string" || name.length === 0) continue;
|
|
145
|
+
const args = safeGetOwnProperty(item, "arguments");
|
|
146
|
+
events.push(makeEvent({ log, provider: "openai", index: events.length, name, input: args }));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return events;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function chunkData(chunk: StreamingChunk): unknown {
|
|
153
|
+
if (typeof chunk.data === "string") {
|
|
154
|
+
try {
|
|
155
|
+
return JSON.parse(chunk.data);
|
|
156
|
+
} catch {
|
|
157
|
+
return chunk.data;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return chunk.data;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function appendUniqueEvent(
|
|
164
|
+
events: ToolTraceEvent[],
|
|
165
|
+
seen: Set<string>,
|
|
166
|
+
event: ToolTraceEvent,
|
|
167
|
+
): void {
|
|
168
|
+
const key = `${event.provider}:${event.name}:${event.index}`;
|
|
169
|
+
if (seen.has(key)) return;
|
|
170
|
+
seen.add(key);
|
|
171
|
+
events.push(event);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function extractAnthropicChunkToolTraceEvents(log: CapturedLog): ToolTraceEvent[] {
|
|
175
|
+
const chunks = log.streamingChunks?.chunks ?? [];
|
|
176
|
+
const events: ToolTraceEvent[] = [];
|
|
177
|
+
const seen = new Set<string>();
|
|
178
|
+
|
|
179
|
+
for (const chunk of chunks) {
|
|
180
|
+
const data = chunkData(chunk);
|
|
181
|
+
const contentBlock = safeGetOwnProperty(data, "content_block");
|
|
182
|
+
if (safeGetOwnProperty(contentBlock, "type") !== "tool_use") continue;
|
|
183
|
+
const name = safeGetOwnProperty(contentBlock, "name");
|
|
184
|
+
if (typeof name !== "string" || name.length === 0) continue;
|
|
185
|
+
const input = safeGetOwnProperty(contentBlock, "input");
|
|
186
|
+
appendUniqueEvent(
|
|
187
|
+
events,
|
|
188
|
+
seen,
|
|
189
|
+
makeEvent({ log, provider: "anthropic", index: chunk.index, name, input }),
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return events.map((event, index) => ({
|
|
194
|
+
...event,
|
|
195
|
+
id: `${String(log.id)}-anthropic-tool-${String(index)}`,
|
|
196
|
+
index,
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function extractOpenAIChunkToolTraceEvents(log: CapturedLog): ToolTraceEvent[] {
|
|
201
|
+
const chunks = log.streamingChunks?.chunks ?? [];
|
|
202
|
+
const events: ToolTraceEvent[] = [];
|
|
203
|
+
const seen = new Set<string>();
|
|
204
|
+
|
|
205
|
+
for (const chunk of chunks) {
|
|
206
|
+
const data = chunkData(chunk);
|
|
207
|
+
const choices = safeGetOwnProperty(data, "choices");
|
|
208
|
+
if (Array.isArray(choices)) {
|
|
209
|
+
for (const choice of choices) {
|
|
210
|
+
const delta = safeGetOwnProperty(choice, "delta");
|
|
211
|
+
const toolCalls = safeGetOwnProperty(delta, "tool_calls");
|
|
212
|
+
if (Array.isArray(toolCalls)) {
|
|
213
|
+
for (const call of toolCalls) {
|
|
214
|
+
const fn = safeGetOwnProperty(call, "function");
|
|
215
|
+
const name = safeGetOwnProperty(fn, "name");
|
|
216
|
+
if (typeof name !== "string" || name.length === 0) continue;
|
|
217
|
+
const args = safeGetOwnProperty(fn, "arguments");
|
|
218
|
+
appendUniqueEvent(
|
|
219
|
+
events,
|
|
220
|
+
seen,
|
|
221
|
+
makeEvent({ log, provider: "openai", index: events.length, name, input: args }),
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const functionCall = safeGetOwnProperty(delta, "function_call");
|
|
226
|
+
const functionCallName = safeGetOwnProperty(functionCall, "name");
|
|
227
|
+
if (typeof functionCallName === "string" && functionCallName.length > 0) {
|
|
228
|
+
const args = safeGetOwnProperty(functionCall, "arguments");
|
|
229
|
+
appendUniqueEvent(
|
|
230
|
+
events,
|
|
231
|
+
seen,
|
|
232
|
+
makeEvent({
|
|
233
|
+
log,
|
|
234
|
+
provider: "openai",
|
|
235
|
+
index: events.length,
|
|
236
|
+
name: functionCallName,
|
|
237
|
+
input: args,
|
|
238
|
+
}),
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const item = safeGetOwnProperty(data, "item");
|
|
245
|
+
if (safeGetOwnProperty(item, "type") === "function_call") {
|
|
246
|
+
const name = safeGetOwnProperty(item, "name");
|
|
247
|
+
if (typeof name === "string" && name.length > 0) {
|
|
248
|
+
const args = safeGetOwnProperty(item, "arguments");
|
|
249
|
+
appendUniqueEvent(
|
|
250
|
+
events,
|
|
251
|
+
seen,
|
|
252
|
+
makeEvent({ log, provider: "openai", index: events.length, name, input: args }),
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return events.map((event, index) => ({
|
|
259
|
+
...event,
|
|
260
|
+
id: `${String(log.id)}-openai-tool-${String(index)}`,
|
|
261
|
+
index,
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function extractToolTraceEvents(log: CapturedLog): ToolTraceEvent[] {
|
|
266
|
+
if (log.toolTraceEvents !== undefined && log.toolTraceEvents.length > 0) {
|
|
267
|
+
return log.toolTraceEvents;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const format = resolveLogFormat(log);
|
|
271
|
+
if (responseMayContainToolTrace(log, format)) {
|
|
272
|
+
switch (format) {
|
|
273
|
+
case "anthropic":
|
|
274
|
+
return extractAnthropicResponseToolTraceEvents(log);
|
|
275
|
+
case "openai":
|
|
276
|
+
return extractOpenAIResponseToolTraceEvents(log);
|
|
277
|
+
case "unknown":
|
|
278
|
+
return [];
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
switch (format) {
|
|
283
|
+
case "anthropic":
|
|
284
|
+
return extractAnthropicChunkToolTraceEvents(log);
|
|
285
|
+
case "openai":
|
|
286
|
+
return extractOpenAIChunkToolTraceEvents(log);
|
|
287
|
+
case "unknown":
|
|
288
|
+
return [];
|
|
289
|
+
}
|
|
290
|
+
}
|
package/src/proxy/logIndex.ts
CHANGED
|
@@ -9,8 +9,10 @@ import {
|
|
|
9
9
|
type ApiFormat,
|
|
10
10
|
type CaptureIncompleteReason,
|
|
11
11
|
type CapturedLog,
|
|
12
|
+
type ToolTraceEvent,
|
|
12
13
|
} from "./schemas";
|
|
13
14
|
import { textByteLength } from "../lib/unknown";
|
|
15
|
+
import { extractToolTraceEvents } from "../lib/toolTrace";
|
|
14
16
|
import {
|
|
15
17
|
getSqliteLogIndexMaxId,
|
|
16
18
|
replaceSqliteLogIndexEntries,
|
|
@@ -60,6 +62,7 @@ export type LogIndexSummary = {
|
|
|
60
62
|
droppedBytes: number;
|
|
61
63
|
captureIncompleteReason: CaptureIncompleteReason | null;
|
|
62
64
|
warnings?: string[];
|
|
65
|
+
toolTraceEvents?: ToolTraceEvent[];
|
|
63
66
|
error: string | null;
|
|
64
67
|
};
|
|
65
68
|
|
|
@@ -138,6 +141,7 @@ export function createLogIndexEntryMetadata(log: CapturedLog): LogIndexEntryMeta
|
|
|
138
141
|
droppedBytes: log.droppedBytes ?? 0,
|
|
139
142
|
captureIncompleteReason: log.captureIncompleteReason ?? null,
|
|
140
143
|
warnings: log.warnings,
|
|
144
|
+
toolTraceEvents: extractToolTraceEvents(log),
|
|
141
145
|
error: log.error ?? null,
|
|
142
146
|
},
|
|
143
147
|
};
|
package/src/proxy/schemas.ts
CHANGED
|
@@ -5,6 +5,7 @@ export {
|
|
|
5
5
|
CapturedLogSchema,
|
|
6
6
|
JsonValueSchema,
|
|
7
7
|
StreamingChunkSchema,
|
|
8
|
+
ToolTraceEventSchema,
|
|
8
9
|
} from "../contracts";
|
|
9
10
|
export type {
|
|
10
11
|
CapturedLog,
|
|
@@ -12,6 +13,7 @@ export type {
|
|
|
12
13
|
CaptureIncompleteReason,
|
|
13
14
|
JsonValue,
|
|
14
15
|
StreamingChunk,
|
|
16
|
+
ToolTraceEvent,
|
|
15
17
|
TokenUsage,
|
|
16
18
|
} from "../contracts";
|
|
17
19
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { logger, resolveLogDir } from "./logger";
|
|
4
|
-
import type
|
|
4
|
+
import { ToolTraceEventSchema, type ApiFormat, type CaptureIncompleteReason } from "./schemas";
|
|
5
5
|
import type { LogIndexEntry, LogIndexSummary } from "./logIndex";
|
|
6
6
|
import { ensurePrivateDirectorySync, securePrivateFileSync } from "./privateDataPath";
|
|
7
7
|
|
|
@@ -52,6 +52,7 @@ CREATE TABLE IF NOT EXISTS log_index (
|
|
|
52
52
|
dropped_bytes INTEGER NOT NULL DEFAULT 0,
|
|
53
53
|
capture_incomplete_reason TEXT,
|
|
54
54
|
warnings_json TEXT,
|
|
55
|
+
tool_trace_events_json TEXT,
|
|
55
56
|
error TEXT
|
|
56
57
|
);
|
|
57
58
|
CREATE INDEX IF NOT EXISTS idx_log_index_session_id_id ON log_index(session_id, id);
|
|
@@ -98,9 +99,10 @@ INSERT INTO log_index (
|
|
|
98
99
|
dropped_bytes,
|
|
99
100
|
capture_incomplete_reason,
|
|
100
101
|
warnings_json,
|
|
102
|
+
tool_trace_events_json,
|
|
101
103
|
error
|
|
102
104
|
) VALUES (
|
|
103
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
105
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
104
106
|
)
|
|
105
107
|
ON CONFLICT(id) DO UPDATE SET
|
|
106
108
|
file = excluded.file,
|
|
@@ -139,6 +141,7 @@ ON CONFLICT(id) DO UPDATE SET
|
|
|
139
141
|
dropped_bytes = excluded.dropped_bytes,
|
|
140
142
|
capture_incomplete_reason = excluded.capture_incomplete_reason,
|
|
141
143
|
warnings_json = excluded.warnings_json,
|
|
144
|
+
tool_trace_events_json = excluded.tool_trace_events_json,
|
|
142
145
|
error = excluded.error
|
|
143
146
|
`;
|
|
144
147
|
|
|
@@ -232,6 +235,7 @@ const REQUIRED_SCHEMA_COLUMNS = [
|
|
|
232
235
|
{ name: "dropped_chunks", definition: "INTEGER NOT NULL DEFAULT 0" },
|
|
233
236
|
{ name: "dropped_bytes", definition: "INTEGER NOT NULL DEFAULT 0" },
|
|
234
237
|
{ name: "capture_incomplete_reason", definition: "TEXT" },
|
|
238
|
+
{ name: "tool_trace_events_json", definition: "TEXT" },
|
|
235
239
|
];
|
|
236
240
|
|
|
237
241
|
function ensureSchemaColumns(db: unknown): boolean {
|
|
@@ -394,6 +398,10 @@ function warningJson(warnings: string[] | undefined): string | null {
|
|
|
394
398
|
return warnings === undefined ? null : JSON.stringify(warnings);
|
|
395
399
|
}
|
|
396
400
|
|
|
401
|
+
function toolTraceEventsJson(summary: LogIndexSummary | undefined): string | null {
|
|
402
|
+
return summary?.toolTraceEvents === undefined ? null : JSON.stringify(summary.toolTraceEvents);
|
|
403
|
+
}
|
|
404
|
+
|
|
397
405
|
function boolToInt(value: boolean): number {
|
|
398
406
|
return value ? 1 : 0;
|
|
399
407
|
}
|
|
@@ -438,6 +446,7 @@ function entryParams(entry: LogIndexEntry): readonly unknown[] {
|
|
|
438
446
|
summary?.droppedBytes ?? 0,
|
|
439
447
|
summary?.captureIncompleteReason ?? null,
|
|
440
448
|
warningJson(summary?.warnings),
|
|
449
|
+
toolTraceEventsJson(summary),
|
|
441
450
|
summary?.error ?? null,
|
|
442
451
|
];
|
|
443
452
|
}
|
|
@@ -504,6 +513,18 @@ function readWarnings(row: unknown): string[] | undefined {
|
|
|
504
513
|
}
|
|
505
514
|
}
|
|
506
515
|
|
|
516
|
+
function readToolTraceEvents(row: unknown): LogIndexSummary["toolTraceEvents"] {
|
|
517
|
+
const value = readString(row, "tool_trace_events_json");
|
|
518
|
+
if (value === null) return undefined;
|
|
519
|
+
try {
|
|
520
|
+
const parsed: unknown = JSON.parse(value);
|
|
521
|
+
const result = ToolTraceEventSchema.array().safeParse(parsed);
|
|
522
|
+
return result.success ? result.data : undefined;
|
|
523
|
+
} catch {
|
|
524
|
+
return undefined;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
507
528
|
function readRequiredNumber(row: unknown, name: string): number | null {
|
|
508
529
|
const value = readNumber(row, name);
|
|
509
530
|
return value === null || !Number.isInteger(value) ? null : value;
|
|
@@ -545,6 +566,7 @@ function summaryFromRow(row: unknown, id: number): LogIndexSummary {
|
|
|
545
566
|
droppedBytes: readNumber(row, "dropped_bytes") ?? 0,
|
|
546
567
|
captureIncompleteReason: readCaptureIncompleteReason(row),
|
|
547
568
|
warnings: readWarnings(row),
|
|
569
|
+
toolTraceEvents: readToolTraceEvents(row),
|
|
548
570
|
error: readString(row, "error"),
|
|
549
571
|
};
|
|
550
572
|
}
|
package/src/proxy/store.ts
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
import { writeChunks } from "./chunkStorage";
|
|
37
37
|
import { closeSqliteLogIndex } from "./sqliteLogIndex";
|
|
38
38
|
import type { SessionInfo, SessionLogSummary } from "../lib/sessionInfoContract";
|
|
39
|
+
import { extractToolTraceEvents } from "../lib/toolTrace";
|
|
39
40
|
import { textByteLength } from "../lib/unknown";
|
|
40
41
|
import { buildSessionInfo, buildSessionLogSummary } from "./sessionInfo";
|
|
41
42
|
import type { CapturedLog } from "./schemas";
|
|
@@ -338,6 +339,7 @@ export function compactLogForList(log: CapturedLog): CapturedLog {
|
|
|
338
339
|
rawHeaders: undefined,
|
|
339
340
|
headers: undefined,
|
|
340
341
|
streamingChunks: undefined,
|
|
342
|
+
toolTraceEvents: log.toolTraceEvents ?? extractToolTraceEvents(log),
|
|
341
343
|
rawRequestBodyBytes: log.rawRequestBodyBytes ?? textByteLength(log.rawRequestBody),
|
|
342
344
|
responseTextBytes: log.responseTextBytes ?? textByteLength(log.responseText),
|
|
343
345
|
bodyContentMode: "compact",
|
|
@@ -383,6 +385,7 @@ function compactLogFromIndexSummary(summary: LogIndexSummary): CapturedLog {
|
|
|
383
385
|
droppedBytes: summary.droppedBytes,
|
|
384
386
|
captureIncompleteReason: summary.captureIncompleteReason,
|
|
385
387
|
warnings: summary.warnings,
|
|
388
|
+
toolTraceEvents: summary.toolTraceEvents,
|
|
386
389
|
error: summary.error,
|
|
387
390
|
});
|
|
388
391
|
}
|