dsh-plugin-bridge 0.2.11 → 0.3.1
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/README.md +24 -12
- package/README.zh.md +24 -12
- package/docs/design.md +5 -3
- package/docs/guide.zh.md +16 -6
- package/docs/native-webui-feasibility.md +56 -28
- package/lib/api-rpc.d.ts +5 -2
- package/lib/api-rpc.js +10 -2
- package/lib/cli.js +12 -10
- package/lib/client-contract.d.ts +87 -0
- package/lib/client-contract.js +487 -0
- package/lib/client.d.ts +16 -0
- package/lib/client.js +1295 -0
- package/lib/client.js.map +1 -0
- package/lib/command.d.ts +10 -20
- package/lib/command.js +139 -18
- package/lib/dsh-alpha-host.d.ts +67 -0
- package/lib/dsh-alpha-host.js +276 -0
- package/lib/host.d.ts +177 -0
- package/lib/host.js +102 -0
- package/lib/index.d.ts +2 -3
- package/lib/index.js +6 -8
- package/lib/migrate.d.ts +17 -38
- package/lib/migrate.js +114 -71
- package/package.json +54 -4
- package/reports/dsh-0.1.2-alpha.2-compat-2026-08-31.md +64 -0
- package/reports/native-workbench-2026-08-25.md +30 -0
- package/reports/native-workbench-2026-08-25.raw.json +34 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSH v0.1.2 alpha typed-controller adapter.
|
|
3
|
+
*
|
|
4
|
+
* The alpha removed the legacy ApiProxy service. Keep that product change at
|
|
5
|
+
* this boundary: migration code continues to consume BridgeHost, while this
|
|
6
|
+
* adapter talks to the Host controllers structurally so the package can still
|
|
7
|
+
* be built against the current rc.2 SDK.
|
|
8
|
+
*/
|
|
9
|
+
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import { createApiProxyHost, probeApiProxy } from './api-rpc.js';
|
|
11
|
+
import { REQUIRED_BRIDGE_CAPABILITIES, } from './host.js';
|
|
12
|
+
import { RpcError } from './rpc.js';
|
|
13
|
+
const ALPHA_METHODS = {
|
|
14
|
+
'session.list': services => services.sessionController?.list,
|
|
15
|
+
'session.create': services => services.sessionController?.create,
|
|
16
|
+
'session.history': services => services.sessionController?.inspect,
|
|
17
|
+
'session.models': services => services.sessionController?.modelCatalog,
|
|
18
|
+
'session.selectModel': services => services.sessionController?.selectModel,
|
|
19
|
+
'session.prompt': services => services.sessionController?.prompt,
|
|
20
|
+
'session.cancel': services => services.sessionController?.cancel,
|
|
21
|
+
'session.rename': services => services.sessionController?.rename,
|
|
22
|
+
'workspace.list': services => services.workspaceRegistry?.list,
|
|
23
|
+
'workspace.archiveSession': services => services.workspaceController?.archiveSession,
|
|
24
|
+
'agentPreset.list': services => services.agentPresets?.remoteExportList,
|
|
25
|
+
'goal.create': services => services.goals?.remoteExportCreate,
|
|
26
|
+
'goal.pause': services => services.goals?.pause,
|
|
27
|
+
};
|
|
28
|
+
function service(ctx, name) {
|
|
29
|
+
try {
|
|
30
|
+
const getter = ctx.get;
|
|
31
|
+
if (typeof getter === 'function') {
|
|
32
|
+
const found = getter.call(ctx, name);
|
|
33
|
+
if (found !== undefined)
|
|
34
|
+
return found;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// Cordis returns undefined for an unavailable optional service; custom
|
|
39
|
+
// Context implementations may throw instead, so fall through to fixtures.
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
return ctx[name];
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function servicesOf(ctx) {
|
|
49
|
+
return {
|
|
50
|
+
apiProxy: service(ctx, 'apiProxy'),
|
|
51
|
+
sessionController: service(ctx, 'sessionController'),
|
|
52
|
+
workspaceRegistry: service(ctx, 'workspaceRegistry'),
|
|
53
|
+
workspaceController: service(ctx, 'workspaceController'),
|
|
54
|
+
agentPresets: service(ctx, 'agentPresets'),
|
|
55
|
+
goals: service(ctx, 'goals'),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function failureOf(error) {
|
|
59
|
+
const value = error;
|
|
60
|
+
return value?.failure ?? value?.rpc ?? {
|
|
61
|
+
code: value?.code,
|
|
62
|
+
message: value?.message,
|
|
63
|
+
details: value?.details,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async function invoke(method, operation) {
|
|
67
|
+
try {
|
|
68
|
+
return await operation();
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error instanceof RpcError)
|
|
72
|
+
throw error;
|
|
73
|
+
const failure = failureOf(error);
|
|
74
|
+
throw new RpcError(method, failure.code ?? 'internal', failure.message ?? (error instanceof Error ? error.message : String(error)), failure.details);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function required(method, value) {
|
|
78
|
+
if (value === undefined)
|
|
79
|
+
throw new RpcError(method, 'unavailable', `DSH alpha host 没有提供 ${method}`);
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
function projectionValues(row) {
|
|
83
|
+
const values = row?.projections?.values;
|
|
84
|
+
return values !== null && typeof values === 'object' ? values : {};
|
|
85
|
+
}
|
|
86
|
+
function sessionRow(row) {
|
|
87
|
+
const source = row;
|
|
88
|
+
const values = projectionValues(row);
|
|
89
|
+
const preset = typeof values.agentPreset === 'string'
|
|
90
|
+
? values.agentPreset
|
|
91
|
+
: typeof source.agentPreset === 'string' ? source.agentPreset : undefined;
|
|
92
|
+
return {
|
|
93
|
+
sessionId: String(source.sessionId ?? ''),
|
|
94
|
+
...(typeof source.running === 'boolean' ? { running: source.running } : {}),
|
|
95
|
+
...(typeof source.blank === 'boolean' ? { blank: source.blank } : {}),
|
|
96
|
+
...(typeof source.cwd === 'string' ? { cwd: source.cwd } : {}),
|
|
97
|
+
...(preset === undefined ? {} : { agentPreset: preset }),
|
|
98
|
+
...(typeof source.parentSessionId === 'string' ? { parentSessionId: source.parentSessionId } : {}),
|
|
99
|
+
...Object.keys(values).length === 0 ? {} : { projections: { values } },
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function currentModel(row) {
|
|
103
|
+
const selection = projectionValues(row).modelSelection;
|
|
104
|
+
if (selection === null || typeof selection !== 'object')
|
|
105
|
+
return undefined;
|
|
106
|
+
const record = selection;
|
|
107
|
+
const candidate = record.next ?? record.lastUsed;
|
|
108
|
+
if (candidate === null || typeof candidate !== 'object')
|
|
109
|
+
return undefined;
|
|
110
|
+
const model = candidate;
|
|
111
|
+
if (typeof model.provider !== 'string' || typeof model.model !== 'string')
|
|
112
|
+
return undefined;
|
|
113
|
+
return {
|
|
114
|
+
provider: model.provider,
|
|
115
|
+
model: model.model,
|
|
116
|
+
...(typeof model.reasoningEffort === 'string' ? { reasoningEffort: model.reasoningEffort } : {}),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function paginateHistory(events, beforeSeq, maxMessages) {
|
|
120
|
+
const visible = beforeSeq === undefined ? [...events] : events.filter(event => (event.seq ?? -1) < beforeSeq);
|
|
121
|
+
let count = 0;
|
|
122
|
+
let cut = 0;
|
|
123
|
+
for (let index = visible.length - 1; index >= 0; index -= 1) {
|
|
124
|
+
const type = visible[index]?.type;
|
|
125
|
+
if (type !== 'user/message' && type !== 'assistant/message')
|
|
126
|
+
continue;
|
|
127
|
+
count += 1;
|
|
128
|
+
if (count >= maxMessages) {
|
|
129
|
+
cut = index;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
events: visible.slice(cut).map(event => ({ event })),
|
|
135
|
+
hasMore: cut > 0,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
async function resolvedAgent(services, sessionId) {
|
|
139
|
+
const resolve = required('session.resolveAgent', services.sessionController?.resolveAgent);
|
|
140
|
+
const result = await invoke('session.resolveAgent', () => resolve.call(services.sessionController, sessionId));
|
|
141
|
+
if (result.error !== undefined) {
|
|
142
|
+
throw new RpcError('session.resolveAgent', result.error.code ?? 'internal', result.error.message ?? 'DSH alpha 无法恢复目标会话', result.error.details);
|
|
143
|
+
}
|
|
144
|
+
return required('session.resolveAgent', result.agent);
|
|
145
|
+
}
|
|
146
|
+
/** Probe the real alpha services instead of the adapter's always-present closures. */
|
|
147
|
+
export function probeDshAlphaHost(input) {
|
|
148
|
+
const services = servicesOf(input);
|
|
149
|
+
return REQUIRED_BRIDGE_CAPABILITIES.map(method => ({
|
|
150
|
+
method,
|
|
151
|
+
available: typeof ALPHA_METHODS[method]?.(services) === 'function',
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
/** Create the semantic BridgeHost over DSH v0.1.2 typed Host controllers. */
|
|
155
|
+
export function createDshAlphaHost(input, signal) {
|
|
156
|
+
const services = servicesOf(input);
|
|
157
|
+
const controller = services.sessionController;
|
|
158
|
+
const abort = signal ?? new AbortController().signal;
|
|
159
|
+
const host = {
|
|
160
|
+
descriptor: Object.freeze({ id: 'dsh-typed-controllers', version: '0.1.2-alpha', transport: 'in-process' }),
|
|
161
|
+
sessions: Object.freeze({
|
|
162
|
+
list: async (request = {}) => {
|
|
163
|
+
const list = required('session.list', controller?.list);
|
|
164
|
+
const value = await invoke('session.list', () => list.call(controller, request, abort));
|
|
165
|
+
return { items: (value.items ?? []).map(sessionRow) };
|
|
166
|
+
},
|
|
167
|
+
create: async (request) => {
|
|
168
|
+
const create = required('session.create', controller?.create);
|
|
169
|
+
return await invoke('session.create', () => create.call(controller, request));
|
|
170
|
+
},
|
|
171
|
+
history: async (request) => {
|
|
172
|
+
const inspect = required('session.history', controller?.inspect);
|
|
173
|
+
const value = await invoke('session.history', () => inspect.call(controller, request.sessionId, abort));
|
|
174
|
+
return paginateHistory(value.events, request.beforeSeq, request.maxMessages ?? 60);
|
|
175
|
+
},
|
|
176
|
+
models: async ({ sessionId }) => {
|
|
177
|
+
const list = required('session.list', controller?.list);
|
|
178
|
+
const catalog = required('session.models', controller?.modelCatalog);
|
|
179
|
+
const [listed, available] = await Promise.all([
|
|
180
|
+
invoke('session.list', () => list.call(controller, {}, abort)),
|
|
181
|
+
invoke('session.models', () => catalog.call(controller)),
|
|
182
|
+
]);
|
|
183
|
+
const row = (listed.items ?? []).find(item => item.sessionId === sessionId);
|
|
184
|
+
return {
|
|
185
|
+
...available,
|
|
186
|
+
current: currentModel(row) ?? available.default,
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
selectModel: request => {
|
|
190
|
+
const select = required('session.selectModel', controller?.selectModel);
|
|
191
|
+
return invoke('session.selectModel', () => select.call(controller, { ...request }));
|
|
192
|
+
},
|
|
193
|
+
prompt: request => {
|
|
194
|
+
const prompt = required('session.prompt', controller?.prompt);
|
|
195
|
+
return invoke('session.prompt', () => prompt.call(controller, {
|
|
196
|
+
...request,
|
|
197
|
+
requestId: `bridge-${randomUUID()}`,
|
|
198
|
+
}, abort));
|
|
199
|
+
},
|
|
200
|
+
cancel: request => {
|
|
201
|
+
const cancel = required('session.cancel', controller?.cancel);
|
|
202
|
+
return invoke('session.cancel', () => cancel.call(controller, request));
|
|
203
|
+
},
|
|
204
|
+
rename: request => {
|
|
205
|
+
const rename = required('session.rename', controller?.rename);
|
|
206
|
+
return invoke('session.rename', () => rename.call(controller, request));
|
|
207
|
+
},
|
|
208
|
+
attachment: request => {
|
|
209
|
+
const attachment = required('session.attachment', controller?.attachment);
|
|
210
|
+
return invoke('session.attachment', () => attachment.call(controller, request));
|
|
211
|
+
},
|
|
212
|
+
}),
|
|
213
|
+
workspaces: Object.freeze({
|
|
214
|
+
list: async () => {
|
|
215
|
+
const list = required('workspace.list', services.workspaceRegistry?.list);
|
|
216
|
+
const rows = await invoke('workspace.list', () => list.call(services.workspaceRegistry));
|
|
217
|
+
return {
|
|
218
|
+
items: rows.map((item) => {
|
|
219
|
+
const row = item;
|
|
220
|
+
return {
|
|
221
|
+
workspaceId: String(row.id ?? row.workspaceId ?? ''),
|
|
222
|
+
sessionIds: Array.isArray(row.sessionIds) ? row.sessionIds.map(String) : [],
|
|
223
|
+
};
|
|
224
|
+
}),
|
|
225
|
+
};
|
|
226
|
+
},
|
|
227
|
+
archiveSession: request => {
|
|
228
|
+
const archive = required('workspace.archiveSession', services.workspaceController?.archiveSession);
|
|
229
|
+
return invoke('workspace.archiveSession', () => archive.call(services.workspaceController, request));
|
|
230
|
+
},
|
|
231
|
+
}),
|
|
232
|
+
presets: Object.freeze({
|
|
233
|
+
list: async () => {
|
|
234
|
+
const list = required('agentPreset.list', services.agentPresets?.remoteExportList);
|
|
235
|
+
return await invoke('agentPreset.list', () => list.call(services.agentPresets));
|
|
236
|
+
},
|
|
237
|
+
}),
|
|
238
|
+
goals: Object.freeze({
|
|
239
|
+
create: async (request) => {
|
|
240
|
+
const agent = await resolvedAgent(services, request.sessionId);
|
|
241
|
+
const create = required('goal.create', services.goals?.remoteExportCreate);
|
|
242
|
+
return await invoke('goal.create', () => create.call(services.goals, agent, {
|
|
243
|
+
objective: request.objective,
|
|
244
|
+
maxGoalRounds: request.maxGoalRounds,
|
|
245
|
+
}));
|
|
246
|
+
},
|
|
247
|
+
pause: async (request) => {
|
|
248
|
+
const agent = await resolvedAgent(services, request.sessionId);
|
|
249
|
+
const pause = required('goal.pause', services.goals?.pause);
|
|
250
|
+
return await invoke('goal.pause', () => pause.call(services.goals, agent, request.ref));
|
|
251
|
+
},
|
|
252
|
+
clear: async (request) => {
|
|
253
|
+
const agent = await resolvedAgent(services, request.sessionId);
|
|
254
|
+
const clear = required('goal.clear', services.goals?.clear);
|
|
255
|
+
return await invoke('goal.clear', () => clear.call(services.goals, agent, request.ref));
|
|
256
|
+
},
|
|
257
|
+
}),
|
|
258
|
+
};
|
|
259
|
+
return Object.freeze(host);
|
|
260
|
+
}
|
|
261
|
+
/** Prefer the stable rc.2 path when present; otherwise use alpha controllers. */
|
|
262
|
+
export function resolveDshHost(input, signal) {
|
|
263
|
+
const services = servicesOf(input);
|
|
264
|
+
if (services.apiProxy !== undefined)
|
|
265
|
+
return createApiProxyHost(services.apiProxy, signal);
|
|
266
|
+
if (services.sessionController !== undefined)
|
|
267
|
+
return createDshAlphaHost(services, signal);
|
|
268
|
+
throw new RpcError('bridge.host', 'unavailable', '当前 DSH 没有可用的 apiProxy 或 typed sessionController');
|
|
269
|
+
}
|
|
270
|
+
/** Probe the active generation using its native surface. */
|
|
271
|
+
export function probeDshHost(input) {
|
|
272
|
+
const services = servicesOf(input);
|
|
273
|
+
if (services.apiProxy !== undefined)
|
|
274
|
+
return probeApiProxy(services.apiProxy);
|
|
275
|
+
return probeDshAlphaHost(services);
|
|
276
|
+
}
|
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.d.ts
CHANGED
|
@@ -2,9 +2,8 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
2
2
|
import Schema from '@deepseek-ai/schemastery';
|
|
3
3
|
export declare const name = "dsh-plugin-bridge";
|
|
4
4
|
/**
|
|
5
|
-
* `commands`
|
|
6
|
-
*
|
|
7
|
-
* 两者在官方 `web` profile 里都在(base 挂 commands,web-app 挂 api-gateway)。
|
|
5
|
+
* `commands` 是跨版本入口,唯一硬依赖。执行时优先使用 rc.2 的 `apiProxy`,
|
|
6
|
+
* alpha 则改走 typed controllers;doctor 探测实际选中的那一面并 fail closed。
|
|
8
7
|
*/
|
|
9
8
|
export declare const inject: string[];
|
|
10
9
|
export interface Config {
|
package/lib/index.js
CHANGED
|
@@ -13,16 +13,15 @@ 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';
|
|
17
16
|
import { createBridgeCommand } from './command.js';
|
|
18
17
|
import { SOURCE_CHAR_BUDGET, SUMMARY_CHAR_BUDGET } from './compression.js';
|
|
18
|
+
import { probeDshHost, resolveDshHost } from './dsh-alpha-host.js';
|
|
19
19
|
export const name = 'dsh-plugin-bridge';
|
|
20
20
|
/**
|
|
21
|
-
* `commands`
|
|
22
|
-
*
|
|
23
|
-
* 两者在官方 `web` profile 里都在(base 挂 commands,web-app 挂 api-gateway)。
|
|
21
|
+
* `commands` 是跨版本入口,唯一硬依赖。执行时优先使用 rc.2 的 `apiProxy`,
|
|
22
|
+
* alpha 则改走 typed controllers;doctor 探测实际选中的那一面并 fail closed。
|
|
24
23
|
*/
|
|
25
|
-
export const inject = ['commands'
|
|
24
|
+
export const inject = ['commands'];
|
|
26
25
|
/** 命令是同步返回的,等压缩工人不能等太久。 */
|
|
27
26
|
const DEFAULT_PREVIEW_TIMEOUT_MS = 180_000;
|
|
28
27
|
export const Config = Schema.object({
|
|
@@ -63,10 +62,9 @@ export function commandConfigOf(config = {}) {
|
|
|
63
62
|
};
|
|
64
63
|
}
|
|
65
64
|
export function apply(ctx, config = {}) {
|
|
66
|
-
const apiProxyOf = () => ctx.apiProxy;
|
|
67
65
|
const command = createBridgeCommand({
|
|
68
|
-
|
|
69
|
-
probe: () =>
|
|
66
|
+
hostFor: (signal) => resolveDshHost(ctx, signal),
|
|
67
|
+
probe: () => probeDshHost(ctx),
|
|
70
68
|
config: commandConfigOf(config),
|
|
71
69
|
writeSummary: writeSummaryFile,
|
|
72
70
|
readSummary: (path) => readFileSync(path, 'utf8'),
|