@blackbelt-technology/pi-anthropic-messages

中文文档
0.3.5发布于 2026/10/28MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 3743

RETIRED — uninstall. Use pi-agent-dashboard's Claude subscription compat switch instead. Anthropic-messages protocol bridge for pi. Activates for any anthropic-messages API session (Anthropic OAuth/API-key + proxy providers): canonicalizes lowercase pi co

原始使用文档
文档目录25 节
  1. What it does
  2. Activation — single tight gate
  3. Escape hatches
  4. For package authors
  5. The short version
  6. When to register under a canonical name
  7. Tools with no pi equivalent
  8. Schema adapters (planned)
  9. The problem: name aliasing is not enough
  10. Solution: per-tool binding adapters
  11. Planned bindings
  12. Data flow with adapters
  13. Adapter example: web_search → WebSearch
  14. Dynamic binding registration
  15. Precedence in resolveOutboundName (with adapters)
  16. Testing strategy
  17. Canonical schema drift
  18. MCP naming convention
  19. Configuration
  20. Environment variables
  21. Install (local)
  22. Dashboard recommended extensions
  23. Relationship to other packages
  24. Releasing
  25. Exported API

@blackbelt-technology/pi-anthropic-messages

RETIRED (2026-10-28). Uninstall this package.

pi remove npm:@blackbelt-technology/pi-anthropic-messages
rm -f /tmp/pi-am.log   # versions ≤ 0.3.4 wrote every prompt here

A live test showed tool renaming has no effect on Claude subscription billing. Only two lines of pi's built-in docs prompt section matter ("pi itself", "pi packages"). pi-agent-dashboard now handles this with its Claude subscription compat switch (off by default, on the anthropic provider row), applied to the main session and flow agents (pi-agent-dashboard f59e34aa3). The tool renaming in this package also causes a 400 when tools are removed mid-chat. Versions ≤ 0.3.4 logged every request payload to a world-readable /tmp/pi-am.log; 0.3.5 removes that logging and is the final release. Findings: docs/reports/2026-10-28-bridge-necessity-report.md.

Protocol-level bridge for pi when talking to Claude-model anthropic-messages endpoints — direct Anthropic (OAuth or API key), 9Router cc/claude-*, pi-model-proxy with a Claude backend, or any other proxy that forwards Claude Code-flavored traffic through the anthropic-messages Messages API.

What it does

Claude Code's upstream endpoints accept tools in exactly three flavours:

  1. Core Claude Code tools by canonical name — Read, Write, Edit, Bash, Grep, Glob, AskUserQuestion, Agent, WebFetch, WebSearch, …
  2. MCP tools, i.e. anything whose name starts with mcp__<server>__.
  3. Anthropic-native typed tools (computer_use, text_editor_20241022…).

pi-coding-agent registers its built-in tools under lowercase names (read, bash, edit, write, grep) and extensions register custom pi tools under names like ask_user, web_search, fetch_content, subagent. None of those match the Claude Code canonical allowlist, so without intervention they either get stripped or the endpoint mangles them with an _ide suffix. The model then falls back to a hallucinated bash_ide and every tool call fails.

This extension intervenes at the protocol layer:

  • Outbound (before_provider_request): rewrites pi tool names to something the endpoint accepts. Canonical pi core tools (read, write, bash, grep) become their Claude Code capitalization (Read, Write, Bash, Grep). Everything else that isn't already canonical, already mcp__*-prefixed, or aliased to an Anthropic-native tool becomes mcp__pi__<name>. tool_choice.name and historical tool_use blocks in message history get the same rewrites. The system prompt is lightly rewritten (pi → the cli) so Claude Code identity fingerprints don't trip on the word "pi".
  • Inbound (message_end): translates the model's renamed tool calls back to their original pi names before the agent dispatches, so existing tool registrations work without modification. A defensive _ide suffix strip handles cases where the Claude Code endpoint mangles the response. Tools registered directly under a canonical Claude Code name (e.g. Agent, AskUserQuestion) are covered end-to-end: the reverse map contains an identity entry so Agent_ide strips back to the registered Agent handler.

The tool registry itself is never mutated — every other pi extension continues to register its tools under their original names and the bridge handles the translation transparently.

Activation — single tight gate

