lark-remote 0.1.6 → 0.1.7

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 (188) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/README.en.md +9 -1
  3. package/README.md +14 -2
  4. package/dist/bridge/approval-coordinator.d.ts +57 -34
  5. package/dist/bridge/approval-coordinator.js +191 -40
  6. package/dist/bridge/inbound-media.d.ts +62 -0
  7. package/dist/bridge/inbound-media.js +282 -0
  8. package/dist/bridge/index.d.ts +78 -7
  9. package/dist/bridge/index.js +202 -79
  10. package/dist/bridge/queue-manager.d.ts +2 -1
  11. package/dist/bridge/queue-manager.js +1 -0
  12. package/dist/card/approval-render.js +152 -36
  13. package/dist/card/bash-card-session.d.ts +2 -24
  14. package/dist/card/bash-card-session.js +6 -73
  15. package/dist/card/bash-renderer.js +2 -3
  16. package/dist/card/card-budget.js +29 -75
  17. package/dist/card/card-session.d.ts +48 -0
  18. package/dist/card/card-session.js +86 -0
  19. package/dist/card/card-shared.d.ts +1 -12
  20. package/dist/card/card-shared.js +1 -26
  21. package/dist/card/run-card-session.d.ts +2 -55
  22. package/dist/card/run-card-session.js +6 -104
  23. package/dist/card/run-renderer.js +99 -232
  24. package/dist/card/run-state.d.ts +13 -0
  25. package/dist/card/run-state.js +25 -16
  26. package/dist/card/text-truncate.d.ts +13 -12
  27. package/dist/card/text-truncate.js +13 -63
  28. package/dist/common/fs.d.ts +8 -0
  29. package/dist/common/fs.js +17 -0
  30. package/dist/common/guards.d.ts +0 -20
  31. package/dist/common/guards.js +0 -43
  32. package/dist/common/sleep.d.ts +2 -0
  33. package/dist/common/sleep.js +5 -0
  34. package/dist/common/truncate.d.ts +39 -0
  35. package/dist/common/truncate.js +92 -0
  36. package/dist/config/codex-config.d.ts +1 -1
  37. package/dist/config/codex-config.js +1 -1
  38. package/dist/config/index.d.ts +88 -23
  39. package/dist/config/index.js +105 -34
  40. package/dist/connector/index.d.ts +69 -2
  41. package/dist/connector/index.js +107 -1
  42. package/dist/index.js +123 -105
  43. package/dist/instance-lock.js +4 -12
  44. package/dist/order/alias-resolve.d.ts +13 -0
  45. package/dist/order/alias-resolve.js +22 -0
  46. package/dist/order/alias-store.d.ts +38 -0
  47. package/dist/order/alias-store.js +106 -0
  48. package/dist/persistence/atomic-write.d.ts +8 -2
  49. package/dist/persistence/atomic-write.js +23 -7
  50. package/dist/restart.js +2 -1
  51. package/dist/router/card-helpers.d.ts +63 -0
  52. package/dist/router/card-helpers.js +146 -0
  53. package/dist/router/config/claude.js +25 -1
  54. package/dist/router/config/codex.js +29 -50
  55. package/dist/router/config/common/model-patch.d.ts +22 -0
  56. package/dist/router/config/common/model-patch.js +26 -0
  57. package/dist/router/config/common/render.js +5 -4
  58. package/dist/router/config/index.d.ts +12 -0
  59. package/dist/router/config/index.js +25 -1
  60. package/dist/router/config/kimi.d.ts +4 -1
  61. package/dist/router/config/kimi.js +27 -0
  62. package/dist/router/config/opencode.js +18 -5
  63. package/dist/router/config/pi.js +4 -5
  64. package/dist/router/config/types.d.ts +11 -2
  65. package/dist/router/index.d.ts +56 -21
  66. package/dist/router/index.js +373 -548
  67. package/dist/router/utils.d.ts +2 -0
  68. package/dist/router/utils.js +4 -0
  69. package/dist/runner/agent-choices-common.d.ts +16 -0
  70. package/dist/runner/agent-choices-common.js +31 -0
  71. package/dist/runner/claude/runner.d.ts +47 -10
  72. package/dist/runner/claude/runner.js +117 -46
  73. package/dist/runner/claude/session.d.ts +166 -0
  74. package/dist/runner/claude/session.js +749 -0
  75. package/dist/runner/codex/app-server/protocol-types.d.ts +31 -39
  76. package/dist/runner/codex/app-server/protocol-types.js +1 -12
  77. package/dist/runner/codex/app-server/runner.d.ts +42 -59
  78. package/dist/runner/codex/app-server/runner.js +160 -337
  79. package/dist/runner/codex/app-server/translator.d.ts +1 -21
  80. package/dist/runner/codex/app-server/translator.js +0 -18
  81. package/dist/runner/codex/index.d.ts +0 -2
  82. package/dist/runner/codex/index.js +0 -2
  83. package/dist/runner/common/acp/protocol-helpers.d.ts +14 -0
  84. package/dist/runner/common/acp/protocol-helpers.js +21 -0
  85. package/dist/runner/common/acp/protocol-types.d.ts +238 -0
  86. package/dist/runner/common/acp/protocol-types.js +75 -0
  87. package/dist/runner/common/connection-based-runner.d.ts +111 -0
  88. package/dist/runner/common/connection-based-runner.js +261 -0
  89. package/dist/runner/{codex/app-server → common/jsonrpc}/client.d.ts +14 -7
  90. package/dist/runner/{codex/app-server → common/jsonrpc}/client.js +27 -18
  91. package/dist/runner/common/jsonrpc/connection-manager.d.ts +125 -0
  92. package/dist/runner/{codex/app-server → common/jsonrpc}/connection-manager.js +63 -28
  93. package/dist/runner/{codex/app-server → common/jsonrpc}/transport.d.ts +5 -4
  94. package/dist/runner/{codex/app-server → common/jsonrpc}/transport.js +18 -19
  95. package/dist/runner/common/jsonrpc/types.d.ts +32 -0
  96. package/dist/runner/common/jsonrpc/types.js +9 -0
  97. package/dist/runner/common/process-stopper.d.ts +2 -1
  98. package/dist/runner/common/process-stopper.js +1 -0
  99. package/dist/runner/common/runner-utils.d.ts +6 -13
  100. package/dist/runner/common/runner-utils.js +6 -47
  101. package/dist/runner/common/spawning-runner.d.ts +24 -70
  102. package/dist/runner/common/spawning-runner.js +98 -152
  103. package/dist/runner/index.d.ts +4 -4
  104. package/dist/runner/index.js +4 -4
  105. package/dist/runner/kimi/acp/runner.d.ts +106 -0
  106. package/dist/runner/kimi/acp/runner.js +448 -0
  107. package/dist/runner/kimi/acp/translator.d.ts +124 -0
  108. package/dist/runner/kimi/acp/translator.js +336 -0
  109. package/dist/runner/kimi/index.d.ts +1 -1
  110. package/dist/runner/kimi/index.js +1 -1
  111. package/dist/runner/opencode/acp/index.d.ts +1 -0
  112. package/dist/runner/opencode/acp/index.js +2 -0
  113. package/dist/runner/opencode/acp/runner.d.ts +121 -0
  114. package/dist/runner/opencode/acp/runner.js +429 -0
  115. package/dist/runner/opencode/acp/translator.d.ts +119 -0
  116. package/dist/runner/opencode/acp/translator.js +345 -0
  117. package/dist/runner/opencode/index.d.ts +1 -1
  118. package/dist/runner/opencode/index.js +1 -1
  119. package/dist/runner/pi/index.d.ts +1 -1
  120. package/dist/runner/pi/index.js +1 -1
  121. package/dist/runner/pi/rpc/client.d.ts +52 -0
  122. package/dist/runner/pi/rpc/client.js +114 -0
  123. package/dist/runner/pi/rpc/index.d.ts +4 -0
  124. package/dist/runner/pi/rpc/index.js +4 -0
  125. package/dist/runner/pi/rpc/protocol-types.d.ts +127 -0
  126. package/dist/runner/pi/rpc/protocol-types.js +21 -0
  127. package/dist/runner/pi/rpc/runner.d.ts +80 -0
  128. package/dist/runner/pi/rpc/runner.js +199 -0
  129. package/dist/runner/pi/rpc/translator.d.ts +55 -0
  130. package/dist/runner/pi/rpc/translator.js +258 -0
  131. package/dist/runner/registry.d.ts +7 -8
  132. package/dist/runner/registry.js +3 -4
  133. package/dist/runner/resolve-agent-choices.js +18 -59
  134. package/dist/runner/sync-agent-choices.js +16 -67
  135. package/dist/runner/types.d.ts +43 -13
  136. package/dist/session/claude/session-index.js +2 -3
  137. package/dist/session/claude/session-reader.js +3 -6
  138. package/dist/session/claude/sessions.js +6 -13
  139. package/dist/session/codex/rollout-reader.d.ts +2 -2
  140. package/dist/session/codex/rollout-reader.js +14 -17
  141. package/dist/session/codex/sessions.d.ts +1 -1
  142. package/dist/session/codex/sessions.js +2 -2
  143. package/dist/session/common/content-blocks.d.ts +7 -0
  144. package/dist/session/common/content-blocks.js +13 -9
  145. package/dist/session/common/jsonl.d.ts +8 -25
  146. package/dist/session/common/jsonl.js +126 -261
  147. package/dist/session/common/pagination.d.ts +28 -0
  148. package/dist/session/common/pagination.js +33 -0
  149. package/dist/session/kimi/sessions.d.ts +34 -1
  150. package/dist/session/kimi/sessions.js +208 -98
  151. package/dist/session/opencode/sessions.d.ts +17 -3
  152. package/dist/session/opencode/sessions.js +29 -29
  153. package/dist/session/pi/sessions.d.ts +5 -1
  154. package/dist/session/pi/sessions.js +12 -16
  155. package/dist/session/session-store.d.ts +11 -1
  156. package/dist/session/session-store.js +75 -178
  157. package/dist/update/index.d.ts +2 -2
  158. package/dist/update/index.js +2 -2
  159. package/dist/update/install.d.ts +20 -2
  160. package/dist/update/install.js +51 -7
  161. package/package.json +1 -1
  162. package/dist/runner/codex/app-server/connection-manager.d.ts +0 -56
  163. package/dist/runner/codex/argv.d.ts +0 -33
  164. package/dist/runner/codex/argv.js +0 -40
  165. package/dist/runner/codex/factory.d.ts +0 -29
  166. package/dist/runner/codex/factory.js +0 -39
  167. package/dist/runner/codex/jsonl.d.ts +0 -37
  168. package/dist/runner/codex/jsonl.js +0 -221
  169. package/dist/runner/codex/runner.d.ts +0 -66
  170. package/dist/runner/codex/runner.js +0 -129
  171. package/dist/runner/common/exec-translator.d.ts +0 -73
  172. package/dist/runner/common/exec-translator.js +0 -105
  173. package/dist/runner/kimi/jsonl.d.ts +0 -28
  174. package/dist/runner/kimi/jsonl.js +0 -143
  175. package/dist/runner/kimi/runner.d.ts +0 -49
  176. package/dist/runner/kimi/runner.js +0 -105
  177. package/dist/runner/opencode/argv.d.ts +0 -32
  178. package/dist/runner/opencode/argv.js +0 -35
  179. package/dist/runner/opencode/jsonl.d.ts +0 -68
  180. package/dist/runner/opencode/jsonl.js +0 -295
  181. package/dist/runner/opencode/runner.d.ts +0 -61
  182. package/dist/runner/opencode/runner.js +0 -93
  183. package/dist/runner/pi/jsonl.d.ts +0 -128
  184. package/dist/runner/pi/jsonl.js +0 -249
  185. package/dist/runner/pi/runner.d.ts +0 -36
  186. package/dist/runner/pi/runner.js +0 -76
  187. package/dist/session/common/catch-up.d.ts +0 -12
  188. package/dist/session/common/catch-up.js +0 -31
