@independo/inderun-web 0.3.0-dev.1 → 0.3.0-dev.10
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 +39 -0
- package/dist/core/browser-host.d.ts +29 -2
- package/dist/core/browser-host.d.ts.map +1 -1
- package/dist/core/browser-host.js +157 -0
- package/dist/core/engine.d.ts +37 -1
- package/dist/core/engine.d.ts.map +1 -1
- package/dist/core/engine.js +294 -25
- package/dist/core/event-gate.d.ts +37 -0
- package/dist/core/event-gate.d.ts.map +1 -0
- package/dist/core/event-gate.js +63 -0
- package/dist/core/generated/inderun-api.d.ts +5 -1
- package/dist/core/generated/inderun-api.d.ts.map +1 -1
- package/dist/core/host.d.ts +1 -1
- package/dist/core/host.d.ts.map +1 -1
- package/dist/core/provider.d.ts +81 -1
- package/dist/core/provider.d.ts.map +1 -1
- package/dist/core/route-planner.d.ts +22 -14
- package/dist/core/route-planner.d.ts.map +1 -1
- package/dist/core/route-planner.js +51 -24
- package/dist/core/router.d.ts +11 -15
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +32 -113
- package/dist/core/sse.d.ts +40 -0
- package/dist/core/sse.d.ts.map +1 -0
- package/dist/core/sse.js +118 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/providers/openai/provider.d.ts +44 -1
- package/dist/providers/openai/provider.d.ts.map +1 -1
- package/dist/providers/openai/provider.js +192 -30
- package/package.json +4 -4
package/dist/core/engine.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getTaskRequestValidationIssues } from "@independo/inderun-contracts";
|
|
2
2
|
import { ProviderRegistry } from "./registry.js";
|
|
3
3
|
import { Router } from "./router.js";
|
|
4
|
-
import { createInternal, toIndeRunException } from "./errors.js";
|
|
4
|
+
import { createCapabilityMismatch, createInternal, createUnavailable, toIndeRunException } from "./errors.js";
|
|
5
5
|
import { NoOpTelemetryService } from "./telemetry.js";
|
|
6
|
+
import { EventGate } from "./event-gate.js";
|
|
6
7
|
/**
|
|
7
8
|
* Main orchestrator SDK entrypoint class.
|
|
8
9
|
* Validates tasks, executes routing rules based on request constraints and connectivity,
|
|
@@ -26,6 +27,35 @@ export class IndeRun {
|
|
|
26
27
|
this.telemetryService =
|
|
27
28
|
telemetryService || this.hostServices.telemetry || new NoOpTelemetryService();
|
|
28
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Current time from the host clock, falling back to wall-clock time when the
|
|
32
|
+
* host does not supply one.
|
|
33
|
+
*/
|
|
34
|
+
now() {
|
|
35
|
+
return this.hostServices.clock ? this.hostServices.clock.now() : Date.now();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Emits `route_decided` for a completed route selection. Shared by `run()` and
|
|
39
|
+
* `stream()`: the payload is identical for both modes, and the interaction
|
|
40
|
+
* mode is already reflected in the plan the router returned.
|
|
41
|
+
*/
|
|
42
|
+
emitRouteDecided(runId, request, routeSelection, providerCount) {
|
|
43
|
+
this.safeEmit({
|
|
44
|
+
type: "route_decided",
|
|
45
|
+
runId,
|
|
46
|
+
timestamp: this.now(),
|
|
47
|
+
payload: {
|
|
48
|
+
selectedProviderId: routeSelection.routePlan.selectedProviderId,
|
|
49
|
+
fallbackProviderIds: routeSelection.routePlan.fallbackProviderIds,
|
|
50
|
+
rejectedProviders: routeSelection.routePlan.rejectedProviders,
|
|
51
|
+
fallbackAvailable: providerCount > 1,
|
|
52
|
+
taskKind: request.task.kind,
|
|
53
|
+
constraints: request.constraints ?? null,
|
|
54
|
+
preferences: request.preferences ?? null,
|
|
55
|
+
explanation: routeSelection.explanation
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
29
59
|
/**
|
|
30
60
|
* Safely emits a telemetry event by swallowing any internal logging/telemetry system errors.
|
|
31
61
|
* @param event - The telemetry event to emit.
|
|
@@ -71,7 +101,7 @@ export class IndeRun {
|
|
|
71
101
|
* @throws {IndeRunException} Standardized error indicating validation, connection, or provider failures.
|
|
72
102
|
*/
|
|
73
103
|
async run(request) {
|
|
74
|
-
const startTime = this.
|
|
104
|
+
const startTime = this.now();
|
|
75
105
|
const runId = request.requestId || `run_${Math.random().toString(36).substring(2, 11)}`;
|
|
76
106
|
try {
|
|
77
107
|
// 1. Validate request payload using schema contracts
|
|
@@ -86,26 +116,9 @@ export class IndeRun {
|
|
|
86
116
|
});
|
|
87
117
|
}
|
|
88
118
|
// 2. Select the route based on constraints and host capabilities
|
|
89
|
-
const routeSelection = await this.router.selectRoute(request, this.hostServices);
|
|
119
|
+
const routeSelection = await this.router.selectRoute(request, this.hostServices, "run");
|
|
90
120
|
const providers = [routeSelection.provider, ...routeSelection.fallbackProviders];
|
|
91
|
-
|
|
92
|
-
this.safeEmit({
|
|
93
|
-
type: "route_decided",
|
|
94
|
-
runId,
|
|
95
|
-
timestamp: this.hostServices.clock ? this.hostServices.clock.now() : Date.now(),
|
|
96
|
-
payload: {
|
|
97
|
-
selectedProviderId: routeSelection.routePlan.selectedProviderId,
|
|
98
|
-
fallbackProviderIds: routeSelection.routePlan.fallbackProviderIds,
|
|
99
|
-
rejectedProviders: routeSelection.routePlan.rejectedProviders,
|
|
100
|
-
fallbackAvailable: providers.length > 1,
|
|
101
|
-
taskKind: request.task.kind,
|
|
102
|
-
explanation: routeSelection.explanation,
|
|
103
|
-
constraints: request.constraints ?? null,
|
|
104
|
-
preferences: request.preferences ?? null,
|
|
105
|
-
plannerSource: routeSelection.plannerSource,
|
|
106
|
-
plannerUnavailableReason: routeSelection.plannerUnavailableReason ?? null
|
|
107
|
-
}
|
|
108
|
-
});
|
|
121
|
+
this.emitRouteDecided(runId, request, routeSelection, providers.length);
|
|
109
122
|
// 3. Execute the run task using the planned provider chain
|
|
110
123
|
const attemptedProviderIds = [];
|
|
111
124
|
let lastError;
|
|
@@ -117,7 +130,7 @@ export class IndeRun {
|
|
|
117
130
|
runId,
|
|
118
131
|
hostServices: this.hostServices
|
|
119
132
|
});
|
|
120
|
-
const endTime = this.
|
|
133
|
+
const endTime = this.now();
|
|
121
134
|
const totalMs = endTime - startTime;
|
|
122
135
|
result.runId = runId;
|
|
123
136
|
result.telemetry = {
|
|
@@ -150,7 +163,7 @@ export class IndeRun {
|
|
|
150
163
|
});
|
|
151
164
|
}
|
|
152
165
|
}
|
|
153
|
-
const endTime = this.
|
|
166
|
+
const endTime = this.now();
|
|
154
167
|
const totalMs = endTime - startTime;
|
|
155
168
|
const exception = toIndeRunException(lastError ?? new Error("No providers were attempted."), {
|
|
156
169
|
runId,
|
|
@@ -169,10 +182,10 @@ export class IndeRun {
|
|
|
169
182
|
details: {
|
|
170
183
|
totalMs: typeof error.details?.totalMs === "number"
|
|
171
184
|
? error.details?.totalMs
|
|
172
|
-
:
|
|
185
|
+
: this.now() - startTime
|
|
173
186
|
}
|
|
174
187
|
});
|
|
175
|
-
const endTime = this.
|
|
188
|
+
const endTime = this.now();
|
|
176
189
|
const totalMs = typeof exception.details?.totalMs === "number"
|
|
177
190
|
? exception.details?.totalMs
|
|
178
191
|
: endTime - startTime;
|
|
@@ -190,6 +203,262 @@ export class IndeRun {
|
|
|
190
203
|
throw exception;
|
|
191
204
|
}
|
|
192
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Orchestrates a Mode 2 streaming execution of a TaskRequest.
|
|
208
|
+
*
|
|
209
|
+
* Routes through the same planner as run(), asking it for a `stream` route: the
|
|
210
|
+
* planner rejects providers that do not declare `supports.streaming` or whose
|
|
211
|
+
* dynamic capability snapshot says they cannot stream here, and reports why. The
|
|
212
|
+
* whole returned chain is therefore mode-compatible and subject to the same
|
|
213
|
+
* privacy, locality, availability, and policy constraints as a Mode 1 route.
|
|
214
|
+
*
|
|
215
|
+
* Fallback to the next provider is allowed only before the first content event
|
|
216
|
+
* has been admitted for the run; once one has been delivered, the run is
|
|
217
|
+
* committed to that provider and any later provider failure becomes a terminal
|
|
218
|
+
* error outcome rather than a silent provider swap — partial output spliced from
|
|
219
|
+
* two providers is undetectable to the caller. Cancellation always forecloses
|
|
220
|
+
* fallback, regardless of commit state, and is idempotent.
|
|
221
|
+
* @param request - The canonical task request containing prompts, tasks, and constraints.
|
|
222
|
+
* @throws {IndeRunException} If validation fails or no eligible streaming provider exists.
|
|
223
|
+
*/
|
|
224
|
+
async stream(request) {
|
|
225
|
+
const startTime = this.now();
|
|
226
|
+
const now = () => this.now();
|
|
227
|
+
const runId = request.requestId || `run_${Math.random().toString(36).substring(2, 11)}`;
|
|
228
|
+
const validationIssues = getTaskRequestValidationIssues(request);
|
|
229
|
+
if (validationIssues.length > 0) {
|
|
230
|
+
const message = `Validation failed for TaskRequest: ${validationIssues
|
|
231
|
+
.map((i) => `${i.path} - ${i.message}`)
|
|
232
|
+
.join("; ")}`;
|
|
233
|
+
throw createInternal(message, { runId, details: { validationIssues } });
|
|
234
|
+
}
|
|
235
|
+
// Route selection failures are the one class of stream error that surfaces as a
|
|
236
|
+
// rejected promise instead of a terminal event, so they must still carry the
|
|
237
|
+
// runId — there is no handle for the caller to correlate them with otherwise.
|
|
238
|
+
let routeSelection;
|
|
239
|
+
try {
|
|
240
|
+
routeSelection = await this.router.selectRoute(request, this.hostServices, "stream");
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
throw toIndeRunException(error, { runId });
|
|
244
|
+
}
|
|
245
|
+
const providers = [routeSelection.provider, ...routeSelection.fallbackProviders];
|
|
246
|
+
this.emitRouteDecided(runId, request, routeSelection, providers.length);
|
|
247
|
+
// Defensive only: streaming eligibility is decided by the route planner, which
|
|
248
|
+
// throws with its own rejection reasons when nothing can stream. Reaching here
|
|
249
|
+
// means a planned provider was unregistered between planning and selection.
|
|
250
|
+
if (providers.length === 0) {
|
|
251
|
+
throw createCapabilityMismatch("No eligible provider supports streaming for this request.", {
|
|
252
|
+
runId,
|
|
253
|
+
details: { taskKind: request.task.kind }
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
const controller = new AbortController();
|
|
257
|
+
const gate = new EventGate(runId);
|
|
258
|
+
const hostServices = this.hostServices;
|
|
259
|
+
const safeEmit = this.safeEmit.bind(this);
|
|
260
|
+
const getStableMessage = this.getStableMessage.bind(this);
|
|
261
|
+
const handle = {
|
|
262
|
+
runId,
|
|
263
|
+
schemaVersion: "1.0",
|
|
264
|
+
startedAt: startTime,
|
|
265
|
+
providerId: providers[0].describe().id
|
|
266
|
+
};
|
|
267
|
+
async function* run() {
|
|
268
|
+
const attemptedProviderIds = [];
|
|
269
|
+
let committed = false;
|
|
270
|
+
const cancelledOutcome = (partialText) => {
|
|
271
|
+
const reason = typeof controller.signal.reason === "string" ? controller.signal.reason : undefined;
|
|
272
|
+
return {
|
|
273
|
+
outcome: "cancelled",
|
|
274
|
+
runId,
|
|
275
|
+
schemaVersion: "1.0",
|
|
276
|
+
partialText,
|
|
277
|
+
...(reason !== undefined ? { reason } : {})
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
for (const [index, provider] of providers.entries()) {
|
|
281
|
+
if (controller.signal.aborted) {
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
const providerId = provider.describe().id;
|
|
285
|
+
attemptedProviderIds.push(providerId);
|
|
286
|
+
safeEmit({
|
|
287
|
+
type: "stream_attempt_started",
|
|
288
|
+
runId,
|
|
289
|
+
timestamp: now(),
|
|
290
|
+
payload: { providerId, fallbackOccurred: index > 0 }
|
|
291
|
+
});
|
|
292
|
+
let partialText = "";
|
|
293
|
+
try {
|
|
294
|
+
const iterable = provider.stream(request, {
|
|
295
|
+
runId,
|
|
296
|
+
hostServices: hostServices,
|
|
297
|
+
signal: controller.signal
|
|
298
|
+
});
|
|
299
|
+
let terminatedInLoop = false;
|
|
300
|
+
for await (const ev of iterable) {
|
|
301
|
+
if (controller.signal.aborted) {
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
if (ev.kind === "error") {
|
|
305
|
+
throw ev.error;
|
|
306
|
+
}
|
|
307
|
+
if (ev.kind === "delta" || ev.kind === "snapshot") {
|
|
308
|
+
partialText = ev.kind === "delta" ? partialText + ev.text : ev.text;
|
|
309
|
+
if (!committed) {
|
|
310
|
+
committed = true;
|
|
311
|
+
safeEmit({
|
|
312
|
+
type: "stream_attempt_succeeded",
|
|
313
|
+
runId,
|
|
314
|
+
timestamp: now(),
|
|
315
|
+
payload: { providerId, attemptedProviderIds }
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
const streamEvent = gate.admit({
|
|
319
|
+
timestamp: now(),
|
|
320
|
+
type: ev.kind === "delta" ? "content_delta" : "content_snapshot",
|
|
321
|
+
payload: { text: ev.text }
|
|
322
|
+
});
|
|
323
|
+
if (streamEvent)
|
|
324
|
+
yield streamEvent;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
// ev.kind === "done"
|
|
328
|
+
const outcome = {
|
|
329
|
+
outcome: "completed",
|
|
330
|
+
runId,
|
|
331
|
+
schemaVersion: "1.0",
|
|
332
|
+
finalText: ev.finalText,
|
|
333
|
+
telemetry: { providerUsed: providerId, totalMs: now() - startTime },
|
|
334
|
+
...(ev.finishReason !== undefined ? { finishReason: ev.finishReason } : {}),
|
|
335
|
+
...(ev.usage !== undefined ? { usage: ev.usage } : {})
|
|
336
|
+
};
|
|
337
|
+
const terminal = gate.terminate(outcome, now());
|
|
338
|
+
safeEmit({
|
|
339
|
+
type: "stream_completed",
|
|
340
|
+
runId,
|
|
341
|
+
timestamp: now(),
|
|
342
|
+
payload: { providerId, durationMs: now() - startTime, attemptedProviderIds }
|
|
343
|
+
});
|
|
344
|
+
terminatedInLoop = true;
|
|
345
|
+
if (terminal)
|
|
346
|
+
yield terminal;
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (terminatedInLoop) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (controller.signal.aborted) {
|
|
353
|
+
const terminal = gate.terminate(cancelledOutcome(partialText), now());
|
|
354
|
+
safeEmit({
|
|
355
|
+
type: "stream_cancelled",
|
|
356
|
+
runId,
|
|
357
|
+
timestamp: now(),
|
|
358
|
+
payload: { providerId, attemptedProviderIds }
|
|
359
|
+
});
|
|
360
|
+
if (terminal)
|
|
361
|
+
yield terminal;
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
// Provider's iterable ended without a terminal "done"/"error" event.
|
|
365
|
+
throw new Error(`Provider '${providerId}' stream ended without a terminal event.`);
|
|
366
|
+
}
|
|
367
|
+
catch (err) {
|
|
368
|
+
if (controller.signal.aborted) {
|
|
369
|
+
const terminal = gate.terminate(cancelledOutcome(partialText), now());
|
|
370
|
+
safeEmit({
|
|
371
|
+
type: "stream_cancelled",
|
|
372
|
+
runId,
|
|
373
|
+
timestamp: now(),
|
|
374
|
+
payload: { providerId, attemptedProviderIds }
|
|
375
|
+
});
|
|
376
|
+
if (terminal)
|
|
377
|
+
yield terminal;
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const exception = toIndeRunException(err, {
|
|
381
|
+
providerId,
|
|
382
|
+
runId,
|
|
383
|
+
details: { attemptedProviderIds }
|
|
384
|
+
});
|
|
385
|
+
if (!committed) {
|
|
386
|
+
safeEmit({
|
|
387
|
+
type: "stream_attempt_failed",
|
|
388
|
+
runId,
|
|
389
|
+
timestamp: now(),
|
|
390
|
+
payload: {
|
|
391
|
+
providerId,
|
|
392
|
+
errorClass: exception.errorClass,
|
|
393
|
+
message: getStableMessage(exception.errorClass)
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
const outcome = {
|
|
399
|
+
outcome: "error",
|
|
400
|
+
runId,
|
|
401
|
+
schemaVersion: "1.0",
|
|
402
|
+
error: exception.toContractError(),
|
|
403
|
+
partialText
|
|
404
|
+
};
|
|
405
|
+
const terminal = gate.terminate(outcome, now());
|
|
406
|
+
safeEmit({
|
|
407
|
+
type: "stream_failed",
|
|
408
|
+
runId,
|
|
409
|
+
timestamp: now(),
|
|
410
|
+
payload: {
|
|
411
|
+
providerId,
|
|
412
|
+
errorClass: exception.errorClass,
|
|
413
|
+
message: getStableMessage(exception.errorClass),
|
|
414
|
+
attemptedProviderIds
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
if (terminal)
|
|
418
|
+
yield terminal;
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
// Every provider failed pre-commit, or cancellation landed before any attempt started.
|
|
423
|
+
if (controller.signal.aborted) {
|
|
424
|
+
const terminal = gate.terminate(cancelledOutcome(""), now());
|
|
425
|
+
safeEmit({
|
|
426
|
+
type: "stream_cancelled",
|
|
427
|
+
runId,
|
|
428
|
+
timestamp: now(),
|
|
429
|
+
payload: { attemptedProviderIds }
|
|
430
|
+
});
|
|
431
|
+
if (terminal)
|
|
432
|
+
yield terminal;
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const exception = createUnavailable("All eligible streaming providers failed.", {
|
|
436
|
+
runId,
|
|
437
|
+
details: { attemptedProviderIds }
|
|
438
|
+
});
|
|
439
|
+
const outcome = {
|
|
440
|
+
outcome: "error",
|
|
441
|
+
runId,
|
|
442
|
+
schemaVersion: "1.0",
|
|
443
|
+
error: exception.toContractError(),
|
|
444
|
+
partialText: ""
|
|
445
|
+
};
|
|
446
|
+
const terminal = gate.terminate(outcome, now());
|
|
447
|
+
safeEmit({
|
|
448
|
+
type: "stream_failed",
|
|
449
|
+
runId,
|
|
450
|
+
timestamp: now(),
|
|
451
|
+
payload: {
|
|
452
|
+
errorClass: exception.errorClass,
|
|
453
|
+
message: getStableMessage(exception.errorClass),
|
|
454
|
+
attemptedProviderIds
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
if (terminal)
|
|
458
|
+
yield terminal;
|
|
459
|
+
}
|
|
460
|
+
return { handle, events: run(), cancel: (reason) => controller.abort(reason) };
|
|
461
|
+
}
|
|
193
462
|
/**
|
|
194
463
|
* Reports each registered provider's static descriptor and current dynamic capability check,
|
|
195
464
|
* without executing a task. Useful for UI that shows live provider availability before a run.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { StreamEvent, StreamTerminalOutcome } from "@independo/inderun-contracts";
|
|
2
|
+
/**
|
|
3
|
+
* Per-run gatekeeper for Mode 2 streaming. Enforces the two structural guarantees
|
|
4
|
+
* required by the streaming orchestrator: exactly one terminal outcome is ever
|
|
5
|
+
* produced for a run, and no event is admitted after that terminal outcome fires.
|
|
6
|
+
*
|
|
7
|
+
* Correctness relies on `admit`/`terminate` performing their check-and-set
|
|
8
|
+
* synchronously with no `await` in between — safe under JS's single-threaded,
|
|
9
|
+
* run-to-completion semantics even with interleaved `await` resumption elsewhere
|
|
10
|
+
* (e.g. a provider's async iterator and a caller's `cancel()` racing).
|
|
11
|
+
*/
|
|
12
|
+
export declare class EventGate {
|
|
13
|
+
private readonly runId;
|
|
14
|
+
private sequence;
|
|
15
|
+
private terminated;
|
|
16
|
+
private terminalOutcome;
|
|
17
|
+
constructor(runId: string);
|
|
18
|
+
/**
|
|
19
|
+
* Admits a non-terminal StreamEvent, assigning it the next monotonic sequence
|
|
20
|
+
* number. Returns null (suppressing the event) if the run has already terminated.
|
|
21
|
+
*/
|
|
22
|
+
admit(event: {
|
|
23
|
+
timestamp: number;
|
|
24
|
+
type: string;
|
|
25
|
+
payload?: StreamEvent["payload"];
|
|
26
|
+
}): StreamEvent | null;
|
|
27
|
+
/**
|
|
28
|
+
* Terminates the run with the given outcome. First writer wins: only the first
|
|
29
|
+
* call produces a terminal StreamEvent and stores the outcome; every subsequent
|
|
30
|
+
* call (duplicate provider "done", late error after cancel, concurrent cancel()
|
|
31
|
+
* calls) is a silent no-op that returns null, making repeated cancellation safe.
|
|
32
|
+
*/
|
|
33
|
+
terminate(outcome: StreamTerminalOutcome, timestamp: number): StreamEvent | null;
|
|
34
|
+
isTerminated(): boolean;
|
|
35
|
+
getOutcome(): StreamTerminalOutcome | undefined;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=event-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-gate.d.ts","sourceRoot":"","sources":["../../src/core/event-gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAEvF;;;;;;;;;GASG;AACH,qBAAa,SAAS;IAKR,OAAO,CAAC,QAAQ,CAAC,KAAK;IAJlC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,eAAe,CAAoC;gBAE9B,KAAK,EAAE,MAAM;IAE1C;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE;QACX,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;KAClC,GAAG,WAAW,GAAG,IAAI;IActB;;;;;OAKG;IACH,SAAS,CAAC,OAAO,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAgBhF,YAAY,IAAI,OAAO;IAIvB,UAAU,IAAI,qBAAqB,GAAG,SAAS;CAGhD"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-run gatekeeper for Mode 2 streaming. Enforces the two structural guarantees
|
|
3
|
+
* required by the streaming orchestrator: exactly one terminal outcome is ever
|
|
4
|
+
* produced for a run, and no event is admitted after that terminal outcome fires.
|
|
5
|
+
*
|
|
6
|
+
* Correctness relies on `admit`/`terminate` performing their check-and-set
|
|
7
|
+
* synchronously with no `await` in between — safe under JS's single-threaded,
|
|
8
|
+
* run-to-completion semantics even with interleaved `await` resumption elsewhere
|
|
9
|
+
* (e.g. a provider's async iterator and a caller's `cancel()` racing).
|
|
10
|
+
*/
|
|
11
|
+
export class EventGate {
|
|
12
|
+
runId;
|
|
13
|
+
sequence = 0;
|
|
14
|
+
terminated = false;
|
|
15
|
+
terminalOutcome;
|
|
16
|
+
constructor(runId) {
|
|
17
|
+
this.runId = runId;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Admits a non-terminal StreamEvent, assigning it the next monotonic sequence
|
|
21
|
+
* number. Returns null (suppressing the event) if the run has already terminated.
|
|
22
|
+
*/
|
|
23
|
+
admit(event) {
|
|
24
|
+
if (this.terminated) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
runId: this.runId,
|
|
29
|
+
schemaVersion: "1.0",
|
|
30
|
+
sequence: this.sequence++,
|
|
31
|
+
timestamp: event.timestamp,
|
|
32
|
+
type: event.type,
|
|
33
|
+
...(event.payload !== undefined ? { payload: event.payload } : {})
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Terminates the run with the given outcome. First writer wins: only the first
|
|
38
|
+
* call produces a terminal StreamEvent and stores the outcome; every subsequent
|
|
39
|
+
* call (duplicate provider "done", late error after cancel, concurrent cancel()
|
|
40
|
+
* calls) is a silent no-op that returns null, making repeated cancellation safe.
|
|
41
|
+
*/
|
|
42
|
+
terminate(outcome, timestamp) {
|
|
43
|
+
if (this.terminated) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
this.terminated = true;
|
|
47
|
+
this.terminalOutcome = outcome;
|
|
48
|
+
return {
|
|
49
|
+
runId: this.runId,
|
|
50
|
+
schemaVersion: "1.0",
|
|
51
|
+
sequence: this.sequence++,
|
|
52
|
+
timestamp,
|
|
53
|
+
type: "terminal",
|
|
54
|
+
payload: outcome
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
isTerminated() {
|
|
58
|
+
return this.terminated;
|
|
59
|
+
}
|
|
60
|
+
getOutcome() {
|
|
61
|
+
return this.terminalOutcome;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { TaskRequest, TaskResult } from "@independo/inderun-contracts";
|
|
2
|
-
import type { ProviderCapabilitySnapshot } from "../provider.js";
|
|
2
|
+
import type { StreamRun, ProviderCapabilitySnapshot } from "../provider.js";
|
|
3
3
|
export interface IndeRunApi {
|
|
4
4
|
/**
|
|
5
5
|
* Executes a Mode 1 (request/response) text-to-text task against a routed provider and returns the completed TaskResult. Routing (local vs. cloud, which provider) is decided internally from TaskRequest.constraints/preferences — the caller does not select a provider directly. Rejects the request up front on validation failure; otherwise throws an IndeRunException (carrying an IndeRunError) if no eligible provider is found or every attempted provider fails. There is no partial or streaming result in this mode: run() either resolves with a complete TaskResult or throws.
|
|
6
6
|
*/
|
|
7
7
|
run(request: TaskRequest): Promise<TaskResult>;
|
|
8
|
+
/**
|
|
9
|
+
* Executes a Mode 2 (streaming) text-to-text task and returns a handle to the run, its canonical StreamEvent sequence, and a cancel hook. The requested interaction mode is a routing input rather than a filter applied after routing: providers that cannot stream are rejected during planning with a normalized reason, so a stream and a run over the same registry may legitimately resolve to different provider chains. Two failure surfaces exist deliberately — validation and route-selection failures reject this call, since there is no handle to correlate them with yet, while provider failure, cancellation, and completion all arrive as the single terminal StreamEvent in the returned sequence. Fallback is narrower than run()'s: a provider failure is only fallback-eligible before the first content event has been delivered; once one has, the run is committed to that provider and a later failure becomes a terminal error rather than a silent provider swap. Cancellation forecloses both at any commit state and always produces exactly one cancelled outcome.
|
|
10
|
+
*/
|
|
11
|
+
stream(request: TaskRequest): Promise<StreamRun>;
|
|
8
12
|
/**
|
|
9
13
|
* Returns a live snapshot of every registered provider's static descriptor and current dynamic availability, without executing a task or producing any side effects. Call this before run() when you need to show which providers/models are currently available — for example a settings screen or provider picker. Availability can change between calls (a local model can unload, cloud credentials can expire), so don't cache the result across a run() call.
|
|
10
14
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inderun-api.d.ts","sourceRoot":"","sources":["../../../src/core/generated/inderun-api.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"inderun-api.d.ts","sourceRoot":"","sources":["../../../src/core/generated/inderun-api.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,KAAK,EAAE,SAAS,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAE5E,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/C;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACjD;;OAEG;IACH,iBAAiB,IAAI,OAAO,CAAC,0BAA0B,EAAE,CAAC,CAAC;CAC5D"}
|
package/dist/core/host.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { ClockService, ConnectivityService, DeviceConstraintsService, HostServices, HttpClientService, HttpRequest, HttpResponse, SecureStorageService, TelemetryService, ThermalState } from "@independo/inderun-contracts";
|
|
1
|
+
export type { AbortSignalLike, ClockService, ConnectivityService, DeviceConstraintsService, HostServices, HttpClientService, HttpRequest, HttpResponse, HttpStreamResponse, HttpStreamingClientService, SecureStorageService, TelemetryService, ThermalState } from "@independo/inderun-contracts";
|
|
2
2
|
//# sourceMappingURL=host.d.ts.map
|
package/dist/core/host.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/core/host.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,YAAY,EACZ,mBAAmB,EACnB,wBAAwB,EACxB,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,EAChB,YAAY,EACb,MAAM,8BAA8B,CAAC"}
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/core/host.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,YAAY,EACZ,mBAAmB,EACnB,wBAAwB,EACxB,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,0BAA0B,EAC1B,oBAAoB,EACpB,gBAAgB,EAChB,YAAY,EACb,MAAM,8BAA8B,CAAC"}
|
package/dist/core/provider.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TaskRequest, TaskResult } from "@independo/inderun-contracts";
|
|
1
|
+
import type { StreamEvent, StreamRunHandle, TaskRequest, TaskResult } from "@independo/inderun-contracts";
|
|
2
2
|
import type { HostServices } from "./host.js";
|
|
3
3
|
/**
|
|
4
4
|
* Static metadata descriptor defining the capabilities, requirements, and constraints
|
|
@@ -82,6 +82,25 @@ export interface ProviderDynamicCapabilities {
|
|
|
82
82
|
* Detail message if availability check fails.
|
|
83
83
|
*/
|
|
84
84
|
reason?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Whether the provider can stream (Mode 2) right now on this host. Leave
|
|
87
|
+
* undefined to inherit the static `descriptor.supports.streaming` value —
|
|
88
|
+
* only set it when the runtime environment can take streaming away from a
|
|
89
|
+
* provider that otherwise declares it (e.g. the host has no chunked HTTP
|
|
90
|
+
* capability).
|
|
91
|
+
*/
|
|
92
|
+
streamingAvailable?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Detail message used when `streamingAvailable` is false. Omit to let the
|
|
95
|
+
* route planner synthesize a generic message.
|
|
96
|
+
*/
|
|
97
|
+
streamingUnavailableReason?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Whether cancellation is honored right now on this host. Leave undefined to
|
|
100
|
+
* inherit the static `descriptor.cancel` value (anything other than `"none"`
|
|
101
|
+
* means available).
|
|
102
|
+
*/
|
|
103
|
+
cancellationAvailable?: boolean;
|
|
85
104
|
}
|
|
86
105
|
/**
|
|
87
106
|
* One registered provider's identity, fixed capabilities, and live availability,
|
|
@@ -118,6 +137,48 @@ export interface RunContext {
|
|
|
118
137
|
*/
|
|
119
138
|
hostServices: HostServices;
|
|
120
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Execution context passed to provider stream commands. Extends RunContext with the
|
|
142
|
+
* caller-driven cancellation signal; the Engine core owns the AbortController and
|
|
143
|
+
* threads its signal here, so providers never need to construct their own.
|
|
144
|
+
*/
|
|
145
|
+
export interface ProviderStreamContext extends RunContext {
|
|
146
|
+
/**
|
|
147
|
+
* Signals caller-driven cancellation of this stream. Providers with `cancel: "hard"`
|
|
148
|
+
* should tear down their connection immediately on abort; `cancel: "soft"` providers
|
|
149
|
+
* may stop relaying events without interrupting underlying local work; `cancel:
|
|
150
|
+
* "none"` providers may ignore it entirely; the engine enforces the caller-visible
|
|
151
|
+
* cancellation guarantee at the consumer loop regardless of what the provider does.
|
|
152
|
+
*/
|
|
153
|
+
signal: AbortSignal;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* A raw, provider-shaped streaming event yielded by ProviderAdapter.stream(). This is
|
|
157
|
+
* intentionally distinct from the canonical StreamEvent contract: providers emit
|
|
158
|
+
* provider-shaped deltas here, and the Engine core's Event Gate normalizes these into
|
|
159
|
+
* StreamEvent/StreamTerminalOutcome, keeping provider-specific mechanics from leaking
|
|
160
|
+
* through the public API.
|
|
161
|
+
*/
|
|
162
|
+
export type ProviderStreamEvent = {
|
|
163
|
+
kind: "delta";
|
|
164
|
+
text: string;
|
|
165
|
+
} | {
|
|
166
|
+
kind: "snapshot";
|
|
167
|
+
text: string;
|
|
168
|
+
} | {
|
|
169
|
+
kind: "done";
|
|
170
|
+
finalText: string;
|
|
171
|
+
/**
|
|
172
|
+
* How generation ended, when the provider reports it. Mirrors
|
|
173
|
+
* `TaskResult.finishReason` for Mode 1 and surfaces on the `completed`
|
|
174
|
+
* terminal outcome; omit when the provider gives no signal.
|
|
175
|
+
*/
|
|
176
|
+
finishReason?: TaskResult["finishReason"];
|
|
177
|
+
usage?: TaskResult["usage"];
|
|
178
|
+
} | {
|
|
179
|
+
kind: "error";
|
|
180
|
+
error: unknown;
|
|
181
|
+
};
|
|
121
182
|
/**
|
|
122
183
|
* Pluggable execution adapter contract that wraps a specific model runtime
|
|
123
184
|
* (system frameworks, local runtimes, or cloud APIs) and exposes normalized APIs.
|
|
@@ -135,5 +196,24 @@ export interface ProviderAdapter {
|
|
|
135
196
|
* Executes a task request in Mode 1 (request/response).
|
|
136
197
|
*/
|
|
137
198
|
run(req: TaskRequest, ctx: RunContext): Promise<TaskResult>;
|
|
199
|
+
/**
|
|
200
|
+
* Executes a task request in Mode 2 (streaming). Optional: only providers declaring
|
|
201
|
+
* `describe().supports.streaming === true` are expected to implement this.
|
|
202
|
+
*/
|
|
203
|
+
stream?(req: TaskRequest, ctx: ProviderStreamContext): AsyncIterable<ProviderStreamEvent>;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* What `IndeRun.stream()` returns: the run's identity, its canonical event
|
|
207
|
+
* sequence, and a cancel hook.
|
|
208
|
+
*
|
|
209
|
+
* `events` terminates in exactly one terminal `StreamEvent`, per the Event
|
|
210
|
+
* Gate's guarantee, and is single-use — the run is driven once, not restarted
|
|
211
|
+
* per consumer. `cancel()` is idempotent: repeated or concurrent calls produce
|
|
212
|
+
* exactly one `cancelled` terminal outcome.
|
|
213
|
+
*/
|
|
214
|
+
export interface StreamRun {
|
|
215
|
+
handle: StreamRunHandle;
|
|
216
|
+
events: AsyncIterable<StreamEvent>;
|
|
217
|
+
cancel(reason?: string): void;
|
|
138
218
|
}
|
|
139
219
|
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/core/provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/core/provider.ts"],"names":[],"mappings":"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"}
|