The bridge activates only when BOTH of these hold for the active session's model:

  1. ctx.model.api === "anthropic-messages"
  2. /claude/i.test(ctx.model.id ?? "") — the model id contains the case-insensitive substring claude
// Simplified:
function isClaudeAnthropicMessages(ctx) {
  return ctx.model?.api === "anthropic-messages"
      && /claude/i.test(ctx.model?.id ?? "");
}

When the gate fails — i.e. the session targets a non-Claude model on anthropic-messages (e.g. 9Router/glm/glm-5, 9Router/gemini/gemini-3-pro-preview) or any api other than anthropic-messages — every hook handler is a true no-op: no tool rename, no mcp__pi__ prefix, no system prompt rewrite, no reverse map, no _ide strip. Tool names flow through pi's registry unchanged.

This is deliberate: the mcp__pi__ namespace and the canonical casing are Claude Code conventions. Non-Claude endpoints that happen to speak anthropic-messages format don't care about them, and the bridge shouldn't impose them.

Escape hatches

Two environment variables override the gate for the rare cases it doesn't match intent:

VariableEffect
PI_ANTHROPIC_MESSAGES_FORCE_CANONICAL=1Forces the gate open for any anthropic-messages session regardless of model id. Useful for Claude models with unusual ids (e.g. c4-omega).
PI_ANTHROPIC_MESSAGES_DISABLE_CANONICAL=1Forces the gate closed even for Claude-matching sessions. Useful for false positives (e.g. a non-Claude model whose id happens to contain "claude").

Both still require api === "anthropic-messages". For any other api the bridge is always a no-op.

For package authors

The short version

Register your tool with its natural name:

pi.registerTool({ name: "my_tool", /* … */ });

For Claude-model anthropic-messages sessions, the bridge automatically sends my_tool to the wire as mcp__pi__my_tool and translates the model's response back to my_tool before pi dispatches. For every other provider (OpenAI, Google, Bedrock, non-Claude anthropic-messages), the name is sent as-is. No configuration required.

When to register under a canonical name

If your tool has the exact name and a compatible schema with a canonical Claude Code tool (see Claude Code tools reference), register under the canonical name with its exact capitalization:

pi.registerTool({
  name: "Agent",          // matches canonical CC Agent tool
  parameters: /* … */     // schema compatible with CC's Agent
});

That passes through unchanged, giving Anthropic's surfaces a native rendering (e.g. the "Agent" card in Claude apps). The bridge's CC_CANONICAL_NAMES set lists every accepted canonical name.

Do not do this if your schema differs from the canonical one — the model will hallucinate canonical-shaped arguments and your handler will fail. When in doubt, use the natural lowercase/snake_case name and let the mcp__pi__ prefix take care of it.

Tools with no pi equivalent

Claude Code's WebSearch, WebFetch, AskUserQuestion, TodoWrite, NotebookEdit, ExitPlanMode, EnterPlanMode, KillShell, Skill, etc. have no direct pi equivalent. You don't need to do anything about them. If no extension registers a tool under those names, the model simply won't have them in this session — pi provides functional equivalents under mcp__pi__* (mcp__pi__ask_user, mcp__pi__web_search, mcp__pi__fetch_content, …) and the model uses those instead.

Schema adapters (planned)

The problem: name aliasing is not enough

Renaming web_search → WebSearch on the wire gives the model a canonical name it recognizes from training — but the model then calls the tool with Claude Code's expected input shape, not pi's. The schemas don't match:

ToolClaude Code canonical schemaPi extension schemaGap
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
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
get_subagent_result → TaskOutput{ task_id, wait?, verbose? }{ agent_id, wait?, verbose? }Near-identical; field rename task_id ↔ agent_id
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

Without schema translation, a name-only alias causes the model to send canonical-shaped input ({ query: "foo" }) to a handler expecting pi's shape — it silently works but the model can never discover or use pi's richer features (multi-query, recency, domain filtering, etc.).

Solution: per-tool binding adapters

Each tool that benefits from canonicalization gets a ToolBinding with an adapter that translates between the canonical wire schema and pi's handler schema:

export interface ToolBinding {
  /** Pi's registered tool name. */
  piName: string;

  /** Canonical name visible on the wire / to the model. */
  canonicalName: string;

