arcane-os 0.5.16 → 0.5.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/README.md +42 -19
- package/bin/arcane-test.mjs +16 -13
- package/browser-runtime/ai/browser-speech-artifacts.mjs +41 -38
- package/browser-runtime/ai/browser-speech-providers.mjs +53 -46
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +127 -124
- package/browser-runtime/ai/browser-wasm.mjs +6 -3
- package/browser-runtime/ai/browser-wllama-runtime.mjs +26 -23
- package/browser-runtime/ai/model-controller.mjs +94 -91
- package/browser-runtime/ai/speech-worker-client.mjs +12 -9
- package/browser-runtime/ai/speech-worker-runtime.mjs +56 -53
- package/browser-runtime/dependencies/strong-type/index.js +207 -82
- package/browser-runtime/dependencies/strong-type/package.json +15 -6
- package/browser-runtime/dom-event-instrumentation.mjs +24 -20
- package/browser-runtime/event-manager.mjs +111 -108
- package/browser-runtime/speech-text.mjs +35 -0
- package/docs/architecture.md +1 -1
- package/docs/reference/README.md +8 -8
- package/docs/reference/ai/browser-speech.md +230 -16
- package/docs/reference/ai/twin-cloud.md +1 -1
- package/docs/reference/availability-and-normalization.md +42 -1
- package/docs/reference/cli.md +1 -1
- package/docs/reference/core/arcane-ai-contracts.md +1 -1
- package/docs/reference/inventory/package-api.json +60 -4
- package/docs/reference/inventory/runtime-components.json +2 -2
- package/docs/reference/inventory/runtime-modules.json +29 -31
- package/docs/reference/protocols.md +17 -13
- package/docs/reference/runtime-components.md +6 -5
- package/docs/reference/runtime-modules.md +212 -70
- package/docs/reference/sdk-api.md +197 -11
- package/examples/wasm-ai-demo/index.html +1 -0
- package/node_modules/event-pubsub/node_modules/strong-type/README.md +408 -0
- package/node_modules/event-pubsub/node_modules/strong-type/assets/strong-type-header.png +0 -0
- package/node_modules/event-pubsub/node_modules/strong-type/index.js +1151 -0
- package/node_modules/event-pubsub/node_modules/strong-type/licence +21 -0
- package/node_modules/event-pubsub/node_modules/strong-type/node.js +125 -0
- package/node_modules/event-pubsub/node_modules/strong-type/package.json +61 -0
- package/node_modules/strong-type/README.md +42 -24
- package/node_modules/strong-type/benchmark/core.js +324 -0
- package/node_modules/strong-type/index.js +207 -82
- package/node_modules/strong-type/package.json +14 -6
- package/package.json +4 -3
- package/runtime/arcane/components/app-bar.html +5 -2
- package/runtime/arcane/components/assistant-panel.html +5 -2
- package/runtime/arcane/components/chart.html +15 -12
- package/runtime/arcane/components/chat.html +118 -141
- package/runtime/arcane/components/dashboard-config.html +6 -3
- package/runtime/arcane/components/data-view.html +4 -1
- package/runtime/arcane/components/directory-picker.html +5 -2
- package/runtime/arcane/components/document-inspector.html +6 -3
- package/runtime/arcane/components/file-drop.html +5 -2
- package/runtime/arcane/components/file-inspector.html +9 -6
- package/runtime/arcane/components/file-manager.html +16 -13
- package/runtime/arcane/components/local-ai-status.html +5 -2
- package/runtime/arcane/components/markdown-document.html +14 -11
- package/runtime/arcane/components/markdown-editor.html +4 -1
- package/runtime/arcane/components/modal.html +4 -1
- package/runtime/arcane/components/output-panel.html +6 -3
- package/runtime/arcane/components/preferences-form.html +4 -1
- package/runtime/arcane/components/record-timeline.html +8 -5
- package/runtime/arcane/components/relationship-board.html +9 -6
- package/runtime/arcane/components/source-code-viewer.html +12 -9
- package/runtime/arcane/components/source-explanation.html +8 -5
- package/runtime/arcane/components/speech.html +24 -21
- package/runtime/arcane/components/summary-strip.html +4 -1
- package/runtime/arcane/components/task-progress.html +6 -3
- package/runtime/arcane/components/terminal-workspace.html +4 -1
- package/runtime/arcane/components/voice-transcription.html +11 -8
- package/runtime/arcane/components/web-navigator.html +5 -2
- package/runtime/arcane/entities/ApiModelRecord.js +5 -2
- package/runtime/arcane/entities/Calculation.js +5 -2
- package/runtime/arcane/entities/Chat.js +33 -33
- package/runtime/arcane/entities/CommunicationMessage.js +4 -1
- package/runtime/arcane/entities/CommunicationThread.js +4 -1
- package/runtime/arcane/entities/File.js +1 -1
- package/runtime/arcane/entities/Image.js +8 -5
- package/runtime/arcane/entities/IntentEnvelope.js +20 -17
- package/runtime/arcane/entities/Preference.js +9 -6
- package/runtime/arcane/entities/Theme.js +5 -2
- package/runtime/arcane/entities/User.js +11 -11
- package/runtime/arcane/entities/Weather.js +5 -2
- package/runtime/arcane/modules/AI.js +503 -187
- package/runtime/arcane/modules/AIPreferenceRuntime.js +5 -2
- package/runtime/arcane/modules/AIPreferenceTuple.js +9 -6
- package/runtime/arcane/modules/AIProviderRuntime.js +117 -92
- package/runtime/arcane/modules/AIResponseURLPolicy.js +148 -62
- package/runtime/arcane/modules/AIRuntimeState.js +29 -26
- package/runtime/arcane/modules/AnsiText.js +4 -1
- package/runtime/arcane/modules/ApiModelDatabase.js +22 -19
- package/runtime/arcane/modules/AppDataScope.js +8 -5
- package/runtime/arcane/modules/ArcaneCommunicationBridge.js +4 -1
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +8 -5
- package/runtime/arcane/modules/ArcaneNetworkPolicy.js +18 -15
- package/runtime/arcane/modules/AsyncBoundary.js +13 -10
- package/runtime/arcane/modules/BrowserTestSuite.js +19 -16
- package/runtime/arcane/modules/CalculatorEngine.js +5 -2
- package/runtime/arcane/modules/ChatRecords.js +18 -15
- package/runtime/arcane/modules/CommunicationAppController.js +9 -6
- package/runtime/arcane/modules/CommunicationHub.js +9 -6
- package/runtime/arcane/modules/CommunicationPreferences.js +4 -1
- package/runtime/arcane/modules/CommunicationProviderRegistry.js +4 -1
- package/runtime/arcane/modules/ComponentContracts.js +56 -53
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +30 -27
- package/runtime/arcane/modules/ConversationActionItems.js +15 -12
- package/runtime/arcane/modules/ConversationClosingReport.js +11 -8
- package/runtime/arcane/modules/ConversationTimebox.js +28 -25
- package/runtime/arcane/modules/CoreLocalModelCatalog.js +17 -14
- package/runtime/arcane/modules/DBLS.js +7 -4
- package/runtime/arcane/modules/DBOPFS.js +7 -7
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +34 -31
- package/runtime/arcane/modules/DevelopmentWorkspace.js +4 -1
- package/runtime/arcane/modules/DirectoryPicker.js +8 -5
- package/runtime/arcane/modules/DocumentLexicalSearch.js +11 -8
- package/runtime/arcane/modules/DocumentNavigation.js +7 -4
- package/runtime/arcane/modules/Errors.js +28 -25
- package/runtime/arcane/modules/HTMLImport.js +7 -4
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +15 -12
- package/runtime/arcane/modules/LocalAIReadiness.js +21 -18
- package/runtime/arcane/modules/LocalAIReadinessController.js +6 -3
- package/runtime/arcane/modules/MD.js +1 -1
- package/runtime/arcane/modules/Mail.js +46 -43
- package/runtime/arcane/modules/MailOutbox.mjs +48 -45
- package/runtime/arcane/modules/MailTransport.mjs +29 -26
- package/runtime/arcane/modules/MarkdownSpeech.js +1 -50
- package/runtime/arcane/modules/MemoryRecords.js +7 -4
- package/runtime/arcane/modules/MessageAdvisory.js +6 -3
- package/runtime/arcane/modules/ModelDefinition.js +7 -4
- package/runtime/arcane/modules/Ollama.js +6 -3
- package/runtime/arcane/modules/OllamaModelIdentifier.js +4 -1
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +18 -15
- package/runtime/arcane/modules/PersistentAIChatSession.js +33 -30
- package/runtime/arcane/modules/PreferenceStore.js +20 -17
- package/runtime/arcane/modules/PreparedSpeech.js +485 -0
- package/runtime/arcane/modules/Questionnaire.js +6 -3
- package/runtime/arcane/modules/RecordLinkIndex.js +4 -1
- package/runtime/arcane/modules/RecordPassageIndex.js +9 -6
- package/runtime/arcane/modules/RecordReviewStore.js +12 -9
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +4 -1
- package/runtime/arcane/modules/ScopedOPFSCache.js +6 -3
- package/runtime/arcane/modules/ScreenCapture.js +20 -17
- package/runtime/arcane/modules/SpeechPlayback.js +61 -28
- package/runtime/arcane/modules/StaticDocumentCatalog.js +33 -30
- package/runtime/arcane/modules/SystemAppearance.js +5 -2
- package/runtime/arcane/modules/SystemToolRegistry.js +6 -3
- package/runtime/arcane/modules/TerminalClient.js +14 -11
- package/runtime/arcane/modules/TerminalCommandRegistry.js +4 -1
- package/runtime/arcane/modules/ThemeBootstrap.js +9 -6
- package/runtime/arcane/modules/ThemeManager.js +5 -2
- package/runtime/arcane/modules/TimeGuard.js +1 -1
- package/runtime/arcane/modules/ToolCallRouter.js +11 -8
- package/runtime/arcane/modules/WaitForComponent.js +19 -16
- package/runtime/arcane/modules/YouTubeMedia.js +4 -1
- package/runtime/strong-type/index.js +1276 -352
- package/runtime/strong-type/package.json +69 -45
- package/src/app-descriptor.mjs +17 -14
- package/src/application-tests.mjs +4 -1
- package/src/cli/main.mjs +8 -5
- package/src/constants.mjs +4 -1
- package/src/dev-server.mjs +8 -5
- package/src/doctor.mjs +5 -2
- package/src/dom-event-instrumentation.mjs +24 -20
- package/src/errors.mjs +7 -3
- package/src/event-manager.mjs +111 -108
- package/src/event-queue.mjs +8 -5
- package/src/events.mjs +12 -9
- package/src/import-map.mjs +36 -27
- package/src/installed-sdk-runtime.mjs +4 -1
- package/src/integrated-provider-loader.mjs +9 -6
- package/src/mail-credentials.mjs +16 -13
- package/src/mail-server.mjs +53 -50
- package/src/mail.mjs +18 -15
- package/src/native-plan.mjs +12 -9
- package/src/native-provider-loader.mjs +7 -4
- package/src/packager/core.mjs +18 -15
- package/src/process.mjs +6 -3
- package/src/release-bundle.mjs +14 -11
- package/src/runtime.mjs +4 -1
- package/src/scaffold.mjs +9 -6
- package/src/sdk-browser-runtime.mjs +4 -1
- package/src/source-server.mjs +17 -14
- package/src/targets/index.mjs +5 -2
- package/src/templates/workspace-template.mjs +13 -6
- package/src/testing-loader.mjs +7 -4
- package/src/testing.mjs +10 -7
- package/src/toolchain.mjs +13 -10
- package/src/update-check.mjs +9 -6
- package/src/workspace-operation-lock.mjs +14 -11
- package/src/workspace-runtime.mjs +4 -1
- package/src/workspace.mjs +17 -14
- package/runtime/arcane/modules/AIResponseLength.js +0 -32
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Is from "../dependencies/strong-type/index.js";
|
|
1
2
|
import { arcaneLogging } from '../logging.mjs';
|
|
2
3
|
import {
|
|
3
4
|
ARCANE_AI_ADAPTER_PROTOCOL,
|
|
@@ -9,6 +10,8 @@ import {
|
|
|
9
10
|
import { createPackagedWllamaRuntime } from "./browser-wllama-runtime.mjs";
|
|
10
11
|
import { arcaneEvents } from "../event-manager.mjs";
|
|
11
12
|
|
|
13
|
+
const is = new Is(false);
|
|
14
|
+
|
|
12
15
|
const completeValue = (value) => value;
|
|
13
16
|
|
|
14
17
|
const BROWSER_MODEL_SOURCES = new WeakSet();
|
|
@@ -76,9 +79,9 @@ function httpContentRange(value) {
|
|
|
76
79
|
const end = Number(match[2]);
|
|
77
80
|
const total = Number(match[3]);
|
|
78
81
|
if (
|
|
79
|
-
!
|
|
80
|
-
|| !
|
|
81
|
-
|| !
|
|
82
|
+
!is.safeInteger(start)
|
|
83
|
+
|| !is.safeInteger(end)
|
|
84
|
+
|| !is.safeInteger(total)
|
|
82
85
|
|| start < 0
|
|
83
86
|
|| end < start
|
|
84
87
|
|| total <= end
|
|
@@ -88,11 +91,11 @@ function httpContentRange(value) {
|
|
|
88
91
|
|
|
89
92
|
function httpContentLength(value) {
|
|
90
93
|
const length = Number(String(value ?? "").trim());
|
|
91
|
-
return
|
|
94
|
+
return is.safeInteger(length) && length > 0 ? length : null;
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
function downloadConcurrencyValue(value) {
|
|
95
|
-
if (!
|
|
98
|
+
if (!is.safeInteger(value) || value < 1) {
|
|
96
99
|
throw new RangeError("Model downloadConcurrency must be a positive safe integer.");
|
|
97
100
|
}
|
|
98
101
|
return value;
|
|
@@ -126,7 +129,7 @@ function modelHttpRanges(total) {
|
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
function progressClock() {
|
|
129
|
-
return
|
|
132
|
+
return is.function(globalThis.performance?.now)
|
|
130
133
|
? globalThis.performance.now()
|
|
131
134
|
: Date.now();
|
|
132
135
|
}
|
|
@@ -142,7 +145,7 @@ function modelSourceUrl(value) {
|
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
function requiredText(value, field) {
|
|
145
|
-
if (
|
|
148
|
+
if (!is.string(value) || !value.trim()) {
|
|
146
149
|
throw new TypeError(`Browser model ${field} must be a nonempty string.`);
|
|
147
150
|
}
|
|
148
151
|
return value.trim();
|
|
@@ -196,7 +199,7 @@ function descriptorFileName(value, url) {
|
|
|
196
199
|
}
|
|
197
200
|
|
|
198
201
|
function descriptorFile(value) {
|
|
199
|
-
if (!value ||
|
|
202
|
+
if (!value || !is.object(value) || is.array(value)) {
|
|
200
203
|
throw new TypeError("Each browser model file descriptor must be an object.");
|
|
201
204
|
}
|
|
202
205
|
if (Object.hasOwn(value, "immutableUrl")) {
|
|
@@ -210,14 +213,14 @@ function descriptorFile(value) {
|
|
|
210
213
|
name: descriptorFileName(value, url),
|
|
211
214
|
url: url.href,
|
|
212
215
|
};
|
|
213
|
-
if (
|
|
216
|
+
if (is.safeInteger(value.bytes) && value.bytes > 0) {
|
|
214
217
|
file.bytes = value.bytes;
|
|
215
218
|
}
|
|
216
219
|
return completeValue(file);
|
|
217
220
|
}
|
|
218
221
|
|
|
219
222
|
function modelDescriptor(value) {
|
|
220
|
-
if (!value ||
|
|
223
|
+
if (!value || !is.object(value) || is.array(value)) {
|
|
221
224
|
throw new TypeError("A browser model descriptor is required.");
|
|
222
225
|
}
|
|
223
226
|
const id = modelIdText(value.id);
|
|
@@ -225,7 +228,7 @@ function modelDescriptor(value) {
|
|
|
225
228
|
.some((field) => Object.hasOwn(value, field))) {
|
|
226
229
|
throw new TypeError("Browser model descriptor files must be declared in files[].");
|
|
227
230
|
}
|
|
228
|
-
if (!
|
|
231
|
+
if (!is.array(value.files) || value.files.length === 0) {
|
|
229
232
|
throw new TypeError("Browser model files must be a nonempty ordered array.");
|
|
230
233
|
}
|
|
231
234
|
let files = value.files.map((file) => descriptorFile(file));
|
|
@@ -282,7 +285,7 @@ function highPerformanceGpuBrowser() {
|
|
|
282
285
|
if (brands.has("Vivaldi") || /\bVivaldi\//u.test(userAgent)) {
|
|
283
286
|
return { name: "Vivaldi", url: "vivaldi://flags/#force-high-performance-gpu" };
|
|
284
287
|
}
|
|
285
|
-
if (brands.has("Brave") ||
|
|
288
|
+
if (brands.has("Brave") || is.function(navigatorObject?.brave?.isBrave)) {
|
|
286
289
|
return { name: "Brave", url: "brave://flags/#force-high-performance-gpu" };
|
|
287
290
|
}
|
|
288
291
|
if (brands.has("Opera") || /\bOPR\//u.test(userAgent)) {
|
|
@@ -368,7 +371,7 @@ export function createBrowserModelSource(descriptor, {
|
|
|
368
371
|
const rangeRequestUrls = new Array(metadata.files.length).fill(null);
|
|
369
372
|
|
|
370
373
|
function selectedMember(memberIndex) {
|
|
371
|
-
if (!
|
|
374
|
+
if (!is.safeInteger(memberIndex)) {
|
|
372
375
|
throw new TypeError("A browser model file index must be a safe integer.");
|
|
373
376
|
}
|
|
374
377
|
if (memberIndex < 0 || memberIndex >= metadata.files.length) {
|
|
@@ -381,7 +384,7 @@ export function createBrowserModelSource(descriptor, {
|
|
|
381
384
|
const member = selectedMember(memberIndex);
|
|
382
385
|
throwIfAborted(signal, "install");
|
|
383
386
|
const fetchFunction = fetchImpl ?? globalThis.fetch?.bind(globalThis);
|
|
384
|
-
if (
|
|
387
|
+
if (!is.function(fetchFunction)) {
|
|
385
388
|
throw fail("ARCANE_AI_MODEL_SOURCE_UNAVAILABLE", "Browser fetch is unavailable.");
|
|
386
389
|
}
|
|
387
390
|
const requestOptions = {
|
|
@@ -418,7 +421,7 @@ export function createBrowserModelSource(descriptor, {
|
|
|
418
421
|
|
|
419
422
|
function openedDownload(download) {
|
|
420
423
|
const { member, response, finalUrl } = download;
|
|
421
|
-
if (!response.body ||
|
|
424
|
+
if (!response.body || !is.function(response.body.getReader)) {
|
|
422
425
|
throw fail("ARCANE_AI_MODEL_SOURCE_INVALID", "The model response did not provide a byte stream.");
|
|
423
426
|
}
|
|
424
427
|
async function cancel(reason) {
|
|
@@ -464,7 +467,7 @@ export function createBrowserModelSource(descriptor, {
|
|
|
464
467
|
if (
|
|
465
468
|
(!header || observed)
|
|
466
469
|
&& (!observed || (observed.start === 0 && observed.end === 0))
|
|
467
|
-
&&
|
|
470
|
+
&& is.safeInteger(total)
|
|
468
471
|
&& total > 0
|
|
469
472
|
) {
|
|
470
473
|
await cancelReadableBody(download.response.body);
|
|
@@ -502,9 +505,9 @@ export function createBrowserModelSource(descriptor, {
|
|
|
502
505
|
|
|
503
506
|
async function openRange(memberIndex, { signal, start, end, total } = {}) {
|
|
504
507
|
if (
|
|
505
|
-
!
|
|
506
|
-
|| !
|
|
507
|
-
|| !
|
|
508
|
+
!is.safeInteger(start)
|
|
509
|
+
|| !is.safeInteger(end)
|
|
510
|
+
|| !is.safeInteger(total)
|
|
508
511
|
|| start < 0
|
|
509
512
|
|| end < start
|
|
510
513
|
|| end >= total
|
|
@@ -550,18 +553,18 @@ export function createBrowserModelSource(descriptor, {
|
|
|
550
553
|
}
|
|
551
554
|
|
|
552
555
|
async function* byteChunks(body, signal) {
|
|
553
|
-
if (body
|
|
556
|
+
if (is.instanceCheck(body, Uint8Array) || is.instanceCheck(body, ArrayBuffer) || is.arrayBufferView(body)) {
|
|
554
557
|
throwIfAborted(signal, "install");
|
|
555
|
-
yield body
|
|
558
|
+
yield is.instanceCheck(body, Uint8Array)
|
|
556
559
|
? body
|
|
557
560
|
: new Uint8Array(body.buffer ?? body, body.byteOffset ?? 0, body.byteLength);
|
|
558
561
|
return;
|
|
559
562
|
}
|
|
560
|
-
if (body &&
|
|
563
|
+
if (body && is.function(body.stream)) {
|
|
561
564
|
yield* byteChunks(body.stream(), signal);
|
|
562
565
|
return;
|
|
563
566
|
}
|
|
564
|
-
if (body &&
|
|
567
|
+
if (body && is.function(body.getReader)) {
|
|
565
568
|
const reader = body.getReader();
|
|
566
569
|
try {
|
|
567
570
|
while (true) {
|
|
@@ -588,7 +591,7 @@ async function* byteChunks(body, signal) {
|
|
|
588
591
|
}
|
|
589
592
|
const { done, value } = step;
|
|
590
593
|
if (done) return;
|
|
591
|
-
yield value
|
|
594
|
+
yield is.instanceCheck(value, Uint8Array) ? value : new Uint8Array(value);
|
|
592
595
|
}
|
|
593
596
|
} finally {
|
|
594
597
|
if (signal?.aborted) await reader.cancel(signal.reason).catch(() => undefined);
|
|
@@ -633,9 +636,9 @@ function rangePartDetails(modelName, name) {
|
|
|
633
636
|
const end = Number(match[2]);
|
|
634
637
|
const total = Number(match[3]);
|
|
635
638
|
if (
|
|
636
|
-
!
|
|
637
|
-
|| !
|
|
638
|
-
|| !
|
|
639
|
+
!is.safeInteger(start)
|
|
640
|
+
|| !is.safeInteger(end)
|
|
641
|
+
|| !is.safeInteger(total)
|
|
639
642
|
|| start < 0
|
|
640
643
|
|| end < start
|
|
641
644
|
|| total <= end
|
|
@@ -659,16 +662,16 @@ export function createDbopfsModelStore({
|
|
|
659
662
|
estimateStorage = null,
|
|
660
663
|
downloadConcurrency = DEFAULT_MODEL_DOWNLOAD_CONCURRENCY,
|
|
661
664
|
} = {}) {
|
|
662
|
-
if (!dbopfs || (
|
|
665
|
+
if (!dbopfs || (!is.object(dbopfs) && !is.function(dbopfs))) {
|
|
663
666
|
throw new TypeError("createDbopfsModelStore requires an existing DBOPFS instance.");
|
|
664
667
|
}
|
|
665
|
-
if (
|
|
668
|
+
if (!is.function(dbopfs.getTableHandle)) {
|
|
666
669
|
throw new TypeError("The DBOPFS instance is missing getTableHandle().");
|
|
667
670
|
}
|
|
668
|
-
if (dbopfs.readyPromise !== undefined &&
|
|
671
|
+
if (dbopfs.readyPromise !== undefined && !is.function(dbopfs.readyPromise?.then)) {
|
|
669
672
|
throw new TypeError("The DBOPFS readyPromise must be thenable.");
|
|
670
673
|
}
|
|
671
|
-
if (estimateStorage !== null &&
|
|
674
|
+
if (estimateStorage !== null && !is.function(estimateStorage)) {
|
|
672
675
|
throw new TypeError("estimateStorage must be a function or null.");
|
|
673
676
|
}
|
|
674
677
|
const workerLimit = downloadConcurrencyValue(downloadConcurrency);
|
|
@@ -678,7 +681,7 @@ export function createDbopfsModelStore({
|
|
|
678
681
|
if (dbopfs.readyPromise) await dbopfs.readyPromise;
|
|
679
682
|
tablePromise ||= Promise.resolve(dbopfs.getTableHandle(tableName));
|
|
680
683
|
const handle = await tablePromise;
|
|
681
|
-
if (!handle ||
|
|
684
|
+
if (!handle || !is.function(handle.getFileHandle) || !is.function(handle.removeEntry)) {
|
|
682
685
|
throw fail("ARCANE_AI_STORAGE_UNAVAILABLE", "DBOPFS did not provide an OPFS table handle.");
|
|
683
686
|
}
|
|
684
687
|
return handle;
|
|
@@ -717,7 +720,7 @@ export function createDbopfsModelStore({
|
|
|
717
720
|
function totalBytesValue() {
|
|
718
721
|
if (memberTotals.some((value) => value === null)) return null;
|
|
719
722
|
const totalBytes = memberTotals.reduce((sum, value) => sum + value, 0);
|
|
720
|
-
return
|
|
723
|
+
return is.safeInteger(totalBytes) ? totalBytes : null;
|
|
721
724
|
}
|
|
722
725
|
|
|
723
726
|
function progressRecord(now) {
|
|
@@ -782,20 +785,20 @@ export function createDbopfsModelStore({
|
|
|
782
785
|
}
|
|
783
786
|
|
|
784
787
|
function setMemberTotal(memberIndex, totalBytes) {
|
|
785
|
-
if (!
|
|
788
|
+
if (!is.safeInteger(totalBytes) || totalBytes <= 0) return;
|
|
786
789
|
if (memberTotals[memberIndex] === totalBytes) return;
|
|
787
790
|
memberTotals[memberIndex] = totalBytes;
|
|
788
791
|
publishSafely({ force: true });
|
|
789
792
|
}
|
|
790
793
|
|
|
791
794
|
function addBytes(value) {
|
|
792
|
-
if (!
|
|
795
|
+
if (!is.safeInteger(value) || value <= 0) return;
|
|
793
796
|
loadedBytes += value;
|
|
794
797
|
publishSafely();
|
|
795
798
|
}
|
|
796
799
|
|
|
797
800
|
function discardBytes(value) {
|
|
798
|
-
if (!
|
|
801
|
+
if (!is.safeInteger(value) || value <= 0) return;
|
|
799
802
|
loadedBytes = Math.max(0, loadedBytes - value);
|
|
800
803
|
samples = [];
|
|
801
804
|
publishSafely({ force: true });
|
|
@@ -808,7 +811,7 @@ export function createDbopfsModelStore({
|
|
|
808
811
|
publishSafely({ force: true });
|
|
809
812
|
},
|
|
810
813
|
completeMember(memberIndex, totalBytes) {
|
|
811
|
-
if (
|
|
814
|
+
if (is.safeInteger(totalBytes) && totalBytes > 0) {
|
|
812
815
|
memberTotals[memberIndex] = totalBytes;
|
|
813
816
|
}
|
|
814
817
|
completed += 1;
|
|
@@ -830,13 +833,13 @@ export function createDbopfsModelStore({
|
|
|
830
833
|
publishSafely({ force: true });
|
|
831
834
|
},
|
|
832
835
|
restoreBytes(value) {
|
|
833
|
-
if (!
|
|
836
|
+
if (!is.safeInteger(value) || value <= 0) return;
|
|
834
837
|
loadedBytes += value;
|
|
835
838
|
samples = [];
|
|
836
839
|
publishSafely({ force: true });
|
|
837
840
|
},
|
|
838
841
|
restoreMember(memberIndex, totalBytes) {
|
|
839
|
-
if (!
|
|
842
|
+
if (!is.safeInteger(totalBytes) || totalBytes <= 0) return;
|
|
840
843
|
memberTotals[memberIndex] = totalBytes;
|
|
841
844
|
loadedBytes += totalBytes;
|
|
842
845
|
completed += 1;
|
|
@@ -971,7 +974,7 @@ export function createDbopfsModelStore({
|
|
|
971
974
|
const expected = range.end - range.start + 1;
|
|
972
975
|
for await (const chunk of byteChunks(opened.body, downloadSignal)) {
|
|
973
976
|
const nextReceived = received + chunk.byteLength;
|
|
974
|
-
if (!
|
|
977
|
+
if (!is.safeInteger(nextReceived) || nextReceived > expected) {
|
|
975
978
|
throw fail(
|
|
976
979
|
"ARCANE_AI_MODEL_DOWNLOAD_FAILED",
|
|
977
980
|
"The model server returned more content than the requested HTTP byte range.",
|
|
@@ -1113,7 +1116,7 @@ export function createDbopfsModelStore({
|
|
|
1113
1116
|
|
|
1114
1117
|
async function memberRangePartNames(modelName) {
|
|
1115
1118
|
const directory = await table();
|
|
1116
|
-
if (
|
|
1119
|
+
if (!is.function(directory.entries)) return null;
|
|
1117
1120
|
const prefix = rangePartPrefix(modelName);
|
|
1118
1121
|
const names = [];
|
|
1119
1122
|
for await (const [name] of directory.entries()) {
|
|
@@ -1133,7 +1136,7 @@ export function createDbopfsModelStore({
|
|
|
1133
1136
|
} = {}) {
|
|
1134
1137
|
const existingNames = await memberRangePartNames(modelName);
|
|
1135
1138
|
let names = existingNames;
|
|
1136
|
-
if (names === null &&
|
|
1139
|
+
if (names === null && is.safeInteger(total) && total > 0) {
|
|
1137
1140
|
names = modelHttpRanges(total).map((range) => rangePartName(modelName, range));
|
|
1138
1141
|
}
|
|
1139
1142
|
names ??= [];
|
|
@@ -1183,7 +1186,7 @@ export function createDbopfsModelStore({
|
|
|
1183
1186
|
if (completeMembers.length === 0) return;
|
|
1184
1187
|
try {
|
|
1185
1188
|
const directory = await table();
|
|
1186
|
-
if (
|
|
1189
|
+
if (is.function(directory.entries)) {
|
|
1187
1190
|
const prefixes = completeMembers.map(({ modelName }) => rangePartPrefix(modelName));
|
|
1188
1191
|
const entries = [];
|
|
1189
1192
|
for await (const [name] of directory.entries()) {
|
|
@@ -1206,7 +1209,7 @@ export function createDbopfsModelStore({
|
|
|
1206
1209
|
async function removeRangeParts(source, names) {
|
|
1207
1210
|
const members = sourceMetadata(source).files;
|
|
1208
1211
|
const directory = await table();
|
|
1209
|
-
if (
|
|
1212
|
+
if (is.function(directory.entries)) {
|
|
1210
1213
|
const prefixes = names.models.map((entry) => rangePartPrefix(entry.name));
|
|
1211
1214
|
const entries = [];
|
|
1212
1215
|
for await (const [name] of directory.entries()) {
|
|
@@ -1219,7 +1222,7 @@ export function createDbopfsModelStore({
|
|
|
1219
1222
|
const removed = [];
|
|
1220
1223
|
for (let memberIndex = 0; memberIndex < members.length; memberIndex += 1) {
|
|
1221
1224
|
const total = members[memberIndex].bytes;
|
|
1222
|
-
if (!
|
|
1225
|
+
if (!is.safeInteger(total) || total <= 0) continue;
|
|
1223
1226
|
removed.push(await removeMemberRangeParts(
|
|
1224
1227
|
names.models[memberIndex].name,
|
|
1225
1228
|
{ total },
|
|
@@ -1280,7 +1283,7 @@ export function createDbopfsModelStore({
|
|
|
1280
1283
|
return null;
|
|
1281
1284
|
}
|
|
1282
1285
|
partFiles.push(partFile);
|
|
1283
|
-
if (
|
|
1286
|
+
if (is.finite(partFile.lastModified)) {
|
|
1284
1287
|
lastModified = Math.max(lastModified, partFile.lastModified);
|
|
1285
1288
|
}
|
|
1286
1289
|
}
|
|
@@ -1310,7 +1313,7 @@ export function createDbopfsModelStore({
|
|
|
1310
1313
|
async function storedRangeMember(member, modelName, signal) {
|
|
1311
1314
|
const names = await memberRangePartNames(modelName);
|
|
1312
1315
|
const availableNames = names === null ? null : new Set(names);
|
|
1313
|
-
const declaredTotal =
|
|
1316
|
+
const declaredTotal = is.safeInteger(member.bytes) && member.bytes > 0
|
|
1314
1317
|
? member.bytes
|
|
1315
1318
|
: null;
|
|
1316
1319
|
if (declaredTotal !== null) {
|
|
@@ -1387,7 +1390,7 @@ export function createDbopfsModelStore({
|
|
|
1387
1390
|
}
|
|
1388
1391
|
|
|
1389
1392
|
async function install(source, { signal, onProgress = null } = {}) {
|
|
1390
|
-
if (onProgress !== null &&
|
|
1393
|
+
if (onProgress !== null && !is.function(onProgress)) {
|
|
1391
1394
|
throw new TypeError("Model store onProgress must be a function or null.");
|
|
1392
1395
|
}
|
|
1393
1396
|
const names = storageName(source);
|
|
@@ -1510,7 +1513,7 @@ export function createDbopfsModelStore({
|
|
|
1510
1513
|
onProgress = null,
|
|
1511
1514
|
offline = false,
|
|
1512
1515
|
} = {}) {
|
|
1513
|
-
if (onProgress !== null &&
|
|
1516
|
+
if (onProgress !== null && !is.function(onProgress)) {
|
|
1514
1517
|
throw new TypeError("Model store onProgress must be a function or null.");
|
|
1515
1518
|
}
|
|
1516
1519
|
const total = sourceMetadata(source).files.length;
|
|
@@ -1675,7 +1678,7 @@ function responseFormat(structuredOutput) {
|
|
|
1675
1678
|
if (structuredOutput === true || structuredOutput === "json") {
|
|
1676
1679
|
return completeValue({ type: "json_object" });
|
|
1677
1680
|
}
|
|
1678
|
-
if (
|
|
1681
|
+
if (!is.object(structuredOutput) || is.array(structuredOutput)) {
|
|
1679
1682
|
throw new TypeError("structuredOutput must be false, true, \"json\", or a JSON Schema object.");
|
|
1680
1683
|
}
|
|
1681
1684
|
return completeValue({
|
|
@@ -1685,14 +1688,14 @@ function responseFormat(structuredOutput) {
|
|
|
1685
1688
|
}
|
|
1686
1689
|
|
|
1687
1690
|
function plainStructuralRecord(value) {
|
|
1688
|
-
if (!value ||
|
|
1691
|
+
if (!value || !is.object(value) || is.array(value)) return false;
|
|
1689
1692
|
const prototype = Object.getPrototypeOf(value);
|
|
1690
1693
|
return prototype === Object.prototype || prototype === null;
|
|
1691
1694
|
}
|
|
1692
1695
|
|
|
1693
1696
|
function validateToolMessageSchemas(value) {
|
|
1694
1697
|
if (value === undefined) return;
|
|
1695
|
-
if (!
|
|
1698
|
+
if (!is.array(value)) throw new TypeError("tools must be an array.");
|
|
1696
1699
|
for (const [index, tool] of value.entries()) {
|
|
1697
1700
|
const parameters = tool?.function?.parameters;
|
|
1698
1701
|
const messageSchema = parameters?.properties?.message;
|
|
@@ -1705,9 +1708,9 @@ function validateToolMessageSchemas(value) {
|
|
|
1705
1708
|
|| !plainStructuralRecord(parameters.properties)
|
|
1706
1709
|
|| !plainStructuralRecord(messageSchema)
|
|
1707
1710
|
|| messageSchema.type !== "string"
|
|
1708
|
-
|| !
|
|
1711
|
+
|| !is.integer(messageSchema.minLength)
|
|
1709
1712
|
|| messageSchema.minLength < 1
|
|
1710
|
-
|| !
|
|
1713
|
+
|| !is.array(parameters.required)
|
|
1711
1714
|
|| !parameters.required.includes("message")
|
|
1712
1715
|
) {
|
|
1713
1716
|
throw fail(
|
|
@@ -1746,7 +1749,7 @@ function validateRequestMessages(messages) {
|
|
|
1746
1749
|
}
|
|
1747
1750
|
}
|
|
1748
1751
|
if (message?.role === "tool") {
|
|
1749
|
-
if (
|
|
1752
|
+
if (!is.string(message.content) || !message.content.trim()) {
|
|
1750
1753
|
throw fail(
|
|
1751
1754
|
"ARCANE_AI_INVALID_TOOL_MESSAGE",
|
|
1752
1755
|
`messages[${String(messageIndex)}] must contain a nonblank user-facing tool result.`,
|
|
@@ -1754,7 +1757,7 @@ function validateRequestMessages(messages) {
|
|
|
1754
1757
|
}
|
|
1755
1758
|
if (
|
|
1756
1759
|
!pendingToolCallIds.size
|
|
1757
|
-
||
|
|
1760
|
+
|| !is.string(message.tool_call_id)
|
|
1758
1761
|
|| !pendingToolCallIds.has(message.tool_call_id)
|
|
1759
1762
|
) {
|
|
1760
1763
|
throw fail(
|
|
@@ -1790,12 +1793,12 @@ function validateStructuralRequest(request) {
|
|
|
1790
1793
|
if (!plainStructuralRecord(request)) {
|
|
1791
1794
|
throw new TypeError("The browser-WASM LLM request must be a plain object.");
|
|
1792
1795
|
}
|
|
1793
|
-
if (!
|
|
1796
|
+
if (!is.array(request.messages)) throw new TypeError("messages must be an array.");
|
|
1794
1797
|
validateRequestMessages(request.messages);
|
|
1795
1798
|
validateToolMessageSchemas(request.tools);
|
|
1796
1799
|
const parallelValues = [request.parallelToolCalls, request.parallel_tool_calls];
|
|
1797
1800
|
if (parallelValues.some(function invalidParallelBrowserWasmPreference(value) {
|
|
1798
|
-
return value !== undefined &&
|
|
1801
|
+
return value !== undefined && !is.boolean(value);
|
|
1799
1802
|
})) {
|
|
1800
1803
|
throw new TypeError("parallelToolCalls must be a boolean when provided.");
|
|
1801
1804
|
}
|
|
@@ -1830,13 +1833,13 @@ function completionOptions(request, abortSignal, stream) {
|
|
|
1830
1833
|
if (request[source] !== undefined) options[target] = request[source];
|
|
1831
1834
|
}
|
|
1832
1835
|
if (request.templateOptions !== undefined) {
|
|
1833
|
-
if (!request.templateOptions ||
|
|
1836
|
+
if (!request.templateOptions || !is.object(request.templateOptions) || is.array(request.templateOptions)) {
|
|
1834
1837
|
throw new TypeError("templateOptions must be a plain object when provided.");
|
|
1835
1838
|
}
|
|
1836
1839
|
options.chat_template_kwargs = { ...request.templateOptions };
|
|
1837
1840
|
}
|
|
1838
1841
|
if (request.tools !== undefined) {
|
|
1839
|
-
if (!
|
|
1842
|
+
if (!is.array(request.tools)) throw new TypeError("tools must be an array.");
|
|
1840
1843
|
options.tools = request.tools;
|
|
1841
1844
|
}
|
|
1842
1845
|
if (request.toolChoice !== undefined) options.tool_choice = request.toolChoice;
|
|
@@ -1863,7 +1866,7 @@ function validateToolCalls(message, location = "The model response") {
|
|
|
1863
1866
|
}
|
|
1864
1867
|
if (!Object.hasOwn(message, "tool_calls")) return [];
|
|
1865
1868
|
const descriptor = Object.getOwnPropertyDescriptor(message, "tool_calls");
|
|
1866
|
-
if (!descriptor || !Object.hasOwn(descriptor, "value") || !
|
|
1869
|
+
if (!descriptor || !Object.hasOwn(descriptor, "value") || !is.array(descriptor.value)) {
|
|
1867
1870
|
throw fail("ARCANE_AI_TOOL_CALL_INVALID", `${location} contains malformed tool calls.`);
|
|
1868
1871
|
}
|
|
1869
1872
|
const calls = descriptor.value;
|
|
@@ -1871,14 +1874,14 @@ function validateToolCalls(message, location = "The model response") {
|
|
|
1871
1874
|
for (const call of calls) {
|
|
1872
1875
|
if (
|
|
1873
1876
|
!plainStructuralRecord(call)
|
|
1874
|
-
||
|
|
1877
|
+
|| !is.string(call.id)
|
|
1875
1878
|
|| !call.id.trim()
|
|
1876
1879
|
|| ids.has(call.id)
|
|
1877
1880
|
|| call.type !== "function"
|
|
1878
1881
|
|| !plainStructuralRecord(call.function)
|
|
1879
|
-
||
|
|
1882
|
+
|| !is.string(call.function.name)
|
|
1880
1883
|
|| !call.function.name.trim()
|
|
1881
|
-
||
|
|
1884
|
+
|| !is.string(call.function.arguments)
|
|
1882
1885
|
) {
|
|
1883
1886
|
throw fail("ARCANE_AI_TOOL_CALL_INVALID", `${location} contains malformed tool calls.`);
|
|
1884
1887
|
}
|
|
@@ -1898,7 +1901,7 @@ function validateToolCalls(message, location = "The model response") {
|
|
|
1898
1901
|
`${location} contains structural tool arguments that are not a JSON object.`,
|
|
1899
1902
|
);
|
|
1900
1903
|
}
|
|
1901
|
-
if (
|
|
1904
|
+
if (!is.string(argumentsRecord.message) || !argumentsRecord.message.trim()) {
|
|
1902
1905
|
throw fail(
|
|
1903
1906
|
"ARCANE_AI_TOOL_MESSAGE_REQUIRED",
|
|
1904
1907
|
`${location} structural tool arguments must include a nonempty user-facing message.`,
|
|
@@ -1940,7 +1943,7 @@ function validateCompletion(value, requestId) {
|
|
|
1940
1943
|
if (
|
|
1941
1944
|
!choicesDescriptor
|
|
1942
1945
|
|| !Object.hasOwn(choicesDescriptor, "value")
|
|
1943
|
-
|| !
|
|
1946
|
+
|| !is.array(choicesDescriptor.value)
|
|
1944
1947
|
|| choicesDescriptor.value.length === 0
|
|
1945
1948
|
) {
|
|
1946
1949
|
throw fail("ARCANE_AI_INVALID_PROVIDER_RESULT", "The model returned an invalid chat completion.");
|
|
@@ -1954,7 +1957,7 @@ function validateCompletion(value, requestId) {
|
|
|
1954
1957
|
: null;
|
|
1955
1958
|
if (
|
|
1956
1959
|
!plainStructuralRecord(choice)
|
|
1957
|
-
|| !
|
|
1960
|
+
|| !is.safeInteger(choice.index)
|
|
1958
1961
|
|| choice.index < 0
|
|
1959
1962
|
|| indexes.has(choice.index)
|
|
1960
1963
|
|| !messageDescriptor
|
|
@@ -1977,7 +1980,7 @@ function selectedCompletionToolCalls(completion) {
|
|
|
1977
1980
|
const message = Object.hasOwn(completion ?? {}, "message")
|
|
1978
1981
|
? completion.message
|
|
1979
1982
|
: completion?.choices?.[0]?.message;
|
|
1980
|
-
return
|
|
1983
|
+
return is.array(message?.tool_calls) ? message.tool_calls : [];
|
|
1981
1984
|
}
|
|
1982
1985
|
|
|
1983
1986
|
function sameCanonicalToolCalls(left, right) {
|
|
@@ -1995,9 +1998,9 @@ function sameCompleteStreamValue(left, right, leftToRight = new Map(), rightToLe
|
|
|
1995
1998
|
if (
|
|
1996
1999
|
!left
|
|
1997
2000
|
|| !right
|
|
1998
|
-
||
|
|
1999
|
-
||
|
|
2000
|
-
||
|
|
2001
|
+
|| !is.object(left)
|
|
2002
|
+
|| !is.object(right)
|
|
2003
|
+
|| is.array(left) !== is.array(right)
|
|
2001
2004
|
) return false;
|
|
2002
2005
|
if (leftToRight.has(left) || rightToLeft.has(right)) {
|
|
2003
2006
|
return leftToRight.get(left) === right && rightToLeft.get(right) === left;
|
|
@@ -2051,9 +2054,9 @@ function isPublicStreamStructuralKey(key) {
|
|
|
2051
2054
|
const OMITTED_PUBLIC_STREAM_DATA = Symbol("omitted-public-stream-data");
|
|
2052
2055
|
|
|
2053
2056
|
function projectPublicStreamData(value, seen = new Map()) {
|
|
2054
|
-
if (value === null || value === undefined ||
|
|
2057
|
+
if (value === null || value === undefined || !is.object(value)) return value;
|
|
2055
2058
|
if (seen.has(value)) return seen.get(value);
|
|
2056
|
-
if (
|
|
2059
|
+
if (is.array(value)) {
|
|
2057
2060
|
const result = [];
|
|
2058
2061
|
seen.set(value, result);
|
|
2059
2062
|
for (const item of value) {
|
|
@@ -2067,7 +2070,7 @@ function projectPublicStreamData(value, seen = new Map()) {
|
|
|
2067
2070
|
let sourceDataFields = 0;
|
|
2068
2071
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
2069
2072
|
for (const key of Reflect.ownKeys(descriptors)) {
|
|
2070
|
-
if (
|
|
2073
|
+
if (is.symbol(key)) continue;
|
|
2071
2074
|
const descriptor = descriptors[key];
|
|
2072
2075
|
if (!Object.hasOwn(descriptor, "value")) continue;
|
|
2073
2076
|
sourceDataFields += 1;
|
|
@@ -2090,7 +2093,7 @@ function createCompletionAccumulator(modelId, requestId) {
|
|
|
2090
2093
|
|
|
2091
2094
|
function choice(index) {
|
|
2092
2095
|
const key = index ?? 0;
|
|
2093
|
-
if (!
|
|
2096
|
+
if (!is.safeInteger(key) || key < 0) {
|
|
2094
2097
|
throw fail("ARCANE_AI_INVALID_PROVIDER_RESULT", "The model returned an invalid stream choice index.");
|
|
2095
2098
|
}
|
|
2096
2099
|
if (!choices.has(key)) {
|
|
@@ -2114,9 +2117,9 @@ function createCompletionAccumulator(modelId, requestId) {
|
|
|
2114
2117
|
}
|
|
2115
2118
|
|
|
2116
2119
|
function push(value) {
|
|
2117
|
-
if (!value ||
|
|
2120
|
+
if (!value || !is.object(value)) return;
|
|
2118
2121
|
base = { ...base, ...value, id: requestId ?? value.id ?? base.id, choices: [] };
|
|
2119
|
-
for (const item of
|
|
2122
|
+
for (const item of is.array(value.choices) ? value.choices : []) {
|
|
2120
2123
|
const record = choice(item.index);
|
|
2121
2124
|
for (const [key, fieldValue] of Object.entries(item)) {
|
|
2122
2125
|
if (key !== "delta" && key !== "message") record.choiceMetadata[key] = fieldValue;
|
|
@@ -2127,14 +2130,14 @@ function createCompletionAccumulator(modelId, requestId) {
|
|
|
2127
2130
|
if (!source) continue;
|
|
2128
2131
|
for (const [key, fieldValue] of Object.entries(source)) {
|
|
2129
2132
|
if (isPublicStreamStructuralKey(key)) continue;
|
|
2130
|
-
if (key === "role" &&
|
|
2133
|
+
if (key === "role" && is.string(fieldValue)) {
|
|
2131
2134
|
record.role = fieldValue;
|
|
2132
2135
|
} else if (key === "content") {
|
|
2133
2136
|
if (
|
|
2134
2137
|
!replaceText
|
|
2135
2138
|
&& record.sawContent
|
|
2136
|
-
&&
|
|
2137
|
-
&&
|
|
2139
|
+
&& is.string(record.content)
|
|
2140
|
+
&& is.string(fieldValue)
|
|
2138
2141
|
) record.content += fieldValue;
|
|
2139
2142
|
else record.content = fieldValue;
|
|
2140
2143
|
record.sawContent = true;
|
|
@@ -2142,8 +2145,8 @@ function createCompletionAccumulator(modelId, requestId) {
|
|
|
2142
2145
|
if (
|
|
2143
2146
|
!replaceText
|
|
2144
2147
|
&& record.sawReasoning
|
|
2145
|
-
&&
|
|
2146
|
-
&&
|
|
2148
|
+
&& is.string(record.reasoning)
|
|
2149
|
+
&& is.string(fieldValue)
|
|
2147
2150
|
) record.reasoning += fieldValue;
|
|
2148
2151
|
else record.reasoning = fieldValue;
|
|
2149
2152
|
record.sawReasoning = true;
|
|
@@ -2151,8 +2154,8 @@ function createCompletionAccumulator(modelId, requestId) {
|
|
|
2151
2154
|
if (
|
|
2152
2155
|
!replaceText
|
|
2153
2156
|
&& record.sawReasoningText
|
|
2154
|
-
&&
|
|
2155
|
-
&&
|
|
2157
|
+
&& is.string(record.reasoningText)
|
|
2158
|
+
&& is.string(fieldValue)
|
|
2156
2159
|
) record.reasoningText += fieldValue;
|
|
2157
2160
|
else record.reasoningText = fieldValue;
|
|
2158
2161
|
record.sawReasoningText = true;
|
|
@@ -2168,11 +2171,11 @@ function createCompletionAccumulator(modelId, requestId) {
|
|
|
2168
2171
|
);
|
|
2169
2172
|
}
|
|
2170
2173
|
if (item.finish_reason !== undefined) record.finish_reason = item.finish_reason;
|
|
2171
|
-
if (delta.tool_calls !== undefined && !
|
|
2174
|
+
if (delta.tool_calls !== undefined && !is.array(delta.tool_calls)) {
|
|
2172
2175
|
throw fail("ARCANE_AI_INVALID_PROVIDER_RESULT", "The model returned malformed streamed tool calls.");
|
|
2173
2176
|
}
|
|
2174
2177
|
for (const fragment of delta.tool_calls ?? []) {
|
|
2175
|
-
if (!
|
|
2178
|
+
if (!is.safeInteger(fragment?.index) || fragment.index < 0) {
|
|
2176
2179
|
throw fail("ARCANE_AI_INVALID_PROVIDER_RESULT", "A streamed tool call had no valid index.");
|
|
2177
2180
|
}
|
|
2178
2181
|
const tool = record.tools.get(fragment.index) ?? {
|
|
@@ -2185,25 +2188,25 @@ function createCompletionAccumulator(modelId, requestId) {
|
|
|
2185
2188
|
arguments: "",
|
|
2186
2189
|
};
|
|
2187
2190
|
if (fragment.id !== undefined) {
|
|
2188
|
-
if (
|
|
2191
|
+
if (!is.string(fragment.id) || !fragment.id || tool.id && tool.id !== fragment.id) {
|
|
2189
2192
|
tool.invalidIdentity = true;
|
|
2190
2193
|
} else {
|
|
2191
2194
|
tool.id = fragment.id;
|
|
2192
2195
|
}
|
|
2193
2196
|
}
|
|
2194
2197
|
if (fragment.type !== undefined) {
|
|
2195
|
-
if (
|
|
2198
|
+
if (!is.string(fragment.type) || !fragment.type || tool.type && tool.type !== fragment.type) {
|
|
2196
2199
|
tool.invalidIdentity = true;
|
|
2197
2200
|
} else {
|
|
2198
2201
|
tool.type = fragment.type;
|
|
2199
2202
|
}
|
|
2200
2203
|
}
|
|
2201
2204
|
if (fragment.function?.name !== undefined) {
|
|
2202
|
-
if (
|
|
2205
|
+
if (!is.string(fragment.function.name)) tool.invalidIdentity = true;
|
|
2203
2206
|
else tool.name += fragment.function.name;
|
|
2204
2207
|
}
|
|
2205
2208
|
if (fragment.function?.arguments !== undefined) {
|
|
2206
|
-
if (
|
|
2209
|
+
if (!is.string(fragment.function.arguments)) tool.invalidArguments = true;
|
|
2207
2210
|
else tool.arguments += fragment.function.arguments;
|
|
2208
2211
|
}
|
|
2209
2212
|
record.tools.set(fragment.index, tool);
|
|
@@ -2298,7 +2301,7 @@ function createCompletionAccumulator(modelId, requestId) {
|
|
|
2298
2301
|
}
|
|
2299
2302
|
if (
|
|
2300
2303
|
fragmentCalls !== null
|
|
2301
|
-
&& (!
|
|
2304
|
+
&& (!is.array(terminalCalls) || !sameCanonicalToolCalls(fragmentCalls, terminalCalls))
|
|
2302
2305
|
) {
|
|
2303
2306
|
throw fail(
|
|
2304
2307
|
"ARCANE_AI_TOOL_CALL_INVALID",
|
|
@@ -2423,13 +2426,13 @@ function callbackStreamHandle({ runtime, request, signal, onSettled }) {
|
|
|
2423
2426
|
function validatedV1StreamHandle(opened, request) {
|
|
2424
2427
|
if (
|
|
2425
2428
|
!opened
|
|
2426
|
-
||
|
|
2427
|
-
||
|
|
2428
|
-
||
|
|
2429
|
+
|| !is.object(opened)
|
|
2430
|
+
|| !is.function(opened[Symbol.asyncIterator])
|
|
2431
|
+
|| !is.function(opened.cancel)
|
|
2429
2432
|
|| !opened.result
|
|
2430
|
-
||
|
|
2433
|
+
|| !is.function(opened.result.then)
|
|
2431
2434
|
) {
|
|
2432
|
-
if (
|
|
2435
|
+
if (is.function(opened?.cancel)) {
|
|
2433
2436
|
Promise.resolve().then(function cancelInvalidV1StreamHandle() {
|
|
2434
2437
|
return opened.cancel("The v1 provider returned an invalid stream handle.");
|
|
2435
2438
|
}).catch(function reportInvalidV1StreamCleanupFailure(error) {
|
|
@@ -2452,7 +2455,7 @@ function validatedV1StreamHandle(opened, request) {
|
|
|
2452
2455
|
});
|
|
2453
2456
|
throw error;
|
|
2454
2457
|
}
|
|
2455
|
-
if (!iterator ||
|
|
2458
|
+
if (!iterator || !is.function(iterator.next)) {
|
|
2456
2459
|
Promise.resolve().then(function cancelInvalidV1Iterator() {
|
|
2457
2460
|
return opened.cancel("The v1 provider returned an invalid stream iterator.");
|
|
2458
2461
|
}).catch(function reportInvalidV1IteratorCleanupFailure(error) {
|
|
@@ -2536,7 +2539,7 @@ function validatedV1StreamHandle(opened, request) {
|
|
|
2536
2539
|
});
|
|
2537
2540
|
},
|
|
2538
2541
|
async return(value) {
|
|
2539
|
-
if (
|
|
2542
|
+
if (is.function(iterator.return)) {
|
|
2540
2543
|
Promise.resolve().then(function returnUnderlyingV1Stream() {
|
|
2541
2544
|
return iterator.return(value);
|
|
2542
2545
|
}).catch(function reportUnderlyingV1StreamReturnFailure(error) {
|
|
@@ -2563,7 +2566,7 @@ function validatedV1StreamHandle(opened, request) {
|
|
|
2563
2566
|
|
|
2564
2567
|
function positiveLoadInteger(value, field, fallback) {
|
|
2565
2568
|
const resolved = value === undefined ? fallback : value;
|
|
2566
|
-
if (!
|
|
2569
|
+
if (!is.safeInteger(resolved) || resolved < 1) {
|
|
2567
2570
|
throw new RangeError(`${field} must be a positive safe integer.`);
|
|
2568
2571
|
}
|
|
2569
2572
|
return resolved;
|
|
@@ -2571,19 +2574,19 @@ function positiveLoadInteger(value, field, fallback) {
|
|
|
2571
2574
|
|
|
2572
2575
|
function optionalLoadBoolean(value, field) {
|
|
2573
2576
|
if (value === undefined) return undefined;
|
|
2574
|
-
if (
|
|
2577
|
+
if (!is.boolean(value)) throw new TypeError(`${field} must be a boolean when provided.`);
|
|
2575
2578
|
return value;
|
|
2576
2579
|
}
|
|
2577
2580
|
|
|
2578
2581
|
function optionalLoadText(value, field) {
|
|
2579
2582
|
if (value === undefined) return undefined;
|
|
2580
|
-
if (
|
|
2583
|
+
if (!is.string(value)) throw new TypeError(`${field} must be a string when provided.`);
|
|
2581
2584
|
return value;
|
|
2582
2585
|
}
|
|
2583
2586
|
|
|
2584
2587
|
function optionalTemplateDefaults(value) {
|
|
2585
2588
|
if (value === undefined) return undefined;
|
|
2586
|
-
if (!value ||
|
|
2589
|
+
if (!value || !is.object(value) || is.array(value)) {
|
|
2587
2590
|
throw new TypeError("templateDefaults must be a plain object when provided.");
|
|
2588
2591
|
}
|
|
2589
2592
|
return { ...value };
|
|
@@ -2591,7 +2594,7 @@ function optionalTemplateDefaults(value) {
|
|
|
2591
2594
|
|
|
2592
2595
|
function measuredRuntimeCapabilities(runtimeCapabilities) {
|
|
2593
2596
|
const measuredDeviceMemory = Number(globalThis.navigator?.deviceMemory);
|
|
2594
|
-
const deviceMemory =
|
|
2597
|
+
const deviceMemory = is.finite(measuredDeviceMemory) && measuredDeviceMemory > 0
|
|
2595
2598
|
? measuredDeviceMemory
|
|
2596
2599
|
: null;
|
|
2597
2600
|
return completeValue({ ...runtimeCapabilities, deviceMemory });
|
|
@@ -2599,7 +2602,7 @@ function measuredRuntimeCapabilities(runtimeCapabilities) {
|
|
|
2599
2602
|
|
|
2600
2603
|
function capabilityLoadPlan(runtimeCapabilities, defaults, options = {}) {
|
|
2601
2604
|
const configured = { ...defaults, ...options };
|
|
2602
|
-
const hardwareConcurrency =
|
|
2605
|
+
const hardwareConcurrency = is.safeInteger(runtimeCapabilities.hardwareConcurrency)
|
|
2603
2606
|
&& runtimeCapabilities.hardwareConcurrency > 0
|
|
2604
2607
|
? runtimeCapabilities.hardwareConcurrency
|
|
2605
2608
|
: 1;
|
|
@@ -2666,8 +2669,8 @@ function sameLoadPlan(left, right) {
|
|
|
2666
2669
|
}
|
|
2667
2670
|
|
|
2668
2671
|
function stableModelFailure(error) {
|
|
2669
|
-
const code =
|
|
2670
|
-
const message =
|
|
2672
|
+
const code = is.string(error?.code) ? error.code : "";
|
|
2673
|
+
const message = is.string(error?.message) ? error.message : "";
|
|
2671
2674
|
if (code === "ARCANE_AI_MODEL_SHARD_TOO_LARGE") {
|
|
2672
2675
|
return completeValue({ code });
|
|
2673
2676
|
}
|
|
@@ -2755,7 +2758,7 @@ function capabilityPolicy(
|
|
|
2755
2758
|
}
|
|
2756
2759
|
|
|
2757
2760
|
function providerModelSources(sources) {
|
|
2758
|
-
if (!
|
|
2761
|
+
if (!is.array(sources) || sources.length === 0) {
|
|
2759
2762
|
throw new TypeError("createBrowserWasmLlmProvider requires a nonempty sources array.");
|
|
2760
2763
|
}
|
|
2761
2764
|
const ids = new Set();
|
|
@@ -2921,8 +2924,8 @@ export function createBrowserWasmLlmProvider({
|
|
|
2921
2924
|
: "unloaded";
|
|
2922
2925
|
errorState = state === "error"
|
|
2923
2926
|
? completeValue({
|
|
2924
|
-
code:
|
|
2925
|
-
message:
|
|
2927
|
+
code: is.string(error?.code) ? error.code : "ARCANE_AI_RUNTIME_FAILED",
|
|
2928
|
+
message: is.string(error?.message)
|
|
2926
2929
|
? error.message
|
|
2927
2930
|
: "The browser-WASM runtime failed.",
|
|
2928
2931
|
})
|
|
@@ -2985,7 +2988,7 @@ export function createBrowserWasmLlmProvider({
|
|
|
2985
2988
|
if (
|
|
2986
2989
|
options.onProgress !== undefined
|
|
2987
2990
|
&& options.onProgress !== null
|
|
2988
|
-
&&
|
|
2991
|
+
&& !is.function(options.onProgress)
|
|
2989
2992
|
) {
|
|
2990
2993
|
throw new TypeError("Browser-WASM load onProgress must be a function or null.");
|
|
2991
2994
|
}
|
|
@@ -2993,7 +2996,7 @@ export function createBrowserWasmLlmProvider({
|
|
|
2993
2996
|
const linked = linkAbortSignal(externalSignal);
|
|
2994
2997
|
const signal = linked.controller.signal;
|
|
2995
2998
|
const generation = ++lifecycleGeneration;
|
|
2996
|
-
const reportProgress =
|
|
2999
|
+
const reportProgress = is.function(context.reportProgress)
|
|
2997
3000
|
? context.reportProgress
|
|
2998
3001
|
: options.onProgress ?? null;
|
|
2999
3002
|
const progressStartedAt = Date.now();
|
|
@@ -3057,7 +3060,7 @@ export function createBrowserWasmLlmProvider({
|
|
|
3057
3060
|
heartbeat: false,
|
|
3058
3061
|
});
|
|
3059
3062
|
const modelFiles = admitted.files.map((file, index) => (
|
|
3060
|
-
|
|
3063
|
+
is.function(globalThis.File)
|
|
3061
3064
|
? new File([file], members[index].name, { type: "application/octet-stream" })
|
|
3062
3065
|
: file
|
|
3063
3066
|
));
|
|
@@ -3282,8 +3285,8 @@ function assertV1LlmAdapterSelection(selection, providerId, modelIds, role) {
|
|
|
3282
3285
|
}
|
|
3283
3286
|
if (
|
|
3284
3287
|
!selection
|
|
3285
|
-
||
|
|
3286
|
-
||
|
|
3288
|
+
|| !is.object(selection)
|
|
3289
|
+
|| is.array(selection)
|
|
3287
3290
|
|| selection.providerId !== providerId
|
|
3288
3291
|
|| !modelIds.has(selection.modelId)
|
|
3289
3292
|
|| selection.localOnly !== true
|
|
@@ -3301,7 +3304,7 @@ function assertV1LlmAdapterSelection(selection, providerId, modelIds, role) {
|
|
|
3301
3304
|
* The adapter is local-only, never falls back, and never executes tool calls.
|
|
3302
3305
|
*/
|
|
3303
3306
|
export function adaptV1LlmProvider(provider) {
|
|
3304
|
-
if (!provider ||
|
|
3307
|
+
if (!provider || !is.object(provider) || is.array(provider)) {
|
|
3305
3308
|
throw new TypeError("adaptV1LlmProvider requires an Arcane browser-WASM LLM provider.");
|
|
3306
3309
|
}
|
|
3307
3310
|
const existing = V1_LLM_PROVIDER_ADAPTERS.get(provider);
|
|
@@ -3315,7 +3318,7 @@ export function adaptV1LlmProvider(provider) {
|
|
|
3315
3318
|
const requiredMethods = ["capabilities", "status", "load", "unload", "chat", "stream", "dispose"];
|
|
3316
3319
|
const methods = Object.create(null);
|
|
3317
3320
|
for (const method of requiredMethods) {
|
|
3318
|
-
if (
|
|
3321
|
+
if (!is.function(provider[method])) {
|
|
3319
3322
|
throw new TypeError(`The browser-WASM LLM provider is missing ${method}().`);
|
|
3320
3323
|
}
|
|
3321
3324
|
methods[method] = provider[method].bind(provider);
|
|
@@ -3325,10 +3328,10 @@ export function adaptV1LlmProvider(provider) {
|
|
|
3325
3328
|
}
|
|
3326
3329
|
|
|
3327
3330
|
const fallbackCatalog = completeValue([model]);
|
|
3328
|
-
const initialCatalog =
|
|
3331
|
+
const initialCatalog = is.function(provider.catalog)
|
|
3329
3332
|
? provider.catalog()
|
|
3330
3333
|
: fallbackCatalog;
|
|
3331
|
-
if (!
|
|
3334
|
+
if (!is.array(initialCatalog) || initialCatalog.length === 0) {
|
|
3332
3335
|
throw new TypeError("The browser-WASM provider catalog must be a nonempty array.");
|
|
3333
3336
|
}
|
|
3334
3337
|
const catalogModels = new Map();
|
|
@@ -3371,10 +3374,10 @@ export function adaptV1LlmProvider(provider) {
|
|
|
3371
3374
|
const value = methods.status();
|
|
3372
3375
|
if (
|
|
3373
3376
|
!value
|
|
3374
|
-
||
|
|
3375
|
-
||
|
|
3376
|
-
||
|
|
3377
|
-
||
|
|
3377
|
+
|| !is.object(value)
|
|
3378
|
+
|| !is.string(value.state)
|
|
3379
|
+
|| !is.boolean(value.loaded)
|
|
3380
|
+
|| !is.boolean(value.busy)
|
|
3378
3381
|
) {
|
|
3379
3382
|
throw fail("ARCANE_AI_PROVIDER_STATUS_INVALID", "The browser-WASM provider returned an invalid status.");
|
|
3380
3383
|
}
|
|
@@ -3394,7 +3397,7 @@ export function adaptV1LlmProvider(provider) {
|
|
|
3394
3397
|
role: "llm",
|
|
3395
3398
|
id: providerId,
|
|
3396
3399
|
localOnly: true,
|
|
3397
|
-
catalog: () =>
|
|
3400
|
+
catalog: () => is.function(provider.catalog)
|
|
3398
3401
|
? provider.catalog()
|
|
3399
3402
|
: fallbackCatalog,
|
|
3400
3403
|
async inspect(selection, { role = "llm", signal = null } = {}) {
|
|
@@ -3435,7 +3438,7 @@ export function adaptV1LlmProvider(provider) {
|
|
|
3435
3438
|
} = {}) {
|
|
3436
3439
|
assertSelection(selection, role);
|
|
3437
3440
|
throwIfAborted(signal, "load");
|
|
3438
|
-
if (progress !== null &&
|
|
3441
|
+
if (progress !== null && !is.function(progress)) {
|
|
3439
3442
|
throw new TypeError("The provider/2 progress sink must be a function or null.");
|
|
3440
3443
|
}
|
|
3441
3444
|
await methods.load({
|