@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.
- package/README.md +18 -0
- package/package.json +19 -0
- 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
|
+
}
|
package/src/protocol.ts
ADDED
|
@@ -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
|
+
}
|