@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/dist/index.d.ts CHANGED
@@ -1,12 +1,13 @@
1
- export { type ConnectivityService, type DeviceConstraintsService, type SecureStorageService, type ClockService, type HttpClientService, type HttpRequest, type HttpResponse, type HostServices, type ThermalState } from "./core/host.js";
2
- export { type ProviderDescriptor, type ProviderDynamicCapabilities, type ProviderCapabilitySnapshot, type RunContext, type ProviderAdapter } from "./core/provider.js";
1
+ export { type AbortSignalLike, type ConnectivityService, type DeviceConstraintsService, type SecureStorageService, type ClockService, type HttpClientService, type HttpRequest, type HttpResponse, type HttpStreamResponse, type HttpStreamingClientService, type HostServices, type ThermalState } from "./core/host.js";
2
+ export { type ProviderDescriptor, type ProviderDynamicCapabilities, type ProviderCapabilitySnapshot, type RunContext, type ProviderAdapter, type ProviderStreamContext, type ProviderStreamEvent, type StreamRun } from "./core/provider.js";
3
+ export { EventGate } from "./core/event-gate.js";
3
4
  export { ProviderRegistry } from "./core/registry.js";
4
5
  export { type RouteSelection, Router } from "./core/router.js";
5
6
  export { type RoutePlanner, type PlannerOutcome, type WasmUnavailableReason, type SharedPlannerInput, type SharedPlannerRoutePlan, WasmRoutePlanner } from "./core/route-planner.js";
6
7
  export { type IndeRunExceptionParams, IndeRunException, createCapabilityMismatch, createOffline, createAuthError, createRateLimited, createTimeout, createUnavailable, createInternal, toIndeRunException } from "./core/errors.js";
7
- export { IndeRun } from "./core/engine.js";
8
+ export { IndeRun, type StreamHandleResult } from "./core/engine.js";
8
9
  export type { IndeRunApi } from "./core/generated/inderun-api.js";
9
10
  export { type TelemetryEventType, type TelemetryEvent, type TelemetryService, NoOpTelemetryService } from "./core/telemetry.js";
10
- export { BrowserConnectivityService, FetchHttpClient, SystemClockService, createBrowserHostServices, type CreateBrowserHostServicesOptions, type FetchHttpClientOptions } from "./core/browser-host.js";
11
+ export { BrowserConnectivityService, FetchHttpClient, FetchStreamingHttpClient, SystemClockService, createBrowserHostServices, type CreateBrowserHostServicesOptions, type FetchHttpClientOptions } from "./core/browser-host.js";
11
12
  export { createIndeRunWeb, type CreateIndeRunWebOptions } from "./web-sdk.js";
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,EAC/B,KAAK,UAAU,EACf,KAAK,eAAe,EACrB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,gBAAgB,EACjB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,KAAK,sBAAsB,EAC3B,gBAAgB,EAChB,wBAAwB,EACxB,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,YAAY,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,oBAAoB,EACrB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,0BAA0B,EAC1B,eAAe,EACf,kBAAkB,EAClB,yBAAyB,EACzB,KAAK,gCAAgC,EACrC,KAAK,sBAAsB,EAC5B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,0BAA0B,EAC/B,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,EAC/B,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,SAAS,EACf,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,gBAAgB,EACjB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,KAAK,sBAAsB,EAC3B,gBAAgB,EAChB,wBAAwB,EACxB,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACpE,YAAY,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,oBAAoB,EACrB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,0BAA0B,EAC1B,eAAe,EACf,wBAAwB,EACxB,kBAAkB,EAClB,yBAAyB,EACzB,KAAK,gCAAgC,EACrC,KAAK,sBAAsB,EAC5B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -1,12 +1,13 @@
1
1
  export {} from "./core/host.js";
2
2
  export {} from "./core/provider.js";
3
+ export { EventGate } from "./core/event-gate.js";
3
4
  export { ProviderRegistry } from "./core/registry.js";
