@forgeax/engine-intelligence 0.1.6 → 0.1.19
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 +27 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/index.mjs +98 -31
- package/dist/index.mjs.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/transport.d.ts +3 -0
- package/dist/transport.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/runtime.test.ts +711 -0
- package/src/__tests__/transport.test.ts +1435 -0
- package/src/runtime.ts +26 -8
- package/src/transport.ts +79 -23
package/src/runtime.ts
CHANGED
|
@@ -108,7 +108,13 @@ export class IntelligenceRuntime implements IntelligenceService {
|
|
|
108
108
|
terminal: false,
|
|
109
109
|
};
|
|
110
110
|
this.records.set(submission.id, record);
|
|
111
|
-
|
|
111
|
+
let started: Result<void, IntelligenceError>;
|
|
112
|
+
try {
|
|
113
|
+
started = this.provider.start(submission, this.createSink(record));
|
|
114
|
+
} catch (cause) {
|
|
115
|
+
this.records.delete(submission.id);
|
|
116
|
+
return err(providerError(this.providerId, cause));
|
|
117
|
+
}
|
|
112
118
|
if (!started.ok) {
|
|
113
119
|
this.records.delete(submission.id);
|
|
114
120
|
return started;
|
|
@@ -142,7 +148,11 @@ export class IntelligenceRuntime implements IntelligenceService {
|
|
|
142
148
|
}),
|
|
143
149
|
);
|
|
144
150
|
}
|
|
145
|
-
|
|
151
|
+
try {
|
|
152
|
+
return this.provider.cancel(activity);
|
|
153
|
+
} catch (cause) {
|
|
154
|
+
return err(providerError(this.providerId, cause));
|
|
155
|
+
}
|
|
146
156
|
}
|
|
147
157
|
|
|
148
158
|
close(): Promise<void> {
|
|
@@ -152,7 +162,11 @@ export class IntelligenceRuntime implements IntelligenceService {
|
|
|
152
162
|
|
|
153
163
|
private async performClose(): Promise<void> {
|
|
154
164
|
this.closed = true;
|
|
155
|
-
|
|
165
|
+
try {
|
|
166
|
+
await this.provider.close();
|
|
167
|
+
} catch {
|
|
168
|
+
// Disposal is terminal; provider failure must not strand the realm transport.
|
|
169
|
+
}
|
|
156
170
|
this.records.clear();
|
|
157
171
|
}
|
|
158
172
|
|
|
@@ -195,7 +209,7 @@ export class IntelligenceRuntime implements IntelligenceService {
|
|
|
195
209
|
private createSink(record: ActivityRecord): ActivitySink {
|
|
196
210
|
return {
|
|
197
211
|
text: (text) => {
|
|
198
|
-
if (record.terminal || text.length === 0) return;
|
|
212
|
+
if (this.closed || record.terminal || text.length === 0) return;
|
|
199
213
|
if (record.outputChars + text.length > this.limits.maxOutputChars) {
|
|
200
214
|
this.overflow(record, 'output-chars', this.limits.maxOutputChars);
|
|
201
215
|
return;
|
|
@@ -208,7 +222,7 @@ export class IntelligenceRuntime implements IntelligenceService {
|
|
|
208
222
|
this.push(record, { type: 'text-delta', text });
|
|
209
223
|
},
|
|
210
224
|
complete: (output) => {
|
|
211
|
-
if (record.terminal) return;
|
|
225
|
+
if (this.closed || record.terminal) return;
|
|
212
226
|
if (output.length > this.limits.maxOutputChars) {
|
|
213
227
|
this.overflow(record, 'output-chars', this.limits.maxOutputChars);
|
|
214
228
|
return;
|
|
@@ -217,13 +231,13 @@ export class IntelligenceRuntime implements IntelligenceService {
|
|
|
217
231
|
this.push(record, { type: 'completed', session: record.ref.session, output });
|
|
218
232
|
},
|
|
219
233
|
fail: (cause) => {
|
|
220
|
-
if (record.terminal) return;
|
|
234
|
+
if (this.closed || record.terminal) return;
|
|
221
235
|
record.terminal = true;
|
|
222
236
|
const error = providerError(this.providerId, cause);
|
|
223
237
|
this.push(record, { type: 'failed', error: intelligenceFailure(error) });
|
|
224
238
|
},
|
|
225
239
|
cancelled: () => {
|
|
226
|
-
if (record.terminal) return;
|
|
240
|
+
if (this.closed || record.terminal) return;
|
|
227
241
|
record.terminal = true;
|
|
228
242
|
this.push(record, { type: 'cancelled' });
|
|
229
243
|
},
|
|
@@ -243,7 +257,11 @@ export class IntelligenceRuntime implements IntelligenceService {
|
|
|
243
257
|
});
|
|
244
258
|
if (record.events.length >= this.limits.maxPendingEventsPerActivity) record.events.pop();
|
|
245
259
|
this.push(record, { type: 'failed', error: intelligenceFailure(error) });
|
|
246
|
-
|
|
260
|
+
try {
|
|
261
|
+
this.provider.cancel(record.ref.id);
|
|
262
|
+
} catch {
|
|
263
|
+
// The overflow terminal already owns the activity outcome.
|
|
264
|
+
}
|
|
247
265
|
}
|
|
248
266
|
|
|
249
267
|
private push(
|
package/src/transport.ts
CHANGED
|
@@ -52,9 +52,11 @@ export function bindIntelligencePort(
|
|
|
52
52
|
runtime: IntelligenceRuntime,
|
|
53
53
|
): IntelligencePortBinding {
|
|
54
54
|
let closed = false;
|
|
55
|
+
let closeTask: Promise<void> | undefined;
|
|
55
56
|
const listener = (
|
|
56
57
|
event: MessageEvent<IntelligenceHostCommand | IntelligenceRealmMessage>,
|
|
57
58
|
): void => {
|
|
59
|
+
if (closed) return;
|
|
58
60
|
const message = event.data;
|
|
59
61
|
if (message.kind === 'intelligence-submit') {
|
|
60
62
|
const accepted = runtime.accept(message.submission);
|
|
@@ -69,26 +71,37 @@ export function bindIntelligencePort(
|
|
|
69
71
|
return;
|
|
70
72
|
}
|
|
71
73
|
if (message.kind === 'intelligence-poll') {
|
|
72
|
-
|
|
74
|
+
const events = runtime.poll(message.maxEvents);
|
|
75
|
+
try {
|
|
76
|
+
port.postMessage({ kind: 'intelligence-events', events });
|
|
77
|
+
} catch {
|
|
78
|
+
// Poll is destructive; a failed response must terminate the Host binding
|
|
79
|
+
// so the drained events cannot remain attached to a live runtime.
|
|
80
|
+
void close().catch(() => undefined);
|
|
81
|
+
}
|
|
73
82
|
return;
|
|
74
83
|
}
|
|
75
84
|
if (message.kind === 'intelligence-cancel') {
|
|
76
85
|
runtime.cancel(message.activityId);
|
|
77
86
|
return;
|
|
78
87
|
}
|
|
79
|
-
if (message.kind === 'intelligence-close') void close(
|
|
88
|
+
if (message.kind === 'intelligence-close') void close();
|
|
80
89
|
};
|
|
81
|
-
const close =
|
|
82
|
-
if (
|
|
90
|
+
const close = (): Promise<void> => {
|
|
91
|
+
if (closeTask !== undefined) return closeTask;
|
|
83
92
|
closed = true;
|
|
84
93
|
port.removeEventListener('message', listener);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
closeTask = (async () => {
|
|
95
|
+
await runtime.close();
|
|
96
|
+
try {
|
|
97
|
+
port.postMessage({ kind: 'intelligence-closed' });
|
|
98
|
+
} catch {
|
|
99
|
+
// A failed terminal notification must not strand the physical transport.
|
|
100
|
+
}
|
|
101
|
+
await new Promise<void>((resolve) => setTimeout(resolve, 0));
|
|
90
102
|
port.close();
|
|
91
|
-
}
|
|
103
|
+
})();
|
|
104
|
+
return closeTask;
|
|
92
105
|
};
|
|
93
106
|
port.addEventListener('message', listener);
|
|
94
107
|
port.start?.();
|
|
@@ -145,6 +158,7 @@ export class IntelligencePortClient implements IntelligenceService {
|
|
|
145
158
|
private pollPending = false;
|
|
146
159
|
private closeTask: Promise<void> | undefined;
|
|
147
160
|
private closeResolve: (() => void) | undefined;
|
|
161
|
+
private transportReleased = false;
|
|
148
162
|
|
|
149
163
|
constructor(
|
|
150
164
|
readonly providerId: string,
|
|
@@ -157,6 +171,7 @@ export class IntelligencePortClient implements IntelligenceService {
|
|
|
157
171
|
this.createSessionId = options.createSessionId ?? (() => nextPortIdentity('session'));
|
|
158
172
|
this.listener = (event) => {
|
|
159
173
|
const message = event.data;
|
|
174
|
+
if (this.closed && message.kind !== 'intelligence-closed') return;
|
|
160
175
|
if (message.kind === 'intelligence-events') {
|
|
161
176
|
this.pollPending = false;
|
|
162
177
|
for (const item of message.events) {
|
|
@@ -167,19 +182,32 @@ export class IntelligencePortClient implements IntelligenceService {
|
|
|
167
182
|
this.active.delete(message.activityId);
|
|
168
183
|
this.rejected.set(message.activityId, message);
|
|
169
184
|
} else if (message.kind === 'intelligence-closed') {
|
|
170
|
-
this.
|
|
171
|
-
this.
|
|
172
|
-
this.active.clear();
|
|
173
|
-
this.rejected.clear();
|
|
174
|
-
this.port.close();
|
|
185
|
+
this.markClosed();
|
|
186
|
+
this.releaseTransport();
|
|
175
187
|
this.closeResolve?.();
|
|
176
188
|
this.closeResolve = undefined;
|
|
189
|
+
this.closeTask ??= Promise.resolve();
|
|
177
190
|
}
|
|
178
191
|
};
|
|
179
192
|
port.addEventListener('message', this.listener);
|
|
180
193
|
port.start?.();
|
|
181
194
|
}
|
|
182
195
|
|
|
196
|
+
private markClosed(): void {
|
|
197
|
+
this.closed = true;
|
|
198
|
+
this.received.length = 0;
|
|
199
|
+
this.active.clear();
|
|
200
|
+
this.rejected.clear();
|
|
201
|
+
this.pollPending = false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private releaseTransport(): void {
|
|
205
|
+
if (this.transportReleased) return;
|
|
206
|
+
this.transportReleased = true;
|
|
207
|
+
this.port.removeEventListener('message', this.listener);
|
|
208
|
+
this.port.close();
|
|
209
|
+
}
|
|
210
|
+
|
|
183
211
|
submit(request: ActivityRequest): Result<ActivityRef, IntelligenceError> {
|
|
184
212
|
if (this.closed) return err(new IntelligenceError({ code: 'intelligence-closed', detail: {} }));
|
|
185
213
|
if (request.input.length === 0 || request.input.length > this.limits.maxInputChars) {
|
|
@@ -220,10 +248,16 @@ export class IntelligencePortClient implements IntelligenceService {
|
|
|
220
248
|
session: request.session ?? { providerId: this.providerId, id: this.createSessionId() },
|
|
221
249
|
};
|
|
222
250
|
this.active.add(ref.id);
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
251
|
+
try {
|
|
252
|
+
this.port.postMessage({
|
|
253
|
+
kind: 'intelligence-submit',
|
|
254
|
+
submission: { ...ref, input: request.input },
|
|
255
|
+
});
|
|
256
|
+
} catch {
|
|
257
|
+
this.markClosed();
|
|
258
|
+
this.releaseTransport();
|
|
259
|
+
return err(new IntelligenceError({ code: 'intelligence-closed', detail: {} }));
|
|
260
|
+
}
|
|
227
261
|
return ok(ref);
|
|
228
262
|
}
|
|
229
263
|
|
|
@@ -243,7 +277,13 @@ export class IntelligencePortClient implements IntelligenceService {
|
|
|
243
277
|
}
|
|
244
278
|
if (!this.pollPending) {
|
|
245
279
|
this.pollPending = true;
|
|
246
|
-
|
|
280
|
+
try {
|
|
281
|
+
this.port.postMessage({ kind: 'intelligence-poll', maxEvents: count });
|
|
282
|
+
} catch {
|
|
283
|
+
this.markClosed();
|
|
284
|
+
this.releaseTransport();
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
247
287
|
}
|
|
248
288
|
return events;
|
|
249
289
|
}
|
|
@@ -258,16 +298,32 @@ export class IntelligencePortClient implements IntelligenceService {
|
|
|
258
298
|
}),
|
|
259
299
|
);
|
|
260
300
|
}
|
|
261
|
-
|
|
301
|
+
try {
|
|
302
|
+
this.port.postMessage({ kind: 'intelligence-cancel', activityId: id });
|
|
303
|
+
} catch {
|
|
304
|
+
this.markClosed();
|
|
305
|
+
this.releaseTransport();
|
|
306
|
+
return err(new IntelligenceError({ code: 'intelligence-closed', detail: {} }));
|
|
307
|
+
}
|
|
262
308
|
return ok(undefined);
|
|
263
309
|
}
|
|
264
310
|
|
|
265
311
|
close(): Promise<void> {
|
|
266
312
|
if (this.closeTask !== undefined) return this.closeTask;
|
|
267
|
-
this.closed
|
|
313
|
+
if (this.closed) {
|
|
314
|
+
this.closeTask = Promise.resolve();
|
|
315
|
+
return this.closeTask;
|
|
316
|
+
}
|
|
317
|
+
this.markClosed();
|
|
268
318
|
this.closeTask = new Promise((resolve) => {
|
|
269
319
|
this.closeResolve = resolve;
|
|
270
|
-
|
|
320
|
+
try {
|
|
321
|
+
this.port.postMessage({ kind: 'intelligence-close' });
|
|
322
|
+
} catch {
|
|
323
|
+
this.releaseTransport();
|
|
324
|
+
this.closeResolve = undefined;
|
|
325
|
+
resolve();
|
|
326
|
+
}
|
|
271
327
|
});
|
|
272
328
|
return this.closeTask;
|
|
273
329
|
}
|