Show tool and web search activity in the chat UI

Tool calls were invisible until the answer arrived: the only trace was the
sources list, and only for web searches. Surface them as they happen instead.

Add llm.stream_tool_calls(), an incremental version of resolve_tool_calls()
that yields a start and a completion event per tool call and ends with a
"resolved" event carrying the previous return value. resolve_tool_calls() is
kept as a thin wrapper that drops the progress events.

api/chat.py relays those events as a new SSE "tool" event, and wraps the
search-summary/knowledge-save step in one of the same shape, since it is a
noticeable pause of its own.

On the front end, ToolActivityList.vue renders one row per tool above the
assistant bubble (label, target, spinner while running, short result when
done). The store merges events by id and marks anything still running as
interrupted when the stream ends early. Display only - not persisted, so the
rows disappear when the session is reloaded, same as sources.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-23 15:11:00 +09:00
co-authored by Claude Opus 5
parent 8cac5d0de0
commit 366320c43a
10 changed files with 236 additions and 14 deletions
+16 -2
View File
@@ -21,9 +21,23 @@ export interface Source {
url: string
}
/** ツール実行1件の進行状況。開始時に status='running' で届き、完了時に同じ id で status='done' が届く。 */
export interface ToolActivity {
id: string
/** バックエンドのツール名 (web_search / read_note / write_note / read_knowledge / save_knowledge)。 */
name?: string
/** 「Web検索」などの表示用ラベル。 */
label?: string
/** 検索クエリやページタイトルなど、何に対して実行したか。 */
detail?: string
status: 'running' | 'done' | 'error'
/** 完了時の短い結果(「5件の結果」など)。 */
result?: string
}
export interface SSEChunk {
type: 'session_id' | 'chunk' | 'done' | 'error' | 'sources' | 'notes_updated'
value?: string | Source[]
type: 'session_id' | 'chunk' | 'done' | 'error' | 'sources' | 'notes_updated' | 'tool'
value?: string | Source[] | ToolActivity
}
const BASE = ''