arcane-os 0.5.7 → 0.5.9

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +39 -27
  3. package/browser-runtime/ai/browser-speech-artifacts.mjs +23 -1683
  4. package/browser-runtime/ai/browser-speech-providers.mjs +353 -143
  5. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +32 -324
  6. package/browser-runtime/ai/speech-worker-client.mjs +51 -14
  7. package/browser-runtime/ai/speech-worker-runtime.mjs +45 -1072
  8. package/browser-runtime/dependencies/strong-type/package.json +22 -23
  9. package/browser-runtime/event-manager.mjs +27 -41
  10. package/package.json +2 -3
  11. package/runtime/arcane/components/chat.html +1 -1
  12. package/runtime/arcane/components/speech.html +8 -38
  13. package/runtime/arcane/components/voice-transcription.html +1 -1
  14. package/runtime/arcane/css/theme.css +1 -1
  15. package/runtime/arcane/entities/Chat.js +12 -7
  16. package/runtime/arcane/modules/AI.js +569 -402
  17. package/runtime/arcane/modules/AIPreferenceTuple.js +1 -1
  18. package/runtime/arcane/modules/AIProviderRuntime.js +129 -68
  19. package/runtime/arcane/modules/AIRuntimeState.js +2 -18
  20. package/runtime/arcane/modules/BrowserTestSuite.js +2 -5
  21. package/runtime/arcane/modules/CalculatorEngine.js +0 -4
  22. package/runtime/arcane/modules/ChatRecords.js +169 -1
  23. package/runtime/arcane/modules/CommunicationHub.js +0 -19
  24. package/runtime/arcane/modules/ConfiguredAIChatSession.js +28 -25
  25. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +4 -5
  26. package/runtime/arcane/modules/DocumentLexicalSearch.js +1 -1
  27. package/runtime/arcane/modules/Errors.js +6 -19
  28. package/runtime/arcane/modules/Mail.js +1 -2
  29. package/runtime/arcane/modules/MailTransport.mjs +1 -3
  30. package/runtime/arcane/modules/OllamaModelIdentifier.js +1 -1
  31. package/runtime/arcane/modules/PersistentAIChatSession.js +5 -5
  32. package/runtime/arcane/modules/ScreenCapture.js +2 -4
  33. package/runtime/arcane/modules/SpeechPlayback.js +0 -27
  34. package/runtime/arcane/modules/WaitForComponent.js +0 -1
  35. package/src/app-descriptor.mjs +1 -1
  36. package/src/doctor.mjs +1 -3
  37. package/src/event-manager.mjs +27 -41
  38. package/src/import-map.mjs +1 -1
  39. package/src/index.mjs +2 -9
  40. package/src/installed-sdk-runtime.mjs +1 -11
  41. package/src/mail-api.mjs +0 -1
  42. package/src/native-provider-loader.mjs +0 -9
  43. package/src/scaffold.mjs +9 -21
  44. package/src/toolchain.mjs +5 -28
  45. package/src/workspace-runtime.mjs +0 -4
  46. package/src/workspace.mjs +2 -23
@@ -10,9 +10,6 @@ import { arcaneEvents } from "../event-manager.mjs";
10
10
 
11
11
  const completeValue = (value) => value;
12
12
 
13
- // Compatibility export only. Ordinary model caching no longer creates or
14
- // requires completion manifests, receipts, or byte identities.
15
- const MODEL_MANIFEST_SCHEMA = "arcane.ai.browser-wasm.model.v4";
16
13
  const BROWSER_MODEL_SOURCES = new WeakSet();
17
14
  const BROWSER_MODEL_SOURCE_METADATA = new WeakMap();
18
15
  const BROWSER_MODEL_SOURCE_TRANSPORTS = new WeakMap();
@@ -31,8 +28,6 @@ const MODEL_DOWNLOAD_PROGRESS_INTERVAL_MS = 250;
31
28
  const MODEL_DOWNLOAD_SPEED_WINDOW_MS = 5_000;
32
29
  const MODEL_DOWNLOAD_MAX_RANGE_PARTS = 4_096;
33
30
  const MODEL_DOWNLOAD_TARGET_RANGE_BYTES = 4_000_000;
