@independo/inderun-web 0.3.0-dev.4 → 0.3.0-dev.6

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 CHANGED
@@ -30,10 +30,49 @@ const result = await inderun.run({
30
30
  });
31
31
  ```
32
32
 
33
+ ## Streaming
34
+
35
+ `stream()` returns the run handle, its canonical `StreamEvent` sequence, and a `cancel()` hook:
36
+
37
+ ```ts
38
+ const { handle, events, cancel } = await inderun.stream({
39
+ schemaVersion: "1.0",
40
+ task: { kind: "text_to_text" },
41
+ prompt: "Write a one-sentence summary of IndeRun."
42
+ });
43
+
44
+ for await (const event of events) {
45
+ if (event.type === "content_delta") process.stdout.write(event.payload.text);
46
+ if (event.type === "terminal") console.log(event.payload.outcome);
47
+ }
48
+ ```
49
+
50
+ Order by `event.sequence`, not by arrival: it is the ordering authority for a run. Treat an
51
+ unrecognized `event.type` as ignore-or-pass-through — the set is open and additive. Exactly one
52
+ `terminal` event is produced per run, and `cancel()` is idempotent.
53
+
54
+ Streaming needs a host that can deliver a response body incrementally.
55
+ `createBrowserHostServices` provides one; a custom host without a
56
+ `streamingHttpClient` still runs Mode 1, and a stream request is refused at routing
57
+ time with a `streaming_unavailable` reason rather than failing later.
58
+
59
+ ### Custom OpenAI-compatible endpoints
60
+
61
+ The adapter speaks the OpenAI **Responses** API, not chat completions. A custom endpoint must
62
+ accept `"stream": true` and emit `text/event-stream` with the Responses event types —
63
+ `response.output_text.delta`, and `response.completed` / `response.incomplete` carrying the
64
+ response object. Unknown event types are ignored, so an endpoint that emits extra events is
65
+ fine; one that emits a different vocabulary entirely is not.
66
+
33
67
  ## Security Model
34
68
 
35
69
  Browser apps should use a proxy endpoint and keep provider credentials server-side. `createIndeRunWeb` rejects direct calls to the public OpenAI Responses endpoint unless `allowDirectOpenAIEndpoint: true` is set for a controlled environment.
36
70
 
71
+ This applies to streaming too, and is the reason `@independo/inderun-demo-proxy` relays event
72
+ streams rather than buffering them: a browser cannot open its own authenticated connection to
73
+ OpenAI without shipping the key in client code. Never embed a developer-owned API key in
74
+ distributed client code — put it behind a trusted backend proxy and point `endpointUrl` at that.
75
+
37
76
  ## Advanced: registering the OpenAI provider directly
38
77
 
39
78
  `createIndeRunWeb` wires the OpenAI Responses provider for you. To register it
@@ -1,4 +1,4 @@
1
- import type { ClockService, ConnectivityService, HostServices, HttpClientService, HttpRequest, HttpResponse } from "./host.js";
1
+ import type { ClockService, ConnectivityService, HostServices, HttpClientService, HttpRequest, HttpResponse, HttpStreamResponse, HttpStreamingClientService } from "./host.js";
2
2
  /**
3
3
  * Configuration for the fetch-backed HTTP host service.
4
4
  */
@@ -49,12 +49,39 @@ export declare class FetchHttpClient implements HttpClientService {
49
49
  */
50
50
  send(request: HttpRequest): Promise<HttpResponse>;
51
51
  }
52
+ /**
53
+ * Streaming HTTP client service that adapts `fetch` to IndeRun's incremental
54
+ * HTTP contract by handing back `Response.body` rather than buffering it.
55
+ *
56
+ * Two abort sources are merged onto one controller: `timeoutMs`, which bounds
57
+ * only the wait for the response head, and the caller's `signal`, which stays
58
+ * live for the whole body. The timeout timer is therefore cleared as soon as
59
+ * the head resolves — a stream that legitimately stays open for minutes must
60
+ * not be killed by a time-to-first-byte budget.
61
+ */
62
+ export declare class FetchStreamingHttpClient implements HttpStreamingClientService {
63
+ private readonly fetchFn;
64
+ /**
65
+ * Creates a fetch-backed streaming HTTP client.
66
+ * @param options - Optional fetch implementation override.
67
+ * @throws Error when no fetch implementation is available.
68
+ */
69
+ constructor(options?: FetchHttpClientOptions);
70
+ /**
71
+ * Sends a normalized HTTP request and resolves once the response head is available.
72
+ * @param request - Normalized HTTP request payload.
73
+ * @param signal - Optional caller-driven cancellation covering the whole response.
74
+ * @returns Response head plus a lazily-consumed body chunk sequence.
75
+ */
76
+ stream(request: HttpRequest, signal?: AbortSignal): Promise<HttpStreamResponse>;
77
+ }
52
78
  /**
53
79
  * Options for constructing default browser host services.
54
80
  */
55
81
  export interface CreateBrowserHostServicesOptions extends Partial<HostServices> {
56
82
  /**
57
- * Optional fetch implementation used when `httpClient` is not supplied.
83
+ * Optional fetch implementation used when `httpClient`/`streamingHttpClient`
84
+ * are not supplied.
58
85
  */
59
86
  fetchFn?: typeof fetch;
60
87
  }
@@ -1 +1 @@
1
- {"version":3,"file":"browser-host.d.ts","sourceRoot":"","sources":["../../src/core/browser-host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,YAAY,EACb,MAAM,WAAW,CAAC;AAEnB;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAED;;GAEG;AACH,qBAAa,0BAA2B,YAAW,mBAAmB;IACpE;;;OAGG;IACG,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;CAOnC;AAED;;GAEG;AACH,qBAAa,kBAAmB,YAAW,YAAY;IACrD;;OAEG;IACH,GAAG,IAAI,MAAM;IAIb;;OAEG;IACH,YAAY,IAAI,MAAM;CAOvB;AAED;;GAEG;AACH,qBAAa,eAAgB,YAAW,iBAAiB;IACvD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC;;;;OAIG;gBACS,OAAO,GAAE,sBAA2B;IAShD;;;;OAIG;IACG,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;CA0CxD;AAED;;GAEG;AACH,MAAM,WAAW,gCAAiC,SAAQ,OAAO,CAAC,YAAY,CAAC;IAC7E;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,gCAAqC,GAC7C,YAAY,CA2Bd"}
1
+ {"version":3,"file":"browser-host.d.ts","sourceRoot":"","sources":["../../src/core/browser-host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,0BAA0B,EAC3B,MAAM,WAAW,CAAC;AAEnB;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAED;;GAEG;AACH,qBAAa,0BAA2B,YAAW,mBAAmB;IACpE;;;OAGG;IACG,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;CAOnC;AAED;;GAEG;AACH,qBAAa,kBAAmB,YAAW,YAAY;IACrD;;OAEG;IACH,GAAG,IAAI,MAAM;IAIb;;OAEG;IACH,YAAY,IAAI,MAAM;CAOvB;AAED;;GAEG;AACH,qBAAa,eAAgB,YAAW,iBAAiB;IACvD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC;;;;OAIG;gBACS,OAAO,GAAE,sBAA2B;IAShD;;;;OAIG;IACG,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;CA0CxD;AAED;;;;;;;;;GASG;AACH,qBAAa,wBAAyB,YAAW,0BAA0B;IACzE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC;;;;OAIG;gBACS,OAAO,GAAE,sBAA2B;IAShD;;;;;OAKG;IACG,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAwHtF;AAED;;GAEG;AACH,MAAM,WAAW,gCAAiC,SAAQ,OAAO,CAAC,YAAY,CAAC;IAC7E;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,GAAE,gCAAqC,GAC7C,YAAY,CAmCd"}
@@ -93,6 +93,154 @@ export class FetchHttpClient {
93
93
  }
94
94
  }
95
95
  }
96
+ /**
97
+ * Streaming HTTP client service that adapts `fetch` to IndeRun's incremental
98
+ * HTTP contract by handing back `Response.body` rather than buffering it.
99
+ *
100
+ * Two abort sources are merged onto one controller: `timeoutMs`, which bounds
101
+ * only the wait for the response head, and the caller's `signal`, which stays
102
+ * live for the whole body. The timeout timer is therefore cleared as soon as
103
+ * the head resolves — a stream that legitimately stays open for minutes must
104
+ * not be killed by a time-to-first-byte budget.
105
+ */
106
+ export class FetchStreamingHttpClient {
107
+ fetchFn;
108
+ /**
109
+ * Creates a fetch-backed streaming HTTP client.
110
+ * @param options - Optional fetch implementation override.
111
+ * @throws Error when no fetch implementation is available.
112
+ */
113
+ constructor(options = {}) {
114
+ const fetchFn = options.fetchFn ?? globalThis.fetch?.bind(globalThis);
115
+ if (!fetchFn) {
116
+ throw new Error("FetchStreamingHttpClient requires a fetch implementation.");
117
+ }
118
+ this.fetchFn = fetchFn;
119
+ }
120
+ /**
121
+ * Sends a normalized HTTP request and resolves once the response head is available.
122
+ * @param request - Normalized HTTP request payload.
123
+ * @param signal - Optional caller-driven cancellation covering the whole response.
124
+ * @returns Response head plus a lazily-consumed body chunk sequence.
125
+ */
126
+ async stream(request, signal) {
127
+ const controller = typeof AbortController === "undefined" ? undefined : new AbortController();
128
+ let timeoutId;
129
+ let onAbort;
130
+ if (controller) {
131
+ if (request.timeoutMs !== undefined) {
132
+ timeoutId = setTimeout(() => controller.abort(), request.timeoutMs);
133
+ }
134
+ if (signal) {
135
+ if (signal.aborted) {
136
+ controller.abort();
137
+ }
138
+ else {
139
+ onAbort = () => controller.abort();
140
+ signal.addEventListener("abort", onAbort, { once: true });
141
+ }
142
+ }
143
+ }
144
+ const clearTimeoutTimer = () => {
145
+ if (timeoutId !== undefined) {
146
+ clearTimeout(timeoutId);
147
+ timeoutId = undefined;
148
+ }
149
+ };
150
+ const releaseAbortListener = () => {
151
+ if (signal && onAbort) {
152
+ signal.removeEventListener("abort", onAbort);
153
+ onAbort = undefined;
154
+ }
155
+ };
156
+ let response;
157
+ try {
158
+ const init = { method: request.method };
159
+ if (request.headers) {
160
+ init.headers = request.headers;
161
+ }
162
+ if (request.body !== undefined) {
163
+ init.body = request.body;
164
+ }
165
+ if (controller) {
166
+ init.signal = controller.signal;
167
+ }
168
+ response = await this.fetchFn(request.url, init);
169
+ }
170
+ catch (error) {
171
+ clearTimeoutTimer();
172
+ releaseAbortListener();
173
+ throw error;
174
+ }
175
+ clearTimeoutTimer();
176
+ const headers = {};
177
+ response.headers.forEach((value, key) => {
178
+ headers[key.toLowerCase()] = value;
179
+ });
180
+ const stream = response.body;
181
+ async function* iterate() {
182
+ try {
183
+ if (!stream) {
184
+ // A bodyless response (204, HEAD) or a fetch polyfill without stream
185
+ // support: fall back to the buffered text so callers still terminate.
186
+ const text = await response.text();
187
+ if (text.length > 0) {
188
+ yield new TextEncoder().encode(text);
189
+ }
190
+ return;
191
+ }
192
+ const reader = stream.getReader();
193
+ // The body is also raced against `signal` directly, not just against the
194
+ // fetch-level abort: a fetch implementation is free not to error an
195
+ // in-flight body when its request signal fires, and the caller's
196
+ // cancellation guarantee must not depend on that.
197
+ let onBodyAbort;
198
+ const aborted = new Promise((_resolve, reject) => {
199
+ if (!signal)
200
+ return;
201
+ onBodyAbort = () => reject(Object.assign(new Error("The stream was aborted."), { name: "AbortError" }));
202
+ if (signal.aborted) {
203
+ onBodyAbort();
204
+ }
205
+ else {
206
+ signal.addEventListener("abort", onBodyAbort, { once: true });
207
+ }
208
+ });
209
+ // Keeps an abort that arrives after the body finished from surfacing as
210
+ // an unhandled rejection; the race below still observes it.
211
+ void aborted.catch(() => undefined);
212
+ try {
213
+ for (;;) {
214
+ const { done, value } = await Promise.race([reader.read(), aborted]);
215
+ if (done)
216
+ return;
217
+ if (value)
218
+ yield value;
219
+ }
220
+ }
221
+ catch (error) {
222
+ await reader.cancel().catch(() => undefined);
223
+ throw error;
224
+ }
225
+ finally {
226
+ if (signal && onBodyAbort) {
227
+ signal.removeEventListener("abort", onBodyAbort);
228
+ }
229
+ reader.releaseLock();
230
+ }
231
+ }
232
+ finally {
233
+ releaseAbortListener();
234
+ }
235
+ }
236
+ return {
237
+ status: response.status,
238
+ statusText: response.statusText,
239
+ headers,
240
+ body: iterate()
241
+ };
242
+ }
243
+ }
96
244
  /**
97
245
  * Creates a browser-oriented HostServices bundle for the Web SDK.
98
246
  * Supplied host services override the defaults; missing connectivity, clock, and HTTP services are created.
@@ -112,6 +260,15 @@ export function createBrowserHostServices(options = {}) {
112
260
  else {
113
261
  hostServices.httpClient = new FetchHttpClient();
114
262
  }
263
+ if (options.streamingHttpClient) {
264
+ hostServices.streamingHttpClient = options.streamingHttpClient;
265
+ }
266
+ else if (options.fetchFn) {
267
+ hostServices.streamingHttpClient = new FetchStreamingHttpClient({ fetchFn: options.fetchFn });
268
+ }
269
+ else {
270
+ hostServices.streamingHttpClient = new FetchStreamingHttpClient();
271
+ }
115
272
  if (options.deviceConstraints) {
116
273
  hostServices.deviceConstraints = options.deviceConstraints;
117
274
  }
@@ -1,20 +1,15 @@
1
- import { type StreamEvent, type StreamRunHandle, type TaskRequest, type TaskResult } from "@independo/inderun-contracts";
1
+ import { type TaskRequest, type TaskResult } from "@independo/inderun-contracts";
2
2
  import type { HostServices } from "./host.js";
3
- import type { ProviderCapabilitySnapshot } from "./provider.js";
3
+ import type { ProviderCapabilitySnapshot, StreamRun } from "./provider.js";
4
4
  import type { IndeRunApi } from "./generated/inderun-api.js";
5
5
  import { ProviderRegistry } from "./registry.js";
6
6
  import { type TelemetryService } from "./telemetry.js";
7
7
  /**
8
- * Handle returned by IndeRun.stream(). `events` is the canonical StreamEvent
9
- * sequence for this run, terminating in exactly one terminal StreamEvent per the
10
- * Event Gate's guarantee. `cancel()` is idempotent: repeated or concurrent calls
11
- * produce exactly one `cancelled` terminal outcome.
8
+ * @deprecated Renamed to `StreamRun`, which is the name the generated
9
+ * `IndeRunApi` surface and the Swift/Kotlin SDKs use. Kept as an alias so the
10
+ * rename is not a breaking change.
12
11
  */
13
- export interface StreamHandleResult {
14
- handle: StreamRunHandle;
15
- events: AsyncIterable<StreamEvent>;
16
- cancel(reason?: string): void;
17
- }
12
+ export type StreamHandleResult = StreamRun;
18
13
  /**
19
14
  * Main orchestrator SDK entrypoint class.
20
15
  * Validates tasks, executes routing rules based on request constraints and connectivity,
@@ -54,17 +49,22 @@ export declare class IndeRun implements IndeRunApi {
54
49
  /**
55
50
  * Orchestrates a Mode 2 streaming execution of a TaskRequest.
56
51
  *
57
- * Reuses the same route selection as run(), restricted to providers that declare
58
- * `supports.streaming` and implement `stream()`. Fallback to the next provider is
59
- * allowed only before the first content event has been admitted for the run; once
60
- * one has been delivered, the run is committed to that provider and any later
61
- * provider failure becomes a terminal error outcome rather than a silent provider
62
- * swap. Cancellation always forecloses fallback, regardless of commit state, and
63
- * is idempotent.
52
+ * Routes through the same planner as run(), asking it for a `stream` route: the
53
+ * planner rejects providers that do not declare `supports.streaming` or whose
54
+ * dynamic capability snapshot says they cannot stream here, and reports why. The
55
+ * whole returned chain is therefore mode-compatible and subject to the same
56
+ * privacy, locality, availability, and policy constraints as a Mode 1 route.
57
+ *
58
+ * Fallback to the next provider is allowed only before the first content event
59
+ * has been admitted for the run; once one has been delivered, the run is
60
+ * committed to that provider and any later provider failure becomes a terminal
61
+ * error outcome rather than a silent provider swap — partial output spliced from
62
+ * two providers is undetectable to the caller. Cancellation always forecloses
63
+ * fallback, regardless of commit state, and is idempotent.
64
64
  * @param request - The canonical task request containing prompts, tasks, and constraints.
65
65
  * @throws {IndeRunException} If validation fails or no eligible streaming provider exists.
66
66
  */
67
- stream(request: TaskRequest): Promise<StreamHandleResult>;
67
+ stream(request: TaskRequest): Promise<StreamRun>;
68
68
  /**
69
69
  * Reports each registered provider's static descriptor and current dynamic capability check,
70
70
  * without executing a task. Useful for UI that shows live provider availability before a run.
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/core/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,eAAe,EAEpB,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAmB,0BAA0B,EAAE,MAAM,eAAe,CAAC;AACjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAQjD,OAAO,EAAuB,KAAK,gBAAgB,EAAwB,MAAM,gBAAgB,CAAC;AAGlG;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,eAAe,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IACnC,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;;;GAIG;AACH,qBAAa,OAAQ,YAAW,UAAU;IAWtC,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,YAAY;IAXtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAAmB;IAE3C;;;;;OAKG;gBAEO,QAAQ,EAAE,gBAAgB,EAC1B,YAAY,EAAE,YAAY,EAClC,gBAAgB,CAAC,EAAE,gBAAgB;IAOrC;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAShB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAoBxB;;;;;;;OAOG;IACG,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAuIpD;;;;;;;;;;;;OAYG;IACG,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAsQ/D;;;OAGG;IACG,iBAAiB,IAAI,OAAO,CAAC,0BAA0B,EAAE,CAAC;CASjE"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/core/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAmB,0BAA0B,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC5F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAQjD,OAAO,EAAuB,KAAK,gBAAgB,EAAwB,MAAM,gBAAgB,CAAC;AAGlG;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,SAAS,CAAC;AAE3C;;;;GAIG;AACH,qBAAa,OAAQ,YAAW,UAAU;IAWtC,OAAO,CAAC,QAAQ;IAChB,OAAO,CAAC,YAAY;IAXtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAAmB;IAE3C;;;;;OAKG;gBAEO,QAAQ,EAAE,gBAAgB,EAC1B,YAAY,EAAE,YAAY,EAClC,gBAAgB,CAAC,EAAE,gBAAgB;IAOrC;;;OAGG;IACH,OAAO,CAAC,QAAQ;IAShB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAoBxB;;;;;;;OAOG;IACG,GAAG,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAuIpD;;;;;;;;;;;;;;;;;OAiBG;IACG,MAAM,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,CAAC;IA+QtD;;;OAGG;IACG,iBAAiB,IAAI,OAAO,CAAC,0BAA0B,EAAE,CAAC;CASjE"}
@@ -87,7 +87,7 @@ export class IndeRun {
87
87
  });
88
88
  }
89
89
  // 2. Select the route based on constraints and host capabilities
90
- const routeSelection = await this.router.selectRoute(request, this.hostServices);
90
+ const routeSelection = await this.router.selectRoute(request, this.hostServices, "run");
91
91
  const providers = [routeSelection.provider, ...routeSelection.fallbackProviders];
92
92
  // Emit route_decided event
93
93
  this.safeEmit({
@@ -194,13 +194,18 @@ export class IndeRun {
194
194
  /**
195
195
  * Orchestrates a Mode 2 streaming execution of a TaskRequest.
196
196
  *
197
- * Reuses the same route selection as run(), restricted to providers that declare
198
- * `supports.streaming` and implement `stream()`. Fallback to the next provider is
199
- * allowed only before the first content event has been admitted for the run; once
200
- * one has been delivered, the run is committed to that provider and any later
201
- * provider failure becomes a terminal error outcome rather than a silent provider
202
- * swap. Cancellation always forecloses fallback, regardless of commit state, and
203
- * is idempotent.
197
+ * Routes through the same planner as run(), asking it for a `stream` route: the
198
+ * planner rejects providers that do not declare `supports.streaming` or whose
199
+ * dynamic capability snapshot says they cannot stream here, and reports why. The
200
+ * whole returned chain is therefore mode-compatible and subject to the same
201
+ * privacy, locality, availability, and policy constraints as a Mode 1 route.
202
+ *
203
+ * Fallback to the next provider is allowed only before the first content event
204
+ * has been admitted for the run; once one has been delivered, the run is
205
+ * committed to that provider and any later provider failure becomes a terminal
206
+ * error outcome rather than a silent provider swap — partial output spliced from
207
+ * two providers is undetectable to the caller. Cancellation always forecloses
208
+ * fallback, regardless of commit state, and is idempotent.
204
209
  * @param request - The canonical task request containing prompts, tasks, and constraints.
205
210
  * @throws {IndeRunException} If validation fails or no eligible streaming provider exists.
206
211
  */
@@ -215,9 +220,17 @@ export class IndeRun {
215
220
  .join("; ")}`;
