@forgeax/engine-intelligence-dsh 0.0.0-dev.8d955ade1c79

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/src/index.ts ADDED
@@ -0,0 +1,229 @@
1
+ import {
2
+ DeepSeekHarness,
3
+ type HarnessClientOptions,
4
+ type HarnessNotification,
5
+ } from '@deepseek-ai/dsh-sdk-client';
6
+ import type {
7
+ ActivityId,
8
+ ActivitySink,
9
+ ActivitySubmission,
10
+ IntelligenceError,
11
+ IntelligenceProvider,
12
+ } from '@forgeax/engine-intelligence';
13
+ import { IntelligenceError as IntelligenceErrorValue } from '@forgeax/engine-intelligence';
14
+ import { err, ok, type Result } from '@forgeax/engine-types';
15
+
16
+ export interface DshLaunchOptions {
17
+ readonly command: string;
18
+ readonly args?: readonly string[];
19
+ readonly cwd?: string;
20
+ readonly env?: Readonly<Record<string, string | undefined>>;
21
+ readonly requestTimeoutMs?: number;
22
+ readonly shutdownTimeoutMs?: number;
23
+ readonly disposeEofGraceMs?: number;
24
+ readonly disposeGraceMs?: number;
25
+ }
26
+
27
+ export interface DshIntelligenceProviderOptions {
28
+ readonly launch: DshLaunchOptions;
29
+ readonly cwd?: string;
30
+ readonly provider?: string;
31
+ readonly model?: string;
32
+ readonly maxTokens?: number;
33
+ /** Test/integration seam; production uses the pinned SDK client. */
34
+ readonly createHarness?: (options: DshHarnessOptions) => DshHarness;
35
+ }
36
+
37
+ export interface DshHarnessOptions {
38
+ readonly launch: DshLaunchOptions;
39
+ readonly cwd?: string;
40
+ readonly provider?: string;
41
+ readonly model?: string;
42
+ readonly maxTokens?: number;
43
+ }
44
+
45
+ export interface DshRunResult {
46
+ readonly finalResponse: string;
47
+ }
48
+
49
+ export interface DshHarness {
50
+ run(
51
+ input: string,
52
+ options: {
53
+ readonly sessionId: string;
54
+ readonly onNotification: (notification: DshNotification) => void;
55
+ },
56
+ ): Promise<DshRunResult>;
57
+ close(): Promise<void>;
58
+ }
59
+
60
+ export interface DshNotification {
61
+ readonly method: string;
62
+ readonly params: Record<string, unknown>;
63
+ }
64
+
65
+ interface DshActivity {
66
+ readonly harness: DshHarness;
67
+ readonly sink: ActivitySink;
68
+ task: Promise<void>;
69
+ cancelRequested: boolean;
70
+ closeTask?: Promise<void>;
71
+ }
72
+
73
+ function closeActivity(activity: DshActivity): Promise<void> {
74
+ if (activity.closeTask !== undefined) return activity.closeTask;
75
+ activity.closeTask = (async () => {
76
+ try {
77
+ await activity.harness.close();
78
+ } catch {
79
+ try {
80
+ await activity.harness.close();
81
+ } catch {
82
+ return;
83
+ }
84
+ }
85
+ })();
86
+ return activity.closeTask;
87
+ }
88
+
89
+ function textDelta(notification: DshNotification): string | undefined {
90
+ if (notification.method !== 'session.event') return undefined;
91
+ const event = notification.params.event;
92
+ if (typeof event !== 'object' || event === null || Array.isArray(event)) return undefined;
93
+ if (Reflect.get(event, 'type') !== 'assistant/chunk') return undefined;
94
+ const data = Reflect.get(event, 'data');
95
+ if (typeof data !== 'object' || data === null || Array.isArray(data)) return undefined;
96
+ const chunk = Reflect.get(data, 'chunk');
97
+ if (typeof chunk !== 'object' || chunk === null || Array.isArray(chunk)) return undefined;
98
+ if (Reflect.get(chunk, 'type') !== 'text-delta') return undefined;
99
+ const text = Reflect.get(chunk, 'text');
100
+ return typeof text === 'string' ? text : undefined;
101
+ }
102
+
103
+ function defaultHarness(options: DshHarnessOptions): DshHarness {
104
+ const launch = {
105
+ command: options.launch.command,
106
+ ...(options.launch.args === undefined ? {} : { args: [...options.launch.args] }),
107
+ ...(options.launch.env === undefined ? {} : { env: { ...options.launch.env } }),
108
+ ...(options.launch.cwd === undefined ? {} : { cwd: options.launch.cwd }),
109
+ ...(options.launch.requestTimeoutMs === undefined
110
+ ? {}
111
+ : { requestTimeoutMs: options.launch.requestTimeoutMs }),
112
+ ...(options.launch.shutdownTimeoutMs === undefined
113
+ ? {}
114
+ : { shutdownTimeoutMs: options.launch.shutdownTimeoutMs }),
115
+ ...(options.launch.disposeEofGraceMs === undefined
116
+ ? {}
117
+ : { disposeEofGraceMs: options.launch.disposeEofGraceMs }),
118
+ ...(options.launch.disposeGraceMs === undefined
119
+ ? {}
120
+ : { disposeGraceMs: options.launch.disposeGraceMs }),
121
+ } satisfies HarnessClientOptions;
122
+ return new DeepSeekHarness({
123
+ launch,
124
+ ...(options.cwd === undefined ? {} : { cwd: options.cwd }),
125
+ ...(options.provider === undefined ? {} : { provider: options.provider }),
126
+ ...(options.model === undefined ? {} : { model: options.model }),
127
+ ...(options.maxTokens === undefined ? {} : { maxTokens: options.maxTokens }),
128
+ });
129
+ }
130
+
131
+ /**
132
+ * Create a Host-only DSH provider. Each Activity owns one runtime process.
133
+ * DSH 0.1 has process close but no wire-level turn cancel; process-per-Activity
134
+ * preserves honest cancellation without terminating unrelated work.
135
+ */
136
+ export function createDshIntelligenceProvider(
137
+ options: DshIntelligenceProviderOptions,
138
+ ): IntelligenceProvider {
139
+ const activities = new Map<ActivityId, DshActivity>();
140
+ const createHarness = options.createHarness ?? defaultHarness;
141
+ let closed = false;
142
+
143
+ const provider: IntelligenceProvider = {
144
+ id: 'deepseek-harness',
145
+ start(submission, sink): Result<void, IntelligenceError> {
146
+ if (closed) {
147
+ return err(new IntelligenceErrorValue({ code: 'intelligence-closed', detail: {} }));
148
+ }
149
+ let harness: DshHarness;
150
+ try {
151
+ harness = createHarness({
152
+ launch: options.launch,
153
+ ...(options.cwd === undefined ? {} : { cwd: options.cwd }),
154
+ ...(options.provider === undefined ? {} : { provider: options.provider }),
155
+ ...(options.model === undefined ? {} : { model: options.model }),
156
+ ...(options.maxTokens === undefined ? {} : { maxTokens: options.maxTokens }),
157
+ });
158
+ } catch (cause) {
159
+ return err(
160
+ new IntelligenceErrorValue({
161
+ code: 'intelligence-provider-failed',
162
+ detail: { providerId: provider.id, cause },
163
+ }),
164
+ );
165
+ }
166
+ const activity: DshActivity = {
167
+ harness,
168
+ sink,
169
+ task: Promise.resolve(),
170
+ cancelRequested: false,
171
+ };
172
+ activities.set(submission.id, activity);
173
+ activity.task = runActivity(submission, activity);
174
+ return ok(undefined);
175
+ },
176
+ cancel(id): Result<void, IntelligenceError> {
177
+ const activity = activities.get(id);
178
+ if (activity === undefined) {
179
+ return err(
180
+ new IntelligenceErrorValue({
181
+ code: 'intelligence-activity-not-found',
182
+ detail: { activityId: id },
183
+ }),
184
+ );
185
+ }
186
+ activity.cancelRequested = true;
187
+ void closeActivity(activity);
188
+ return ok(undefined);
189
+ },
190
+ async close(): Promise<void> {
191
+ if (closed) return;
192
+ closed = true;
193
+ const current = [...activities.values()];
194
+ for (const activity of current) {
195
+ activity.cancelRequested = true;
196
+ }
197
+ const closeTasks = current.map(closeActivity);
198
+ await Promise.allSettled(current.map((activity) => activity.task));
199
+ await Promise.allSettled(closeTasks);
200
+ activities.clear();
201
+ },
202
+ };
203
+
204
+ async function runActivity(submission: ActivitySubmission, activity: DshActivity): Promise<void> {
205
+ try {
206
+ const result = await activity.harness.run(submission.input, {
207
+ sessionId: submission.session.id,
208
+ onNotification(notification: HarnessNotification) {
209
+ if (activity.cancelRequested) return;
210
+ const delta = textDelta(notification);
211
+ if (delta !== undefined) activity.sink.text(delta);
212
+ },
213
+ });
214
+ await closeActivity(activity);
215
+ if (activity.cancelRequested) activity.sink.cancelled();
216
+ else activity.sink.complete(result.finalResponse);
217
+ } catch (cause) {
218
+ await closeActivity(activity);
219
+ if (activity.cancelRequested) activity.sink.cancelled();
220
+ else activity.sink.fail(cause);
221
+ } finally {
222
+ activities.delete(submission.id);
223
+ }
224
+ }
225
+
226
+ return provider;
227
+ }
228
+
229
+ export { textDelta as extractDshTextDelta };