  /**
   * Schema strategy:
   *   passthrough — pi's schema under the canonical name, zero translation.
   *   canonical   — canonical schema only; adaptInput required.
   *   hybrid      — canonical fields + pi-specific extras exposed.
   */
  schemaStrategy: "passthrough" | "canonical" | "hybrid";

  /** Schema sent to the model. Omitted for passthrough. */
  canonicalSchema?: TSchema;

  /** Reshape model's canonical input → pi handler's expected input. */
  adaptInput?: (wireInput: unknown) => unknown;

  /** Reshape pi's result → canonical result (rarely needed). */
  adaptOutput?: (piResult: unknown) => unknown;
}

The hybrid strategy is the sweet spot for feature-rich pi tools: it exposes the canonical fields the model is trained on plus pi-specific extras, so the model can use either shape. The adapter normalizes whichever the model produces.

Planned bindings

Pi toolCanonicalStrategyRationale
web_searchWebSearchhybridExpose query + CC domain fields + pi extras (queries[], recencyFilter, numResults). Adapter merges allowed_domains/blocked_domains → pi's domainFilter[] (prefix - for blocked).
fetch_contentWebFetchhybridExpose url + prompt + pi extras (urls[], timestamp, frames). Adapter normalizes url/urls to pi's shape.
get_subagent_resultTaskOutputcanonicalNear-1:1 schema. Adapter renames task_id → agent_id.
ask_user—skipPi'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.

Data flow with adapters

  OUTBOUND (before_provider_request)
  ──────────────────────────────────
  1. Find binding by pi tool name
  2. Replace tool.name with binding.canonicalName
  3. If hybrid/canonical: replace tool.input_schema with binding.canonicalSchema
  4. Rewrite historical tool_use blocks in messages (name + input if needed)

  INBOUND (message_end)
  ─────────────────────
  1. Find binding by canonical name on tool_use block
  2. Restore tool_use.name to binding.piName
  3. If adaptInput defined: tool_use.input = binding.adaptInput(tool_use.input)
  4. Pi dispatches to original handler with pi-shaped arguments ✓

Adapter example: web_search → WebSearch

// Hybrid schema: CC canonical fields + pi extras.
// Model can use either { query, blocked_domains } (CC-trained)
// or { queries, recencyFilter, numResults } (pi-specific).
const webSearchBinding: ToolBinding = {
  piName: "web_search",
  canonicalName: "WebSearch",
  schemaStrategy: "hybrid",
  canonicalSchema: Type.Object({
    query:             Type.Optional(Type.String()),
    allowed_domains:   Type.Optional(Type.Array(Type.String())),
    blocked_domains:   Type.Optional(Type.Array(Type.String())),
    // pi extras:
    queries:           Type.Optional(Type.Array(Type.String())),
    numResults:        Type.Optional(Type.Number()),
    recencyFilter:     Type.Optional(StringEnum(["day","week","month","year"])),
  }),
  adaptInput: (cc: any) => {
    const out: Record<string, unknown> = {};
    // Prefer queries[] over query (pi's richer multi-query)
    if (cc.queries?.length) out.queries = cc.queries;
    else if (cc.query)      out.query = cc.query;
    // Merge CC domain fields → pi's unified domainFilter[]
    const dom: string[] = [];
    if (cc.allowed_domains) dom.push(...cc.allowed_domains);
    if (cc.blocked_domains) dom.push(...cc.blocked_domains.map((d: string) => `-${d}`));
    if (dom.length)         out.domainFilter = dom;
    if (cc.numResults    !== undefined) out.numResults    = cc.numResults;
    if (cc.recencyFilter !== undefined) out.recencyFilter = cc.recencyFilter;
    return out;
  },
};

Dynamic binding registration

Bindings can be registered in two ways:

  1. Built-in — shipped in extensions/bindings/ within this package for the recommended extensions (web_search, fetch_content, get_subagent_result).

  2. Dynamic — any extension can register a binding at runtime via a shared globalThis registry, supporting both install-order scenarios:

// In any extension's onLoad / session_start:
const registry = (globalThis as any).__piAnthropicBindings__;
if (registry) {
  registry.register({
    piName: "my_tool",
    canonicalName: "MyCanonical",
    schemaStrategy: "passthrough",
  });
}

