@independo/inderun-web 0.3.0-dev.8 → 0.3.0
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 +12 -10
- package/dist/core/engine.d.ts +11 -0
- package/dist/core/engine.d.ts.map +1 -1
- package/dist/core/engine.js +38 -42
- package/dist/core/route-planner.d.ts +16 -13
- package/dist/core/route-planner.d.ts.map +1 -1
- package/dist/core/route-planner.js +22 -19
- package/dist/core/router.d.ts +1 -39
- package/dist/core/router.d.ts.map +1 -1
- package/dist/core/router.js +12 -206
- package/dist/index.d.ts +63 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -2
- package/dist/onnx.d.ts +21 -0
- package/dist/onnx.d.ts.map +1 -1
- package/dist/onnx.js +15 -0
- package/dist/openai.d.ts +7 -0
- package/dist/openai.d.ts.map +1 -1
- package/dist/openai.js +7 -0
- package/dist/system-model.d.ts +13 -0
- package/dist/system-model.d.ts.map +1 -1
- package/dist/system-model.js +13 -0
- package/package.json +22 -8
- package/src/core/browser-host.ts +339 -0
- package/src/core/engine.ts +540 -0
- package/src/core/errors.ts +220 -0
- package/src/core/event-gate.ts +71 -0
- package/src/core/generated/inderun-api.ts +19 -0
- package/src/core/host.ts +15 -0
- package/src/core/provider.ts +231 -0
- package/src/core/registry.ts +48 -0
- package/src/core/route-planner.ts +261 -0
- package/src/core/router.ts +149 -0
- package/src/core/sse.ts +134 -0
- package/src/core/telemetry.ts +20 -0
- package/src/index.ts +147 -0
- package/src/onnx.ts +57 -0
- package/src/openai.ts +21 -0
- package/src/providers/onnx/provider.ts +300 -0
- package/src/providers/onnx/runtime.ts +188 -0
- package/src/providers/onnx/transformers-runtime.ts +345 -0
- package/src/providers/openai/provider.ts +765 -0
- package/src/providers/system-model/chrome-runtime.ts +221 -0
- package/src/providers/system-model/provider.ts +260 -0
- package/src/providers/system-model/runtime.ts +202 -0
- package/src/system-model.ts +49 -0
- package/src/web-sdk.ts +114 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SystemModelRuntimeError,
|
|
3
|
+
type SystemModelAvailability,
|
|
4
|
+
type SystemModelGenerationInput,
|
|
5
|
+
type SystemModelGenerationOutput,
|
|
6
|
+
type SystemModelRuntime
|
|
7
|
+
} from "./runtime.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Structural view of the Chrome/Edge Prompt API's `LanguageModel` global this runtime uses.
|
|
11
|
+
*
|
|
12
|
+
* Typed structurally (not imported from `@types/dom`) since the Prompt API is not yet part of
|
|
13
|
+
* stable browser type definitions. See https://developer.chrome.com/docs/ai/prompt-api.
|
|
14
|
+
*/
|
|
15
|
+
export interface PromptApiLanguageModel {
|
|
16
|
+
availability: (options?: Record<string, unknown>) => Promise<string>;
|
|
17
|
+
create: (options?: Record<string, unknown>) => Promise<PromptApiSession>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Structural view of a Prompt API session. */
|
|
21
|
+
export interface PromptApiSession {
|
|
22
|
+
prompt: (input: string, options?: { signal?: AbortSignal }) => Promise<string>;
|
|
23
|
+
destroy: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generation controls forwarded to `LanguageModel.availability()` and `LanguageModel.create()`.
|
|
28
|
+
*
|
|
29
|
+
* Chrome's docs require passing the same options to both calls, since availability can depend on
|
|
30
|
+
* the requested configuration.
|
|
31
|
+
*/
|
|
32
|
+
export interface ChromePromptApiGenerationOptions {
|
|
33
|
+
temperature?: number;
|
|
34
|
+
topK?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Configuration for the Chrome Prompt API-backed system-model runtime.
|
|
39
|
+
*/
|
|
40
|
+
export interface ChromePromptApiRuntimeOptions {
|
|
41
|
+
/** Generation controls passed to both `availability()` and `create()`. */
|
|
42
|
+
generation?: ChromePromptApiGenerationOptions;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Creates the default Web system-model runtime, backed by Chrome's built-in Prompt API
|
|
47
|
+
* (`LanguageModel`).
|
|
48
|
+
*
|
|
49
|
+
* The browser owns model availability, download, and execution; this runtime never bundles or
|
|
50
|
+
* fetches a model itself. When `LanguageModel` is not defined (unsupported browser, disabled
|
|
51
|
+
* flag, or origin trial not enrolled) the runtime reports itself unavailable instead of throwing,
|
|
52
|
+
* so routing degrades to an explainable `capability_unavailable` rejection.
|
|
53
|
+
*
|
|
54
|
+
* @param options - Generation control overrides forwarded to the Prompt API.
|
|
55
|
+
*/
|
|
56
|
+
export function createChromePromptApiRuntime(
|
|
57
|
+
options: ChromePromptApiRuntimeOptions = {}
|
|
58
|
+
): SystemModelRuntime {
|
|
59
|
+
const promptOptions = toPromptApiOptions(options.generation);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
async availability(): Promise<SystemModelAvailability> {
|
|
63
|
+
const languageModel = getLanguageModel();
|
|
64
|
+
if (!languageModel) {
|
|
65
|
+
return {
|
|
66
|
+
kind: "api_missing",
|
|
67
|
+
reason:
|
|
68
|
+
"system model API missing: 'LanguageModel' is not defined on this browser. See https://developer.chrome.com/docs/ai/prompt-api."
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let result: string;
|
|
73
|
+
try {
|
|
74
|
+
result = await languageModel.availability(promptOptions);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
return mapAvailabilityError(err);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
switch (result) {
|
|
80
|
+
case "available":
|
|
81
|
+
return { kind: "available" };
|
|
82
|
+
case "downloadable":
|
|
83
|
+
return {
|
|
84
|
+
kind: "downloadable",
|
|
85
|
+
reason: "model downloadable: the model can be downloaded but is not installed yet."
|
|
86
|
+
};
|
|
87
|
+
case "downloading":
|
|
88
|
+
return {
|
|
89
|
+
kind: "downloading",
|
|
90
|
+
reason: "model downloading: the model is currently downloading."
|
|
91
|
+
};
|
|
92
|
+
case "unavailable":
|
|
93
|
+
return {
|
|
94
|
+
kind: "model_unavailable",
|
|
95
|
+
reason: "model unavailable: the model is unavailable on this device."
|
|
96
|
+
};
|
|
97
|
+
default:
|
|
98
|
+
return {
|
|
99
|
+
kind: "unavailable",
|
|
100
|
+
reason: `provider temporarily unavailable: unrecognized availability result '${result}'.`
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
async generate(
|
|
106
|
+
input: SystemModelGenerationInput,
|
|
107
|
+
signal?: AbortSignal
|
|
108
|
+
): Promise<SystemModelGenerationOutput> {
|
|
109
|
+
const languageModel = getLanguageModel();
|
|
110
|
+
if (!languageModel) {
|
|
111
|
+
throw new SystemModelRuntimeError(
|
|
112
|
+
"capability",
|
|
113
|
+
"system model API missing: 'LanguageModel' is not defined on this browser."
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let session: PromptApiSession;
|
|
118
|
+
try {
|
|
119
|
+
session = await languageModel.create({ ...promptOptions, signal });
|
|
120
|
+
} catch (err) {
|
|
121
|
+
throw mapGenerateError(err);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const text = await session.prompt(
|
|
126
|
+
toPromptText(input),
|
|
127
|
+
signal !== undefined ? { signal } : {}
|
|
128
|
+
);
|
|
129
|
+
return { text };
|
|
130
|
+
} catch (err) {
|
|
131
|
+
throw mapGenerateError(err);
|
|
132
|
+
} finally {
|
|
133
|
+
session.destroy();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getLanguageModel(): PromptApiLanguageModel | undefined {
|
|
140
|
+
return (globalThis as { LanguageModel?: PromptApiLanguageModel }).LanguageModel;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function toPromptApiOptions(
|
|
144
|
+
generation?: ChromePromptApiGenerationOptions
|
|
145
|
+
): Record<string, unknown> {
|
|
146
|
+
const promptOptions: Record<string, unknown> = {};
|
|
147
|
+
if (generation?.temperature !== undefined) promptOptions.temperature = generation.temperature;
|
|
148
|
+
if (generation?.topK !== undefined) promptOptions.topK = generation.topK;
|
|
149
|
+
return promptOptions;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function toPromptText(input: SystemModelGenerationInput): string {
|
|
153
|
+
return input.messages.map((message) => `${message.role}: ${message.content}`).join("\n");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function mapAvailabilityError(error: unknown): SystemModelAvailability {
|
|
157
|
+
const name = getDomExceptionName(error);
|
|
158
|
+
if (name === "NotSupportedError") {
|
|
159
|
+
return {
|
|
160
|
+
kind: "browser_unsupported",
|
|
161
|
+
reason: `browser unsupported: ${getErrorMessage(error)}`
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (name === "NotAllowedError" || name === "SecurityError") {
|
|
165
|
+
return {
|
|
166
|
+
kind: "feature_disabled",
|
|
167
|
+
reason: `browser feature disabled: ${getErrorMessage(error)}`
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
kind: "unavailable",
|
|
172
|
+
reason: `provider temporarily unavailable: ${getErrorMessage(error)}`
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function mapGenerateError(error: unknown): SystemModelRuntimeError {
|
|
177
|
+
if (error instanceof SystemModelRuntimeError) return error;
|
|
178
|
+
|
|
179
|
+
const name = getDomExceptionName(error);
|
|
180
|
+
switch (name) {
|
|
181
|
+
case "NotSupportedError":
|
|
182
|
+
return new SystemModelRuntimeError(
|
|
183
|
+
"capability",
|
|
184
|
+
`hardware unsupported: ${getErrorMessage(error)}`,
|
|
185
|
+
error
|
|
186
|
+
);
|
|
187
|
+
case "NotAllowedError":
|
|
188
|
+
case "SecurityError":
|
|
189
|
+
return new SystemModelRuntimeError(
|
|
190
|
+
"capability",
|
|
191
|
+
`browser feature disabled: ${getErrorMessage(error)}`,
|
|
192
|
+
error
|
|
193
|
+
);
|
|
194
|
+
case "QuotaExceededError":
|
|
195
|
+
return new SystemModelRuntimeError(
|
|
196
|
+
"unavailable",
|
|
197
|
+
`storage or network constraints: ${getErrorMessage(error)}`,
|
|
198
|
+
error
|
|
199
|
+
);
|
|
200
|
+
case "AbortError":
|
|
201
|
+
return new SystemModelRuntimeError(
|
|
202
|
+
"timeout",
|
|
203
|
+
`system model generation was aborted: ${getErrorMessage(error)}`,
|
|
204
|
+
error
|
|
205
|
+
);
|
|
206
|
+
default:
|
|
207
|
+
return new SystemModelRuntimeError(
|
|
208
|
+
"internal",
|
|
209
|
+
`system model generation failed: ${getErrorMessage(error)}`,
|
|
210
|
+
error
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function getDomExceptionName(error: unknown): string | undefined {
|
|
216
|
+
return error instanceof DOMException ? error.name : undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function getErrorMessage(error: unknown): string {
|
|
220
|
+
return error instanceof Error ? error.message : String(error);
|
|
221
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import type { TaskRequest, TaskResult } from "@independo/inderun-contracts";
|
|
2
|
+
import {
|
|
3
|
+
createCapabilityMismatch,
|
|
4
|
+
createInternal,
|
|
5
|
+
createTimeout,
|
|
6
|
+
createUnavailable,
|
|
7
|
+
toIndeRunException,
|
|
8
|
+
type IndeRunException
|
|
9
|
+
} from "../../core/errors.js";
|
|
10
|
+
import type { HostServices } from "../../core/host.js";
|
|
11
|
+
import { createChromePromptApiRuntime } from "./chrome-runtime.js";
|
|
12
|
+
import {
|
|
13
|
+
SystemModelRuntimeError,
|
|
14
|
+
type SystemModelAvailability,
|
|
15
|
+
type SystemModelPromptMessage,
|
|
16
|
+
type SystemModelRuntime
|
|
17
|
+
} from "./runtime.js";
|
|
18
|
+
import type {
|
|
19
|
+
ProviderAdapter,
|
|
20
|
+
ProviderDescriptor,
|
|
21
|
+
ProviderDynamicCapabilities,
|
|
22
|
+
RunContext
|
|
23
|
+
} from "../../core/provider.js";
|
|
24
|
+
|
|
25
|
+
/** Default provider id of the Web member of the system-model provider family. */
|
|
26
|
+
export const DEFAULT_SYSTEM_MODEL_WEB_PROVIDER_ID = "local.system-model.web";
|
|
27
|
+
|
|
28
|
+
/** Task kind this provider implements in Milestone 2 (Mode 1 only). */
|
|
29
|
+
const TEXT_TO_TEXT = "text_to_text";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Configuration for the Web system-model provider.
|
|
33
|
+
*/
|
|
34
|
+
export interface SystemModelProviderOptions {
|
|
35
|
+
/**
|
|
36
|
+
* Provider id exposed in telemetry and routing explanations.
|
|
37
|
+
*/
|
|
38
|
+
id?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Runtime implementation used to check availability and execute generation. Defaults to
|
|
41
|
+
* `createChromePromptApiRuntime()`; pass `createFixtureSystemModelRuntime()` from
|
|
42
|
+
* `system-model.js` for tests and offline demos.
|
|
43
|
+
*/
|
|
44
|
+
runtime?: SystemModelRuntime;
|
|
45
|
+
/**
|
|
46
|
+
* Optional generation timeout in milliseconds. A request-level `constraints.timeoutMs` wins.
|
|
47
|
+
*/
|
|
48
|
+
timeoutMs?: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Local provider adapter that executes Mode-1 text-to-text requests against a browser-managed
|
|
53
|
+
* on-device model (for example Chrome's Prompt API / Gemini Nano).
|
|
54
|
+
*
|
|
55
|
+
* Unlike the ONNX Runtime provider family, this provider takes no developer-supplied model: the
|
|
56
|
+
* browser owns model availability, download, and execution. The adapter owns the IndeRun-facing
|
|
57
|
+
* contract (descriptor, capability checks, error taxonomy) and delegates everything else to an
|
|
58
|
+
* injectable {@link SystemModelRuntime}.
|
|
59
|
+
*/
|
|
60
|
+
export class SystemModelWebProvider implements ProviderAdapter {
|
|
61
|
+
private readonly id: string;
|
|
62
|
+
private readonly runtime: SystemModelRuntime;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Creates a Web system-model provider.
|
|
66
|
+
* @param options - Optional id, runtime, and timeout overrides.
|
|
67
|
+
*/
|
|
68
|
+
constructor(private readonly options: SystemModelProviderOptions = {}) {
|
|
69
|
+
this.id = options.id ?? DEFAULT_SYSTEM_MODEL_WEB_PROVIDER_ID;
|
|
70
|
+
this.runtime = options.runtime ?? createChromePromptApiRuntime();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Returns static metadata used by the router for deterministic provider selection.
|
|
75
|
+
*/
|
|
76
|
+
describe(): ProviderDescriptor {
|
|
77
|
+
return {
|
|
78
|
+
id: this.id,
|
|
79
|
+
type: "local",
|
|
80
|
+
transport: "system_service",
|
|
81
|
+
supports: {
|
|
82
|
+
run: true,
|
|
83
|
+
streaming: false,
|
|
84
|
+
realtime: false,
|
|
85
|
+
tools: false,
|
|
86
|
+
reasoningEvents: false,
|
|
87
|
+
structuredOutput: false,
|
|
88
|
+
multimodal: false
|
|
89
|
+
},
|
|
90
|
+
cancel: "soft",
|
|
91
|
+
tasks: [TEXT_TO_TEXT],
|
|
92
|
+
privacy: {
|
|
93
|
+
dataLeavesDevice: false
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Reports dynamic provider availability for the current host.
|
|
100
|
+
*
|
|
101
|
+
* Every failure below is provider-internal detail: the shared route planner flattens it into a
|
|
102
|
+
* single `capability_unavailable` rejection carrying the returned reason.
|
|
103
|
+
*
|
|
104
|
+
* @param _host - Host services available to the engine (unused; the browser owns execution).
|
|
105
|
+
*/
|
|
106
|
+
async capabilities(_host: HostServices): Promise<ProviderDynamicCapabilities> {
|
|
107
|
+
const availability = await this.runtime.availability();
|
|
108
|
+
return toDynamicCapabilities(availability);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Executes a normalized text-to-text task against the browser-managed on-device model.
|
|
113
|
+
* @param request - Canonical IndeRun task request.
|
|
114
|
+
* @param context - Engine execution context containing run id and host services.
|
|
115
|
+
* @returns Normalized IndeRun task result.
|
|
116
|
+
* @throws IndeRunException mapped to the standard error taxonomy.
|
|
117
|
+
*/
|
|
118
|
+
async run(request: TaskRequest, context: RunContext): Promise<TaskResult> {
|
|
119
|
+
const availability = await this.runtime.availability();
|
|
120
|
+
const capabilities = toDynamicCapabilities(availability);
|
|
121
|
+
if (!capabilities.available) {
|
|
122
|
+
throw createCapabilityMismatch(
|
|
123
|
+
capabilities.reason ?? "Web system-model provider is unavailable on this host.",
|
|
124
|
+
{ providerId: this.id, runId: context.runId }
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const messages = createMessages(request);
|
|
129
|
+
if (messages.length === 0) {
|
|
130
|
+
throw createInternal("Web system-model provider requires a prompt or messages.", {
|
|
131
|
+
providerId: this.id,
|
|
132
|
+
runId: context.runId
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const timeoutMs = request.constraints?.timeoutMs ?? this.options.timeoutMs;
|
|
137
|
+
const controller = new AbortController();
|
|
138
|
+
const timer =
|
|
139
|
+
timeoutMs !== undefined && timeoutMs > 0
|
|
140
|
+
? setTimeout(() => controller.abort(), timeoutMs)
|
|
141
|
+
: undefined;
|
|
142
|
+
|
|
143
|
+
let output: Awaited<ReturnType<SystemModelRuntime["generate"]>>;
|
|
144
|
+
try {
|
|
145
|
+
output = await this.runtime.generate(
|
|
146
|
+
{
|
|
147
|
+
messages,
|
|
148
|
+
...(request.generation !== undefined ? { generation: request.generation } : {})
|
|
149
|
+
},
|
|
150
|
+
controller.signal
|
|
151
|
+
);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
throw this.mapRuntimeError(err, context);
|
|
154
|
+
} finally {
|
|
155
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (typeof output.text !== "string" || output.text.length === 0) {
|
|
159
|
+
throw createInternal("Web system-model provider returned no text output.", {
|
|
160
|
+
providerId: this.id,
|
|
161
|
+
runId: context.runId
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
schemaVersion: "1.0",
|
|
167
|
+
runId: context.runId,
|
|
168
|
+
output: {
|
|
169
|
+
type: "text",
|
|
170
|
+
text: output.text
|
|
171
|
+
},
|
|
172
|
+
finishReason: output.finishReason ?? "stop",
|
|
173
|
+
telemetry: {
|
|
174
|
+
providerUsed: this.id,
|
|
175
|
+
totalMs: 0
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private mapRuntimeError(error: unknown, context: RunContext): IndeRunException {
|
|
181
|
+
const extra = { providerId: this.id, runId: context.runId };
|
|
182
|
+
|
|
183
|
+
if (error instanceof SystemModelRuntimeError) {
|
|
184
|
+
const details =
|
|
185
|
+
error.originalError !== undefined
|
|
186
|
+
? { details: { originalError: getErrorSummary(error.originalError) } }
|
|
187
|
+
: {};
|
|
188
|
+
|
|
189
|
+
switch (error.kind) {
|
|
190
|
+
case "capability":
|
|
191
|
+
return createCapabilityMismatch(error.message, { ...extra, ...details });
|
|
192
|
+
case "unavailable":
|
|
193
|
+
return createUnavailable(error.message, { ...extra, ...details });
|
|
194
|
+
case "timeout":
|
|
195
|
+
return createTimeout(error.message, { ...extra, ...details });
|
|
196
|
+
default:
|
|
197
|
+
return createInternal(error.message, { ...extra, ...details });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (isAbortError(error)) {
|
|
202
|
+
return createTimeout("Web system-model generation timed out.", extra);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return toIndeRunException(error, extra);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const REASON_PREFIX: Record<SystemModelAvailability["kind"], string | undefined> = {
|
|
210
|
+
available: undefined,
|
|
211
|
+
downloadable: "model downloadable",
|
|
212
|
+
downloading: "model downloading",
|
|
213
|
+
model_unavailable: "model unavailable",
|
|
214
|
+
api_missing: "system model API missing",
|
|
215
|
+
browser_unsupported: "browser unsupported",
|
|
216
|
+
feature_disabled: "browser feature disabled",
|
|
217
|
+
hardware_unsupported: "hardware unsupported",
|
|
218
|
+
resource_constrained: "storage or network constraints",
|
|
219
|
+
unavailable: "provider temporarily unavailable"
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
function toDynamicCapabilities(availability: SystemModelAvailability): ProviderDynamicCapabilities {
|
|
223
|
+
if (availability.kind === "available") {
|
|
224
|
+
return { available: true };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const prefix = REASON_PREFIX[availability.kind];
|
|
228
|
+
const reason = availability.reason
|
|
229
|
+
? `${prefix}: ${availability.reason}`
|
|
230
|
+
: `${prefix}: the Web system-model provider is unavailable.`;
|
|
231
|
+
|
|
232
|
+
return { available: false, reason };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function createMessages(request: TaskRequest): SystemModelPromptMessage[] {
|
|
236
|
+
if (request.messages && request.messages.length > 0) {
|
|
237
|
+
return request.messages.map((message) => ({
|
|
238
|
+
role: message.role,
|
|
239
|
+
content: message.content
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const prompt = request.prompt;
|
|
244
|
+
return prompt !== undefined && prompt.length > 0 ? [{ role: "user", content: prompt }] : [];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function isAbortError(error: unknown): boolean {
|
|
248
|
+
return (
|
|
249
|
+
typeof error === "object" &&
|
|
250
|
+
error !== null &&
|
|
251
|
+
(error as { name?: unknown }).name === "AbortError"
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function getErrorSummary(error: unknown): Record<string, unknown> {
|
|
256
|
+
if (error instanceof Error) {
|
|
257
|
+
return { name: error.name, message: error.message, stack: error.stack };
|
|
258
|
+
}
|
|
259
|
+
return { message: String(error) };
|
|
260
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { TaskRequest, TaskResult } from "@independo/inderun-contracts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Capability state reported by a system-model runtime.
|
|
5
|
+
*
|
|
6
|
+
* The `reason` string uses the provider-internal capability vocabulary documented in
|
|
7
|
+
* `docs/architecture/web-system-model-provider-family.md`. It is not a public enum: the provider
|
|
8
|
+
* flattens every non-`available` kind into a single `capability_unavailable` route rejection and
|
|
9
|
+
* carries the reason as human-readable text.
|
|
10
|
+
*/
|
|
11
|
+
export type SystemModelAvailabilityKind =
|
|
12
|
+
| "available"
|
|
13
|
+
| "downloadable"
|
|
14
|
+
| "downloading"
|
|
15
|
+
| "model_unavailable"
|
|
16
|
+
| "api_missing"
|
|
17
|
+
| "browser_unsupported"
|
|
18
|
+
| "feature_disabled"
|
|
19
|
+
| "hardware_unsupported"
|
|
20
|
+
| "resource_constrained"
|
|
21
|
+
| "unavailable";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Availability snapshot reported by a system-model runtime.
|
|
25
|
+
*/
|
|
26
|
+
export interface SystemModelAvailability {
|
|
27
|
+
/** The specific availability state the runtime observed. */
|
|
28
|
+
kind: SystemModelAvailabilityKind;
|
|
29
|
+
/** Human-readable detail describing the state; expected for every non-`available` kind. */
|
|
30
|
+
reason?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Normalized conversation turn handed to a system-model runtime.
|
|
35
|
+
*/
|
|
36
|
+
export interface SystemModelPromptMessage {
|
|
37
|
+
/** The role of the author of this turn. */
|
|
38
|
+
role: "system" | "user" | "assistant";
|
|
39
|
+
/** Plain-text content of this turn. */
|
|
40
|
+
content: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Normalized generation request handed to a system-model runtime.
|
|
45
|
+
*/
|
|
46
|
+
export interface SystemModelGenerationInput {
|
|
47
|
+
/** Conversation turns, normalized from `prompt` or `messages` by the provider. */
|
|
48
|
+
messages: SystemModelPromptMessage[];
|
|
49
|
+
/** Optional generation controls copied from the task request. */
|
|
50
|
+
generation?: NonNullable<TaskRequest["generation"]>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Normalized generation result returned by a system-model runtime.
|
|
55
|
+
*/
|
|
56
|
+
export interface SystemModelGenerationOutput {
|
|
57
|
+
/** The generated text. */
|
|
58
|
+
text: string;
|
|
59
|
+
/** Optional normalized finish reason; the provider defaults to `stop`. */
|
|
60
|
+
finishReason?: TaskResult["finishReason"];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Failure category a runtime can signal so the provider maps it onto the IndeRun error taxonomy.
|
|
65
|
+
*
|
|
66
|
+
* - `capability`: the browser or hardware cannot serve this request (`CapabilityMismatch`).
|
|
67
|
+
* - `unavailable`: resource exhaustion or transient failure (`Unavailable`).
|
|
68
|
+
* - `timeout`: generation exceeded its budget or was aborted (`Timeout`).
|
|
69
|
+
* - `internal`: unexpected runtime failure (`Internal`).
|
|
70
|
+
*/
|
|
71
|
+
export type SystemModelRuntimeErrorKind = "capability" | "unavailable" | "timeout" | "internal";
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Error type system-model runtime implementations throw to steer IndeRun error normalization.
|
|
75
|
+
*
|
|
76
|
+
* Anything else thrown by a runtime is normalized to `Internal`.
|
|
77
|
+
*/
|
|
78
|
+
export class SystemModelRuntimeError extends Error {
|
|
79
|
+
/** Failure category used by the provider to select an IndeRun error class. */
|
|
80
|
+
readonly kind: SystemModelRuntimeErrorKind;
|
|
81
|
+
/** Optional underlying error captured for telemetry details. */
|
|
82
|
+
readonly originalError?: unknown;
|
|
83
|
+
|
|
84
|
+
constructor(kind: SystemModelRuntimeErrorKind, message: string, originalError?: unknown) {
|
|
85
|
+
super(message);
|
|
86
|
+
this.name = "SystemModelRuntimeError";
|
|
87
|
+
this.kind = kind;
|
|
88
|
+
if (originalError !== undefined) this.originalError = originalError;
|
|
89
|
+
|
|
90
|
+
Object.setPrototypeOf(this, SystemModelRuntimeError.prototype);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Injectable seam between the IndeRun Web system-model provider and the browser's on-device model
|
|
96
|
+
* API.
|
|
97
|
+
*
|
|
98
|
+
* IndeRun ships a Chrome Prompt API-backed implementation as the default and a deterministic
|
|
99
|
+
* fixture for tests. The seam exposes only Mode-1 generation (no session object, no streaming
|
|
100
|
+
* method) so streaming/session support cannot leak in even as unused scaffolding.
|
|
101
|
+
*/
|
|
102
|
+
export interface SystemModelRuntime {
|
|
103
|
+
/**
|
|
104
|
+
* Checks browser API presence and model readiness. Implementations must resolve with an
|
|
105
|
+
* availability snapshot rather than throwing.
|
|
106
|
+
*/
|
|
107
|
+
availability(): Promise<SystemModelAvailability>;
|
|
108
|
+
/**
|
|
109
|
+
* Runs one Mode-1 generation. Implementations should honor `signal` where the underlying API
|
|
110
|
+
* allows it.
|
|
111
|
+
*/
|
|
112
|
+
generate(
|
|
113
|
+
input: SystemModelGenerationInput,
|
|
114
|
+
signal?: AbortSignal
|
|
115
|
+
): Promise<SystemModelGenerationOutput>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Configuration for the deterministic fixture runtime.
|
|
120
|
+
*/
|
|
121
|
+
export interface FixtureSystemModelRuntimeOptions {
|
|
122
|
+
/** Availability snapshot returned by `availability`. Defaults to `{ kind: "available" }`. */
|
|
123
|
+
availability?: SystemModelAvailability;
|
|
124
|
+
/** Text or full output produced by `generate`. Defaults to a deterministic echo. */
|
|
125
|
+
respond?: (input: SystemModelGenerationInput) => string | SystemModelGenerationOutput;
|
|
126
|
+
/** Error thrown by `generate` instead of producing output. */
|
|
127
|
+
failWith?: unknown;
|
|
128
|
+
/** Artificial generation delay in milliseconds, used to exercise timeouts. */
|
|
129
|
+
delayMs?: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Creates a deterministic in-memory system-model runtime.
|
|
134
|
+
*
|
|
135
|
+
* This is the test seam for the Web system-model provider family: it proves capability checks,
|
|
136
|
+
* routing, and error normalization without depending on a real browser Prompt API implementation.
|
|
137
|
+
*
|
|
138
|
+
* @param options - Scripted availability, response, failure, and delay behavior.
|
|
139
|
+
*/
|
|
140
|
+
export function createFixtureSystemModelRuntime(
|
|
141
|
+
options: FixtureSystemModelRuntimeOptions = {}
|
|
142
|
+
): SystemModelRuntime {
|
|
143
|
+
const availability = options.availability ?? { kind: "available" };
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
async availability(): Promise<SystemModelAvailability> {
|
|
147
|
+
return availability;
|
|
148
|
+
},
|
|
149
|
+
async generate(
|
|
150
|
+
input: SystemModelGenerationInput,
|
|
151
|
+
signal?: AbortSignal
|
|
152
|
+
): Promise<SystemModelGenerationOutput> {
|
|
153
|
+
if (options.delayMs !== undefined && options.delayMs > 0) {
|
|
154
|
+
await delay(options.delayMs, signal);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
throwIfAborted(signal);
|
|
158
|
+
|
|
159
|
+
if (options.failWith !== undefined) {
|
|
160
|
+
throw options.failWith;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const response = options.respond ? options.respond(input) : createFixtureText(input.messages);
|
|
164
|
+
|
|
165
|
+
return typeof response === "string" ? { text: response } : response;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function createFixtureText(messages: SystemModelPromptMessage[]): string {
|
|
171
|
+
const lastUserMessage = [...messages].reverse().find((message) => message.role === "user");
|
|
172
|
+
const prompt = lastUserMessage?.content ?? "";
|
|
173
|
+
return `[fixture:system-model] ${prompt}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function delay(ms: number, signal?: AbortSignal): Promise<void> {
|
|
177
|
+
return new Promise<void>((resolve, reject) => {
|
|
178
|
+
const timer = setTimeout(() => {
|
|
179
|
+
signal?.removeEventListener("abort", onAbort);
|
|
180
|
+
resolve();
|
|
181
|
+
}, ms);
|
|
182
|
+
|
|
183
|
+
function onAbort(): void {
|
|
184
|
+
clearTimeout(timer);
|
|
185
|
+
reject(
|
|
186
|
+
new SystemModelRuntimeError("timeout", "Fixture system-model generation was aborted.")
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (signal?.aborted) {
|
|
191
|
+
onAbort();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function throwIfAborted(signal?: AbortSignal): void {
|
|
199
|
+
if (signal?.aborted) {
|
|
200
|
+
throw new SystemModelRuntimeError("timeout", "Fixture system-model generation was aborted.");
|
|
201
|
+
}
|
|
202
|
+
}
|