@dieulc/pi-office-bridge

Native Pi extension — WebSocket bridge + Office tool proxy so Pi can drive Excel, Word, and PowerPoint through the pi-for-office add-in.

Packages

Package details

extension

Install @dieulc/pi-office-bridge from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@dieulc/pi-office-bridge
Package
@dieulc/pi-office-bridge
Version
0.5.0
Published
Sep 19, 2026
Downloads
441/mo · 247/wk
Author
dieulc
License
MIT
Types
extension
Size
85.2 KB
Dependencies
3 dependencies · 2 peers
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

@dieulc/pi-office-bridge

Native Pi extension that lets a local Pi process drive Excel, Word, and PowerPoint through the pi-for-office task-pane add-in.

Pure extension — it only uses Pi's public extension API, so Pi core is never touched and Pi can be updated freely.

How it works

Excel / Word / PowerPoint  (pi-for-office task pane)
        │  WebSocket  ws://127.0.0.1:38617
        ▼
local Pi process  (this extension)
   • registers office_<host>_<op> tools
   • proxies Office.js calls back to the pane
   • injects pane prompts into the Pi session

Two flows:

  1. Tool proxy (Pi → pane): the Pi agent calls office_excel_read_range, office_word_insert_text, …; the extension forwards a tool_call to the attached pane; the pane runs the Office.js op and answers with a tool_result. The LLM then sees the document content and has Pi's full system tools (bash, git, files).

  2. Pane-driven chat (pane → Pi): the user types in the add-in sidebar; the pane forwards a user_message; the extension injects it into the Pi session and streams the assistant's final reply back to the pane.

Multiple open documents (protocol v2)

The bridge serves every open document of every attached host app, not just the most recently connected one. In hello each pane reports a privacy-preserving document identity (document.id = a local hash, document.name = display name, document.source), and the server routes each tool call to one specific document:

  • Every office_* tool takes an optional document argument: a document id (from office_list_documents), a unique document name, or "active".
  • An omitted / "active" target resolves to the focused document, then to the only attached document of that host. If several documents are open and none is focused, the call fails with the candidate list instead of editing a guess.
  • Panes report focus/visibility with pane_state frames; typing in a pane also counts as focus, and Excel additionally uses Workbook.onActivated.
  • office_list_documents lists the attached documents (id, name, host, focused, windows); the system prompt carries the same list before every turn.
  • Two windows of the same file share one document id: the call routes to the focused window, and errors if neither is focused.
  • In the Pi TUI, type # while composing a prompt to pick a document from a dropdown (focused first). Enter inserts #<name> — or #<document id> when two open documents share a name. The server strips the leading # when resolving the target, so the mention is directly usable.
  • Agent replies are matched to the pane prompt that caused them and are sent only to that pane (never broadcast), and the injected prompt carries an [office:<host> "<name>" id=…] tag so a shared conversation stays traceable.

Protocol v2 is breaking: update the add-in and the bridge together (protocol 0.4.0 + bridge 0.4.0). A v1 pane is rejected at the handshake with protocol_mismatch.

Install

pi install npm:@dieulc/pi-office-bridge

