@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.
@@ -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.hostServices.clock ? this.hostServices.clock.now() : Date.now();
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
- // Emit route_decided event
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.hostServices.clock ? this.hostServices.clock.now() : Date.now();
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.hostServices.clock ? this.hostServices.clock.now() : Date.now();
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
- : (this.hostServices.clock ? this.hostServices.clock.now() : Date.now()) - startTime
185
+ : this.now() - startTime
173
186
  }
174
187
  });
175
- const endTime = this.hostServices.clock ? this.hostServices.clock.now() : Date.now();
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;AAEjE,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/C;;OAEG;IACH,iBAAiB,IAAI,OAAO,CAAC,0BAA0B,EAAE,CAAC,CAAC;CAC5D"}
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"}
@@ -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
@@ -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"}
@@ -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,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC5E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IACX;;OAEG;IACH,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IACjC;;OAEG;IACH,SAAS,EAAE,YAAY,GAAG,gBAAgB,GAAG,MAAM,GAAG,KAAK,GAAG,UAAU,CAAC;IACzE;;OAEG;IACH,cAAc,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;IAEnD;;OAEG;IACH,QAAQ,EAAE;QACR,mEAAmE;QACnE,GAAG,EAAE,OAAO,CAAC;QACb,mDAAmD;QACnD,SAAS,EAAE,OAAO,CAAC;QACnB,6DAA6D;QAC7D,QAAQ,EAAE,OAAO,CAAC;QAClB,6DAA6D;QAC7D,KAAK,EAAE,OAAO,CAAC;QACf,wEAAwE;QACxE,eAAe,EAAE,OAAO,CAAC;QACzB,kFAAkF;QAClF,gBAAgB,EAAE,OAAO,CAAC;QAC1B,6EAA6E;QAC7E,UAAU,EAAE,OAAO,CAAC;KACrB,CAAC;IAEF;;;;;OAKG;IACH,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAEjC;;OAEG;IACH,KAAK,EAAE,MAAM,EAAE,CAAC;IAEhB;;OAEG;IACH,MAAM,CAAC,EAAE;QACP,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IAEF;;OAEG;IACH,OAAO,CAAC,EAAE;QACR,6DAA6D;QAC7D,gBAAgB,EAAE,OAAO,CAAC;QAC1B,2DAA2D;QAC3D,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;OAEG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,UAAU,EAAE,kBAAkB,CAAC;IAC/B;;;OAGG;IACH,YAAY,EAAE,2BAA2B,CAAC;CAC3C;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,YAAY,EAAE,YAAY,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,QAAQ,IAAI,kBAAkB,CAAC;IAC/B;;OAEG;IACH,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAAC;IACvE;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAC7D"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/core/provider.ts"],"names":[],"mappings":"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"}