4
5
  export { Router } from "./core/router.js";
5
6
  export { WasmRoutePlanner } from "./core/route-planner.js";
6
7
  export { IndeRunException, createCapabilityMismatch, createOffline, createAuthError, createRateLimited, createTimeout, createUnavailable, createInternal, toIndeRunException } from "./core/errors.js";
7
8
  export { IndeRun } from "./core/engine.js";
8
9
  export { NoOpTelemetryService } from "./core/telemetry.js";
9
- export { BrowserConnectivityService, FetchHttpClient, SystemClockService, createBrowserHostServices } from "./core/browser-host.js";
10
+ export { BrowserConnectivityService, FetchHttpClient, FetchStreamingHttpClient, SystemClockService, createBrowserHostServices } from "./core/browser-host.js";
10
11
  export { createIndeRunWeb } from "./web-sdk.js";
11
12
  // The OpenAI Responses adapter is provider-specific and intentionally excluded
12
13
  // from this unified index. Import it from "@independo/inderun-web/openai".
@@ -1,6 +1,6 @@
1
1
  import type { TaskRequest, TaskResult } from "@independo/inderun-contracts";
2
2
  import type { HostServices } from "../../core/host.js";
3
- import type { ProviderAdapter, ProviderDescriptor, ProviderDynamicCapabilities, RunContext } from "../../core/provider.js";
3
+ import type { ProviderAdapter, ProviderDescriptor, ProviderDynamicCapabilities, ProviderStreamContext, ProviderStreamEvent, RunContext } from "../../core/provider.js";
4
4
  export declare const DEFAULT_OPENAI_RESPONSES_ENDPOINT = "https://api.openai.com/v1/responses";
5
5
  /**
6
6
  * Configuration for the OpenAI Responses provider.
@@ -84,6 +84,49 @@ export declare class OpenAIResponsesProvider implements ProviderAdapter {
84
84
  * @throws IndeRunException mapped to the standard error taxonomy.
85
85
  */
86
86
  run(request: TaskRequest, context: RunContext): Promise<TaskResult>;
87
+ /**
88
+ * Executes a normalized text-to-text task in Mode 2 against the OpenAI
89
+ * Responses API's server-sent event stream.
90
+ *
91
+ * The request is the Mode 1 body plus `"stream": true`, so both modes stay a
92
+ * single code path up to the transport. The response head is classified
93
+ * before the body is read: a non-2xx is drained to text and run through the
94
+ * same `mapHttpError` as Mode 1, because a 429 must surface as RateLimited
95
+ * rather than as a malformed event stream.
96
+ *
97
+ * Event mapping, from the Responses API's typed SSE events to the canonical
98
+ * provider vocabulary:
99
+ *
100
+ * - `response.output_text.delta` becomes a delta;
101
+ * - `response.completed` and `response.incomplete` become the terminal done,
102
+ * with `finalText`, `usage`, and `finishReason` read off the embedded
103
+ * response object by the same helpers Mode 1 uses — it has the same shape;
104
+ * - `response.failed` and `error` become a terminal error;
105
+ * - every other event type is ignored, since the set is open and additive.
106
+ *
107
+ * @param request - Canonical IndeRun task request.
108
+ * @param context - Engine stream context: run id, host services, abort signal.
109
+ */
110
+ stream(request: TaskRequest, context: ProviderStreamContext): AsyncGenerator<ProviderStreamEvent>;
111
+ /**
112
+ * Maps a `response.failed` / `error` stream event onto the error taxonomy.
113
+ * These arrive over an HTTP 200 body, so there is no status code to classify
114
+ * from; OpenAI reports rate limiting and auth failures here with the same
115
+ * `code` values it uses in unary error bodies.
116
+ *
117
+ * Two payload shapes exist and both are accepted: the standalone `error`
118
+ * event carries `code`/`message` at its root, while `response.failed` nests
119
+ * them under `response.error`. The root `type` is the event name rather than
120
+ * an error type, so it is never read as one.
121
+ */
122
+ private mapStreamError;
123
+ /**
124
+ * Resolves the request headers, including the bearer token behind
125
+ * `authContextRef`. Shared by `run` and `stream`: the credential never travels
126
+ * in the request payload, only the slot id does, so both modes must resolve it
127
+ * the same way.
128
+ */
129
+ private resolveHeaders;
87
130
  private createRequestBody;
