arcane-os 0.3.1 → 0.3.2

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 (153) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +86 -117
  3. package/bin/arcane-test.mjs +170 -46
  4. package/browser-runtime/ai/browser-speech-artifacts.mjs +855 -895
  5. package/browser-runtime/ai/browser-speech-providers.mjs +80 -204
  6. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +627 -819
  7. package/browser-runtime/ai/browser-wasm.mjs +24 -35
  8. package/browser-runtime/ai/browser-wllama-runtime.mjs +64 -316
  9. package/browser-runtime/ai/model-controller.mjs +584 -181
  10. package/browser-runtime/ai/speech-worker-client.mjs +8 -148
  11. package/browser-runtime/ai/speech-worker-runtime.mjs +642 -374
  12. package/browser-runtime/dom-event-instrumentation.mjs +55 -147
  13. package/browser-runtime/event-manager.mjs +239 -624
  14. package/package.json +5 -6
  15. package/runtime/arcane/components/app-bar.html +3 -15
  16. package/runtime/arcane/components/assistant-panel.html +10 -10
  17. package/runtime/arcane/components/calculator.html +1 -1
  18. package/runtime/arcane/components/chat.html +1359 -135
  19. package/runtime/arcane/components/conversation-view.html +2 -2
  20. package/runtime/arcane/components/document-inspector.html +11 -17
  21. package/runtime/arcane/components/file-manager.html +13 -56
  22. package/runtime/arcane/components/markdown-document.html +82 -281
  23. package/runtime/arcane/components/markdown-editor.html +7 -10
  24. package/runtime/arcane/components/media-embed.html +6 -6
  25. package/runtime/arcane/components/screen-capture.html +4 -4
  26. package/runtime/arcane/components/source-explanation.html +2 -2
  27. package/runtime/arcane/components/speech.html +112 -68
  28. package/runtime/arcane/components/terminal-workspace.html +4 -4
  29. package/runtime/arcane/components/theme-editor.html +1 -1
  30. package/runtime/arcane/components/unified-inbox.html +2 -2
  31. package/runtime/arcane/components/voice-transcription.html +31 -21
  32. package/runtime/arcane/entities/Calculation.js +2 -3
  33. package/runtime/arcane/entities/Chat.js +228 -43
  34. package/runtime/arcane/entities/Preference.js +3 -5
  35. package/runtime/arcane/entities/Weather.js +5 -5
  36. package/runtime/arcane/modules/AI.js +1042 -427
  37. package/runtime/arcane/modules/AIProviderRuntime.js +618 -359
  38. package/runtime/arcane/modules/AIResponseLength.js +9 -19
  39. package/runtime/arcane/modules/AIRuntimeState.js +109 -72
  40. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +45 -32
  41. package/runtime/arcane/modules/BrowserTestSuite.js +78 -122
  42. package/runtime/arcane/modules/CalculatorEngine.js +9 -9
  43. package/runtime/arcane/modules/CommunicationAppController.js +3 -7
  44. package/runtime/arcane/modules/ComponentContracts.js +30 -32
  45. package/runtime/arcane/modules/ConfiguredAIChatSession.js +281 -230
  46. package/runtime/arcane/modules/ConversationActionItems.js +26 -59
  47. package/runtime/arcane/modules/ConversationClosingReport.js +34 -61
  48. package/runtime/arcane/modules/ConversationTimebox.js +27 -15
  49. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +152 -344
  50. package/runtime/arcane/modules/DocumentLexicalSearch.js +25 -91
  51. package/runtime/arcane/modules/HTMLImport.js +54 -1
  52. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +40 -203
  53. package/runtime/arcane/modules/LocalAIReadiness.js +40 -60
  54. package/runtime/arcane/modules/LocalAIReadinessController.js +15 -13
  55. package/runtime/arcane/modules/MD.js +1 -45
  56. package/runtime/arcane/modules/Mail.js +51 -103
  57. package/runtime/arcane/modules/MailOutbox.mjs +95 -193
  58. package/runtime/arcane/modules/MailTransport.mjs +36 -57
  59. package/runtime/arcane/modules/ModelDefinition.js +22 -106
  60. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +39 -101
  61. package/runtime/arcane/modules/PersistentAIChatSession.js +281 -18
  62. package/runtime/arcane/modules/PreferenceStore.js +102 -30
  63. package/runtime/arcane/modules/RiskSignalAnalyzer.js +8 -9
  64. package/runtime/arcane/modules/ScopedOPFSCache.js +7 -42
  65. package/runtime/arcane/modules/ScreenCapture.js +175 -128
  66. package/runtime/arcane/modules/SpeechPlayback.js +46 -149
  67. package/runtime/arcane/modules/StaticDocumentCatalog.js +173 -407
  68. package/runtime/arcane/modules/ToolCallRouter.js +25 -12
  69. package/runtime/arcane/modules/YouTubeMedia.js +6 -5
  70. package/schemas/arcane-app-bundle.schema.json +13 -78
  71. package/schemas/arcane-app.schema.json +9 -25
  72. package/schemas/arcane-lock.schema.json +18 -151
  73. package/schemas/arcane-package.schema.json +2 -16
  74. package/schemas/native-build-plan.schema.json +119 -122
  75. package/src/app-descriptor.mjs +75 -132
  76. package/src/application-tests.mjs +200 -0
  77. package/src/cli/main.mjs +27 -46
  78. package/src/constants.mjs +3 -4
  79. package/src/dev-server.mjs +30 -324
  80. package/src/doctor.mjs +92 -154
  81. package/src/dom-event-instrumentation.mjs +55 -147
  82. package/src/errors.mjs +2 -3
  83. package/src/event-manager.mjs +239 -624
  84. package/src/event-queue.mjs +3 -3
  85. package/src/import-map.mjs +273 -1028
  86. package/src/index.mjs +14 -16
  87. package/src/installed-sdk-runtime.mjs +27 -67
  88. package/src/integrated-provider-loader.mjs +53 -382
  89. package/src/mail-api.mjs +0 -2
  90. package/src/mail-server.mjs +224 -580
  91. package/src/mail.mjs +4 -10
  92. package/src/native-plan.mjs +163 -598
  93. package/src/native-provider-loader.mjs +104 -1063
  94. package/src/packager/core.mjs +485 -3229
  95. package/src/process.mjs +5 -10
  96. package/src/release-bundle.mjs +292 -2405
  97. package/src/runtime.mjs +76 -396
  98. package/src/scaffold.mjs +30 -80
  99. package/src/sdk-browser-runtime.mjs +70 -626
  100. package/src/source-server.mjs +588 -0
  101. package/src/targets/index.mjs +78 -188
  102. package/src/templates/workspace-template.mjs +19 -135
  103. package/src/testing-loader.mjs +164 -0
  104. package/src/testing.mjs +1 -1
  105. package/src/toolchain.mjs +131 -544
  106. package/src/update-check.mjs +26 -64
  107. package/src/workspace-operation-lock.mjs +139 -430
  108. package/src/workspace-runtime.mjs +109 -1558
  109. package/src/workspace.mjs +40 -302
  110. package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +0 -218
  111. package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +0 -203
  112. package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +0 -80
  113. package/browser-runtime/ai/internal/sha256.mjs +0 -166
  114. package/docs/architecture.md +0 -344
  115. package/docs/compatibility.md +0 -36
  116. package/docs/event-manager.md +0 -294
  117. package/docs/platform-targets.md +0 -108
  118. package/docs/publishing.md +0 -201
  119. package/docs/reference/README.md +0 -185
  120. package/docs/reference/ai/browser-speech-package-authority.json +0 -835
  121. package/docs/reference/ai/browser-speech.md +0 -1295
  122. package/docs/reference/ai/browser-wasm.md +0 -530
  123. package/docs/reference/arcane-ollama.md +0 -288
  124. package/docs/reference/availability-and-normalization.md +0 -183
  125. package/docs/reference/behavioral-testing.md +0 -133
  126. package/docs/reference/cli.md +0 -779
  127. package/docs/reference/core/README.md +0 -62
  128. package/docs/reference/core/arcane-ai-contracts.md +0 -907
  129. package/docs/reference/core/arcane-api.md +0 -601
  130. package/docs/reference/core/arcane-entities.md +0 -65
  131. package/docs/reference/core/arcane-events.md +0 -134
  132. package/docs/reference/core/ollama-module.md +0 -181
  133. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +0 -1909
  134. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +0 -1057
  135. package/docs/reference/core/reference/arcane-api/core-and-events.md +0 -320
  136. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +0 -610
  137. package/docs/reference/core/reference/arcane-api/namespaces.md +0 -1157
  138. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +0 -1423
  139. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +0 -315
  140. package/docs/reference/event-manager.md +0 -1511
  141. package/docs/reference/inventory/package-api.json +0 -3284
  142. package/docs/reference/inventory/runtime-components.json +0 -1011
  143. package/docs/reference/inventory/runtime-entities.json +0 -26
  144. package/docs/reference/inventory/runtime-modules.json +0 -1431
  145. package/docs/reference/mail.md +0 -316
  146. package/docs/reference/protocols.md +0 -719
  147. package/docs/reference/runtime-components.md +0 -1366
  148. package/docs/reference/runtime-entities.md +0 -303
  149. package/docs/reference/runtime-modules.md +0 -2965
  150. package/docs/reference/sdk-api.md +0 -6698
  151. package/docs/roadmap.md +0 -79
  152. package/docs/work-amplification.md +0 -129
  153. package/runtime/ARCANE_RUNTIME_RELEASE.json +0 -826
@@ -4,17 +4,15 @@ import {
4
4
  normalizeModelSecurity,
5
5
  normalizeArcaneAIError,
6
6
  resolveModelSecurity,
7
- sameModelSecurity,
8
7
  } from "./model-controller.mjs";
9
8
  import { createPackagedWllamaRuntime } from "./browser-wllama-runtime.mjs";
10
- import { createStreamingSha256 } from "./internal/sha256.mjs";
11
9
  import { arcaneEvents } from "../event-manager.mjs";
12
10
 
11
+ const completeValue = (value) => value;
12
+
13
+ // Compatibility export only. Ordinary model caching no longer creates or
14
+ // requires completion manifests, receipts, or byte identities.
13
15
  const MODEL_MANIFEST_SCHEMA = "arcane.ai.browser-wasm.model.v4";
14
- const SINGLE_MODEL_MANIFEST_SCHEMA = "arcane.ai.browser-wasm.model.v3";
15
- const LEGACY_MODEL_MANIFEST_SCHEMA = "arcane.ai.browser-wasm.model.v2";
16
- const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
17
- const MUTABLE_PATH_PATTERN = /\/(?:resolve\/)?(?:main|master|latest)(?:\/|$)/iu;
18
16
  const BROWSER_MODEL_SOURCES = new WeakSet();
19
17
  const BROWSER_MODEL_SOURCE_METADATA = new WeakMap();
20
18
  const MODEL_DESCRIPTOR_METADATA = new WeakMap();
@@ -28,7 +26,6 @@ const CHROME_HIGH_PERFORMANCE_GPU_FLAG_URL =
28
26
  "chrome://flags/#force-high-performance-gpu";
29
27
  const INTEL_VENDOR_ID = 0x8086;
30
28
  const CAPABILITY_POLICY_PROTOCOL = "arcane-ai-browser-capability-policy/1";
31
- const WLLAMA_MAX_FILE_BYTES = 2_000_000_000;
32
29
  let highPerformanceGpuNoticeShown = false;
33
30
 
