- {messages
- .filter((m) => m.role !== "system")
- .map((m, i) => (
+ {messages.map((m, i) => {
+ if (m.role === "system" || m.role === "tool") return null
+ const calls =
+ m.role === "assistant" && m.toolCalls
+ ? m.toolCalls.map((tc) =>
+ buildAgentToolCall(tc, messages, isStreaming, !!pendingConfirm),
+ )
+ : []
+ const isWritePending =
+ pendingConfirm?.afterIndex === i ? pendingConfirm.writes : null
+ return (
- {pendingConfirm?.afterIndex === i && (
+ {calls.length > 0 && (
+
+ {calls.map((c) => (
+
+ ))}
+
+ )}
+ {isWritePending && (
)}
- ))}
- {isStreaming && messages.at(-1)?.role !== "assistant" && (
-
-
-
- )}
+ )
+ })}
+ {(() => {
+ const activity = deriveAgentActivity({
+ isStreaming,
+ lastMessage: messages.at(-1),
+ pendingConfirm: !!pendingConfirm,
+ confirmBusy,
+ })
+ if (activity === "idle") return null
+ return (
+
+ )
+ })()}
0) return "speaking"
+ return "thinking"
+}
+
+function buildAgentToolCall(
+ tc: ToolCall,
+ allMessages: LLMMessage[],
+ isStreaming: boolean,
+ pendingConfirm: boolean,
+): AgentToolCall {
+ const result = allMessages.find(
+ (m) => m.role === "tool" && m.toolCallId === tc.id,
+ )
+ let parsedArgs: Record
| undefined
+ try {
+ parsedArgs = tc.arguments ? JSON.parse(tc.arguments) : undefined
+ } catch {
+ parsedArgs = undefined
+ }
+ if (result) {
+ let parsedResult: unknown = result.content
+ let errorMsg: string | undefined
+ try {
+ const obj = JSON.parse(result.content) as Record
+ if (obj && typeof obj === "object" && typeof obj.error === "string") {
+ errorMsg = obj.error
+ } else {
+ parsedResult = obj
+ }
+ } catch {
+ // leave as raw text
+ }
+ return {
+ id: tc.id,
+ name: tc.name,
+ status: errorMsg ? "error" : "success",
+ args: parsedArgs,
+ result: errorMsg ? undefined : parsedResult,
+ error: errorMsg,
+ }
+ }
+ let status: ToolCallStatus = "running"
+ if (pendingConfirm) status = "pending"
+ else if (!isStreaming) status = "running"
+ return {
+ id: tc.id,
+ name: tc.name,
+ status,
+ args: parsedArgs,
+ }
+}
+
function MessageRow({
role,
content,
toolCalls,
}: {
- role: "user" | "assistant" | "tool"
+ role: "user" | "assistant"
content: string
toolCalls?: ToolCall[]
}) {
- if (role === "tool") {
- return (
-
-
-
- )
- }
if (role === "user") {
return (
@@ -640,6 +736,9 @@ function MessageRow({
)
}
+ // Assistant messages with only tool_calls and no prose render nothing here —
+ // the ToolCallCards beneath them carry the visual weight.
+ if (!content.trim()) return null
return (
diff --git a/tsconfig.json b/tsconfig.json
index 05ca354..13eb69c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -38,6 +38,8 @@
"@crema/feedback-ui/*": ["../lib-feedback-ui/src/*"],
"@crema/auth-ui": ["../lib-auth-ui/src/index.tsx"],
"@crema/auth-ui/*": ["../lib-auth-ui/src/*"],
+ "@crema/agent-ui": ["../lib-agent-ui/src/index.tsx"],
+ "@crema/agent-ui/*": ["../lib-agent-ui/src/*"],
"// CREMA:PATHS": [""],
"react": ["./node_modules/@types/react"],
"react/*": ["./node_modules/@types/react/*"],