88
131
  /**
89
132
  * Maps an OpenAI HTTP error response onto the IndeRun error taxonomy:
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/providers/openai/provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAQ5E,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,oBAAoB,CAAC;AACpE,OAAO,KAAK,EACV,eAAe,EACf,kBAAkB,EAClB,2BAA2B,EAC3B,UAAU,EACX,MAAM,wBAAwB,CAAC;AAEhC,eAAO,MAAM,iCAAiC,wCAAwC,CAAC;AAKvF;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,IAAI,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAAC;IACjC;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAwBD;;;;;;GAMG;AACH,qBAAa,uBAAwB,YAAW,eAAe;IAUjD,OAAO,CAAC,QAAQ,CAAC,OAAO;IATpC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA4B;IACjD,OAAO,CAAC,YAAY,CAAC,CAA6D;IAElF;;;OAGG;gBAC0B,OAAO,EAAE,qBAAqB;IAM3D;;OAEG;IACH,QAAQ,IAAI,kBAAkB;IAsB9B;;;;;;;;OAQG;IACG,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,2BAA2B,CAAC;YAkB9D,sBAAsB;IAuCpC;;;;;;OAMG;IACG,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAkHzE,OAAO,CAAC,iBAAiB;IAuBzB;;;;;;;OAOG;IACH,OAAO,CAAC,YAAY;CAmErB"}
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/providers/openai/provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAQ5E,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,oBAAoB,CAAC;AACpE,OAAO,KAAK,EACV,eAAe,EACf,kBAAkB,EAClB,2BAA2B,EAC3B,qBAAqB,EACrB,mBAAmB,EACnB,UAAU,EACX,MAAM,wBAAwB,CAAC;AA4BhC,eAAO,MAAM,iCAAiC,wCAAwC,CAAC;AAKvF;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,IAAI,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAAC;IACjC;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AA6BD;;;;;;GAMG;AACH,qBAAa,uBAAwB,YAAW,eAAe;IAUjD,OAAO,CAAC,QAAQ,CAAC,OAAO;IATpC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA4B;IACjD,OAAO,CAAC,YAAY,CAAC,CAA6D;IAElF;;;OAGG;gBAC0B,OAAO,EAAE,qBAAqB;IAM3D;;OAEG;IACH,QAAQ,IAAI,kBAAkB;IAuB9B;;;;;;;;OAQG;IACG,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,2BAA2B,CAAC;YA+B9D,sBAAsB;IAuCpC;;;;;;OAMG;IACG,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAiFzE;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACI,MAAM,CACX,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,qBAAqB,GAC7B,cAAc,CAAC,mBAAmB,CAAC;IAiGtC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,cAAc;IAmBtB;;;;;OAKG;YACW,cAAc;IA2C5B,OAAO,CAAC,iBAAiB;IA2BzB;;;;;;;OAOG;IACH,OAAO,CAAC,YAAY;CAmErB"}
@@ -1,4 +1,14 @@
1
1
  import { createAuthError, createInternal, createRateLimited, createTimeout, createUnavailable } from "../../core/errors.js";
2
+ import { parseSseStream } from "../../core/sse.js";
3
+ /** Buffers a body stream to text, for error responses that are not event streams. */
4
+ async function drainToText(body) {
5
+ const decoder = new TextDecoder();
6
+ let text = "";
7
+ for await (const chunk of body) {
8
+ text += decoder.decode(chunk, { stream: true });
9
+ }
10
+ return text + decoder.decode();
11
+ }
2
12
  export const DEFAULT_OPENAI_RESPONSES_ENDPOINT = "https://api.openai.com/v1/responses";
