lark-remote 0.1.7 → 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 +28 -0
- package/dist/bridge/approval-coordinator.d.ts +15 -1
- package/dist/bridge/approval-coordinator.js +35 -2
- package/dist/bridge/index.d.ts +19 -6
- package/dist/bridge/index.js +25 -8
- package/dist/card/approval-render.js +187 -45
- package/dist/card/card-shared.d.ts +6 -4
- package/dist/card/card-shared.js +6 -4
- package/dist/card/run-renderer.d.ts +7 -0
- package/dist/card/run-renderer.js +22 -11
- package/dist/config/index.d.ts +24 -1
- package/dist/config/index.js +34 -3
- package/dist/index.js +28 -14
- package/dist/router/card-action-payload.d.ts +30 -0
- package/dist/router/card-action-payload.js +39 -0
- package/dist/router/config/claude.js +8 -3
- package/dist/router/config/dsh.d.ts +51 -0
- package/dist/router/config/dsh.js +179 -0
- package/dist/router/config/index.d.ts +1 -1
- package/dist/router/config/index.js +4 -1
- package/dist/router/config/types.d.ts +6 -0
- package/dist/router/index.d.ts +20 -1
- package/dist/router/index.js +113 -7
- package/dist/router/utils.js +12 -2
- package/dist/runner/agent-choices-common.js +3 -0
- package/dist/runner/claude/session.js +24 -5
- package/dist/runner/codex/app-server/protocol-types.d.ts +35 -0
- package/dist/runner/codex/app-server/protocol-types.js +1 -1
- package/dist/runner/codex/app-server/runner.d.ts +10 -0
- package/dist/runner/codex/app-server/runner.js +56 -1
- package/dist/runner/codex/app-server/translator.d.ts +7 -0
- package/dist/runner/codex/app-server/translator.js +45 -0
- package/dist/runner/common/acp/protocol-types.d.ts +33 -0
- package/dist/runner/common/acp/protocol-types.js +2 -0
- 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 +1 -0
- package/dist/runner/index.js +1 -0
- package/dist/runner/kimi/acp/runner.js +69 -1
- package/dist/runner/kimi/acp/translator.d.ts +14 -0
- package/dist/runner/kimi/acp/translator.js +108 -3
- package/dist/runner/pi/rpc/runner.d.ts +1 -7
- package/dist/runner/pi/rpc/runner.js +2 -11
- 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/types.d.ts +63 -10
- package/dist/session/claude/sessions.js +37 -15
- 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/workspace/index.d.ts +4 -0
- package/dist/workspace/index.js +8 -1
- package/package.json +2 -1
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DshRunner — workspace-lifetime runner bridging to the DSH Web Host
|
|
3
|
+
* (DeepSeek Harness) over plain HTTP. No local subprocess is spawned.
|
|
4
|
+
*
|
|
5
|
+
* Flow per run: session.create(cwd) (when no sessionId) → synthetic system/init
|
|
6
|
+
* (DSH has no init event) → open per-session mux subscription → session.prompt
|
|
7
|
+
* (mode:'queue') → consume subscription until turn/end → result event.
|
|
8
|
+
*
|
|
9
|
+
* stop() → session.cancel + abort the mux subscription. killOrphan /
|
|
10
|
+
* registerExitHandlers are no-ops (no local process). approval/requested is
|
|
11
|
+
* surfaced as a WARN log + an assistant text prompt pointing to the DSH Web UI
|
|
12
|
+
* (never silently parked — the turn stays live until the user resolves it in
|
|
13
|
+
* the UI or the turn is cancelled/times out).
|
|
14
|
+
*/
|
|
15
|
+
import type { AgentEvent, AgentSessionReader, AgentStatusInfo, SpawnOptions } from '../types.js';
|
|
16
|
+
import type { AgentRunner } from '../types.js';
|
|
17
|
+
import { DshClient, type WebSocketFactory } from './client.js';
|
|
18
|
+
/** DSH selectModel provider(deepseek-official 是 rc.7 模型目录的 provider)。 */
|
|
19
|
+
export declare const DSH_MODEL_PROVIDER = "deepseek-official";
|
|
20
|
+
export interface DshRunnerOptions {
|
|
21
|
+
kind: 'dsh';
|
|
22
|
+
sessionReader: AgentSessionReader;
|
|
23
|
+
/** DSH Web Host base URL, e.g. http://127.0.0.1:3080. */
|
|
24
|
+
host?: string;
|
|
25
|
+
/** Session preset(agentPreset)固定于 session 创建时;undefined = 服务端默认。 */
|
|
26
|
+
agentPreset?: string;
|
|
27
|
+
/** Display model name. */
|
|
28
|
+
model?: string;
|
|
29
|
+
/** 推理强度(off/low/high/max);undefined = 服务端默认。 */
|
|
30
|
+
reasoningEffort?: string;
|
|
31
|
+
/** Injectable fetch for tests. */
|
|
32
|
+
fetchImpl?: typeof fetch;
|
|
33
|
+
/** Injectable WebSocket factory (defaults to global WebSocket). */
|
|
34
|
+
webSocketImpl?: WebSocketFactory;
|
|
35
|
+
}
|
|
36
|
+
export declare class DshRunner implements AgentRunner {
|
|
37
|
+
readonly kind: "dsh";
|
|
38
|
+
readonly sessionReader: AgentSessionReader;
|
|
39
|
+
readonly client: DshClient;
|
|
40
|
+
/** 用户配置的模型 ID(run 前 selectModel 对齐用);undefined = 服务端默认。 */
|
|
41
|
+
private readonly configuredModel;
|
|
42
|
+
private readonly agentPreset;
|
|
43
|
+
private readonly reasoningEffort;
|
|
44
|
+
private activeAbort;
|
|
45
|
+
private activeSessionId;
|
|
46
|
+
private running;
|
|
47
|
+
constructor(opts: DshRunnerOptions);
|
|
48
|
+
get isRunning(): boolean;
|
|
49
|
+
getStatusInfo(): AgentStatusInfo;
|
|
50
|
+
run(message: string, opts: SpawnOptions): AsyncGenerator<AgentEvent>;
|
|
51
|
+
stop(_opts?: {
|
|
52
|
+
immediate?: boolean;
|
|
53
|
+
}): Promise<void>;
|
|
54
|
+
killOrphan(): void;
|
|
55
|
+
registerExitHandlers(): void;
|
|
56
|
+
unregisterExitHandlers(): void;
|
|
57
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DshRunner — workspace-lifetime runner bridging to the DSH Web Host
|
|
3
|
+
* (DeepSeek Harness) over plain HTTP. No local subprocess is spawned.
|
|
4
|
+
*
|
|
5
|
+
* Flow per run: session.create(cwd) (when no sessionId) → synthetic system/init
|
|
6
|
+
* (DSH has no init event) → open per-session mux subscription → session.prompt
|
|
7
|
+
* (mode:'queue') → consume subscription until turn/end → result event.
|
|
8
|
+
*
|
|
9
|
+
* stop() → session.cancel + abort the mux subscription. killOrphan /
|
|
10
|
+
* registerExitHandlers are no-ops (no local process). approval/requested is
|
|
11
|
+
* surfaced as a WARN log + an assistant text prompt pointing to the DSH Web UI
|
|
12
|
+
* (never silently parked — the turn stays live until the user resolves it in
|
|
13
|
+
* the UI or the turn is cancelled/times out).
|
|
14
|
+
*/
|
|
15
|
+
import { DshClient } from './client.js';
|
|
16
|
+
import { DshTranslator } from './translator.js';
|
|
17
|
+
import { getLogger } from '../../logger/index.js';
|
|
18
|
+
/** DSH selectModel provider(deepseek-official 是 rc.7 模型目录的 provider)。 */
|
|
19
|
+
export const DSH_MODEL_PROVIDER = 'deepseek-official';
|
|
20
|
+
export class DshRunner {
|
|
21
|
+
kind = 'dsh';
|
|
22
|
+
sessionReader;
|
|
23
|
+
client;
|
|
24
|
+
/** 用户配置的模型 ID(run 前 selectModel 对齐用);undefined = 服务端默认。 */
|
|
25
|
+
configuredModel;
|
|
26
|
+
agentPreset;
|
|
27
|
+
reasoningEffort;
|
|
28
|
+
activeAbort = null;
|
|
29
|
+
activeSessionId = null;
|
|
30
|
+
running = false;
|
|
31
|
+
constructor(opts) {
|
|
32
|
+
this.sessionReader = opts.sessionReader;
|
|
33
|
+
this.configuredModel = opts.model;
|
|
34
|
+
this.agentPreset = opts.agentPreset;
|
|
35
|
+
this.reasoningEffort = opts.reasoningEffort;
|
|
36
|
+
this.client = new DshClient(opts.host ?? 'http://127.0.0.1:3080', opts.fetchImpl, opts.webSocketImpl);
|
|
37
|
+
}
|
|
38
|
+
get isRunning() {
|
|
39
|
+
return this.running;
|
|
40
|
+
}
|
|
41
|
+
getStatusInfo() {
|
|
42
|
+
const extras = { host: this.client.baseUrl };
|
|
43
|
+
if (this.agentPreset)
|
|
44
|
+
extras.preset = this.agentPreset;
|
|
45
|
+
if (this.reasoningEffort)
|
|
46
|
+
extras.reasoning = this.reasoningEffort;
|
|
47
|
+
return {
|
|
48
|
+
kind: this.kind,
|
|
49
|
+
model: this.configuredModel ?? 'DSH',
|
|
50
|
+
extras,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async *run(message, opts) {
|
|
54
|
+
const abort = new AbortController();
|
|
55
|
+
this.activeAbort = abort;
|
|
56
|
+
this.running = true;
|
|
57
|
+
let sessionId = opts.sessionId;
|
|
58
|
+
if (!sessionId) {
|
|
59
|
+
const created = await this.client.createSession({
|
|
60
|
+
cwd: opts.cwd,
|
|
61
|
+
...(this.agentPreset ? { agentPreset: this.agentPreset } : {}),
|
|
62
|
+
});
|
|
63
|
+
sessionId = created.sessionId;
|
|
64
|
+
}
|
|
65
|
+
this.activeSessionId = sessionId;
|
|
66
|
+
// 模型/推理强度变更 = 保留 session,每次 run 前对齐(§4.3):
|
|
67
|
+
// 该调用同时会写服务端全局默认;失败仅告警不阻断 run(模型校验由 DSH 负责)。
|
|
68
|
+
if (this.configuredModel) {
|
|
69
|
+
try {
|
|
70
|
+
await this.client.selectModel({
|
|
71
|
+
sessionId,
|
|
72
|
+
provider: DSH_MODEL_PROVIDER,
|
|
73
|
+
model: this.configuredModel,
|
|
74
|
+
...(this.reasoningEffort ? { reasoningEffort: this.reasoningEffort } : {}),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
getLogger().warn(`[dsh] selectModel failed for session ${sessionId} (run continues): ${err.message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
yield {
|
|
82
|
+
type: 'system',
|
|
83
|
+
subtype: 'init',
|
|
84
|
+
session_id: sessionId,
|
|
85
|
+
cwd: opts.cwd,
|
|
86
|
+
model: this.configuredModel ?? 'DSH',
|
|
87
|
+
};
|
|
88
|
+
let terminalEmitted = false;
|
|
89
|
+
try {
|
|
90
|
+
// Snapshot the pre-prompt high-water mark before mutating the session.
|
|
91
|
+
// DSH session.history returns the whole session, so starting from seq 0
|
|
92
|
+
// would replay the first completed turn on every follow-up prompt.
|
|
93
|
+
const prePromptSeq = await this.client.latestEventSeq(sessionId);
|
|
94
|
+
await this.client.unary('session.prompt', {
|
|
95
|
+
sessionId,
|
|
96
|
+
mode: 'queue',
|
|
97
|
+
content: [{ type: 'text', text: message }],
|
|
98
|
+
});
|
|
99
|
+
const translator = new DshTranslator();
|
|
100
|
+
try {
|
|
101
|
+
for await (const item of this.client.sessionEvents(sessionId, opts.cwd, abort.signal, prePromptSeq)) {
|
|
102
|
+
if (abort.signal.aborted)
|
|
103
|
+
break;
|
|
104
|
+
if (item.kind === 'approval') {
|
|
105
|
+
getLogger().warn(`[dsh] approval requested approvalId=${item.approvalId} tool=${item.toolName} — resolve in DSH Web UI`);
|
|
106
|
+
yield {
|
|
107
|
+
type: 'assistant',
|
|
108
|
+
message: {
|
|
109
|
+
content: [
|
|
110
|
+
{
|
|
111
|
+
type: 'text',
|
|
112
|
+
text: `⚠️ 需要授权(${item.toolName}):请在 DSH Web UI 处理 ${this.client.baseUrl}`,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
for (const ev of translator.eventToAgentEvents(item.event, sessionId)) {
|
|
120
|
+
yield ev;
|
|
121
|
+
if (ev.type === 'result') {
|
|
122
|
+
terminalEmitted = true;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
if (abort.signal.aborted)
|
|
130
|
+
throw err;
|
|
131
|
+
getLogger().error(`[dsh] run consume error: ${err.message}`);
|
|
132
|
+
yield {
|
|
133
|
+
type: 'result',
|
|
134
|
+
subtype: 'error',
|
|
135
|
+
session_id: sessionId,
|
|
136
|
+
errorMessage: `DSH stream error: ${err.message}`,
|
|
137
|
+
};
|
|
138
|
+
terminalEmitted = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
if (!abort.signal.aborted) {
|
|
143
|
+
yield {
|
|
144
|
+
type: 'result',
|
|
145
|
+
subtype: 'error',
|
|
146
|
+
session_id: sessionId,
|
|
147
|
+
errorMessage: `DSH run failed: ${err.message}`,
|
|
148
|
+
};
|
|
149
|
+
terminalEmitted = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
this.running = false;
|
|
154
|
+
this.activeSessionId = null;
|
|
155
|
+
this.activeAbort = null;
|
|
156
|
+
}
|
|
157
|
+
if (!terminalEmitted) {
|
|
158
|
+
yield {
|
|
159
|
+
type: 'result',
|
|
160
|
+
subtype: abort.signal.aborted ? 'interrupted' : 'error',
|
|
161
|
+
session_id: sessionId,
|
|
162
|
+
errorMessage: abort.signal.aborted ? 'run stopped' : 'turn ended without a terminal event',
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async stop(_opts) {
|
|
167
|
+
const sessionId = this.activeSessionId;
|
|
168
|
+
const abort = this.activeAbort;
|
|
169
|
+
if (abort && !abort.signal.aborted)
|
|
170
|
+
abort.abort();
|
|
171
|
+
if (sessionId) {
|
|
172
|
+
try {
|
|
173
|
+
await this.client.unary('session.cancel', { sessionId });
|
|
174
|
+
getLogger().info(`[dsh] cancelled session ${sessionId}`);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
getLogger().warn(`[dsh] session.cancel failed: ${err.message}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
killOrphan() {
|
|
182
|
+
// No local subprocess for DSH — nothing to reap.
|
|
183
|
+
}
|
|
184
|
+
registerExitHandlers() {
|
|
185
|
+
// No local subprocess for DSH — nothing to register.
|
|
186
|
+
}
|
|
187
|
+
unregisterExitHandlers() {
|
|
188
|
+
// No-op (see registerExitHandlers).
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DshTranslator — maps DSH SessionEvents to AgentEvents.
|
|
3
|
+
*
|
|
4
|
+
* Per-turn stateful: accumulates usage from assistant/message.usage records and
|
|
5
|
+
* converts assistant/chunk deltas to assistant text/thinking events.
|
|
6
|
+
*
|
|
7
|
+
* Mapping (task spec + DSH SessionEventMap):
|
|
8
|
+
* assistant/chunk {chunk:{type:'text-delta',text}} → assistant text (incremental)
|
|
9
|
+
* assistant/chunk {chunk:{type:'reasoning-delta',text}} → assistant thinking (incremental)
|
|
10
|
+
* assistant/message {usage?} → records usage (no event)
|
|
11
|
+
* turn/end {reason.kind} → result:
|
|
12
|
+
* completed → success; aborted/interrupted → interrupted;
|
|
13
|
+
* blocked/error/max-tokens → error (errorMessage carries the reason)
|
|
14
|
+
*
|
|
15
|
+
* DSH TokenUsage: {inputTokens, outputTokens, cacheReadTokens?, cacheWriteTokens?}
|
|
16
|
+
* → ResultEvent.usage: input_tokens/output_tokens/cache_read_tokens/
|
|
17
|
+
* cache_creation_tokens (cacheWrite = cache creation) + total_tokens sum.
|
|
18
|
+
*/
|
|
19
|
+
import type { AgentEvent, ResultEvent } from '../types.js';
|
|
20
|
+
import type { DshSessionEvent, DshTokenUsage } from './types.js';
|
|
21
|
+
export declare class DshTranslator {
|
|
22
|
+
private liveUsage?;
|
|
23
|
+
private hasUsage;
|
|
24
|
+
/** Translate one session event into AgentEvents (may be empty). */
|
|
25
|
+
eventToAgentEvents(ev: DshSessionEvent, sessionId: string): AgentEvent[];
|
|
26
|
+
}
|
|
27
|
+
/** Map DSH TokenUsage → ResultEvent.usage (cacheWrite = cache creation). */
|
|
28
|
+
export declare function mapUsage(u: DshTokenUsage): NonNullable<ResultEvent['usage']>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DshTranslator — maps DSH SessionEvents to AgentEvents.
|
|
3
|
+
*
|
|
4
|
+
* Per-turn stateful: accumulates usage from assistant/message.usage records and
|
|
5
|
+
* converts assistant/chunk deltas to assistant text/thinking events.
|
|
6
|
+
*
|
|
7
|
+
* Mapping (task spec + DSH SessionEventMap):
|
|
8
|
+
* assistant/chunk {chunk:{type:'text-delta',text}} → assistant text (incremental)
|
|
9
|
+
* assistant/chunk {chunk:{type:'reasoning-delta',text}} → assistant thinking (incremental)
|
|
10
|
+
* assistant/message {usage?} → records usage (no event)
|
|
11
|
+
* turn/end {reason.kind} → result:
|
|
12
|
+
* completed → success; aborted/interrupted → interrupted;
|
|
13
|
+
* blocked/error/max-tokens → error (errorMessage carries the reason)
|
|
14
|
+
*
|
|
15
|
+
* DSH TokenUsage: {inputTokens, outputTokens, cacheReadTokens?, cacheWriteTokens?}
|
|
16
|
+
* → ResultEvent.usage: input_tokens/output_tokens/cache_read_tokens/
|
|
17
|
+
* cache_creation_tokens (cacheWrite = cache creation) + total_tokens sum.
|
|
18
|
+
*/
|
|
19
|
+
export class DshTranslator {
|
|
20
|
+
liveUsage;
|
|
21
|
+
hasUsage = false;
|
|
22
|
+
/** Translate one session event into AgentEvents (may be empty). */
|
|
23
|
+
eventToAgentEvents(ev, sessionId) {
|
|
24
|
+
switch (ev.type) {
|
|
25
|
+
case 'assistant/chunk': {
|
|
26
|
+
const chunk = ev.data.chunk;
|
|
27
|
+
if (!chunk)
|
|
28
|
+
return [];
|
|
29
|
+
const ts = new Date(ev.time).toISOString();
|
|
30
|
+
if (chunk.type === 'text-delta' && chunk.text) {
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
type: 'assistant',
|
|
34
|
+
message: { content: [{ type: 'text', text: chunk.text }] },
|
|
35
|
+
timestamp: ts,
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
if (chunk.type === 'reasoning-delta' && chunk.text) {
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
type: 'assistant',
|
|
43
|
+
message: { content: [{ type: 'thinking', thinking: chunk.text }] },
|
|
44
|
+
timestamp: ts,
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
case 'assistant/message': {
|
|
51
|
+
const raw = ev.data.usage;
|
|
52
|
+
if (raw && typeof raw === 'object') {
|
|
53
|
+
this.liveUsage = raw;
|
|
54
|
+
this.hasUsage = true;
|
|
55
|
+
}
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
case 'turn/end': {
|
|
59
|
+
const reason = ev.data.reason?.kind;
|
|
60
|
+
const subtype = reason === 'completed'
|
|
61
|
+
? 'success'
|
|
62
|
+
: reason === 'aborted' || reason === 'interrupted'
|
|
63
|
+
? 'interrupted'
|
|
64
|
+
: 'error';
|
|
65
|
+
const result = {
|
|
66
|
+
type: 'result',
|
|
67
|
+
subtype,
|
|
68
|
+
session_id: sessionId,
|
|
69
|
+
timestamp: new Date(ev.time).toISOString(),
|
|
70
|
+
};
|
|
71
|
+
if (this.hasUsage && this.liveUsage) {
|
|
72
|
+
result.usage = mapUsage(this.liveUsage);
|
|
73
|
+
}
|
|
74
|
+
if (subtype === 'error') {
|
|
75
|
+
result.errorMessage = `turn ended with reason: ${reason}`;
|
|
76
|
+
}
|
|
77
|
+
return [result];
|
|
78
|
+
}
|
|
79
|
+
default:
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Map DSH TokenUsage → ResultEvent.usage (cacheWrite = cache creation). */
|
|
85
|
+
export function mapUsage(u) {
|
|
86
|
+
const input = u.inputTokens ?? 0;
|
|
87
|
+
const output = u.outputTokens ?? 0;
|
|
88
|
+
const cacheRead = u.cacheReadTokens ?? 0;
|
|
89
|
+
const cacheWrite = u.cacheWriteTokens ?? 0;
|
|
90
|
+
return {
|
|
91
|
+
input_tokens: input,
|
|
92
|
+
output_tokens: output,
|
|
93
|
+
cache_read_tokens: cacheRead,
|
|
94
|
+
cache_creation_tokens: cacheWrite,
|
|
95
|
+
total_tokens: input + output + cacheRead + cacheWrite,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=translator.js.map
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH wire type shapes (DeepSeek Harness HTTP proxy contract).
|
|
3
|
+
*
|
|
4
|
+
* Source of truth: packages/host/apiproxy/src/api/{rpc,sessions,events}.ts and
|
|
5
|
+
* packages/core/session/src/types.ts in the deepseek-harness repo. Only the
|
|
6
|
+
* fields DshClient/DshRunner/DshSessionReader consume are declared here.
|
|
7
|
+
*
|
|
8
|
+
* Unary: POST /api/<method>, body `{type:'client-request',rpcId,method,payload}`,
|
|
9
|
+
* response `{type:'server-response',rpcId,result:{ok:true,value}|{ok:false,error}}`.
|
|
10
|
+
* Stream: SSE GET /api/events.mux, frames `data: {type:'server-request',rpcId,
|
|
11
|
+
* method:<frame.type>, payload:<MuxFrame>}\n\n`.
|
|
12
|
+
*/
|
|
13
|
+
/** One SessionEvent entry (core SessionEvent<T> — seq/time/data discriminated on type). */
|
|
14
|
+
export interface DshSessionEvent {
|
|
15
|
+
type: string;
|
|
16
|
+
seq: number;
|
|
17
|
+
time: number;
|
|
18
|
+
data: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
/** assistant/chunk chunk payload (StreamChunk). */
|
|
21
|
+
export interface DshChunk {
|
|
22
|
+
type: string;
|
|
23
|
+
index?: number;
|
|
24
|
+
text?: string;
|
|
25
|
+
}
|
|
26
|
+
/** TokenUsage from assistant/message.usage (dsh-llm TokenUsage). */
|
|
27
|
+
export interface DshTokenUsage {
|
|
28
|
+
inputTokens: number;
|
|
29
|
+
outputTokens: number;
|
|
30
|
+
cacheReadTokens?: number;
|
|
31
|
+
cacheWriteTokens?: number;
|
|
32
|
+
reasoningTokens?: number;
|
|
33
|
+
}
|
|
34
|
+
/** SessionSummary row from session.list items[]. */
|
|
35
|
+
export interface DshSessionSummary {
|
|
36
|
+
sessionId: string;
|
|
37
|
+
updatedAt: number;
|
|
38
|
+
running: boolean;
|
|
39
|
+
blank: boolean;
|
|
40
|
+
cwd?: string;
|
|
41
|
+
/** 创建时固定的 preset(/resume preset 一致性校验用)。 */
|
|
42
|
+
agentPreset?: string;
|
|
43
|
+
}
|
|
44
|
+
/** session.list → value. */
|
|
45
|
+
export interface DshSessionListValue {
|
|
46
|
+
items: DshSessionSummary[];
|
|
47
|
+
}
|
|
48
|
+
/** session.history → value.events[].event. */
|
|
49
|
+
export interface DshHistoryEntry {
|
|
50
|
+
event: DshSessionEvent;
|
|
51
|
+
view?: unknown;
|
|
52
|
+
}
|
|
53
|
+
/** session.history → value. */
|
|
54
|
+
export interface DshSessionHistoryValue {
|
|
55
|
+
events: DshHistoryEntry[];
|
|
56
|
+
hasMore: boolean;
|
|
57
|
+
}
|
|
58
|
+
/** One model entry inside a provider group (llm.models / session.models groups[].models[]). */
|
|
59
|
+
export interface DshCatalogModel {
|
|
60
|
+
id: string;
|
|
61
|
+
name: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
reasoning?: {
|
|
64
|
+
efforts: Array<{
|
|
65
|
+
id: string;
|
|
66
|
+
name: string;
|
|
67
|
+
}>;
|
|
68
|
+
defaultEffort?: string;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** One provider group of the model catalog (llm.models groups[]). */
|
|
72
|
+
export interface DshModelGroup {
|
|
73
|
+
id: string;
|
|
74
|
+
name: string;
|
|
75
|
+
models: DshCatalogModel[];
|
|
76
|
+
}
|
|
77
|
+
/** llm.models → value. */
|
|
78
|
+
export interface DshModelCatalogValue {
|
|
79
|
+
groups: DshModelGroup[];
|
|
80
|
+
failures?: Array<{
|
|
81
|
+
id: string;
|
|
82
|
+
name: string;
|
|
83
|
+
message: string;
|
|
84
|
+
}>;
|
|
85
|
+
}
|
|
86
|
+
/** Current model selection (session.models current / session.selectModel selected). */
|
|
87
|
+
export interface DshModelSelection {
|
|
88
|
+
provider: string;
|
|
89
|
+
model: string;
|
|
90
|
+
reasoningEffort?: string;
|
|
91
|
+
}
|
|
92
|
+
/** session.models → value. */
|
|
93
|
+
export interface DshSessionModelsValue {
|
|
94
|
+
current: DshModelSelection;
|
|
95
|
+
routable: boolean;
|
|
96
|
+
groups: DshModelGroup[];
|
|
97
|
+
failures?: Array<{
|
|
98
|
+
id: string;
|
|
99
|
+
name: string;
|
|
100
|
+
message: string;
|
|
101
|
+
}>;
|
|
102
|
+
}
|
|
103
|
+
/** One preset row of agentPreset.list. */
|
|
104
|
+
export interface DshPresetEntry {
|
|
105
|
+
id: string;
|
|
106
|
+
trust: 'system' | 'user';
|
|
107
|
+
isDefault: boolean;
|
|
108
|
+
name?: string;
|
|
109
|
+
description?: string;
|
|
110
|
+
broken?: string;
|
|
111
|
+
}
|
|
112
|
+
/** agentPreset.list → value. */
|
|
113
|
+
export interface DshPresetListValue {
|
|
114
|
+
presets: DshPresetEntry[];
|
|
115
|
+
authorable: boolean;
|
|
116
|
+
hasDocument: boolean;
|
|
117
|
+
}
|
|
118
|
+
/** Mux frame payloads we care about (subset of MuxFrame). */
|
|
119
|
+
export type DshMuxFrame = {
|
|
120
|
+
type: 'session/event';
|
|
121
|
+
sessionId: string;
|
|
122
|
+
event: DshSessionEvent;
|
|
123
|
+
} | {
|
|
124
|
+
type: 'approval/requested';
|
|
125
|
+
sessionId: string;
|
|
126
|
+
approvalId: string;
|
|
127
|
+
toolName: string;
|
|
128
|
+
callId?: string;
|
|
129
|
+
reason?: string;
|
|
130
|
+
} | {
|
|
131
|
+
type: 'stream/error';
|
|
132
|
+
error: {
|
|
133
|
+
code: string;
|
|
134
|
+
message: string;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
/** ServerRequest envelope (frame.method === frame.payload.type). */
|
|
138
|
+
export interface DshServerRequest {
|
|
139
|
+
type: 'server-request';
|
|
140
|
+
rpcId: string;
|
|
141
|
+
method: string;
|
|
142
|
+
payload: unknown;
|
|
143
|
+
}
|
|
144
|
+
/** Unified per-session subscription item: a session event or an approval notice. */
|
|
145
|
+
export type DshStreamItem = {
|
|
146
|
+
kind: 'event';
|
|
147
|
+
sessionId: string;
|
|
148
|
+
event: DshSessionEvent;
|
|
149
|
+
} | {
|
|
150
|
+
kind: 'approval';
|
|
151
|
+
sessionId: string;
|
|
152
|
+
approvalId: string;
|
|
153
|
+
toolName: string;
|
|
154
|
+
reason?: string;
|
|
155
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH wire type shapes (DeepSeek Harness HTTP proxy contract).
|
|
3
|
+
*
|
|
4
|
+
* Source of truth: packages/host/apiproxy/src/api/{rpc,sessions,events}.ts and
|
|
5
|
+
* packages/core/session/src/types.ts in the deepseek-harness repo. Only the
|
|
6
|
+
* fields DshClient/DshRunner/DshSessionReader consume are declared here.
|
|
7
|
+
*
|
|
8
|
+
* Unary: POST /api/<method>, body `{type:'client-request',rpcId,method,payload}`,
|
|
9
|
+
* response `{type:'server-response',rpcId,result:{ok:true,value}|{ok:false,error}}`.
|
|
10
|
+
* Stream: SSE GET /api/events.mux, frames `data: {type:'server-request',rpcId,
|
|
11
|
+
* method:<frame.type>, payload:<MuxFrame>}\n\n`.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=types.js.map
|
package/dist/runner/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { CodexAppServerRunner } from './codex/index.js';
|
|
|
12
12
|
export { OpencodeAcpRunner } from './opencode/index.js';
|
|
13
13
|
export { PiRpcRunner } from './pi/index.js';
|
|
14
14
|
export { KimiAcpRunner } from './kimi/index.js';
|
|
15
|
+
export { DshRunner } from './dsh/index.js';
|
|
15
16
|
export { BashProcessRunner, type BashRunner } from './bash/index.js';
|
|
16
17
|
export { AgentRegistry } from './registry.js';
|
|
17
18
|
export type { ResultEvent, PlanEvent, FileChangeEvent, AgentEvent, Runner, RunnerLifetime, AgentKind, AgentSession, AgentSessionContentEvent, AgentSessionUsage, SessionContent, AgentSessionReader, AgentRunner, TurnStartedEvent, TurnDiffEvent, ApprovalView, ApprovalRequestedEvent, ApprovalResolvedEvent, } from './types.js';
|
package/dist/runner/index.js
CHANGED
|
@@ -13,6 +13,7 @@ export { CodexAppServerRunner } from './codex/index.js';
|
|
|
13
13
|
export { OpencodeAcpRunner } from './opencode/index.js';
|
|
14
14
|
export { PiRpcRunner } from './pi/index.js';
|
|
15
15
|
export { KimiAcpRunner } from './kimi/index.js';
|
|
16
|
+
export { DshRunner } from './dsh/index.js';
|
|
16
17
|
export { BashProcessRunner } from './bash/index.js';
|
|
17
18
|
// Re-export registry
|
|
18
19
|
export { AgentRegistry } from './registry.js';
|
|
@@ -15,6 +15,7 @@ import { ConnectionManager as KimiAcpConnectionManager, } from '../../common/jso
|
|
|
15
15
|
import { KimiAcpTranslator } from './translator.js';
|
|
16
16
|
import { RpcErrorCode, ServerRequestMethod, } from '../../common/acp/protocol-types.js';
|
|
17
17
|
import { findOptionIdByKind } from '../../common/acp/protocol-helpers.js';
|
|
18
|
+
import { mapAnswersByIndex } from '../../question-common.js';
|
|
18
19
|
import { getLogger } from '../../../logger/index.js';
|
|
19
20
|
import { ConnectionBasedRunner } from '../../common/connection-based-runner.js';
|
|
20
21
|
/** R2: how long to wait for the background compaction record after the
|
|
@@ -92,6 +93,63 @@ function buildApprovalResponse(action, pending) {
|
|
|
92
93
|
}
|
|
93
94
|
return { outcome: { outcome: 'cancelled' } };
|
|
94
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* AskUserQuestion 回编(kind === 'question'):
|
|
98
|
+
* - elicitation form:answer → {action:'accept', content:{q0..qn}}(多选数组/
|
|
99
|
+
* 单选字符串,key 按问题顺序);decline → {action:'decline'};cancel →
|
|
100
|
+
* {action:'cancel'}。
|
|
101
|
+
* - request_permission 兜底桥:answer → 按 label 回显 q0_opt_{i} optionId;
|
|
102
|
+
* decline → q0_skip optionId(无则 cancelled);cancel → cancelled。
|
|
103
|
+
* 答案以 {问题文本: 值} 回传,先按问题顺序展开再协议编码。
|
|
104
|
+
*/
|
|
105
|
+
function buildQuestionResponse(action, pending, response) {
|
|
106
|
+
if (pending.proto === 'elicitation') {
|
|
107
|
+
if (action === 'answer') {
|
|
108
|
+
const answers = response.answers;
|
|
109
|
+
const questions = pending.view.questions ?? [];
|
|
110
|
+
const byIndex = mapAnswersByIndex(questions, answers ?? {});
|
|
111
|
+
const content = {};
|
|
112
|
+
questions.forEach((q, i) => {
|
|
113
|
+
const value = byIndex[i];
|
|
114
|
+
if (value === undefined)
|
|
115
|
+
return;
|
|
116
|
+
if (q.multiSelect) {
|
|
117
|
+
const values = Array.isArray(value) ? value : [value];
|
|
118
|
+
const cleaned = values.filter((v) => typeof v === 'string' && v.length > 0);
|
|
119
|
+
if (cleaned.length > 0)
|
|
120
|
+
content[`q${i}`] = cleaned;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const single = Array.isArray(value) ? value[0] : value;
|
|
124
|
+
if (typeof single === 'string' && single.length > 0)
|
|
125
|
+
content[`q${i}`] = single;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return { action: 'accept', content };
|
|
129
|
+
}
|
|
130
|
+
if (action === 'decline')
|
|
131
|
+
return { action: 'decline' };
|
|
132
|
+
return { action: 'cancel' };
|
|
133
|
+
}
|
|
134
|
+
// request_permission 兜底桥(单题单选)
|
|
135
|
+
if (action === 'answer') {
|
|
136
|
+
const answers = response.answers;
|
|
137
|
+
const first = pending.view.questions?.[0];
|
|
138
|
+
const value = first ? answers?.[first.question] : undefined;
|
|
139
|
+
const label = Array.isArray(value) ? value[0] : value;
|
|
140
|
+
const option = (pending.options ?? []).find((opt) => /^q\d+_opt_\d+$/.test(opt.optionId) && opt.name === label);
|
|
141
|
+
if (option)
|
|
142
|
+
return { outcome: { outcome: 'selected', optionId: option.optionId } };
|
|
143
|
+
return { outcome: { outcome: 'cancelled' } };
|
|
144
|
+
}
|
|
145
|
+
if (action === 'decline') {
|
|
146
|
+
const skip = (pending.options ?? []).find((opt) => /^q\d+_skip$/.test(opt.optionId));
|
|
147
|
+
if (skip)
|
|
148
|
+
return { outcome: { outcome: 'selected', optionId: skip.optionId } };
|
|
149
|
+
return { outcome: { outcome: 'cancelled' } };
|
|
150
|
+
}
|
|
151
|
+
return { outcome: { outcome: 'cancelled' } };
|
|
152
|
+
}
|
|
95
153
|
// =============================================================================
|
|
96
154
|
// Runner
|
|
97
155
|
// =============================================================================
|
|
@@ -122,6 +180,13 @@ export class KimiAcpRunner extends ConnectionBasedRunner {
|
|
|
122
180
|
clientCapabilities: {
|
|
123
181
|
fs: { readTextFile: false, writeTextFile: false },
|
|
124
182
|
terminal: false,
|
|
183
|
+
// AskUserQuestion 走 elicitation form(原生多题+多选;form 失败时
|
|
184
|
+
// kimi 服务端自动回退 request_permission 桥,客户端两者都处理)。
|
|
185
|
+
// 注意:ACP SDK 对 elicitation.form 的 zod schema 是对象
|
|
186
|
+
// (z.record(z.string(), z.any())),布尔 true 会被 kimi 服务端静默
|
|
187
|
+
// 丢弃 → elicitationForm=false → 多选回退成 request_permission 单选
|
|
188
|
+
// 桥(勾一个选项即提交)。必须用空对象 {}。
|
|
189
|
+
elicitation: { form: {} },
|
|
125
190
|
},
|
|
126
191
|
},
|
|
127
192
|
};
|
|
@@ -261,7 +326,9 @@ export class KimiAcpRunner extends ConnectionBasedRunner {
|
|
|
261
326
|
if (!client || !pending)
|
|
262
327
|
return;
|
|
263
328
|
const action = response?.action ?? 'decline';
|
|
264
|
-
const acpResponse =
|
|
329
|
+
const acpResponse = pending.kind === 'question'
|
|
330
|
+
? buildQuestionResponse(action, pending, response)
|
|
331
|
+
: buildApprovalResponse(action, pending);
|
|
265
332
|
client.respond(requestId, acpResponse);
|
|
266
333
|
this.pendingApprovals.delete(requestId);
|
|
267
334
|
getLogger().info(`[${this.logTag}] approval responded requestId=${requestId} action=${action}`);
|
|
@@ -438,6 +505,7 @@ export class KimiAcpRunner extends ConnectionBasedRunner {
|
|
|
438
505
|
kind: ev.kind,
|
|
439
506
|
view: ev.view,
|
|
440
507
|
options: params.options,
|
|
508
|
+
proto: method === ServerRequestMethod.ELICITATION_CREATE ? 'elicitation' : 'permission',
|
|
441
509
|
});
|
|
442
510
|
getLogger().info(`[${this.logTag}] approval requested requestId=${ev.requestId} kind=${ev.kind}`);
|
|
443
511
|
}
|