prompt-api-polyfill 1.2.2 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -179,8 +179,8 @@ including:
179
179
  - `LanguageModel.create()` with options
180
180
  - `prompt()` and `promptStreaming()`
181
181
  - Multimodal inputs (text, image, audio)
182
- - `append()` and `measureInputUsage()`
183
- - Quota handling via `onquotaoverflow`
182
+ - `append()` and `measureContextUsage()`
183
+ - Quota handling via `oncontextwindowoverflow`
184
184
  - `clone()` and `destroy()`
185
185
 
186
186
  A simplified version of how it is wired up:
@@ -236,18 +236,17 @@ This repo ships with a template file:
236
236
  // For Transformers.js:
237
237
  "device": "webgpu",
238
238
  "dtype": "q4f16",
239
-
240
239
  // Optional library-level overrides:
241
240
  "env": {
242
241
  "allowRemoteModels": true,
243
242
  "backends": {
244
243
  "onnx": {
245
244
  "wasm": {
246
- "wasmPaths": "https://cdn.example.com/wasm-assets/"
247
- }
248
- }
249
- }
250
- }
245
+ "wasmPaths": "https://cdn.example.com/wasm-assets/",
246
+ },
247
+ },
248
+ },
249
+ },
251
250
  }
252
251
  ```
253
252
 
@@ -460,21 +459,17 @@ export default class CustomBackend extends PolyfillBackend {
460
459
 
461
460
  ### Register your backend
462
461
 
463
- The polyfill uses a "First-Match Priority" strategy based on global
464
- configuration. You need to register your backend in the `prompt-api-polyfill.js`
465
- file by adding it to the static `#backends` array:
462
+ The polyfill uses an automated registration strategy. To register your new
463
+ backend, simply run the registration script:
466
464
 
467
- ```js
468
- // prompt-api-polyfill.js
469
- static #backends = [
470
- // ... existing backends
471
- {
472
- config: 'CUSTOM_CONFIG', // The global object to look for on `window`
473
- path: './backends/custom.js',
474
- },
475
- ];
465
+ ```bash
466
+ npm run generate:registry
476
467
  ```
477
468
 
469
+ This updates the `backends-registry.js` file, which the polyfill imports. The
470
+ registry contains the configuration mapping and a dynamic loader that ensures
471
+ compatibility with bundlers and CDNs.
472
+
478
473
  ### Set a default model
479
474
 
480
475
  Define the fallback model identity in `backends/defaults.js`. This is used when
@@ -490,9 +485,11 @@ export const DEFAULT_MODELS = {
490
485
 
491
486
  ### Enable local development and testing
492
487
 
493
- The project uses a discovery script (`scripts/list-backends.js`) to generate
494
- test matrices. To include your new backend in the test runner, create a
495
- `.env-[name].json` file (for example, `.env-custom.json`) in the root directory:
488
+ The project uses a discovery script (`scripts/backend-discovery.js`) to generate
489
+ test matrices and list active backends based on the presence of
490
+ `.env-[name].json` files. To include your new backend in the test runner, create
491
+ a `.env-[name].json` file (for example, `.env-custom.json`) in the root
492
+ directory:
496
493
 
497
494
  ```json
498
495
  {
@@ -501,6 +498,12 @@ test matrices. To include your new backend in the test runner, create a
501
498
  }
