dsh-plugin-bridge 0.2.10 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/host.d.ts ADDED
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Bridge 的宿主端口。
3
+ *
4
+ * 迁移核心只依赖这些语义能力;DSH HTTP、进程内 apiProxy,以及未来可能出现的
5
+ * dsh-std participant 都应在 adapter 中实现本接口。RPC 方法名与产品对象形状不应
6
+ * 再进入 migrate.ts。
7
+ */
8
+ import { RpcError, type Rpc } from './rpc.ts';
9
+ import type { ImageAttachmentRef, SessionEvent } from './types.ts';
10
+ export interface BridgeHostDescriptor {
11
+ /** adapter 的稳定标识,例如 dsh-api-proxy、dsh-http-api 或 dsh-std。 */
12
+ id: string;
13
+ version?: string;
14
+ transport?: string;
15
+ }
16
+ export interface SessionRow {
17
+ sessionId: string;
18
+ running?: boolean;
19
+ blank?: boolean;
20
+ cwd?: string;
21
+ agentPreset?: string;
22
+ parentSessionId?: string;
23
+ projections?: {
24
+ values?: Record<string, unknown>;
25
+ };
26
+ }
27
+ export interface PresetRow {
28
+ id: string;
29
+ trust?: 'system' | 'user';
30
+ isDefault?: boolean;
31
+ name?: string;
32
+ description?: string;
33
+ broken?: string;
34
+ }
35
+ export interface ModelSelection {
36
+ provider: string;
37
+ model: string;
38
+ reasoningEffort?: string;
39
+ }
40
+ export interface SessionModels {
41
+ current?: Partial<ModelSelection>;
42
+ groups?: {
43
+ id: string;
44
+ models?: {
45
+ id: string;
46
+ }[];
47
+ }[];
48
+ [key: string]: unknown;
49
+ }
50
+ export type PromptContent = {
51
+ type: 'text';
52
+ text: string;
53
+ } | {
54
+ type: 'image';
55
+ mediaType: ImageAttachmentRef['mediaType'];
56
+ data: string;
57
+ name?: string;
58
+ };
59
+ export interface BridgeHost {
60
+ readonly descriptor: BridgeHostDescriptor;
61
+ readonly sessions: {
62
+ list(input?: {
63
+ cursor?: string;
64
+ }): Promise<{
65
+ items?: SessionRow[];
66
+ nextCursor?: string;
67
+ }>;
68
+ create(input: {
69
+ workspaceId?: string;
70
+ cwd?: string;
71
+ agentPreset?: string;
72
+ }): Promise<{
73
+ sessionId: string;
74
+ agentPreset?: string;
75
+ }>;
76
+ history(input: {
77
+ sessionId: string;
78
+ maxMessages?: number;
79
+ beforeSeq?: number;
80
+ }, options?: {
81
+ timeoutMs?: number;
82
+ }): Promise<{
83
+ events?: {
84
+ event: SessionEvent;
85
+ }[];
86
+ hasMore?: boolean;
87
+ }>;
88
+ models(input: {
89
+ sessionId: string;
90
+ }): Promise<SessionModels>;
91
+ selectModel(input: {
92
+ sessionId: string;
93
+ } & ModelSelection): Promise<unknown>;
94
+ prompt(input: {
95
+ sessionId: string;
96
+ mode: 'queue';
97
+ content: readonly PromptContent[];
98
+ }): Promise<unknown>;
99
+ cancel(input: {
100
+ sessionId: string;
101
+ }): Promise<unknown>;
102
+ rename(input: {
103
+ sessionId: string;
104
+ title: string;
105
+ }): Promise<unknown>;
106
+ attachment?(input: {
107
+ sessionId: string;
108
+ attachmentId: string;
109
+ }): Promise<{
110
+ attachment?: ImageAttachmentRef;
111
+ data?: string;
112
+ }>;
113
+ };
114
+ readonly workspaces: {
115
+ list(): Promise<{
116
+ items?: {
117
+ workspaceId: string;
118
+ sessionIds?: string[];
119
+ }[];
120
+ }>;
121
+ archiveSession(input: {
122
+ sessionId: string;
123
+ }): Promise<unknown>;
124
+ };
125
+ readonly presets: {
126
+ list(): Promise<{
127
+ presets?: PresetRow[];
128
+ }>;
129
+ };
130
+ readonly goals: {
131
+ create(input: {
132
+ sessionId: string;
133
+ objective: string;
134
+ maxGoalRounds: number;
135
+ }): Promise<{
136
+ ref: {
137
+ id: string;
138
+ revision: number;
139
+ };
140
+ }>;
141
+ pause(input: {
142
+ sessionId: string;
143
+ ref: {
144
+ id: string;
145
+ revision: number;
146
+ };
147
+ }): Promise<unknown>;
148
+ clear?(input: {
149
+ sessionId: string;
150
+ ref: {
151
+ id: string;
152
+ revision: number;
153
+ };
154
+ }): Promise<unknown>;
155
+ };
156
+ }
157
+ /** 0.2.x 兼容入口:现有调用者仍可传入旧的字符串 Rpc。 */
158
+ export type BridgeHostInput = BridgeHost | Rpc;
159
+ export declare const REQUIRED_BRIDGE_CAPABILITIES: readonly ["session.list", "session.create", "session.history", "session.models", "session.selectModel", "session.prompt", "session.cancel", "session.rename", "workspace.list", "workspace.archiveSession", "agentPreset.list", "goal.create", "goal.pause"];
160
+ export declare const OPTIONAL_BRIDGE_CAPABILITIES: readonly ["session.attachment", "goal.clear"];
161
+ export interface BridgeHostProbe {
162
+ method: string;
163
+ available: boolean;
164
+ }
165
+ /** 只检查端口形状,不执行任何宿主操作。 */
166
+ export declare function probeBridgeHost(host: BridgeHost | undefined): BridgeHostProbe[];
167
+ /**
168
+ * 把现有 DSH 字符串 RPC 包成语义端口。
169
+ *
170
+ * 这是兼容 adapter,不是迁移核心的一部分;未来的 dsh-std adapter 可以直接实现
171
+ * BridgeHost,而不需要复刻这些 DSH 路由名。
172
+ */
173
+ export declare function createBridgeHostFromRpc(rpc: Rpc, descriptor?: BridgeHostDescriptor): BridgeHost;
174
+ /** 将兼容输入规范化为 BridgeHost;核心入口统一调用此函数。 */
175
+ export declare function asBridgeHost(input: BridgeHostInput): BridgeHost;
176
+ /** 对缺失的可选能力给出与 RPC 错误一致的可分类失败。 */
177
+ export declare function missingHostCapability(capability: string): RpcError;
package/lib/host.js ADDED
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Bridge 的宿主端口。
3
+ *
4
+ * 迁移核心只依赖这些语义能力;DSH HTTP、进程内 apiProxy,以及未来可能出现的
5
+ * dsh-std participant 都应在 adapter 中实现本接口。RPC 方法名与产品对象形状不应
6
+ * 再进入 migrate.ts。
7
+ */
8
+ import { RpcError } from './rpc.js';
9
+ export const REQUIRED_BRIDGE_CAPABILITIES = Object.freeze([
10
+ 'session.list',
11
+ 'session.create',
12
+ 'session.history',
13
+ 'session.models',
14
+ 'session.selectModel',
15
+ 'session.prompt',
16
+ 'session.cancel',
17
+ 'session.rename',
18
+ 'workspace.list',
19
+ 'workspace.archiveSession',
20
+ 'agentPreset.list',
21
+ 'goal.create',
22
+ 'goal.pause',
23
+ ]);
24
+ export const OPTIONAL_BRIDGE_CAPABILITIES = Object.freeze([
25
+ 'session.attachment',
26
+ 'goal.clear',
27
+ ]);
28
+ const REQUIRED_ACCESSORS = {
29
+ 'session.list': host => host.sessions.list,
30
+ 'session.create': host => host.sessions.create,
31
+ 'session.history': host => host.sessions.history,
32
+ 'session.models': host => host.sessions.models,
33
+ 'session.selectModel': host => host.sessions.selectModel,
34
+ 'session.prompt': host => host.sessions.prompt,
35
+ 'session.cancel': host => host.sessions.cancel,
36
+ 'session.rename': host => host.sessions.rename,
37
+ 'workspace.list': host => host.workspaces.list,
38
+ 'workspace.archiveSession': host => host.workspaces.archiveSession,
39
+ 'agentPreset.list': host => host.presets.list,
40
+ 'goal.create': host => host.goals.create,
41
+ 'goal.pause': host => host.goals.pause,
42
+ };
43
+ /** 只检查端口形状,不执行任何宿主操作。 */
44
+ export function probeBridgeHost(host) {
45
+ return REQUIRED_BRIDGE_CAPABILITIES.map(method => {
46
+ let available = false;
47
+ try {
48
+ available = host !== undefined && typeof REQUIRED_ACCESSORS[method](host) === 'function';
49
+ }
50
+ catch {
51
+ // 外部 adapter 可能只交出部分端口;doctor 必须报告缺失,不能自己先崩。
52
+ }
53
+ return { method, available };
54
+ });
55
+ }
56
+ /**
57
+ * 把现有 DSH 字符串 RPC 包成语义端口。
58
+ *
59
+ * 这是兼容 adapter,不是迁移核心的一部分;未来的 dsh-std adapter 可以直接实现
60
+ * BridgeHost,而不需要复刻这些 DSH 路由名。
61
+ */
62
+ export function createBridgeHostFromRpc(rpc, descriptor = { id: 'dsh-rpc', transport: 'rpc' }) {
63
+ const host = {
64
+ descriptor: Object.freeze({ ...descriptor }),
65
+ sessions: Object.freeze({
66
+ list: (input = {}) => rpc('session.list', input),
67
+ create: input => rpc('session.create', input),
68
+ history: (input, options) => rpc('session.history', input, options?.timeoutMs),
69
+ models: input => rpc('session.models', input),
70
+ selectModel: input => rpc('session.selectModel', input),
71
+ prompt: input => rpc('session.prompt', input),
72
+ cancel: input => rpc('session.cancel', input),
73
+ rename: input => rpc('session.rename', input),
74
+ attachment: input => rpc('session.attachment', input),
75
+ }),
76
+ workspaces: Object.freeze({
77
+ list: () => rpc('workspace.list', {}),
78
+ archiveSession: input => rpc('workspace.archiveSession', input),
79
+ }),
80
+ presets: Object.freeze({
81
+ list: () => rpc('agentPreset.list', {}),
82
+ }),
83
+ goals: Object.freeze({
84
+ create: input => rpc('goal.create', input),
85
+ pause: input => rpc('goal.pause', input),
86
+ clear: input => rpc('goal.clear', input),
87
+ }),
88
+ };
89
+ return Object.freeze(host);
90
+ }
91
+ /** 将兼容输入规范化为 BridgeHost;核心入口统一调用此函数。 */
92
+ export function asBridgeHost(input) {
93
+ if (typeof input === 'function')
94
+ return createBridgeHostFromRpc(input);
95
+ if (input && typeof input === 'object' && typeof input.sessions?.list === 'function')
96
+ return input;
97
+ throw new RpcError('bridge.host', 'invalid-adapter', '宿主 adapter 没有实现 BridgeHost。');
98
+ }
99
+ /** 对缺失的可选能力给出与 RPC 错误一致的可分类失败。 */
100
+ export function missingHostCapability(capability) {
101
+ return new RpcError('bridge.host', 'unavailable', `宿主 adapter 没有提供 ${capability}`);
102
+ }
package/lib/index.js CHANGED
@@ -13,7 +13,7 @@ import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
13
13
  import { tmpdir } from 'node:os';
