modelpact-providers 1.0.0 → 1.2.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.
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Chrome's built-in model, through the Prompt API.
3
+ *
4
+ * The one backend that keeps the conversation itself: `LanguageModel` is a
5
+ * session object, `prompt()` appends to it, and `request.history` is therefore
6
+ * read by nobody here. Two more things it owns that the others do not — it
7
+ * fires its own `contextoverflow`, which is forwarded rather than re-derived,
8
+ * and it reports usage against a window it decides.
9
+ *
10
+ * It lives in a page and nowhere else. In node the global is missing, and
11
+ * `access` answers `unavailable` with `unsupported` rather than throwing.
12
+ *
13
+ * The declarations are `@types/dom-chromium-ai`, patched under `patches/` to
14
+ * close the gap between the IDL and the spec: several states the algorithm
15
+ * rejects at runtime are writable in the IDL, and a TS error at the keyboard
16
+ * beats a `TypeError` in the browser. They are named in function bodies and
17
+ * nowhere in a signature that leaves this file: an exported type naming
18
+ * `LanguageModel` puts the same name in the emitted `.d.ts`, and a consumer
19
+ * who has not installed those declarations, or who lists `types` explicitly,
20
+ * then cannot compile this package at all. `src/surface.ts` is the guard that
21
+ * keeps it that way, and the WebGPU backend next door is where the lesson
22
+ * was learned.
23
+ */
24
+ import { AiError, contextUsage, createProvider, err, failureFromError, fraction, ok, runTool, tokens, } from "modelpact/backend";
25
+ /**
26
+ * The page's own class, or nothing.
27
+ *
28
+ * There is no way to hand one in. A polyfill is already this — it defines the
29
+ * global — and a test sets the global too, which is the same path the browser
30
+ * takes rather than one beside it.
31
+ *
32
+ * `typeof` and not a bare read: outside a page the name is not declared, and
33
+ * naming it there throws rather than answering undefined.
34
+ */
35
+ const getPlatformApi = () => typeof LanguageModel === "undefined" ? null : LanguageModel;
36
+ const toExpectations = (askedExpectations) => {
37
+ if (askedExpectations === undefined || askedExpectations.length === 0)
38
+ return undefined;
39
+ return askedExpectations.map((expectation) => ({
40
+ type: expectation.type,
41
+ ...(expectation.languages === undefined
42
+ ? {}
43
+ : { languages: [...expectation.languages] }),
44
+ }));
45
+ };
46
+ const NEVER_ABORTED = new AbortController().signal;
47
+ const makeToolTurn = () => ({ signal: NEVER_ABORTED, failure: null });
48
+ const asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value)
49
+ ? value
50
+ : null;
51
+ /**
52
+ * The browser hands `execute` the model's arguments and nothing else. One
53
+ * plain object, which is what Chrome's examples pass, goes through as the
54
+ * input; anything else is kept whole under `arguments`. Unverified against a
55
+ * running call: no Chrome to date has created a session with tools here —
56
+ * 152 refuses at `create`, measured.
57
+ */
58
+ const toToolInput = (args) => asRecord(args[0]) ?? { arguments: [...args] };
59
+ const toPlatformTools = (tools, turn) => tools.map((tool) => ({
60
+ name: tool.name,
61
+ description: tool.description,
62
+ inputSchema: tool.inputSchema,
63
+ execute: async (...args) => {
64
+ const toolResult = await runTool(tool, toToolInput(args), turn.signal);
65
+ if (toolResult.ok)
66
+ return toolResult.value;
67
+ // Remembered as well as thrown: whatever the browser does with a
68
+ // rejected execute, the turn ends with this failure.
69
+ turn.failure = toolResult.error;
70
+ throw new AiError(toolResult.error);
71
+ },
72
+ }));
73
+ /** Chrome 152 refuses tools unless a tool-call output is expected; the spec text does not say so (measured). */
74
+ const withToolCallOutput = (outputs, hasTools) => hasTools ? [...(outputs ?? []), { type: "tool-call" }] : outputs;
75
+ const toCoreOptions = (request, turn) => {
76
+ const tools = request.tools ?? [];
77
+ const inputs = toExpectations(request.inputs);
78
+ const outputs = withToolCallOutput(toExpectations(request.outputs), tools.length > 0);
79
+ return {
80
+ ...(inputs === undefined ? {} : { expectedInputs: inputs }),
81
+ ...(outputs === undefined ? {} : { expectedOutputs: outputs }),
82
+ ...(tools.length === 0 ? {} : { tools: toPlatformTools(tools, turn) }),
83
+ };
84
+ };
85
+ /**
86
+ * The four the spec has, against the three the contract has. `downloading` and
87
+ * `downloadable` are one branch here and differ only in `started`, which is the
88
+ * question a caller actually has: is someone already fetching this.
89
+ */
90
+ const toAvailability = (specAvailability) => {
91
+ switch (specAvailability) {
92
+ case "available":
93
+ return { kind: "ready" };
94
+ case "downloadable":
95
+ return { kind: "needs-download", started: false };
96
+ case "downloading":
97
+ return { kind: "needs-download", started: true };
98
+ case "unavailable":
99
+ // Empty lists: the spec says no for the whole request without naming a
100
+ // part, and inventing one would be worse than saying nothing.
101
+ return {
102
+ kind: "unavailable",
103
+ reason: { kind: "unsupported-config", languages: [] },
104
+ };
105
+ }
106
+ };
107
+ const getAvailability = async (request) => {
108
+ const api = getPlatformApi();
109
+ if (api === null)
110
+ return { kind: "unavailable", reason: { kind: "unsupported" } };
111
+ try {
112
+ const specAvailability = await api.availability(toCoreOptions(request, makeToolTurn()));
113
+ return toAvailability(specAvailability);
114
+ }
115
+ catch (error) {
116
+ // `NotAllowedError` for the permissions policy, `InvalidStateError` for a
117
+ // document that is not fully active: both are refusals, not crashes.
118
+ return { kind: "unavailable", reason: failureFromError(error) };
119
+ }
120
+ };
121
+ /** System first or not at all, which is what the patched tuple type says too. */
122
+ const toInitialPrompts = (session) => {
123
+ const historyPrompts = (session.history ?? []).map((message) => ({ role: message.role, content: message.content }));
124
+ if (session.system === undefined)
125
+ return historyPrompts.length === 0 ? undefined : historyPrompts;
126
+ return [{ role: "system", content: session.system }, ...historyPrompts];
127
+ };
128
+ const toPromptOptions = (request) => request.schema === undefined
129
+ ? { signal: request.signal }
130
+ : { signal: request.signal, responseConstraint: request.schema };
131
+ /**
132
+ * `contextUsage` and `contextWindow` replaced `inputUsage` and `inputQuota`,
133
+ * and a browser in the field may have either pair. Read as unknown because the
134
+ * declarations promise the new names on a version that has only the old.
135
+ */
136
+ const readUsage = (session) => {
137
+ const sessionFields = session;
138
+ const used = readNumber(sessionFields, "contextUsage", "inputUsage");
139
+ const windowTokens = readNumber(sessionFields, "contextWindow", "inputQuota");
140
+ if (used === null || windowTokens === null)
141
+ return { kind: "unknown" };
142
+ const usedTokens = tokens(used);
143
+ return usedTokens === null
144
+ ? { kind: "unknown" }
145
+ : contextUsage(usedTokens, windowTokens);
146
+ };
147
+ const readNumber = (sessionFields, currentName, olderName) => {
148
+ const value = sessionFields[currentName] ?? sessionFields[olderName];
149
+ return typeof value === "number" ? value : null;
150
+ };
151
+ class PromptApiModel {
152
+ #session;
153
+ #turn;
154
+ constructor(session, turn) {
155
+ this.#session = session;
156
+ this.#turn = turn;
157
+ }
158
+ generateStream = (input, request) => {
159
+ try {
160
+ this.#beginTurn(request);
161
+ const deltas = this.#session.promptStreaming(input, toPromptOptions(request));
162
+ return Promise.resolve(ok(deltas.pipeThrough(this.#failIfToolFailed())));
163
+ }
164
+ catch (error) {
165
+ return Promise.resolve(err(this.#refineFailure(error)));
166
+ }
167
+ };
168
+ generateWhole = async (input, request) => {
169
+ try {
170
+ this.#beginTurn(request);
171
+ const answerText = await this.#session.prompt(input, toPromptOptions(request));
172
+ const toolFailure = this.#turn.failure;
173
+ return toolFailure === null ? ok(answerText) : err(toolFailure);
174
+ }
175
+ catch (error) {
176
+ return err(this.#refineFailure(error));
177
+ }
178
+ };
179
+ usage = () => readUsage(this.#session);
180
+ dispose = () => {
181
+ this.#session.destroy();
182
+ };
183
+ #beginTurn(request) {
184
+ this.#turn.signal = request.signal;
185
+ this.#turn.failure = null;
186
+ }
187
+ /** A stream that ended is not an answer if a tool failed on the way: the end is where that is checked. */
188
+ #failIfToolFailed() {
189
+ return new TransformStream({
190
+ flush: () => {
191
+ const toolFailure = this.#turn.failure;
192
+ if (toolFailure !== null)
193
+ throw new AiError(toolFailure);
194
+ },
195
+ });
196
+ }
197
+ /**
198
+ * `QuotaExceededError` carries no measurement, and the generic mapping says
199
+ * so; holding the session is what lets this one attach the reading. A tool
200
+ * failure outranks whatever the browser threw about it.
201
+ */
202
+ #refineFailure(error) {
203
+ const toolFailure = this.#turn.failure;
204
+ if (toolFailure !== null)
205
+ return toolFailure;
206
+ const failure = failureFromError(error);
207
+ return failure.kind === "context-overflow"
208
+ ? { ...failure, usage: this.usage() }
209
+ : failure;
210
+ }
211
+ }
212
+ const connectPromptApi = async (options) => {
213
+ const api = getPlatformApi();
214
+ if (api === null)
215
+ return err({ kind: "unsupported" });
216
+ const initialPrompts = toInitialPrompts(options.session);
217
+ const turn = makeToolTurn();
218
+ try {
219
+ const session = await api.create({
220
+ ...toCoreOptions(options.request, turn),
221
+ ...(initialPrompts === undefined ? {} : { initialPrompts }),
222
+ ...(options.session.signal === undefined
223
+ ? {}
224
+ : { signal: options.session.signal }),
225
+ // Translated rather than forwarded, though the browser's monitor would
226
+ // satisfy `DownloadMonitor` as it stands: going through the lifecycle's
227
+ // own is what applies the never-decreasing rule to every backend alike.
228
+ monitor: (monitor) => {
229
+ monitor.ondownloadprogress = (event) => {
230
+ const progress = fraction(event.total === 0 ? 0 : event.loaded / event.total);
231
+ if (progress !== null)
232
+ options.reportProgress(progress);
233
+ };
234
+ },
235
+ });
236
+ // Forwarded, not re-derived: by the time a listener reads the counters the
237
+ // dropped turns are gone, and `used` can be back under the window.
238
+ session.oncontextoverflow = () => {
239
+ options.reportOverflow();
240
+ };
241
+ return ok(new PromptApiModel(session, turn));
242
+ }
243
+ catch (error) {
244
+ return err(failureFromError(error));
245
+ }
246
+ };
247
+ export function makePromptApiProvider() {
248
+ const backend = {
249
+ name: "prompt-api",
250
+ // Text only, because that is all `AiMessage` carries. The model takes more,
251
+ // and asking for it would promise a message this contract cannot build.
252
+ modalities: ["text"],
253
+ // The platform executes them itself; whether a given Chrome will open such
254
+ // a session is its `create` to refuse, and that refusal is what `open` returns.
255
+ tools: true,
256
+ availability: (request) => getAvailability(request),
257
+ connect: (options) => connectPromptApi(options),
258
+ };
259
+ return createProvider(backend);
260
+ }
261
+ //# sourceMappingURL=prompt-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-api.js","sourceRoot":"","sources":["../src/prompt-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * A model in the tab itself, on WebGPU, through `@mlc-ai/web-llm`.
3
+ *
4
+ * `modelpact/backend` is the whole of what it reaches for, so if a backend
5
+ * cannot be built from what that door exports, this file is where it shows.
6
+ * It was written before this package existed, in a directory one repository
7
+ * over, to ask exactly that — and it needed nothing added to the contract.
8
+ *
9
+ * What the engine brings that the other backends do not: the download is the
10
+ * page's own, several hundred megabytes into browser storage, and it is the
11
+ * only one where `needs-download` costs the user their bandwidth rather than a
12
+ * daemon's.
13
+ */
14
+ import { type AiProvider } from "modelpact/backend";
15
+ /** A delta as the engine sends it; these are the only fields read. */
16
+ export interface EngineChunk {
17
+ readonly choices: readonly {
18
+ readonly delta: {
19
+ readonly content?: string | null;
20
+ };
21
+ }[];
22
+ readonly usage?: {
23
+ readonly total_tokens: number;
24
+ } | null;
25
+ }
26
+ export interface EngineRequest {
27
+ readonly model: string;
28
+ readonly messages: readonly {
29
+ readonly role: "system" | "user" | "assistant";
30
+ readonly content: string;
31
+ }[];
32
+ readonly stream: true;
33
+ readonly stream_options: {
34
+ readonly include_usage: true;
35
+ };
36
+ readonly response_format?: {
37
+ readonly type: "json_object";
38
+ readonly schema: string;
39
+ };
40
+ }
41
+ /**
42
+ * What this backend uses of the engine, which is less than the engine has.
43
+ *
44
+ * Named here and not imported, so that the emitted declarations name nothing
45
+ * from `@mlc-ai/web-llm`. Its own `.d.ts` files reference packages it does not
46
+ * depend on, and a consumer with `skipLibCheck` off inherits that break through
47
+ * a type they never asked for — the demo did, the moment it started resolving
48
+ * this package the way npm would. The Prompt API backend next door keeps
49
+ * `LanguageModel` out of its own declarations for the same reason.
50
+ *
51
+ * Method syntax on purpose: it is bivariant, which is what lets the real
52
+ * engine, with its overloads and wider request type, satisfy this narrower one
53
+ * without restating all of it.
54
+ */
55
+ export interface WebGpuEngine {
56
+ readonly chat: {
57
+ readonly completions: {
58
+ create(request: EngineRequest): Promise<AsyncIterable<EngineChunk>>;
59
+ };
60
+ };
61
+ unload(): Promise<void>;
62
+ interruptGenerate(): void;
63
+ }
64
+ export interface WebGpuConfig {
65
+ /** A tag from the engine's own catalogue, such as `SmolLM2-360M-Instruct-q4f16_1-MLC`. */
66
+ readonly model: string;
67
+ /** Tokens the session is measured against; the engine reports no window of its own. */
68
+ readonly contextWindow?: number;
69
+ /** For a test: an engine already built, instead of one loaded from the network. */
70
+ readonly engine?: (reportProgress: (progress: number) => void) => Promise<WebGpuEngine>;
71
+ }
72
+ export declare function makeWebGpuProvider(config: WebGpuConfig): AiProvider;
73
+ //# sourceMappingURL=webgpu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webgpu.d.ts","sourceRoot":"","sources":["../src/webgpu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EASL,KAAK,UAAU,EAQhB,MAAM,mBAAmB,CAAC;AAG3B,sEAAsE;AACtE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS;QACzB,QAAQ,CAAC,KAAK,EAAE;YAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE,CAAC;KACtD,EAAE,CAAC;IACJ,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC3D;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS;QAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;QAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;KAC1B,EAAE,CAAC;IACJ,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE;QAAE,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAA;KAAE,CAAC;IAC1D,QAAQ,CAAC,eAAe,CAAC,EAAE;QACzB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;QAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE;QACb,QAAQ,CAAC,WAAW,EAAE;YACpB,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC;SACrE,CAAC;KACH,CAAC;IACF,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,iBAAiB,IAAI,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,0FAA0F;IAC1F,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uFAAuF;IACvF,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,mFAAmF;IACnF,QAAQ,CAAC,MAAM,CAAC,EAAE,CAChB,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,KACvC,OAAO,CAAC,YAAY,CAAC,CAAC;CAC5B;AAyND,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,UAAU,CAQnE"}
package/dist/webgpu.js ADDED
@@ -0,0 +1,214 @@
1
+ /**
2
+ * A model in the tab itself, on WebGPU, through `@mlc-ai/web-llm`.
3
+ *
4
+ * `modelpact/backend` is the whole of what it reaches for, so if a backend
5
+ * cannot be built from what that door exports, this file is where it shows.
6
+ * It was written before this package existed, in a directory one repository
7
+ * over, to ask exactly that — and it needed nothing added to the contract.
8
+ *
9
+ * What the engine brings that the other backends do not: the download is the
10
+ * page's own, several hundred megabytes into browser storage, and it is the
11
+ * only one where `needs-download` costs the user their bandwidth rather than a
12
+ * daemon's.
13
+ */
14
+ import { contextUsage, createProvider, err, failureFromError, fraction, ok, tokens, } from "modelpact/backend";
15
+ import { CreateMLCEngine } from "@mlc-ai/web-llm";
16
+ const DEFAULTS = { contextWindow: 4096 };
17
+ const ZERO_TOKENS = tokens(0) ?? 0;
18
+ /** WebGPU or nothing: the engine has a WASM path, but not one worth offering. */
19
+ const hasWebGpu = () => typeof navigator !== "undefined" && "gpu" in navigator;
20
+ /**
21
+ * Weights live in the browser's cache, so "downloaded" is a question about
22
+ * this profile rather than about the machine. The engine exposes no such
23
+ * check, so the cache is asked directly.
24
+ */
25
+ const isCached = async (model) => {
26
+ if (typeof caches === "undefined")
27
+ return false;
28
+ try {
29
+ const cacheNames = await caches.keys();
30
+ for (const cacheName of cacheNames) {
31
+ const cache = await caches.open(cacheName);
32
+ const cachedRequests = await cache.keys();
33
+ if (cachedRequests.some((request) => request.url.includes(model)))
34
+ return true;
35
+ }
36
+ return false;
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ };
42
+ const getAvailability = async (config) => {
43
+ if (config.engine !== undefined)
44
+ return { kind: "ready" };
45
+ if (!hasWebGpu()) {
46
+ return {
47
+ kind: "unavailable",
48
+ reason: { kind: "unsupported-config", languages: [], modalities: [] },
49
+ };
50
+ }
51
+ const isModelCached = await isCached(config.model);
52
+ return isModelCached
53
+ ? { kind: "ready" }
54
+ : { kind: "needs-download", started: false };
55
+ };
56
+ /** The engine reports a stage and a percentage; only the number is ours to pass on. */
57
+ const readProgress = (initReport) => typeof initReport.progress === "number" ? initReport.progress : 0;
58
+ class WebGpuConnection {
59
+ #engine;
60
+ #model;
61
+ #contextWindow;
62
+ #system;
63
+ #usedTokens = 0;
64
+ constructor(engine, config, options) {
65
+ this.#engine = engine;
66
+ this.#model = config.model;
67
+ this.#contextWindow = config.contextWindow ?? DEFAULTS.contextWindow;
68
+ this.#system = options.session.system;
69
+ }
70
+ generateStream = async (input, request) => {
71
+ try {
72
+ const chunks = await this.#engine.chat.completions.create({
73
+ model: this.#model,
74
+ messages: this.#toEngineMessages(input, request),
75
+ stream: true,
76
+ stream_options: { include_usage: true },
77
+ ...(request.schema === undefined
78
+ ? {}
79
+ : {
80
+ response_format: {
81
+ type: "json_object",
82
+ schema: JSON.stringify(request.schema),
83
+ },
84
+ }),
85
+ });
86
+ return ok(this.#toDeltaStream(chunks));
87
+ }
88
+ catch (error) {
89
+ return err(failureFromError(error));
90
+ }
91
+ };
92
+ usage = () => contextUsage(tokens(this.#usedTokens) ?? ZERO_TOKENS, this.#contextWindow);
93
+ dispose = () => {
94
+ void this.#engine.unload();
95
+ };
96
+ #toEngineMessages(input, request) {
97
+ const historyMessages = request.history.map((message) => ({
98
+ role: message.role,
99
+ content: message.content,
100
+ }));
101
+ const askedMessage = { role: "user", content: input };
102
+ return this.#system === undefined
103
+ ? [...historyMessages, askedMessage]
104
+ : [
105
+ { role: "system", content: this.#system },
106
+ ...historyMessages,
107
+ askedMessage,
108
+ ];
109
+ }
110
+ /**
111
+ * An async iterable into a `ReadableStream`, because that is what the
112
+ * contract carries.
113
+ *
114
+ * The abort is deliberately not policed here. The lifecycle checks the signal
115
+ * around every read and errors the stream itself, in the vocabulary; a
116
+ * backend that raced it to the punch with a plain `Error` would land in
117
+ * `unknown` instead of `aborted`, which is what the conformance suite caught
118
+ * when this file did exactly that.
119
+ *
120
+ * What is left to do is tell the engine, since it takes no signal of its own:
121
+ * `cancel` reaches every way out — the reader stopping, the abort, the close.
122
+ */
123
+ #toDeltaStream(chunks) {
124
+ const iterator = chunks[Symbol.asyncIterator]();
125
+ return new ReadableStream({
126
+ // Loops until it can enqueue or close: a pull that returns empty-handed
127
+ // is not called again unless a read arrived meanwhile, and the engine
128
+ // sends chunks with no text — the usage one, a stop one — back to back.
129
+ // Found in the sibling package on `claude -p`, fixed here before it bit.
130
+ pull: async (controller) => {
131
+ for (;;) {
132
+ const iteration = await iterator.next();
133
+ if (iteration.done === true) {
134
+ controller.close();
135
+ return;
136
+ }
137
+ const chunk = iteration.value;
138
+ if (chunk.usage != null)
139
+ this.#usedTokens = chunk.usage.total_tokens;
140
+ const delta = chunk.choices[0]?.delta.content ?? "";
141
+ if (delta !== "") {
142
+ controller.enqueue(delta);
143
+ return;
144
+ }
145
+ }
146
+ },
147
+ cancel: async () => {
148
+ this.#engine.interruptGenerate();
149
+ await iterator.return?.();
150
+ },
151
+ });
152
+ }
153
+ }
154
+ /**
155
+ * The real engine behind `WebGpuEngine`. An adapter and not a cast: the real
156
+ * `create` is an overload set, and an overload set is not assignable to a
157
+ * single signature even when one member fits — so each call is written out,
158
+ * and the compiler resolves the streaming overload from `stream: true` and
159
+ * checks our request shape against it. Nothing here is asserted.
160
+ */
161
+ const loadEngine = async (model, reportProgress) => {
162
+ const realEngine = await CreateMLCEngine(model, {
163
+ initProgressCallback: (initReport) => {
164
+ reportProgress(readProgress(initReport));
165
+ },
166
+ });
167
+ return {
168
+ chat: {
169
+ completions: {
170
+ // A fresh array: ours is `readonly`, which is right for a request
171
+ // nobody should edit, and the engine's signature wants a mutable one.
172
+ create: (request) => realEngine.chat.completions.create({
173
+ ...request,
174
+ messages: [...request.messages],
175
+ }),
176
+ },
177
+ },
178
+ unload: () => realEngine.unload(),
179
+ interruptGenerate: () => {
180
+ // The engine's own is a promise, though its published interface says
181
+ // otherwise; dropped on purpose, and marked rather than left floating.
182
+ // By the time this runs the lifecycle has already errored the stream as
183
+ // `aborted`, so waiting for the engine to acknowledge would hold a turn
184
+ // whose caller has been answered.
185
+ void realEngine.interruptGenerate();
186
+ },
187
+ };
188
+ };
189
+ const connectEngine = async (config, options) => {
190
+ const reportProgress = (progress) => {
191
+ const progressFraction = fraction(progress);
192
+ if (progressFraction !== null)
193
+ options.reportProgress(progressFraction);
194
+ };
195
+ try {
196
+ const engine = config.engine === undefined
197
+ ? await loadEngine(config.model, reportProgress)
198
+ : await config.engine(reportProgress);
199
+ return ok(new WebGpuConnection(engine, config, options));
200
+ }
201
+ catch (error) {
202
+ return err(failureFromError(error));
203
+ }
204
+ };
205
+ export function makeWebGpuProvider(config) {
206
+ const backend = {
207
+ name: "webgpu",
208
+ modalities: ["text"],
209
+ availability: () => getAvailability(config),
210
+ connect: (options) => connectEngine(config, options),
211
+ };
212
+ return createProvider(backend);
213
+ }
214
+ //# sourceMappingURL=webgpu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webgpu.js","sourceRoot":"","sources":["../src/webgpu.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "modelpact-providers",
3
- "version": "1.0.0",
4
- "description": "Language-model backends written against the modelpact contract — one transport per runtime, each green on the contract suite",
3
+ "version": "1.2.0",
4
+ "description": "Language-model backends written against the modelpact contract — a daemon, the OpenAI dialect, the browser's own model, one in the tab, each green on the contract suite",
5
5
  "keywords": [
6
6
  "llm",
7
7
  "modelpact",
8
8
  "typescript",
9
9
  "ollama",
10
+ "openai",
11
+ "openai-compatible",
10
12
  "prompt-api",
11
13
  "webgpu",
12
14
  "on-device",
@@ -28,38 +30,60 @@
28
30
  ".": {
29
31
  "types": "./dist/index.d.ts",
30
32
  "import": "./dist/index.js"
33
+ },
34
+ "./webgpu": {
35
+ "types": "./dist/webgpu.d.ts",
36
+ "import": "./dist/webgpu.js"
31
37
  }
32
38
  },
33
39
  "files": [
34
40
  "dist"
35
41
  ],
42
+ "workspaces": [
43
+ "demo"
44
+ ],
36
45
  "sideEffects": false,
37
46
  "engines": {
38
47
  "node": ">=22"
39
48
  },
40
49
  "scripts": {
50
+ "prepare": "patch-package",
41
51
  "build": "tsc -p tsconfig.build.json",
42
52
  "test": "vitest run",
43
53
  "test:watch": "vitest",
54
+ "test:e2e": "playwright test",
55
+ "demo": "npm --prefix demo run dev",
56
+ "demo:check": "npm --prefix demo run typecheck",
44
57
  "lint": "eslint .",
45
58
  "lint:fix": "eslint . --fix",
46
59
  "format": "prettier --write .",
47
60
  "format:check": "prettier --check .",
48
- "typecheck": "tsc --noEmit",
61
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.patched.json && tsc -p e2e --noEmit",
62
+ "check:surface": "npm run build && tsc -p tsconfig.surface.json",
49
63
  "prepublishOnly": "npm run build"
50
64
  },
51
65
  "peerDependencies": {
52
- "modelpact": ">=2"
66
+ "modelpact": ">=2.2",
67
+ "@mlc-ai/web-llm": ">=0.2"
53
68
  },
54
69
  "devDependencies": {
55
70
  "@eslint/js": "^10.0.1",
71
+ "@playwright/test": "^1.62.1",
72
+ "@mlc-ai/web-llm": "^0.2.84",
73
+ "@types/dom-chromium-ai": "^0.0.17",
56
74
  "@types/node": "^26.4.1",
57
75
  "eslint": "^10.9.1",
58
76
  "eslint-config-prettier": "^10.1.8",
59
- "modelpact": "^2.2.0",
77
+ "modelpact": "^2.2.1",
78
+ "patch-package": "^8.0.1",
60
79
  "prettier": "^3.9.6",
61
80
  "typescript": "~6.0.3",
62
81
  "typescript-eslint": "^8.69.0",
63
82
  "vitest": "^4.1.11"
83
+ },
84
+ "peerDependenciesMeta": {
85
+ "@mlc-ai/web-llm": {
86
+ "optional": true
87
+ }
64
88
  }
65
89
  }