@@ -0,0 +1,345 @@
1
+ /**
2
+ * OpencodeAcpTranslator: translates opencode ACP session/update notifications
3
+ * and session/request_permission server requests into AgentEvents.
4
+ *
5
+ * Channel choice (event contract in common/acp/protocol-types.ts header):
6
+ * opencode's wire `content.text` IS an incremental delta
7
+ * (opencode acp/event.ts:231-258 handlePartDelta sends `props.delta`
8
+ * verbatim), so text/thinking chunks go straight onto the `assistant`
9
+ * incremental channel — NO accumulation in the translator, NO turn_diff
10
+ * snapshot. (Contrast kimi: same field is also a delta but kimi's contract
11
+ * accumulates into turn_diff snapshots; both end with exactly one
12
+ * concatenation in run-state, enforced by each side's seam test.)
13
+ *
14
+ * Event mapping (source: opencode acp/service.ts + event.ts + tool.ts,
15
+ * dev@1c965451b5):
16
+ * agent_message_chunk → assistant/text delta (content.text passthrough)
17
+ * agent_thought_chunk → assistant/thinking delta (content.text passthrough)
18
+ * tool_call → assistant/tool_use (rawInput object passthrough;
19
+ * missing → {} per contract, string → JSON.parse)
20
+ * tool_call_update → user/tool_result (status:'failed' → is_error:true;
21
+ * rawOutput is an OBJECT: {output} / {error})
22
+ * usage_update → live context occupancy {used, size} + cost →
23
+ * {total_tokens, context_limit, total_cost_usd}
24
+ * available_commands_update / plan / session_info_update /
25
+ * current_mode_update / config_option_update / user_message_chunk → discard
26
+ * prompt stopReason:'end_turn' → result success
27
+ * prompt stopReason:'cancelled' → result interrupted (独立终态)
28
+ * prompt other stopReason/error → result error
29
+ * session/request_permission → approval_requested (optionId echo)
30
+ */
31
+ import { NotificationMethod, ServerRequestMethod, SessionEventType, } from '../../common/acp/protocol-types.js';
32
+ import { getLogger } from '../../../logger/index.js';
33
+ // =============================================================================
34
+ // Translator
35
+ // =============================================================================
36
+ export class OpencodeAcpTranslator {
37
+ /** Pending tool calls keyed by toolCallId (for matching tool_call_update). */
38
+ pendingToolCalls = new Map();
39
+ /** Current operation kind (turn vs compact). */
40
+ operationKind = 'turn';
41
+ /** Live usage snapshot from usage_update events (context occupancy only). */
42
+ liveUsage = {};
43
+ /** Session cost from usage_update cost.amount (USD). */
44
+ liveCostUsd;
45
+ /** Whether any usage_update has been seen this turn (no update → no usage). */
46
+ hasLiveUsage = false;
47
+ /** Current turn id, set by produceTurnStarted. */
48
+ currentTurnId = '';
49
+ /**
50
+ * Handle a notification from the ACP server and return translated events.
51
+ */
52
+ handleNotification(method, params) {
53
+ if (method !== NotificationMethod.SESSION_UPDATE) {
54
+ return [];
55
+ }
56
+ // Envelope: {sessionId, update: {sessionUpdate: ...}} (same as kimi).
57
+ const notif = params;
58
+ const update = notif.update;
59
+ switch (update.sessionUpdate) {
60
+ case SessionEventType.AGENT_MESSAGE_CHUNK:
61
+ return this.handleAgentMessageChunk(update);
62
+ case SessionEventType.AGENT_THOUGHT_CHUNK:
63
+ return this.handleAgentThoughtChunk(update);
64
+ case SessionEventType.TOOL_CALL:
65
+ return this.handleToolCall(update);
66
+ case SessionEventType.TOOL_CALL_UPDATE:
67
+ return this.handleToolCallUpdate(update);
68
+ case SessionEventType.USAGE_UPDATE:
69
+ return this.handleUsageUpdate(update);
70
+ case SessionEventType.PLAN:
71
+ getLogger().debug(`[opencode-acp-translator] discarding plan event`);
72
+ return [];
73
+ default:
74
+ // Control-plane noise (command list, mode/config echoes, user replay)
75
+ // — not content.
76
+ return [];
77
+ }
78
+ }
79
+ /**
80
+ * Handle a server request (reverse RPC from the ACP server).
81
+ */
82
+ handleServerRequest(id, method, params) {
83
+ if (method === ServerRequestMethod.REQUEST_PERMISSION) {
84
+ return this.handleRequestPermission(id, params);
85
+ }
86
+ return [];
87
+ }
88
+ /**
89
+ * Produce a turn_started event. Called by the runner when session/prompt
90
+ * is sent (no wire notification for this — self-produced).
91
+ */
92
+ produceTurnStarted(sessionId, turnId) {
93
+ this.currentTurnId = turnId;
94
+ return {
95
+ type: 'turn_started',
96
+ threadId: sessionId,
97
+ turnId,
98
+ operationKind: this.operationKind === 'compact' ? 'compaction' : 'turn',
99
+ timestamp: new Date().toISOString(),
100
+ };
101
+ }
102
+ /**
103
+ * Translate a prompt response into a result event.
104
+ * stopReason: end_turn → success; cancelled → interrupted; anything else
105
+ * (max_tokens / refusal / …) → error.
106
+ */
107
+ handlePromptResponse(sessionId, result) {
108
+ const subtype = result.stopReason === 'end_turn'
109
+ ? 'success'
110
+ : result.stopReason === 'cancelled'
111
+ ? 'interrupted'
112
+ : 'error';
113
+ return {
114
+ type: 'result',
115
+ subtype,
116
+ session_id: sessionId,
117
+ ...(this.hasLiveUsage ? { usage: this.liveUsageSnapshot() } : {}),
118
+ ...(this.liveCostUsd !== undefined ? { total_cost_usd: this.liveCostUsd } : {}),
119
+ ...(subtype === 'error'
120
+ ? { errorMessage: `Prompt ended with stopReason: ${result.stopReason}` }
121
+ : {}),
122
+ };
123
+ }
124
+ /**
125
+ * Produce an error result event (for JSON-RPC errors during prompt).
126
+ */
127
+ produceErrorResult(sessionId, errorMessage) {
128
+ return {
129
+ type: 'result',
130
+ subtype: 'error',
131
+ session_id: sessionId,
132
+ errorMessage,
133
+ ...(this.hasLiveUsage ? { usage: this.liveUsageSnapshot() } : {}),
134
+ ...(this.liveCostUsd !== undefined ? { total_cost_usd: this.liveCostUsd } : {}),
135
+ };
136
+ }
137
+ /**
138
+ * Set the operation kind (turn vs compact).
139
+ */
140
+ setOperationKind(kind) {
141
+ this.operationKind = kind;
142
+ }
143
+ // =========================================================================
144
+ // Private notification handlers
145
+ // =========================================================================
146
+ handleAgentMessageChunk(event) {
147
+ // opencode wire delta → assistant incremental channel directly
148
+ // (event.ts handlePartDelta: content.text = props.delta). The reducer
149
+ // appends assistant text — no accumulation here, no turn_diff.
150
+ return [
151
+ {
152
+ type: 'assistant',
153
+ message: {
154
+ content: [{ type: 'text', text: event.content.text }],
155
+ },
156
+ timestamp: new Date().toISOString(),
157
+ },
158
+ ];
159
+ }
160
+ handleAgentThoughtChunk(event) {
161
+ return [
162
+ {
163
+ type: 'assistant',
164
+ message: {
165
+ content: [{ type: 'thinking', thinking: event.content.text }],
166
+ },
167
+ timestamp: new Date().toISOString(),
168
+ },
169
+ ];
170
+ }
171
+ handleToolCall(event) {
172
+ // opencode pendingToolCall always carries rawInput as an object
173
+ // (tool.ts:124-138), but the event contract requires defensive
174
+ // normalization: missing → {} (lazy-create), string → JSON.parse.
175
+ let input = event.rawInput ?? {};
176
+ if (typeof event.rawInput === 'string') {
177
+ try {
178
+ input = JSON.parse(event.rawInput);
179
+ }
180
+ catch {
181
+ // Keep raw string as-is
182
+ }
183
+ }
184
+ this.pendingToolCalls.set(event.toolCallId, {
185
+ id: event.toolCallId,
186
+ name: event.title,
187
+ input,
188
+ });
189
+ return [
190
+ {
191
+ type: 'assistant',
192
+ message: {
193
+ content: [
194
+ {
195
+ type: 'tool_use',
196
+ id: event.toolCallId,
197
+ name: event.title,
198
+ input,
199
+ },
200
+ ],
201
+ },
202
+ timestamp: new Date().toISOString(),
203
+ },
204
+ ];
205
+ }
206
+ handleToolCallUpdate(event) {
207
+ const isError = event.status === 'failed';
208
+ const content = extractToolResultContent(event);
209
+ return [
210
+ {
211
+ type: 'user',
212
+ message: {
213
+ content: [
214
+ {
215
+ type: 'tool_result',
216
+ tool_use_id: event.toolCallId,
217
+ content,
218
+ is_error: isError,
219
+ },
220
+ ],
221
+ },
222
+ timestamp: new Date().toISOString(),
223
+ },
224
+ ];
225
+ }
226
+ handleUsageUpdate(event) {
227
+ // opencode usage_update: {used, size, cost:{amount, currency:'USD'}}
228
+ // (service.ts:653-663) — context occupancy + session cost, no
229
+ // input/output split.
230
+ this.liveUsage = {
231
+ total_tokens: event.used,
232
+ context_limit: event.size,
233
+ };
234
+ const cost = event.cost;
235
+ if (typeof cost?.amount === 'number') {
236
+ this.liveCostUsd = cost.amount;
237
+ }
238
+ this.hasLiveUsage = true;
239
+ return [];
240
+ }
241
+ // =========================================================================
242
+ // Private approval handlers
243
+ // =========================================================================
244
+ handleRequestPermission(requestId, params) {
245
+ // opencode always sends a toolCall (permission.ts:62-70 — built via
246
+ // permissionToolCall) and has no question elicitation; absent toolCall
247
+ // is treated as unsupported and left to the runner's reject fallback.
248
+ if (!params.toolCall) {
249
+ getLogger().info(`[opencode-acp-translator] permission request without toolCall, leaving to runner fallback (requestId=${requestId})`);
250
+ return [];
251
+ }
252
+ const rawInput = params.toolCall.rawInput;
253
+ const view = {
254
+ requestId,
255
+ kind: 'command',
256
+ command: params.toolCall.title ?? undefined,
257
+ reason: rawInput ? truncate(stringify(rawInput), 200) : undefined,
258
+ // §P4: 从服务端 options kind 派生——带 allow_always(或 approve_always)
259
+ // 才提供「本会话总是允许」(acceptForSession)。
260
+ availableDecisions: this.deriveAvailableDecisions(params),
261
+ };
262
+ return [
263
+ {
264
+ type: 'approval_requested',
265
+ requestId,
266
+ kind: 'command',
267
+ threadId: params.sessionId,
268
+ turnId: '',
269
+ itemId: '',
270
+ view,
271
+ timestamp: new Date().toISOString(),
272
+ },
273
+ ];
274
+ }
275
+ /**
276
+ * Derive the approval decision list from the server's offered options.
277
+ * accept/decline/cancel are universal; acceptForSession requires an
278
+ * always-class option (opencode `allow_always`, kimi `approve_always`).
279
+ */
280
+ deriveAvailableDecisions(params) {
281
+ const decisions = ['accept', 'decline', 'cancel'];
282
+ const hasAlwaysOption = (params.options ?? []).some((opt) => opt.kind === 'allow_always' || opt.kind === 'approve_always');
283
+ if (hasAlwaysOption) {
284
+ decisions.push('acceptForSession');
285
+ }
286
+ return decisions;
287
+ }
288
+ // =========================================================================
289
+ // Private helpers
290
+ // =========================================================================
291
+ /**
292
+ * ResultEvent.usage nominally requires input_tokens/output_tokens, but the
293
+ * ACP wire has no such split — the live snapshot is context occupancy only.
294
+ * Cast is deliberate: consumers must tolerate undefined input/output.
295
+ */
296
+ liveUsageSnapshot() {
297
+ return { ...this.liveUsage };
298
+ }
299
+ }
300
+ // =============================================================================
301
+ // Helpers
302
+ // =============================================================================
303
+ /**
304
+ * Extract a display string from a tool_call_update. opencode shapes
305
+ * (tool.ts): completed → rawOutput {output, metadata?}; failed → rawOutput
306
+ * {error, metadata}; in_progress → content[] with shell output snapshot.
307
+ */
308
+ function extractToolResultContent(event) {
309
+ const rawOutput = event.rawOutput;
310
+ if (rawOutput && typeof rawOutput === 'object') {
311
+ const rec = rawOutput;
312
+ if (typeof rec.output === 'string')
313
+ return rec.output;
314
+ if (typeof rec.error === 'string')
315
+ return rec.error;
316
+ }
317
+ if (typeof rawOutput === 'string')
318
+ return rawOutput;
319
+ const content = event.content;
320
+ if (Array.isArray(content)) {
321
+ const texts = content
322
+ .map((item) => {
323
+ const c = item?.content;
324
+ return c?.type === 'text' && typeof c.text === 'string' ? c.text : '';
325
+ })
326
+ .filter((t) => t.length > 0);
327
+ if (texts.length > 0)
328
+ return texts.join('\n');
329
+ }
330
+ return '';
331
+ }
332
+ function truncate(text, maxLen) {
333
+ return text.length <= maxLen ? text : text.slice(0, maxLen) + '…';
334
+ }
335
+ function stringify(value) {
336
+ if (typeof value === 'string')
337
+ return value;
338
+ try {
339
+ return JSON.stringify(value);
340
+ }
341
+ catch {
342
+ return String(value);
343
+ }
344
+ }
345
+ //# sourceMappingURL=translator.js.map
@@ -1 +1 @@
1
- export { OpencodeExecRunner } from './runner.js';
1
+ export { OpencodeAcpRunner } from './acp/index.js';
@@ -1,2 +1,2 @@
1
- export { OpencodeExecRunner } from './runner.js';
1
+ export { OpencodeAcpRunner } from './acp/index.js';
2
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- export { PiRunner } from './runner.js';
1
+ export { PiRpcRunner } from './rpc/index.js';
@@ -1,2 +1,2 @@
1
- export { PiRunner } from './runner.js';
1
+ export { PiRpcRunner } from './rpc/index.js';
2
2
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,52 @@
1
+ /**
2
+ * PiRpcClient: JSON-lines client for pi's `--mode rpc` protocol.
3
+ *
4
+ * Reuses the shared JsonlRpcTransport (spawn + line read/write + graceful
5
+ * close) but speaks pi's own framing — commands `{type,id,...}` on stdin,
6
+ * responses `{id,type:"response",command,success,...}` and bare events on
7
+ * stdout — rather than JSON-RPC. There is no initialize handshake: the server
8
+ * starts streaming events immediately after spawn.
9
+ *
10
+ * Response correlation is by command id; every non-response line is forwarded
11
+ * as an event to the active hooks (the runner's translator).
12
+ */
13
+ import type { JsonlRpcTransport } from '../../common/jsonrpc/transport.js';
14
+ import type { PiRpcCommand, PiRpcEvent, PiRpcResponse } from './protocol-types.js';
15
+ /** Per-run hooks set by the runner. Accepts a superset so the base class's
16
+ * respawnAfterConnectionLost({onNotification,onServerRequest,onClose})
17
+ * detach call stays type-compatible. */
18
+ export interface PiRpcClientHooks {
19
+ onEvent?(evt: PiRpcEvent): void;
20
+ onClose?(): void;
21
+ onNotification?(): void;
22
+ onServerRequest?(): void;
23
+ }
24
+ export declare class PiRpcClient {
25
+ private transport;
26
+ private readonly requestTimeoutMs;
27
+ /** Connection-layer close hook (slot cleanup) — survives setHooks(). */
28
+ private readonly baseOnClose;
29
+ /** Per-run hooks from the runner. */
30
+ private runHooks;
31
+ private pending;
32
+ private nextId;
33
+ private _disposed;
34
+ private _ready;
35
+ constructor(transport: JsonlRpcTransport, baseOnClose: () => void, requestTimeoutMs?: number);
36
+ /** Replace the per-run hooks (runner wires its translator + failTurn here). */
37
+ setHooks(hooks: PiRpcClientHooks): void;
38
+ get ready(): boolean;
39
+ get healthy(): boolean;
40
+ /** Start the transport. Throws ConnectionLostError if spawn fails (ENOENT). */
41
+ connect(): Promise<void>;
42
+ /**
43
+ * Send a command and await its response (correlated by id).
44
+ * `timeoutMs` overrides the default (defaults to requestTimeoutMs).
45
+ */
46
+ request(command: PiRpcCommand, timeoutMs?: number): Promise<PiRpcResponse>;
47
+ /** Fire-and-forget command (no response awaited) — e.g. `abort`. */
48
+ notify(command: PiRpcCommand): void;
49
+ dispose(): Promise<void>;
50
+ private handleMessage;
51
+ private failPending;
52
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * PiRpcClient: JSON-lines client for pi's `--mode rpc` protocol.
3
+ *
4
+ * Reuses the shared JsonlRpcTransport (spawn + line read/write + graceful
5
+ * close) but speaks pi's own framing — commands `{type,id,...}` on stdin,
6
+ * responses `{id,type:"response",command,success,...}` and bare events on
7
+ * stdout — rather than JSON-RPC. There is no initialize handshake: the server
8
+ * starts streaming events immediately after spawn.
9
+ *
10
+ * Response correlation is by command id; every non-response line is forwarded
11
+ * as an event to the active hooks (the runner's translator).
12
+ */
13
+ import { ConnectionLostError } from '../../common/jsonrpc/client.js';
14
+ export class PiRpcClient {
15
+ transport;
16
+ requestTimeoutMs;
17
+ /** Connection-layer close hook (slot cleanup) — survives setHooks(). */
18
+ baseOnClose;
19
+ /** Per-run hooks from the runner. */
20
+ runHooks = null;
21
+ pending = new Map();
22
+ nextId = 1;
23
+ _disposed = false;
24
+ _ready = false;
25
+ constructor(transport, baseOnClose, requestTimeoutMs = 60_000) {
26
+ this.transport = transport;
27
+ this.baseOnClose = baseOnClose;
28
+ this.requestTimeoutMs = requestTimeoutMs;
29
+ }
30
+ /** Replace the per-run hooks (runner wires its translator + failTurn here). */
31
+ setHooks(hooks) {
32
+ this.runHooks = hooks;
33
+ }
34
+ get ready() {
35
+ return this._ready;
36
+ }
37
+ get healthy() {
38
+ return !this._disposed && !this.transport.closed;
39
+ }
40
+ /** Start the transport. Throws ConnectionLostError if spawn fails (ENOENT). */
41
+ async connect() {
42
+ const events = {
43
+ onMessage: (msg) => this.handleMessage(msg),
44
+ onClose: () => {
45
+ this.failPending(new ConnectionLostError('pi rpc transport closed'));
46
+ this.baseOnClose();
47
+ this.runHooks?.onClose?.();
48
+ },
49
+ };
50
+ await this.transport.start(events);
51
+ if (this.transport.closed) {
52
+ throw new ConnectionLostError('pi RPC server failed to start (binary missing or crashed)');
53
+ }
54
+ this._ready = true;
55
+ }
56
+ /**
57
+ * Send a command and await its response (correlated by id).
58
+ * `timeoutMs` overrides the default (defaults to requestTimeoutMs).
59
+ */
60
+ async request(command, timeoutMs) {
61
+ if (this._disposed || this.transport.closed) {
62
+ throw new ConnectionLostError('pi rpc client is disposed/closed');
63
+ }
64
+ const id = `req_${this.nextId++}`;
65
+ return new Promise((resolve, reject) => {
66
+ const effectiveTimeout = Math.min(timeoutMs ?? this.requestTimeoutMs, 2_147_483_647);
67
+ const timer = setTimeout(() => {
68
+ this.pending.delete(id);
69
+ reject(new Error(`timeout waiting for pi rpc response: ${command.type}`));
70
+ }, effectiveTimeout);
71
+ this.pending.set(id, { resolve, reject, timer });
72
+ this.transport.write({ ...command, id });
73
+ });
74
+ }
75
+ /** Fire-and-forget command (no response awaited) — e.g. `abort`. */
76
+ notify(command) {
77
+ if (this._disposed || this.transport.closed)
78
+ return;
79
+ this.transport.write({ ...command, id: `req_${this.nextId++}` });
80
+ }
81
+ async dispose() {
82
+ if (this._disposed)
83
+ return;
84
+ this._disposed = true;
85
+ this._ready = false;
86
+ this.failPending(new ConnectionLostError('pi rpc client disposed'));
87
+ await this.transport.close();
88
+ }
89
+ // =========================================================================
90
+ // Internal
91
+ // =========================================================================
92
+ handleMessage(msg) {
93
+ const message = msg;
94
+ if (message.type === 'response' && message.id != null) {
95
+ const id = String(message.id);
96
+ const pending = this.pending.get(id);
97
+ if (pending) {
98
+ clearTimeout(pending.timer);
99
+ this.pending.delete(id);
100
+ pending.resolve(message);
101
+ }
102
+ return;
103
+ }
104
+ this.runHooks?.onEvent?.(message);
105
+ }
106
+ failPending(err) {
107
+ for (const [_id, pending] of this.pending) {
108
+ clearTimeout(pending.timer);
109
+ pending.reject(err);
110
+ }
111
+ this.pending.clear();
112
+ }
113
+ }
114
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,4 @@
1
+ export { PiRpcRunner } from './runner.js';
2
+ export type { PiRpcRunnerOptions } from './runner.js';
3
+ export { PiRpcClient } from './client.js';
4
+ export { PiRpcTranslator } from './translator.js';
@@ -0,0 +1,4 @@
1
+ export { PiRpcRunner } from './runner.js';
2
+ export { PiRpcClient } from './client.js';
3
+ export { PiRpcTranslator } from './translator.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Pi RPC protocol types.
3
+ *
4
+ * pi's `--mode rpc` is a headless JSON-lines protocol over stdio (NOT JSON-RPC):
5
+ * - client → stdin: commands `{ type, id, ... }`
6
+ * - server → stdout: responses `{ id, type:"response", command, success, data|error }`
7
+ * and events (session lifecycle / message / compaction) as bare
8
+ * JSON objects (no envelope).
9
+ *
10
+ * Turn semantics (verified live against pi 0.84.1, 2026-08-18):
11
+ * - `prompt` returns an ack (`success:true`) immediately; the turn then streams
12
+ * events and completes with an `agent_settled` event.
13
+ * - `compact` runs synchronously and returns its response at completion; it also
14
+ * streams `compaction_start` / `compaction_end` events.
15
+ * - `abort` cancels the current operation.
16
+ * - `get_state` returns the bound session id (`data.sessionId`) — pi binds the
17
+ * session at spawn via `--session-id`, so this is how a new session's id is
18
+ * discovered.
19
+ */
20
+ export type PiRpcCommand = {
21
+ type: 'prompt';
22
+ message: string;
23
+ images?: unknown[];
24
+ } | {
25
+ type: 'compact';
26
+ customInstructions?: string;
27
+ } | {
28
+ type: 'abort';
29
+ } | {
30
+ type: 'new_session';
31
+ parentSession?: string;
32
+ } | {
33
+ type: 'get_state';
34
+ };
35
+ export interface PiRpcGetStateData {
36
+ model?: unknown;
37
+ sessionId: string;
38
+ sessionFile?: string;
39
+ isStreaming?: boolean;
40
+ isCompacting?: boolean;
41
+ [key: string]: unknown;
42
+ }
43
+ export interface PiRpcSuccessResponse {
44
+ id?: string | number;
45
+ type: 'response';
46
+ command: string;
47
+ success: true;
48
+ data?: unknown;
49
+ }
50
+ export interface PiRpcErrorResponse {
51
+ id?: string | number;
52
+ type: 'response';
53
+ command: string;
54
+ success: false;
55
+ error: string;
56
+ }
57
+ export type PiRpcResponse = PiRpcSuccessResponse | PiRpcErrorResponse;
58
+ export interface PiRpcSessionEvent {
59
+ type: 'session';
60
+ id: string;
61
+ cwd: string;
62
+ model?: string;
63
+ }
64
+ export interface PiRpcAgentEvent {
65
+ type: 'agent_start' | 'agent_end' | 'agent_settled';
66
+ }
67
+ export interface PiRpcTurnEvent {
68
+ type: 'turn_start' | 'turn_end';
69
+ message?: {
70
+ usage?: PiRpcUsage;
71
+ };
72
+ }
73
+ export interface PiRpcMessageStartEvent {
74
+ type: 'message_start';
75
+ message: {
76
+ role: 'user' | 'assistant' | 'toolResult';
77
+ content?: unknown[];
78
+ usage?: PiRpcUsage;
79
+ };
80
+ }
81
+ export interface PiRpcMessageUpdateEvent {
82
+ type: 'message_update';
83
+ usage?: PiRpcUsage;
84
+ assistantMessageEvent: {
85
+ type: 'text_start' | 'text_delta' | 'text_end' | 'thinking_start' | 'thinking_delta' | 'thinking_end' | 'toolcall_start' | 'toolcall_delta' | 'toolcall_end';
86
+ contentIndex?: number;
87
+ delta?: string;
88
+ content?: string;
89
+ toolCall?: {
90
+ id: string;
91
+ name: string;
92
+ arguments: Record<string, unknown>;
93
+ };
94
+ };
95
+ }
96
+ export interface PiRpcMessageEndEvent {
97
+ type: 'message_end';
98
+ message: {
99
+ role: 'user' | 'assistant' | 'toolResult';
100
+ content?: unknown[];
101
+ usage?: PiRpcUsage;
102
+ toolCallId?: string;
103
+ toolName?: string;
104
+ isError?: boolean;
105
+ stopReason?: string;
106
+ errorMessage?: string;
107
+ };
108
+ }
109
+ export interface PiRpcCompactionEvent {
110
+ type: 'compaction_start' | 'compaction_end';
111
+ reason?: string;
112
+ aborted?: boolean;
113
+ willRetry?: boolean;
114
+ errorMessage?: string;
115
+ }
116
+ export interface PiRpcUsage {
117
+ input?: number;
118
+ output?: number;
119
+ cacheRead?: number;
120
+ cacheWrite?: number;
121
+ totalTokens?: number;
122
+ reasoning?: number;
123
+ }
124
+ /** Any event line emitted by the pi RPC server (excluding responses). */
125
+ export type PiRpcEvent = PiRpcSessionEvent | PiRpcAgentEvent | PiRpcTurnEvent | PiRpcMessageStartEvent | PiRpcMessageUpdateEvent | PiRpcMessageEndEvent | PiRpcCompactionEvent | {
126
+ type: string;
127
+ };