shared: canonical ToolKind taxonomy mapped from both Anthropic + Cursor tool names #954

Closed
opened 2026-05-08 12:05:37 +00:00 by claude-desktop · 0 comments
Collaborator

User story

As a frontend engineer rendering the agent activity timeline, I want a stable provider-agnostic enum identifying what kind of tool a tool call invoked, so I can pick the right widget (file diff, shell pane, grep results, etc.) without conditioning on provider.

Context

Today each provider emits its own tool names:

  • Claude Code: Read, Write, Edit, Bash, Grep, Glob, WebFetch, WebSearch, Task, TodoWrite, NotebookEdit, mcp__forgejo__* etc.
  • Cursor: Edit, Write, Read, Shell, Grep, Glob, SemSearch, Ls, Mcp, Task, CreatePlan, UpdateTodos, Delete, ReadLints (typed via tool-call-types.d.ts).

Frontend code currently can't render a "shell output" widget for cursor without knowing that cursor calls it Shell while claude calls it Bash. This grows fragile as more providers join (LangChain tool sets, OpenAI Responses tool calls).

Acceptance criteria

@claude-hooks/shared

  • enum ToolKind (or string-literal union) with members:
    • read, write, edit, delete
    • shell (any execute-command tool)
    • grep, glob, sem_search, ls, read_lints
    • web_fetch, web_search
    • mcp (any MCP-mounted tool — keep the original name in metadata)
    • subagent_task
    • plan (CreatePlan-style)
    • todos (UpdateTodos / TodoWrite)
    • notebook_edit
    • unknown (catch-all; renders a generic JSON view)
  • mapToolNameToKind(provider, name): ToolKind — pure function; covers every name in the matrix above with a fallback to unknown.
  • ToolCallNormalized type — { kind: ToolKind, providerName: string, args: unknown, result?: unknown, ok?: boolean }. Used in event-log payloads sent over SSE.

Adapters

  • Both cursor-sdk-adapter.ts and sdk-adapter.ts (claude) populate ToolCallNormalized.kind on every tool-related event they emit. The unmapped kind for an unknown tool is unknown and the providerName carries the original.

Frontend

  • One widget per ToolKind:
    • <EditToolCallView> — render unified diff (use diff-viewer lib of choice; new dep can be discussed in the widget issue itself)
    • <WriteToolCallView> — file path + size + contents with syntax highlight
    • <ReadToolCallView> — file path + line range + collapsed body
    • <ShellToolCallView> — command line + live stdout pane
    • <GrepToolCallView> — pattern + matches list (file:line:preview)
    • <GlobToolCallView> / <LsToolCallView> — file list
    • <SemSearchToolCallView> — query + ranked results
    • <WebFetchToolCallView> / <WebSearchToolCallView> — URL + title + excerpt
    • <McpToolCallView> — provider/tool + JSON payload (collapsible)
    • <SubagentTaskView> — child agent dispatch (links to subagent timeline if available)
    • <PlanView> — bullet list of plan items with checkbox state
    • <TodosView> — todo list with status pills
    • <UnknownToolCallView> — JSON tree fallback
  • Widgets accept the ToolCallNormalized shape directly, not provider-specific shapes.
  • Lazy-load each widget — splitting reduces dashboard bundle size.

Tests

  • Unit tests for mapToolNameToKind against a fixture matrix.
  • Render snapshot tests for each widget at empty / running / completed / error states.

Out of scope

  • Server-side persistence schema migration — task_history already stores tool calls in event JSON, no DDL needed; new fields land in event payload only.
  • Live shell output streaming — covered by the InteractionUpdate-deltas issue + the synthetic-shell-deltas-for-claude issue.

References

  • Parent: #950
  • Cursor tool types: node_modules/.bun/@cursor+sdk@1.0.12/node_modules/@cursor/sdk/dist/cjs/types/tool-call-types.d.ts
  • Claude tool names: see apps/server/src/infrastructure/agent/sdk-adapter.ts + Agent SDK docs.
