{"id":51432663,"url":"https://github.com/chgeuer/jido_tool_renderers","last_synced_at":"2026-07-05T05:02:03.890Z","repository":{"id":344804906,"uuid":"1183091330","full_name":"chgeuer/jido_tool_renderers","owner":"chgeuer","description":null,"archived":false,"fork":false,"pushed_at":"2026-03-24T21:09:44.000Z","size":67,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-03-26T02:42:33.479Z","etag":null,"topics":["agents","elixir","jido","phoenix-liveview"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chgeuer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-16T09:07:55.000Z","updated_at":"2026-03-24T21:09:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/chgeuer/jido_tool_renderers","commit_stats":null,"previous_names":["chgeuer/jido_tool_renderers"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/chgeuer/jido_tool_renderers","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chgeuer%2Fjido_tool_renderers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chgeuer%2Fjido_tool_renderers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chgeuer%2Fjido_tool_renderers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chgeuer%2Fjido_tool_renderers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chgeuer","download_url":"https://codeload.github.com/chgeuer/jido_tool_renderers/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chgeuer%2Fjido_tool_renderers/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35143802,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-05T02:00:06.290Z","response_time":100,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agents","elixir","jido","phoenix-liveview"],"created_at":"2026-07-05T05:02:03.072Z","updated_at":"2026-07-05T05:02:03.878Z","avatar_url":"https://github.com/chgeuer.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JidoToolRenderers\n\nPhoenix LiveView components for rendering coding agent chat sessions. Provides specialized, rich UI renderers for AI tool calls (bash, file editing, search, etc.) and a session viewer with dual-mode rendering (rich HTML and xterm.js terminal).\n\nUsed by [copilot_lv](https://github.com/agentjido/copilot_lv) and other LiveView apps that display conversations with coding agents like Claude, Codex, Gemini, and GitHub Copilot.\n\n## Installation\n\nAdd the dependency to your `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:jido_tool_renderers, github: \"chgeuer/jido_tool_renderers\"}\n  ]\nend\n```\n\nFor the terminal view mode, install xterm.js in your consuming app:\n\n```bash\nnpm install @xterm/xterm @xterm/addon-fit\n```\n\n## Architecture\n\n```\n┌─────────────────────────────────────────────────────────┐\n│                   Consuming LiveView App                │\n│  (copilot_lv, symphony, etc.)                           │\n└────────────┬──────────────────────────────┬─────────────┘\n             │ raw events                   │\n             ▼                              │\n┌────────────────────────┐                  │\n│       Adapters         │                  │\n│  CopilotLv | Symphony  │                  │\n└────────────┬───────────┘                  │\n             │ SessionEvent structs         │\n             ▼                              ▼\n┌────────────────────────────────────────────────────────┐\n│                    SessionViewer                       │\n│  ┌──────────────────────┐  ┌────────────────────────┐  │\n│  │    Rich (HTML)       │  │  Terminal (xterm.js)   │  │\n│  │  ┌────────────────┐  │  │  ┌──────────────────┐  │  │\n│  │  │ Tool Renderers │  │  │  │  AnsiFormatter   │  │  │\n│  │  │ (per-tool UI)  │  │  │  │  (ANSI output)   │  │  │\n│  │  └────────────────┘  │  │  └──────────────────┘  │  │\n│  └──────────────────────┘  └────────────────────────┘  │\n│                 Interaction Controls                   │\n│           (interactive / readonly_live / readonly)     │\n└────────────────────────────────────────────────────────┘\n```\n\n## Core Concepts\n\n### SessionEvent\n\nAll agent events are normalized into `Jido.ToolRenderers.SessionEvent` structs with a type and data map:\n\n```elixir\n%SessionEvent{\n  id: \"evt-1\",\n  type: :tool_call,\n  data: %{\n    \"tool\" =\u003e \"bash\",\n    \"arguments\" =\u003e %{\"command\" =\u003e \"mix test\"},\n    \"tool_call_id\" =\u003e \"tc-123\",\n    \"completed\" =\u003e true,\n    \"result\" =\u003e \"All tests passed\",\n    \"error\" =\u003e nil\n  },\n  timestamp: ~U[2025-01-01 00:00:00Z],\n  metadata: %{}\n}\n```\n\n**Event types:**\n\n| Type | Description |\n|------|-------------|\n| `:user_message` | User prompt text, optional attachments |\n| `:assistant_message` | LLM output text (accumulated from chunks) |\n| `:assistant_reasoning` | Internal thinking/reasoning blocks |\n| `:assistant_intent` | Reported intent/status update |\n| `:assistant_usage` | Token usage stats for a turn |\n| `:tool_call` | Individual tool invocation with args and result |\n| `:tool_group` | Grouped consecutive tool calls (collapsible) |\n| `:turn_start` / `:turn_end` | Turn boundaries |\n| `:session_error` | Error message |\n| `:session_info` | Informational status message |\n| `:session_idle` | Session is idle/waiting |\n| `:ask_user` | Agent requesting user input |\n\nConvenience constructors are provided:\n\n```elixir\nSessionEvent.user_message(\"Fix the tests\")\nSessionEvent.assistant_message(\"I'll look into it...\")\nSessionEvent.tool_call(\"bash\", %{\"command\" =\u003e \"mix test\"}, completed: true, result: \"OK\")\nSessionEvent.tool_group(child_events, tool_names: [\"bash\", \"grep\"])\n```\n\n### Tool Renderer Registry\n\n`Jido.ToolRenderers.renderer_for/1` maps tool names to specialized renderer modules. It also normalizes provider-specific tool names to canonical forms via `canonical_tool_name/1`:\n\n```elixir\nJido.ToolRenderers.renderer_for(\"bash\")    #=\u003e Jido.ToolRenderers.Bash\nJido.ToolRenderers.renderer_for(\"Read\")    #=\u003e Jido.ToolRenderers.View\nJido.ToolRenderers.renderer_for(\"unknown\") #=\u003e Jido.ToolRenderers.Generic\n\n# Provider-agnostic normalization\nJido.ToolRenderers.canonical_tool_name(\"Bash\")              #=\u003e \"bash\"\nJido.ToolRenderers.canonical_tool_name(\"shell_command\")      #=\u003e \"bash\"\nJido.ToolRenderers.canonical_tool_name(\"run_shell_command\")  #=\u003e \"bash\"\nJido.ToolRenderers.canonical_tool_name(\"Read\")               #=\u003e \"view\"\n```\n\n## Tool Renderers\n\nEach renderer is a `Phoenix.Component` module with a `render/1` function. They receive assigns including `tool`, `args`, `completed`, `content`, `error_msg`, and `tool_call_id`.\n\n| Renderer | Tool Names | Description |\n|----------|-----------|-------------|\n| `Bash` | `bash`, `read_bash`, `write_bash`, `stop_bash`, `list_bash` | Terminal commands with `$ ` prompt styling, auto-collapsing output, shell ID and mode badges |\n| `View` | `view`, `Read`, `read_file` | File viewer; renders `.md` files as markdown, others as preformatted text |\n| `FileWrite` | `create`, `edit`, `Write`, `replace` | File create/edit with inline diff display (old → new) and markdown preview |\n| `ApplyPatch` | `apply_patch` | Unified diff display with color-coded additions/deletions |\n| `Grep` | `grep`, `rg`, `search_file_content` | Search pattern displayed as inline code with path/glob/type context |\n| `Glob` | `glob`, `list_directory`, `list_files` | File pattern matching with path context |\n| `WebSearch` | `web_search` | Search results rendered as markdown with citation badges |\n| `WebFetch` | `web_fetch` | Fetched URL with clickable link and markdown result |\n| `AskUser` | `ask_user` | Question with choice list; shows selected answer with checkmarks |\n| `Task` | `task` | Sub-agent tasks with type badges (explore/general-purpose/code-review/task), prompt preview |\n| `ReadAgent` | `read_agent`, `list_agents` | Agent results with metadata line and markdown body |\n| `Sql` | `sql` | SQL query display with database badge and tabular result rendering |\n| `GitHub` | `github-mcp-server-*` | GitHub MCP tools with repo reference, method badge, and markdown results |\n| `ReportIntent` | `report_intent`, `update_plan`, `ExitPlanMode` | Compact intent display |\n| `UpdateTodo` | `update_todo`, `task_complete` | Todo list with checkboxes; task completion summary |\n| `Generic` | *(fallback)* | Default renderer: JSON arguments + preformatted result |\n\n### Shared Sub-Components\n\n`Jido.ToolRenderers.Generic` provides shared components used across all renderers:\n\n- `Generic.status_indicator/1` — Shows ✓ (success), ✗ (error), or loading dots (pending)\n- `Generic.error_display/1` — Conditional error message display\n- `Generic.result_pre/1` — Collapsible preformatted text result\n- `Generic.result_markdown/1` — Collapsible markdown-rendered result with copy button\n\n## Session Viewer\n\nThe main `SessionViewer.session_view/1` component provides a complete chat UI with:\n\n- **Dual view modes**: Rich HTML (DaisyUI chat bubbles) or Terminal (xterm.js)\n- **Three interaction modes**: `:interactive`, `:readonly_live`, `:readonly`\n- **Session metadata display**: Title, status badge, model info\n\n### Usage\n\n```elixir\nalias Jido.ToolRenderers.SessionViewer\nalias Jido.ToolRenderers.SessionViewer.Rich\n\n# Historical replay (readonly)\n\u003cSessionViewer.session_view\n  id=\"session-1\"\n  view_mode={:rich}\n  interaction_mode={:readonly}\n  session_metadata={@session_meta}\n\u003e\n  \u003c:events\u003e\n    \u003cdiv id=\"events\" phx-update=\"stream\"\u003e\n      \u003cdiv :for={{dom_id, event} \u003c- @streams.events} id={dom_id}\u003e\n        \u003cRich.event_item event={event} /\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/:events\u003e\n\u003c/SessionViewer.session_view\u003e\n\n# Active driving (interactive)\n\u003cSessionViewer.session_view\n  id=\"session-1\"\n  view_mode={@view_mode}\n  interaction_mode={:interactive}\n  status={@status}\n  model={@model}\n  session_metadata={@session_meta}\n  ask_user_request={@ask_user_request}\n\u003e\n  \u003c:events\u003e...\u003c/:events\u003e\n\u003c/SessionViewer.session_view\u003e\n\n# Passive watching (readonly_live)\n\u003cSessionViewer.session_view\n  id=\"session-1\"\n  view_mode={@view_mode}\n  interaction_mode={:readonly_live}\n  status={@status}\n  session_metadata={@session_meta}\n\u003e\n  \u003c:events\u003e...\u003c/:events\u003e\n\u003c/SessionViewer.session_view\u003e\n```\n\n### Rich Mode\n\n`SessionViewer.Rich.event_item/1` renders each `SessionEvent` as styled HTML:\n\n- **User messages** — Right-aligned chat bubbles with markdown, copy button, and pasted attachment badges\n- **Assistant messages** — Left-aligned chat bubbles with rendered markdown\n- **Reasoning** — Collapsible italic blocks with 🧠 preview\n- **Intent** — Inline italic status with 💭 icon\n- **Tool calls** — Card with the specialized tool renderer dispatched via the registry\n- **Tool groups** — Collapsible section grouping parallel tool calls with summary\n- **Usage stats** — Compact token count and cost display\n- **Errors** — Alert banners\n\n### Terminal Mode\n\n`SessionViewer.Terminal` renders events as ANSI-colored text in an xterm.js terminal. The `AnsiFormatter` converts `SessionEvent` structs to ANSI escape sequences.\n\n```elixir\n# Format a single event for incremental push\nansi = Terminal.format_event(event)\nsocket = push_event(socket, \"xterm:write\", %{data: ansi, target: \"session-term\"})\n\n# Format all events for initial content\ncontent = Terminal.format_all(events)\n```\n\n### Session Overview Dashboard\n\n`SessionOverview.grid/1` renders a responsive grid of mini terminal panels, each showing a live preview of a session:\n\n```elixir\n\u003cSessionOverview.grid\n  sessions={@sessions}\n  navigate_fn={fn id -\u003e \"/session/#{id}\" end}\n/\u003e\n```\n\nEach session in the list should have `id`, `metadata` (`SessionMetadata` struct), and `recent_events` (list of `SessionEvent` structs).\n\n## Adapters\n\nAdapters convert provider-specific event formats into `SessionEvent` structs.\n\n### CopilotLv Adapter\n\nConverts copilot_lv's normalized event maps (string-keyed, with types like `\"user.message\"`, `\"tool.combined\"`, `\"tool.group\"`) into `SessionEvent` structs:\n\n```elixir\nalias Jido.ToolRenderers.Adapters.CopilotLv\n\nevents = CopilotLv.convert_events(raw_events)\nevent = CopilotLv.convert_event(raw_event)\n```\n\n### Symphony Adapter\n\nConverts jido_symphony's atom-keyed event maps (`:agent_text`, `:tool_call`, etc.) and coalesced block maps into `SessionEvent` structs:\n\n```elixir\nalias Jido.ToolRenderers.Adapters.Symphony\n\nevents = Symphony.convert_events(raw_events)\nevent = Symphony.convert_block(coalesced_block)\n```\n\n## JavaScript Hooks\n\n### XtermSession Hook\n\nThe `priv/static/js/xterm_hook.js` file provides a LiveView hook for xterm.js terminal rendering. Register it in your app's JavaScript:\n\n```javascript\nimport { XtermSession } from \"jido_tool_renderers/xterm_hook\"\n\nlet liveSocket = new LiveSocket(\"/live\", Socket, {\n  hooks: { XtermSession }\n})\n```\n\nThe hook:\n- Dynamically imports `@xterm/xterm` and `@xterm/addon-fit`\n- Renders with a dark VS Code-style theme\n- Listens for `xterm:write` and `xterm:clear` push events from the server\n- Auto-resizes via `ResizeObserver`\n\n### Additional Hooks\n\nThe consuming app should also provide these LiveView hooks used by the rich renderer:\n\n- **`MarkdownContent`** — Renders `data-markdown` attribute content as HTML (e.g., using `marked` or `markdown-it`)\n- **`CopyMarkdown`** — Copies rendered markdown content to clipboard\n- **`UserMessage`** — Renders user message markdown\n\n## Adding a New Tool Renderer\n\n1. Create a module in `lib/jido/tool_renderers/` with `use Phoenix.Component` and a `render/1` function:\n\n```elixir\ndefmodule Jido.ToolRenderers.MyTool do\n  use Phoenix.Component\n  alias Jido.ToolRenderers.Generic\n\n  def render(assigns) do\n    args = assigns.args || %{}\n    # Extract relevant args...\n\n    ~H\"\"\"\n    \u003cdiv class=\"flex items-center gap-2\"\u003e\n      \u003cspan class=\"badge badge-info badge-sm\"\u003e🔧 my_tool\u003c/span\u003e\n      \u003cGeneric.status_indicator completed={@completed} error_msg={@error_msg} /\u003e\n    \u003c/div\u003e\n    \u003cGeneric.error_display error_msg={@error_msg} /\u003e\n    \u003cGeneric.result_pre content={@content} completed={@completed} /\u003e\n    \"\"\"\n  end\nend\n```\n\n2. Add a mapping in `Jido.ToolRenderers.renderer_for/1`:\n\n```elixir\n\"my_tool\" -\u003e MyTool\n```\n\n## UI Framework\n\nComponents use [DaisyUI](https://daisyui.com/) classes (built on Tailwind CSS). The consuming app must include DaisyUI in its CSS build. Key classes used:\n\n- `chat`, `chat-bubble` — Conversation layout\n- `badge` — Tool name and status indicators\n- `card` — Tool call containers\n- `alert` — Errors and info messages\n- `btn` — Interactive controls\n- `loading` — Spinner animations\n\n## Requirements\n\n- Elixir ~\u003e 1.18\n- Phoenix LiveView ~\u003e 1.0\n- Jason ~\u003e 1.4\n- DaisyUI + Tailwind CSS (in consuming app)\n- `@xterm/xterm` + `@xterm/addon-fit` (optional, for terminal mode)\n\n## License\n\nSee [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchgeuer%2Fjido_tool_renderers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchgeuer%2Fjido_tool_renderers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchgeuer%2Fjido_tool_renderers/lists"}