14
14
  import { join } from 'node:path';
15
15
  import Schema from '@deepseek-ai/schemastery';
16
- import { createApiProxyRpc, probeApiProxy } from './api-rpc.js';
16
+ import { createApiProxyHost, probeApiProxy } from './api-rpc.js';
17
17
  import { createBridgeCommand } from './command.js';
18
18
  import { SOURCE_CHAR_BUDGET, SUMMARY_CHAR_BUDGET } from './compression.js';
19
19
  export const name = 'dsh-plugin-bridge';
@@ -65,7 +65,7 @@ export function commandConfigOf(config = {}) {
65
65
  export function apply(ctx, config = {}) {
66
66
  const apiProxyOf = () => ctx.apiProxy;
67
67
  const command = createBridgeCommand({
68
- rpcFor: (signal) => createApiProxyRpc(apiProxyOf(), signal),
68
+ hostFor: (signal) => createApiProxyHost(apiProxyOf(), signal),
69
69
  probe: () => probeApiProxy(apiProxyOf()),
70
70
  config: commandConfigOf(config),
71
71
  writeSummary: writeSummaryFile,
package/lib/migrate.d.ts CHANGED
@@ -1,59 +1,36 @@
1
1
  /**
2
2
  * 迁移编排:取材 → 压缩工人 → 目标会话。
3
3
  *
4
- * 只依赖注入进来的 `Rpc`,不碰 process / argv / 文件系统,所以可以对着一个
5
- * 假的 RPC(见 test/migrate.test.mjs)跑完整条链路而不烧任何 token。
4
+ * 只依赖注入进来的 `BridgeHost`,不碰 process / argv / 文件系统,所以可以对着
5
+ * 任意宿主 adapter(见 test/migrate.test.mjs)跑完整条链路而不烧任何 token。
6
6
  * CLI(`cli.ts`)与客户端 GUI 都消费这里的函数,保证「被验证的」和
7
7
  * 「被交付的」是同一条代码路径。
8
8
  */
9
9
  import { type BridgeSource } from './compression.ts';
10
- import { type Rpc } from './rpc.ts';
10
+ import { type BridgeHostInput, type ModelSelection, type PresetRow, type SessionRow } from './host.ts';
11
11
  import type { ChatMessage } from './types.ts';
12
+ export type { ModelSelection, PresetRow, SessionRow } from './host.ts';
12
13
  export type ModelTier = 'flash' | 'current' | 'pro';
13
14
  export type InjectMode = 'goal' | 'prompt' | 'both';
14
15
  export type Lang = 'zh' | 'en' | 'auto';
15
- export interface SessionRow {
16
- sessionId: string;
17
- running?: boolean;
18
- blank?: boolean;
19
- cwd?: string;
20
- agentPreset?: string;
21
- parentSessionId?: string;
22
- projections?: {
23
- values?: Record<string, unknown>;
24
- };
25
- }
26
- export interface PresetRow {
27
- id: string;
28
- trust?: 'system' | 'user';
29
- isDefault?: boolean;
30
- name?: string;
31
- description?: string;
32
- broken?: string;
33
- }
34
16
  export interface ModelRoute {
35
17
  provider: string;
36
18
  model: string;
37
19
  /** 为什么选中它:configured / follow-session / tier:<tier> / fallback-current。 */
38
20
  reason: string;
39
21
  }
40
- export interface ModelSelection {
41
- provider: string;
42
- model: string;
43
- reasoningEffort?: string;
44
- }
45
22
  /**
46
23
  * 列出全部会话。
47
24
  *
48
25
  * `session.list` 的 v1 一次返回全部,`cursor` 是预留位;这里仍按 cursor 取完,
49
26
  * 免得上游哪天真的分页之后这里悄悄只看第一页。
50
27
  */
51
- export declare function listSessions(rpc: Rpc): Promise<SessionRow[]>;
52
- export declare function findSession(rpc: Rpc, sessionId: string): Promise<SessionRow | undefined>;
28
+ export declare function listSessions(input: BridgeHostInput): Promise<SessionRow[]>;
29
+ export declare function findSession(host: BridgeHostInput, sessionId: string): Promise<SessionRow | undefined>;
53
30
  /** 找出会话所属工作区;找不到就返回 undefined(调用方退回用 cwd 建会话)。 */
54
- export declare function findWorkspaceId(rpc: Rpc, sessionId: string): Promise<string | undefined>;
31
+ export declare function findWorkspaceId(input: BridgeHostInput, sessionId: string): Promise<string | undefined>;
55
32
  /** 可作为迁移目标的 preset(去掉 broken 的)。 */
56
- export declare function listPresets(rpc: Rpc): Promise<PresetRow[]>;
33
+ export declare function listPresets(input: BridgeHostInput): Promise<PresetRow[]>;
57
34
  /**
58
35
  * 选压缩工人的模型。
59
36
  *
@@ -61,38 +38,42 @@ export declare function listPresets(rpc: Rpc): Promise<PresetRow[]>;
61
38
  * 里挑。挑不到就退回源会话当前模型——档位是省钱偏好,不该成为换 provider 的人
62
39
  * 装不上的理由。
63
40
  */
64
- export declare function resolveWorkerModel(rpc: Rpc, sessionId: string, tier: ModelTier, override?: {
41
+ export declare function resolveWorkerModel(input: BridgeHostInput, sessionId: string, tier: ModelTier, override?: {
65
42
  provider?: string;
66
43
  model?: string;
67
44
  }): Promise<ModelRoute>;
68
45
  /** 压缩工人用哪个 preset:优先 minimal,其次 standard,再否则 host 默认。 */
69
- export declare function resolveWorkerPreset(rpc: Rpc): Promise<string | undefined>;
46
+ export declare function resolveWorkerPreset(input: BridgeHostInput): Promise<string | undefined>;
70
47
  export interface WaitOptions {
71
48
  timeoutMs?: number;
72
- /** 等「开始跑」的宽限期;超过还没 running 就当它已经跑完了。 */
49
+ /** 等待新 `turn/start` 的宽限期;超过仍未出现就按未启动处理。 */
73
50
  startGraceMs?: number;
74
51
  pollMs?: number;
52
+ /** 只观察这个事件序号之后的新一轮;worker 新建后通常为 0。 */
53
+ afterSeq?: number;
75
54
  }
76
55
  /**
77
- * 等一个会话回到空闲。
56
+ * 等一个会话的新一轮写入 `turn/end`。
78
57
  *
79
- * 旧实现是「先 sleep 2s,再看 running 是不是 false」——host 排队稍慢一点,
80
- * 第一次轮询就会把「还没开始」误判成「已经跑完」,取到的是上一轮的回答。
81
- * 这里先等它真的 running 起来(有宽限期),再等它落回 false。
58
+ * `session.list` 是全局列表,拿它每两秒轮询一个 worker 会把会话总量放大成
59
+ * O(会话数 × 轮询次数)。`session.history` 则只读目标会话;用 prompt 前的事件
60
+ * 水位隔开旧轮次后,`turn/start` / `turn/end` 也比易过期的 running 快照更可靠。
82
61
  */
83
- export declare function waitIdle(rpc: Rpc, sessionId: string, options?: WaitOptions): Promise<{
62
+ export declare function waitIdle(input: BridgeHostInput, sessionId: string, options?: WaitOptions): Promise<{
84
63
  idle: boolean;
85
64
  started: boolean;
86
65
  }>;
87
66
  /** 拉取并折叠会话历史(按需翻页)。 */
88
- export declare function foldedHistory(rpc: Rpc, sessionId: string, options?: {
67
+ export declare function foldedHistory(input: BridgeHostInput, sessionId: string, options?: {
89
68
  pageMessages?: number;
90
69
  maxPages?: number;
91
70
  }): Promise<ChatMessage[]>;
92
71
  /** 会话里最后一条非空 assistant 文本。 */
93
- export declare function lastAssistantText(rpc: Rpc, sessionId: string): Promise<string>;
72
+ export declare function lastAssistantText(host: BridgeHostInput, sessionId: string): Promise<string>;
94
73
  export interface PreviewOptions {
95
74
  sessionId: string;
75
+ /** 同一命令已经读取过的源会话行,避免重复扫描全局列表。 */
76
+ sourceSession?: SessionRow;
96
77
  tier?: ModelTier;
97
78
  provider?: string;
98
79
  model?: string;
@@ -122,9 +103,11 @@ export interface PreviewResult {
122
103
  sourceSession: SessionRow | undefined;
123
104
  }
124
105
  /** 生成交接摘要:取材 → 起临时工人 → 收摘要 → 归档工人。 */
125
- export declare function previewMigration(rpc: Rpc, options: PreviewOptions): Promise<PreviewResult>;
106
+ export declare function previewMigration(input: BridgeHostInput, options: PreviewOptions): Promise<PreviewResult>;
126
107
  export interface MigrateOptions {
127
108
  sessionId: string;
109
+ /** 同一流程已经读取过的源会话行,避免重复扫描全局列表。 */
110
+ sourceSession?: SessionRow;
128
111
  to: string;
129
112
  summary: string;
130
113
  lang?: 'zh' | 'en';
@@ -169,7 +152,7 @@ export interface MigrateResult {
169
152
  * 摘要能被模型看见依赖 goal-round-driver 把它渲染成 `<goal_round>` 提示,
170
153
  * 或模型主动调 `get_goal`。所以默认把摘要同时放进首轮提示:任何组装下都成立。
171
154
  */
172
- export declare function executeMigration(rpc: Rpc, options: MigrateOptions): Promise<MigrateResult>;
155
+ export declare function executeMigration(input: BridgeHostInput, options: MigrateOptions): Promise<MigrateResult>;
173
156
  /** 默认标题:让新会话在侧栏里一眼看得出来源。 */
174
157
  export declare function migratedTitle(sourceTitle: string | undefined, to: string): string;
175
158
  /** 从 session.list 行里尽力取出标题(projection 形状随部署而异)。 */