216
221
  throw createInternal(message, { runId, details: { validationIssues } });
217
222
  }
218
- const routeSelection = await this.router.selectRoute(request, this.hostServices);
219
- const allProviders = [routeSelection.provider, ...routeSelection.fallbackProviders];
220
- const providers = allProviders.filter((p) => p.describe().supports.streaming && typeof p.stream === "function");
223
+ // Route selection failures are the one class of stream error that surfaces as a
224
+ // rejected promise instead of a terminal event, so they must still carry the
225
+ // runId — there is no handle for the caller to correlate them with otherwise.
226
+ let routeSelection;
227
+ try {
228
+ routeSelection = await this.router.selectRoute(request, this.hostServices, "stream");
229
+ }
230
+ catch (error) {
231
+ throw toIndeRunException(error, { runId });
232
+ }
233
+ const providers = [routeSelection.provider, ...routeSelection.fallbackProviders];
221
234
  this.safeEmit({
222
235
  type: "route_decided",
223
236
  runId,
@@ -235,6 +248,9 @@ export class IndeRun {
235
248
  plannerUnavailableReason: routeSelection.plannerUnavailableReason ?? null
236
249
  }
237
250
  });
251
+ // Defensive only: streaming eligibility is decided by the route planner, which
252
+ // throws with its own rejection reasons when nothing can stream. Reaching here
253
+ // means a planned provider was unregistered between planning and selection.
238
254
  if (providers.length === 0) {
239
255
  throw createCapabilityMismatch("No eligible provider supports streaming for this request.", {
240
256
  runId,
@@ -319,6 +335,7 @@ export class IndeRun {
319
335
  schemaVersion: "1.0",
320
336
  finalText: ev.finalText,
321
337
  telemetry: { providerUsed: providerId, totalMs: now() - startTime },
338
+ ...(ev.finishReason !== undefined ? { finishReason: ev.finishReason } : {}),
322
339
  ...(ev.usage !== undefined ? { usage: ev.usage } : {})
323
340
  };
324
341
  const terminal = gate.terminate(outcome, now());
@@ -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,
@@ -149,6 +168,12 @@ export type ProviderStreamEvent = {
149
168
  } | {
150
169
  kind: "done";
151
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"];
152
177
  usage?: TaskResult["usage"];
153
178
  } | {
154
179
  kind: "error";
@@ -177,4 +202,18 @@ export interface ProviderAdapter {
177
202
  */
178
203
  stream?(req: TaskRequest, ctx: ProviderStreamContext): AsyncIterable<ProviderStreamEvent>;
179
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;
218
+ }
180
219
  //# sourceMappingURL=provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/core/provider.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/core/provider.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,11 @@ import type { RoutePlan as SharedPlannerRoutePlan, RoutePlannerInput as SharedPl
2
2
  import type { HostServices } from "./host.js";
3
3
  import type { ProviderAdapter } from "./provider.js";
4
4
  export type { SharedPlannerInput, SharedPlannerRoutePlan };
5
+ /**
6
+ * Interaction mode the caller is requesting a route for. Passed to the shared
7
+ * planner, which rejects providers that cannot satisfy it.
8
+ */
9
+ export type InteractionMode = NonNullable<SharedPlannerInput["interactionMode"]>;
5
10
  /**
6
11
  * Minimal shape of the shared route-core module (the WASM wrapper), used to
7
12
  * plan routes via JSON in/JSON out. `initSharedCore` initializes the WASM
@@ -73,5 +78,5 @@ export declare function collectProviderRuntimeSnapshots(registryProviders: Provi
73
78
  * consumes, applying default constraints/preferences and the current network
74
79
  * state.
75
80
  */
76
- export declare function buildSharedPlannerInput(request: TaskRequest, snapshots: ProviderRuntimeSnapshot[], networkOnline: boolean): SharedPlannerInput;
81
+ export declare function buildSharedPlannerInput(request: TaskRequest, snapshots: ProviderRuntimeSnapshot[], networkOnline: boolean, interactionMode?: InteractionMode): SharedPlannerInput;
77
82
  //# sourceMappingURL=route-planner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"route-planner.d.ts","sourceRoot":"","sources":["../../src/core/route-planner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,IAAI,sBAAsB,EACnC,iBAAiB,IAAI,kBAAkB,EACvC,WAAW,EACZ,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EACV,eAAe,EAGhB,MAAM,eAAe,CAAC;AAEvB,YAAY,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,CAAC;AAE3D;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAChE;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAC/B,eAAe,GAAG,sBAAsB,GAAG,aAAa,GAAG,aAAa,CAAC;AAE3E;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACzC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC;IAC/B,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;CAC3C;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CAC/D;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,eAAe,CAAC;IAC1B,UAAU,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IAClE,YAAY,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;CACvE;AAED;;;;;;;;;GASG;AACH,qBAAa,gBAAiB,YAAW,YAAY;IACnD,OAAO,CAAC,aAAa,CAAC,CAAsC;IAC5D,OAAO,CAAC,iBAAiB,CAAC,CAAwB;IAClD,OAAO,CAAC,SAAS,CAAS;IAEpB,SAAS,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC;YAkBrD,UAAU;YAOV,YAAY;IA6B1B,OAAO,CAAC,QAAQ;CAUjB;AAED;;;GAGG;AACH,wBAAsB,+BAA+B,CACnD,iBAAiB,EAAE,eAAe,EAAE,EACpC,YAAY,EAAE,YAAY,GACzB,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAepC;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,uBAAuB,EAAE,EACpC,aAAa,EAAE,OAAO,GACrB,kBAAkB,CAsBpB"}
1
+ {"version":3,"file":"route-planner.d.ts","sourceRoot":"","sources":["../../src/core/route-planner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,IAAI,sBAAsB,EACnC,iBAAiB,IAAI,kBAAkB,EACvC,WAAW,EACZ,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EACV,eAAe,EAGhB,MAAM,eAAe,CAAC;AAEvB,YAAY,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,CAAC;AAE3D;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,CAAC,CAAC;AAEjF;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAChE;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAC/B,eAAe,GAAG,sBAAsB,GAAG,aAAa,GAAG,aAAa,CAAC;AAE3E;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACzC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC;IAC/B,iBAAiB,CAAC,EAAE,qBAAqB,CAAC;CAC3C;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CAC/D;AAED;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,eAAe,CAAC;IAC1B,UAAU,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IAClE,YAAY,EAAE,kBAAkB,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;CACvE;AAED;;;;;;;;;GASG;AACH,qBAAa,gBAAiB,YAAW,YAAY;IACnD,OAAO,CAAC,aAAa,CAAC,CAAsC;IAC5D,OAAO,CAAC,iBAAiB,CAAC,CAAwB;IAClD,OAAO,CAAC,SAAS,CAAS;IAEpB,SAAS,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,cAAc,CAAC;YAkBrD,UAAU;YAOV,YAAY;IA6B1B,OAAO,CAAC,QAAQ;CAUjB;AAED;;;GAGG;AACH,wBAAsB,+BAA+B,CACnD,iBAAiB,EAAE,eAAe,EAAE,EACpC,YAAY,EAAE,YAAY,GACzB,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAepC;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,uBAAuB,EAAE,EACpC,aAAa,EAAE,OAAO,EACtB,eAAe,GAAE,eAAuB,GACvC,kBAAkB,CAuBpB"}
@@ -81,7 +81,7 @@ export async function collectProviderRuntimeSnapshots(registryProviders, hostSer
81
81
  return {
82
82
  provider,
83
83
  descriptor: toSharedPlannerDescriptor(descriptor),
84
- capabilities: toSharedPlannerCapabilities(capabilities)
84
+ capabilities: toSharedPlannerCapabilities(capabilities, descriptor, provider)
85
85
  };
86
86
  }));
87
87
  return snapshots.sort((left, right) => left.descriptor.id.localeCompare(right.descriptor.id));
@@ -91,13 +91,14 @@ export async function collectProviderRuntimeSnapshots(registryProviders, hostSer
91
91
  * consumes, applying default constraints/preferences and the current network
92
92
  * state.
93
93
  */
94
- export function buildSharedPlannerInput(request, snapshots, networkOnline) {
94
+ export function buildSharedPlannerInput(request, snapshots, networkOnline, interactionMode = "run") {
95
95
  const constraints = request.constraints ?? {};
96
96
  const preferences = request.preferences ?? {};
97
97
  return {
98
98
  task: {
99
99
  kind: request.task.kind
100
100
  },
101
+ interactionMode,
101
102
  constraints: {
102
103
  privacy: constraints.privacy ?? "cloud_allowed",
103
104
  cloud: constraints.cloud ?? "allowed",
@@ -118,8 +119,10 @@ function toSharedPlannerDescriptor(descriptor) {
118
119
  id: descriptor.id,
119
120
  type: descriptor.type,
120
121
  supports: {
121
- run: descriptor.supports.run
122
+ run: descriptor.supports.run,
123
+ streaming: descriptor.supports.streaming
122
124
  },
125
+ cancel: descriptor.cancel,
123
126
  tasks: descriptor.tasks,
124
127
  ...(descriptor.privacy
125
128
  ? {
@@ -131,9 +134,30 @@ function toSharedPlannerDescriptor(descriptor) {
131
134
  : {})
132
135
  };
133
136
  }
134
- function toSharedPlannerCapabilities(capabilities) {
137
+ /**
138
+ * Projects the dynamic capability snapshot for the planner.
139
+ *
140
+ * `stream()` being absent from the adapter is folded in here rather than
141
+ * filtered out downstream: an adapter that declares `supports.streaming` but
142
+ * never implemented the method is not streaming-capable, and saying so as a
143
+ * planner-visible reason is what lets the route explanation name the provider
144
+ * instead of silently dropping it.
145
+ */
146
+ function toSharedPlannerCapabilities(capabilities, descriptor, provider) {
147
+ const declaresStreaming = capabilities.streamingAvailable ?? descriptor.supports.streaming;
148
+ const implementsStream = typeof provider.stream === "function";
149
+ const streamingAvailable = declaresStreaming && implementsStream;
150
+ const streamingUnavailableReason = capabilities.streamingUnavailableReason ??
151
+ (declaresStreaming && !implementsStream
152
+ ? `Provider '${descriptor.id}' declares streaming but does not implement stream().`
153
+ : undefined);
135
154
  return {
136
155
  available: capabilities.available,
137
- ...(capabilities.reason ? { reason: capabilities.reason } : {})
156
+ ...(capabilities.reason ? { reason: capabilities.reason } : {}),
157
+ streamingAvailable,
158
+ ...(!streamingAvailable && streamingUnavailableReason ? { streamingUnavailableReason } : {}),
159
+ ...(capabilities.cancellationAvailable !== undefined
160
+ ? { cancellationAvailable: capabilities.cancellationAvailable }
161
+ : {})
138
162
  };
139
163
  }