Update to the latest (the full 29-tool catalog incl. structure/format tools needs ≥ 0.3.0; multi-document targeting, office_list_documents and # mention autocomplete need ≥ 0.4.0 (protocol v2, which requires the matching add-in build); the /health probe and version display need ≥ 0.2.0):

pi install npm:@dieulc/pi-office-bridge@latest

Then restart Pi. Note that pi install pins the version it fetched into ~/.pi/agent/npm/package.json, so Pi will not auto-upgrade — re-run the command above to get the newest bridge.

Or from the monorepo (development) as the dev bridge on port 38618 (--no-extensions ignores settings, so the globally installed prod bridge never double-loads in the same process):

npm run bridge:dev   # repo root → pi --no-extensions -e ./packages/bridge-extension/src/index.ts --office-bridge-port 38618

Troubleshooting: the agent says it has no office_excel_modify_structure (or any other newer tool). A bridge < 0.3.0 ships a small hardcoded catalog (12 tools) and ignores the pane's hello.ops, so newer ops silently never register. Fix: pi install npm:@dieulc/pi-office-bridge@latest, restart Pi, then verify with /office-tools (29 tools) and /office (pane ops + catalog version).

Enable in the add-in

  1. Install/run the bridge so a Pi process with this extension is listening (see above). Keep that Pi process running in the background.
  2. Open pi-for-office in Excel / Word / PowerPoint.
  3. Go to Settings → Connections → Local Pi agent (advanced) and flip the Enable local Pi agent toggle on. The card shows the live connection state (Connecting… → Connected); no taskpane reload is needed.
  4. Verify with the card's Test connection button, or from a terminal:
curl http://127.0.0.1:38617/health
# → { "ok": true, "service": "pi-office-bridge", "serverVersion": "0.2.0",
#     "capabilities": ["http-health"], "panes": [ … ] }

/health lists the attached pane(s) and their host app (excel / word / powerpoint), so it doubles as a quick host-detection check. The add-in's probe classifies the response (current / older bridge / timeout / browser blocked) instead of reporting a bare failure — see docs/local-development.md.

Version & capabilities

Every welcome frame (and GET /health) advertises additive server metadata:

  • serverVersion — this package's version (e.g. "0.2.0").
  • capabilities["http-health"] means the HTTP /health surface is served.

Clients (the add-in card) treat an absent capabilities as “legacy bridge (< 0.2.0)” and tell the user to update instead of claiming the bridge is down. The connection state is surfaced live to the Pi TUI as soon as a pane attaches or detaches (onPanesChanged).

Commands

Command Description
/office Show bridge status: port + attached apps (Excel/Word/PowerPoint)
/office-tools List every office_* tool registered

Configuration

  • Port — flag --office-bridge-port <port> or env PI_OFFICE_BRIDGE_PORT (default 38617). The add-in connects to the same default; change both if you override it (the add-in's bridge card has a Bridge URL row that both the WebSocket client and the probe use). If the port is already taken by another Pi process, the extension reports EADDRINUSE with the override hint.
  • Allowed origins — env PI_OFFICE_BRIDGE_ALLOWED_ORIGINS (comma-separated) extends the browser origins allowed to read GET /health. Defaults cover the dev Vite server (https://localhost:3141) and the hosted GitHub Pages add-in (https://dieuluucanh.github.io). The pane's WebSocket connection is loopback-only and is not restricted by this list.

Office tools

The extension registers a office_<host>_<op> tool per op in the shared catalog. The catalog is the single source of truth: it lives in @dieulc/pi-office-protocol (office-catalog.ts) and BOTH the Pi extension and the add-in derive from it — the Pi side here, and the pane's bridge op registry (packages/add-in/src/bridge/) there. Op ids are namespaced by host:

Host Ops
Excel get_overview, read_range, write_cells, fill_formula, search_workbook, modify_structure, format_cells, conditional_format, charts, trace_dependencies, explain_formula, view_settings, comments, workbook_history
Word get_overview, read_document, insert_text, replace_text, format_range, insert_blocks, insert_table, insert_page_break, insert_image, insert_hyperlink
PowerPoint get_overview, read_slide, add_slide, add_text_box, format_slide

Plus the host-agnostic discovery tool office_list_documents (id, name, host, focused, windows), always active while the extension is loaded.

Active-tool reconciliation

The office tools are registered at session_start from the catalog, but only the ops the currently attached pane advertises are kept active in the Pi session (pi.setActiveTools()). Opening an app activates that host's tools; closing it deactivates them; everything else stays untouched. Panes that don't advertise an ops list (legacy 0.2.x clients) are given only the v1 op set. This keeps the agent's prompt small and focused on the app actually open.

Capability handshake

Panes send hello.ops + hello.catalogVersion with the op ids they can execute. The server validates them against its own catalog (entries that don't belong to the pane's host, or that the server doesn't know, are dropped and counted). callOfficeTool then rejects any op the pane did not advertise with an actionable message, so a mismatched add-in/bridge pair fails loudly instead of silently. That op list — together with opsIgnoredCount — is the real enforcement: catalogVersion is informational only (reported in /health and the /office summary), so bumping it never silently changes what a pane may execute.

  • /office shows each attached pane's host, op count, catalog version, and any ignored-op count.
  • /office-tools lists every registered tool + catalog version.
  • GET /health exposes catalogVersion and per-pane ops / catalogVersion.
  • before_agent_start appends a pane-context block (attached host, "the office_* tools edit the live document", "never emit HTML for Word") so the agent uses the tools directly. It deliberately does not claim formatting is universally supported: each tool's schema lists exactly the properties it accepts, and the block instructs the agent to say a property is not available rather than assert support — consulting office_word_get_capabilities when a request depends on a Word feature it is unsure about.

Tools never silently degrade: when a requested feature needs an Office.js requirement set the host lacks, the call returns a message naming the capability, the set + version, the platform, and the remedy. Two CI guards keep the surface honest — every advertised formatting property must have a real assignment in the implementation that applies it, and every declared capability must either be consumed by a caller or be explicitly probe-only. The panes and this extension both read the same catalog parameter objects (no per-side schema copies), so a new parameter cannot drift between the two agents.

The pane-side executors are thin delegates to the same local tool factories the browser-only path uses (see packages/add-in/src/bridge/), and a parity test (packages/add-in/tests/bridge-catalog-parity.test.ts) fails CI if the pane registry ever drifts from the shared catalog.

Development

npm run typecheck     # typecheck against @earendil-works/pi-coding-agent 0.85.x
npm run build         # emit dist/ (for the node smoke tests)
npm test              # smoke test + end-to-end interop test (real client ↔ real server)

tests/pane-interop.mjs wires the real add-in PaneBridgeClient to the real bridge server through the shared @dieulc/pi-office-protocol package — the strongest proof the two halves agree on the wire format.

Protocol

The wire protocol is shared in @dieulc/pi-office-protocol (packages/protocol). It is currently at v2 (document identity + targeting); bump BRIDGE_PROTOCOL_VERSION on breaking changes. See packages/protocol/README.md for the v2 changelog.

License

MIT