34
- const LEGACY_MODEL_DOWNLOAD_MAX_RANGE_PARTS = 16;
35
- const LEGACY_MODEL_DOWNLOAD_TARGET_RANGE_BYTES = 128_000_000;
36
31
  const INTEL_VENDOR_ID = 0x8086;
37
32
  const CAPABILITY_POLICY_PROTOCOL = "arcane-ai-browser-capability-policy/1";
38
33
  let highPerformanceGpuNoticeShown = false;
@@ -132,39 +127,6 @@ function modelHttpRanges(total) {
132
127
  );
133
128
  }
134
129
 
135
- function modelHttpSubranges(range) {
136
- const length = range.end - range.start + 1;
137
- const maximumParts = Math.floor(
138
- MODEL_DOWNLOAD_MAX_RANGE_PARTS / LEGACY_MODEL_DOWNLOAD_MAX_RANGE_PARTS,
139
- );
140
- return completeValue(modelHttpRangesFor(
141
- length,
142
- maximumParts,
143
- MODEL_DOWNLOAD_TARGET_RANGE_BYTES,
144
- ).map((part) => completeValue({
145
- start: range.start + part.start,
146
- end: range.start + part.end,
147
- total: range.total,
148
- })));
149
- }
150
-
151
- function modelHttpRangePlans(total) {
152
- const current = modelHttpRanges(total);
153
- const legacy = modelHttpRangesFor(
154
- total,
155
- LEGACY_MODEL_DOWNLOAD_MAX_RANGE_PARTS,
156
- LEGACY_MODEL_DOWNLOAD_TARGET_RANGE_BYTES,
157
- );
158
- if (
159
- current.length === legacy.length
160
- && current.every((range, index) => (
161
- range.start === legacy[index].start
162
- && range.end === legacy[index].end
163
- ))
164
- ) return completeValue([current]);
165
- return completeValue([current, legacy]);
166
- }
167
-
168
130
  function progressClock() {
169
131
  return typeof globalThis.performance?.now === "function"
170
132
  ? globalThis.performance.now()
@@ -208,7 +170,7 @@ function modelIdText(value) {
208
170
  return id;
209
171
  }
210
172
 
211
- function descriptorFileName(value, url, fallbackName = null) {
173
+ function descriptorFileName(value, url) {
212
174
  const suppliedName = value.name;
213
175
  let name = suppliedName;
214
176
  if (name === undefined) {
@@ -218,7 +180,6 @@ function descriptorFileName(value, url, fallbackName = null) {
218
180
  } catch {
219
181
  name = "";
220
182
  }
221
- if (!name && fallbackName) name = fallbackName;
222
183
  }
223
184
  name = requiredText(name, "file name");
224
185
  if (
@@ -236,23 +197,19 @@ function descriptorFileName(value, url, fallbackName = null) {
236
197
  return name;
237
198
  }
238
199
 
239
- function descriptorFile(value, { fallbackName = null } = {}) {
200
+ function descriptorFile(value) {
240
201
  if (!value || typeof value !== "object" || Array.isArray(value)) {
241
202
  throw new TypeError("Each browser model file descriptor must be an object.");
242
203
  }
243
- if (
244
- value.url !== undefined
245
- && value.immutableUrl !== undefined
246
- && value.url !== value.immutableUrl
247
- ) {
248
- throw new TypeError("Browser model file url and legacy immutableUrl must match when both are provided.");
204
+ if (Object.hasOwn(value, "immutableUrl")) {
205
+ throw new TypeError("Browser model file descriptors must use url.");
249
206
  }
250
- const url = modelSourceUrl(value.url ?? value.immutableUrl);
207
+ const url = modelSourceUrl(value.url);
251
208
  if (!url) {
252
209
  throw new TypeError("Browser model file url must be a valid absolute URL.");
253
210
  }
254
211
  const file = {
255
- name: descriptorFileName(value, url, fallbackName),
212
+ name: descriptorFileName(value, url),
256
213
  url: url.href,
257
214
  };
258
215
  if (Number.isSafeInteger(value.bytes) && value.bytes > 0) {
@@ -266,24 +223,14 @@ function modelDescriptor(value) {
266
223
  throw new TypeError("A browser model descriptor is required.");
267
224
  }
268
225
  const id = modelIdText(value.id);
269
- const hasFiles = value.files !== undefined;
270
- const hasLegacyFile = ["url", "immutableUrl", "name"]
271
- .some((field) => value[field] !== undefined);
272
- if (hasFiles && hasLegacyFile) {
273
- throw new TypeError("Browser model files[] is mutually exclusive with legacy one-file fields.");
274
- }
275
- let files;
276
- let legacy = false;
277
- if (hasFiles) {
278
- if (!Array.isArray(value.files) || value.files.length === 0) {
279
- throw new TypeError("Browser model files must be a nonempty ordered array.");
280
- }
281
- files = value.files.map((file) => descriptorFile(file));
282
- } else {
283
- legacy = true;
284
- const safeId = id.replace(/[^a-z0-9._-]+/giu, "_");
285
- files = [descriptorFile(value, { fallbackName: `${safeId}.gguf` })];
226
+ if (["url", "immutableUrl", "name", "bytes", "sha256", "licenseSpdx", "sourceRevision"]
227
+ .some((field) => Object.hasOwn(value, field))) {
228
+ throw new TypeError("Browser model descriptor files must be declared in files[].");
229
+ }
230
+ if (!Array.isArray(value.files) || value.files.length === 0) {
231
+ throw new TypeError("Browser model files must be a nonempty ordered array.");
286
232
  }
233
+ let files = value.files.map((file) => descriptorFile(file));
287
234
  const names = new Set();
288
235
  const urls = new Set();
289
236
  for (const file of files) {
@@ -303,16 +250,8 @@ function modelDescriptor(value) {
303
250
  url: file.url,
304
251
  ...(file.bytes === undefined ? {} : { bytes: file.bytes }),
305
252
  })));
306
- let descriptor;
307
- if (legacy) {
308
- const [file] = files;
309
- descriptor = { id, url: file.url };
310
- if (file.bytes !== undefined) descriptor.bytes = file.bytes;
311
- } else {
312
- descriptor = { id, files: publicFiles };
313
- }
314
- descriptor = completeValue(descriptor);
315
- MODEL_DESCRIPTOR_METADATA.set(descriptor, completeValue({ files, legacy }));
253
+ const descriptor = completeValue({ id, files: publicFiles });
254
+ MODEL_DESCRIPTOR_METADATA.set(descriptor, completeValue({ files }));
316
255
  return descriptor;
317
256
  }
318
257
 
@@ -394,7 +333,6 @@ function sourceMetadata(source) {
394
333
 
395
334
  /**
396
335
  * Creates a browser download authority for one caller-supplied model file set.
397
- * Legacy one-file descriptors normalize to one ordered member.
398
336
  * Security is an intent-only seam and does not change ordinary downloads.
399
337
  */
400
338
  export function createBrowserModelSource(descriptor, {
@@ -470,15 +408,7 @@ export function createBrowserModelSource(descriptor, {
470
408
  });
471
409
  }
472
410
 
473
- async function open(memberOrOptions = 0, options = {}) {
474
- let memberIndex = memberOrOptions;
475
- if (!Number.isSafeInteger(memberOrOptions)) {
476
- if (metadata.files.length !== 1) {
477
- throw new TypeError("A browser model file index is required for a multi-file source.");
478
- }
479
- memberIndex = 0;
480
- options = memberOrOptions ?? {};
481
- }
411
+ async function open(memberIndex = 0, options = {}) {
482
412
  const { signal } = options;
483
413
  const download = await request(memberIndex, { signal });
484
414
  if (!download.response?.ok) await rejectHttpResponse(download);
@@ -587,10 +517,6 @@ export function createBrowserModelSource(descriptor, {
587
517
  descriptor: model,
588
518
  open,
589
519
  };
590
- if (metadata.legacy) {
591
- sourceRecord.name = metadata.files[0].name;
592
- sourceRecord.immutableUrl = metadata.files[0].url;
593
- }
594
520
  const source = completeValue(sourceRecord);
595
521
  BROWSER_MODEL_SOURCES.add(source);
596
522
  BROWSER_MODEL_SOURCE_METADATA.set(source, metadata);
@@ -654,18 +580,14 @@ function injectiveStorageId(id) {
654
580
  ).join("");
655
581
  }
656
582
 
657
- function storageName(source, { legacy = false } = {}) {
658
- const safeId = legacy
659
- ? source.id.replace(/[^a-z0-9._-]+/giu, "_")
660
- : `id-${injectiveStorageId(source.id)}`;
583
+ function storageName(source) {
584
+ const safeId = `id-${injectiveStorageId(source.id)}`;
661
585
  const models = sourceMetadata(source).files.map((file) => completeValue({
662
586
  file,
663
587
  name: `${safeId}--${file.name}`,
664
588
  }));
665
589
  return completeValue({
666
590
  models: completeValue(models),
667
- model: models[0].name,
668
- manifest: `${safeId}.complete.json`,
669
591
  });
670
592
  }
671
593
 
@@ -973,7 +895,7 @@ export function createDbopfsModelStore({
973
895
  } = {},
974
896
  ) {
975
897
  const transport = BROWSER_MODEL_SOURCE_TRANSPORTS.get(source);
976
- const ranges = await rangePlanForInstall(name, total, signal);
898
+ const ranges = rangePlanForInstall(total, signal);
977
899
  const partFiles = new Array(ranges.length);
978
900
  const pendingRangeIndexes = [];
979
901
  for (let rangeIndex = 0; rangeIndex < ranges.length; rangeIndex += 1) {
@@ -1161,7 +1083,6 @@ export function createDbopfsModelStore({
1161
1083
  async function removeNames(names) {
1162
1084
  const removed = [];
1163
1085
  for (const entry of names.models) removed.push(await removeEntry(entry.name));
1164
- removed.push(await removeEntry(names.manifest));
1165
1086
  return removed.some(Boolean);
1166
1087
  }
1167
1088
 
@@ -1176,74 +1097,9 @@ export function createDbopfsModelStore({
1176
1097
  return names;
1177
1098
  }
1178
1099
 
1179
- async function rangePlanForInstall(
1180
- modelName,
1181
- total,
1182
- signal,
1183
- knownNames = undefined,
1184
- ) {
1185
- const plans = modelHttpRangePlans(total);
1186
- const existingNames = knownNames === undefined
1187
- ? await memberRangePartNames(modelName)
1188
- : knownNames;
1189
- const available = existingNames === null
1190
- ? null
1191
- : existingNames instanceof Set
1192
- ? existingNames
1193
- : new Set(existingNames);
1194
- if (available?.size === 0) return plans[0];
1195
- if (plans.length === 1) return plans[0];
1196
- const completedParts = new Map();
1197
-
1198
- async function completedPart(range) {
1199
- throwIfAborted(signal, "install");
1200
- const partName = rangePartName(modelName, range);
1201
- if (available && !available.has(partName)) return null;
1202
- if (completedParts.has(partName)) return completedParts.get(partName);
1203
- const partFile = await file(partName);
1204
- const expected = range.end - range.start + 1;
1205
- const completed = partFile?.size === expected ? partFile : null;
1206
- completedParts.set(partName, completed);
1207
- return completed;
1208
- }
1209
-
1210
- const completedLegacyRanges = new Set();
1211
- for (const range of plans[1]) {
1212
- if (await completedPart(range)) completedLegacyRanges.add(range);
1213
- }
1214
- const hybrid = completeValue(plans[1].flatMap((range) => (
1215
- completedLegacyRanges.has(range)
1216
- ? [range]
1217
- : modelHttpSubranges(range)
1218
- )));
1219
- let selected = plans[0];
1220
- let selectedBytes = 0;
1221
- let selectedLastModified = 0;
1222
- for (const ranges of [plans[0], hybrid]) {
1223
- let completedBytes = 0;
1224
- let lastModified = 0;
1225
- for (const range of ranges) {
1226
- const partFile = await completedPart(range);
1227
- if (!partFile) continue;
1228
- completedBytes += range.end - range.start + 1;
1229
- if (Number.isFinite(partFile.lastModified)) {
1230
- lastModified = Math.max(lastModified, partFile.lastModified);
1231
- }
1232
- }
1233
- if (
1234
- completedBytes > selectedBytes
1235
- || (
1236
- completedBytes === selectedBytes
1237
- && completedBytes > 0
1238
- && lastModified > selectedLastModified
1239
- )
1240
- ) {
1241
- selected = ranges;
1242
- selectedBytes = completedBytes;
1243
- selectedLastModified = lastModified;
1244
- }
1245
- }
1246
- return selected;
1100
+ function rangePlanForInstall(total, signal) {
1101
+ throwIfAborted(signal, "install");
1102
+ return modelHttpRanges(total);
1247
1103
  }
1248
1104
 
1249
1105
  async function removeMemberRangeParts(modelName, {
@@ -1253,14 +1109,7 @@ export function createDbopfsModelStore({
1253
1109
  const existingNames = await memberRangePartNames(modelName);
1254
1110
  let names = existingNames;
1255
1111
  if (names === null && Number.isSafeInteger(total) && total > 0) {
1256
- const plans = modelHttpRangePlans(total);
1257
- const hybridRanges = plans.length === 1
1258
- ? []
1259
- : plans[1].flatMap((range) => modelHttpSubranges(range));
1260
- names = [...new Set([
1261
- ...plans.flat(),
1262
- ...hybridRanges,
1263
- ].map((range) => rangePartName(modelName, range)))];
1112
+ names = modelHttpRanges(total).map((range) => rangePartName(modelName, range));
1264
1113
  }
1265
1114
  names ??= [];
1266
1115
  const removed = [];
@@ -1356,120 +1205,11 @@ export function createDbopfsModelStore({
1356
1205
 
1357
1206
  async function remove(source) {
1358
1207
  const names = storageName(source);
1359
- const legacyNames = storageName(source, { legacy: true });
1360
- let legacyManifest = null;
1361
- let removeLegacy = sourceMetadata(source).legacy;
1362
- try {
1363
- legacyManifest = await legacyManifestForSource(source, legacyNames);
1364
- removeLegacy ||= Boolean(legacyManifest);
1365
- } catch (error) {
1366
- globalThis.console?.warn?.(
1367
- "Arcane could not inspect the legacy browser model cache during removal.",
1368
- error,
1369
- );
1370
- }
1371
1208
  let removed = await removeNames(names);
1372
1209
  removed = await removeRangeParts(source, names) || removed;
1373
- if (removeLegacy) {
1374
- removed = await removeNames(
1375
- legacyStorageNames(source, legacyNames, legacyManifest),
1376
- ) || removed;
1377
- }
1378
1210
  return removed;
1379
1211
  }
1380
1212
 
1381
- async function legacyManifestForSource(source, legacyNames) {
1382
- const manifestFile = await file(legacyNames.manifest);
1383
- if (!manifestFile) return null;
1384
- let manifest;
1385
- try {
1386
- manifest = JSON.parse(await manifestFile.text());
1387
- } catch {
1388
- return null;
1389
- }
1390
- const model = manifest?.model;
1391
- return manifest?.complete === true && model?.id === source.id ? manifest : null;
1392
- }
1393
-
1394
- function legacyStorageNames(source, names, manifest) {
1395
- if (!manifest) return names;
1396
- const safeId = source.id.replace(/[^a-z0-9._-]+/giu, "_");
1397
- const storedNames = new Set(names.models.map((entry) => entry.name));
1398
- const candidates = [];
1399
- if (Array.isArray(manifest.model?.files)) candidates.push(...manifest.model.files);
1400
- if (manifest.model && typeof manifest.model === "object") candidates.push(manifest.model);
1401
- if (Array.isArray(manifest.files)) candidates.push(...manifest.files);
1402
- for (const candidate of candidates) {
1403
- if (!candidate || typeof candidate !== "object") continue;
1404
- if (
1405
- candidate.name === undefined
1406
- && candidate.url === undefined
1407
- && candidate.immutableUrl === undefined
1408
- ) continue;
1409
- try {
1410
- const url = modelSourceUrl(candidate.url ?? candidate.immutableUrl)
1411
- ?? new URL("https://arcane.invalid/model.gguf");
1412
- const name = descriptorFileName(candidate, url);
1413
- storedNames.add(`${safeId}--${name}`);
1414
- } catch {
1415
- // Keep cleanup limited to valid filenames attributable to this manifest.
1416
- }
1417
- }
1418
- return completeValue({
1419
- ...names,
1420
- models: completeValue([...storedNames].map((name) => completeValue({ name }))),
1421
- });
1422
- }
1423
-
1424
- async function legacyNamespaceMatchesSource(source, legacyNames) {
1425
- const metadata = sourceMetadata(source);
1426
- if (metadata.legacy) return true;
1427
- if (metadata.files.length !== 1) return false;
1428
- const manifest = await legacyManifestForSource(source, legacyNames);
1429
- if (!manifest) return false;
1430
- const model = manifest.model;
1431
- const [member] = metadata.files;
1432
- if (model.url === member.url) return true;
1433
- if (model.name === member.name && model.immutableUrl === member.url) return true;
1434
- return Array.isArray(model.files)
1435
- && model.files.length === 1
1436
- && model.files[0]?.name === member.name
1437
- && model.files[0]?.url === member.url;
1438
- }
1439
-
1440
- async function legacyCacheForSource(source, signal) {
1441
- const names = storageName(source, { legacy: true });
1442
- if (!await legacyNamespaceMatchesSource(source, names)) return null;
1443
- const files = await storedModelFiles(names, signal);
1444
- return files ? completeValue({ files, names }) : null;
1445
- }
1446
-
1447
- async function removeLegacyCacheAfterReplacement(source) {
1448
- try {
1449
- const names = storageName(source, { legacy: true });
1450
- const manifest = await legacyManifestForSource(source, names);
1451
- if (!sourceMetadata(source).legacy && !manifest) return;
1452
- await removeNames(legacyStorageNames(source, names, manifest));
1453
- } catch (error) {
1454
- globalThis.console?.warn?.(
1455
- "Arcane could not remove the superseded legacy browser model cache.",
1456
- error,
1457
- );
1458
- }
1459
- }
1460
-
1461
- async function removeIncompleteReplacement(source, names) {
1462
- try {
1463
- await removeNames(names);
1464
- await removeRangeParts(source, names);
1465
- } catch (error) {
1466
- globalThis.console?.warn?.(
1467
- "Arcane could not remove an incomplete duplicate browser model cache.",
1468
- error,
1469
- );
1470
- }
1471
- }
1472
-
1473
1213
  async function storagePolicy({ cached = false } = {}) {
1474
1214
  return completeValue({
1475
1215
  compatibility: "compatible",
@@ -1495,11 +1235,6 @@ export function createDbopfsModelStore({
1495
1235
  return modelFiles;
1496
1236
  }
1497
1237
 
1498
- async function storedModelFiles(names, signal) {
1499
- const modelFiles = await availableModelFiles(names, signal);
1500
- return modelFiles.every(Boolean) ? modelFiles : null;
1501
- }
1502
-
1503
1238
  async function storedRangeCandidateForPlan(
1504
1239
  modelName,
1505
1240
  ranges,
@@ -1538,12 +1273,7 @@ export function createDbopfsModelStore({
1538
1273
  signal,
1539
1274
  availableNames = null,
1540
1275
  ) {
1541
- const ranges = await rangePlanForInstall(
1542
- modelName,
1543
- total,
1544
- signal,
1545
- availableNames,
1546
- );
1276
+ const ranges = rangePlanForInstall(total, signal);
1547
1277
  return storedRangeCandidateForPlan(
1548
1278
  modelName,
1549
1279
  ranges,
@@ -1624,20 +1354,10 @@ export function createDbopfsModelStore({
1624
1354
  signal,
1625
1355
  } = {}) {
1626
1356
  const names = storageName(source);
1627
- let modelFiles = await availableResumableModelFiles(source, names, signal);
1628
- if (modelFiles.every(Boolean)) {
1629
- await removeLegacyCacheAfterReplacement(source);
1630
- } else {
1631
- const legacyCache = await legacyCacheForSource(source, signal);
1632
- if (legacyCache) {
1633
- await removeIncompleteReplacement(source, names);
1634
- modelFiles = legacyCache.files;
1635
- }
1636
- }
1357
+ const modelFiles = await availableResumableModelFiles(source, names, signal);
1637
1358
  if (!modelFiles.every(Boolean)) return null;
1638
1359
  return completeValue({
1639
1360
  files: completeValue(modelFiles),
1640
- file: modelFiles.length === 1 ? modelFiles[0] : null,
1641
1361
  });
1642
1362
  }
1643
1363
 
@@ -1744,13 +1464,11 @@ export function createDbopfsModelStore({
1744
1464
  }
1745
1465
  }
1746
1466
  if (failure !== null) throw failure;
1747
- await removeLegacyCacheAfterReplacement(source);
1748
1467
  progress.finish();
1749
1468
  if (failure !== null) throw failure;
1750
1469
  throwIfAborted(downloadSignal, "install");
1751
1470
  return completeValue({
1752
1471
  files: completeValue(modelFiles),
1753
- file: modelFiles.length === 1 ? modelFiles[0] : null,
1754
1472
  });
1755
1473
  } catch (error) {
1756
1474
  retainFailure(error);
@@ -3011,13 +2729,12 @@ function capabilityPolicy(
3011
2729
  });
3012
2730
  }
3013
2731
 
3014
- function providerModelSources(source, sources) {
3015
- const list = sources === undefined ? [source] : sources;
3016
- if (!Array.isArray(list) || list.length === 0) {
3017
- throw new TypeError("createBrowserWasmLlmProvider requires a nonempty sources array or legacy source.");
2732
+ function providerModelSources(sources) {
2733
+ if (!Array.isArray(sources) || sources.length === 0) {
2734
+ throw new TypeError("createBrowserWasmLlmProvider requires a nonempty sources array.");
3018
2735
  }
3019
2736
  const ids = new Set();
3020
- for (const candidate of list) {
2737
+ for (const candidate of sources) {
3021
2738
  if (!BROWSER_MODEL_SOURCES.has(candidate)) {
3022
2739
  throw new TypeError("Every browser-WASM model source must come from createBrowserModelSource().");
3023
2740
  }
@@ -3026,27 +2743,20 @@ function providerModelSources(source, sources) {
3026
2743
  }
3027
2744
  ids.add(candidate.id);
3028
2745
  }
3029
- if (source !== undefined && !BROWSER_MODEL_SOURCES.has(source)) {
3030
- throw new TypeError("The legacy default source must come from createBrowserModelSource().");
3031
- }
3032
- if (source !== undefined && !list.includes(source)) {
3033
- throw new TypeError("The legacy default source must be one member of sources.");
3034
- }
3035
2746
  return completeValue({
3036
- sources: completeValue(list.slice()),
3037
- defaultSource: source ?? list[0],
2747
+ sources: completeValue(sources.slice()),
2748
+ defaultSource: sources[0],
3038
2749
  });
3039
2750
  }
3040
2751
 
3041
2752
  export function createBrowserWasmLlmProvider({
3042
- source,
3043
2753
  sources,
3044
2754
  store,
3045
2755
  loadDefaults = {},
3046
2756
  security,
3047
2757
  logger = console,
3048
2758
  } = {}) {
3049
- const configuredModels = providerModelSources(source, sources);
2759
+ const configuredModels = providerModelSources(sources);
3050
2760
  const modelSources = configuredModels.sources;
3051
2761
  const defaultSource = configuredModels.defaultSource;
3052
2762
  if (!DBOPFS_MODEL_STORES.has(store)) {
@@ -3751,5 +3461,3 @@ export function adaptV1LlmProvider(provider) {
3751
3461
  V1_LLM_PROVIDER_ADAPTERS.set(provider, adapted);
3752
3462
  return adapted;
3753
3463
  }
3754
-
3755
- export { MODEL_MANIFEST_SCHEMA };
@@ -52,8 +52,9 @@ function validateWorker(worker) {
52
52
  const WORKER_CLIENTS = new WeakSet();
53
53
 
54
54
  /**
55
- * Owns exactly one role Worker. Cancellation terminates that Worker, which is
56
- * the only reliable preemption boundary for the third-party WASM engines.
55
+ * Owns exactly one role Worker. STT and lifecycle cancellation terminate that
56
+ * Worker. TTS use cancellation targets only the active request so sibling pool
57
+ * Workers remain available.
57
58
  */
58
59
  class SpeechWorkerClient {
59
60
  #role;
@@ -194,22 +195,54 @@ class SpeechWorkerClient {
194
195
  }
195
196
  const id = this.#nextId;
196
197
  this.#nextId += 1;
197
- return new Promise((resolve, reject) => {
198
+ const client = this;
199
+ return new Promise(function requestSpeechWorkerOperation(resolve, reject) {
198
200
  let listening = true;
199
201
  const cleanup = () => {
200
202
  if (!listening) return;
201
203
  listening = false;
202
204
  signal?.removeEventListener?.("abort", onAbort);
203
205
  };
204
- const onAbort = () => {
205
- if (!this.#pending.has(id)) return;
206
- void this.terminate(
207
- abortError(signal, this.#role, op),
206
+ function onAbort() {
207
+ const pending = client.#pending.get(id);
208
+ if (!pending) return;
209
+ if (client.#role === "tts" && op === "use") {
210
+ client.#pending.delete(id);
211
+ pending.cleanup();
212
+ pending.reject(abortError(signal, client.#role, op));
213
+ const cancelId = client.#nextId;
214
+ client.#nextId += 1;
215
+ try {
216
+ client.#transport.postMessage({
217
+ protocol: SPEECH_WORKER_PROTOCOL,
218
+ id: cancelId,
219
+ op: "cancel",
220
+ payload: { targetId: id },
221
+ });
222
+ } catch (error) {
223
+ const failure = clientError(
224
+ "ARCANE_AI_WORKER_MESSAGE_ERROR",
225
+ "Unable to cancel an operation in the speech Worker.",
226
+ error,
227
+ `${operationSubject(client.#role, op)}-message-rejected`,
228
+ );
229
+ void client.terminate(failure, { intentional: false }).catch(
230
+ function ignoreCancellationTerminationFailure() {
231
+ return undefined;
232
+ },
233
+ );
234
+ }
235
+ return;
236
+ }
237
+ void client.terminate(
238
+ abortError(signal, client.#role, op),
208
239
  { intentional: true },
209
- ).catch(() => undefined);
210
- };
240
+ ).catch(function ignoreAbortTerminationFailure() {
241
+ return undefined;
242
+ });
243
+ }
211
244
  signal?.addEventListener?.("abort", onAbort, { once: true });
212
- this.#pending.set(id, { resolve, reject, cleanup, op });
245
+ client.#pending.set(id, { resolve, reject, cleanup, op });
213
246
  const message = {
214
247
  protocol: SPEECH_WORKER_PROTOCOL,
215
248
  id,
@@ -218,18 +251,22 @@ class SpeechWorkerClient {
218
251
  };
219
252
  const transfers = collectSpeechTransferables(payload);
220
253
  try {
221
- this.#transport.postMessage(message, transfers);
254
+ client.#transport.postMessage(message, transfers);
222
255
  } catch (error) {
223
- this.#pending.delete(id);
256
+ client.#pending.delete(id);
224
257
  cleanup();
225
258
  const failure = clientError(
226
259
  "ARCANE_AI_WORKER_MESSAGE_ERROR",
227
260
  "Unable to send an operation to the speech Worker.",
228
261
  error,
229
- `${operationSubject(this.#role, op)}-message-rejected`,
262
+ `${operationSubject(client.#role, op)}-message-rejected`,
230
263
  );
231
264
  reject(failure);
232
- void this.terminate(failure, { intentional: false }).catch(() => undefined);
265
+ void client.terminate(failure, { intentional: false }).catch(
266
+ function ignoreRequestTerminationFailure() {
267
+ return undefined;
268
+ },
269
+ );
233
270
  }
234
271
  });
235
272
  }