34
31
  function fail(code, message, cause) {
@@ -52,20 +49,13 @@ function normalizationSignal(error, signal) {
52
49
  return error?.code === "ARCANE_AI_WORKER_TERMINATION_UNCONFIRMED" ? null : signal;
53
50
  }
54
51
 
55
- function immutableHttpsUrl(value) {
52
+ function modelSourceUrl(value) {
56
53
  let url;
57
54
  try {
58
55
  url = new URL(value);
59
56
  } catch {
60
57
  return null;
61
58
  }
62
- if (
63
- url.protocol !== "https:"
64
- || url.username
65
- || url.password
66
- || url.hash
67
- || MUTABLE_PATH_PATTERN.test(url.pathname)
68
- ) return null;
69
59
  return url;
70
60
  }
71
61
 
@@ -135,28 +125,15 @@ function descriptorFile(value, { fallbackName = null } = {}) {
135
125
  ) {
136
126
  throw new TypeError("Browser model file url and legacy immutableUrl must match when both are provided.");
137
127
  }
138
- const url = immutableHttpsUrl(value.url ?? value.immutableUrl);
128
+ const url = modelSourceUrl(value.url ?? value.immutableUrl);
139
129
  if (!url) {
140
- throw new TypeError("Browser model file url must be immutable HTTPS without credentials or fragments.");
130
+ throw new TypeError("Browser model file url must be a valid absolute URL.");
141
131
  }
142
132
  const file = {
143
133
  name: descriptorFileName(value, url, fallbackName),
144
134
  url: url.href,
145
135
  };
146
- if (value.bytes !== undefined) {
147
- if (!Number.isSafeInteger(value.bytes) || value.bytes < 1) {
148
- throw new TypeError("Browser model file bytes must be a positive safe integer when provided.");
149
- }
150
- file.bytes = value.bytes;
151
- }
152
- if (value.sha256 !== undefined) {
153
- const sha256 = requiredText(value.sha256, "file sha256").toLowerCase();
154
- if (!SHA256_PATTERN.test(sha256)) {
155
- throw new TypeError("Browser model file sha256 must be exactly 64 hexadecimal characters when provided.");
156
- }
157
- file.sha256 = sha256;
158
- }
159
- return Object.freeze(file);
136
+ return completeValue(file);
160
137
  }
161
138
 
162
139
  function modelDescriptor(value) {
@@ -165,7 +142,7 @@ function modelDescriptor(value) {
165
142
  }
166
143
  const id = modelIdText(value.id);
167
144
  const hasFiles = value.files !== undefined;
168
- const hasLegacyFile = ["url", "immutableUrl", "name", "bytes", "sha256"]
145
+ const hasLegacyFile = ["url", "immutableUrl", "name"]
169
146
  .some((field) => value[field] !== undefined);
170
147
  if (hasFiles && hasLegacyFile) {
171
148
  throw new TypeError("Browser model files[] is mutually exclusive with legacy one-file fields.");
@@ -195,18 +172,20 @@ function modelDescriptor(value) {
195
172
  names.add(nameKey);
196
173
  urls.add(file.url);
197
174
  }
198
- files = Object.freeze(files);
175
+ files = completeValue(files);
176
+ const publicFiles = completeValue(files.map((file) => completeValue({
177
+ name: file.name,
178
+ url: file.url,
179
+ })));
199
180
  let descriptor;
200
181
  if (legacy) {
201
182
  const [file] = files;
202
183
  descriptor = { id, url: file.url };
203
- if (file.bytes !== undefined) descriptor.bytes = file.bytes;
204
- if (file.sha256 !== undefined) descriptor.sha256 = file.sha256;
205
184
  } else {
206
- descriptor = { id, files };
185
+ descriptor = { id, files: publicFiles };
207
186
  }
208
- descriptor = Object.freeze(descriptor);
209
- MODEL_DESCRIPTOR_METADATA.set(descriptor, Object.freeze({ files, legacy }));
187
+ descriptor = completeValue(descriptor);
188
+ MODEL_DESCRIPTOR_METADATA.set(descriptor, completeValue({ files, legacy }));
210
189
  return descriptor;
211
190
  }
212
191
 
@@ -263,7 +242,7 @@ function emitWebgpuAdapterSelection(source, runtime) {
263
242
  const webgpu = evidence?.webgpu;
264
243
  if (webgpu?.observed !== true || !webgpu.adapter) return;
265
244
  try {
266
- arcaneEvents.instrument(WEBGPU_ADAPTER_SELECTED_EVENT, Object.freeze({
245
+ arcaneEvents.instrument(WEBGPU_ADAPTER_SELECTED_EVENT, completeValue({
267
246
  protocol: WEBGPU_ADAPTER_SELECTION_PROTOCOL,
268
247
  providerId: "arcane-browser-wasm-wllama",
269
248
  modelId: source.id,
@@ -272,7 +251,7 @@ function emitWebgpuAdapterSelection(source, runtime) {
272
251
  offload: webgpu.offload ?? null,
273
252
  buffers: webgpu.buffers ?? null,
274
253
  queue: webgpu.queue ?? null,
275
- }), Object.freeze({
254
+ }), completeValue({
276
255
  source: "sdk:ai/browser-wasm",
277
256
  category: "capability",
278
257
  }));
@@ -286,20 +265,10 @@ function sourceMetadata(source) {
286
265
  ?? MODEL_DESCRIPTOR_METADATA.get(publicDescriptor(source));
287
266
  }
288
267
 
289
- function manifestModelIdentity(source) {
290
- return Object.freeze({
291
- id: source.id,
292
- files: Object.freeze(sourceMetadata(source).files.map((file) => Object.freeze({
293
- name: file.name,
294
- url: file.url,
295
- }))),
296
- });
297
- }
298
-
299
268
  /**
300
- * Creates a browser download authority for one caller-supplied immutable
301
- * model file set. Legacy one-file descriptors normalize to one ordered member.
302
- * Effective load security decides which expected-byte checks run.
269
+ * Creates a browser download authority for one caller-supplied model file set.
270
+ * Legacy one-file descriptors normalize to one ordered member.
271
+ * Security is an intent-only seam and does not change ordinary downloads.
303
272
  */
304
273
  export function createBrowserModelSource(descriptor, {
305
274
  fetchImpl = null,
@@ -331,10 +300,7 @@ export function createBrowserModelSource(descriptor, {
331
300
  try {
332
301
  response = await fetchFunction(member.url, {
333
302
  cache: "no-store",
334
- credentials: "omit",
335
- mode: "cors",
336
303
  redirect: "follow",
337
- referrerPolicy: "no-referrer",
338
304
  signal,
339
305
  });
340
306
  } catch (error) {
@@ -354,22 +320,14 @@ export function createBrowserModelSource(descriptor, {
354
320
  } catch {
355
321
  finalUrl = null;
356
322
  }
357
- if (finalUrl?.protocol !== "https:") {
358
- await response.body?.cancel?.().catch(() => undefined);
359
- throw fail("ARCANE_AI_MODEL_REDIRECT_BLOCKED", "The model response left HTTPS.");
360
- }
323
+ finalUrl ??= new URL(member.url);
361
324
  if (!response.body || typeof response.body.getReader !== "function") {
362
325
  throw fail("ARCANE_AI_MODEL_SOURCE_INVALID", "The model response did not provide a byte stream.");
363
326
  }
364
- const header = response.headers?.get?.("content-length");
365
- const reported = header === null || header === undefined || header === ""
366
- ? null
367
- : Number(header);
368
- return Object.freeze({
327
+ return completeValue({
369
328
  body: response.body,
370
329
  requestedUrl: member.url,
371
330
  finalUrl: finalUrl.href,
372
- reportedBytes: Number.isSafeInteger(reported) && reported >= 0 ? reported : null,
373
331
  cancel: (reason) => response.body.cancel(reason),
374
332
  });
375
333
  }
@@ -386,7 +344,7 @@ export function createBrowserModelSource(descriptor, {
386
344
  immutableUrl: { value: metadata.files[0].url, enumerable: false },
387
345
  });
388
346
  }
389
- const source = Object.freeze(sourceRecord);
347
+ const source = completeValue(sourceRecord);
390
348
  BROWSER_MODEL_SOURCES.add(source);
391
349
  BROWSER_MODEL_SOURCE_METADATA.set(source, metadata);
392
350
  return source;
@@ -452,213 +410,25 @@ function storageName(source, { legacy = false } = {}) {
452
410
  const safeId = legacy
453
411
  ? source.id.replace(/[^a-z0-9._-]+/giu, "_")
454
412
  : `id-${injectiveStorageId(source.id)}`;
455
- const models = sourceMetadata(source).files.map((file) => Object.freeze({
413
+ const models = sourceMetadata(source).files.map((file) => completeValue({
456
414
  file,
457
415
  name: `${safeId}--${file.name}`,
458
416
  }));
459
- return Object.freeze({
460
- models: Object.freeze(models),
417
+ return completeValue({
418
+ models: completeValue(models),
461
419
  model: models[0].name,
462
420
  manifest: `${safeId}.complete.json`,
463
421
  });
464
422
  }
465
423
 
466
- function manifestFor(source, files) {
467
- const observedBytes = files.reduce((total, file) => total + file.observedBytes, 0);
468
- return Object.freeze({
469
- schema: MODEL_MANIFEST_SCHEMA,
470
- complete: true,
471
- model: manifestModelIdentity(source),
472
- files: Object.freeze(files.map((file) => Object.freeze({
473
- name: file.name,
474
- finalUrl: file.finalUrl,
475
- observedBytes: file.observedBytes,
476
- }))),
477
- observedBytes,
478
- completedAt: new Date().toISOString(),
479
- });
480
- }
481
-
482
- function manifestByteLength(manifest) {
483
- return new TextEncoder().encode(`${JSON.stringify(manifest)}\n`).byteLength;
484
- }
485
-
486
- function projectedManifestByteLength(source) {
487
- return manifestByteLength(manifestFor(
488
- source,
489
- sourceMetadata(source).files.map((file) => ({
490
- name: file.name,
491
- finalUrl: file.url,
492
- observedBytes: file.bytes,
493
- })),
494
- ));
495
- }
496
-
497
- function manifestKind(manifest, source) {
498
- const model = manifest?.model;
499
- const members = sourceMetadata(source).files;
500
- if (
501
- manifest?.schema === MODEL_MANIFEST_SCHEMA
502
- && manifest?.complete === true
503
- && model?.id === source.id
504
- && Array.isArray(model?.files)
505
- && model.files.length === members.length
506
- && model.files.every((file, index) => (
507
- file?.name === members[index].name
508
- && file?.url === members[index].url
509
- ))
510
- && Array.isArray(manifest.files)
511
- && manifest.files.length === members.length
512
- && manifest.files.every((file, index) => (
513
- file?.name === members[index].name
514
- && Number.isSafeInteger(file?.observedBytes)
515
- && file.observedBytes >= 0
516
- && immutableHttpsUrl(file?.finalUrl)
517
- ))
518
- && Number.isSafeInteger(manifest.observedBytes)
519
- && manifest.observedBytes >= 0
520
- && manifest.files.reduce((total, file) => total + file.observedBytes, 0)
521
- === manifest.observedBytes
522
- ) return "set";
523
- if (!sourceMetadata(source).legacy || members.length !== 1) return null;
524
- const [member] = members;
525
- if (
526
- manifest?.schema === SINGLE_MODEL_MANIFEST_SCHEMA
527
- && manifest?.complete === true
528
- && model?.id === source.id
529
- && model?.url === member.url
530
- && Number.isSafeInteger(manifest.observedBytes)
531
- && manifest.observedBytes >= 0
532
- ) return "single";
533
- if (
534
- manifest?.schema === LEGACY_MODEL_MANIFEST_SCHEMA
535
- && manifest?.complete === true
536
- && model?.id === source.id
537
- && model?.name === member.name
538
- && model?.immutableUrl === member.url
539
- ) return "legacy";
540
- return null;
541
- }
542
-
543
- function progress(source, phase, loaded, total = null, memberIndex = 0, memberLoaded = loaded) {
544
- const members = sourceMetadata(source).files;
545
- const member = members[memberIndex] ?? members[0];
546
- return Object.freeze({
547
- modelId: source.id,
548
- phase,
549
- loaded,
550
- total,
551
- percent: Number.isSafeInteger(total) && total > 0 ? (loaded / total) * 100 : null,
552
- file: Object.freeze({
553
- index: memberIndex,
554
- count: members.length,
555
- name: member.name,
556
- loaded: memberLoaded,
557
- total: member.bytes ?? null,
558
- }),
559
- });
560
- }
561
-
562
424
  function securitySnapshot(security) {
563
- return Object.freeze({
564
- secure: security.secure,
565
- checks: Object.freeze({
566
- byteLength: security.checks.byteLength,
567
- sha256: security.checks.sha256,
568
- }),
569
- });
570
- }
571
-
572
- function assertDescriptorChecks(source, security) {
573
- const files = sourceMetadata(source).files;
574
- if (security.checks.byteLength && files.some((file) => file.bytes === undefined)) {
575
- throw fail(
576
- "ARCANE_AI_MODEL_SOURCE_INVALID",
577
- "Browser model bytes is required for every file when the byteLength check is enabled.",
578
- );
579
- }
580
- if (security.checks.sha256 && files.some((file) => file.sha256 === undefined)) {
581
- throw fail(
582
- "ARCANE_AI_MODEL_SOURCE_INVALID",
583
- "Browser model sha256 is required for every file when the sha256 check is enabled.",
584
- );
585
- }
586
- }
587
-
588
- function knownModelBytes(source) {
589
- const files = sourceMetadata(source).files;
590
- if (files.some((file) => file.bytes === undefined)) return null;
591
- const total = files.reduce((sum, file) => sum + file.bytes, 0);
592
- return Number.isSafeInteger(total) ? total : null;
593
- }
594
-
595
- function oversizedModelFile(source) {
596
- return sourceMetadata(source).files.find(
597
- (file) => file.bytes !== undefined && file.bytes > WLLAMA_MAX_FILE_BYTES,
598
- ) ?? null;
599
- }
600
-
601
- function integritySnapshot(security, source, {
602
- observedBytes = null,
603
- byteLength = security.checks.byteLength ? "pending" : "unchecked",
604
- sha256 = security.checks.sha256 ? "pending" : "unchecked",
605
- actualSha256 = null,
606
- files = null,
607
- } = {}) {
608
- const enabledStates = [];
609
- if (security.checks.byteLength) enabledStates.push(byteLength);
610
- if (security.checks.sha256) enabledStates.push(sha256);
611
- const state = enabledStates.length === 0
612
- ? "unchecked"
613
- : enabledStates.every((value) => value === "verified")
614
- ? "verified"
615
- : enabledStates.some((value) => value === "failed")
616
- ? "failed"
617
- : "pending";
618
- const members = sourceMetadata(source).files;
619
- const fileStates = members.map((member, index) => {
620
- const evidence = files?.[index] ?? {};
621
- return Object.freeze({
622
- name: member.name,
623
- observedBytes: evidence.observedBytes ?? null,
624
- byteLength: Object.freeze({
625
- enabled: security.checks.byteLength,
626
- state: evidence.byteLength ?? byteLength,
627
- expected: member.bytes ?? null,
628
- observed: evidence.observedBytes ?? null,
629
- }),
630
- sha256: Object.freeze({
631
- enabled: security.checks.sha256,
632
- state: evidence.sha256 ?? sha256,
633
- expected: member.sha256 ?? null,
634
- actual: evidence.actualSha256 ?? (members.length === 1 ? actualSha256 : null),
635
- }),
636
- });
637
- });
638
- return Object.freeze({
639
- state,
640
- observedBytes,
641
- byteLength: Object.freeze({
642
- enabled: security.checks.byteLength,
643
- state: byteLength,
644
- expected: knownModelBytes(source),
645
- observed: observedBytes,
646
- }),
647
- sha256: Object.freeze({
648
- enabled: security.checks.sha256,
649
- state: sha256,
650
- expected: members.length === 1 ? members[0].sha256 ?? null : null,
651
- actual: members.length === 1 ? actualSha256 : null,
652
- }),
653
- files: Object.freeze(fileStates),
654
- });
425
+ return security?.secure === true ? { secure: true } : undefined;
655
426
  }
656
427
 
657
428
  /**
658
429
  * Adapts an existing Arcane DBOPFS singleton without rebinding or changing any
659
- * of its public methods. The completion manifest is committed only after the
660
- * model file has been written. SHA-256 is read and computed only when its
661
- * effective check is enabled.
430
+ * of its public methods. Ordinary cache reuse is based on the selected model's
431
+ * expected files being present; no receipt or byte identity is created.
662
432
  */
663
433
  export function createDbopfsModelStore({
664
434
  dbopfs,
@@ -709,20 +479,17 @@ export function createDbopfsModelStore({
709
479
  }
710
480
  }
711
481
 
712
- async function write(name, body, { signal, onChunk } = {}) {
482
+ async function write(name, body, { signal } = {}) {
713
483
  const directory = await table();
714
484
  const handle = await directory.getFileHandle(name, { create: true });
715
485
  const writable = await handle.createWritable();
716
- let written = 0;
717
486
  try {
718
487
  for await (const chunk of byteChunks(body, signal)) {
719
488
  await writable.write(chunk);
720
- written += chunk.byteLength;
721
- await onChunk?.(chunk, written);
722
489
  }
723
490
  throwIfAborted(signal, "install");
724
491
  await writable.close();
725
- return written;
492
+ return undefined;
726
493
  } catch (error) {
727
494
  await writable.abort?.(error).catch(() => undefined);
728
495
  await directory.removeEntry(name).catch(() => undefined);
@@ -730,17 +497,6 @@ export function createDbopfsModelStore({
730
497
  }
731
498
  }
732
499
 
733
- async function readManifest(name, { removeInvalid = true } = {}) {
734
- const manifestFile = await file(name);
735
- if (!manifestFile) return null;
736
- try {
737
- return JSON.parse(await manifestFile.text());
738
- } catch {
739
- if (removeInvalid) await removeEntry(name);
740
- return null;
741
- }
742
- }
743
-
744
500
  async function removeNames(names) {
745
501
  const removed = [await removeEntry(names.manifest)];
746
502
  for (const entry of names.models) removed.push(await removeEntry(entry.name));
@@ -750,272 +506,56 @@ export function createDbopfsModelStore({
750
506
  async function remove(source) {
751
507
  let removed = await removeNames(storageName(source));
752
508
  if (sourceMetadata(source).legacy) {
753
- const legacyNames = storageName(source, { legacy: true });
754
- const legacyManifest = await readManifest(legacyNames.manifest, { removeInvalid: false });
755
- if (manifestKind(legacyManifest, source)) {
756
- removed = await removeNames(legacyNames) || removed;
757
- }
509
+ removed = await removeNames(storageName(source, { legacy: true })) || removed;
758
510
  }
759
511
  return removed;
760
512
  }
761
513
 
762
- async function writeManifest(name, manifest, signal) {
763
- const encoded = new TextEncoder().encode(`${JSON.stringify(manifest)}\n`);
764
- await write(name, encoded, { signal });
765
- }
766
-
767
- async function verifySha256(source, memberIndex, modelFile, {
768
- signal,
769
- onProgress,
770
- phase,
771
- completedBytes = 0,
772
- totalBytes = null,
773
- }) {
774
- const digest = createStreamingSha256();
775
- let hashed = 0;
776
- for await (const chunk of byteChunks(modelFile, signal)) {
777
- digest.update(chunk);
778
- hashed += chunk.byteLength;
779
- onProgress?.(progress(
780
- source,
781
- phase,
782
- completedBytes + hashed,
783
- totalBytes,
784
- memberIndex,
785
- hashed,
786
- ));
787
- }
788
- return Object.freeze({ hashed, sha256: digest.digestHex() });
514
+ async function storagePolicy({ cached = false } = {}) {
515
+ return completeValue({
516
+ compatibility: "compatible",
517
+ code: cached
518
+ ? "ARCANE_AI_MODEL_CACHE_AVAILABLE"
519
+ : "ARCANE_AI_MODEL_STORAGE_AVAILABLE",
520
+ measured: false,
521
+ });
789
522
  }
790
523
 
791
- async function storagePolicy(source, { cached = null, security } = {}) {
792
- if (cached) {
793
- const payloadBytes = cached.observedBytes;
794
- const manifestBytes = manifestByteLength(cached.manifest);
795
- const requiredBytes = payloadBytes + manifestBytes;
796
- return Object.freeze({
797
- compatibility: "compatible",
798
- code: "ARCANE_AI_MODEL_CACHE_COMPLETE",
799
- requiredBytes,
800
- payloadBytes,
801
- manifestBytes,
802
- quotaBytes: null,
803
- usageBytes: null,
804
- availableBytes: null,
805
- measured: false,
806
- admitted: true,
807
- });
808
- }
809
- if (security?.checks?.byteLength !== true) {
810
- return Object.freeze({
811
- compatibility: "unknown",
812
- code: "ARCANE_AI_MODEL_STORAGE_REQUIREMENT_UNBOUNDED",
813
- requiredBytes: null,
814
- payloadBytes: null,
815
- manifestBytes: null,
816
- quotaBytes: null,
817
- usageBytes: null,
818
- availableBytes: null,
819
- measured: false,
820
- admitted: false,
821
- });
822
- }
823
- const payloadBytes = knownModelBytes(source);
824
- const manifestBytes = payloadBytes === null ? null : projectedManifestByteLength(source);
825
- const requiredBytes = payloadBytes === null || !Number.isSafeInteger(payloadBytes + manifestBytes)
826
- ? null
827
- : payloadBytes + manifestBytes;
828
- if (requiredBytes === null) {
829
- return Object.freeze({
830
- compatibility: "unknown",
831
- code: "ARCANE_AI_MODEL_STORAGE_REQUIREMENT_UNKNOWN",
832
- requiredBytes: null,
833
- payloadBytes,
834
- manifestBytes,
835
- quotaBytes: null,
836
- usageBytes: null,
837
- availableBytes: null,
838
- measured: false,
839
- admitted: false,
840
- });
841
- }
842
- const estimator = estimateStorage
843
- ?? globalThis.navigator?.storage?.estimate?.bind(globalThis.navigator.storage);
844
- if (typeof estimator !== "function") {
845
- return Object.freeze({
846
- compatibility: "unknown",
847
- code: "ARCANE_AI_STORAGE_ESTIMATE_UNAVAILABLE",
848
- requiredBytes,
849
- payloadBytes,
850
- manifestBytes,
851
- quotaBytes: null,
852
- usageBytes: null,
853
- availableBytes: null,
854
- measured: false,
855
- admitted: false,
856
- });
857
- }
858
- let estimate;
859
- try {
860
- estimate = await estimator();
861
- } catch {
862
- return Object.freeze({
863
- compatibility: "unknown",
864
- code: "ARCANE_AI_STORAGE_ESTIMATE_FAILED",
865
- requiredBytes,
866
- payloadBytes,
867
- manifestBytes,
868
- quotaBytes: null,
869
- usageBytes: null,
870
- availableBytes: null,
871
- measured: true,
872
- admitted: false,
873
- });
524
+ async function storedModelFiles(names, signal) {
525
+ const modelFiles = [];
526
+ for (const entry of names.models) {
527
+ throwIfAborted(signal, "install");
528
+ const modelFile = await file(entry.name);
529
+ if (!modelFile) return null;
530
+ modelFiles.push(modelFile);
874
531
  }
875
- const quotaBytes = Number.isSafeInteger(estimate?.quota) && estimate.quota >= 0
876
- ? estimate.quota
877
- : null;
878
- const usageBytes = Number.isSafeInteger(estimate?.usage) && estimate.usage >= 0
879
- ? estimate.usage
880
- : null;
881
- const availableBytes = quotaBytes !== null && usageBytes !== null && quotaBytes >= usageBytes
882
- ? quotaBytes - usageBytes
883
- : null;
884
- const incompatible = availableBytes !== null && requiredBytes > availableBytes;
885
- return Object.freeze({
886
- compatibility: incompatible ? "incompatible" : availableBytes === null ? "unknown" : "compatible",
887
- code: incompatible
888
- ? "ARCANE_AI_STORAGE_CAPACITY_INSUFFICIENT"
889
- : availableBytes === null
890
- ? "ARCANE_AI_STORAGE_ESTIMATE_INVALID"
891
- : "ARCANE_AI_STORAGE_CAPACITY_AVAILABLE",
892
- requiredBytes,
893
- payloadBytes,
894
- manifestBytes,
895
- quotaBytes,
896
- usageBytes,
897
- availableBytes,
898
- measured: true,
899
- admitted: false,
900
- });
532
+ return modelFiles;
901
533
  }
902
534
 
903
535
  async function openCached(source, {
904
536
  signal,
905
- onProgress,
906
- security = resolveModelSecurity(),
907
537
  } = {}) {
908
- assertDescriptorChecks(source, security);
909
538
  let names = storageName(source);
910
- let manifest = await readManifest(names.manifest);
911
- let kind = manifestKind(manifest, source);
912
- if (!kind && sourceMetadata(source).legacy) {
539
+ let modelFiles = await storedModelFiles(names, signal);
540
+ if (!modelFiles && sourceMetadata(source).legacy) {
541
+ await removeNames(names);
913
542
  const legacyNames = storageName(source, { legacy: true });
914
- const legacyManifest = await readManifest(legacyNames.manifest, { removeInvalid: false });
915
- const legacyKind = manifestKind(legacyManifest, source);
916
- if (legacyKind) {
543
+ const legacyFiles = await storedModelFiles(legacyNames, signal);
544
+ if (legacyFiles) {
917
545
  names = legacyNames;
918
- manifest = legacyManifest;
919
- kind = legacyKind;
546
+ modelFiles = legacyFiles;
547
+ } else {
548
+ await removeNames(legacyNames);
920
549
  }
921
550
  }
922
- if (!kind) {
923
- // Model files without the exact ordered completion manifest are partial.
924
- await remove(source);
551
+ if (!modelFiles) {
552
+ await removeNames(names);
925
553
  return null;
926
554
  }
927
- const members = sourceMetadata(source).files;
928
- const modelFiles = [];
929
- const fileEvidence = [];
930
- let observedBytes = 0;
931
555
  try {
932
- for (let index = 0; index < names.models.length; index += 1) {
933
- const member = members[index];
934
- const modelFile = await file(names.models[index].name);
935
- if (!modelFile) {
936
- await removeNames(names);
937
- return null;
938
- }
939
- if (modelFile.size > WLLAMA_MAX_FILE_BYTES) {
940
- await removeNames(names);
941
- throw fail(
942
- "ARCANE_AI_MODEL_SHARD_TOO_LARGE",
943
- `Cached model file ${member.name} exceeds Wllama's ${WLLAMA_MAX_FILE_BYTES}-byte boundary.`,
944
- );
945
- }
946
- if (kind === "set" && manifest.files[index].observedBytes !== modelFile.size) {
947
- await removeNames(names);
948
- return null;
949
- }
950
- if (security.checks.byteLength && modelFile.size !== member.bytes) {
951
- await removeNames(names);
952
- return null;
953
- }
954
- modelFiles.push(modelFile);
955
- fileEvidence.push({
956
- observedBytes: modelFile.size,
957
- byteLength: security.checks.byteLength ? "verified" : "unchecked",
958
- sha256: security.checks.sha256 ? "pending" : "unchecked",
959
- actualSha256: null,
960
- });
961
- observedBytes += modelFile.size;
962
- }
963
- if ((kind === "set" || kind === "single") && manifest.observedBytes !== observedBytes) {
964
- await removeNames(names);
965
- return null;
966
- }
967
- let completedBytes = 0;
968
- for (let index = 0; index < modelFiles.length; index += 1) {
969
- const modelFile = modelFiles[index];
970
- if (security.checks.sha256) {
971
- const verification = await verifySha256(source, index, modelFile, {
972
- signal,
973
- onProgress,
974
- phase: "verify-cache",
975
- completedBytes,
976
- totalBytes: observedBytes,
977
- });
978
- fileEvidence[index].actualSha256 = verification.sha256;
979
- if (
980
- verification.hashed !== modelFile.size
981
- || verification.sha256 !== members[index].sha256
982
- ) {
983
- await removeNames(names);
984
- return null;
985
- }
986
- fileEvidence[index].sha256 = "verified";
987
- } else {
988
- onProgress?.(progress(
989
- source,
990
- "cache",
991
- completedBytes + modelFile.size,
992
- observedBytes,
993
- index,
994
- modelFile.size,
995
- ));
996
- }
997
- completedBytes += modelFile.size;
998
- }
999
- let completion = manifest;
1000
- if (kind !== "set") {
1001
- completion = manifestFor(source, [{
1002
- name: members[0].name,
1003
- finalUrl: manifest.finalUrl ?? members[0].url,
1004
- observedBytes,
1005
- }]);
1006
- }
1007
- return Object.freeze({
1008
- files: Object.freeze(modelFiles),
556
+ return completeValue({
557
+ files: completeValue(modelFiles),
1009
558
  file: modelFiles.length === 1 ? modelFiles[0] : null,
1010
- manifest: completion,
1011
- observedBytes,
1012
- integrity: integritySnapshot(security, source, {
1013
- observedBytes,
1014
- byteLength: security.checks.byteLength ? "verified" : "unchecked",
1015
- sha256: security.checks.sha256 ? "verified" : "unchecked",
1016
- actualSha256: fileEvidence[0]?.actualSha256 ?? null,
1017
- files: fileEvidence,
1018
- }),
1019
559
  });
1020
560
  } catch (error) {
1021
561
  if (!signal?.aborted) await removeNames(names);
@@ -1023,121 +563,33 @@ export function createDbopfsModelStore({
1023
563
  }
1024
564
  }
1025
565
 
1026
- async function openVerified(source, { signal, onProgress } = {}) {
1027
- const security = resolveModelSecurity({ load: { secure: true } });
1028
- return openCached(source, { signal, onProgress, security });
1029
- }
1030
-
1031
- async function install(source, { signal, onProgress, security: configuredSecurity } = {}) {
1032
- const security = resolveModelSecurity({ load: configuredSecurity });
1033
- assertDescriptorChecks(source, security);
566
+ async function install(source, { signal } = {}) {
1034
567
  const names = storageName(source);
1035
568
  const members = sourceMetadata(source).files;
1036
569
  await remove(source);
1037
570
  const modelFiles = [];
1038
- const manifestFiles = [];
1039
- const fileEvidence = [];
1040
- const expectedTotal = knownModelBytes(source);
1041
- let observedBytes = 0;
1042
571
  try {
1043
572
  for (let index = 0; index < members.length; index += 1) {
1044
573
  const member = members[index];
1045
574
  const opened = await source.open(index, { signal });
1046
575
  try {
1047
- if (opened.reportedBytes !== null && opened.reportedBytes > WLLAMA_MAX_FILE_BYTES) {
1048
- throw fail(
1049
- "ARCANE_AI_MODEL_SHARD_TOO_LARGE",
1050
- `Model file ${member.name} exceeds Wllama's ${WLLAMA_MAX_FILE_BYTES}-byte boundary.`,
1051
- );
1052
- }
1053
- if (
1054
- security.checks.byteLength
1055
- && opened.reportedBytes !== null
1056
- && opened.reportedBytes !== member.bytes
1057
- ) {
1058
- throw fail(
1059
- "ARCANE_AI_MODEL_SIZE_MISMATCH",
1060
- "A model response Content-Length did not match its expected byte length.",
1061
- );
1062
- }
1063
- const downloadDigest = security.checks.sha256 ? createStreamingSha256() : null;
1064
- const written = await write(names.models[index].name, opened.body, {
1065
- signal,
1066
- async onChunk(chunk, loaded) {
1067
- downloadDigest?.update(chunk);
1068
- if (loaded > WLLAMA_MAX_FILE_BYTES) {
1069
- throw fail(
1070
- "ARCANE_AI_MODEL_SHARD_TOO_LARGE",
1071
- `Model file ${member.name} exceeds Wllama's ${WLLAMA_MAX_FILE_BYTES}-byte boundary.`,
1072
- );
1073
- }
1074
- if (security.checks.byteLength && loaded > member.bytes) {
1075
- throw fail("ARCANE_AI_MODEL_SIZE_MISMATCH", "Downloaded model file exceeded its declared size.");
1076
- }
1077
- onProgress?.(progress(
1078
- source,
1079
- "download",
1080
- observedBytes + loaded,
1081
- expectedTotal,
1082
- index,
1083
- loaded,
1084
- ));
1085
- },
1086
- });
1087
- if (security.checks.byteLength && written !== member.bytes) {
1088
- throw fail(
1089
- "ARCANE_AI_MODEL_SIZE_MISMATCH",
1090
- "Downloaded model file bytes did not match the caller-supplied expected byte length.",
1091
- );
1092
- }
576
+ await write(names.models[index].name, opened.body, { signal });
1093
577
  const modelFile = await file(names.models[index].name);
1094
- if (!modelFile || modelFile.size !== written) {
578
+ if (!modelFile) {
1095
579
  throw fail(
1096
580
  "ARCANE_AI_MODEL_CACHE_REJECTED",
1097
- "A stored model file did not preserve the observed downloaded byte count.",
581
+ "A stored model file could not be reopened.",
1098
582
  );
1099
583
  }
1100
- const evidence = {
1101
- observedBytes: written,
1102
- byteLength: security.checks.byteLength ? "verified" : "unchecked",
1103
- sha256: security.checks.sha256 ? "pending" : "unchecked",
1104
- actualSha256: null,
1105
- };
1106
- if (security.checks.sha256) {
1107
- evidence.actualSha256 = downloadDigest.digestHex();
1108
- if (evidence.actualSha256 !== member.sha256) {
1109
- throw fail(
1110
- "ARCANE_AI_MODEL_DIGEST_MISMATCH",
1111
- "Downloaded model file bytes did not match the caller-supplied SHA-256 value.",
1112
- );
1113
- }
1114
- evidence.sha256 = "verified";
1115
- }
1116
584
  modelFiles.push(modelFile);
1117
- fileEvidence.push(evidence);
1118
- manifestFiles.push({ name: member.name, finalUrl: opened.finalUrl, observedBytes: written });
1119
- observedBytes += written;
1120
585
  } catch (error) {
1121
586
  await opened.cancel?.(error).catch(() => undefined);
1122
587
  throw error;
1123
588
  }
1124
589
  }
1125
- // Completion is the final storage mutation. No member is admitted until
1126
- // this exact ordered set manifest exists.
1127
- const manifest = manifestFor(source, manifestFiles);
1128
- await writeManifest(names.manifest, manifest, signal);
1129
- return Object.freeze({
1130
- files: Object.freeze(modelFiles),
590
+ return completeValue({
591
+ files: completeValue(modelFiles),
1131
592
  file: modelFiles.length === 1 ? modelFiles[0] : null,
1132
- manifest,
1133
- observedBytes,
1134
- integrity: integritySnapshot(security, source, {
1135
- observedBytes,
1136
- byteLength: security.checks.byteLength ? "verified" : "unchecked",
1137
- sha256: security.checks.sha256 ? "verified" : "unchecked",
1138
- actualSha256: fileEvidence[0]?.actualSha256 ?? null,
1139
- files: fileEvidence,
1140
- }),
1141
593
  });
1142
594
  } catch (error) {
1143
595
  await remove(source).catch(() => undefined);
@@ -1147,42 +599,31 @@ export function createDbopfsModelStore({
1147
599
 
1148
600
  async function ensure(source, {
1149
601
  signal,
1150
- onProgress,
1151
602
  onCapabilityPolicy,
1152
603
  offline = false,
1153
- security: configuredSecurity,
1154
604
  } = {}) {
1155
- const security = resolveModelSecurity({ load: configuredSecurity });
1156
- assertDescriptorChecks(source, security);
1157
- const cached = await openCached(source, { signal, onProgress, security });
605
+ const cached = await openCached(source, { signal });
1158
606
  if (cached) {
1159
- const storage = await storagePolicy(source, { cached, security });
607
+ const storage = await storagePolicy({ cached: true });
1160
608
  onCapabilityPolicy?.(storage);
1161
- return Object.freeze({ ...cached, cache: "cached", storage });
609
+ return completeValue({ ...cached, cache: "cached", storage });
1162
610
  }
1163
611
  if (offline) {
1164
- throw fail("ARCANE_AI_MODEL_OFFLINE_MISS", "No admitted offline model cache is available.");
612
+ throw fail("ARCANE_AI_MODEL_OFFLINE_MISS", "No cached offline model is available.");
1165
613
  }
1166
- const storage = await storagePolicy(source, { security });
614
+ const storage = await storagePolicy();
1167
615
  onCapabilityPolicy?.(storage);
1168
- if (storage.compatibility === "incompatible") {
1169
- throw fail(
1170
- storage.code,
1171
- "Available browser storage is smaller than the model file set and completion manifest.",
1172
- );
1173
- }
1174
- const installed = await install(source, { signal, onProgress, security });
1175
- const admittedStorage = await storagePolicy(source, { cached: installed, security });
616
+ const installed = await install(source, { signal });
617
+ const admittedStorage = await storagePolicy({ cached: true });
1176
618
  onCapabilityPolicy?.(admittedStorage);
1177
- return Object.freeze({ ...installed, cache: "installed", storage: admittedStorage });
619
+ return completeValue({ ...installed, cache: "installed", storage: admittedStorage });
1178
620
  }
1179
621
 
1180
- const store = Object.freeze({
622
+ const store = completeValue({
1181
623
  kind: "arcane-dbopfs-model-store",
1182
624
  tableName,
1183
625
  adapter: dbopfs,
1184
626
  ready: () => table().then(() => undefined),
1185
- openVerified,
1186
627
  install,
1187
628
  ensure,
1188
629
  remove,
@@ -1196,7 +637,7 @@ function linkAbortSignal(externalSignal) {
1196
637
  const forward = () => controller.abort(externalSignal.reason);
1197
638
  if (externalSignal?.aborted) forward();
1198
639
  else externalSignal?.addEventListener?.("abort", forward, { once: true });
1199
- return Object.freeze({
640
+ return completeValue({
1200
641
  controller,
1201
642
  release() {
1202
643
  externalSignal?.removeEventListener?.("abort", forward);
@@ -1300,7 +741,7 @@ function createSerialRequestQueue(onDepth) {
1300
741
  return ready;
1301
742
  }
1302
743
 
1303
- return Object.freeze({
744
+ return completeValue({
1304
745
  schedule,
1305
746
  openStream,
1306
747
  idle: () => tail,
@@ -1312,19 +753,139 @@ function responseFormat(structuredOutput) {
1312
753
  return undefined;
1313
754
  }
1314
755
  if (structuredOutput === true || structuredOutput === "json") {
1315
- return Object.freeze({ type: "json_object" });
756
+ return completeValue({ type: "json_object" });
1316
757
  }
1317
758
  if (typeof structuredOutput !== "object" || Array.isArray(structuredOutput)) {
1318
759
  throw new TypeError("structuredOutput must be false, true, \"json\", or a JSON Schema object.");
1319
760
  }
1320
- return Object.freeze({
761
+ return completeValue({
1321
762
  type: "json_schema",
1322
- json_schema: Object.freeze({ name: "arcane_response", strict: true, schema: structuredOutput }),
763
+ json_schema: completeValue({ name: "arcane_response", strict: true, schema: structuredOutput }),
1323
764
  });
1324
765
  }
1325
766
 
767
+ function plainStructuralRecord(value) {
768
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
769
+ const prototype = Object.getPrototypeOf(value);
770
+ return prototype === Object.prototype || prototype === null;
771
+ }
772
+
773
+ function validateToolMessageSchemas(value) {
774
+ if (value === undefined) return;
775
+ if (!Array.isArray(value)) throw new TypeError("tools must be an array.");
776
+ for (const [index, tool] of value.entries()) {
777
+ const parameters = tool?.function?.parameters;
778
+ const messageSchema = parameters?.properties?.message;
779
+ if (
780
+ !plainStructuralRecord(tool)
781
+ || tool.type !== "function"
782
+ || !plainStructuralRecord(tool.function)
783
+ || !plainStructuralRecord(parameters)
784
+ || parameters.type !== "object"
785
+ || !plainStructuralRecord(parameters.properties)
786
+ || !plainStructuralRecord(messageSchema)
787
+ || messageSchema.type !== "string"
788
+ || !Number.isInteger(messageSchema.minLength)
789
+ || messageSchema.minLength < 1
790
+ || !Array.isArray(parameters.required)
791
+ || !parameters.required.includes("message")
792
+ ) {
793
+ throw fail(
794
+ "ARCANE_AI_TOOL_MESSAGE_REQUIRED",
795
+ `tools[${String(index)}] must require a nonempty string parameters.properties.message.`,
796
+ );
797
+ }
798
+ }
799
+ }
800
+
801
+ function validateRequestMessages(messages) {
802
+ let pendingToolCallId = null;
803
+ for (const [messageIndex, message] of messages.entries()) {
804
+ if (!plainStructuralRecord(message)) {
805
+ throw new TypeError(`messages[${String(messageIndex)}] must be a plain object.`);
806
+ }
807
+ const hasToolCalls = Object.hasOwn(message, "tool_calls");
808
+ const calls = validateToolCalls(message, `messages[${String(messageIndex)}]`);
809
+ let openedToolCall = false;
810
+ if (hasToolCalls) {
811
+ if (message?.role !== "assistant") {
812
+ throw fail(
813
+ "ARCANE_AI_TOOL_CALL_INVALID",
814
+ `messages[${String(messageIndex)}].tool_calls is supported only for assistant messages.`,
815
+ );
816
+ }
817
+ if (pendingToolCallId !== null && calls.length) {
818
+ throw fail(
819
+ "ARCANE_AI_PARALLEL_TOOLS_UNSUPPORTED",
820
+ "The Arcane chat session accepts one structural tool call at a time.",
821
+ );
822
+ }
823
+ if (calls.length) {
824
+ pendingToolCallId = calls[0].id;
825
+ openedToolCall = true;
826
+ }
827
+ }
828
+ if (message?.role === "tool") {
829
+ if (typeof message.content !== "string" || !message.content.trim()) {
830
+ throw fail(
831
+ "ARCANE_AI_INVALID_TOOL_MESSAGE",
832
+ `messages[${String(messageIndex)}] must contain a nonblank user-facing tool result.`,
833
+ );
834
+ }
835
+ if (
836
+ pendingToolCallId === null
837
+ || typeof message.tool_call_id !== "string"
838
+ || message.tool_call_id !== pendingToolCallId
839
+ ) {
840
+ throw fail(
841
+ "ARCANE_AI_INVALID_TOOL_MESSAGE",
842
+ `messages[${String(messageIndex)}] does not settle the pending structural tool call.`,
843
+ );
844
+ }
845
+ pendingToolCallId = null;
846
+ } else {
847
+ if (Object.hasOwn(message, "tool_call_id")) {
848
+ throw fail(
849
+ "ARCANE_AI_INVALID_TOOL_MESSAGE",
850
+ `messages[${String(messageIndex)}].tool_call_id is valid only for a tool result.`,
851
+ );
852
+ }
853
+ if (pendingToolCallId !== null && !openedToolCall) {
854
+ throw fail(
855
+ "ARCANE_AI_TOOL_RESULT_REQUIRED",
856
+ `messages[${String(messageIndex)}] precedes the pending structural tool result.`,
857
+ );
858
+ }
859
+ }
860
+ }
861
+ if (pendingToolCallId !== null) {
862
+ throw fail(
863
+ "ARCANE_AI_TOOL_RESULT_REQUIRED",
864
+ "The pending structural tool call must be settled before requesting another response.",
865
+ );
866
+ }
867
+ }
868
+
869
+ function validateStructuralRequest(request) {
870
+ if (!plainStructuralRecord(request)) {
871
+ throw new TypeError("The browser-WASM LLM request must be a plain object.");
872
+ }
873
+ if (!Array.isArray(request.messages)) throw new TypeError("messages must be an array.");
874
+ validateRequestMessages(request.messages);
875
+ validateToolMessageSchemas(request.tools);
876
+ const parallelValues = [request.parallelToolCalls, request.parallel_tool_calls];
877
+ if (parallelValues.some(function enablesParallelBrowserWasmTools(value) {
878
+ return value !== undefined && value !== false;
879
+ })) {
880
+ throw fail(
881
+ "ARCANE_AI_PARALLEL_TOOLS_UNSUPPORTED",
882
+ "The Arcane chat session accepts one structural tool call at a time.",
883
+ );
884
+ }
885
+ }
886
+
1326
887
  function completionOptions(request, abortSignal, stream) {
1327
- if (!Array.isArray(request?.messages)) throw new TypeError("messages must be an array.");
888
+ validateStructuralRequest(request);
1328
889
  const options = {
1329
890
  messages: request.messages,
1330
891
  stream,
@@ -1339,8 +900,11 @@ function completionOptions(request, abortSignal, stream) {
1339
900
  ["minP", "min_p"],
1340
901
  ["min_p", "min_p"],
1341
902
  ["repeatPenalty", "penalty_repeat"],
903
+ ["repeat", "penalty_repeat"],
904
+ ["repeat_penalty", "penalty_repeat"],
1342
905
  ["penalty_repeat", "penalty_repeat"],
1343
906
  ["maxTokens", "max_tokens"],
907
+ ["maxOutputTokens", "max_tokens"],
1344
908
  ["max_tokens", "max_tokens"],
1345
909
  ["seed", "seed"],
1346
910
  ["stop", "stop"],
@@ -1348,6 +912,12 @@ function completionOptions(request, abortSignal, stream) {
1348
912
  for (const [source, target] of copy) {
1349
913
  if (request[source] !== undefined) options[target] = request[source];
1350
914
  }
915
+ if (request.templateOptions !== undefined) {
916
+ if (!request.templateOptions || typeof request.templateOptions !== "object" || Array.isArray(request.templateOptions)) {
917
+ throw new TypeError("templateOptions must be a plain object when provided.");
918
+ }
919
+ options.chat_template_kwargs = { ...request.templateOptions };
920
+ }
1351
921
  if (request.tools !== undefined) {
1352
922
  if (!Array.isArray(request.tools)) throw new TypeError("tools must be an array.");
1353
923
  options.tools = request.tools;
@@ -1356,51 +926,207 @@ function completionOptions(request, abortSignal, stream) {
1356
926
  if (request.tool_choice !== undefined) options.tool_choice = request.tool_choice;
1357
927
  if (request.parallelToolCalls !== undefined) options.parallel_tool_calls = request.parallelToolCalls;
1358
928
  if (request.parallel_tool_calls !== undefined) options.parallel_tool_calls = request.parallel_tool_calls;
929
+ if (
930
+ request.tools?.length
931
+ && request.parallelToolCalls === undefined
932
+ && request.parallel_tool_calls === undefined
933
+ ) options.parallel_tool_calls = false;
1359
934
  const format = responseFormat(request.structuredOutput);
1360
935
  if (format) options.response_format = format;
1361
936
  return options;
1362
937
  }
1363
938
 
1364
- function validateToolCalls(message) {
1365
- if (message?.tool_calls === undefined) return;
1366
- if (!Array.isArray(message.tool_calls)) {
1367
- throw fail("ARCANE_AI_INVALID_PROVIDER_RESULT", "The model returned malformed tool calls.");
939
+ function validateToolCalls(message, location = "The model response") {
940
+ if (!plainStructuralRecord(message)) {
941
+ throw fail("ARCANE_AI_INVALID_PROVIDER_RESULT", `${location} is not a plain assistant message.`);
942
+ }
943
+ if (
944
+ Object.hasOwn(message, "toolCalls")
945
+ || Object.hasOwn(message, "function_call")
946
+ || Object.hasOwn(message, "functionCall")
947
+ ) {
948
+ throw fail("ARCANE_AI_TOOL_CALL_INVALID", `${location} contains a noncanonical structural tool-call field.`);
949
+ }
950
+ if (!Object.hasOwn(message, "tool_calls")) return [];
951
+ const descriptor = Object.getOwnPropertyDescriptor(message, "tool_calls");
952
+ if (!descriptor || !Object.hasOwn(descriptor, "value") || !Array.isArray(descriptor.value)) {
953
+ throw fail("ARCANE_AI_TOOL_CALL_INVALID", `${location} contains malformed tool calls.`);
954
+ }
955
+ const calls = descriptor.value;
956
+ if (calls.length > 1) {
957
+ throw fail(
958
+ "ARCANE_AI_PARALLEL_TOOLS_UNSUPPORTED",
959
+ "The Arcane chat session accepts one structural tool call at a time.",
960
+ );
1368
961
  }
1369
962
  const ids = new Set();
1370
- for (const call of message.tool_calls) {
963
+ for (const call of calls) {
1371
964
  if (
1372
- typeof call?.id !== "string"
1373
- || !call.id
965
+ !plainStructuralRecord(call)
966
+ || typeof call.id !== "string"
967
+ || !call.id.trim()
1374
968
  || ids.has(call.id)
1375
969
  || call.type !== "function"
1376
- || typeof call.function?.name !== "string"
1377
- || !call.function.name
1378
- || typeof call.function?.arguments !== "string"
970
+ || !plainStructuralRecord(call.function)
971
+ || typeof call.function.name !== "string"
972
+ || !call.function.name.trim()
973
+ || typeof call.function.arguments !== "string"
1379
974
  ) {
1380
- throw fail("ARCANE_AI_INVALID_PROVIDER_RESULT", "The model returned malformed tool calls.");
975
+ throw fail("ARCANE_AI_TOOL_CALL_INVALID", `${location} contains malformed tool calls.`);
976
+ }
977
+ let argumentsRecord;
978
+ try {
979
+ argumentsRecord = JSON.parse(call.function.arguments);
980
+ } catch (error) {
981
+ throw fail(
982
+ "ARCANE_AI_TOOL_CALL_INVALID",
983
+ `${location} contains structural tool arguments that are not a JSON object.`,
984
+ error,
985
+ );
986
+ }
987
+ if (!plainStructuralRecord(argumentsRecord)) {
988
+ throw fail(
989
+ "ARCANE_AI_TOOL_CALL_INVALID",
990
+ `${location} contains structural tool arguments that are not a JSON object.`,
991
+ );
992
+ }
993
+ if (typeof argumentsRecord.message !== "string" || !argumentsRecord.message.trim()) {
994
+ throw fail(
995
+ "ARCANE_AI_TOOL_MESSAGE_REQUIRED",
996
+ `${location} structural tool arguments must include a nonempty user-facing message.`,
997
+ );
1381
998
  }
1382
999
  ids.add(call.id);
1383
1000
  }
1001
+ return calls;
1384
1002
  }
1385
1003
 
1386
1004
  function validateCompletion(value, requestId) {
1387
1005
  if (
1388
- !value
1389
- || typeof value !== "object"
1390
- || !Array.isArray(value.choices)
1391
- || value.choices.length === 0
1006
+ !plainStructuralRecord(value)
1007
+ ) {
1008
+ throw fail("ARCANE_AI_INVALID_PROVIDER_RESULT", "The model returned an invalid chat completion.");
1009
+ }
1010
+ const hasMessage = Object.hasOwn(value, "message");
1011
+ const hasChoices = Object.hasOwn(value, "choices");
1012
+ if (hasMessage === hasChoices) {
1013
+ throw fail(
1014
+ "ARCANE_AI_INVALID_PROVIDER_RESULT",
1015
+ "The model completion must contain exactly one message or choices envelope.",
1016
+ );
1017
+ }
1018
+ if (hasMessage) {
1019
+ const messageDescriptor = Object.getOwnPropertyDescriptor(value, "message");
1020
+ if (
1021
+ !messageDescriptor
1022
+ || !Object.hasOwn(messageDescriptor, "value")
1023
+ || !plainStructuralRecord(messageDescriptor.value)
1024
+ || messageDescriptor.value.role !== "assistant"
1025
+ ) {
1026
+ throw fail("ARCANE_AI_INVALID_PROVIDER_RESULT", "The model returned an invalid assistant message.");
1027
+ }
1028
+ validateToolCalls(messageDescriptor.value);
1029
+ return requestId === undefined ? value : completeValue({ ...value, id: requestId });
1030
+ }
1031
+ const choicesDescriptor = Object.getOwnPropertyDescriptor(value, "choices");
1032
+ if (
1033
+ !choicesDescriptor
1034
+ || !Object.hasOwn(choicesDescriptor, "value")
1035
+ || !Array.isArray(choicesDescriptor.value)
1036
+ || choicesDescriptor.value.length === 0
1392
1037
  ) {
1393
1038
  throw fail("ARCANE_AI_INVALID_PROVIDER_RESULT", "The model returned an invalid chat completion.");
1394
1039
  }
1040
+ const choices = choicesDescriptor.value;
1395
1041
  const indexes = new Set();
1396
- for (const choice of value.choices) {
1397
- if (!Number.isSafeInteger(choice?.index) || choice.index < 0 || indexes.has(choice.index)) {
1042
+ let toolCallCount = 0;
1043
+ for (let choicePosition = 0; choicePosition < choices.length; choicePosition += 1) {
1044
+ const choice = choices[choicePosition];
1045
+ const messageDescriptor = plainStructuralRecord(choice)
1046
+ ? Object.getOwnPropertyDescriptor(choice, "message")
1047
+ : null;
1048
+ if (
1049
+ !plainStructuralRecord(choice)
1050
+ || !Number.isSafeInteger(choice.index)
1051
+ || choice.index < 0
1052
+ || indexes.has(choice.index)
1053
+ || !messageDescriptor
1054
+ || !Object.hasOwn(messageDescriptor, "value")
1055
+ || !plainStructuralRecord(messageDescriptor.value)
1056
+ || messageDescriptor.value.role !== "assistant"
1057
+ ) {
1398
1058
  throw fail("ARCANE_AI_INVALID_PROVIDER_RESULT", "The model returned an invalid choice index.");
1399
1059
  }
1400
1060
  indexes.add(choice.index);
1401
- validateToolCalls(choice.message);
1061
+ const choiceToolCallCount = validateToolCalls(
1062
+ messageDescriptor.value,
1063
+ `The model response choice ${String(choicePosition)}`,
1064
+ ).length;
1065
+ if (choicePosition > 0 && choiceToolCallCount) {
1066
+ throw fail(
1067
+ "ARCANE_AI_INVALID_PROVIDER_RESULT",
1068
+ "The model placed a structural tool call outside the selected first choice.",
1069
+ );
1070
+ }
1071
+ toolCallCount += choiceToolCallCount;
1072
+ if (toolCallCount > 1) {
1073
+ throw fail(
1074
+ "ARCANE_AI_PARALLEL_TOOLS_UNSUPPORTED",
1075
+ "The Arcane chat session accepts one structural tool call at a time.",
1076
+ );
1077
+ }
1078
+ }
1079
+ return requestId === undefined ? value : completeValue({ ...value, id: requestId });
1080
+ }
1081
+
1082
+ function isPublicStreamContentKey(key) {
1083
+ return key === "content"
1084
+ || key === "text"
1085
+ || key === "thinking"
1086
+ || key === "reasoning"
1087
+ || key === "reasoning_content";
1088
+ }
1089
+
1090
+ function projectPublicStreamContent(value, seen = new WeakSet()) {
1091
+ if (!value || typeof value !== "object" || seen.has(value)) return null;
1092
+ seen.add(value);
1093
+ if (Array.isArray(value)) {
1094
+ const result = [];
1095
+ for (const item of value) {
1096
+ const projected = projectPublicStreamContent(item, seen);
1097
+ if (projected !== null) result.push(projected);
1098
+ }
1099
+ seen.delete(value);
1100
+ return result.length ? result : null;
1402
1101
  }
1403
- return requestId === undefined ? value : Object.freeze({ ...value, id: requestId });
1102
+ if (!plainStructuralRecord(value)) {
1103
+ seen.delete(value);
1104
+ return null;
1105
+ }
1106
+ const result = {};
1107
+ const descriptors = Object.getOwnPropertyDescriptors(value);
1108
+ for (const key of Reflect.ownKeys(descriptors)) {
1109
+ if (typeof key === "symbol") continue;
1110
+ const descriptor = descriptors[key];
1111
+ if (!Object.hasOwn(descriptor, "value")) continue;
1112
+ if (
1113
+ isPublicStreamContentKey(key)
1114
+ && descriptor.value !== null
1115
+ && descriptor.value !== undefined
1116
+ ) {
1117
+ result[key] = descriptor.value;
1118
+ continue;
1119
+ }
1120
+ const projected = projectPublicStreamContent(descriptor.value, seen);
1121
+ if (projected !== null) result[key] = projected;
1122
+ }
1123
+ seen.delete(value);
1124
+ return Object.keys(result).length ? result : null;
1125
+ }
1126
+
1127
+ function projectPublicStreamChunk(value) {
1128
+ if (typeof value === "string") return value;
1129
+ return projectPublicStreamContent(value);
1404
1130
  }
1405
1131
 
1406
1132
  function createCompletionAccumulator(modelId, requestId) {
@@ -1453,14 +1179,34 @@ function createCompletionAccumulator(modelId, requestId) {
1453
1179
  const tool = record.tools.get(fragment.index) ?? {
1454
1180
  index: fragment.index,
1455
1181
  id: "",
1182
+ invalidArguments: false,
1183
+ invalidIdentity: false,
1456
1184
  type: "",
1457
1185
  name: "",
1458
1186
  arguments: "",
1459
1187
  };
1460
- if (typeof fragment.id === "string" && !tool.id) tool.id = fragment.id;
1461
- if (typeof fragment.type === "string") tool.type = fragment.type;
1462
- if (typeof fragment.function?.name === "string") tool.name += fragment.function.name;
1463
- if (typeof fragment.function?.arguments === "string") tool.arguments += fragment.function.arguments;
1188
+ if (fragment.id !== undefined) {
1189
+ if (typeof fragment.id !== "string" || !fragment.id || tool.id && tool.id !== fragment.id) {
1190
+ tool.invalidIdentity = true;
1191
+ } else {
1192
+ tool.id = fragment.id;
1193
+ }
1194
+ }
1195
+ if (fragment.type !== undefined) {
1196
+ if (typeof fragment.type !== "string" || !fragment.type || tool.type && tool.type !== fragment.type) {
1197
+ tool.invalidIdentity = true;
1198
+ } else {
1199
+ tool.type = fragment.type;
1200
+ }
1201
+ }
1202
+ if (fragment.function?.name !== undefined) {
1203
+ if (typeof fragment.function.name !== "string") tool.invalidIdentity = true;
1204
+ else tool.name += fragment.function.name;
1205
+ }
1206
+ if (fragment.function?.arguments !== undefined) {
1207
+ if (typeof fragment.function.arguments !== "string") tool.invalidArguments = true;
1208
+ else tool.arguments += fragment.function.arguments;
1209
+ }
1464
1210
  record.tools.set(fragment.index, tool);
1465
1211
  }
1466
1212
  }
@@ -1479,11 +1225,19 @@ function createCompletionAccumulator(modelId, requestId) {
1479
1225
  if (record.tools.size) {
1480
1226
  message.tool_calls = [...record.tools.values()]
1481
1227
  .sort((a, b) => a.index - b.index)
1482
- .map((tool) => ({
1483
- id: tool.id,
1484
- type: tool.type,
1485
- function: { name: tool.name, arguments: tool.arguments },
1486
- }));
1228
+ .map((tool) => {
1229
+ if (tool.invalidArguments || tool.invalidIdentity) {
1230
+ throw fail(
1231
+ "ARCANE_AI_TOOL_CALL_INVALID",
1232
+ "A streamed structural tool call changed or omitted an exact field.",
1233
+ );
1234
+ }
1235
+ return {
1236
+ id: tool.id,
1237
+ type: tool.type,
1238
+ function: { name: tool.name, arguments: tool.arguments },
1239
+ };
1240
+ });
1487
1241
  }
1488
1242
  return { index: record.index, message, finish_reason: record.finish_reason };
1489
1243
  }),
@@ -1491,7 +1245,7 @@ function createCompletionAccumulator(modelId, requestId) {
1491
1245
  return validateCompletion(completion, requestId);
1492
1246
  }
1493
1247
 
1494
- return Object.freeze({ push, result });
1248
+ return completeValue({ push, result });
1495
1249
  }
1496
1250
 
1497
1251
  function callbackStreamHandle({ runtime, request, signal, onSettled }) {
@@ -1508,9 +1262,11 @@ function callbackStreamHandle({ runtime, request, signal, onSettled }) {
1508
1262
  if (ended || linked.controller.signal.aborted) return;
1509
1263
  const chunk = request.id === undefined ? value : { ...value, id: request.id };
1510
1264
  accumulator.push(chunk);
1265
+ const publicChunk = projectPublicStreamChunk(chunk);
1266
+ if (publicChunk === null) return;
1511
1267
  const waiter = waiters.shift();
1512
- if (waiter) waiter.resolve({ value: chunk, done: false });
1513
- else chunks.push(chunk);
1268
+ if (waiter) waiter.resolve({ value: publicChunk, done: false });
1269
+ else chunks.push(publicChunk);
1514
1270
  }
1515
1271
 
1516
1272
  function finish(error = null) {
@@ -1579,7 +1335,11 @@ function callbackStreamHandle({ runtime, request, signal, onSettled }) {
1579
1335
  return new Promise((resolve, reject) => waiters.push({ resolve, reject }));
1580
1336
  },
1581
1337
  async return(value) {
1582
- await this.cancel("The stream consumer stopped before completion.");
1338
+ Promise.resolve().then(() => this.cancel(
1339
+ "The stream consumer stopped before completion.",
1340
+ )).catch(function reportBrowserWasmStreamReturnCancellationFailure(error) {
1341
+ console.error("Arcane browser-WASM stream early-return cancellation failed.", error);
1342
+ });
1583
1343
  return { value, done: true };
1584
1344
  },
1585
1345
  async throw(error) {
@@ -1590,23 +1350,133 @@ function callbackStreamHandle({ runtime, request, signal, onSettled }) {
1590
1350
  return this;
1591
1351
  },
1592
1352
  };
1593
- return Object.freeze(handle);
1353
+ return completeValue(handle);
1594
1354
  }
1595
1355
 
1596
- function positiveLoadInteger(value, field, fallback, maximum) {
1356
+ function validatedV1StreamHandle(opened, request) {
1357
+ if (
1358
+ !opened
1359
+ || typeof opened !== "object"
1360
+ || typeof opened[Symbol.asyncIterator] !== "function"
1361
+ || typeof opened.cancel !== "function"
1362
+ || !opened.result
1363
+ || typeof opened.result.then !== "function"
1364
+ ) {
1365
+ if (typeof opened?.cancel === "function") {
1366
+ Promise.resolve().then(function cancelInvalidV1StreamHandle() {
1367
+ return opened.cancel("The v1 provider returned an invalid stream handle.");
1368
+ }).catch(function reportInvalidV1StreamCleanupFailure(error) {
1369
+ console.error("Arcane invalid v1 stream cleanup failed.", error);
1370
+ });
1371
+ }
1372
+ throw fail(
1373
+ "ARCANE_AI_INVALID_PROVIDER_RESULT",
1374
+ "The browser-WASM adapter stream must expose an async iterator, result, and cancel().",
1375
+ );
1376
+ }
1377
+ let iterator;
1378
+ try {
1379
+ iterator = opened[Symbol.asyncIterator]();
1380
+ } catch (error) {
1381
+ Promise.resolve().then(function cancelRejectedV1Iterator() {
1382
+ return opened.cancel(error);
1383
+ }).catch(function reportRejectedV1IteratorCleanupFailure(cleanupError) {
1384
+ console.error("Arcane rejected v1 stream iterator cleanup failed.", cleanupError);
1385
+ });
1386
+ throw error;
1387
+ }
1388
+ if (!iterator || typeof iterator.next !== "function") {
1389
+ Promise.resolve().then(function cancelInvalidV1Iterator() {
1390
+ return opened.cancel("The v1 provider returned an invalid stream iterator.");
1391
+ }).catch(function reportInvalidV1IteratorCleanupFailure(error) {
1392
+ console.error("Arcane invalid v1 stream iterator cleanup failed.", error);
1393
+ });
1394
+ throw fail(
1395
+ "ARCANE_AI_INVALID_PROVIDER_RESULT",
1396
+ "The browser-WASM adapter stream iterator has no next() method.",
1397
+ );
1398
+ }
1399
+ const result = Promise.resolve(opened.result).then(
1400
+ function validateV1StreamTerminal(value) {
1401
+ return validateCompletion(value, request.id);
1402
+ },
1403
+ );
1404
+ result.catch(function retainV1StreamTerminalRejection() {});
1405
+ const handle = {
1406
+ result,
1407
+ cancel: function cancelValidatedV1Stream(reason) {
1408
+ return opened.cancel(reason);
1409
+ },
1410
+ async next(value) {
1411
+ let nextValue = value;
1412
+ while (true) {
1413
+ const next = await iterator.next(nextValue);
1414
+ nextValue = undefined;
1415
+ if (next.done) return { value: undefined, done: true };
1416
+ const projected = projectPublicStreamChunk(next.value);
1417
+ if (projected !== null) return { value: projected, done: false };
1418
+ }
1419
+ },
1420
+ async return(value) {
1421
+ if (typeof iterator.return === "function") {
1422
+ Promise.resolve().then(function returnUnderlyingV1Stream() {
1423
+ return iterator.return(value);
1424
+ }).catch(function reportUnderlyingV1StreamReturnFailure(error) {
1425
+ console.error("Arcane v1 stream iterator return failed.", error);
1426
+ });
1427
+ }
1428
+ Promise.resolve().then(function cancelReturnedV1Stream() {
1429
+ return opened.cancel("The stream consumer stopped before completion.");
1430
+ }).catch(function reportReturnedV1StreamCancellationFailure(error) {
1431
+ console.error("Arcane v1 stream early-return cancellation failed.", error);
1432
+ });
1433
+ return { value, done: true };
1434
+ },
1435
+ async throw(error) {
1436
+ await opened.cancel(error);
1437
+ throw error;
1438
+ },
1439
+ [Symbol.asyncIterator]() {
1440
+ return this;
1441
+ },
1442
+ };
1443
+ return completeValue(handle);
1444
+ }
1445
+
1446
+ function positiveLoadInteger(value, field, fallback) {
1597
1447
  const resolved = value === undefined ? fallback : value;
1598
- if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > maximum) {
1599
- throw new RangeError(`${field} must be a positive safe integer no greater than ${maximum}.`);
1448
+ if (!Number.isSafeInteger(resolved) || resolved < 1) {
1449
+ throw new RangeError(`${field} must be a positive safe integer.`);
1600
1450
  }
1601
1451
  return resolved;
1602
1452
  }
1603
1453
 
1454
+ function optionalLoadBoolean(value, field) {
1455
+ if (value === undefined) return undefined;
1456
+ if (typeof value !== "boolean") throw new TypeError(`${field} must be a boolean when provided.`);
1457
+ return value;
1458
+ }
1459
+
1460
+ function optionalLoadText(value, field) {
1461
+ if (value === undefined) return undefined;
1462
+ if (typeof value !== "string") throw new TypeError(`${field} must be a string when provided.`);
1463
+ return value;
1464
+ }
1465
+
1466
+ function optionalTemplateDefaults(value) {
1467
+ if (value === undefined) return undefined;
1468
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1469
+ throw new TypeError("templateDefaults must be a plain object when provided.");
1470
+ }
1471
+ return { ...value };
1472
+ }
1473
+
1604
1474
  function measuredRuntimeCapabilities(runtimeCapabilities) {
1605
1475
  const measuredDeviceMemory = Number(globalThis.navigator?.deviceMemory);
1606
1476
  const deviceMemory = Number.isFinite(measuredDeviceMemory) && measuredDeviceMemory > 0
1607
1477
  ? measuredDeviceMemory
1608
1478
  : null;
1609
- return Object.freeze({ ...runtimeCapabilities, deviceMemory });
1479
+ return completeValue({ ...runtimeCapabilities, deviceMemory });
1610
1480
  }
1611
1481
 
1612
1482
  function capabilityLoadPlan(runtimeCapabilities, defaults, options = {}) {
@@ -1632,32 +1502,36 @@ function capabilityLoadPlan(runtimeCapabilities, defaults, options = {}) {
1632
1502
  configured.threads,
1633
1503
  "threads",
1634
1504
  Math.max(1, Math.min(4, hardwareConcurrency - 1 || 1)),
1635
- 64,
1636
1505
  );
1637
1506
  const contextTokens = positiveLoadInteger(
1638
1507
  configured.contextTokens,
1639
1508
  "contextTokens",
1640
1509
  defaultContext,
1641
- 1_048_576,
1642
1510
  );
1643
1511
  const batchTokens = positiveLoadInteger(
1644
1512
  configured.batchTokens,
1645
1513
  "batchTokens",
1646
1514
  Math.min(defaultBatch, contextTokens),
1647
- contextTokens,
1648
1515
  );
1649
1516
  const microBatchTokens = positiveLoadInteger(
1650
1517
  configured.microBatchTokens,
1651
1518
  "microBatchTokens",
1652
1519
  Math.min(defaultMicroBatch, batchTokens),
1653
- batchTokens,
1654
1520
  );
1655
- return Object.freeze({
1521
+ const reasoning = optionalLoadBoolean(configured.reasoning, "reasoning");
1522
+ const chatTemplate = optionalLoadText(configured.chatTemplate, "chatTemplate");
1523
+ const jinja = optionalLoadBoolean(configured.jinja, "jinja");
1524
+ const templateDefaults = optionalTemplateDefaults(configured.templateDefaults);
1525
+ return completeValue({
1656
1526
  threads,
1657
1527
  contextTokens,
1658
1528
  batchTokens,
1659
1529
  microBatchTokens,
1660
1530
  gpuLayers: 99_999,
1531
+ ...(reasoning!==undefined?{ reasoning }:{}),
1532
+ ...(chatTemplate!==undefined?{ chatTemplate }:{}),
1533
+ ...(jinja!==undefined?{ jinja }:{}),
1534
+ ...(templateDefaults!==undefined?{ templateDefaults }:{}),
1661
1535
  });
1662
1536
  }
1663
1537
 
@@ -1666,23 +1540,27 @@ function sameLoadPlan(left, right) {
1666
1540
  && left?.contextTokens === right?.contextTokens
1667
1541
  && left?.batchTokens === right?.batchTokens
1668
1542
  && left?.microBatchTokens === right?.microBatchTokens
1669
- && left?.gpuLayers === right?.gpuLayers;
1543
+ && left?.gpuLayers === right?.gpuLayers
1544
+ && left?.reasoning === right?.reasoning
1545
+ && left?.chatTemplate === right?.chatTemplate
1546
+ && left?.jinja === right?.jinja
1547
+ && JSON.stringify(left?.templateDefaults) === JSON.stringify(right?.templateDefaults);
1670
1548
  }
1671
1549
 
1672
1550
  function stableModelFailure(error) {
1673
1551
  const code = typeof error?.code === "string" ? error.code : "";
1674
1552
  const message = typeof error?.message === "string" ? error.message : "";
1675
1553
  if (code === "ARCANE_AI_MODEL_SHARD_TOO_LARGE") {
1676
- return Object.freeze({ code });
1554
+ return completeValue({ code });
1677
1555
  }
1678
1556
  if (/(?:out of memory|allocation failed|failed to allocate|memory exhausted)/iu.test(message)) {
1679
- return Object.freeze({ code: "ARCANE_AI_MODEL_GPU_MEMORY_INSUFFICIENT" });
1557
+ return completeValue({ code: "ARCANE_AI_MODEL_GPU_MEMORY_INSUFFICIENT" });
1680
1558
  }
1681
1559
  if (code === "ARCANE_AI_WEBGPU_EVIDENCE_INVALID") {
1682
- return Object.freeze({ code: "ARCANE_AI_MODEL_FULL_OFFLOAD_UNPROVEN" });
1560
+ return completeValue({ code: "ARCANE_AI_MODEL_FULL_OFFLOAD_UNPROVEN" });
1683
1561
  }
1684
1562
  if (code === "ARCANE_AI_WEBGPU_REQUIRED" && /(?:offload|GPU|WebGPU)/iu.test(message)) {
1685
- return Object.freeze({ code: "ARCANE_AI_MODEL_WEBGPU_REQUIREMENT_FAILED" });
1563
+ return completeValue({ code: "ARCANE_AI_MODEL_WEBGPU_REQUIREMENT_FAILED" });
1686
1564
  }
1687
1565
  return null;
1688
1566
  }
@@ -1697,10 +1575,10 @@ function capabilityPolicy(
1697
1575
  failure = null,
1698
1576
  ) {
1699
1577
  const reasons = [];
1700
- const add = (code, compatibility, details = {}) => reasons.push(Object.freeze({
1578
+ const add = (code, compatibility, details = {}) => reasons.push(completeValue({
1701
1579
  code,
1702
1580
  compatibility,
1703
- details: Object.freeze(details),
1581
+ details: completeValue(details),
1704
1582
  }));
1705
1583
  if (runtimeCapabilities.webAssembly !== true) {
1706
1584
  add("ARCANE_AI_WEBASSEMBLY_UNAVAILABLE", "incompatible");
@@ -1714,37 +1592,21 @@ function capabilityPolicy(
1714
1592
  if (runtimeCapabilities.webgpuApiPresent !== true) {
1715
1593
  add("ARCANE_AI_WEBGPU_API_UNAVAILABLE", "incompatible");
1716
1594
  }
1717
- const oversized = oversizedModelFile(source);
1718
- if (oversized) {
1719
- add("ARCANE_AI_MODEL_SHARD_TOO_LARGE", "incompatible", {
1720
- name: oversized.name,
1721
- bytes: oversized.bytes,
1722
- maximumBytes: WLLAMA_MAX_FILE_BYTES,
1723
- });
1724
- }
1725
- if (failure && failure.code !== "ARCANE_AI_MODEL_SHARD_TOO_LARGE") {
1595
+ if (failure) {
1726
1596
  add(failure.code, "incompatible");
1727
1597
  }
1728
1598
  if (storage?.compatibility === "incompatible") {
1729
- add(storage.code, "incompatible", {
1730
- requiredBytes: storage.requiredBytes,
1731
- availableBytes: storage.availableBytes,
1732
- });
1599
+ add(storage.code, "incompatible");
1733
1600
  } else if (!storage || storage.compatibility === "unknown") {
1734
- add(storage?.code ?? "ARCANE_AI_STORAGE_NOT_MEASURED", "unknown", {
1735
- requiredBytes: storage?.requiredBytes ?? knownModelBytes(source),
1736
- availableBytes: storage?.availableBytes ?? null,
1737
- });
1601
+ add(storage?.code ?? "ARCANE_AI_STORAGE_NOT_MEASURED", "unknown");
1738
1602
  }
1739
1603
  const webgpu = runtimeEvidence?.webgpu;
1740
1604
  if (state === "ready" && webgpu?.observed === true) {
1741
- add("ARCANE_AI_WEBGPU_EXECUTION_OBSERVED", "compatible", {
1742
- requestedGpuLayers: loadPlan.gpuLayers,
1743
- offloadedLayers: webgpu.offload?.layers ?? null,
1744
- totalLayers: webgpu.offload?.totalLayers ?? null,
1745
- queueSubmissions: webgpu.queue?.submissions ?? null,
1746
- logicalBufferDescriptorBytes: webgpu.buffers?.descriptorBytes ?? null,
1747
- });
1605
+ add(
1606
+ "ARCANE_AI_WEBGPU_EXECUTION_OBSERVED",
1607
+ "compatible",
1608
+ {},
1609
+ );
1748
1610
  } else if (runtimeCapabilities.webgpuApiPresent === true) {
1749
1611
  add("ARCANE_AI_WEBGPU_EXECUTION_UNOBSERVED", "unknown", {
1750
1612
  requestedGpuLayers: loadPlan.gpuLayers,
@@ -1755,18 +1617,14 @@ function capabilityPolicy(
1755
1617
  : reasons.some((reason) => reason.compatibility === "unknown")
1756
1618
  ? "unknown"
1757
1619
  : "compatible";
1758
- return Object.freeze({
1620
+ return completeValue({
1759
1621
  protocol: CAPABILITY_POLICY_PROTOCOL,
1760
1622
  compatibility,
1761
- reasons: Object.freeze(reasons),
1762
- model: Object.freeze({
1763
- id: source.id,
1764
- fileCount: sourceMetadata(source).files.length,
1765
- declaredBytes: knownModelBytes(source),
1766
- }),
1623
+ reasons: completeValue(reasons),
1624
+ model: completeValue({ id: source.id }),
1767
1625
  load: loadPlan,
1768
1626
  storage: storage ?? null,
1769
- inputs: Object.freeze({
1627
+ inputs: completeValue({
1770
1628
  hardwareConcurrency: runtimeCapabilities.hardwareConcurrency,
1771
1629
  deviceMemory: runtimeCapabilities.deviceMemory,
1772
1630
  deviceMemoryMeaning: "coarse-system-memory-gib",
@@ -1799,8 +1657,8 @@ function providerModelSources(source, sources) {
1799
1657
  if (source !== undefined && !list.includes(source)) {
1800
1658
  throw new TypeError("The legacy default source must be one member of sources.");
1801
1659
  }
1802
- return Object.freeze({
1803
- sources: Object.freeze(list.slice()),
1660
+ return completeValue({
1661
+ sources: completeValue(list.slice()),
1804
1662
  defaultSource: source ?? list[0],
1805
1663
  });
1806
1664
  }
@@ -1834,11 +1692,9 @@ export function createBrowserWasmLlmProvider({
1834
1692
  runtimeLoadDefaults,
1835
1693
  );
1836
1694
  let state = "unloaded";
1837
- let progressState = null;
1838
1695
  let errorState = null;
1839
1696
  let cacheState = "unknown";
1840
1697
  let activeSecurity = null;
1841
- let activeIntegrity = null;
1842
1698
  let queueDepth = 0;
1843
1699
  let disposed = false;
1844
1700
  let disposing = false;
@@ -1865,7 +1721,7 @@ export function createBrowserWasmLlmProvider({
1865
1721
 
1866
1722
  function capabilities() {
1867
1723
  const runtimeCapabilities = measuredRuntimeCapabilities(runtime.capabilities());
1868
- return Object.freeze({
1724
+ return completeValue({
1869
1725
  localOnly: true,
1870
1726
  toolCalls: "structural-only",
1871
1727
  webAssembly: runtimeCapabilities.webAssembly,
@@ -1899,7 +1755,7 @@ export function createBrowserWasmLlmProvider({
1899
1755
 
1900
1756
  function catalog() {
1901
1757
  const runtimeCapabilities = measuredRuntimeCapabilities(runtime.capabilities());
1902
- return Object.freeze(modelSources.map((candidate) => {
1758
+ return completeValue(modelSources.map((candidate) => {
1903
1759
  const selected = candidate === activeSource;
1904
1760
  const plan = selected
1905
1761
  ? activeLoadPlan
@@ -1913,7 +1769,7 @@ export function createBrowserWasmLlmProvider({
1913
1769
  selected ? state : "unloaded",
1914
1770
  modelFailures.get(candidate.id) ?? null,
1915
1771
  );
1916
- return Object.freeze({
1772
+ return completeValue({
1917
1773
  ...publicDescriptor(candidate),
1918
1774
  compatibility: policy.compatibility,
1919
1775
  compatibilityDetails: policy,
@@ -1926,8 +1782,8 @@ export function createBrowserWasmLlmProvider({
1926
1782
  app: context?.security,
1927
1783
  binding: bindingSecurity,
1928
1784
  });
1929
- const integrity = activeIntegrity ?? integritySnapshot(effectiveSecurity, activeSource);
1930
- return Object.freeze({
1785
+ const publicSecurity = securitySnapshot(effectiveSecurity);
1786
+ return completeValue({
1931
1787
  protocol: ARCANE_AI_ADAPTER_PROTOCOL,
1932
1788
  provider: "arcane-browser-wasm-wllama",
1933
1789
  state,
@@ -1935,10 +1791,8 @@ export function createBrowserWasmLlmProvider({
1935
1791
  busy: activeCount > 0,
1936
1792
  queued: Math.max(0, queueDepth - activeCount),
1937
1793
  model: publicDescriptor(activeSource),
1938
- cache: Object.freeze({ state: cacheState, schema: MODEL_MANIFEST_SCHEMA }),
1939
- security: securitySnapshot(effectiveSecurity),
1940
- integrity,
1941
- progress: progressState,
1794
+ cache: completeValue({ state: cacheState }),
1795
+ ...(publicSecurity ? { security: publicSecurity } : {}),
1942
1796
  error: errorState,
1943
1797
  runtime: runtime.authority,
1944
1798
  runtimeEvidence: runtime.evidence(),
@@ -1955,9 +1809,8 @@ export function createBrowserWasmLlmProvider({
1955
1809
  || error?.code === "ARCANE_AI_WORKER_TERMINATION_UNCONFIRMED"
1956
1810
  ? "error"
1957
1811
  : "unloaded";
1958
- progressState = null;
1959
1812
  errorState = state === "error"
1960
- ? Object.freeze({
1813
+ ? completeValue({
1961
1814
  code: typeof error?.code === "string" ? error.code : "ARCANE_AI_RUNTIME_FAILED",
1962
1815
  message: typeof error?.message === "string"
1963
1816
  ? error.message
@@ -1966,12 +1819,6 @@ export function createBrowserWasmLlmProvider({
1966
1819
  : null;
1967
1820
  }
1968
1821
 
1969
- function report(value, options, context) {
1970
- progressState = value;
1971
- options?.onProgress?.(value);
1972
- context?.reportProgress?.(value);
1973
- }
1974
-
1975
1822
  async function load(options = {}, context = {}) {
1976
1823
  if (disposed || disposing) {
1977
1824
  throw fail("ARCANE_AI_DISPOSED", "The browser-WASM provider is disposed or disposing.");
@@ -1988,22 +1835,11 @@ export function createBrowserWasmLlmProvider({
1988
1835
  binding: bindingSecurity,
1989
1836
  load: options.security,
1990
1837
  });
1991
- assertDescriptorChecks(requestedSource, effectiveSecurity);
1992
1838
  const requestedLoadPlan = capabilityLoadPlan(
1993
1839
  measuredRuntimeCapabilities(runtime.capabilities()),
1994
1840
  runtimeLoadDefaults,
1995
1841
  options,
1996
1842
  );
1997
- const oversized = oversizedModelFile(requestedSource);
1998
- if (oversized) {
1999
- modelFailures.set(requestedSource.id, Object.freeze({
2000
- code: "ARCANE_AI_MODEL_SHARD_TOO_LARGE",
2001
- }));
2002
- throw fail(
2003
- "ARCANE_AI_MODEL_SHARD_TOO_LARGE",
2004
- `Model file ${oversized.name} exceeds Wllama's ${WLLAMA_MAX_FILE_BYTES}-byte boundary.`,
2005
- );
2006
- }
2007
1843
  if (state === "ready") {
2008
1844
  if (activeSource !== requestedSource) {
2009
1845
  throw fail(
@@ -2017,14 +1853,8 @@ export function createBrowserWasmLlmProvider({
2017
1853
  "Unload the browser-WASM model before changing its context or batch load plan.",
2018
1854
  );
2019
1855
  }
2020
- if (sameModelSecurity(activeSecurity, effectiveSecurity)) {
2021
- activeSecurity = effectiveSecurity;
2022
- return Object.freeze({ model: publicDescriptor(activeSource), status: status() });
2023
- }
2024
- throw fail(
2025
- "ARCANE_AI_SECURITY_RELOAD_REQUIRED",
2026
- "Unload the browser-WASM model before changing its effective security checks.",
2027
- );
1856
+ activeSecurity = effectiveSecurity;
1857
+ return completeValue({ model: publicDescriptor(activeSource), status: status() });
2028
1858
  }
2029
1859
  if (loadPromise) {
2030
1860
  if (activeSource !== requestedSource) {
@@ -2039,14 +1869,8 @@ export function createBrowserWasmLlmProvider({
2039
1869
  "The in-flight browser-WASM load uses a different context or batch plan.",
2040
1870
  );
2041
1871
  }
2042
- if (sameModelSecurity(activeSecurity, effectiveSecurity)) {
2043
- activeSecurity = effectiveSecurity;
2044
- return loadPromise;
2045
- }
2046
- throw fail(
2047
- "ARCANE_AI_SECURITY_RELOAD_REQUIRED",
2048
- "The in-flight browser-WASM load uses different effective security checks.",
2049
- );
1872
+ activeSecurity = effectiveSecurity;
1873
+ return loadPromise;
2050
1874
  }
2051
1875
  const externalSignal = options.signal ?? context.signal ?? null;
2052
1876
  const linked = linkAbortSignal(externalSignal);
@@ -2056,9 +1880,7 @@ export function createBrowserWasmLlmProvider({
2056
1880
  activeSource = requestedSource;
2057
1881
  activeSecurity = effectiveSecurity;
2058
1882
  activeLoadPlan = requestedLoadPlan;
2059
- activeIntegrity = integritySnapshot(effectiveSecurity, activeSource);
2060
1883
  state = "loading";
2061
- progressState = null;
2062
1884
  errorState = null;
2063
1885
  loadPromise = (async () => {
2064
1886
  try {
@@ -2066,21 +1888,13 @@ export function createBrowserWasmLlmProvider({
2066
1888
  const admitted = await store.ensure(activeSource, {
2067
1889
  signal,
2068
1890
  offline: options.offline === true,
2069
- security: effectiveSecurity,
2070
- onProgress: (value) => report(value, options, context),
2071
1891
  onCapabilityPolicy: (value) => { storagePolicies.set(activeSource.id, value); },
2072
1892
  });
2073
1893
  cacheState = admitted.cache;
2074
- activeIntegrity = admitted.integrity;
2075
1894
  throwIfAborted(signal, "load");
2076
1895
  if (generation !== lifecycleGeneration || state !== "loading") {
2077
1896
  throw fail("ARCANE_AI_OPERATION_SUPERSEDED", "The model load was superseded by unload.");
2078
1897
  }
2079
- report(
2080
- progress(activeSource, "initialize", admitted.observedBytes, admitted.observedBytes),
2081
- options,
2082
- context,
2083
- );
2084
1898
  throwIfAborted(signal, "load");
2085
1899
  if (generation !== lifecycleGeneration || state !== "loading") {
2086
1900
  throw fail("ARCANE_AI_OPERATION_SUPERSEDED", "The model load was superseded by unload.");
@@ -2115,9 +1929,8 @@ export function createBrowserWasmLlmProvider({
2115
1929
  throw fail("ARCANE_AI_OPERATION_SUPERSEDED", "The model load was superseded by unload.");
2116
1930
  }
2117
1931
  state = "ready";
2118
- progressState = null;
2119
1932
  modelFailures.delete(activeSource.id);
2120
- return Object.freeze({ model: publicDescriptor(activeSource), status: status() });
1933
+ return completeValue({ model: publicDescriptor(activeSource), status: status() });
2121
1934
  } catch (error) {
2122
1935
  let cleanupFailure = null;
2123
1936
  try {
@@ -2135,7 +1948,7 @@ export function createBrowserWasmLlmProvider({
2135
1948
  if (modelFailure) modelFailures.set(activeSource.id, modelFailure);
2136
1949
  if (generation === lifecycleGeneration && state === "loading") {
2137
1950
  state = "error";
2138
- errorState = Object.freeze({ code: normalized.code, message: normalized.message });
1951
+ errorState = completeValue({ code: normalized.code, message: normalized.message });
2139
1952
  }
2140
1953
  throw normalized;
2141
1954
  } finally {
@@ -2205,7 +2018,7 @@ export function createBrowserWasmLlmProvider({
2205
2018
  if (error) reconcileRuntimeAfterRequestError(error);
2206
2019
  },
2207
2020
  });
2208
- activeAbort = Object.freeze({
2021
+ activeAbort = completeValue({
2209
2022
  abort: (reason) => handle.cancel(reason),
2210
2023
  });
2211
2024
  return handle;
@@ -2239,10 +2052,8 @@ export function createBrowserWasmLlmProvider({
2239
2052
  throwIfAborted(signal, "unload");
2240
2053
  await runtime.exit();
2241
2054
  state = "unloaded";
2242
- progressState = null;
2243
2055
  errorState = null;
2244
2056
  activeSecurity = null;
2245
- activeIntegrity = null;
2246
2057
  return status();
2247
2058
  } catch (error) {
2248
2059
  const normalized = normalizeArcaneAIError(error, {
@@ -2251,7 +2062,7 @@ export function createBrowserWasmLlmProvider({
2251
2062
  signal: normalizationSignal(error, signal),
2252
2063
  });
2253
2064
  state = "error";
2254
- errorState = Object.freeze({ code: normalized.code, message: normalized.message });
2065
+ errorState = completeValue({ code: normalized.code, message: normalized.message });
2255
2066
  throw normalized;
2256
2067
  } finally {
2257
2068
  unloadPromise = null;
@@ -2287,7 +2098,7 @@ export function createBrowserWasmLlmProvider({
2287
2098
  return runtime.probe(options);
2288
2099
  }
2289
2100
 
2290
- return Object.freeze({
2101
+ return completeValue({
2291
2102
  protocol: ARCANE_AI_ADAPTER_PROTOCOL,
2292
2103
  id: "arcane-browser-wasm-wllama",
2293
2104
  model: publicDescriptor(defaultSource),
@@ -2324,22 +2135,6 @@ function assertV1LlmAdapterSelection(selection, providerId, modelIds, role) {
2324
2135
  }
2325
2136
  }
2326
2137
 
2327
- function provider2ByteProgress(value) {
2328
- const phase = typeof value?.phase === "string" ? value.phase.trim() : "";
2329
- const completed = Number(value?.loaded);
2330
- const total = value?.total === null ? null : Number(value?.total);
2331
- if (
2332
- !phase
2333
- || !Number.isSafeInteger(completed)
2334
- || completed < 0
2335
- || (total !== null && (!Number.isSafeInteger(total) || total < 0))
2336
- || (total !== null && completed > total)
2337
- ) {
2338
- throw fail("ARCANE_AI_PROVIDER_PROGRESS_INVALID", "The browser-WASM provider returned invalid byte progress.");
2339
- }
2340
- return Object.freeze({ phase, completed, total, unit: "bytes", heartbeat: false });
2341
- }
2342
-
2343
2138
  /**
2344
2139
  * Projects the existing browser-WASM LLM provider into the provider-neutral
2345
2140
  * Arcane AI /2 lifecycle without changing the provider's public v1 contract.
@@ -2369,7 +2164,7 @@ export function adaptV1LlmProvider(provider) {
2369
2164
  throw new TypeError("The browser-WASM LLM provider must be explicitly local-only.");
2370
2165
  }
2371
2166
 
2372
- const fallbackCatalog = Object.freeze([model]);
2167
+ const fallbackCatalog = completeValue([model]);
2373
2168
  const initialCatalog = typeof provider.catalog === "function"
2374
2169
  ? provider.catalog()
2375
2170
  : fallbackCatalog;
@@ -2393,11 +2188,10 @@ export function adaptV1LlmProvider(provider) {
2393
2188
 
2394
2189
  function authorityFor(selection) {
2395
2190
  const selectedModel = catalogModels.get(selection.modelId);
2396
- return Object.freeze({
2191
+ return completeValue({
2397
2192
  protocol: AI_MODEL_AUTHORITY_PROTOCOL,
2398
2193
  providerId,
2399
2194
  modelId: selectedModel.id,
2400
- admitted: true,
2401
2195
  localOnly: true,
2402
2196
  model: selectedModel,
2403
2197
  });
@@ -2424,19 +2218,18 @@ export function adaptV1LlmProvider(provider) {
2424
2218
  ) {
2425
2219
  throw fail("ARCANE_AI_PROVIDER_STATUS_INVALID", "The browser-WASM provider returned an invalid status.");
2426
2220
  }
2427
- return Object.freeze({
2221
+ return completeValue({
2428
2222
  state: disposed ? "disposed" : value.state,
2429
2223
  loaded: disposed ? false : value.loaded,
2430
2224
  busy: disposed ? false : value.busy,
2431
2225
  cache: value.cache,
2432
- security: value.security,
2433
- integrity: value.integrity,
2226
+ ...(value.security?.secure===true?{security:{secure:true}}:{}),
2434
2227
  capabilityPolicy: value.capabilityPolicy,
2435
2228
  compatibility: value.capabilityPolicy?.compatibility ?? "unknown",
2436
2229
  });
2437
2230
  }
2438
2231
 
2439
- const adapted = Object.freeze({
2232
+ const adapted = completeValue({
2440
2233
  protocol: AI_PROVIDER_PROTOCOL,
2441
2234
  role: "llm",
2442
2235
  id: providerId,
@@ -2448,7 +2241,7 @@ export function adaptV1LlmProvider(provider) {
2448
2241
  assertSelection(selection, role);
2449
2242
  throwIfAborted(signal, "inspect");
2450
2243
  if (disposed) {
2451
- return Object.freeze({
2244
+ return completeValue({
2452
2245
  available: false,
2453
2246
  code: "ARCANE_AI_DISPOSED",
2454
2247
  message: "The browser-WASM provider is disposed.",
@@ -2463,13 +2256,13 @@ export function adaptV1LlmProvider(provider) {
2463
2256
  ];
2464
2257
  const missing = requirements.find(([available]) => !available)?.[1] ?? null;
2465
2258
  if (missing) {
2466
- return Object.freeze({
2259
+ return completeValue({
2467
2260
  available: false,
2468
2261
  code: "ARCANE_AI_PROVIDER_UNAVAILABLE",
2469
2262
  message: `The browser-WASM provider requires ${missing}.`,
2470
2263
  });
2471
2264
  }
2472
- return Object.freeze({ available: true, authority: authorityFor(selection) });
2265
+ return completeValue({ available: true, authority: authorityFor(selection) });
2473
2266
  },
2474
2267
  status,
2475
2268
  async load({
@@ -2478,6 +2271,7 @@ export function adaptV1LlmProvider(provider) {
2478
2271
  signal = null,
2479
2272
  progress = null,
2480
2273
  security,
2274
+ ...loadOptions
2481
2275
  } = {}) {
2482
2276
  assertSelection(selection, role);
2483
2277
  throwIfAborted(signal, "load");
@@ -2485,10 +2279,10 @@ export function adaptV1LlmProvider(provider) {
2485
2279
  throw new TypeError("The provider/2 progress sink must be a function or null.");
2486
2280
  }
2487
2281
  await methods.load({
2282
+ ...loadOptions,
2488
2283
  modelId: selection.modelId,
2489
2284
  signal,
2490
- security,
2491
- ...(progress ? { onProgress: (value) => progress(provider2ByteProgress(value)) } : {}),
2285
+ ...(security?.secure===true?{security:{secure:true}}:{}),
2492
2286
  });
2493
2287
  return status();
2494
2288
  },
@@ -2496,8 +2290,22 @@ export function adaptV1LlmProvider(provider) {
2496
2290
  assertSelection(selection, role);
2497
2291
  assertActiveSelection(selection);
2498
2292
  throwIfAborted(signal);
2499
- if (operation === "chat") return methods.chat(payload, { signal });
2500
- if (operation === "stream") return methods.stream(payload, { signal });
2293
+ if (operation === "chat") {
2294
+ validateStructuralRequest(payload);
2295
+ return Promise.resolve(methods.chat(payload, { signal })).then(
2296
+ function validateV1ChatTerminal(value) {
2297
+ return validateCompletion(value, payload.id);
2298
+ },
2299
+ );
2300
+ }
2301
+ if (operation === "stream") {
2302
+ validateStructuralRequest(payload);
2303
+ return Promise.resolve(methods.stream(payload, { signal })).then(
2304
+ function wrapV1StreamResult(opened) {
2305
+ return validatedV1StreamHandle(opened, payload);
2306
+ },
2307
+ );
2308
+ }
2501
2309
  throw fail("ARCANE_AI_PROVIDER_OPERATION_UNAVAILABLE", "The browser-WASM adapter supports only chat and stream.");
2502
2310
  },
2503
2311
  async unload({ role = "llm", selection, signal = null } = {}) {