3
13
  const DEFAULT_HEALTH_CHECK_TIMEOUT_MS = 3000;
4
14
  const DEFAULT_HEALTH_CHECK_CACHE_MS = 5000;
@@ -33,9 +43,10 @@ export class OpenAIResponsesProvider {
33
43
  id: this.id,
34
44
  type: "cloud",
35
45
  transport: "http",
46
+ streamingStyle: "tokens",
36
47
  supports: {
37
48
  run: true,
38
- streaming: false,
49
+ streaming: true,
39
50
  realtime: false,
40
51
  tools: false,
41
52
  reasoningEvents: false,
@@ -71,7 +82,18 @@ export class OpenAIResponsesProvider {
71
82
  reason: "OpenAI Responses provider requires a SecureStorageService when auth is enabled."
72
83
  };
73
84
  }
74
- return this.checkEndpointReachable(host.httpClient, host.clock);
85
+ const reachability = await this.checkEndpointReachable(host.httpClient, host.clock);
86
+ if (host.streamingHttpClient) {
87
+ return reachability;
88
+ }
89
+ // Mode 1 still works without it; only streaming is taken away, and the
90
+ // planner turns this into an inspectable `streaming_unavailable` rejection
91
+ // rather than an unexplained routing failure.
92
+ return {
93
+ ...reachability,
94
+ streamingAvailable: false,
95
+ streamingUnavailableReason: "Host does not provide an HttpStreamingClientService, which OpenAI streaming requires."
96
+ };
75
97
  }
76
98
  async checkEndpointReachable(httpClient, clock) {
77
99
  const now = clock ? clock.now() : Date.now();
@@ -120,33 +142,7 @@ export class OpenAIResponsesProvider {
120
142
  runId: context.runId
121
143
  });
122
144
  }
123
- const headers = {
124
- "Content-Type": "application/json"
125
- };
126
- if (this.auth === "authContextRef") {
127
- const slotId = request.authContextRef ?? this.options.authContextRef;
128
- if (!slotId) {
129
- throw createAuthError("OpenAI Responses provider requires authContextRef.", {
130
- providerId: this.id,
131
- runId: context.runId
132
- });
133
- }
134
- const secureStorage = context.hostServices.secureStorage;
135
- if (!secureStorage) {
136
- throw createAuthError("OpenAI Responses provider requires a SecureStorageService when auth is enabled.", {
137
- providerId: this.id,
138
- runId: context.runId
139
- });
140
- }
141
- const secret = await secureStorage.getSecret(slotId);
142
- if (!secret) {
143
- throw createAuthError(`No OpenAI credential found for authContextRef '${slotId}'.`, {
144
- providerId: this.id,
145
- runId: context.runId
146
- });
147
- }
148
- headers.Authorization = `Bearer ${secret}`;
149
- }
145
+ const headers = await this.resolveHeaders(request, context);
150
146
  let response;
