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.
- package/CHANGELOG.md +23 -1
- package/README.en.md +9 -1
- package/README.md +14 -2
- package/dist/bridge/approval-coordinator.d.ts +57 -34
- package/dist/bridge/approval-coordinator.js +191 -40
- package/dist/bridge/inbound-media.d.ts +62 -0
- package/dist/bridge/inbound-media.js +282 -0
- package/dist/bridge/index.d.ts +78 -7
- package/dist/bridge/index.js +202 -79
- package/dist/bridge/queue-manager.d.ts +2 -1
- package/dist/bridge/queue-manager.js +1 -0
- package/dist/card/approval-render.js +152 -36
- package/dist/card/bash-card-session.d.ts +2 -24
- package/dist/card/bash-card-session.js +6 -73
- package/dist/card/bash-renderer.js +2 -3
- package/dist/card/card-budget.js +29 -75
- package/dist/card/card-session.d.ts +48 -0
- package/dist/card/card-session.js +86 -0
- package/dist/card/card-shared.d.ts +1 -12
- package/dist/card/card-shared.js +1 -26
- package/dist/card/run-card-session.d.ts +2 -55
- package/dist/card/run-card-session.js +6 -104
- package/dist/card/run-renderer.js +99 -232
- package/dist/card/run-state.d.ts +13 -0
- package/dist/card/run-state.js +25 -16
- package/dist/card/text-truncate.d.ts +13 -12
- package/dist/card/text-truncate.js +13 -63
- package/dist/common/fs.d.ts +8 -0
- package/dist/common/fs.js +17 -0
- package/dist/common/guards.d.ts +0 -20
- package/dist/common/guards.js +0 -43
- package/dist/common/sleep.d.ts +2 -0
- package/dist/common/sleep.js +5 -0
- package/dist/common/truncate.d.ts +39 -0
- package/dist/common/truncate.js +92 -0
- package/dist/config/codex-config.d.ts +1 -1
- package/dist/config/codex-config.js +1 -1
- package/dist/config/index.d.ts +88 -23
- package/dist/config/index.js +105 -34
- package/dist/connector/index.d.ts +69 -2
- package/dist/connector/index.js +107 -1
- package/dist/index.js +123 -105
- package/dist/instance-lock.js +4 -12
- package/dist/order/alias-resolve.d.ts +13 -0
- package/dist/order/alias-resolve.js +22 -0
- package/dist/order/alias-store.d.ts +38 -0
- package/dist/order/alias-store.js +106 -0
- package/dist/persistence/atomic-write.d.ts +8 -2
- package/dist/persistence/atomic-write.js +23 -7
- package/dist/restart.js +2 -1
- package/dist/router/card-helpers.d.ts +63 -0
- package/dist/router/card-helpers.js +146 -0
- package/dist/router/config/claude.js +25 -1
- package/dist/router/config/codex.js +29 -50
- package/dist/router/config/common/model-patch.d.ts +22 -0
- package/dist/router/config/common/model-patch.js +26 -0
- package/dist/router/config/common/render.js +5 -4
- package/dist/router/config/index.d.ts +12 -0
- package/dist/router/config/index.js +25 -1
- package/dist/router/config/kimi.d.ts +4 -1
- package/dist/router/config/kimi.js +27 -0
- package/dist/router/config/opencode.js +18 -5
- package/dist/router/config/pi.js +4 -5
- package/dist/router/config/types.d.ts +11 -2
- package/dist/router/index.d.ts +56 -21
- package/dist/router/index.js +373 -548
- package/dist/router/utils.d.ts +2 -0
- package/dist/router/utils.js +4 -0
- package/dist/runner/agent-choices-common.d.ts +16 -0
- package/dist/runner/agent-choices-common.js +31 -0
- package/dist/runner/claude/runner.d.ts +47 -10
- package/dist/runner/claude/runner.js +117 -46
- package/dist/runner/claude/session.d.ts +166 -0
- package/dist/runner/claude/session.js +749 -0
- package/dist/runner/codex/app-server/protocol-types.d.ts +31 -39
- package/dist/runner/codex/app-server/protocol-types.js +1 -12
- package/dist/runner/codex/app-server/runner.d.ts +42 -59
- package/dist/runner/codex/app-server/runner.js +160 -337
- package/dist/runner/codex/app-server/translator.d.ts +1 -21
- package/dist/runner/codex/app-server/translator.js +0 -18
- package/dist/runner/codex/index.d.ts +0 -2
- package/dist/runner/codex/index.js +0 -2
- package/dist/runner/common/acp/protocol-helpers.d.ts +14 -0
- package/dist/runner/common/acp/protocol-helpers.js +21 -0
- package/dist/runner/common/acp/protocol-types.d.ts +238 -0
- package/dist/runner/common/acp/protocol-types.js +75 -0
- package/dist/runner/common/connection-based-runner.d.ts +111 -0
- package/dist/runner/common/connection-based-runner.js +261 -0
- package/dist/runner/{codex/app-server → common/jsonrpc}/client.d.ts +14 -7
- package/dist/runner/{codex/app-server → common/jsonrpc}/client.js +27 -18
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +125 -0
- package/dist/runner/{codex/app-server → common/jsonrpc}/connection-manager.js +63 -28
- package/dist/runner/{codex/app-server → common/jsonrpc}/transport.d.ts +5 -4
- package/dist/runner/{codex/app-server → common/jsonrpc}/transport.js +18 -19
- package/dist/runner/common/jsonrpc/types.d.ts +32 -0
- package/dist/runner/common/jsonrpc/types.js +9 -0
- package/dist/runner/common/process-stopper.d.ts +2 -1
- package/dist/runner/common/process-stopper.js +1 -0
- package/dist/runner/common/runner-utils.d.ts +6 -13
- package/dist/runner/common/runner-utils.js +6 -47
- package/dist/runner/common/spawning-runner.d.ts +24 -70
- package/dist/runner/common/spawning-runner.js +98 -152
- package/dist/runner/index.d.ts +4 -4
- package/dist/runner/index.js +4 -4
- package/dist/runner/kimi/acp/runner.d.ts +106 -0
- package/dist/runner/kimi/acp/runner.js +448 -0
- package/dist/runner/kimi/acp/translator.d.ts +124 -0
- package/dist/runner/kimi/acp/translator.js +336 -0
- package/dist/runner/kimi/index.d.ts +1 -1
- package/dist/runner/kimi/index.js +1 -1
- package/dist/runner/opencode/acp/index.d.ts +1 -0
- package/dist/runner/opencode/acp/index.js +2 -0
- package/dist/runner/opencode/acp/runner.d.ts +121 -0
- package/dist/runner/opencode/acp/runner.js +429 -0
- package/dist/runner/opencode/acp/translator.d.ts +119 -0
- package/dist/runner/opencode/acp/translator.js +345 -0
- package/dist/runner/opencode/index.d.ts +1 -1
- package/dist/runner/opencode/index.js +1 -1
- package/dist/runner/pi/index.d.ts +1 -1
- package/dist/runner/pi/index.js +1 -1
- package/dist/runner/pi/rpc/client.d.ts +52 -0
- package/dist/runner/pi/rpc/client.js +114 -0
- package/dist/runner/pi/rpc/index.d.ts +4 -0
- package/dist/runner/pi/rpc/index.js +4 -0
- package/dist/runner/pi/rpc/protocol-types.d.ts +127 -0
- package/dist/runner/pi/rpc/protocol-types.js +21 -0
- package/dist/runner/pi/rpc/runner.d.ts +80 -0
- package/dist/runner/pi/rpc/runner.js +199 -0
- package/dist/runner/pi/rpc/translator.d.ts +55 -0
- package/dist/runner/pi/rpc/translator.js +258 -0
- package/dist/runner/registry.d.ts +7 -8
- package/dist/runner/registry.js +3 -4
- package/dist/runner/resolve-agent-choices.js +18 -59
- package/dist/runner/sync-agent-choices.js +16 -67
- package/dist/runner/types.d.ts +43 -13
- package/dist/session/claude/session-index.js +2 -3
- package/dist/session/claude/session-reader.js +3 -6
- package/dist/session/claude/sessions.js +6 -13
- package/dist/session/codex/rollout-reader.d.ts +2 -2
- package/dist/session/codex/rollout-reader.js +14 -17
- package/dist/session/codex/sessions.d.ts +1 -1
- package/dist/session/codex/sessions.js +2 -2
- package/dist/session/common/content-blocks.d.ts +7 -0
- package/dist/session/common/content-blocks.js +13 -9
- package/dist/session/common/jsonl.d.ts +8 -25
- package/dist/session/common/jsonl.js +126 -261
- package/dist/session/common/pagination.d.ts +28 -0
- package/dist/session/common/pagination.js +33 -0
- package/dist/session/kimi/sessions.d.ts +34 -1
- package/dist/session/kimi/sessions.js +208 -98
- package/dist/session/opencode/sessions.d.ts +17 -3
- package/dist/session/opencode/sessions.js +29 -29
- package/dist/session/pi/sessions.d.ts +5 -1
- package/dist/session/pi/sessions.js +12 -16
- package/dist/session/session-store.d.ts +11 -1
- package/dist/session/session-store.js +75 -178
- package/dist/update/index.d.ts +2 -2
- package/dist/update/index.js +2 -2
- package/dist/update/install.d.ts +20 -2
- package/dist/update/install.js +51 -7
- package/package.json +1 -1
- package/dist/runner/codex/app-server/connection-manager.d.ts +0 -56
- package/dist/runner/codex/argv.d.ts +0 -33
- package/dist/runner/codex/argv.js +0 -40
- package/dist/runner/codex/factory.d.ts +0 -29
- package/dist/runner/codex/factory.js +0 -39
- package/dist/runner/codex/jsonl.d.ts +0 -37
- package/dist/runner/codex/jsonl.js +0 -221
- package/dist/runner/codex/runner.d.ts +0 -66
- package/dist/runner/codex/runner.js +0 -129
- package/dist/runner/common/exec-translator.d.ts +0 -73
- package/dist/runner/common/exec-translator.js +0 -105
- package/dist/runner/kimi/jsonl.d.ts +0 -28
- package/dist/runner/kimi/jsonl.js +0 -143
- package/dist/runner/kimi/runner.d.ts +0 -49
- package/dist/runner/kimi/runner.js +0 -105
- package/dist/runner/opencode/argv.d.ts +0 -32
- package/dist/runner/opencode/argv.js +0 -35
- package/dist/runner/opencode/jsonl.d.ts +0 -68
- package/dist/runner/opencode/jsonl.js +0 -295
- package/dist/runner/opencode/runner.d.ts +0 -61
- package/dist/runner/opencode/runner.js +0 -93
- package/dist/runner/pi/jsonl.d.ts +0 -128
- package/dist/runner/pi/jsonl.js +0 -249
- package/dist/runner/pi/runner.d.ts +0 -36
- package/dist/runner/pi/runner.js +0 -76
- package/dist/session/common/catch-up.d.ts +0 -12
- 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 {
|
|
1
|
+
export { OpencodeAcpRunner } from './acp/index.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { OpencodeAcpRunner } from './acp/index.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { PiRpcRunner } from './rpc/index.js';
|
package/dist/runner/pi/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
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,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
|
+
};
|