## User story As a frontend engineer rendering the agent activity timeline, I want a stable provider-agnostic enum identifying *what kind of tool* a tool call invoked, so I can pick the right widget (file diff, shell pane, grep results, etc.) without conditioning on provider. ## Context Today each provider emits its own tool names: - Claude Code: `Read`, `Write`, `Edit`, `Bash`, `Grep`, `Glob`, `WebFetch`, `WebSearch`, `Task`, `TodoWrite`, `NotebookEdit`, `mcp__forgejo__*` etc. - Cursor: `Edit`, `Write`, `Read`, `Shell`, `Grep`, `Glob`, `SemSearch`, `Ls`, `Mcp`, `Task`, `CreatePlan`, `UpdateTodos`, `Delete`, `ReadLints` (typed via `tool-call-types.d.ts`). Frontend code currently can't render a "shell output" widget for cursor without knowing that cursor calls it `Shell` while claude calls it `Bash`. This grows fragile as more providers join (LangChain tool sets, OpenAI Responses tool calls). ## Acceptance criteria ### `@claude-hooks/shared` - [ ] `enum ToolKind` (or string-literal union) with members: - `read`, `write`, `edit`, `delete` - `shell` (any execute-command tool) - `grep`, `glob`, `sem_search`, `ls`, `read_lints` - `web_fetch`, `web_search` - `mcp` (any MCP-mounted tool — keep the original name in metadata) - `subagent_task` - `plan` (CreatePlan-style) - `todos` (UpdateTodos / TodoWrite) - `notebook_edit` - `unknown` (catch-all; renders a generic JSON view) - [ ] `mapToolNameToKind(provider, name): ToolKind` — pure function; covers every name in the matrix above with a fallback to `unknown`. - [ ] `ToolCallNormalized` type — `{ kind: ToolKind, providerName: string, args: unknown, result?: unknown, ok?: boolean }`. Used in event-log payloads sent over SSE. ### Adapters - [ ] Both `cursor-sdk-adapter.ts` and `sdk-adapter.ts` (claude) populate `ToolCallNormalized.kind` on every tool-related event they emit. The unmapped `kind` for an unknown tool is `unknown` and the `providerName` carries the original. ### Frontend - [ ] One widget per `ToolKind`: - `<EditToolCallView>` — render unified diff (use diff-viewer lib of choice; new dep can be discussed in the widget issue itself) - `<WriteToolCallView>` — file path + size + contents with syntax highlight - `<ReadToolCallView>` — file path + line range + collapsed body - `<ShellToolCallView>` — command line + live stdout pane - `<GrepToolCallView>` — pattern + matches list (file:line:preview) - `<GlobToolCallView>` / `<LsToolCallView>` — file list - `<SemSearchToolCallView>` — query + ranked results - `<WebFetchToolCallView>` / `<WebSearchToolCallView>` — URL + title + excerpt - `<McpToolCallView>` — provider/tool + JSON payload (collapsible) - `<SubagentTaskView>` — child agent dispatch (links to subagent timeline if available) - `<PlanView>` — bullet list of plan items with checkbox state - `<TodosView>` — todo list with status pills - `<UnknownToolCallView>` — JSON tree fallback - [ ] Widgets accept the `ToolCallNormalized` shape directly, not provider-specific shapes. - [ ] Lazy-load each widget — splitting reduces dashboard bundle size. ### Tests - [ ] Unit tests for `mapToolNameToKind` against a fixture matrix. - [ ] Render snapshot tests for each widget at empty / running / completed / error states. ## Out of scope - Server-side persistence schema migration — `task_history` already stores tool calls in event JSON, no DDL needed; new fields land in event payload only. - Live shell output streaming — covered by the InteractionUpdate-deltas issue + the synthetic-shell-deltas-for-claude issue. ## References - Parent: #950 - Cursor tool types: `node_modules/.bun/@cursor+sdk@1.0.12/node_modules/@cursor/sdk/dist/cjs/types/tool-call-types.d.ts` - Claude tool names: see `apps/server/src/infrastructure/agent/sdk-adapter.ts` + `Agent SDK` docs.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
charles/claude-hooks#954
No description provided.