Skip to content

Commit 79a063e

Browse files
authored
fix: now tool calls appear in the UI (#23)
Now tool calls appear in the UI. I also removed the tools loading up all the time. Closes #20 https://github.com/user-attachments/assets/1202cece-e9e2-4c17-a976-f6922b90230c
1 parent a4d878e commit 79a063e

2 files changed

Lines changed: 124 additions & 25 deletions

File tree

src/components/Chat.tsx

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@ type StreamEvent =
1818
serverLabel: string
1919
tools?: any[]
2020
itemId?: string
21+
toolName?: string
22+
arguments?: unknown
2123
}
2224
| { type: 'user'; id: string; content: string }
25+
2326
export function Chat() {
2427
const messagesEndRef = useRef<HTMLDivElement>(null)
2528
const [hasStartedChat, setHasStartedChat] = useState(false)
@@ -57,6 +60,29 @@ export function Chat() {
5760
if (line.startsWith('t:')) {
5861
try {
5962
const toolState = JSON.parse(line.slice(2))
63+
64+
if ('delta' in toolState) {
65+
try {
66+
toolState.delta =
67+
'delta' in toolState && toolState.delta !== ''
68+
? JSON.parse(toolState.delta)
69+
: {}
70+
} catch (e) {
71+
console.error('Failed to parse delta:', toolState.delta)
72+
toolState.delta = {}
73+
}
74+
}
75+
76+
try {
77+
toolState.arguments =
78+
'arguments' in toolState && toolState.arguments !== ''
79+
? JSON.parse(toolState.arguments)
80+
: {}
81+
} catch (e) {
82+
console.error('Failed to parse arguments:', toolState.arguments)
83+
toolState.arguments = {}
84+
}
85+
6086
setStreamBuffer((prev) => [
6187
...prev,
6288
{
@@ -65,6 +91,9 @@ export function Chat() {
6591
serverLabel: toolState.serverLabel,
6692
tools: toolState.tools,
6793
itemId: toolState.itemId,
94+
delta: toolState.delta,
95+
arguments: toolState.arguments,
96+
toolName: toolState.toolName,
6897
},
6998
])
7099
} catch (e) {
@@ -122,6 +151,10 @@ export function Chat() {
122151
},
123152
})
124153

154+
// What to render: if streaming or streamBuffer has content, use streamBuffer; else, use messages
155+
const renderEvents: (StreamEvent | Message)[] =
156+
streaming || streamBuffer.length > 0 ? [...streamBuffer] : messages
157+
125158
// Auto-scroll to the bottom when messages or streamBuffer change
126159
useEffect(() => {
127160
if (messagesEndRef.current) {
@@ -144,14 +177,6 @@ export function Chat() {
144177
handleSubmit(new Event('submit'))
145178
}
146179

147-
// What to render: if streaming or streamBuffer has content, use streamBuffer; else, use messages
148-
let renderEvents: (StreamEvent | Message)[]
149-
if (streaming || streamBuffer.length > 0) {
150-
renderEvents = [...streamBuffer]
151-
} else {
152-
renderEvents = messages
153-
}
154-
155180
return (
156181
<div className="flex flex-col h-full relative">
157182
<div className="flex-1 overflow-y-auto">
@@ -163,10 +188,7 @@ export function Chat() {
163188
<ToolCallMessage
164189
key={`tool-${event.toolType}-${event.serverLabel || ''}-${event.itemId || generateMessageId()}`}
165190
name={event.serverLabel || ''}
166-
args={{
167-
status: event.toolType,
168-
tools: event.tools,
169-
}}
191+
args={event}
170192
/>
171193
)
172194
} else if ('type' in event && event.type === 'assistant') {

src/lib/streaming.ts

Lines changed: 90 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,6 @@ export function streamText(
3232
for await (const chunk of answer) {
3333
// Handle tool-related chunks
3434
switch (chunk.type) {
35-
case 'response.output_item.added':
36-
if (chunk.item.type === 'mcp_list_tools') {
37-
controller.enqueue(
38-
encoder.encode(
39-
`t:${JSON.stringify({
40-
type: 'tool_added',
41-
serverLabel: chunk.item.server_label,
42-
tools: chunk.item.tools,
43-
})}\n`,
44-
),
45-
)
46-
}
47-
break
4835
case 'response.mcp_list_tools.in_progress':
4936
controller.enqueue(
5037
encoder.encode(
@@ -88,6 +75,96 @@ export function streamText(
8875
}
8976
}
9077
break
78+
case 'response.mcp_call.failed':
79+
console.error('[TOOL CALL FAILED]', chunk)
80+
81+
controller.enqueue(
82+
encoder.encode(
83+
`t:${JSON.stringify({
84+
type: 'mcp_call_failed',
85+
itemId: chunk.item_id,
86+
})}\n`,
87+
),
88+
)
89+
break
90+
91+
case 'response.mcp_call.created':
92+
controller.enqueue(
93+
encoder.encode(
94+
`t:${JSON.stringify({
95+
type: 'mcp_call_created',
96+
itemId: chunk.item_id,
97+
toolName: chunk.tool.name,
98+
arguments: chunk.tool.input,
99+
})}\n`,
100+
),
101+
)
102+
break
103+
104+
case 'response.mcp_call.in_progress':
105+
controller.enqueue(
106+
encoder.encode(
107+
`t:${JSON.stringify({
108+
type: 'mcp_call_in_progress',
109+
itemId: chunk.item_id,
110+
})}\n`,
111+
),
112+
)
113+
break
114+
115+
case 'response.mcp_call_arguments.delta':
116+
controller.enqueue(
117+
encoder.encode(
118+
`t:${JSON.stringify({
119+
type: 'mcp_call_arguments_delta',
120+
itemId: chunk.item_id,
121+
delta: chunk.delta,
122+
})}\n`,
123+
),
124+
)
125+
break
126+
127+
case 'response.mcp_call_arguments.done':
128+
controller.enqueue(
129+
encoder.encode(
130+
`t:${JSON.stringify({
131+
type: 'mcp_call_arguments_done',
132+
itemId: chunk.item_id,
133+
arguments: chunk.arguments,
134+
})}\n`,
135+
),
136+
)
137+
break
138+
139+
case 'response.mcp_call.completed':
140+
controller.enqueue(
141+
encoder.encode(
142+
`t:${JSON.stringify({
143+
type: 'mcp_call_completed',
144+
itemId: chunk.item_id,
145+
})}\n`,
146+
),
147+
)
148+
break
149+
150+
case 'response.output_item.added':
151+
if (chunk.item.type === 'mcp_call') {
152+
controller.enqueue(
153+
encoder.encode(
154+
`t:${JSON.stringify({
155+
type: 'mcp_call',
156+
itemId: chunk.item.id,
157+
toolName: chunk.item.name,
158+
serverLabel: chunk.item.server_label,
159+
arguments: chunk.item.arguments,
160+
})}\n`,
161+
),
162+
)
163+
}
164+
break
165+
166+
default:
167+
break
91168
}
92169
}
93170

0 commit comments

Comments
 (0)