The bridge exposes the registry on globalThis.__piAnthropicBindings__ at load time. If the bridge isn't installed, the property doesn't exist and the if (registry) guard is a no-op — zero coupling.

For order-independent install (bridge loads before or after other extensions), both sides use a symmetric emit-and-listen pattern:

  • Each extension that wants to register a binding calls declareToolBinding() at load time. This both emits the binding immediately AND subscribes to future request_bindings events so it can re-emit if the bridge loads later.
  • The bridge subscribes to register_binding events AND emits request_bindings on load so already-running extensions re-announce.
  • Result: regardless of load order, all bindings converge.

Precedence in resolveOutboundName (with adapters)

Once adapters are implemented, the outbound name resolution gains a new highest-priority step:

  1. binding registry (new)     ← adapter-equipped, schema translation
  2. CC_CANONICAL_NAMES          exact-case passthrough (name only)
  3. already mcp__*              passthrough
  4. NATIVE_ALIASES (legacy)     name-only alias
  5. FLAT_TO_MCP                 companion aliases
  6. PI_TO_CC_CANONICAL          core tool capitalization
  7. default                     mcp__pi__<name>

Testing strategy

Each binding adapter is a pure function (wireInput → piInput) tested in isolation — no wire, no hooks, no mocks:

test("blocked_domains → negative domainFilter entries", () => {
  expect(webSearchBinding.adaptInput({
    query: "foo", blocked_domains: ["reddit.com", "x.com"]
  })).toEqual({
    query: "foo", domainFilter: ["-reddit.com", "-x.com"]
  });
});

test("queries[] preferred over query", () => {
  expect(webSearchBinding.adaptInput({
    query: "A", queries: ["B", "C"]
  })).toEqual({ queries: ["B", "C"] });
});

Canonical schema drift

Anthropic may update tool schemas over time. Mitigations:

  • Keep canonicalSchema minimal — only map fields we actually translate. Fewer fields = less drift surface.
  • Pin the Claude Code docs version in a comment per binding.
  • Hybrid strategy is partially self-healing — unknown canonical fields flow through the adapter untouched (pi handler ignores them).

MCP naming convention

The MCP namespace convention is mcp__<server>__<tool> where __ (double underscore) is the segment delimiter. The tool-name portion — anything after the second __ — may contain single underscores freely:

mcp__pi__web_search               ✓  server=pi, tool=web_search
mcp__pi__get_subagent_result      ✓  server=pi, tool=get_subagent_result
mcp__exa__get_code_context        ✓  server=exa, tool=get_code_context

Even when the Claude Code endpoint appends its _ide suffix, the delimiter __ is preserved:

mcp__pi__web_search_ide           server=pi, tool=web_search_ide

The bridge's reverse-map lookup is map-based (not split("__")-based), so single underscores in tool bodies are never confused with segment delimiters. The _ide mangling is handled by a single suffix-strip retry.

Configuration

Most users don't need any configuration. The defaults ship sensible values:

  • CC_CANONICAL_NAMES — exact Claude Code canonical tool names that pass through unchanged.
  • PI_TO_CC_CANONICAL — pi lowercase core tools → canonical capitalization (read → Read, write → Write, bash → Bash, grep → Grep).
  • FLAT_TO_MCP — well-known third-party companions (Exa, Firecrawl, Antigravity).
  • NATIVE_ALIASES — empty by default. Populate in core-tools.ts if you want to alias a pi tool to its Anthropic-native equivalent (e.g. ask_user → AskUserQuestion). Only enable when you've verified the schemas are compatible — Anthropic will deliver the native-shaped input.

Environment variables

VariablePurpose
PI_ANTHROPIC_MESSAGES_FORCE_CANONICAL=1Force the gate open on any anthropic-messages session regardless of model id.
PI_ANTHROPIC_MESSAGES_DISABLE_CANONICAL=1Force the gate closed even for Claude-matching sessions.

Install (local)

pi install -l /home/botond/pi-packages/pi-anthropic-messages

Or from GitHub (SSH):

pi install [email protected]:BlackBeltTechnology/pi-anthropic-messages.git

