{"_id":"@blackbelt-technology/pi-anthropic-messages","_rev":"3-1d1e214c7d097b20184320a33ac60072","name":"@blackbelt-technology/pi-anthropic-messages","dist-tags":{"latest":"0.3.4"},"versions":{"0.3.2":{"name":"@blackbelt-technology/pi-anthropic-messages","version":"0.3.2","keywords":["pi","pi-package","pi-extension","pi-coding-agent","anthropic","anthropic-messages","mcp","tool-namespacing","proxy"],"license":"MIT","_id":"@blackbelt-technology/pi-anthropic-messages@0.3.2","maintainers":[{"name":"mbotond","email":"botond.molnar@blackbelt.hu"},{"name":"mrbence","email":"dbence10@gmail.com"},{"name":"robertcsakany","email":"robert.csakany@blackbelt.hu"},{"name":"norbert.herczeg","email":"norbert.herczeg@blackbelt.hu"}],"homepage":"https://github.com/BlackBeltTechnology/pi-anthropic-messages#readme","bugs":{"url":"https://github.com/BlackBeltTechnology/pi-anthropic-messages/issues"},"pi":{"extensions":["./extensions/index.ts"]},"dist":{"shasum":"cf9e49dde4aed102a5ba71a05d8bd3d6e6b39a59","tarball":"https://registry.npmjs.org/@blackbelt-technology/pi-anthropic-messages/-/pi-anthropic-messages-0.3.2.tgz","fileCount":7,"integrity":"sha512-X4OHbZNxJCpnUumb+19hp7Uv6XMU9hrl+rXZuyTu3NEWOuHyieJ0FyPIxAcy0fMRDmapbtkAisNCRKaO/D9kwQ==","signatures":[{"sig":"MEUCIQCoOQI8cSHTfM1ly5jhGIlHxrTGF3yvs4AC9gWXEtVcLwIgbKer7VCkyDAJGV8f2hxG9jjS+1GZ5BoH4Mwnz91Uvsw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":50824},"main":"./extensions/index.ts","type":"module","exports":{".":"./extensions/index.ts"},"gitHead":"c5602c87ddfac326898457a4bc400f14b50c56bc","scripts":{"lint":"tsc --noEmit"},"_npmUser":{"name":"robertcsakany","email":"robert.csakany@blackbelt.hu"},"repository":{"url":"git+https://github.com/BlackBeltTechnology/pi-anthropic-messages.git","type":"git"},"_npmVersion":"11.11.0","description":"Anthropic-messages protocol bridge for pi. Activates for any anthropic-messages API session (Anthropic OAuth/API-key + proxy providers): canonicalizes lowercase pi core tools (read/write/bash/grep → Read/Write/Bash/Grep), namespaces custom tools under mcp","directories":{},"_nodeVersion":"25.8.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.7.0","@types/node":"^22.0.0"},"peerDependencies":{"@earendil-works/pi-coding-agent":">=0.74.0"},"_npmOperationalInternal":{"tmp":"tmp/pi-anthropic-messages_0.3.2_1778940628205_0.08071709225460455","host":"s3://npm-registry-packages-npm-production"}},"0.3.3":{"name":"@blackbelt-technology/pi-anthropic-messages","version":"0.3.3","keywords":["pi","pi-package","pi-extension","pi-coding-agent","anthropic","anthropic-messages","mcp","tool-namespacing","proxy"],"license":"MIT","_id":"@blackbelt-technology/pi-anthropic-messages@0.3.3","maintainers":[{"name":"mbotond","email":"botond.molnar@blackbelt.hu"},{"name":"mrbence","email":"dbence10@gmail.com"},{"name":"robertcsakany","email":"robert.csakany@blackbelt.hu"},{"name":"norbert.herczeg","email":"norbert.herczeg@blackbelt.hu"}],"homepage":"https://github.com/BlackBeltTechnology/pi-anthropic-messages#readme","bugs":{"url":"https://github.com/BlackBeltTechnology/pi-anthropic-messages/issues"},"pi":{"extensions":["./extensions/index.ts"]},"dist":{"shasum":"bf5c33672a272ccdb3a89180a8fcb9644e6ddc53","tarball":"https://registry.npmjs.org/@blackbelt-technology/pi-anthropic-messages/-/pi-anthropic-messages-0.3.3.tgz","fileCount":7,"integrity":"sha512-M6U6QT5Wtqw8UzUVgBuITK3sCuNoG0BEVAnXRQohnaLyL9UF4jBgAi1O4dsd6aQpsaJPmVQVN4MTaLe153qvBg==","signatures":[{"sig":"MEUCIQDA662zvl5D5BBr7efZLjUnkLUszxntCW8f3plrpVTI+gIgIuitE6DtCvG9YmNR2Hm+PY8pUvCO6T6DbFRxkEdC+pE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@blackbelt-technology%2fpi-anthropic-messages@0.3.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":50824},"main":"./extensions/index.ts","type":"module","exports":{".":"./extensions/index.ts"},"gitHead":"9e5ffbb4ab5e09f6d1c07cd60bd6117ad426910e","scripts":{"lint":"tsc --noEmit"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9dec9920-9b69-4e6f-93a3-fbc6b3dee3c9"}},"repository":{"url":"git+https://github.com/BlackBeltTechnology/pi-anthropic-messages.git","type":"git"},"_npmVersion":"11.17.0","description":"Anthropic-messages protocol bridge for pi. Activates for any anthropic-messages API session (Anthropic OAuth/API-key + proxy providers): canonicalizes lowercase pi core tools (read/write/bash/grep → Read/Write/Bash/Grep), namespaces custom tools under mcp","directories":{},"_nodeVersion":"22.22.3","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.7.0","@types/node":"^22.0.0"},"peerDependencies":{"@earendil-works/pi-coding-agent":">=0.74.0"},"_npmOperationalInternal":{"tmp":"tmp/pi-anthropic-messages_0.3.3_1781792160594_0.23096569465701133","host":"s3://npm-registry-packages-npm-production"}},"0.3.4":{"name":"@blackbelt-technology/pi-anthropic-messages","version":"0.3.4","publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/BlackBeltTechnology/pi-anthropic-messages.git"},"description":"Anthropic-messages protocol bridge for pi. Activates for any anthropic-messages API session (Anthropic OAuth/API-key + proxy providers): canonicalizes lowercase pi core tools (read/write/bash/grep → Read/Write/Bash/Grep), namespaces custom tools under mcp","keywords":["pi","pi-package","pi-extension","pi-coding-agent","anthropic","anthropic-messages","mcp","tool-namespacing","proxy"],"type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"pi":{"extensions":["./dist/index.js"]},"peerDependencies":{"@earendil-works/pi-coding-agent":">=0.74.0"},"devDependencies":{"@types/node":"^22.0.0","typescript":"^5.7.0"},"scripts":{"lint":"tsc --noEmit","build":"tsc -p tsconfig.build.json","prepack":"npm run build"},"license":"MIT","gitHead":"e872052dbd53613a6290673ac991d539d1cbe66e","_id":"@blackbelt-technology/pi-anthropic-messages@0.3.4","bugs":{"url":"https://github.com/BlackBeltTechnology/pi-anthropic-messages/issues"},"homepage":"https://github.com/BlackBeltTechnology/pi-anthropic-messages#readme","_nodeVersion":"22.22.3","_npmVersion":"11.17.0","dist":{"integrity":"sha512-aqAGbHghmrDMM1AjQQ9e5446ETKTtgCFEAiDDLq75tLtiLD1XZfijhO0Fzu1vVNC9D1q1NbfNpowu9++/qA07A==","shasum":"30d2b70cf6ea785e5431c19ba7a7a707667bc266","tarball":"https://registry.npmjs.org/@blackbelt-technology/pi-anthropic-messages/-/pi-anthropic-messages-0.3.4.tgz","fileCount":12,"unpackedSize":62331,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@blackbelt-technology%2fpi-anthropic-messages@0.3.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDfFolEnXqDWUfiROHQSIDhiPc0Ucy4MM1xD7vfJXRFoQIgCiCVYQ2QfTDlx8+OgZypBhSBWrmxeBf7GnPZeDKZAOs="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9dec9920-9b69-4e6f-93a3-fbc6b3dee3c9"}},"directories":{},"maintainers":[{"name":"mbotond","email":"botond.molnar@blackbelt.hu"},{"name":"mrbence","email":"dbence10@gmail.com"},{"name":"robertcsakany","email":"robert.csakany@blackbelt.hu"},{"name":"norbert.herczeg","email":"norbert.herczeg@blackbelt.hu"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/pi-anthropic-messages_0.3.4_1781794247212_0.8587606095458766"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-16T14:10:28.107Z","modified":"2026-06-18T14:50:47.638Z","0.3.2":"2026-05-16T14:10:28.357Z","0.3.3":"2026-06-18T14:16:00.747Z","0.3.4":"2026-06-18T14:50:47.337Z"},"bugs":{"url":"https://github.com/BlackBeltTechnology/pi-anthropic-messages/issues"},"license":"MIT","homepage":"https://github.com/BlackBeltTechnology/pi-anthropic-messages#readme","keywords":["pi","pi-package","pi-extension","pi-coding-agent","anthropic","anthropic-messages","mcp","tool-namespacing","proxy"],"repository":{"type":"git","url":"git+https://github.com/BlackBeltTechnology/pi-anthropic-messages.git"},"description":"Anthropic-messages protocol bridge for pi. Activates for any anthropic-messages API session (Anthropic OAuth/API-key + proxy providers): canonicalizes lowercase pi core tools (read/write/bash/grep → Read/Write/Bash/Grep), namespaces custom tools under mcp","maintainers":[{"name":"mbotond","email":"botond.molnar@blackbelt.hu"},{"name":"mrbence","email":"dbence10@gmail.com"},{"name":"robertcsakany","email":"robert.csakany@blackbelt.hu"},{"name":"norbert.herczeg","email":"norbert.herczeg@blackbelt.hu"}],"readme":"# @blackbelt-technology/pi-anthropic-messages\n\nProtocol-level bridge for pi when talking to **Claude-model**\nanthropic-messages endpoints — direct Anthropic (OAuth or API key),\n9Router `cc/claude-*`, pi-model-proxy with a Claude backend, or any other\nproxy that forwards Claude Code-flavored traffic through the\nanthropic-messages Messages API.\n\n## What it does\n\nClaude Code's upstream endpoints accept tools in exactly three flavours:\n\n1. **Core Claude Code tools** by canonical name — `Read`, `Write`, `Edit`,\n   `Bash`, `Grep`, `Glob`, `AskUserQuestion`, `Agent`, `WebFetch`,\n   `WebSearch`, …\n2. **MCP tools**, i.e. anything whose name starts with `mcp__<server>__`.\n3. **Anthropic-native typed tools** (`computer_use`,\n   `text_editor_20241022`…).\n\npi-coding-agent registers its built-in tools under lowercase names\n(`read`, `bash`, `edit`, `write`, `grep`) and extensions register custom\npi tools under names like `ask_user`, `web_search`, `fetch_content`,\n`subagent`. None of those match the Claude Code canonical allowlist, so\nwithout intervention they either get stripped or the endpoint mangles\nthem with an `_ide` suffix. The model then falls back to a hallucinated\n`bash_ide` and every tool call fails.\n\nThis extension intervenes at the protocol layer:\n\n- **Outbound** (`before_provider_request`): rewrites pi tool names to\n  something the endpoint accepts. Canonical pi core tools (`read`, `write`,\n  `bash`, `grep`) become their Claude Code capitalization (`Read`, `Write`,\n  `Bash`, `Grep`). Everything else that isn't already canonical, already\n  `mcp__*`-prefixed, or aliased to an Anthropic-native tool becomes\n  `mcp__pi__<name>`. `tool_choice.name` and historical `tool_use` blocks in\n  message history get the same rewrites. The system prompt is lightly\n  rewritten (`pi` → `the cli`) so Claude Code identity fingerprints don't\n  trip on the word \"pi\".\n- **Inbound** (`message_end`): translates the model's renamed tool calls\n  back to their original pi names before the agent dispatches, so existing\n  tool registrations work without modification. A defensive `_ide` suffix\n  strip handles cases where the Claude Code endpoint mangles the response.\n  Tools registered directly under a canonical Claude Code name (e.g.\n  `Agent`, `AskUserQuestion`) are covered end-to-end: the reverse map\n  contains an identity entry so `Agent_ide` strips back to the registered\n  `Agent` handler.\n\nThe tool registry itself is never mutated — every other pi extension\ncontinues to register its tools under their original names and the bridge\nhandles the translation transparently.\n\n## Activation — single tight gate\n\nThe bridge activates only when BOTH of these hold for the active\nsession's model:\n\n1. `ctx.model.api === \"anthropic-messages\"`\n2. `/claude/i.test(ctx.model.id ?? \"\")` — the model id contains the\n   case-insensitive substring `claude`\n\n```ts\n// Simplified:\nfunction isClaudeAnthropicMessages(ctx) {\n  return ctx.model?.api === \"anthropic-messages\"\n      && /claude/i.test(ctx.model?.id ?? \"\");\n}\n```\n\nWhen the gate fails — i.e. the session targets a non-Claude model on\nanthropic-messages (e.g. `9Router/glm/glm-5`,\n`9Router/gemini/gemini-3-pro-preview`) or any `api` other than\n`anthropic-messages` — **every hook handler is a true no-op**: no tool\nrename, no `mcp__pi__` prefix, no system prompt rewrite, no reverse map,\nno `_ide` strip. Tool names flow through pi's registry unchanged.\n\nThis is deliberate: the `mcp__pi__` namespace and the canonical casing are\nClaude Code conventions. Non-Claude endpoints that happen to speak\nanthropic-messages format don't care about them, and the bridge shouldn't\nimpose them.\n\n### Escape hatches\n\nTwo environment variables override the gate for the rare cases it\ndoesn't match intent:\n\n| Variable | Effect |\n|---|---|\n| `PI_ANTHROPIC_MESSAGES_FORCE_CANONICAL=1` | Forces the gate open for any `anthropic-messages` session regardless of model id. Useful for Claude models with unusual ids (e.g. `c4-omega`). |\n| `PI_ANTHROPIC_MESSAGES_DISABLE_CANONICAL=1` | Forces the gate closed even for Claude-matching sessions. Useful for false positives (e.g. a non-Claude model whose id happens to contain \"claude\"). |\n\nBoth still require `api === \"anthropic-messages\"`. For any other api the\nbridge is always a no-op.\n\n## For package authors\n\n### The short version\n\nRegister your tool with its natural name:\n\n```ts\npi.registerTool({ name: \"my_tool\", /* … */ });\n```\n\nFor Claude-model anthropic-messages sessions, the bridge automatically\nsends `my_tool` to the wire as `mcp__pi__my_tool` and translates the\nmodel's response back to `my_tool` before pi dispatches. For every other\nprovider (OpenAI, Google, Bedrock, non-Claude anthropic-messages), the\nname is sent as-is. No configuration required.\n\n### When to register under a canonical name\n\nIf your tool has the **exact name and a compatible schema** with a\ncanonical Claude Code tool (see\n[Claude Code tools reference](https://docs.claude.com/en/docs/claude-code/tools-reference)),\nregister under the canonical name with its exact capitalization:\n\n```ts\npi.registerTool({\n  name: \"Agent\",          // matches canonical CC Agent tool\n  parameters: /* … */     // schema compatible with CC's Agent\n});\n```\n\nThat passes through unchanged, giving Anthropic's surfaces a native\nrendering (e.g. the \"Agent\" card in Claude apps). The bridge's\n`CC_CANONICAL_NAMES` set lists every accepted canonical name.\n\n**Do not** do this if your schema differs from the canonical one — the\nmodel will hallucinate canonical-shaped arguments and your handler will\nfail. When in doubt, use the natural lowercase/snake_case name and let\nthe `mcp__pi__` prefix take care of it.\n\n### Tools with no pi equivalent\n\nClaude Code's `WebSearch`, `WebFetch`, `AskUserQuestion`, `TodoWrite`,\n`NotebookEdit`, `ExitPlanMode`, `EnterPlanMode`, `KillShell`, `Skill`,\netc. have no direct pi equivalent. You don't need to do anything about\nthem. If no extension registers a tool under those names, the model\nsimply won't have them in this session — pi provides functional\nequivalents under `mcp__pi__*` (`mcp__pi__ask_user`,\n`mcp__pi__web_search`, `mcp__pi__fetch_content`, …) and the model uses\nthose instead.\n\n## Schema adapters (planned)\n\n### The problem: name aliasing is not enough\n\nRenaming `web_search` → `WebSearch` on the wire gives the model a\ncanonical name it recognizes from training — but the model then calls\nthe tool with **Claude Code's expected input shape**, not pi's. The\nschemas don't match:\n\n| Tool | Claude Code canonical schema | Pi extension schema | Gap |\n|---|---|---|---|\n| `web_search` → `WebSearch` | `{ query, allowed_domains?, blocked_domains? }` | `{ query?, queries?[], numResults?, recencyFilter?, domainFilter?[], provider?, workflow?, includeContent? }` | CC is a strict subset; pi supports multi-query, recency filtering, provider selection, content prefetch |\n| `fetch_content` → `WebFetch` | `{ url, prompt? }` | `{ url?, urls?[], prompt?, timestamp?, frames?, forceClone?, model? }` | CC is a strict subset; pi supports multi-URL, video frame extraction, GitHub clone |\n| `get_subagent_result` → `TaskOutput` | `{ task_id, wait?, verbose? }` | `{ agent_id, wait?, verbose? }` | Near-identical; field rename `task_id` ↔ `agent_id` |\n| `ask_user` → `AskUserQuestion` | `{ question, options?[], allow_multiple? }` | `{ method: confirm\\|select\\|multiselect\\|input\\|batch, prompt, options?[], questions?[] }` | Fundamentally different model — pi's discriminated union with batch support loses too much expressiveness to adapt |\n\nWithout schema translation, a name-only alias causes the model to send\ncanonical-shaped input (`{ query: \"foo\" }`) to a handler expecting pi's\nshape — it silently works but **the model can never discover or use pi's\nricher features** (multi-query, recency, domain filtering, etc.).\n\n### Solution: per-tool binding adapters\n\nEach tool that benefits from canonicalization gets a **ToolBinding** with\nan adapter that translates between the canonical wire schema and pi's\nhandler schema:\n\n```ts\nexport interface ToolBinding {\n  /** Pi's registered tool name. */\n  piName: string;\n\n  /** Canonical name visible on the wire / to the model. */\n  canonicalName: string;\n\n  /**\n   * Schema strategy:\n   *   passthrough — pi's schema under the canonical name, zero translation.\n   *   canonical   — canonical schema only; adaptInput required.\n   *   hybrid      — canonical fields + pi-specific extras exposed.\n   */\n  schemaStrategy: \"passthrough\" | \"canonical\" | \"hybrid\";\n\n  /** Schema sent to the model. Omitted for passthrough. */\n  canonicalSchema?: TSchema;\n\n  /** Reshape model's canonical input → pi handler's expected input. */\n  adaptInput?: (wireInput: unknown) => unknown;\n\n  /** Reshape pi's result → canonical result (rarely needed). */\n  adaptOutput?: (piResult: unknown) => unknown;\n}\n```\n\nThe **hybrid** strategy is the sweet spot for feature-rich pi tools: it\nexposes the canonical fields the model is trained on *plus* pi-specific\nextras, so the model can use either shape. The adapter normalizes\nwhichever the model produces.\n\n### Planned bindings\n\n| Pi tool | Canonical | Strategy | Rationale |\n|---|---|---|---|\n| `web_search` | `WebSearch` | **hybrid** | Expose `query` + CC domain fields + pi extras (`queries[]`, `recencyFilter`, `numResults`). Adapter merges `allowed_domains`/`blocked_domains` → pi's `domainFilter[]` (prefix `-` for blocked). |\n| `fetch_content` | `WebFetch` | **hybrid** | Expose `url` + `prompt` + pi extras (`urls[]`, `timestamp`, `frames`). Adapter normalizes `url`/`urls` to pi's shape. |\n| `get_subagent_result` | `TaskOutput` | **canonical** | Near-1:1 schema. Adapter renames `task_id` → `agent_id`. |\n| `ask_user` | — | **skip** | Pi's discriminated union (`confirm`/`select`/`multiselect`/`input`/`batch`) is fundamentally richer than CC's flat `AskUserQuestion`. Adapting would lose batch support, typed confirm UX, and multiselect semantics. Stays as `mcp__pi__ask_user`. |\n\n### Data flow with adapters\n\n```\n  OUTBOUND (before_provider_request)\n  ──────────────────────────────────\n  1. Find binding by pi tool name\n  2. Replace tool.name with binding.canonicalName\n  3. If hybrid/canonical: replace tool.input_schema with binding.canonicalSchema\n  4. Rewrite historical tool_use blocks in messages (name + input if needed)\n\n  INBOUND (message_end)\n  ─────────────────────\n  1. Find binding by canonical name on tool_use block\n  2. Restore tool_use.name to binding.piName\n  3. If adaptInput defined: tool_use.input = binding.adaptInput(tool_use.input)\n  4. Pi dispatches to original handler with pi-shaped arguments ✓\n```\n\n### Adapter example: `web_search → WebSearch`\n\n```ts\n// Hybrid schema: CC canonical fields + pi extras.\n// Model can use either { query, blocked_domains } (CC-trained)\n// or { queries, recencyFilter, numResults } (pi-specific).\nconst webSearchBinding: ToolBinding = {\n  piName: \"web_search\",\n  canonicalName: \"WebSearch\",\n  schemaStrategy: \"hybrid\",\n  canonicalSchema: Type.Object({\n    query:             Type.Optional(Type.String()),\n    allowed_domains:   Type.Optional(Type.Array(Type.String())),\n    blocked_domains:   Type.Optional(Type.Array(Type.String())),\n    // pi extras:\n    queries:           Type.Optional(Type.Array(Type.String())),\n    numResults:        Type.Optional(Type.Number()),\n    recencyFilter:     Type.Optional(StringEnum([\"day\",\"week\",\"month\",\"year\"])),\n  }),\n  adaptInput: (cc: any) => {\n    const out: Record<string, unknown> = {};\n    // Prefer queries[] over query (pi's richer multi-query)\n    if (cc.queries?.length) out.queries = cc.queries;\n    else if (cc.query)      out.query = cc.query;\n    // Merge CC domain fields → pi's unified domainFilter[]\n    const dom: string[] = [];\n    if (cc.allowed_domains) dom.push(...cc.allowed_domains);\n    if (cc.blocked_domains) dom.push(...cc.blocked_domains.map((d: string) => `-${d}`));\n    if (dom.length)         out.domainFilter = dom;\n    if (cc.numResults    !== undefined) out.numResults    = cc.numResults;\n    if (cc.recencyFilter !== undefined) out.recencyFilter = cc.recencyFilter;\n    return out;\n  },\n};\n```\n\n### Dynamic binding registration\n\nBindings can be registered in two ways:\n\n1. **Built-in** — shipped in `extensions/bindings/` within this package\n   for the recommended extensions (`web_search`, `fetch_content`,\n   `get_subagent_result`).\n\n2. **Dynamic** — any extension can register a binding at runtime via a\n   shared `globalThis` registry, supporting both install-order\n   scenarios:\n\n```ts\n// In any extension's onLoad / session_start:\nconst registry = (globalThis as any).__piAnthropicBindings__;\nif (registry) {\n  registry.register({\n    piName: \"my_tool\",\n    canonicalName: \"MyCanonical\",\n    schemaStrategy: \"passthrough\",\n  });\n}\n```\n\nThe bridge exposes the registry on `globalThis.__piAnthropicBindings__`\nat load time. If the bridge isn't installed, the property doesn't exist\nand the `if (registry)` guard is a no-op — zero coupling.\n\nFor **order-independent install** (bridge loads before or after other\nextensions), both sides use a symmetric emit-and-listen pattern:\n\n- Each extension that wants to register a binding calls\n  `declareToolBinding()` at load time. This both emits the binding\n  immediately AND subscribes to future `request_bindings` events so it\n  can re-emit if the bridge loads later.\n- The bridge subscribes to `register_binding` events AND emits\n  `request_bindings` on load so already-running extensions re-announce.\n- **Result**: regardless of load order, all bindings converge.\n\n### Precedence in `resolveOutboundName` (with adapters)\n\nOnce adapters are implemented, the outbound name resolution gains a new\nhighest-priority step:\n\n```\n  1. binding registry (new)     ← adapter-equipped, schema translation\n  2. CC_CANONICAL_NAMES          exact-case passthrough (name only)\n  3. already mcp__*              passthrough\n  4. NATIVE_ALIASES (legacy)     name-only alias\n  5. FLAT_TO_MCP                 companion aliases\n  6. PI_TO_CC_CANONICAL          core tool capitalization\n  7. default                     mcp__pi__<name>\n```\n\n### Testing strategy\n\nEach binding adapter is a pure function (`wireInput → piInput`) tested\nin isolation — no wire, no hooks, no mocks:\n\n```ts\ntest(\"blocked_domains → negative domainFilter entries\", () => {\n  expect(webSearchBinding.adaptInput({\n    query: \"foo\", blocked_domains: [\"reddit.com\", \"x.com\"]\n  })).toEqual({\n    query: \"foo\", domainFilter: [\"-reddit.com\", \"-x.com\"]\n  });\n});\n\ntest(\"queries[] preferred over query\", () => {\n  expect(webSearchBinding.adaptInput({\n    query: \"A\", queries: [\"B\", \"C\"]\n  })).toEqual({ queries: [\"B\", \"C\"] });\n});\n```\n\n### Canonical schema drift\n\nAnthropic may update tool schemas over time. Mitigations:\n- Keep `canonicalSchema` **minimal** — only map fields we actually\n  translate. Fewer fields = less drift surface.\n- Pin the Claude Code docs version in a comment per binding.\n- Hybrid strategy is partially self-healing — unknown canonical fields\n  flow through the adapter untouched (pi handler ignores them).\n\n## MCP naming convention\n\nThe MCP namespace convention is `mcp__<server>__<tool>` where `__`\n(double underscore) is the segment delimiter. The tool-name portion —\nanything after the second `__` — may contain single underscores freely:\n\n```\nmcp__pi__web_search               ✓  server=pi, tool=web_search\nmcp__pi__get_subagent_result      ✓  server=pi, tool=get_subagent_result\nmcp__exa__get_code_context        ✓  server=exa, tool=get_code_context\n```\n\nEven when the Claude Code endpoint appends its `_ide` suffix, the delimiter\n`__` is preserved:\n\n```\nmcp__pi__web_search_ide           server=pi, tool=web_search_ide\n```\n\nThe bridge's reverse-map lookup is map-based (not `split(\"__\")`-based),\nso single underscores in tool bodies are never confused with segment\ndelimiters. The `_ide` mangling is handled by a single suffix-strip\nretry.\n\n## Configuration\n\nMost users don't need any configuration. The defaults ship sensible\nvalues:\n\n- `CC_CANONICAL_NAMES` — exact Claude Code canonical tool names that\n  pass through unchanged.\n- `PI_TO_CC_CANONICAL` — pi lowercase core tools → canonical\n  capitalization (`read` → `Read`, `write` → `Write`, `bash` → `Bash`,\n  `grep` → `Grep`).\n- `FLAT_TO_MCP` — well-known third-party companions (Exa, Firecrawl,\n  Antigravity).\n- `NATIVE_ALIASES` — **empty by default**. Populate in `core-tools.ts` if\n  you want to alias a pi tool to its Anthropic-native equivalent (e.g.\n  `ask_user` → `AskUserQuestion`). Only enable when you've verified the\n  schemas are compatible — Anthropic will deliver the native-shaped\n  input.\n\n### Environment variables\n\n| Variable | Purpose |\n|---|---|\n| `PI_ANTHROPIC_MESSAGES_DEBUG_LOG=/tmp/pi-am.log` | Dump every outbound before/after payload and inbound rename to the given path. Logger failures never break requests. |\n| `PI_ANTHROPIC_MESSAGES_FORCE_CANONICAL=1` | Force the gate open on any anthropic-messages session regardless of model id. |\n| `PI_ANTHROPIC_MESSAGES_DISABLE_CANONICAL=1` | Force the gate closed even for Claude-matching sessions. |\n\n## Install (local)\n\n```bash\npi install -l /home/botond/pi-packages/pi-anthropic-messages\n```\n\nOr from GitHub (SSH):\n\n```bash\npi install git@github.com:BlackBeltTechnology/pi-anthropic-messages.git\n```\n\n## Dashboard recommended extensions\n\n`pi-agent-dashboard` ships a curated manifest of extensions it integrates\nwith — including this bridge (required for Claude-model\nanthropic-messages providers), `@tintinweb/pi-subagents` (Agent card UI),\n`pi-flows` (Flow dashboard), `pi-web-access` (web tools), and\n`pi-agent-browser` (browser automation).\n\nThe dashboard's first-launch wizard prompts for installation; its\nPackages tab shows a Recommended section with live install/active state;\nand a banner surfaces any missing **required** entry until it's resolved.\nSee the dashboard's `packages/shared/src/recommended-extensions.ts`\nmanifest for the authoritative list.\n\nTypical installs:\n\n```bash\n# Required bridge (this package)\npi install git@github.com:BlackBeltTechnology/pi-anthropic-messages.git\n\n# Strongly suggested\npi install npm:@tintinweb/pi-subagents\npi install git@github.com:BlackBeltTechnology/pi-flows.git\npi install npm:pi-web-access\n\n# Optional\npi install npm:pi-agent-browser\n```\n\n## Relationship to other packages\n\n- **`@earendil-works/pi-ai`** — the Anthropic provider in pi-ai already\n  canonicalizes tool names (`toClaudeCodeName`) when it detects an\n  **OAuth token**. Our bridge is idempotent with that behavior: for\n  OAuth sessions pi-ai rewrites `read` → `Read` before we see the\n  payload, our exact-match check against `CC_CANONICAL_NAMES` finds\n  `Read`, and we pass it through unchanged. For non-OAuth Claude\n  anthropic-messages sessions (9Router `sk-*` key, pi-model-proxy,\n  OAuth-subscription proxies, etc.), pi-ai does nothing and our bridge\n  does the canonicalization. No double rename, no conflict.\n- **`@benvargas/pi-claude-code-use`** — superseded by this package for\n  this repo. This package has a superset of behaviour (adds `mcp__`\n  prefixing for custom tools; gates on Claude-model detection; does not\n  filter tools).\n- **`pi-flows`** — its subagent-side tool prefixing\n  (`extensions/flow-engine/tool-prefix.ts`, `mcp__flows__`) handles\n  in-process SDK sessions; this package handles the main session\n  payload. Both complement each other.\n- **`pi-agent-dashboard`** — pulls this bridge in as a required\n  recommended extension for any Claude-model anthropic-messages provider\n  setup.\n\n## Releasing\n\nReleases are cut via the [`release-cut`](.pi/skills/release-cut/SKILL.md) skill and revoked via [`release-revoke`](.pi/skills/release-revoke/SKILL.md); both walk through pre-flight, CHANGELOG curation, version bump, tag, and push, with the actual `npm publish` happening on GitHub Actions (`.github/workflows/release.yml`). The Release workflow can also be triggered by `workflow_dispatch` from the Actions UI with a version input, which performs the bump-commit-tag-push on the runner.\n\n## Exported API\n\nFor consumers that need the authoritative lists without duplication:\n\n```ts\nimport {\n  CC_CANONICAL_NAMES,\n  PI_TO_CC_CANONICAL,\n  NATIVE_ALIASES,\n  FLAT_TO_MCP,\n  DEFAULT_MCP_PREFIX,\n  resolveOutboundName,\n  transformPayload,\n  buildReverseMap,\n  lookupReverse,\n  renameToolCallsInPlace,\n  isClaudeAnthropicMessages,\n} from \"@blackbelt-technology/pi-anthropic-messages\";\n```\n","readmeFilename":"README.md"}