@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.
Files changed (41) hide show
  1. package/.output/backend/nitro.json +1 -1
  2. package/.output/cli.js +14 -4
  3. package/.output/server/_ssr/index.mjs +1 -1
  4. package/.output/server/_ssr/{router-6IijwoYv.mjs → router-Lc1aOs3g.mjs} +270 -3
  5. package/.output/server/index.mjs +1 -1
  6. package/.output/ui/assets/{CompareDrawer-CBqIDEv5.js → CompareDrawer-YdfnAMiC.js} +1 -1
  7. package/.output/ui/assets/{InspectorPet-Cx21mu-r.js → InspectorPet-Bt0vxGL8.js} +1 -1
  8. package/.output/ui/assets/ProxyViewerContainer-D4_GM20S.js +59 -0
  9. package/.output/ui/assets/{ReplayDialog-5h3GQEg_.js → ReplayDialog-9BWotxhQ.js} +1 -1
  10. package/.output/ui/assets/{RequestAnatomy-CAlwQcTB.js → RequestAnatomy-0VrsjZmK.js} +1 -1
  11. package/.output/ui/assets/ResponseView-mmW2Z9sl.js +2 -0
  12. package/.output/ui/assets/StreamingChunkSequence-ZC90PIBc.js +1 -0
  13. package/.output/ui/assets/{_sessionId-1G3bJYx7.js → _sessionId-C84un6eF.js} +1 -1
  14. package/.output/ui/assets/{_sessionId-BtmyX4Vb.js → _sessionId-CB7Eytuo.js} +1 -1
  15. package/.output/ui/assets/{index--SNPe17S.js → index-2feCKPew.js} +2 -2
  16. package/.output/ui/assets/{index-CtSb9B-7.js → index-7oLdSt17.js} +1 -1
  17. package/.output/ui/assets/{index-CfAo7qLu.js → index-BswExFrv.js} +1 -1
  18. package/.output/ui/assets/index-IBEDJoV_.css +1 -0
  19. package/.output/ui/assets/{index-DPCSM3Ig.js → index-YEgoJRUl.js} +1 -1
  20. package/.output/ui/assets/{json-viewer-BC3R5htL.js → json-viewer-CJ96a0P_.js} +1 -1
  21. package/.output/ui/assets/{jszip.min-DEdQuE_J.js → jszip.min-BDO1ePnu.js} +1 -1
  22. package/.output/ui/index.html +2 -2
  23. package/.output/workers/logFinalizer.worker.js +10 -0
  24. package/.output/workers/sessionWorkerEntry.js +10 -0
  25. package/package.json +4 -4
  26. package/src/components/proxy-viewer/AnswerMarkdown.tsx +37 -7
  27. package/src/components/proxy-viewer/LogEntry.tsx +82 -32
  28. package/src/components/proxy-viewer/ResponseView.tsx +33 -3
  29. package/src/components/proxy-viewer/StreamingChunkSequence.tsx +60 -3
  30. package/src/components/proxy-viewer/viewerState.ts +11 -169
  31. package/src/contracts/index.ts +2 -0
  32. package/src/contracts/log.ts +13 -0
  33. package/src/lib/toolTrace.ts +290 -0
  34. package/src/proxy/logIndex.ts +4 -0
  35. package/src/proxy/schemas.ts +2 -0
  36. package/src/proxy/sqliteLogIndex.ts +24 -2
  37. package/src/proxy/store.ts +3 -0
  38. package/.output/ui/assets/ProxyViewerContainer-Btek1Lgk.js +0 -59
  39. package/.output/ui/assets/ResponseView-B0QKCR9M.js +0 -2
  40. package/.output/ui/assets/StreamingChunkSequence-DXbDSW9D.js +0 -1
  41. 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
+ }
@@ -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
  };
@@ -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 { ApiFormat, CaptureIncompleteReason } from "./schemas";
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
  }
@@ -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
  }