lark-remote 0.1.6 → 0.1.8
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 +51 -1
- package/README.en.md +9 -1
- package/README.md +14 -2
- package/dist/bridge/approval-coordinator.d.ts +71 -34
- package/dist/bridge/approval-coordinator.js +225 -41
- package/dist/bridge/inbound-media.d.ts +62 -0
- package/dist/bridge/inbound-media.js +282 -0
- package/dist/bridge/index.d.ts +97 -13
- package/dist/bridge/index.js +227 -87
- package/dist/bridge/queue-manager.d.ts +2 -1
- package/dist/bridge/queue-manager.js +1 -0
- package/dist/card/approval-render.js +294 -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 +7 -16
- package/dist/card/card-shared.js +7 -30
- package/dist/card/run-card-session.d.ts +2 -55
- package/dist/card/run-card-session.js +6 -104
- package/dist/card/run-renderer.d.ts +7 -0
- package/dist/card/run-renderer.js +116 -238
- 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 +112 -24
- package/dist/config/index.js +139 -37
- package/dist/connector/index.d.ts +69 -2
- package/dist/connector/index.js +107 -1
- package/dist/index.js +150 -118
- 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-action-payload.d.ts +30 -0
- package/dist/router/card-action-payload.js +39 -0
- package/dist/router/card-helpers.d.ts +63 -0
- package/dist/router/card-helpers.js +146 -0
- package/dist/router/config/claude.js +30 -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/dsh.d.ts +51 -0
- package/dist/router/config/dsh.js +179 -0
- package/dist/router/config/index.d.ts +12 -0
- package/dist/router/config/index.js +28 -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 +17 -2
- package/dist/router/index.d.ts +76 -22
- package/dist/router/index.js +479 -548
- package/dist/router/utils.d.ts +2 -0
- package/dist/router/utils.js +16 -2
- package/dist/runner/agent-choices-common.d.ts +16 -0
- package/dist/runner/agent-choices-common.js +34 -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 +768 -0
- package/dist/runner/codex/app-server/protocol-types.d.ts +66 -39
- package/dist/runner/codex/app-server/protocol-types.js +2 -13
- package/dist/runner/codex/app-server/runner.d.ts +52 -59
- package/dist/runner/codex/app-server/runner.js +216 -338
- package/dist/runner/codex/app-server/translator.d.ts +8 -21
- package/dist/runner/codex/app-server/translator.js +45 -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 +271 -0
- package/dist/runner/common/acp/protocol-types.js +77 -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/dsh/client.d.ts +95 -0
- package/dist/runner/dsh/client.js +418 -0
- package/dist/runner/dsh/fake-dsh-server.d.ts +60 -0
- package/dist/runner/dsh/fake-dsh-server.js +156 -0
- package/dist/runner/dsh/index.d.ts +3 -0
- package/dist/runner/dsh/index.js +4 -0
- package/dist/runner/dsh/runner.d.ts +57 -0
- package/dist/runner/dsh/runner.js +191 -0
- package/dist/runner/dsh/translator.d.ts +28 -0
- package/dist/runner/dsh/translator.js +98 -0
- package/dist/runner/dsh/types.d.ts +155 -0
- package/dist/runner/dsh/types.js +14 -0
- package/dist/runner/index.d.ts +5 -4
- package/dist/runner/index.js +5 -4
- package/dist/runner/kimi/acp/runner.d.ts +106 -0
- package/dist/runner/kimi/acp/runner.js +516 -0
- package/dist/runner/kimi/acp/translator.d.ts +138 -0
- package/dist/runner/kimi/acp/translator.js +441 -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 +74 -0
- package/dist/runner/pi/rpc/runner.js +190 -0
- package/dist/runner/pi/rpc/translator.d.ts +55 -0
- package/dist/runner/pi/rpc/translator.js +258 -0
- package/dist/runner/probe.js +6 -1
- package/dist/runner/question-common.d.ts +26 -0
- package/dist/runner/question-common.js +42 -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 +97 -14
- package/dist/session/claude/session-index.js +2 -3
- package/dist/session/claude/session-reader.js +3 -6
- package/dist/session/claude/sessions.js +43 -28
- 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/dsh/index.d.ts +1 -0
- package/dist/session/dsh/index.js +2 -0
- package/dist/session/dsh/sessions.d.ts +39 -0
- package/dist/session/dsh/sessions.js +237 -0
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.js +1 -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/dist/workspace/index.d.ts +4 -0
- package/dist/workspace/index.js +8 -1
- package/package.json +2 -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,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KimiAcpTranslator: translates ACP session/update notifications and
|
|
3
|
+
* session/request_permission server requests into AgentEvents.
|
|
4
|
+
*
|
|
5
|
+
* Per-turn stateful: accumulates text/thinking deltas, tracks tool calls,
|
|
6
|
+
* and produces turn_started events with operationKind.
|
|
7
|
+
*
|
|
8
|
+
* Wire envelope (R1, source: kimi-code packages/acp-server/src/events-map.ts,
|
|
9
|
+
* verified live 2026-08-15/16 against kimi 0.36.0):
|
|
10
|
+
* session/update params = {sessionId, update: {sessionUpdate: '<kind>', ...}}
|
|
11
|
+
* — the discriminator is `update.sessionUpdate`, NOT a nested `event.type`.
|
|
12
|
+
*
|
|
13
|
+
* Event mapping (design doc §4.2):
|
|
14
|
+
* agent_message_chunk → turn_diff text snapshot (content.text accumulation)
|
|
15
|
+
* agent_thought_chunk → turn_diff reasoning snapshot (content.text accumulation)
|
|
16
|
+
* tool_call → assistant/tool_use (rawInput object passthrough,
|
|
17
|
+
* string → JSON.parse fallback)
|
|
18
|
+
* tool_call_update → user/tool_result (status:'failed' → is_error:true)
|
|
19
|
+
* plan / available_commands_update / session_info_update /
|
|
20
|
+
* current_mode_update / config_option_update → discard
|
|
21
|
+
* usage_update → live context occupancy {used, size} →
|
|
22
|
+
* {total_tokens, context_limit}; NO input/output
|
|
23
|
+
* split exists on the wire — cumulative token stats
|
|
24
|
+
* fall back to wire.jsonl usage.record (dual path)
|
|
25
|
+
* prompt stopReason:'end_turn' → result success
|
|
26
|
+
* prompt stopReason:'cancelled' → result interrupted (独立终态)
|
|
27
|
+
* prompt other stopReason/error → result error
|
|
28
|
+
* turn_started (self-produced) → operationKind 'turn'/'compaction'
|
|
29
|
+
* session/request_permission → approval_requested
|
|
30
|
+
*/
|
|
31
|
+
import type { AgentEvent } from '../../types.js';
|
|
32
|
+
import type { ApprovalRequestedEvent } from '../../types.js';
|
|
33
|
+
/** Self-produced turn_started event (no wire equivalent — produced on prompt start). */
|
|
34
|
+
export interface AcpTurnStartedEvent {
|
|
35
|
+
type: 'turn_started';
|
|
36
|
+
threadId: string;
|
|
37
|
+
turnId: string;
|
|
38
|
+
operationKind: 'turn' | 'compaction';
|
|
39
|
+
timestamp?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Live usage snapshot from usage_update. The wire only carries context
|
|
43
|
+
* occupancy (`used`) and the model context window (`size`) — there is NO
|
|
44
|
+
* input/output token split (events-map.ts:505-516). input_tokens/output_tokens
|
|
45
|
+
* stay undefined so the bridge falls back to wire.jsonl usage.record for
|
|
46
|
+
* cumulative token stats (R1 dual path).
|
|
47
|
+
*/
|
|
48
|
+
export interface AcpLiveUsage {
|
|
49
|
+
total_tokens?: number;
|
|
50
|
+
context_limit?: number;
|
|
51
|
+
input_tokens?: number;
|
|
52
|
+
output_tokens?: number;
|
|
53
|
+
}
|
|
54
|
+
/** Usage update event for live token display. */
|
|
55
|
+
export interface AcpUsageEvent {
|
|
56
|
+
type: 'usage';
|
|
57
|
+
usage: AcpLiveUsage;
|
|
58
|
+
timestamp?: string;
|
|
59
|
+
}
|
|
60
|
+
export type AcpTranslatorEvent = AgentEvent | AcpTurnStartedEvent | AcpUsageEvent | ApprovalRequestedEvent;
|
|
61
|
+
export declare class KimiAcpTranslator {
|
|
62
|
+
/** Accumulated text per tool-call or message (keyed by a logical item id). */
|
|
63
|
+
private textByItem;
|
|
64
|
+
/** Accumulated thinking per item. */
|
|
65
|
+
private thinkingByItem;
|
|
66
|
+
/** Pending tool calls keyed by toolCallId (for matching tool_call_update). */
|
|
67
|
+
private pendingToolCalls;
|
|
68
|
+
/** Current operation kind (turn vs compact). */
|
|
69
|
+
private operationKind;
|
|
70
|
+
/** Live usage snapshot from usage_update events (context occupancy only). */
|
|
71
|
+
private liveUsage;
|
|
72
|
+
/** Whether any usage_update has been seen this turn (no update → no usage). */
|
|
73
|
+
private hasLiveUsage;
|
|
74
|
+
/** Current turn id, set by produceTurnStarted; carried on turn_diff events. */
|
|
75
|
+
private currentTurnId;
|
|
76
|
+
/**
|
|
77
|
+
* Handle a notification from the ACP server and return translated events.
|
|
78
|
+
*/
|
|
79
|
+
handleNotification(method: string, params: unknown): AcpTranslatorEvent[];
|
|
80
|
+
/**
|
|
81
|
+
* Handle a server request (reverse RPC from the ACP server).
|
|
82
|
+
*/
|
|
83
|
+
handleServerRequest(id: number | string, method: string, params: unknown): AcpTranslatorEvent[];
|
|
84
|
+
/**
|
|
85
|
+
* Kimi elicitation form:requestedSchema.properties 按顺序解析为 UserQuestion[]
|
|
86
|
+
* (type:array → 多选;oneOf/anyOf const → 选项 label;title → header??question)。
|
|
87
|
+
* 题面完整文本只在 form 级 message 里合并出现(逐字段 title 可能只是短 header),
|
|
88
|
+
* 因此 message 作为卡片概要行(view.intro)透传。
|
|
89
|
+
* form 会丢弃非声明选项值 → isOther=false(卡片隐藏自定义答案输入)。
|
|
90
|
+
*/
|
|
91
|
+
private handleElicitationCreate;
|
|
92
|
+
/**
|
|
93
|
+
* Produce a turn_started event. Called by the runner when session/prompt
|
|
94
|
+
* is sent (no wire notification for this — self-produced).
|
|
95
|
+
*/
|
|
96
|
+
produceTurnStarted(sessionId: string, turnId: string): AcpTurnStartedEvent;
|
|
97
|
+
/**
|
|
98
|
+
* Translate a prompt response into a result event. Carries the live usage
|
|
99
|
+
* snapshot only when a usage_update was seen (otherwise undefined, so the
|
|
100
|
+
* bridge falls back to wire.jsonl usage.record readback).
|
|
101
|
+
*/
|
|
102
|
+
handlePromptResponse(sessionId: string, result: {
|
|
103
|
+
stopReason: string;
|
|
104
|
+
}): AgentEvent;
|
|
105
|
+
/**
|
|
106
|
+
* Produce an error result event (for JSON-RPC errors during prompt).
|
|
107
|
+
*/
|
|
108
|
+
produceErrorResult(sessionId: string, errorMessage: string): AgentEvent;
|
|
109
|
+
/**
|
|
110
|
+
* Set the operation kind (turn vs compact).
|
|
111
|
+
*/
|
|
112
|
+
setOperationKind(kind: 'turn' | 'compact'): void;
|
|
113
|
+
private handleAgentMessageChunk;
|
|
114
|
+
private handleAgentThoughtChunk;
|
|
115
|
+
private handleToolCall;
|
|
116
|
+
private handleToolCallUpdate;
|
|
117
|
+
private handleUsageUpdate;
|
|
118
|
+
private handleRequestPermission;
|
|
119
|
+
/**
|
|
120
|
+
* request_permission 兜底桥(elicitation/create 失败或旧版 kimi):
|
|
121
|
+
* 选项为 q{n}_opt_{i}(allow_once,label 直取 name)+ q{n}_skip(reject_once,
|
|
122
|
+
* 跳过语义)。单题单选、无 Other(kimi 桥丢弃非声明值)。
|
|
123
|
+
*/
|
|
124
|
+
private handlePermissionQuestion;
|
|
125
|
+
/**
|
|
126
|
+
* Derive the approval decision list from the server's offered options.
|
|
127
|
+
* accept/decline/cancel are universal; acceptForSession requires an
|
|
128
|
+
* always-class option (kimi `approve_always`, opencode `allow_always`).
|
|
129
|
+
*/
|
|
130
|
+
private deriveAvailableDecisions;
|
|
131
|
+
/**
|
|
132
|
+
* ResultEvent.usage requires input_tokens/output_tokens: number, but the
|
|
133
|
+
* ACP wire has no such split — the live snapshot is context occupancy only.
|
|
134
|
+
* Cast is deliberate: consumers must tolerate undefined input/output (the
|
|
135
|
+
* bridge's kimi path already falls back to wire.jsonl readback).
|
|
136
|
+
*/
|
|
137
|
+
private liveUsageSnapshot;
|
|
138
|
+
}
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KimiAcpTranslator: translates ACP session/update notifications and
|
|
3
|
+
* session/request_permission server requests into AgentEvents.
|
|
4
|
+
*
|
|
5
|
+
* Per-turn stateful: accumulates text/thinking deltas, tracks tool calls,
|
|
6
|
+
* and produces turn_started events with operationKind.
|
|
7
|
+
*
|
|
8
|
+
* Wire envelope (R1, source: kimi-code packages/acp-server/src/events-map.ts,
|
|
9
|
+
* verified live 2026-08-15/16 against kimi 0.36.0):
|
|
10
|
+
* session/update params = {sessionId, update: {sessionUpdate: '<kind>', ...}}
|
|
11
|
+
* — the discriminator is `update.sessionUpdate`, NOT a nested `event.type`.
|
|
12
|
+
*
|
|
13
|
+
* Event mapping (design doc §4.2):
|
|
14
|
+
* agent_message_chunk → turn_diff text snapshot (content.text accumulation)
|
|
15
|
+
* agent_thought_chunk → turn_diff reasoning snapshot (content.text accumulation)
|
|
16
|
+
* tool_call → assistant/tool_use (rawInput object passthrough,
|
|
17
|
+
* string → JSON.parse fallback)
|
|
18
|
+
* tool_call_update → user/tool_result (status:'failed' → is_error:true)
|
|
19
|
+
* plan / available_commands_update / session_info_update /
|
|
20
|
+
* current_mode_update / config_option_update → discard
|
|
21
|
+
* usage_update → live context occupancy {used, size} →
|
|
22
|
+
* {total_tokens, context_limit}; NO input/output
|
|
23
|
+
* split exists on the wire — cumulative token stats
|
|
24
|
+
* fall back to wire.jsonl usage.record (dual path)
|
|
25
|
+
* prompt stopReason:'end_turn' → result success
|
|
26
|
+
* prompt stopReason:'cancelled' → result interrupted (独立终态)
|
|
27
|
+
* prompt other stopReason/error → result error
|
|
28
|
+
* turn_started (self-produced) → operationKind 'turn'/'compaction'
|
|
29
|
+
* session/request_permission → approval_requested
|
|
30
|
+
*/
|
|
31
|
+
import { makeQuestionApprovalEvent } from '../../question-common.js';
|
|
32
|
+
import { NotificationMethod, ServerRequestMethod, SessionEventType, } from '../../common/acp/protocol-types.js';
|
|
33
|
+
import { getLogger } from '../../../logger/index.js';
|
|
34
|
+
/**
|
|
35
|
+
* Fixed snapshot item ids: ACP has exactly one text stream and one thinking
|
|
36
|
+
* stream per turn (no itemId on the wire), so turn_diff snapshots use these
|
|
37
|
+
* stable ids for in-place block replacement.
|
|
38
|
+
*/
|
|
39
|
+
const TEXT_ITEM_ID = 'text';
|
|
40
|
+
const THINKING_ITEM_ID = 'thinking';
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// Translator
|
|
43
|
+
// =============================================================================
|
|
44
|
+
export class KimiAcpTranslator {
|
|
45
|
+
/** Accumulated text per tool-call or message (keyed by a logical item id). */
|
|
46
|
+
textByItem = new Map();
|
|
47
|
+
/** Accumulated thinking per item. */
|
|
48
|
+
thinkingByItem = new Map();
|
|
49
|
+
/** Pending tool calls keyed by toolCallId (for matching tool_call_update). */
|
|
50
|
+
pendingToolCalls = new Map();
|
|
51
|
+
/** Current operation kind (turn vs compact). */
|
|
52
|
+
operationKind = 'turn';
|
|
53
|
+
/** Live usage snapshot from usage_update events (context occupancy only). */
|
|
54
|
+
liveUsage = {};
|
|
55
|
+
/** Whether any usage_update has been seen this turn (no update → no usage). */
|
|
56
|
+
hasLiveUsage = false;
|
|
57
|
+
/** Current turn id, set by produceTurnStarted; carried on turn_diff events. */
|
|
58
|
+
currentTurnId = '';
|
|
59
|
+
/**
|
|
60
|
+
* Handle a notification from the ACP server and return translated events.
|
|
61
|
+
*/
|
|
62
|
+
handleNotification(method, params) {
|
|
63
|
+
if (method !== NotificationMethod.SESSION_UPDATE) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
// R1: real envelope is {sessionId, update: {sessionUpdate: ...}}.
|
|
67
|
+
const notif = params;
|
|
68
|
+
const update = notif.update;
|
|
69
|
+
switch (update.sessionUpdate) {
|
|
70
|
+
case SessionEventType.AGENT_MESSAGE_CHUNK:
|
|
71
|
+
return this.handleAgentMessageChunk(update, notif.sessionId);
|
|
72
|
+
case SessionEventType.AGENT_THOUGHT_CHUNK:
|
|
73
|
+
return this.handleAgentThoughtChunk(update, notif.sessionId);
|
|
74
|
+
case SessionEventType.TOOL_CALL:
|
|
75
|
+
return this.handleToolCall(update, notif.sessionId);
|
|
76
|
+
case SessionEventType.TOOL_CALL_UPDATE:
|
|
77
|
+
return this.handleToolCallUpdate(update, notif.sessionId);
|
|
78
|
+
case SessionEventType.PLAN:
|
|
79
|
+
getLogger().debug(`[kimi-acp-translator] discarding plan event`);
|
|
80
|
+
return [];
|
|
81
|
+
case SessionEventType.AVAILABLE_COMMANDS_UPDATE:
|
|
82
|
+
case SessionEventType.SESSION_INFO_UPDATE:
|
|
83
|
+
case SessionEventType.CURRENT_MODE_UPDATE:
|
|
84
|
+
case SessionEventType.CONFIG_OPTION_UPDATE:
|
|
85
|
+
// Control-plane noise (command list, mode/config echoes) — not content.
|
|
86
|
+
return [];
|
|
87
|
+
case SessionEventType.USAGE_UPDATE:
|
|
88
|
+
return this.handleUsageUpdate(update);
|
|
89
|
+
default:
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Handle a server request (reverse RPC from the ACP server).
|
|
95
|
+
*/
|
|
96
|
+
handleServerRequest(id, method, params) {
|
|
97
|
+
if (method === ServerRequestMethod.REQUEST_PERMISSION) {
|
|
98
|
+
return this.handleRequestPermission(id, params);
|
|
99
|
+
}
|
|
100
|
+
if (method === ServerRequestMethod.ELICITATION_CREATE) {
|
|
101
|
+
return this.handleElicitationCreate(id, params);
|
|
102
|
+
}
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Kimi elicitation form:requestedSchema.properties 按顺序解析为 UserQuestion[]
|
|
107
|
+
* (type:array → 多选;oneOf/anyOf const → 选项 label;title → header??question)。
|
|
108
|
+
* 题面完整文本只在 form 级 message 里合并出现(逐字段 title 可能只是短 header),
|
|
109
|
+
* 因此 message 作为卡片概要行(view.intro)透传。
|
|
110
|
+
* form 会丢弃非声明选项值 → isOther=false(卡片隐藏自定义答案输入)。
|
|
111
|
+
*/
|
|
112
|
+
handleElicitationCreate(requestId, params) {
|
|
113
|
+
const properties = params.requestedSchema?.properties ?? {};
|
|
114
|
+
const questions = [];
|
|
115
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
116
|
+
const q = elicitationPropertyToQuestion(prop);
|
|
117
|
+
if (!q) {
|
|
118
|
+
getLogger().warn(`[kimi-acp-translator] elicitation property skipped: key=${key}`);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
questions.push(q);
|
|
122
|
+
}
|
|
123
|
+
if (questions.length === 0) {
|
|
124
|
+
// 无法解析 → 交还 runner 自动响应(不悬挂服务端)。
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
const message = params.message?.trim();
|
|
128
|
+
const single = questions.length === 1;
|
|
129
|
+
const intro = message && (!single || message !== questions[0].question) ? message : undefined;
|
|
130
|
+
const event = makeQuestionApprovalEvent(requestId, questions, params.sessionId);
|
|
131
|
+
if (intro)
|
|
132
|
+
event.view.intro = intro;
|
|
133
|
+
return [event];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Produce a turn_started event. Called by the runner when session/prompt
|
|
137
|
+
* is sent (no wire notification for this — self-produced).
|
|
138
|
+
*/
|
|
139
|
+
produceTurnStarted(sessionId, turnId) {
|
|
140
|
+
this.currentTurnId = turnId;
|
|
141
|
+
return {
|
|
142
|
+
type: 'turn_started',
|
|
143
|
+
threadId: sessionId,
|
|
144
|
+
turnId,
|
|
145
|
+
operationKind: this.operationKind === 'compact' ? 'compaction' : 'turn',
|
|
146
|
+
timestamp: new Date().toISOString(),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Translate a prompt response into a result event. Carries the live usage
|
|
151
|
+
* snapshot only when a usage_update was seen (otherwise undefined, so the
|
|
152
|
+
* bridge falls back to wire.jsonl usage.record readback).
|
|
153
|
+
*/
|
|
154
|
+
handlePromptResponse(sessionId, result) {
|
|
155
|
+
// §4.2: cancelled is independent terminal state, must NOT merge into error
|
|
156
|
+
const subtype = result.stopReason === 'end_turn'
|
|
157
|
+
? 'success'
|
|
158
|
+
: result.stopReason === 'cancelled'
|
|
159
|
+
? 'interrupted'
|
|
160
|
+
: 'error';
|
|
161
|
+
return {
|
|
162
|
+
type: 'result',
|
|
163
|
+
subtype,
|
|
164
|
+
session_id: sessionId,
|
|
165
|
+
...(this.hasLiveUsage ? { usage: this.liveUsageSnapshot() } : {}),
|
|
166
|
+
...(subtype === 'error'
|
|
167
|
+
? { errorMessage: `Prompt ended with stopReason: ${result.stopReason}` }
|
|
168
|
+
: {}),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Produce an error result event (for JSON-RPC errors during prompt).
|
|
173
|
+
*/
|
|
174
|
+
produceErrorResult(sessionId, errorMessage) {
|
|
175
|
+
return {
|
|
176
|
+
type: 'result',
|
|
177
|
+
subtype: 'error',
|
|
178
|
+
session_id: sessionId,
|
|
179
|
+
errorMessage,
|
|
180
|
+
...(this.hasLiveUsage ? { usage: this.liveUsageSnapshot() } : {}),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Set the operation kind (turn vs compact).
|
|
185
|
+
*/
|
|
186
|
+
setOperationKind(kind) {
|
|
187
|
+
this.operationKind = kind;
|
|
188
|
+
}
|
|
189
|
+
// =========================================================================
|
|
190
|
+
// Private notification handlers
|
|
191
|
+
// =========================================================================
|
|
192
|
+
handleAgentMessageChunk(event, sessionId) {
|
|
193
|
+
// Use a stable key for the current text stream. Kimi ACP doesn't
|
|
194
|
+
// provide an itemId in agent_message_chunk; we use 'text' as the
|
|
195
|
+
// single logical text accumulator (one text stream per turn).
|
|
196
|
+
// R1: wire chunk text lives at content.text and is a DELTA
|
|
197
|
+
// (kimi-code events-map.ts assistantDeltaToSessionUpdate:
|
|
198
|
+
// content.text = event.delta), so we accumulate here into a full-text
|
|
199
|
+
// snapshot and emit it as turn_diff. The card reducer's turn_diff path
|
|
200
|
+
// REPLACES the block in place (snapshot semantics). Emitting the full
|
|
201
|
+
// text as assistant/text (delta semantics) made the reducer append the
|
|
202
|
+
// full text to itself on every chunk — duplicated card output
|
|
203
|
+
// (2026-08-17 live regression, reproduced with 3 chunks).
|
|
204
|
+
const key = 'text';
|
|
205
|
+
const prev = this.textByItem.get(key) ?? '';
|
|
206
|
+
const next = prev + event.content.text;
|
|
207
|
+
this.textByItem.set(key, next);
|
|
208
|
+
return [
|
|
209
|
+
{
|
|
210
|
+
type: 'turn_diff',
|
|
211
|
+
itemId: TEXT_ITEM_ID,
|
|
212
|
+
text: next,
|
|
213
|
+
threadId: sessionId,
|
|
214
|
+
turnId: this.currentTurnId,
|
|
215
|
+
timestamp: new Date().toISOString(),
|
|
216
|
+
},
|
|
217
|
+
];
|
|
218
|
+
}
|
|
219
|
+
handleAgentThoughtChunk(event, sessionId) {
|
|
220
|
+
// Same delta→snapshot contract as handleAgentMessageChunk: accumulate
|
|
221
|
+
// wire deltas and emit a full reasoning snapshot via turn_diff so the
|
|
222
|
+
// reducer replaces the thinking block instead of appending.
|
|
223
|
+
const key = THINKING_ITEM_ID;
|
|
224
|
+
const prev = this.thinkingByItem.get(key) ?? '';
|
|
225
|
+
const next = prev + event.content.text;
|
|
226
|
+
this.thinkingByItem.set(key, next);
|
|
227
|
+
return [
|
|
228
|
+
{
|
|
229
|
+
type: 'turn_diff',
|
|
230
|
+
itemId: THINKING_ITEM_ID,
|
|
231
|
+
reasoning: next,
|
|
232
|
+
threadId: sessionId,
|
|
233
|
+
turnId: this.currentTurnId,
|
|
234
|
+
timestamp: new Date().toISOString(),
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
handleToolCall(event, _sessionId) {
|
|
239
|
+
// rawInput arrives as an object on the wire; older kimi versions sent a
|
|
240
|
+
// JSON string — parse defensively in that case. The acp-server's
|
|
241
|
+
// lazy-create tool_call (first args delta, events-map.ts
|
|
242
|
+
// toolCallLazyCreateToSessionUpdate) carries NO rawInput field at all —
|
|
243
|
+
// normalize to an empty object so the card renders an empty args summary
|
|
244
|
+
// instead of crashing on undefined input (2026-08-17 live TypeError).
|
|
245
|
+
let input = event.rawInput ?? {};
|
|
246
|
+
if (typeof event.rawInput === 'string') {
|
|
247
|
+
try {
|
|
248
|
+
input = JSON.parse(event.rawInput);
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// Keep raw string as-is
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
this.pendingToolCalls.set(event.toolCallId, {
|
|
255
|
+
id: event.toolCallId,
|
|
256
|
+
name: event.title,
|
|
257
|
+
input,
|
|
258
|
+
});
|
|
259
|
+
return [
|
|
260
|
+
{
|
|
261
|
+
type: 'assistant',
|
|
262
|
+
message: {
|
|
263
|
+
content: [
|
|
264
|
+
{
|
|
265
|
+
type: 'tool_use',
|
|
266
|
+
id: event.toolCallId,
|
|
267
|
+
name: event.title,
|
|
268
|
+
input,
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
},
|
|
272
|
+
timestamp: new Date().toISOString(),
|
|
273
|
+
},
|
|
274
|
+
];
|
|
275
|
+
}
|
|
276
|
+
handleToolCallUpdate(event, _sessionId) {
|
|
277
|
+
// §4.2: status:'failed' → is_error:true (fixes CLI mode's is_error always false)
|
|
278
|
+
const isError = event.status === 'failed';
|
|
279
|
+
const content = event.rawOutput ?? '';
|
|
280
|
+
return [
|
|
281
|
+
{
|
|
282
|
+
type: 'user',
|
|
283
|
+
message: {
|
|
284
|
+
content: [
|
|
285
|
+
{
|
|
286
|
+
type: 'tool_result',
|
|
287
|
+
tool_use_id: event.toolCallId,
|
|
288
|
+
content,
|
|
289
|
+
is_error: isError,
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
},
|
|
293
|
+
timestamp: new Date().toISOString(),
|
|
294
|
+
},
|
|
295
|
+
];
|
|
296
|
+
}
|
|
297
|
+
handleUsageUpdate(event) {
|
|
298
|
+
// R1: usage_update is {sessionUpdate:'usage_update', used, size}
|
|
299
|
+
// (events-map.ts:505-516) — context occupancy, not cumulative tokens.
|
|
300
|
+
this.liveUsage = {
|
|
301
|
+
total_tokens: event.used,
|
|
302
|
+
context_limit: event.size,
|
|
303
|
+
};
|
|
304
|
+
this.hasLiveUsage = true;
|
|
305
|
+
return [];
|
|
306
|
+
}
|
|
307
|
+
// =========================================================================
|
|
308
|
+
// Private approval handlers
|
|
309
|
+
// =========================================================================
|
|
310
|
+
handleRequestPermission(requestId, params) {
|
|
311
|
+
// §5.4 升级:question elicitation 识别(isQuestion 标记 / AskUserQuestion
|
|
312
|
+
// toolCall 标题 / q{n}_opt_* 选项命名空间)→ 渲染为提问卡;无法解析的
|
|
313
|
+
// 退化形态(无 toolCall 也无选项)仍返回 [] 由 runner 自动响应 cancelled。
|
|
314
|
+
const toolCall = params.toolCall;
|
|
315
|
+
const questionMarker = params.isQuestion === true ||
|
|
316
|
+
toolCall?.title === 'AskUserQuestion' ||
|
|
317
|
+
optionIdsMatchQuestionNamespace(params.options);
|
|
318
|
+
if (questionMarker) {
|
|
319
|
+
return this.handlePermissionQuestion(requestId, params);
|
|
320
|
+
}
|
|
321
|
+
// §5.4:无 toolCall 且无提问标记 → 无法构成审批视图,返回 [] 由 runner
|
|
322
|
+
// 自动响应 cancelled(服务端不会悬挂)。
|
|
323
|
+
if (!toolCall) {
|
|
324
|
+
getLogger().info(`[kimi-acp-translator] permission request without toolCall, auto-responding cancelled (requestId=${requestId})`);
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
const view = {
|
|
328
|
+
requestId,
|
|
329
|
+
kind: 'command',
|
|
330
|
+
command: params.toolCall?.title ?? undefined,
|
|
331
|
+
reason: params.toolCall?.rawInput ? truncate(params.toolCall.rawInput, 200) : undefined,
|
|
332
|
+
// §P4: 从服务端 options kind 派生——带 approve_always(或 allow_always)
|
|
333
|
+
// 才提供「本会话总是允许」(acceptForSession);否则与旧行为一致。
|
|
334
|
+
availableDecisions: this.deriveAvailableDecisions(params),
|
|
335
|
+
};
|
|
336
|
+
return [
|
|
337
|
+
{
|
|
338
|
+
type: 'approval_requested',
|
|
339
|
+
requestId,
|
|
340
|
+
kind: 'command',
|
|
341
|
+
threadId: params.sessionId,
|
|
342
|
+
turnId: '',
|
|
343
|
+
itemId: '',
|
|
344
|
+
view,
|
|
345
|
+
timestamp: new Date().toISOString(),
|
|
346
|
+
},
|
|
347
|
+
];
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* request_permission 兜底桥(elicitation/create 失败或旧版 kimi):
|
|
351
|
+
* 选项为 q{n}_opt_{i}(allow_once,label 直取 name)+ q{n}_skip(reject_once,
|
|
352
|
+
* 跳过语义)。单题单选、无 Other(kimi 桥丢弃非声明值)。
|
|
353
|
+
*/
|
|
354
|
+
handlePermissionQuestion(requestId, params) {
|
|
355
|
+
const selectable = (params.options ?? []).filter((opt) => /^q\d+_opt_\d+$/.test(opt.optionId));
|
|
356
|
+
if (selectable.length === 0) {
|
|
357
|
+
getLogger().info(`[kimi-acp-translator] question elicitation unparseable, auto-responding cancelled (requestId=${requestId})`);
|
|
358
|
+
return [];
|
|
359
|
+
}
|
|
360
|
+
const questionText = extractQuestionText(params.toolCall);
|
|
361
|
+
const questions = [
|
|
362
|
+
{
|
|
363
|
+
question: questionText,
|
|
364
|
+
isOther: false,
|
|
365
|
+
options: selectable.map((opt) => ({ label: opt.name })),
|
|
366
|
+
},
|
|
367
|
+
];
|
|
368
|
+
return [makeQuestionApprovalEvent(requestId, questions, params.sessionId)];
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Derive the approval decision list from the server's offered options.
|
|
372
|
+
* accept/decline/cancel are universal; acceptForSession requires an
|
|
373
|
+
* always-class option (kimi `approve_always`, opencode `allow_always`).
|
|
374
|
+
*/
|
|
375
|
+
deriveAvailableDecisions(params) {
|
|
376
|
+
const decisions = ['accept', 'decline', 'cancel'];
|
|
377
|
+
const hasAlwaysOption = (params.options ?? []).some((opt) => opt.kind === 'approve_always' || opt.kind === 'allow_always');
|
|
378
|
+
if (hasAlwaysOption) {
|
|
379
|
+
decisions.push('acceptForSession');
|
|
380
|
+
}
|
|
381
|
+
return decisions;
|
|
382
|
+
}
|
|
383
|
+
// =========================================================================
|
|
384
|
+
// Private helpers
|
|
385
|
+
// =========================================================================
|
|
386
|
+
/**
|
|
387
|
+
* ResultEvent.usage requires input_tokens/output_tokens: number, but the
|
|
388
|
+
* ACP wire has no such split — the live snapshot is context occupancy only.
|
|
389
|
+
* Cast is deliberate: consumers must tolerate undefined input/output (the
|
|
390
|
+
* bridge's kimi path already falls back to wire.jsonl readback).
|
|
391
|
+
*/
|
|
392
|
+
liveUsageSnapshot() {
|
|
393
|
+
return { ...this.liveUsage };
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// =============================================================================
|
|
397
|
+
// Helpers
|
|
398
|
+
// =============================================================================
|
|
399
|
+
function truncate(text, maxLen) {
|
|
400
|
+
return text.length <= maxLen ? text : text.slice(0, maxLen) + '…';
|
|
401
|
+
}
|
|
402
|
+
/** elicitation schema property → UserQuestion(无法解析返回 null)。 */
|
|
403
|
+
function elicitationPropertyToQuestion(prop) {
|
|
404
|
+
const title = prop.title?.trim();
|
|
405
|
+
if (!title)
|
|
406
|
+
return null;
|
|
407
|
+
const multiSelect = prop.type === 'array';
|
|
408
|
+
const rawOptions = multiSelect ? prop.items?.anyOf : prop.oneOf;
|
|
409
|
+
const options = (rawOptions ?? [])
|
|
410
|
+
.filter((opt) => typeof opt?.const === 'string')
|
|
411
|
+
.map((opt) => ({
|
|
412
|
+
label: opt.const,
|
|
413
|
+
...(opt.description ? { description: opt.description } : {}),
|
|
414
|
+
}));
|
|
415
|
+
if (options.length === 0)
|
|
416
|
+
return null;
|
|
417
|
+
return {
|
|
418
|
+
question: title,
|
|
419
|
+
header: title,
|
|
420
|
+
multiSelect,
|
|
421
|
+
isOther: false,
|
|
422
|
+
options,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/** 选项命名空间是否匹配 kimi question 桥(q{n}_opt_{i} / q{n}_skip)。 */
|
|
426
|
+
function optionIdsMatchQuestionNamespace(options) {
|
|
427
|
+
return (options ?? []).some((opt) => /^q\d+_(opt_\d+|skip)$/.test(opt.optionId));
|
|
428
|
+
}
|
|
429
|
+
/** 从 AskUserQuestion toolCall 提取题面(content 文本优先,标题兜底)。 */
|
|
430
|
+
function extractQuestionText(toolCall) {
|
|
431
|
+
const content = toolCall?.content;
|
|
432
|
+
if (Array.isArray(content)) {
|
|
433
|
+
for (const block of content) {
|
|
434
|
+
const text = block.content?.text;
|
|
435
|
+
if (typeof text === 'string' && text.trim())
|
|
436
|
+
return text.trim();
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return toolCall?.title?.trim() || '请选择';
|
|
440
|
+
}
|
|
441
|
+
//# sourceMappingURL=translator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { KimiAcpRunner } from './acp/runner.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { KimiAcpRunner } from './acp/runner.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { OpencodeAcpRunner, type OpencodeAcpRunnerOptions } from './runner.js';
|