@independo/inderun-web 0.3.0-dev.1 → 0.3.0-dev.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/core/browser-host.d.ts +29 -2
- package/dist/core/browser-host.d.ts.map +1 -1
- package/dist/core/browser-host.js +157 -0
- package/dist/core/engine.d.ts +37 -1
- package/dist/core/engine.d.ts.map +1 -1
- package/dist/core/engine.js +294 -25
- package/dist/core/event-gate.d.ts +37 -0
- package/dist/core/event-gate.d.ts.map +1 -0
- package/dist/core/event-gate.js +63 -0
- package/dist/core/generated/inderun-api.d.ts +5 -1
- package/dist/core/generated/inderun-api.d.ts.map +1 -1
- package/dist/core/host.d.ts +1 -1
- package/dist/core/host.d.ts.map +1 -1
- package/dist/core/provider.d.ts +81 -1
- package/dist/core/provider.d.ts.map +1 -1
- package/dist/core/route-planner.d.ts +22 -14
- package/dist/core/route-planner.d.ts.map +1 -1
- package/dist/core/route-planner.js +51 -24
- package/dist/core/router.d.ts +11 -15
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +32 -113
- package/dist/core/sse.d.ts +40 -0
- package/dist/core/sse.d.ts.map +1 -0
- package/dist/core/sse.js +118 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/providers/openai/provider.d.ts +44 -1
- package/dist/providers/openai/provider.d.ts.map +1 -1
- package/dist/providers/openai/provider.js +192 -30
- package/package.json +5 -5
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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;
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.3.0-dev.11",
|
|
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-
|
|
59
|
-
"@independo/inderun-
|
|
58
|
+
"@independo/inderun-contracts": "^0.3.0-dev.11",
|
|
59
|
+
"@independo/inderun-route-core-wasm": "^0.3.0-dev.11"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"@huggingface/transformers": "^4.0.0"
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
}
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@types/node": "^26.
|
|
70
|
+
"@types/node": "^26.4.0",
|
|
71
71
|
"typescript": "^6.0.3",
|
|
72
|
-
"vitest": "^4.1.
|
|
72
|
+
"vitest": "^4.1.11"
|
|
73
73
|
},
|
|
74
74
|
"scripts": {
|
|
75
75
|
"build": "tsc -p tsconfig.json",
|