@dieulc/pi-office-protocol 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +18 -0
  2. package/package.json +19 -0
  3. package/src/protocol.ts +178 -0
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # @dieulc/pi-office-protocol
2
+
3
+ Shared WebSocket wire protocol between the pi-for-office task-pane add-in
4
+ (`packages/add-in/src/bridge/`) and the Pi bridge extension
5
+ (`packages/bridge-extension/`).
6
+
7
+ Holding the protocol in one package keeps the two halves of the bridge in
8
+ lockstep — a breaking change is one commit, and the end-to-end interop test
9
+ (`packages/bridge-extension/tests/pane-interop.mjs`) proves both sides speak the
10
+ same format at runtime.
11
+
12
+ ## Messages
13
+
14
+ - **Client → Server:** `hello`, `ping`, `tool_result`, `user_message`, `status`
15
+ - **Server → Client:** `welcome`, `pong`, `tool_call`, `agent_message` (delta/final), `tool_activity`, `error`
16
+
17
+ See `src/protocol.ts` for the typed shapes. Bump `BRIDGE_PROTOCOL_VERSION` on
18
+ breaking changes.
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@dieulc/pi-office-protocol",
3
+ "version": "0.1.0",
4
+ "description": "Shared bridge wire protocol between the pi-office-bridge Pi extension and the pi-for-office task-pane add-in.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "main": "./src/protocol.ts",
8
+ "types": "./src/protocol.ts",
9
+ "exports": {
10
+ ".": "./src/protocol.ts"
11
+ },
12
+ "files": ["src/protocol.ts", "README.md"],
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "scripts": {
17
+ "typecheck": "tsc --noEmit"
18
+ }
19
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Bridge protocol — shared wire format between the pi-for-office task pane
3
+ * (client) and this Pi extension (server).
4
+ *
5
+ * The server lives inside a local Pi process. The task pane connects to it
6
+ * over WebSocket at `ws://127.0.0.1:<port>`.
7
+ *
8
+ * Two flows are supported:
9
+ *
10
+ * 1. **Tool proxy (Pi → pane)** — the Pi agent calls an `office_*` tool; the
11
+ * extension forwards a `tool_call` to the attached pane for the matching
12
+ * host app; the pane executes the Office.js operation and answers with a
13
+ * `tool_result`.
14
+ *
15
+ * 2. **Pane-driven chat (pane → Pi)** — the user types in the add-in sidebar;
16
+ * the pane forwards a `user_message`; the extension injects it into the Pi
17
+ * session and streams the assistant's reply back as `agent_message`.
18
+ *
19
+ * Every connection starts with a `hello` (client) / `welcome` (server) pair so
20
+ * both sides know which Office host app is attached.
21
+ */
22
+
23
+ /** Default TCP port the bridge server listens on (loopback only). */
24
+ export const BRIDGE_DEFAULT_PORT = 38617;
25
+
26
+ /** Protocol version. Bump on breaking message-shape changes. */
27
+ export const BRIDGE_PROTOCOL_VERSION = 1;
28
+
29
+ /** Office host applications the add-in can attach. */
30
+ export type OfficeHostApp = "excel" | "word" | "powerpoint";
31
+
32
+ /** Every office tool name is namespaced by host: "excel.read_range" etc. */
33
+ export type OfficeToolId = string;
34
+
35
+ /* ── Client → Server ─────────────────────────────────────────────────── */
36
+
37
+ export interface HelloMessage {
38
+ type: "hello";
39
+ protocolVersion: number;
40
+ host: OfficeHostApp;
41
+ clientName: string;
42
+ paneId: string;
43
+ }
44
+
45
+ export interface PingMessage {
46
+ type: "ping";
47
+ ts: number;
48
+ }
49
+
50
+ /** Result of a proxied Office.js tool call executed by the pane. */
51
+ export interface ToolResultMessage {
52
+ type: "tool_result";
53
+ id: string;
54
+ ok: boolean;
55
+ /** Markdown/text shown to the LLM. */
56
+ text: string;
57
+ /** Structured details (may be large; used by the agent tool result). */
58
+ details?: unknown;
59
+ error?: string;
60
+ }
61
+
62
+ /** User typed a prompt in the add-in sidebar; ask Pi to answer it. */
63
+ export interface UserMessageMessage {
64
+ type: "user_message";
65
+ text: string;
66
+ }
67
+
68
+ /** Pane notifies the server it finished applying a message (optional). */
69
+ export interface ClientStatusMessage {
70
+ type: "status";
71
+ model?: string;
72
+ provider?: string;
73
+ }
74
+
75
+ export type ClientMessage =
76
+ | HelloMessage
77
+ | PingMessage
78
+ | ToolResultMessage
79
+ | UserMessageMessage
80
+ | ClientStatusMessage;
81
+
82
+ /* ── Server → Client ─────────────────────────────────────────────────── */
83
+
84
+ export interface WelcomeMessage {
85
+ type: "welcome";
86
+ protocolVersion: number;
87
+ piVersion: string | null;
88
+ serverName: string;
89
+ }
90
+
91
+ export interface PongMessage {
92
+ type: "pong";
93
+ ts: number;
94
+ }
95
+
96
+ /** Pi agent asks the pane to execute one Office.js operation. */
97
+ export interface ToolCallMessage {
98
+ type: "tool_call";
99
+ id: string;
100
+ tool: OfficeToolId;
101
+ args: Record<string, unknown>;
102
+ }
103
+
104
+ /** A chunk of the assistant's reply (streamed, display-only). */
105
+ export interface AgentDeltaMessage {
106
+ type: "agent_message";
107
+ kind: "delta";
108
+ text: string;
109
+ }
110
+
111
+ /** Final assistant reply for a pane-initiated prompt. */
112
+ export interface AgentFinalMessage {
113
+ type: "agent_message";
114
+ kind: "final";
115
+ text: string;
116
+ messageId?: string;
117
+ }
118
+
119
+ /** Non-office tool activity (bash, read, …) so the pane can show progress. */
120
+ export interface ToolActivityMessage {
121
+ type: "tool_activity";
122
+ tool: string;
123
+ status: "start" | "end" | "error";
124
+ summary?: string;
125
+ }
126
+
127
+ export interface ServerErrorMessage {
128
+ type: "error";
129
+ code: string;
130
+ message: string;
131
+ }
132
+
133
+ export type ServerMessage =
134
+ | WelcomeMessage
135
+ | PongMessage
136
+ | ToolCallMessage
137
+ | AgentDeltaMessage
138
+ | AgentFinalMessage
139
+ | ToolActivityMessage
140
+ | ServerErrorMessage;
141
+
142
+ /* ── Utilities ───────────────────────────────────────────────────────── */
143
+
144
+ let callCounter = 0;
145
+
146
+ /** Monotonic id generator for tool_call / tool_result correlation. */
147
+ export function nextCallId(prefix = "c"): string {
148
+ callCounter += 1;
149
+ return `${prefix}-${Date.now().toString(36)}-${callCounter.toString(36)}`;
150
+ }
151
+
152
+ /** Validate an incoming client frame. Returns the parsed message or throws. */
153
+ export function parseClientMessage(raw: string): ClientMessage {
154
+ const data: unknown = JSON.parse(raw);
155
+ if (typeof data !== "object" || data === null) {
156
+ throw new Error("bridge: expected a JSON object message");
157
+ }
158
+ const msg = data as Record<string, unknown>;
159
+ if (typeof msg.type !== "string") {
160
+ throw new Error("bridge: message missing 'type'");
161
+ }
162
+ switch (msg.type) {
163
+ case "hello": {
164
+ const m = data as HelloMessage;
165
+ if (!["excel", "word", "powerpoint"].includes(m.host)) {
166
+ throw new Error(`bridge: hello with unknown host '${String(m.host)}'`);
167
+ }
168
+ return m;
169
+ }
170
+ case "ping":
171
+ case "tool_result":
172
+ case "user_message":
173
+ case "status":
174
+ return data as ClientMessage;
175
+ default:
176
+ throw new Error(`bridge: unknown client message type '${String(msg.type)}'`);
177
+ }
178
+ }