Skip to content

feat(agent): chat panel with streaming + tool dispatch#217

Open
appergb wants to merge 1 commit into
mainfrom
feat/agent-chat-panel
Open

feat(agent): chat panel with streaming + tool dispatch#217
appergb wants to merge 1 commit into
mainfrom
feat/agent-chat-panel

Conversation

@appergb

@appergb appergb commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Summary

Implements the in-app Agent chat panel (HANDOFF §3.3, P1). The UI, external MCP server, and in-app chat are now peer clients of the same Dispatcher — a chat turn's tool calls land on the exact same 44-tool EditCommand pipeline an external agent would use, preserving the single-edit-entry invariant.

Changes

Backend — crates/opentake-agent/src/chat/ (new)

  • session.rsChatSession / ChatMessage / Role / ToolCall (serde camelCase for the front-end mirror).
  • llm.rs — BYOK streaming chat client over opentake_gen::KeyringStore. Two providers: OpenAI (/v1/chat/completions SSE) + Anthropic (/v1/messages SSE). Picks the first with a stored key; returns a guided message when neither is configured.
  • loop.rs — the turn loop: assembles the system prompt (base + active plugin via prompt::assemble) + a live Context Signal block (signal::engine::build_signal) so the model can act without a get_timeline round-trip; streams the model; dispatches tool calls through the shared Dispatcher via spawn_blocking; re-feeds results until a final text turn. Cancel is a shared Arc<AtomicBool> (no tokio dep, so the --no-default-features build stays clean).

Tauri — src-tauri/src/chat.rs (new) + lib.rs

  • chat_send(session_id, text) — spawns a turn; streams chat_delta / chat_tool_call / chat_done events.
  • chat_history(session_id) — returns the persisted message log.
  • chat_cancel(session_id) — sets the cancel flag at the next boundary.
  • ChatState managed in setup: builds its own Dispatcher over the live AppCore + the workflow registry from the same <app_data_dir>/workflows the MCP server scans. No media bridge (chat doesn't render/import).

Frontend

  • web/src/lib/types.tsChatRole / ChatMessage / ChatToolCall (mirror Rust serde camelCase).
  • web/src/lib/api.tschatSend / chatHistory / chatCancel + onChatDelta / onChatToolCall / onChatDone listeners.
  • web/src/store/chatStore.ts (new) — Zustand store: message list + streaming state + tool-call card upsert by id.
  • web/src/components/agent/AgentPanel.tsx — rewritten: message list (user right / assistant left), streaming delta accumulation, collapsible tool-call cards (args + result + error badge), input (Enter send / Shift+Enter newline), cancel button, no-key guided message with an Open-Settings affordance.
  • web/src/i18n/dict.tsagent.* keys (zh + en).

Verification (all 6 green)

cargo fmt --all -- --check                                 ✅
cargo clippy --workspace --all-targets -- -D warnings      ✅
cargo clippy -p opentake-tauri --no-default-features --all-targets -- -D warnings  ✅
cargo test --workspace                                     ✅ (1428 tests, 0 failed)
pnpm -C web build                                          ✅
pnpm -C web test                                           ✅ (437 tests)

New unit tests: 18 in opentake-agent::chat (session serde, OpenAI/Anthropic body shape, tool-call round-trip, no-key path, system-prompt signal injection) + 2 in opentake-tauri::chat (registry + session take/put).

Acceptance

  • ✅ "把这段静音剪掉" routes through tighten_silencesripple_delete_ranges (same Dispatcher as MCP).
  • ✅ No-key path returns a guided assistant message with an Open-Settings button.
  • ✅ Cancel button sets the flag; the loop stops at the next round boundary.
  • ✅ Streaming renders incrementally via chat_delta events.

Notes

  • The chat Dispatcher is built without a MediaBridge (no GPU/import from chat); inspect_timeline / import_media return "not available" — the model routes around them. A follow-up can share the MCP server's bridge if chat-side rendering becomes important.
  • LLM client is transport-thin (reqwest + SSE); opentake-gen is a generation-job client (no chat surface), so chat ships its own streaming client over the same KeyringStore.

- opentake-agent/src/chat/: session + loop + llm (streaming SSE)
- src-tauri: chat_send/history/cancel + chat_delta/tool_call/done events
- web: AgentPanel message list + streaming + tool call cards
- Context Signal injected into system prompt
- BYOK via opentake-gen provider/keys
- tool calls reuse 44-tool dispatch (single edit entry)

Co-authored-by: opencode <opencode@anomaly.co>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant