@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.
Package details
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:
Tool proxy (Pi → pane): the Pi agent calls
office_excel_read_range,office_word_insert_text, …; the extension forwards atool_callto the attached pane; the pane runs the Office.js op and answers with atool_result. The LLM then sees the document content and has Pi's full system tools (bash, git, files).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 optionaldocumentargument: a document id (fromoffice_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_stateframes; typing in a pane also counts as focus, and Excel additionally usesWorkbook.onActivated. office_list_documentslists 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'shello.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
- Install/run the bridge so a Pi process with this extension is listening (see above). Keep that Pi process running in the background.
- Open pi-for-office in Excel / Word / PowerPoint.
- 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.
- 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/healthsurface 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 envPI_OFFICE_BRIDGE_PORT(default38617). 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 reportsEADDRINUSEwith the override hint. - Allowed origins — env
PI_OFFICE_BRIDGE_ALLOWED_ORIGINS(comma-separated) extends the browser origins allowed to readGET /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.
/officeshows each attached pane's host, op count, catalog version, and any ignored-op count./office-toolslists every registered tool + catalog version.GET /healthexposescatalogVersionand per-paneops/catalogVersion.before_agent_startappends 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 — consultingoffice_word_get_capabilitieswhen 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