@tonyclaw/agent-inspector 2.1.21 → 2.1.23
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-Pii_cIqn.js → CompareDrawer-Cvgl_Jmi.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-CNLLZq37.js +106 -0
- package/.output/public/assets/{ReplayDialog-BqnYC8ze.js → ReplayDialog-DeEELx0X.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-I8iBshz1.js → RequestAnatomy-CrLSqdiA.js} +1 -1
- package/.output/public/assets/ResponseView-CFIqDL5E.js +3 -0
- package/.output/public/assets/{StreamingChunkSequence-BrSx21IQ.js → StreamingChunkSequence-DSRjbgqb.js} +1 -1
- package/.output/public/assets/_sessionId-ZuH6-ZX5.js +1 -0
- package/.output/public/assets/index-B26q0-ch.js +1 -0
- package/.output/public/assets/{index-Dw-jXKKY.js → index-BZxLBVYd.js} +1 -1
- package/.output/public/assets/index-CxszCIFm.css +1 -0
- package/.output/public/assets/{json-viewer-D5mzV0oV.js → json-viewer-DERQNZRV.js} +1 -1
- package/.output/public/assets/{main-B7OIQMeA.js → main-BoshTI0W.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +42 -42
- package/.output/server/{_sessionId-DeU_JdSX.mjs → _sessionId-BYGtH1pK.mjs} +18 -3
- package/.output/server/_ssr/{CompareDrawer-DgDbG4oF.mjs → CompareDrawer-OchnKiac.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-BOszhOJD.mjs → ProxyViewerContainer-C2lwYwDu.mjs} +2747 -2639
- package/.output/server/_ssr/{ReplayDialog-Cj4gWQv5.mjs → ReplayDialog-BYwaF14u.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-CVzNAA03.mjs → RequestAnatomy-DTueiqT5.mjs} +2 -2
- package/.output/server/_ssr/{ResponseView-DLNpai6P.mjs → ResponseView-CqB9NKHB.mjs} +202 -55
- package/.output/server/_ssr/{StreamingChunkSequence-BiH8EEQB.mjs → StreamingChunkSequence-DENB9VUj.mjs} +2 -2
- package/.output/server/_ssr/{index-DaWXoUgf.mjs → index-BR3ea3Zb.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-CqqNYbmS.mjs → json-viewer-BU9ZUaHK.mjs} +3 -3
- package/.output/server/_ssr/{router-Di7D0Bcm.mjs → router-DT6BcrEg.mjs} +4 -4
- package/.output/server/{_tanstack-start-manifest_v-BR0YvW29.mjs → _tanstack-start-manifest_v-xQ1BRoZw.mjs} +1 -1
- package/.output/server/index.mjs +75 -75
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +71 -15
- package/src/components/ProxyViewerContainer.tsx +1 -1
- package/src/components/proxy-viewer/ConversationGroupList.tsx +23 -7
- package/src/components/proxy-viewer/ConversationHeader.tsx +14 -7
- package/src/components/proxy-viewer/LogEntry.tsx +10 -0
- package/src/components/proxy-viewer/ResponseView.tsx +5 -1
- package/src/components/proxy-viewer/ToolTraceEvents.tsx +19 -31
- package/src/components/proxy-viewer/TurnGroup.tsx +21 -7
- package/src/components/proxy-viewer/formats/anthropic/ContentBlocks.tsx +37 -4
- package/src/components/proxy-viewer/formats/anthropic/ResponseView.tsx +17 -3
- package/src/components/proxy-viewer/formats/index.tsx +8 -3
- package/src/components/proxy-viewer/formats/openai/ResponseView.tsx +122 -16
- package/src/components/proxy-viewer/logFocus.ts +11 -3
- package/src/components/proxy-viewer/viewerState.ts +22 -7
- package/src/routes/session/$sessionId.tsx +27 -1
- package/.output/public/assets/ProxyViewerContainer-BxselkhN.js +0 -106
- package/.output/public/assets/ResponseView-wZzskUas.js +0 -3
- package/.output/public/assets/_sessionId-ByxbBMbE.js +0 -1
- package/.output/public/assets/index-BjzVMXJq.css +0 -1
- package/.output/public/assets/index-DRCRDo_E.js +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Brain, ChevronDown, ChevronRight, Terminal } from "lucide-react";
|
|
2
|
-
import { type JSX, memo, Suspense, useState } from "react";
|
|
2
|
+
import { type JSX, memo, Suspense, useEffect, useRef, useState } from "react";
|
|
3
3
|
import type { ResponseContentBlockType } from "../../../../contracts/anthropic";
|
|
4
|
+
import type { ResponseToolFocus } from "../index";
|
|
4
5
|
import { AnswerMarkdown } from "../../AnswerMarkdown";
|
|
5
6
|
import { Badge } from "../../../ui/badge";
|
|
6
7
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../../../ui/collapsible";
|
|
@@ -99,16 +100,37 @@ export const ThinkingBlock = memo(function ThinkingBlock({
|
|
|
99
100
|
export const ToolUseBlock = memo(function ToolUseBlock({
|
|
100
101
|
name,
|
|
101
102
|
input,
|
|
103
|
+
toolIndex,
|
|
104
|
+
toolFocus,
|
|
102
105
|
}: {
|
|
103
106
|
name: string;
|
|
104
107
|
input: Record<string, unknown>;
|
|
108
|
+
toolIndex: number;
|
|
109
|
+
toolFocus?: ResponseToolFocus;
|
|
105
110
|
}): JSX.Element {
|
|
106
111
|
const [open, setOpen] = useState(false);
|
|
112
|
+
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
113
|
+
const shouldFocus = toolFocus?.focusedToolIndex === toolIndex;
|
|
114
|
+
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (!shouldFocus || toolFocus === undefined || toolFocus.toolFocusNonce <= 0) return undefined;
|
|
117
|
+
setOpen(true);
|
|
118
|
+
const timer = window.setTimeout(() => {
|
|
119
|
+
const trigger = triggerRef.current;
|
|
120
|
+
if (trigger === null) return;
|
|
121
|
+
trigger.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
122
|
+
trigger.focus({ preventScroll: true });
|
|
123
|
+
}, 0);
|
|
124
|
+
return () => window.clearTimeout(timer);
|
|
125
|
+
}, [shouldFocus, toolFocus, toolFocus?.toolFocusNonce]);
|
|
107
126
|
|
|
108
127
|
return (
|
|
109
128
|
<Collapsible open={open} onOpenChange={setOpen}>
|
|
110
|
-
<div className="border-l-2 border-sky-400/25 my-1">
|
|
111
|
-
<CollapsibleTrigger
|
|
129
|
+
<div className="border-l-2 border-sky-400/25 my-1" data-response-tool-index={toolIndex}>
|
|
130
|
+
<CollapsibleTrigger
|
|
131
|
+
ref={triggerRef}
|
|
132
|
+
className="flex items-center gap-1.5 px-3 py-1 w-full text-left cursor-pointer hover:bg-sky-400/[0.04] transition-colors rounded-r-sm group focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
133
|
+
>
|
|
112
134
|
<Terminal className="size-3.5 text-sky-400/70 shrink-0" />
|
|
113
135
|
<Badge variant="outline" className="text-[10px] font-mono px-1.5 py-0 h-4">
|
|
114
136
|
{name}
|
|
@@ -140,8 +162,12 @@ export const ToolUseBlock = memo(function ToolUseBlock({
|
|
|
140
162
|
|
|
141
163
|
export const ResponseContentBlockRenderer = memo(function ResponseContentBlockRenderer({
|
|
142
164
|
block,
|
|
165
|
+
toolIndex,
|
|
166
|
+
toolFocus,
|
|
143
167
|
}: {
|
|
144
168
|
block: ResponseContentBlockType;
|
|
169
|
+
toolIndex?: number;
|
|
170
|
+
toolFocus?: ResponseToolFocus;
|
|
145
171
|
}): JSX.Element {
|
|
146
172
|
switch (block.type) {
|
|
147
173
|
case "text":
|
|
@@ -150,7 +176,14 @@ export const ResponseContentBlockRenderer = memo(function ResponseContentBlockRe
|
|
|
150
176
|
case "think":
|
|
151
177
|
return <ThinkingBlock thinking={block.thinking} />;
|
|
152
178
|
case "tool_use":
|
|
153
|
-
return
|
|
179
|
+
return (
|
|
180
|
+
<ToolUseBlock
|
|
181
|
+
name={block.name}
|
|
182
|
+
input={block.input}
|
|
183
|
+
toolIndex={toolIndex ?? 0}
|
|
184
|
+
toolFocus={toolFocus}
|
|
185
|
+
/>
|
|
186
|
+
);
|
|
154
187
|
default:
|
|
155
188
|
return assertNever(block);
|
|
156
189
|
}
|
|
@@ -4,13 +4,18 @@ import type { AnthropicResponse } from "../../../../contracts/anthropic";
|
|
|
4
4
|
import { formatTokens } from "../../../../lib/utils";
|
|
5
5
|
import { Badge } from "../../../ui/badge";
|
|
6
6
|
import { Separator } from "../../../ui/separator";
|
|
7
|
+
import type { ResponseToolFocus } from "../index";
|
|
7
8
|
import { ResponseContentBlockRenderer } from "./ContentBlocks";
|
|
8
9
|
|
|
9
10
|
export const StructuredResponseViewAnthropic = memo(function StructuredResponseViewAnthropic({
|
|
10
11
|
response,
|
|
12
|
+
toolFocus,
|
|
11
13
|
}: {
|
|
12
14
|
response: AnthropicResponse;
|
|
15
|
+
toolFocus?: ResponseToolFocus;
|
|
13
16
|
}): JSX.Element {
|
|
17
|
+
let toolIndex = 0;
|
|
18
|
+
|
|
14
19
|
return (
|
|
15
20
|
<div className="space-y-3">
|
|
16
21
|
<div className="flex items-center gap-2 flex-wrap">
|
|
@@ -54,9 +59,18 @@ export const StructuredResponseViewAnthropic = memo(function StructuredResponseV
|
|
|
54
59
|
<Separator className="opacity-50" />
|
|
55
60
|
|
|
56
61
|
<div className="space-y-2">
|
|
57
|
-
{response.content.map((block, i) =>
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
{response.content.map((block, i) => {
|
|
63
|
+
const currentToolIndex = block.type === "tool_use" ? toolIndex : undefined;
|
|
64
|
+
if (block.type === "tool_use") toolIndex += 1;
|
|
65
|
+
return (
|
|
66
|
+
<ResponseContentBlockRenderer
|
|
67
|
+
key={i}
|
|
68
|
+
block={block}
|
|
69
|
+
toolIndex={currentToolIndex}
|
|
70
|
+
toolFocus={toolFocus}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
})}
|
|
60
74
|
{response.content.length === 0 && (
|
|
61
75
|
<p className="text-xs text-muted-foreground italic">Empty response content</p>
|
|
62
76
|
)}
|
|
@@ -28,15 +28,20 @@ function isAnthropicResponse(response: unknown): response is AnthropicResponse {
|
|
|
28
28
|
export function formatViewFor(
|
|
29
29
|
apiFormat: ApiFormat | undefined,
|
|
30
30
|
response: unknown,
|
|
31
|
+
toolFocus?: ResponseToolFocus,
|
|
31
32
|
): JSX.Element | null {
|
|
32
33
|
if (apiFormat === "openai" && isOpenAIResponse(response)) {
|
|
33
|
-
return <OpenAIResponseView response={response} />;
|
|
34
|
+
return <OpenAIResponseView response={response} toolFocus={toolFocus} />;
|
|
34
35
|
}
|
|
35
36
|
if (apiFormat === "openai" && isOpenAIResponsesResponse(response)) {
|
|
36
|
-
return <OpenAIResponsesResponseView response={response} />;
|
|
37
|
+
return <OpenAIResponsesResponseView response={response} toolFocus={toolFocus} />;
|
|
37
38
|
}
|
|
38
39
|
if (apiFormat === "anthropic" && isAnthropicResponse(response)) {
|
|
39
|
-
return <StructuredResponseViewAnthropic response={response} />;
|
|
40
|
+
return <StructuredResponseViewAnthropic response={response} toolFocus={toolFocus} />;
|
|
40
41
|
}
|
|
41
42
|
return null;
|
|
42
43
|
}
|
|
44
|
+
export type ResponseToolFocus = {
|
|
45
|
+
focusedToolIndex: number | null;
|
|
46
|
+
toolFocusNonce: number;
|
|
47
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StopCircle, Terminal, Zap } from "lucide-react";
|
|
2
|
-
import { memo, Suspense, useState, type JSX } from "react";
|
|
2
|
+
import { memo, Suspense, useEffect, useRef, useState, type JSX, type RefObject } from "react";
|
|
3
3
|
import type {
|
|
4
4
|
OpenAIResponse,
|
|
5
5
|
OpenAIResponsesResponse,
|
|
@@ -17,6 +17,7 @@ import { ChevronDown, ChevronRight } from "lucide-react";
|
|
|
17
17
|
import { ThinkingBlock } from "../anthropic/ContentBlocks";
|
|
18
18
|
import { extractThinkingFromContent } from "../anthropic/thinkingExtract";
|
|
19
19
|
import { LazyJsonViewer } from "../../lazy";
|
|
20
|
+
import type { ResponseToolFocus } from "../index";
|
|
20
21
|
|
|
21
22
|
// Re-export for use in other components
|
|
22
23
|
export { extractThinkingFromContent } from "../anthropic/thinkingExtract";
|
|
@@ -36,15 +37,95 @@ function parseToolArguments(raw: string | undefined): unknown {
|
|
|
36
37
|
/** One collapsible tool_use row, mirroring the Anthropic ToolUseBlock visual
|
|
37
38
|
* treatment (Terminal icon, soft tool accent, name as a Badge, JSON input in
|
|
38
39
|
* a scrollable JsonViewer). */
|
|
39
|
-
function
|
|
40
|
+
function useToolFocus({
|
|
41
|
+
toolIndex,
|
|
42
|
+
toolFocus,
|
|
43
|
+
setOpen,
|
|
44
|
+
}: {
|
|
45
|
+
toolIndex: number;
|
|
46
|
+
toolFocus?: ResponseToolFocus;
|
|
47
|
+
setOpen: (open: boolean) => void;
|
|
48
|
+
}): RefObject<HTMLButtonElement | null> {
|
|
49
|
+
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
50
|
+
const shouldFocus = toolFocus?.focusedToolIndex === toolIndex;
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!shouldFocus || toolFocus === undefined || toolFocus.toolFocusNonce <= 0) return undefined;
|
|
54
|
+
setOpen(true);
|
|
55
|
+
const timer = window.setTimeout(() => {
|
|
56
|
+
const trigger = triggerRef.current;
|
|
57
|
+
if (trigger === null) return;
|
|
58
|
+
trigger.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
59
|
+
trigger.focus({ preventScroll: true });
|
|
60
|
+
}, 0);
|
|
61
|
+
return () => window.clearTimeout(timer);
|
|
62
|
+
}, [setOpen, shouldFocus, toolFocus, toolFocus?.toolFocusNonce]);
|
|
63
|
+
|
|
64
|
+
return triggerRef;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function OpenAIFunctionCallBlock({
|
|
68
|
+
name,
|
|
69
|
+
argumentsText,
|
|
70
|
+
toolIndex,
|
|
71
|
+
toolFocus,
|
|
72
|
+
}: {
|
|
73
|
+
name: string | undefined;
|
|
74
|
+
argumentsText: string | undefined;
|
|
75
|
+
toolIndex: number;
|
|
76
|
+
toolFocus?: ResponseToolFocus;
|
|
77
|
+
}): JSX.Element {
|
|
78
|
+
const blockRef = useRef<HTMLDivElement | null>(null);
|
|
79
|
+
const shouldFocus = toolFocus?.focusedToolIndex === toolIndex;
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!shouldFocus || toolFocus === undefined || toolFocus.toolFocusNonce <= 0) return undefined;
|
|
83
|
+
const timer = window.setTimeout(() => {
|
|
84
|
+
const block = blockRef.current;
|
|
85
|
+
if (block === null) return;
|
|
86
|
+
block.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
87
|
+
block.focus({ preventScroll: true });
|
|
88
|
+
}, 0);
|
|
89
|
+
return () => window.clearTimeout(timer);
|
|
90
|
+
}, [shouldFocus, toolFocus, toolFocus?.toolFocusNonce]);
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div
|
|
94
|
+
ref={blockRef}
|
|
95
|
+
tabIndex={-1}
|
|
96
|
+
className="border border-sky-400/20 rounded-md p-3 bg-muted/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
97
|
+
data-response-tool-index={toolIndex}
|
|
98
|
+
>
|
|
99
|
+
<div className="text-xs text-sky-400/80 font-mono mb-1">function_call</div>
|
|
100
|
+
<div className="font-mono text-xs">
|
|
101
|
+
<span className="text-foreground/80">{name}</span>
|
|
102
|
+
<span className="text-muted-foreground">({argumentsText})</span>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function OpenAIToolCallBlock({
|
|
109
|
+
call,
|
|
110
|
+
toolIndex,
|
|
111
|
+
toolFocus,
|
|
112
|
+
}: {
|
|
113
|
+
call: OpenAIToolCall;
|
|
114
|
+
toolIndex: number;
|
|
115
|
+
toolFocus?: ResponseToolFocus;
|
|
116
|
+
}): JSX.Element {
|
|
40
117
|
const [open, setOpen] = useState(false);
|
|
41
118
|
const name = call.function.name ?? "(unnamed tool)";
|
|
42
119
|
const parsed = open ? parseToolArguments(call.function.arguments) : {};
|
|
120
|
+
const triggerRef = useToolFocus({ toolIndex, toolFocus, setOpen });
|
|
43
121
|
|
|
44
122
|
return (
|
|
45
123
|
<Collapsible open={open} onOpenChange={setOpen}>
|
|
46
|
-
<div className="border-l-2 border-sky-400/25 my-1">
|
|
47
|
-
<CollapsibleTrigger
|
|
124
|
+
<div className="border-l-2 border-sky-400/25 my-1" data-response-tool-index={toolIndex}>
|
|
125
|
+
<CollapsibleTrigger
|
|
126
|
+
ref={triggerRef}
|
|
127
|
+
className="flex items-center gap-1.5 px-3 py-1 w-full text-left cursor-pointer hover:bg-sky-400/[0.04] transition-colors rounded-r-sm group focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
128
|
+
>
|
|
48
129
|
<Terminal className="size-3.5 text-sky-400/70 shrink-0" />
|
|
49
130
|
<Badge variant="outline" className="text-[10px] font-mono px-1.5 py-0 h-4">
|
|
50
131
|
{name}
|
|
@@ -136,15 +217,27 @@ function extractResponsesFunctionCalls(response: OpenAIResponsesResponse): Respo
|
|
|
136
217
|
return calls;
|
|
137
218
|
}
|
|
138
219
|
|
|
139
|
-
function ResponsesFunctionCallBlock({
|
|
220
|
+
function ResponsesFunctionCallBlock({
|
|
221
|
+
call,
|
|
222
|
+
toolIndex,
|
|
223
|
+
toolFocus,
|
|
224
|
+
}: {
|
|
225
|
+
call: ResponsesFunctionCall;
|
|
226
|
+
toolIndex: number;
|
|
227
|
+
toolFocus?: ResponseToolFocus;
|
|
228
|
+
}): JSX.Element {
|
|
140
229
|
const [open, setOpen] = useState(false);
|
|
141
230
|
const parsed = open ? parseToolArguments(call.argumentsText) : {};
|
|
142
231
|
const identity = call.callId ?? call.id;
|
|
232
|
+
const triggerRef = useToolFocus({ toolIndex, toolFocus, setOpen });
|
|
143
233
|
|
|
144
234
|
return (
|
|
145
235
|
<Collapsible open={open} onOpenChange={setOpen}>
|
|
146
|
-
<div className="border-l-2 border-sky-400/25 my-1">
|
|
147
|
-
<CollapsibleTrigger
|
|
236
|
+
<div className="border-l-2 border-sky-400/25 my-1" data-response-tool-index={toolIndex}>
|
|
237
|
+
<CollapsibleTrigger
|
|
238
|
+
ref={triggerRef}
|
|
239
|
+
className="flex items-center gap-1.5 px-3 py-1 w-full text-left cursor-pointer hover:bg-sky-400/[0.04] transition-colors rounded-r-sm group focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
240
|
+
>
|
|
148
241
|
<Terminal className="size-3.5 text-sky-400/70 shrink-0" />
|
|
149
242
|
<Badge variant="outline" className="text-[10px] font-mono px-1.5 py-0 h-4">
|
|
150
243
|
{call.name}
|
|
@@ -192,8 +285,10 @@ function ResponsesFunctionCallBlock({ call }: { call: ResponsesFunctionCall }):
|
|
|
192
285
|
|
|
193
286
|
export const OpenAIResponseView = memo(function OpenAIResponseView({
|
|
194
287
|
response,
|
|
288
|
+
toolFocus,
|
|
195
289
|
}: {
|
|
196
290
|
response: OpenAIResponse;
|
|
291
|
+
toolFocus?: ResponseToolFocus;
|
|
197
292
|
}): JSX.Element {
|
|
198
293
|
const choice = response.choices[0];
|
|
199
294
|
const message = choice?.message;
|
|
@@ -253,16 +348,20 @@ export const OpenAIResponseView = memo(function OpenAIResponseView({
|
|
|
253
348
|
})()}
|
|
254
349
|
{toolCalls.map((call, i) => (
|
|
255
350
|
// biome-ignore lint/suspicious/noArrayIndexKey: tool_calls is the positionally stable list from the response
|
|
256
|
-
<OpenAIToolCallBlock
|
|
351
|
+
<OpenAIToolCallBlock
|
|
352
|
+
key={call.id ?? `tc-${i}`}
|
|
353
|
+
call={call}
|
|
354
|
+
toolIndex={i}
|
|
355
|
+
toolFocus={toolFocus}
|
|
356
|
+
/>
|
|
257
357
|
))}
|
|
258
358
|
{message?.function_call !== null && message?.function_call !== undefined && (
|
|
259
|
-
<
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
</div>
|
|
359
|
+
<OpenAIFunctionCallBlock
|
|
360
|
+
name={message.function_call.name}
|
|
361
|
+
argumentsText={message.function_call.arguments}
|
|
362
|
+
toolIndex={toolCalls.length}
|
|
363
|
+
toolFocus={toolFocus}
|
|
364
|
+
/>
|
|
266
365
|
)}
|
|
267
366
|
{(message?.content === null ||
|
|
268
367
|
message?.content === undefined ||
|
|
@@ -281,8 +380,10 @@ export const OpenAIResponseView = memo(function OpenAIResponseView({
|
|
|
281
380
|
|
|
282
381
|
export const OpenAIResponsesResponseView = memo(function OpenAIResponsesResponseView({
|
|
283
382
|
response,
|
|
383
|
+
toolFocus,
|
|
284
384
|
}: {
|
|
285
385
|
response: OpenAIResponsesResponse;
|
|
386
|
+
toolFocus?: ResponseToolFocus;
|
|
286
387
|
}): JSX.Element {
|
|
287
388
|
const text = extractResponsesOutputText(response);
|
|
288
389
|
const calls = extractResponsesFunctionCalls(response);
|
|
@@ -323,7 +424,12 @@ export const OpenAIResponsesResponseView = memo(function OpenAIResponsesResponse
|
|
|
323
424
|
{text.length > 0 && <AnswerMarkdown text={text} />}
|
|
324
425
|
{calls.map((call, i) => (
|
|
325
426
|
// biome-ignore lint/suspicious/noArrayIndexKey: response output order is stable
|
|
326
|
-
<ResponsesFunctionCallBlock
|
|
427
|
+
<ResponsesFunctionCallBlock
|
|
428
|
+
key={call.callId ?? call.id ?? `call-${i}`}
|
|
429
|
+
call={call}
|
|
430
|
+
toolIndex={i}
|
|
431
|
+
toolFocus={toolFocus}
|
|
432
|
+
/>
|
|
327
433
|
))}
|
|
328
434
|
{text.length === 0 && calls.length === 0 && (
|
|
329
435
|
<p className="text-xs text-muted-foreground italic">Empty response content</p>
|
|
@@ -2,13 +2,14 @@ import { safeGetOwnProperty } from "../../lib/objectUtils";
|
|
|
2
2
|
|
|
3
3
|
export const LOG_FOCUS_REQUEST_EVENT = "agent-inspector:focus-log";
|
|
4
4
|
|
|
5
|
-
export type LogFocusTab = "anatomy" | "request";
|
|
5
|
+
export type LogFocusTab = "anatomy" | "request" | "response";
|
|
6
6
|
|
|
7
7
|
export type LogFocusSource = "virtualizer";
|
|
8
8
|
|
|
9
9
|
export type LogFocusRequest = {
|
|
10
10
|
logId: number;
|
|
11
11
|
tab: LogFocusTab;
|
|
12
|
+
toolCallIndex?: number;
|
|
12
13
|
source?: LogFocusSource;
|
|
13
14
|
};
|
|
14
15
|
|
|
@@ -27,6 +28,7 @@ export function readLogFocusRequest(event: Event): LogFocusRequest | null {
|
|
|
27
28
|
switch (tab) {
|
|
28
29
|
case "anatomy":
|
|
29
30
|
case "request":
|
|
31
|
+
case "response":
|
|
30
32
|
break;
|
|
31
33
|
case undefined:
|
|
32
34
|
break;
|
|
@@ -35,11 +37,17 @@ export function readLogFocusRequest(event: Event): LogFocusRequest | null {
|
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
const source = safeGetOwnProperty(detail, "source");
|
|
40
|
+
const toolCallIndex = safeGetOwnProperty(detail, "toolCallIndex");
|
|
41
|
+
const focusRequest =
|
|
42
|
+
typeof toolCallIndex === "number" && Number.isInteger(toolCallIndex) && toolCallIndex >= 0
|
|
43
|
+
? { logId, tab: tab ?? "request", toolCallIndex }
|
|
44
|
+
: { logId, tab: tab ?? "request" };
|
|
45
|
+
|
|
38
46
|
switch (source) {
|
|
39
47
|
case "virtualizer":
|
|
40
|
-
return {
|
|
48
|
+
return { ...focusRequest, source };
|
|
41
49
|
case undefined:
|
|
42
|
-
return
|
|
50
|
+
return focusRequest;
|
|
43
51
|
default:
|
|
44
52
|
return null;
|
|
45
53
|
}
|
|
@@ -181,18 +181,33 @@ function extractOpenAIToolTraceEvents(log: CapturedLog): ToolTraceEvent[] {
|
|
|
181
181
|
for (const choice of choices) {
|
|
182
182
|
const message = safeGetOwnProperty(choice, "message");
|
|
183
183
|
const toolCalls = safeGetOwnProperty(message, "tool_calls");
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
184
|
+
if (Array.isArray(toolCalls)) {
|
|
185
|
+
for (const call of toolCalls) {
|
|
186
|
+
const fn = safeGetOwnProperty(call, "function");
|
|
187
|
+
const name = safeGetOwnProperty(fn, "name");
|
|
188
|
+
if (typeof name !== "string" || name.length === 0) continue;
|
|
189
|
+
const args = safeGetOwnProperty(fn, "arguments");
|
|
190
|
+
events.push({
|
|
191
|
+
id: `${String(log.id)}-openai-tool-${String(events.length)}`,
|
|
192
|
+
logId: log.id,
|
|
193
|
+
index: events.length,
|
|
194
|
+
provider: "openai",
|
|
195
|
+
name,
|
|
196
|
+
argumentsText: copyValue(args),
|
|
197
|
+
argumentsPreview: previewValue(args),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const functionCall = safeGetOwnProperty(message, "function_call");
|
|
202
|
+
const functionCallName = safeGetOwnProperty(functionCall, "name");
|
|
203
|
+
if (typeof functionCallName === "string" && functionCallName.length > 0) {
|
|
204
|
+
const args = safeGetOwnProperty(functionCall, "arguments");
|
|
190
205
|
events.push({
|
|
191
206
|
id: `${String(log.id)}-openai-tool-${String(events.length)}`,
|
|
192
207
|
logId: log.id,
|
|
193
208
|
index: events.length,
|
|
194
209
|
provider: "openai",
|
|
195
|
-
name,
|
|
210
|
+
name: functionCallName,
|
|
196
211
|
argumentsText: copyValue(args),
|
|
197
212
|
argumentsPreview: previewValue(args),
|
|
198
213
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
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
|
import { decodeSessionIdFromPath, encodeSessionIdForPath } from "../../lib/sessionUrl";
|
|
4
5
|
|
|
@@ -17,7 +18,32 @@ export const Route = createFileRoute("/session/$sessionId")({
|
|
|
17
18
|
}),
|
|
18
19
|
});
|
|
19
20
|
|
|
20
|
-
function SessionViewerRoute():
|
|
21
|
+
function SessionViewerRoute(): JSX.Element {
|
|
21
22
|
const { sessionId } = Route.useParams();
|
|
23
|
+
return <ClientOnlySessionViewer sessionId={sessionId} />;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ClientOnlySessionViewer({ sessionId }: { sessionId: string }): JSX.Element {
|
|
27
|
+
const [mounted, setMounted] = useState(false);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
setMounted(true);
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
if (!mounted) {
|
|
34
|
+
return (
|
|
35
|
+
<main className="min-h-screen bg-background text-foreground">
|
|
36
|
+
<div className="mx-auto flex min-h-screen w-full max-w-2xl flex-col justify-center px-6 py-16 text-center">
|
|
37
|
+
<div className="border border-border bg-card shadow-sm rounded-[8px] px-4 py-8">
|
|
38
|
+
<p className="text-sm font-medium text-muted-foreground">Loading session data...</p>
|
|
39
|
+
<p className="mt-3 inline-block max-w-full rounded bg-muted px-3 py-1 font-mono text-xs break-all text-muted-foreground">
|
|
40
|
+
{sessionId}
|
|
41
|
+
</p>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</main>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
22
48
|
return <ProxyViewerContainer key={sessionId} initialSessionId={sessionId} />;
|
|
23
49
|
}
|