502
499
  ```
503
500
 
501
+ Then run the WPT generation script:
502
+
503
+ ```bash
504
+ npm run generate:wpt
505
+ ```
506
+
504
507
  ### Verify via Web Platform Tests (WPT)
505
508
 
506
509
  The final step is ensuring compliance. Because the polyfill is spec-driven, any
@@ -1,15 +1,15 @@
1
1
  import { Schema as T } from "firebase/ai";
2
2
  ReadableStream.prototype[Symbol.asyncIterator] || (ReadableStream.prototype[Symbol.asyncIterator] = async function* () {
3
- const l = this.getReader();
3
+ const s = this.getReader();
4
4
  try {
5
5
  for (; ; ) {
6
- const { done: t, value: e } = await l.read();
6
+ const { done: t, value: e } = await s.read();
7
7
  if (t)
8
8
  return;
9
9
  yield e;
10
10
  }
11
11
  } finally {
12
- l.releaseLock();
12
+ s.releaseLock();
13
13
  }
14
14
  });
15
15
  class M {
@@ -110,21 +110,21 @@ class M {
110
110
  static async canvasSourceToInlineData(t) {
111
111
  typeof HTMLImageElement < "u" && t instanceof HTMLImageElement && !t.complete && await t.decode().catch(() => {
112
112
  });
113
- const e = (s) => {
114
- const c = t[s];
115
- return typeof c == "object" && c !== null && "baseVal" in c ? c.baseVal.value : typeof c == "number" ? c : 0;
113
+ const e = (a) => {
114
+ const l = t[a];
115
+ return typeof l == "object" && l !== null && "baseVal" in l ? l.baseVal.value : typeof l == "number" ? l : 0;
116
116
  };
117
117
  let r = t.displayWidth || t.naturalWidth || t.videoWidth || e("width"), o = t.displayHeight || t.naturalHeight || t.videoHeight || e("height");
118
118
  if ((!r || !o) && typeof t.getBBox == "function")
119
119
  try {
120
- const s = t.getBBox();
121
- r = r || s.width, o = o || s.height;
120
+ const a = t.getBBox();
121
+ r = r || a.width, o = o || a.height;
122
122
  } catch {
123
123
  }
124
124
  if ((!r || !o) && typeof t.getBoundingClientRect == "function")
125
125
  try {
126
- const s = t.getBoundingClientRect();
127
- r = r || s.width, o = o || s.height;
126
+ const a = t.getBoundingClientRect();
127
+ r = r || a.width, o = o || a.height;
128
128
  } catch {
129
129
  }
130
130
  if (!r || !o)
@@ -163,8 +163,8 @@ class M {
163
163
  return o;
164
164
  }
165
165
  static encodeWAV(t, e, r, o, n) {
166
- const i = n / 8, a = o * i, s = new ArrayBuffer(44 + t.length * i), c = new DataView(s);
167
- return this.writeString(c, 0, "RIFF"), c.setUint32(4, 36 + t.length * i, !0), this.writeString(c, 8, "WAVE"), this.writeString(c, 12, "fmt "), c.setUint32(16, 16, !0), c.setUint16(20, e, !0), c.setUint16(22, o, !0), c.setUint32(24, r, !0), c.setUint32(28, r * a, !0), c.setUint16(32, a, !0), c.setUint16(34, n, !0), this.writeString(c, 36, "data"), c.setUint32(40, t.length * i, !0), this.floatTo16BitPCM(c, 44, t), s;
166
+ const i = n / 8, c = o * i, a = new ArrayBuffer(44 + t.length * i), l = new DataView(a);
167
+ return this.writeString(l, 0, "RIFF"), l.setUint32(4, 36 + t.length * i, !0), this.writeString(l, 8, "WAVE"), this.writeString(l, 12, "fmt "), l.setUint32(16, 16, !0), l.setUint16(20, e, !0), l.setUint16(22, o, !0), l.setUint32(24, r, !0), l.setUint32(28, r * c, !0), l.setUint16(32, c, !0), l.setUint16(34, n, !0), this.writeString(l, 36, "data"), l.setUint32(40, t.length * i, !0), this.floatTo16BitPCM(l, 44, t), a;
168
168
  }
169
169
  static floatTo16BitPCM(t, e, r) {
170
170
  for (let o = 0; o < r.length; o++, e += 2) {
@@ -177,19 +177,19 @@ class M {
177
177
  t.setUint8(e + o, r.charCodeAt(o));
178
178
  }
179
179
  }
180
- function v(l) {
181
- if (!l)
180
+ function v(s) {
181
+ if (!s)
182
182
  return;
183
183
  const t = {
184
- description: l.description,
185
- nullable: l.nullable || !1,
186
- format: l.format
184
+ description: s.description,
185
+ nullable: s.nullable || !1,
186
+ format: s.format
187
187
  };
188
- switch (Array.isArray(l.type) && l.type.includes("null") && (t.nullable = !0, l.type = l.type.find((e) => e !== "null")), l.type) {
188
+ switch (Array.isArray(s.type) && s.type.includes("null") && (t.nullable = !0, s.type = s.type.find((e) => e !== "null")), s.type) {
189
189
  case "string":
190
- return l.enum && Array.isArray(l.enum) ? T.enumString({
190
+ return s.enum && Array.isArray(s.enum) ? T.enumString({
191
191
  ...t,
192
- enum: l.enum
192
+ enum: s.enum
193
193
  }) : T.string(t);
194
194
  case "number":
195
195
  return T.number(t);
@@ -201,16 +201,16 @@ function v(l) {
201
201
  return T.array({
202
202
  ...t,
203
203
  // Recursively convert the 'items' schema
204
- items: v(l.items)
204
+ items: v(s.items)
205
205
  });
206
206
  case "object":
207
- const e = {}, r = l.properties ? Object.keys(l.properties) : [];
207
+ const e = {}, r = s.properties ? Object.keys(s.properties) : [];
208
208
  r.forEach((i) => {
209
209
  e[i] = v(
210
- l.properties[i]
210
+ s.properties[i]
211
211
  );
212
212
  });
213
- const o = l.required || [], n = r.filter(
213
+ const o = s.required || [], n = r.filter(
214
214
  (i) => !o.includes(i)
215
215
  );
216
216
  return T.object({
@@ -220,33 +220,62 @@ function v(l) {
220
220
  });
221
221
  default:
222
222
  return console.warn(
223
- `Unsupported type: ${l.type}, defaulting to string.`
223
+ `Unsupported type: ${s.type}, defaulting to string.`
224
224
  ), T.string(t);
225
225
  }
226
226
  }
227
- async function I(l, t = globalThis) {
227
+ const A = [
228
+ {
229
+ config: "FIREBASE_CONFIG",
230
+ path: "./backends/firebase.js"
231
+ },
232
+ {
233
+ config: "GEMINI_CONFIG",
234
+ path: "./backends/gemini.js"
235
+ },
236
+ {
237
+ config: "OPENAI_CONFIG",
238
+ path: "./backends/openai.js"
239
+ },
240
+ {
241
+ config: "TRANSFORMERS_CONFIG",
242
+ path: "./backends/transformers.js"
243
+ }
244
+ ];
245
+ async function I(s) {
246
+ if (s === "./backends/firebase.js")
247
+ return (await import("./backends/firebase.js")).default;
248
+ if (s === "./backends/gemini.js")
249
+ return (await import("./backends/gemini.js")).default;
250
+ if (s === "./backends/openai.js")
251
+ return (await import("./backends/openai.js")).default;
252
+ if (s === "./backends/transformers.js")
253
+ return (await import("./backends/transformers.js")).default;
254
+ throw new Error(`Unknown backend path "${s}"`);
255
+ }
256
+ async function C(s, t = globalThis) {
228
257
  const e = [];
229
- for (const r of l) {
258
+ for (const r of s) {
230
259
  const o = r.role === "assistant" ? "model" : "user", n = o === "model";
231
260
  let i = [];
232
261
  if (Array.isArray(r.content))
233
- for (const a of r.content)
234
- if (a.type === "text") {
235
- const s = a.value || a.text || "";
236
- if (typeof s != "string")
262
+ for (const c of r.content)
263
+ if (c.type === "text") {
264
+ const a = c.value || c.text || "";
265
+ if (typeof a != "string")
237
266
  throw new (t.DOMException || globalThis.DOMException)(
238
267
  'The content type "text" must have a string value.',
239
268
  "SyntaxError"
240
269
  );
241
- i.push({ text: s });
270
+ i.push({ text: a });
242
271
  } else {
243
272
  if (n)
244
273
  throw new (t.DOMException || globalThis.DOMException)(
245
274
  "Assistant messages only support text content.",
246
275
  "NotSupportedError"
247
276
  );
248
- const s = await M.convert(a.type, a.value);
249
- i.push(s);
277
+ const a = await M.convert(c.type, c.value);
278
+ i.push(a);
250
279
  }
251
280
  else
252
281
  i.push({ text: r.content });
@@ -254,7 +283,7 @@ async function I(l, t = globalThis) {
254
283
  }
255
284
  return e;
256
285
  }
257
- class d extends EventTarget {
286
+ class h extends EventTarget {
258
287
  #o;
259
288
  #f;
260
289
  #r;
@@ -262,24 +291,27 @@ class d extends EventTarget {
262
291
  #e;
263
292
  #n;
264
293
  #i;
265
- #l;
294
+ #c;
266
295
  #t;
267
- constructor(t, e, r, o = {}, n, i = 0, a = globalThis) {
268
- super(), this.#o = t, this.#f = e, this.#r = r || [], this.#s = o, this.#e = n, this.#n = !1, this.#i = i, this.#l = {}, this.#t = a;
296
+ constructor(t, e, r, o = {}, n, i = 0, c = globalThis) {
297
+ super(), this.#o = t, this.#f = e, this.#r = r || [], this.#s = o, this.#e = n, this.#n = !1, this.#i = i, this.#c = {}, this.#t = c;
269
298
  }
270
- get inputUsage() {
299
+ get contextUsage() {
271
300
  return this.#i;
272
301
  }
273
- get inputQuota() {
302
+ get contextWindow() {
274
303
  return 1e6;
275
304
  }
276
- get onquotaoverflow() {
277
- return this.#l;
305
+ get oncontextwindowoverflow() {
306
+ return this.#c;
278
307
  }
279
- set onquotaoverflow(t) {
280
- this.#l && this.removeEventListener("quotaoverflow", this.#l), this.#l = t, typeof t == "function" && this.addEventListener("quotaoverflow", t);
308
+ set oncontextwindowoverflow(t) {
309
+ this.#c && this.removeEventListener(
310
+ "contextwindowoverflow",
311
+ this.#c
312
+ ), this.#c = t, typeof t == "function" && this.addEventListener("contextwindowoverflow", t);
281
313
  }
282
- static #h(t) {
314
+ static #d(t) {
283
315
  try {
284
316
  if (!t || !t.document || t.document.defaultView !== t)
285
317
  throw new Error();
@@ -294,13 +326,13 @@ class d extends EventTarget {
294
326
  }
295
327
  }
296
328
  #a() {
297
- d.#h(this.#t);
329
+ h.#d(this.#t);
298
330
  }
299
331
  static async availability(t = {}) {
300
332
  const e = this.__window || globalThis;
301
- d.#h(e);
333
+ h.#d(e);
302
334
  try {
303
- await d.#x(t, e);
335
+ await h.#w(t, e);
304
336
  } catch (o) {
305
337
  if (o instanceof RangeError) {
306
338
  if (o.message.includes("language tag"))
@@ -316,64 +348,37 @@ class d extends EventTarget {
316
348
  }
317
349
  return "unavailable";
318
350
  }
319
- return (await d.#p(e)).availability(t);
351
+ return (await h.#p(e)).availability(t);
320
352
  }
321
- static #m = [
322
- {
323
- config: "FIREBASE_CONFIG",
324
- path: "./backends/firebase.js"
325
- },
326
- {
327
- config: "GEMINI_CONFIG",
328
- path: "./backends/gemini.js"
329
- },
330
- {
331
- config: "OPENAI_CONFIG",
332
- path: "./backends/openai.js"
333
- },
334
- {
335
- config: "TRANSFORMERS_CONFIG",
336
- path: "./backends/transformers.js"
337
- }
338
- ];
339
- static #d(t = globalThis) {
340
- for (const e of d.#m) {
341
- const r = t[e.config] || globalThis[e.config];
342
- if (r && r.apiKey)
343
- return { ...e, configValue: r };
353
+ static #x = A;
354
+ static #h(t = globalThis) {
355
+ for (const r of h.#x) {
356
+ const o = t[r.config] || globalThis[r.config];
357
+ if (o && o.apiKey)
358
+ return { ...r, configValue: o };
344
359
  }
360
+ const e = h.#x.map((r) => `window.${r.config}`).join(", ");
345
361
  throw new (t.DOMException || globalThis.DOMException)(
346
- "Prompt API Polyfill: No backend configuration found. Please set window.FIREBASE_CONFIG, window.GEMINI_CONFIG, window.OPENAI_CONFIG, or window.TRANSFORMERS_CONFIG.",
362
+ `Prompt API Polyfill: No backend configuration found. Please set one of: ${e}.`,
347
363
  "NotSupportedError"
348
364
  );
349
365
  }
350
366
  static async #p(t = globalThis) {
351
- const e = d.#d(t);
352
- if (e.path === "./backends/firebase.js")
353
- return (await import("./backends/firebase.js")).default;
354
- if (e.path === "./backends/gemini.js")
355
- return (await import("./backends/gemini.js")).default;
356
- if (e.path === "./backends/openai.js")
357
- return (await import("./backends/openai.js")).default;
358
- if (e.path === "./backends/transformers.js")
359
- return (await import("./backends/transformers.js")).default;
360
- throw new (t.DOMException || globalThis.DOMException)(
361
- `Prompt API Polyfill: Unknown backend path "${e.path}".`,
362
- "NotSupportedError"
363
- );
367
+ const e = h.#h(t);
368
+ return I(e.path);
364
369
  }
365
- static async #x(t = {}, e = globalThis) {
370
+ static async #w(t = {}, e = globalThis) {
366
371
  if (t.expectedInputs)
367
372
  for (const o of t.expectedInputs) {
368
373
  if (o.type !== "text" && o.type !== "image" && o.type !== "audio")
369
374
  throw new TypeError(`Invalid input type: ${o.type}`);
370
- o.languages && d.#E(o.languages);
375
+ o.languages && h.#y(o.languages);
371
376
  }
372
377
  if (t.expectedOutputs)
373
378
  for (const o of t.expectedOutputs) {
374
379
  if (o.type !== "text")
375
380
  throw new RangeError(`Unsupported output type: ${o.type}`);
376
- o.languages && d.#E(o.languages);
381
+ o.languages && h.#y(o.languages);
377
382
  }
378
383
  const r = t.expectedInputs ? ["text", ...t.expectedInputs.map((o) => o.type)] : ["text"];
379
384
  if (t.initialPrompts && Array.isArray(t.initialPrompts)) {
@@ -392,11 +397,11 @@ class d extends EventTarget {
392
397
  o = !0;
393
398
  }
394
399
  if (Array.isArray(i.content))
395
- for (const a of i.content) {
396
- const s = a.type || "text";
397
- if (!r.includes(s))
400
+ for (const c of i.content) {
401
+ const a = c.type || "text";
402
+ if (!r.includes(a))
398
403
  throw new (e.DOMException || globalThis.DOMException)(
399
- `The content type "${s}" is not in the expectedInputs.`,
404
+ `The content type "${a}" is not in the expectedInputs.`,
400
405
  "NotSupportedError"
401
406
  );
402
407
  }
@@ -408,7 +413,7 @@ class d extends EventTarget {
408
413
  }
409
414
  }
410
415
  }
411
- static #E(t) {
416
+ static #y(t) {
412
417
  if (!Array.isArray(t))
413
418
  throw new RangeError("The `languages` option must be an array.");
414
419
  for (const e of t) {
@@ -429,7 +434,7 @@ class d extends EventTarget {
429
434
  }
430
435
  static async create(t = {}) {
431
436
  const e = this.__window || globalThis;
432
- if (d.#h(e), await d.#x(t, e), t.signal?.aborted)
437
+ if (h.#d(e), await h.#w(t, e), t.signal?.aborted)
433
438
  throw t.signal.reason || new (e.DOMException || globalThis.DOMException)(
434
439
  "Aborted",
435
440
  "AbortError"
@@ -450,33 +455,33 @@ class d extends EventTarget {
450
455
  "Aborted",
451
456
  "AbortError"
452
457
  );
453
- const o = d.#d(e), n = await d.#p(e), i = new n(o.configValue), a = { ...t };
454
- d.#g(
455
- a.responseConstraint,
458
+ const o = h.#h(e), n = await h.#p(e), i = new n(o.configValue), c = { ...t };
459
+ h.#g(
460
+ c.responseConstraint,
456
461
  e
457
462
  );
458
- const s = {
463
+ const a = {
459
464
  model: i.modelName,
460
465
  generationConfig: {}
461
466
  };
462
- let c = [], w = 0;
463
- if (a.initialPrompts && Array.isArray(a.initialPrompts)) {
464
- const E = a.initialPrompts.filter(
467
+ let l = [], y = 0;
468
+ if (c.initialPrompts && Array.isArray(c.initialPrompts)) {
469
+ const w = c.initialPrompts.filter(
465
470
  (f) => f.role === "system"
466
- ), p = a.initialPrompts.filter(
471
+ ), p = c.initialPrompts.filter(
467
472
  (f) => f.role !== "system"
468
473
  );
469
- E.length > 0 && (s.systemInstruction = E.map((f) => typeof f.content == "string" ? f.content : Array.isArray(f.content) ? f.content.filter((h) => h.type === "text").map((h) => h.value || h.text || "").join(`
474
+ w.length > 0 && (a.systemInstruction = w.map((f) => typeof f.content == "string" ? f.content : Array.isArray(f.content) ? f.content.filter((d) => d.type === "text").map((d) => d.value || d.text || "").join(`
470
475
  `) : "").join(`
471
- `)), c = await I(p, e);
472
- for (const f of a.initialPrompts) {
476
+ `)), l = await C(p, e);
477
+ for (const f of c.initialPrompts) {
473
478
  if (typeof f.content != "string")
474
479
  continue;
475
- const h = d.#w([
480
+ const d = h.#E([
476
481
  { text: f.content }
477
482
  ]);
478
- if (h === "QuotaExceededError" || h === "quotaoverflow") {
479
- const y = e.QuotaExceededError || e.DOMException || globalThis.QuotaExceededError || globalThis.DOMException, u = new y(
483
+ if (d === "QuotaExceededError" || d === "contextwindowoverflow") {
484
+ const E = e.QuotaExceededError || e.DOMException || globalThis.QuotaExceededError || globalThis.DOMException, u = new E(
480
485
  "The initial prompts are too large, they exceed the quota.",
481
486
  "QuotaExceededError"
482
487
  );
@@ -484,25 +489,25 @@ class d extends EventTarget {
484
489
  value: 22,
485
490
  configurable: !0
486
491
  });
487
- const g = h === "QuotaExceededError" ? 1e7 : 5e5;
492
+ const g = d === "QuotaExceededError" ? 1e7 : 5e5;
488
493
  throw u.requested = g, u.quota = 1e6, u;
489
494
  }
490
495
  }
491
496
  }
492
497
  let x = null;
493
- if (typeof a.monitor == "function") {
498
+ if (typeof c.monitor == "function") {
494
499
  x = new EventTarget();
495
500
  try {
496
- a.monitor(x);
497
- } catch (E) {
498
- throw E;
501
+ c.monitor(x);
502
+ } catch (w) {
503
+ throw w;
499
504
  }
500
505
  }
501
506
  x && (x.__lastProgressLoaded = -1);
502
- const b = async (E) => {
507
+ const b = async (w) => {
503
508
  if (!x || t.signal?.aborted)
504
509
  return !t.signal?.aborted;
505
- const p = 1 / 65536, f = Math.floor(E / p) * p;
510
+ const p = 1 / 65536, f = Math.floor(w / p) * p;
506
511
  if (f <= x.__lastProgressLoaded)
507
512
  return !0;
508
513
  try {
@@ -513,10 +518,10 @@ class d extends EventTarget {
513
518
  lengthComputable: !0
514
519
  })
515
520
  ), x.__lastProgressLoaded = f;
516
- } catch (h) {
517
- console.error("Error dispatching downloadprogress events:", h);
521
+ } catch (d) {
522
+ console.error("Error dispatching downloadprogress events:", d);
518
523
  }
519
- return await new Promise((h) => setTimeout(h, 0)), !t.signal?.aborted;
524
+ return await new Promise((d) => setTimeout(d, 0)), !t.signal?.aborted;
520
525
  };
521
526
  if (!await b(0))
522
527
  throw t.signal.reason || new (e.DOMException || globalThis.DOMException)(
@@ -524,8 +529,8 @@ class d extends EventTarget {
524
529
  "AbortError"
525
530
  );
526
531
  const m = await i.createSession(
532
+ c,
527
533
  a,
528
- s,
529
534
  x
530
535
  );
531
536
  if (!await b(1))
@@ -533,26 +538,26 @@ class d extends EventTarget {
533
538
  "Aborted",
534
539
  "AbortError"
535
540
  );
536
- if (a.initialPrompts?.length > 0) {
537
- const E = [...c];
538
- if (s.systemInstruction && E.unshift({
541
+ if (c.initialPrompts?.length > 0) {
542
+ const w = [...l];
543
+ if (a.systemInstruction && w.unshift({
539
544
  role: "system",
540
- parts: [{ text: s.systemInstruction }]
541
- }), w = await i.countTokens(E) || 0, w > 1e6) {
545
+ parts: [{ text: a.systemInstruction }]
546
+ }), y = await i.countTokens(w) || 0, y > 1e6) {
542
547
  const p = e.QuotaExceededError || e.DOMException || globalThis.QuotaExceededError || globalThis.DOMException, f = new p(
543
548
  "The initial prompts are too large, they exceed the quota.",
544
549
  "QuotaExceededError"
545
550
  );
546
- throw Object.defineProperty(f, "code", { value: 22, configurable: !0 }), f.requested = w, f.quota = 1e6, f;
551
+ throw Object.defineProperty(f, "code", { value: 22, configurable: !0 }), f.requested = y, f.quota = 1e6, f;
547
552
  }
548
553
  }
549
554
  return new this(
550
555
  i,
551
556
  m,
557
+ l,
552
558
  c,
553
559
  a,
554
- s,
555
- w,
560
+ y,
556
561
  e
557
562
  );
558
563
  }
@@ -568,7 +573,7 @@ class d extends EventTarget {
568
573
  "Aborted",
569
574
  "AbortError"
570
575
  );
571
- const e = JSON.parse(JSON.stringify(this.#r)), r = { ...this.#s, ...t }, o = await d.#p(this.#t), n = d.#d(this.#t), i = new o(n.configValue), a = await i.createSession(
576
+ const e = JSON.parse(JSON.stringify(this.#r)), r = { ...this.#s, ...t }, o = await h.#p(this.#t), n = h.#h(this.#t), i = new o(n.configValue), c = await i.createSession(
572
577
  r,
573
578
  this.#e
574
579
  );
@@ -579,7 +584,7 @@ class d extends EventTarget {
579
584
  );
580
585
  return new this.constructor(
581
586
  i,
582
- a,
587
+ c,
583
588
  e,
584
589
  r,
585
590
  this.#e,
@@ -604,27 +609,27 @@ class d extends EventTarget {
604
609
  if (typeof t == "object" && t !== null && !Array.isArray(t) && Object.keys(t).length === 0)
605
610
  return "[object Object]";
606
611
  if (e.responseConstraint) {
607
- d.#g(
612
+ h.#g(
608
613
  e.responseConstraint,
609
614
  this.#t
610
615
  );
611
- const s = v(
616
+ const a = v(
612
617
  e.responseConstraint
613
618
  );
614
- this.#e.generationConfig.responseMimeType = "application/json", this.#e.generationConfig.responseSchema = s, this.#f = this.#o.createSession(
619
+ this.#e.generationConfig.responseMimeType = "application/json", this.#e.generationConfig.responseSchema = a, this.#f = this.#o.createSession(
615
620
  this.#s,
616
621
  this.#e
617
622
  );
618
623
  }
619
- const r = this.#y(t), o = await this.#c(t);
624
+ const r = this.#b(t), o = await this.#l(t);
620
625
  if (this.#n)
621
626
  throw new (this.#t.DOMException || globalThis.DOMException)(
622
627
  "Session is destroyed",
623
628
  "InvalidStateError"
624
629
  );
625
- const n = { role: "user", parts: o }, i = new Promise((s, c) => {
630
+ const n = { role: "user", parts: o }, i = new Promise((a, l) => {
626
631
  if (e.signal?.aborted) {
627
- c(
632
+ l(
628
633
  e.signal.reason || new (this.#t.DOMException || globalThis.DOMException)(
629
634
  "Aborted",
630
635
  "AbortError"
@@ -635,7 +640,7 @@ class d extends EventTarget {
635
640
  e.signal?.addEventListener(
636
641
  "abort",
637
642
  () => {
638
- c(
643
+ l(
639
644
  e.signal.reason || new (this.#t.DOMException || globalThis.DOMException)(
640
645
  "Aborted",
641
646
  "AbortError"
@@ -644,53 +649,53 @@ class d extends EventTarget {
644
649
  },
645
650
  { once: !0 }
646
651
  );
647
- }), a = (async () => {
648
- const s = this.#u(o);
649
- if (s === "QuotaExceededError") {
650
- const f = this.#t && this.#t.QuotaExceededError || this.#t && this.#t.DOMException || globalThis.QuotaExceededError || globalThis.DOMException, h = new f(
652
+ }), c = (async () => {
653
+ const a = this.#u(o);
654
+ if (a === "QuotaExceededError") {
655
+ const f = this.#t && this.#t.QuotaExceededError || this.#t && this.#t.DOMException || globalThis.QuotaExceededError || globalThis.DOMException, d = new f(
651
656
  "The prompt is too large, it exceeds the quota.",
652
657
  "QuotaExceededError"
653
658
  );
654
- Object.defineProperty(h, "code", { value: 22, configurable: !0 });
655
- const y = 1e7;
656
- throw h.requested = y, h.quota = this.inputQuota, h;
657
- } else if (s === "quotaoverflow")
658
- return this.dispatchEvent(new Event("quotaoverflow")), "Mock response for quota overflow test.";
659
- const c = [...this.#r, n];
660
- this.#e.systemInstruction && c.unshift({
659
+ Object.defineProperty(d, "code", { value: 22, configurable: !0 });
660
+ const E = 1e7;
661
+ throw d.requested = E, d.quota = this.contextWindow, d;
662
+ } else if (a === "contextwindowoverflow")
663
+ return this.dispatchEvent(new Event("contextwindowoverflow")), "Mock response for quota overflow test.";
664
+ const l = [...this.#r, n];
665
+ this.#e.systemInstruction && l.unshift({
661
666
  role: "system",
662
667
  parts: [{ text: this.#e.systemInstruction }]
663
668
  });
664
- const w = await this.#o.countTokens(
665
- c
669
+ const y = await this.#o.countTokens(
670
+ l
666
671
  );
667
- if (w > this.inputQuota) {
668
- const f = this.#t && this.#t.QuotaExceededError || this.#t && this.#t.DOMException || globalThis.QuotaExceededError || globalThis.DOMException, h = new f(
669
- `The prompt is too large (${w} tokens), it exceeds the quota of ${this.inputQuota} tokens.`,
672
+ if (y > this.contextWindow) {
673
+ const f = this.#t && this.#t.QuotaExceededError || this.#t && this.#t.DOMException || globalThis.QuotaExceededError || globalThis.DOMException, d = new f(
674
+ `The prompt is too large (${y} tokens), it exceeds the quota of ${this.contextWindow} tokens.`,
670
675
  "QuotaExceededError"
671
676
  );
672
- throw Object.defineProperty(h, "code", { value: 22, configurable: !0 }), h.requested = w, h.quota = this.inputQuota, h;
677
+ throw Object.defineProperty(d, "code", { value: 22, configurable: !0 }), d.requested = y, d.quota = this.contextWindow, d;
673
678
  }
674
- w > this.inputQuota && this.dispatchEvent(new Event("quotaoverflow"));
679
+ y > this.contextWindow && this.dispatchEvent(new Event("contextwindowoverflow"));
675
680
  const x = [...this.#r, n];
676
681
  let b;
677
682
  try {
678
683
  b = await this.#o.generateContent(x);
679
684
  } catch (f) {
680
- throw this.#b(f, o), f;
685
+ throw this.#m(f, o), f;
681
686
  }
682
- const { text: m, usage: E } = b;
687
+ const { text: m, usage: w } = b;
683
688
  let p = m;
684
689
  if (r) {
685
690
  const f = p.match(/^\s*{\s*"Rating"\s*:\s*/);
686
691
  f && (p = p.slice(f[0].length));
687
692
  }
688
- return E && (this.#i = E), this.#r.push(n), this.#r.push({ role: "model", parts: [{ text: p }] }), p;
693
+ return w && (this.#i = w), this.#r.push(n), this.#r.push({ role: "model", parts: [{ text: p }] }), p;
689
694
  })();
690
695
  try {
691
- return await Promise.race([a, i]);
692
- } catch (s) {
693
- throw s.name === "AbortError" || console.error("Prompt API Polyfill Error:", s), s;
696
+ return await Promise.race([c, i]);
697
+ } catch (a) {
698
+ throw a.name === "AbortError" || console.error("Prompt API Polyfill Error:", a), a;
694
699
  }
695
700
  }
696
701
  promptStreaming(t, e = {}) {
@@ -714,25 +719,25 @@ class d extends EventTarget {
714
719
  return new ReadableStream({
715
720
  async start(n) {
716
721
  let i = !1;
717
- const a = () => {
722
+ const c = () => {
718
723
  i = !0;
719
724
  try {
720
- const s = o?.reason || new (r.#t.DOMException || globalThis.DOMException)(
725
+ const a = o?.reason || new (r.#t.DOMException || globalThis.DOMException)(
721
726
  "Aborted",
722
727
  "AbortError"
723
728
  );
724
- n.error(s);
729
+ n.error(a);
725
730
  } catch {
726
731
  }
727
732
  };
728
733
  if (o?.aborted) {
729
- a();
734
+ c();
730
735
  return;
731
736
  }
732
- o && o.addEventListener("abort", a);
737
+ o && o.addEventListener("abort", c);
733
738
  try {
734
739
  if (e.responseConstraint) {
735
- d.#g(
740
+ h.#g(
736
741
  e.responseConstraint,
737
742
  r.#t
738
743
  );
@@ -744,13 +749,13 @@ class d extends EventTarget {
744
749
  r.#e
745
750
  );
746
751
  }
747
- const s = r.#y(t), c = await r.#c(t);
752
+ const a = r.#b(t), l = await r.#l(t);
748
753
  if (r.#n)
749
754
  throw new (r.#t.DOMException || globalThis.DOMException)(
750
755
  "Session is destroyed",
751
756
  "InvalidStateError"
752
757
  );
753
- const w = { role: "user", parts: c }, x = r.#u(c);
758
+ const y = { role: "user", parts: l }, x = r.#u(l);
754
759
  if (x === "QuotaExceededError") {
755
760
  const u = r.#t && r.#t.QuotaExceededError || r.#t && r.#t.DOMException || globalThis.QuotaExceededError || globalThis.DOMException, g = new u(
756
761
  "The prompt is too large, it exceeds the quota.",
@@ -761,12 +766,12 @@ class d extends EventTarget {
761
766
  configurable: !0
762
767
  });
763
768
  const O = 1e7;
764
- throw g.requested = O, g.quota = r.inputQuota, g;
765
- } else if (x === "quotaoverflow") {
766
- r.dispatchEvent(new Event("quotaoverflow")), n.enqueue("Mock response for quota overflow test."), n.close();
769
+ throw g.requested = O, g.quota = r.contextWindow, g;
770
+ } else if (x === "contextwindowoverflow") {
771
+ r.dispatchEvent(new Event("contextwindowoverflow")), n.enqueue("Mock response for quota overflow test."), n.close();
767
772
  return;
768
773
  }
769
- const b = [...r.#r, w];
774
+ const b = [...r.#r, y];
770
775
  r.#e.systemInstruction && b.unshift({
771
776
  role: "system",
772
777
  parts: [{ text: r.#e.systemInstruction }]
@@ -774,51 +779,51 @@ class d extends EventTarget {
774
779
  const m = await r.#o.countTokens(
775
780
  b
776
781
  );
777
- if (m > r.inputQuota) {
782
+ if (m > r.contextWindow) {
778
783
  const u = r.#t && r.#t.QuotaExceededError || r.#t && r.#t.DOMException || globalThis.QuotaExceededError || globalThis.DOMException, g = new u(
779
- `The prompt is too large (${m} tokens), it exceeds the quota of ${r.inputQuota} tokens.`,
784
+ `The prompt is too large (${m} tokens), it exceeds the quota of ${r.contextWindow} tokens.`,
780
785
  "QuotaExceededError"
781
786
  );
782
787
  throw Object.defineProperty(g, "code", {
783
788
  value: 22,
784
789
  configurable: !0
785
- }), g.requested = m, g.quota = r.inputQuota, g;
790
+ }), g.requested = m, g.quota = r.contextWindow, g;
786
791
  }
787
- m > r.inputQuota && r.dispatchEvent(new Event("quotaoverflow"));
788
- const E = [...r.#r, w];
792
+ m > r.contextWindow && r.dispatchEvent(new Event("contextwindowoverflow"));
793
+ const w = [...r.#r, y];
789
794
  let p;
790
795
  try {
791
- p = await r.#o.generateContentStream(E);
796
+ p = await r.#o.generateContentStream(w);
792
797
  } catch (u) {
793
- throw r.#b(u, c), u;
798
+ throw r.#m(u, l), u;
794
799
  }
795
- let f = "", h = !1, y = "";
800
+ let f = "", d = !1, E = "";
796
801
  for await (const u of p) {
797
802
  if (i) {
798
803
  typeof p.return == "function" && await p.return();
799
804
  return;
800
805
  }
801
806
  let g = u.text();
802
- if (s && !h) {
803
- y += g;
804
- const O = y.match(/^\s*{\s*"Rating"\s*:\s*/);
807
+ if (a && !d) {
808
+ E += g;
809
+ const O = E.match(/^\s*{\s*"Rating"\s*:\s*/);
805
810
  if (O)
806
- g = y.slice(O[0].length), h = !0, y = "";
807
- else if (y.length > 50)
808
- g = y, h = !0, y = "";
811
+ g = E.slice(O[0].length), d = !0, E = "";
812
+ else if (E.length > 50)
813
+ g = E, d = !0, E = "";
809
814
  else
810
815
  continue;
811
816
  }
812
817
  f += g, u.usageMetadata?.totalTokenCount && (r.#i = u.usageMetadata.totalTokenCount), n.enqueue(g);
813
818
  }
814
- i || (r.#r.push(w), r.#r.push({
819
+ i || (r.#r.push(y), r.#r.push({
815
820
  role: "model",
816
821
  parts: [{ text: f }]
817
822
  }), n.close());
818
- } catch (s) {
819
- i || n.error(s);
823
+ } catch (a) {
824
+ i || n.error(a);
820
825
  } finally {
821
- o && o.removeEventListener("abort", a);
826
+ o && o.removeEventListener("abort", c);
822
827
  }
823
828
  }
824
829
  });
@@ -834,7 +839,7 @@ class d extends EventTarget {
834
839
  "Aborted",
835
840
  "AbortError"
836
841
  );
837
- const r = await this.#c(t);
842
+ const r = await this.#l(t);
838
843
  if (this.#n)
839
844
  throw new (this.#t.DOMException || globalThis.DOMException)(
840
845
  "Session is destroyed",
@@ -852,23 +857,23 @@ class d extends EventTarget {
852
857
  this.#i = i || 0;
853
858
  } catch {
854
859
  }
855
- this.#i > this.inputQuota && this.dispatchEvent(new Event("quotaoverflow"));
860
+ this.#i > this.contextWindow && this.dispatchEvent(new Event("contextwindowoverflow"));
856
861
  }
857
- async measureInputUsage(t) {
862
+ async measureContextUsage(t) {
858
863
  if (this.#a(), this.#n)
859
864
  throw new (this.#t.DOMException || globalThis.DOMException)(
860
865
  "Session is destroyed",
861
866
  "InvalidStateError"
862
867
  );
863
868
  try {
864
- const e = await this.#c(t);
869
+ const e = await this.#l(t);
865
870
  if (this.#n)
866
871
  throw new (this.#t.DOMException || globalThis.DOMException)(
867
872
  "Session is destroyed",
868
873
  "InvalidStateError"
869
874
  );
870
875
  const r = this.#u(e);
871
- return r === "QuotaExceededError" ? 1e7 : r === "quotaoverflow" ? 5e5 : await this.#o.countTokens([
876
+ return r === "QuotaExceededError" ? 1e7 : r === "contextwindowoverflow" ? 5e5 : await this.#o.countTokens([
872
877
  { role: "user", parts: e }
873
878
  ]) || 0;
874
879
  } catch {
@@ -879,13 +884,13 @@ class d extends EventTarget {
879
884
  }
880
885
  // Volkswagen mode detection to avoid cloud costs for WPT tests.
881
886
  #u(t) {
882
- return d.#w(t);
887
+ return h.#E(t);
883
888
  }
884
- static #w(t) {
889
+ static #E(t) {
885
890
  if (t.length !== 1 || !t[0].text)
886
891
  return null;
887
892
  const e = t[0].text;
888
- return typeof e != "string" || !e.startsWith("Please write a sentence in English.") ? null : e.length > 1e7 ? "QuotaExceededError" : e.length > 5e4 ? "quotaoverflow" : null;
893
+ return typeof e != "string" || !e.startsWith("Please write a sentence in English.") ? null : e.length > 1e7 ? "QuotaExceededError" : e.length > 5e4 ? "contextwindowoverflow" : null;
889
894
  }
890
895
  static #g(t, e) {
891
896
  if (t)
@@ -898,7 +903,7 @@ class d extends EventTarget {
898
903
  );
899
904
  }
900
905
  }
901
- #y(t) {
906
+ #b(t) {
902
907
  if (Array.isArray(t)) {
903
908
  for (const e of t)
904
909
  if (e.prefix && (e.role === "assistant" || e.role === "model") && typeof e.content == "string" && e.content.includes('"Rating":'))
@@ -907,7 +912,7 @@ class d extends EventTarget {
907
912
  return null;
908
913
  }
909
914
  // Private Helper to process diverse input types
910
- async #c(t) {
915
+ async #l(t) {
911
916
  const e = this.#s.expectedInputs ? ["text", ...this.#s.expectedInputs.map((o) => o.type)] : ["text"];
912
917
  if (typeof t == "string") {
913
918
  if (!e.includes("text"))
@@ -934,28 +939,28 @@ class d extends EventTarget {
934
939
  "The `prefix` flag isn't supported and was ignored."
935
940
  );
936
941
  } else if (Array.isArray(n.content))
937
- for (const a of n.content) {
938
- const s = a.type || "text";
939
- if (!e.includes(s))
942
+ for (const c of n.content) {
943
+ const a = c.type || "text";
944
+ if (!e.includes(a))
940
945
  throw new (this.#t.DOMException || globalThis.DOMException)(
941
- `The content type "${s}" is not in the expectedInputs.`,
946
+ `The content type "${a}" is not in the expectedInputs.`,
942
947
  "NotSupportedError"
943
948
  );
944
- if (s === "text") {
945
- if (typeof a.value != "string")
949
+ if (a === "text") {
950
+ if (typeof c.value != "string")
946
951
  throw new (this.#t.DOMException || globalThis.DOMException)(
947
952
  'The content type "text" must have a string value.',
948
953
  "SyntaxError"
949
954
  );
950
- o.push({ text: a.value });
955
+ o.push({ text: c.value });
951
956
  } else {
952
957
  if (i)
953
958
  throw new (this.#t.DOMException || globalThis.DOMException)(
954
959
  "Assistant messages only support text content.",
955
960
  "NotSupportedError"
956
961
  );
957
- const c = await M.convert(a.type, a.value);
958
- o.push(c);
962
+ const l = await M.convert(c.type, c.value);
963
+ o.push(l);
959
964
  }
960
965
  }
961
966
  }
@@ -978,13 +983,13 @@ class d extends EventTarget {
978
983
  return [{ text: JSON.stringify(t) }];
979
984
  }
980
985
  // Map backend errors to WPT expectations
981
- #b(t, e) {
986
+ #m(t, e) {
982
987
  const r = String(t.message || t);
983
988
  if (r.includes("400") || r.toLowerCase().includes("unable to process") || r.toLowerCase().includes("invalid")) {
984
989
  const o = e.some(
985
- (a) => a.inlineData?.mimeType.startsWith("audio/")
990
+ (c) => c.inlineData?.mimeType.startsWith("audio/")
986
991
  ), n = e.some(
987
- (a) => a.inlineData?.mimeType.startsWith("image/")
992
+ (c) => c.inlineData?.mimeType.startsWith("image/")
988
993
  ), i = this.#t.DOMException || globalThis.DOMException;
989
994
  if (o)
990
995
  throw new i("Invalid audio data", "DataError");
@@ -994,47 +999,47 @@ class d extends EventTarget {
994
999
  }
995
1000
  }
996
1001
  globalThis.DOMException && (globalThis.QuotaExceededError = globalThis.DOMException);
997
- const D = (l) => {
1002
+ const D = (s) => {
998
1003
  try {
999
- if (!l || l.LanguageModel?.__isPolyfill)
1004
+ if (!s || s.LanguageModel?.__isPolyfill)
1000
1005
  return;
1001
- const t = class extends d {
1006
+ const t = class extends h {
1002
1007
  };
1003
- t.__window = l, t.__isPolyfill = !0, l.LanguageModel = t, l.DOMException && (l.QuotaExceededError = l.DOMException);
1008
+ t.__window = s, t.__isPolyfill = !0, s.LanguageModel = t, s.DOMException && (s.QuotaExceededError = s.DOMException);
1004
1009
  } catch {
1005
1010
  }
1006
1011
  };
1007
1012
  if (typeof HTMLIFrameElement < "u")
1008
1013
  try {
1009
- const l = Object.getOwnPropertyDescriptor(
1014
+ const s = Object.getOwnPropertyDescriptor(
1010
1015
  HTMLIFrameElement.prototype,
1011
1016
  "contentWindow"
1012
1017
  );
1013
- l && l.get && Object.defineProperty(HTMLIFrameElement.prototype, "contentWindow", {
1018
+ s && s.get && Object.defineProperty(HTMLIFrameElement.prototype, "contentWindow", {
1014
1019
  get() {
1015
- const t = l.get.call(this);
1020
+ const t = s.get.call(this);
1016
1021
  return t && D(t), t;
1017
1022
  },
1018
1023
  configurable: !0
1019
1024
  });
1020
1025
  } catch {
1021
1026
  }
1022
- const A = new MutationObserver((l) => {
1023
- for (const t of l)
1027
+ const P = new MutationObserver((s) => {
1028
+ for (const t of s)
1024
1029
  for (const e of t.addedNodes)
1025
1030
  e.tagName === "IFRAME" && (D(e.contentWindow), e.addEventListener("load", () => D(e.contentWindow), {
1026
1031
  once: !1
1027
1032
  }));
1028
1033
  });
1029
- globalThis.document?.documentElement && (A.observe(globalThis.document.documentElement, {
1034
+ globalThis.document?.documentElement && (P.observe(globalThis.document.documentElement, {
1030
1035
  childList: !0,
1031
1036
  subtree: !0
1032
- }), globalThis.document.querySelectorAll("iframe").forEach((l) => {
1033
- D(l.contentWindow);
1037
+ }), globalThis.document.querySelectorAll("iframe").forEach((s) => {
1038
+ D(s.contentWindow);
1034
1039
  }));
1035
- (!("LanguageModel" in globalThis) || globalThis.__FORCE_PROMPT_API_POLYFILL__) && (globalThis.LanguageModel = d, d.__isPolyfill = !0, console.log(
1040
+ (!("LanguageModel" in globalThis) || globalThis.__FORCE_PROMPT_API_POLYFILL__) && (globalThis.LanguageModel = h, h.__isPolyfill = !0, console.log(
1036
1041
  "Polyfill: window.LanguageModel is now backed by the Prompt API polyfill."
1037
1042
  ));
1038
1043
  export {
1039
- d as LanguageModel
1044
+ h as LanguageModel
1040
1045
  };
package/dot_env.json CHANGED
@@ -7,15 +7,5 @@
7
7
  "reCaptchaSiteKey": "",
8
8
  "useLimitedUseAppCheckTokens": true,
9
9
  "device": "webgpu",
10
- "dtype": "q4f16",
11
- "env": {
12
- "allowRemoteModels": true,
13
- "backends": {
14
- "onnx": {
15
- "wasm": {
16
- "wasmPaths": ""
17
- }
18
- }
19
- }
20
- }
10
+ "dtype": "q4f16"
21
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prompt-api-polyfill",
3
- "version": "1.2.2",
3
+ "version": "1.3.1",
4
4
  "description": "Polyfill for the Prompt API (`LanguageModel`) backed by Firebase AI Logic, Gemini API, OpenAI API, or Transformers.js.",
5
5
  "type": "module",
6
6
  "main": "./dist/prompt-api-polyfill.js",
@@ -38,9 +38,10 @@
38
38
  "start": "vite",
39
39
  "build": "vite build",
40
40
  "prepublishOnly": "npm run build",
41
+ "generate:registry": "node scripts/generate-registry.js",
41
42
  "sync:wpt": "node scripts/sync-wpt.js",
42
- "generate:wpt": "node scripts/list-backends.js && node scripts/generate-wpt-wrappers.js",
43
- "test:wpt": "npm run sync:wpt && npm run generate:wpt && npx vite --open /tests/wpt/index.html",
43
+ "generate:wpt": "npm run generate:registry && node scripts/backend-discovery.js && node scripts/generate-wpt-wrappers.js",
44
+ "test:wpt": "npm run generate:registry && npm run sync:wpt && npm run generate:wpt && npx vite --open /tests/wpt/index.html",
44
45
  "fix": "npx prettier --write ."
45
46
  },
46
47
  "devDependencies": {