# Tool normalization contract

This contract is only for tool parts. Text and reasoning normalization stay as-is.

## Canonical shape

Every provider tool event should normalize toward this shape:

```ts
type ToolState = 'started' | 'running' | 'complete' | 'error';

type ToolKind =
  | 'web_search'
  | 'browser'
  | 'file_read'
  | 'file_write'
  | 'file_edit'
  | 'bash'
  | 'subagent'
  | 'mcp'
  | 'question'
  | 'todo'
  | 'unknown';

type NormalizedToolPart = {
  id: string;
  name: string;      // canonical machine-ish name: Bash, Read, webSearch, browser_navigate
  kind: ToolKind;    // stable UI bucket for icon/grouping
  title: string;     // human title shown in UI
  input: unknown;    // provider args, normalized only enough to be inspectable
  output?: unknown;  // successful result, if present
  error?: {
    message: string;
    code?: string;
    details?: unknown;
  };
  state: ToolState;
  timing?: {
    startedAt?: string | number;
    completedAt?: string | number;
    elapsedMs?: number;
  };
  metadata?: Record<string, unknown>; // provider-specific detail that UI may use later
};
```

## Rules

1. `id` must be stable for the whole lifecycle.
2. `name` is the normalized tool identity. It should not be guessed from input if
   the provider gave an explicit tool name.
3. `kind` is a small taxonomy for UI rendering. It is not the provider tool name.
4. `title` is the human line. Prefer action text over raw names.
5. `input` should preserve meaningful args: command, cwd, path, query, url,
   prompt, MCP arguments.
6. `output` is only successful result data.
7. `error` is only failure data. Do not force errors into `output`.
8. `metadata` preserves useful provider detail without expanding the core shape:
   MCP server/tool, Codex action type, Claude task id, OpenCode child session id,
   provider raw item type, attachments, truncation flags.
9. Avoid persisting giant raw provider blobs in `metadata`. Keep raw fixtures in
   the recorder/audit layer.

## Title examples

| Native source | Title |
| --- | --- |
| Codex `webSearch.action.type = "search"` | `Searching Reuters top news` |
| Codex `webSearch.action.type = "openPage"` | `Opening apnews.com` |
| Codex `webSearch.action.type = "findInPage"` | `Finding "Updated" on apnews.com` |
| Codex `mcpToolCall` `{ server:"amalgm", tool:"browser_snapshot" }` | `Reading browser page` |
| Codex `mcpToolCall` `{ server:"amalgm", tool:"browser_navigate" }` | `Opening browser page` |
| Claude `mcp__amalgm__artifacts_register` | `Registering artifact Spotify Clone` |
| OpenCode `amalgm_browser_tabs_new` | `Opening browser tab` |
| OpenCode `task` with description | `Research Northern India politics` |

## Provider mapping targets

### Claude Code

- `tool_use.name` -> `name`
- `tool_use.input` -> `input`
- `tool_result.content` -> `output`
- `tool_result.is_error` -> `error`
- `mcp__<server>__<tool>` -> `metadata.mcp = { server, tool }`
- `task_started/task_progress/task_completed` -> `kind: "subagent"` or
  `metadata.task`, without changing text/reasoning.

### Codex

- `item.id` -> `id`
- `item.type` + explicit tool fields -> `name` / `kind`
- `item.arguments` or provider args -> `input`
- `item.result` / `aggregatedOutput` / `diff` -> `output`
- `item.error` or failed status -> `error`
- `item.durationMs` -> `timing.elapsedMs`
- `mcpToolCall.server/tool` -> `metadata.mcp`
- `webSearch.action` -> `metadata.action` and title derivation
- `collabAgentToolCall` -> `kind: "subagent"`

### OpenCode

- `part.callID || part.id` -> `id`
- `part.tool` -> `name`
- `part.state.input` -> `input`
- `part.state.output` -> `output`
- `part.state.error` -> `error`
- `part.state.time` -> `timing`
- `part.state.metadata.actionDescriptor` -> preferred `title`
- `part.state.metadata.sessionId` on `task` -> `metadata.childSessionId`

## Compatibility path

The first implementation should add fields without breaking existing consumers:

1. Engine emits richer tool events.
2. ACP frames carry `toolName`, `kind`, `title`, `rawInput`, `rawOutput`,
   `error`, `metadata`, `timing`.
3. UI maps these into the existing `ToolPart` while keeping old
   `calling/output-available/output-error` state names until the renderer is
   ready to switch.
4. Once fixtures pass, rename UI state internally to
   `started/running/complete/error`.