pi-agent-dashboard ships a curated manifest of extensions it integrates with — including this bridge (required for Claude-model anthropic-messages providers), @tintinweb/pi-subagents (Agent card UI), pi-flows (Flow dashboard), pi-web-access (web tools), and pi-agent-browser (browser automation).

The dashboard's first-launch wizard prompts for installation; its Packages tab shows a Recommended section with live install/active state; and a banner surfaces any missing required entry until it's resolved. See the dashboard's packages/shared/src/recommended-extensions.ts manifest for the authoritative list.

Typical installs:

# Required bridge (this package)
pi install [email protected]:BlackBeltTechnology/pi-anthropic-messages.git

# Strongly suggested
pi install npm:@tintinweb/pi-subagents
pi install [email protected]:BlackBeltTechnology/pi-flows.git
pi install npm:pi-web-access

# Optional
pi install npm:pi-agent-browser

Relationship to other packages

  • @earendil-works/pi-ai — the Anthropic provider in pi-ai already canonicalizes tool names (toClaudeCodeName) when it detects an OAuth token. Our bridge is idempotent with that behavior: for OAuth sessions pi-ai rewrites read → Read before we see the payload, our exact-match check against CC_CANONICAL_NAMES finds Read, and we pass it through unchanged. For non-OAuth Claude anthropic-messages sessions (9Router sk-* key, pi-model-proxy, OAuth-subscription proxies, etc.), pi-ai does nothing and our bridge does the canonicalization. No double rename, no conflict.
  • @benvargas/pi-claude-code-use — superseded by this package for this repo. This package has a superset of behaviour (adds mcp__ prefixing for custom tools; gates on Claude-model detection; does not filter tools).
  • pi-flows — its subagent-side tool prefixing (extensions/flow-engine/tool-prefix.ts, mcp__flows__) handles in-process SDK sessions; this package handles the main session payload. Both complement each other.
  • pi-agent-dashboard — pulls this bridge in as a required recommended extension for any Claude-model anthropic-messages provider setup.

Releasing

Releases are cut via the release-cut skill and revoked via release-revoke; 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.

Exported API

For consumers that need the authoritative lists without duplication:

import {
  CC_CANONICAL_NAMES,
  PI_TO_CC_CANONICAL,
  NATIVE_ALIASES,
  FLAT_TO_MCP,
  DEFAULT_MCP_PREFIX,
  resolveOutboundName,
  transformPayload,
  buildReverseMap,
  lookupReverse,
  renameToolCallsInPlace,
  isClaudeAnthropicMessages,
} from "@blackbelt-technology/pi-anthropic-messages";

版本与发布记录

@blackbelt-technology/pi-anthropic-messages 共发布 4 个版本,首次发布于 2026/05/16;过去 12 个月发布了 4 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.3.5latest2026/10/2810 天前
0.3.42026/06/184 个月前
0.3.32026/06/184 个月前
0.3.22026/05/165 个月前

关于 @blackbelt-technology/pi-anthropic-messages 的常见问题

@blackbelt-technology/pi-anthropic-messages 是什么?

RETIRED — uninstall. Use pi-agent-dashboard's Claude subscription compat switch instead. Anthropic-messages protocol bridge for pi. Activates for any anthropic-messages API session (Anthropic OAuth/API-key + proxy providers): canonicalizes lowercase pi co

如何安装 @blackbelt-technology/pi-anthropic-messages?

在项目目录运行 npm install @blackbelt-technology/pi-anthropic-messages,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@earendil-works/pi-coding-agent(>=0.74.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@blackbelt-technology/pi-anthropic-messages 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@blackbelt-technology/pi-anthropic-messages 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@blackbelt-technology/pi-anthropic-messages 可以商用吗?

@blackbelt-technology/pi-anthropic-messages 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@blackbelt-technology/pi-anthropic-messages 的下载量有多少?

根据 npm 官方下载统计,@blackbelt-technology/pi-anthropic-messages 在 2026/10/09 – 2026/11/07 这 30 天内被下载 3,743 次,日均约 125 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@blackbelt-technology/pi-anthropic-messages 还在维护吗?

最新版本 0.3.5 发布于 2026/10/28(10 天前);至今共发布 4 个版本,其中过去 12 个月发布了 4 个;首次发布于 2026/05/16。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。