151
147
  try {
152
148
  const httpRequest = {
@@ -203,11 +199,177 @@ export class OpenAIResponsesProvider {
203
199
  }
204
200
  return result;
205
201
  }
206
- createRequestBody(request) {
202
+ /**
203
+ * Executes a normalized text-to-text task in Mode 2 against the OpenAI
204
+ * Responses API's server-sent event stream.
205
+ *
206
+ * The request is the Mode 1 body plus `"stream": true`, so both modes stay a
207
+ * single code path up to the transport. The response head is classified
208
+ * before the body is read: a non-2xx is drained to text and run through the
209
+ * same `mapHttpError` as Mode 1, because a 429 must surface as RateLimited
210
+ * rather than as a malformed event stream.
211
+ *
212
+ * Event mapping, from the Responses API's typed SSE events to the canonical
213
+ * provider vocabulary:
214
+ *
215
+ * - `response.output_text.delta` becomes a delta;
216
+ * - `response.completed` and `response.incomplete` become the terminal done,
217
+ * with `finalText`, `usage`, and `finishReason` read off the embedded
218
+ * response object by the same helpers Mode 1 uses — it has the same shape;
219
+ * - `response.failed` and `error` become a terminal error;
220
+ * - every other event type is ignored, since the set is open and additive.
221
+ *
222
+ * @param request - Canonical IndeRun task request.
223
+ * @param context - Engine stream context: run id, host services, abort signal.
224
+ */
225
+ async *stream(request, context) {
226
+ const streamingClient = context.hostServices.streamingHttpClient;
227
+ if (!streamingClient) {
228
+ throw createUnavailable("OpenAI Responses streaming requires an HttpStreamingClientService.", {
229
+ providerId: this.id,
230
+ runId: context.runId
231
+ });
232
+ }
233
+ const headers = await this.resolveHeaders(request, context);
234
+ headers.Accept = "text/event-stream";
235
+ let response;
236
+ try {
237
+ const httpRequest = {
238
+ method: "POST",
239
+ url: this.endpointUrl,
240
+ headers,
241
+ body: JSON.stringify(this.createRequestBody(request, true))
242
+ };
243
+ if (this.options.timeoutMs !== undefined) {
244
+ httpRequest.timeoutMs = this.options.timeoutMs;
245
+ }
246
+ response = await streamingClient.stream(httpRequest, context.signal);
247
+ }
248
+ catch (err) {
249
+ if (isAbortError(err)) {
250
+ throw createTimeout("OpenAI Responses stream timed out before the first byte.", {
251
+ providerId: this.id,
252
+ runId: context.runId
253
+ });
254
+ }
255
+ throw createUnavailable("OpenAI Responses stream failed before a response was received.", {
256
+ providerId: this.id,
257
+ runId: context.runId,
258
+ details: { originalError: getErrorSummary(err) }
259
+ });
260
+ }
261
+ if (response.status < 200 || response.status >= 300) {
262
+ throw this.mapHttpError(response.status, response.statusText, response.headers, await drainToText(response.body), context);
263
+ }
264
+ for await (const sseEvent of parseSseStream(response.body)) {
265
+ if (sseEvent.data === "[DONE]") {
266
+ continue;
267
+ }
268
+ const parsed = parseJson(sseEvent.data);
269
+ // The `event:` line and the payload's own `type` carry the same value;
270
+ // prefer the payload so a proxy that drops event names still works.
271
+ const type = parsed.type ?? sseEvent.event;
272
+ if (type === "response.output_text.delta") {
273
+ if (parsed.delta) {
274
+ yield { kind: "delta", text: parsed.delta };
275
+ }
276
+ continue;
277
+ }
278
+ if (type === "response.completed" || type === "response.incomplete") {
279
+ const body = parsed.response ?? {};
280
+ const done = {
281
+ kind: "done",
282
+ finalText: extractOutputText(body) ?? "",
283
+ finishReason: getFinishReason(body)
284
+ };
285
+ const usage = getUsage(body);
286
+ if (usage) {
287
+ done.usage = usage;
288
+ }
289
+ yield done;
290
+ return;
291
+ }
292
+ if (type === "response.failed" || type === "error") {
293
+ yield { kind: "error", error: this.mapStreamError(parsed, context) };
294
+ return;
295
+ }
296
+ }
297
+ // Falling out of the loop means the stream ended without a terminal event.
298
+ // The engine reports that as a provider fault; inventing a completion from
299
+ // whatever deltas happened to arrive would hide a truncated response.
300
+ }
301
+ /**
302
+ * Maps a `response.failed` / `error` stream event onto the error taxonomy.
303
+ * These arrive over an HTTP 200 body, so there is no status code to classify
304
+ * from; OpenAI reports rate limiting and auth failures here with the same
305
+ * `code` values it uses in unary error bodies.
306
+ *
307
+ * Two payload shapes exist and both are accepted: the standalone `error`
308
+ * event carries `code`/`message` at its root, while `response.failed` nests
309
+ * them under `response.error`. The root `type` is the event name rather than
310
+ * an error type, so it is never read as one.
311
+ */
312
+ mapStreamError(event, context) {
313
+ const nested = event.error ?? event.response?.error;
314
+ const message = nested?.message ?? event.message ?? "OpenAI Responses stream reported a failure.";
315
+ const code = nested?.code ?? event.code;
316
+ const details = { errorType: nested?.type, errorCode: code };
317
+ const params = { providerId: this.id, runId: context.runId, details };
318
+ switch (code) {
319
+ case "rate_limit_exceeded":
320
+ return createRateLimited(message, { ...params, retryable: true });
321
+ case "invalid_api_key":
322
+ case "authentication_error":
323
+ return createAuthError(message, params);
324
+ default:
325
+ return createUnavailable(message, { ...params, retryable: true });
326
+ }
327
+ }
328
+ /**
329
+ * Resolves the request headers, including the bearer token behind
330
+ * `authContextRef`. Shared by `run` and `stream`: the credential never travels
331
+ * in the request payload, only the slot id does, so both modes must resolve it
332
+ * the same way.
333
+ */
334
+ async resolveHeaders(request, context) {
335
+ const headers = {
336
+ "Content-Type": "application/json"
337
+ };
338
+ if (this.auth !== "authContextRef") {
339
+ return headers;
340
+ }
341
+ const slotId = request.authContextRef ?? this.options.authContextRef;
342
+ if (!slotId) {
343
+ throw createAuthError("OpenAI Responses provider requires authContextRef.", {
344
+ providerId: this.id,
345
+ runId: context.runId
346
+ });
347
+ }
348
+ const secureStorage = context.hostServices.secureStorage;
349
+ if (!secureStorage) {
350
+ throw createAuthError("OpenAI Responses provider requires a SecureStorageService when auth is enabled.", {
351
+ providerId: this.id,
352
+ runId: context.runId
353
+ });
354
+ }
355
+ const secret = await secureStorage.getSecret(slotId);
356
+ if (!secret) {
357
+ throw createAuthError(`No OpenAI credential found for authContextRef '${slotId}'.`, {
358
+ providerId: this.id,
359
+ runId: context.runId
360
+ });
361
+ }
362
+ headers.Authorization = `Bearer ${secret}`;
363
+ return headers;
364
+ }
365
+ createRequestBody(request, stream = false) {
207
366
  const body = {
208
367
  model: this.options.model,
209
368
  input: createInput(request)
210
369
  };
370
+ if (stream) {
371
+ body.stream = true;
372
+ }
211
373
  const generation = request.generation;
212
374
  if (generation?.maxOutputTokens !== undefined) {
213
375
  body.max_output_tokens = generation.maxOutputTokens;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@independo/inderun-web",
3
- "version": "0.3.0-dev.1",
3
+ "version": "0.3.0-dev.10",
4
4
  "description": "TypeScript/Web SDK for IndeRun.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -55,8 +55,8 @@
55
55
  "README.md"
56
56
  ],
57
57
  "dependencies": {
58
- "@independo/inderun-route-core-wasm": "^0.3.0-dev.1",
59
- "@independo/inderun-contracts": "^0.3.0-dev.1"
58
+ "@independo/inderun-contracts": "^0.3.0-dev.10",
59
+ "@independo/inderun-route-core-wasm": "^0.3.0-dev.10"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "@huggingface/transformers": "^4.0.0"
@@ -69,7 +69,7 @@
69
69
  "devDependencies": {
70
70
  "@types/node": "^26.2.0",
71
71
  "typescript": "^6.0.3",
72
- "vitest": "^4.1.10"
72
+ "vitest": "^4.1.11"
73
73
  },
74
74
  "scripts": {
75
75
  "build": "tsc -p tsconfig.json",