arcane-os 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/NOTICE +5 -3
- package/README.md +73 -24
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +67 -18
- package/browser-runtime/ai/ARCANE_AI_BROWSER_WASM_COMPONENTS.json +16 -5
- package/browser-runtime/ai/browser-kokoro-worker.mjs +3 -0
- package/browser-runtime/ai/browser-speech-artifacts.mjs +1108 -0
- package/browser-runtime/ai/browser-speech-providers.mjs +780 -0
- package/browser-runtime/ai/browser-speech.mjs +9 -0
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +1537 -167
- package/browser-runtime/ai/browser-wasm.mjs +46 -1
- package/browser-runtime/ai/browser-whisper-worker.mjs +3 -0
- package/browser-runtime/ai/browser-wllama-runtime.mjs +677 -132
- package/browser-runtime/ai/model-controller.mjs +138 -12
- package/browser-runtime/ai/speech-worker-client.mjs +207 -0
- package/browser-runtime/ai/speech-worker-runtime.mjs +516 -0
- package/browser-runtime/ai/wllama/index.mjs +389 -0
- package/docs/architecture.md +132 -22
- package/docs/reference/README.md +1 -1
- package/docs/reference/ai/browser-wasm.md +101 -42
- package/docs/reference/availability-and-normalization.md +19 -5
- package/docs/reference/behavioral-testing.md +18 -5
- package/docs/reference/cli.md +2 -2
- package/docs/reference/inventory/package-api.json +14 -14
- package/docs/reference/protocols.md +4 -4
- package/docs/reference/sdk-api.md +68 -38
- package/docs/work-amplification.md +8 -4
- package/package.json +7 -3
- package/runtime/ARCANE_RUNTIME_RELEASE.json +50 -20
- package/runtime/arcane/components/chat.html +551 -62
- package/runtime/arcane/components/speech.html +1113 -265
- package/runtime/arcane/entities/Chat.js +246 -43
- package/runtime/arcane/modules/AI.js +1394 -162
- package/runtime/arcane/modules/AIProviderRuntime.js +2289 -0
- package/runtime/arcane/modules/AIRuntimeState.js +872 -0
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +382 -31
- package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +1106 -0
- package/runtime/arcane/modules/DocumentLexicalSearch.js +292 -0
- package/runtime/arcane/modules/PersistentAIChatSession.js +268 -0
- package/runtime/arcane/modules/StaticDocumentCatalog.js +25 -206
- package/schemas/arcane-lock.schema.json +10 -6
- package/src/cli/main.mjs +14 -2
- package/src/constants.mjs +1 -1
- package/src/dev-server.mjs +273 -26
- package/src/doctor.mjs +1 -3
- package/src/import-map.mjs +193 -84
- package/src/packager/core.mjs +313 -41
- package/src/runtime.mjs +14 -4
- package/src/scaffold.mjs +45 -17
- package/src/sdk-browser-runtime.mjs +28 -75
- package/src/templates/workspace-template.mjs +27 -8
- package/src/toolchain.mjs +13 -2
- package/src/workspace-runtime.mjs +1 -1
- package/src/workspace.mjs +178 -25
|
@@ -2,6 +2,16 @@ import { Wllama } from "./wllama/index.mjs";
|
|
|
2
2
|
|
|
3
3
|
const MODULE_URL = new URL("./wllama/index.mjs", import.meta.url).href;
|
|
4
4
|
const WASM_URL = new URL("./wllama/wllama.wasm", import.meta.url).href;
|
|
5
|
+
const WEBGPU_EVIDENCE_PROTOCOL = "arcane-wllama-webgpu-evidence/1";
|
|
6
|
+
const RUNTIME_EVIDENCE_PROTOCOL = "arcane-wllama-runtime-evidence/1";
|
|
7
|
+
const FULL_GPU_LAYERS = 99_999;
|
|
8
|
+
const WEBGPU_ADAPTER_PATTERN = /^ggml_webgpu: adapter_info: vendor_id: (\d+) \| vendor: (.*?) \| architecture: (.*?) \| device_id: (\d+) \| name: (.*?) \| device_desc: (.*)$/u;
|
|
9
|
+
const GPU_OFFLOAD_PATTERN = /^[^:]+: offloaded (\d+)\/(\d+) layers to GPU$/u;
|
|
10
|
+
const PEG_NATIVE_OUTPUT_PREFIX = "common_chat_peg_parse: unparsed peg-native output: ";
|
|
11
|
+
const PEG_NATIVE_FINAL_PREFIX = "<|channel|>final <|constrain|>content<|message|>";
|
|
12
|
+
const PEG_NATIVE_FAILURE = "The model produced output that does not match the expected peg-native format";
|
|
13
|
+
const MAX_RECOVERED_COMPLETION_CHARACTERS = 1_048_576;
|
|
14
|
+
const MAX_RECOVERED_COMPLETION_LINES = 16_384;
|
|
5
15
|
|
|
6
16
|
function deepFreeze(value) {
|
|
7
17
|
if (!value || typeof value !== "object" || Object.isFrozen(value)) return value;
|
|
@@ -10,8 +20,19 @@ function deepFreeze(value) {
|
|
|
10
20
|
}
|
|
11
21
|
|
|
12
22
|
export const BROWSER_WASM_RUNTIME_AUTHORITY = deepFreeze({
|
|
13
|
-
protocol: "arcane-ai-browser-wasm/
|
|
23
|
+
protocol: "arcane-ai-browser-wasm/2",
|
|
14
24
|
provider: "wllama",
|
|
25
|
+
executionPolicy: {
|
|
26
|
+
webgpuRequired: true,
|
|
27
|
+
cpuFallback: false,
|
|
28
|
+
operationalEvidence: RUNTIME_EVIDENCE_PROTOCOL,
|
|
29
|
+
navigatorPresenceIsOperationalEvidence: false,
|
|
30
|
+
cancellation: "abortSignal-plus-llama-cancel-acknowledgement",
|
|
31
|
+
cleanup: "worker-termination-only",
|
|
32
|
+
nativeUnloadClaimed: false,
|
|
33
|
+
physicalVramReclamationClaimed: false,
|
|
34
|
+
telemetryThreatModel: "authenticated-module-closure-and-frozen-prototypes-not-hostile-platform-global-attestation",
|
|
35
|
+
},
|
|
15
36
|
package: {
|
|
16
37
|
name: "@wllama/wllama",
|
|
17
38
|
version: "3.6.0",
|
|
@@ -40,9 +61,17 @@ export const BROWSER_WASM_RUNTIME_AUTHORITY = deepFreeze({
|
|
|
40
61
|
module: {
|
|
41
62
|
path: "ai/wllama/index.mjs",
|
|
42
63
|
url: MODULE_URL,
|
|
43
|
-
bytes:
|
|
44
|
-
sha256: "
|
|
64
|
+
bytes: 392_852,
|
|
65
|
+
sha256: "b119a7cdffabc8541dce283381d18ada4027c0560728aac1fe45bdd30cdac8e2",
|
|
45
66
|
mediaType: "text/javascript",
|
|
67
|
+
projection: {
|
|
68
|
+
protocol: WEBGPU_EVIDENCE_PROTOCOL,
|
|
69
|
+
tool: "tools/project-wllama-webgpu-runtime.mjs",
|
|
70
|
+
sourcePath: "node_modules/@wllama/wllama/esm/index.js",
|
|
71
|
+
sourceBytes: 373_519,
|
|
72
|
+
sourceSha256: "4637e42d636010493a9b274fbbe70bfd8120365da726b1d9e589d85ca84a00d6",
|
|
73
|
+
wasmModified: false,
|
|
74
|
+
},
|
|
46
75
|
},
|
|
47
76
|
wasm: {
|
|
48
77
|
path: "ai/wllama/wllama.wasm",
|
|
@@ -59,12 +88,23 @@ export const BROWSER_WASM_RUNTIME_AUTHORITY = deepFreeze({
|
|
|
59
88
|
},
|
|
60
89
|
});
|
|
61
90
|
|
|
62
|
-
function
|
|
91
|
+
function runtimeFailure(code, message, cause) {
|
|
92
|
+
const error = new Error(message, cause === undefined ? undefined : { cause });
|
|
93
|
+
error.name = "ArcaneWllamaRuntimeError";
|
|
94
|
+
error.code = code;
|
|
95
|
+
return error;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function runtimeCapabilitySnapshot(evidence) {
|
|
63
99
|
const navigatorObject = globalThis.navigator;
|
|
100
|
+
const webgpuOperational = evidence?.state === "ready" && evidence?.webgpu?.observed === true;
|
|
64
101
|
return Object.freeze({
|
|
65
102
|
webAssembly: typeof globalThis.WebAssembly === "object",
|
|
66
103
|
opfs: typeof navigatorObject?.storage?.getDirectory === "function",
|
|
67
|
-
webgpu:
|
|
104
|
+
webgpu: webgpuOperational,
|
|
105
|
+
webgpuApiPresent: Boolean(navigatorObject?.gpu),
|
|
106
|
+
webgpuOperational,
|
|
107
|
+
webgpuEvidenceProtocol: RUNTIME_EVIDENCE_PROTOCOL,
|
|
68
108
|
crossOriginIsolated: globalThis.crossOriginIsolated === true,
|
|
69
109
|
secureContext: globalThis.isSecureContext === true,
|
|
70
110
|
hardwareConcurrency: Number.isSafeInteger(navigatorObject?.hardwareConcurrency)
|
|
@@ -82,16 +122,308 @@ function normalizePositiveInteger(value, fallback, { maximum = Number.MAX_SAFE_I
|
|
|
82
122
|
return number;
|
|
83
123
|
}
|
|
84
124
|
|
|
125
|
+
function createEvidenceLogger(logger) {
|
|
126
|
+
let adapter = null;
|
|
127
|
+
let offload = null;
|
|
128
|
+
let invalid = false;
|
|
129
|
+
let completionCapture = null;
|
|
130
|
+
|
|
131
|
+
function same(left, right) {
|
|
132
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function observeCompletionLine(level, value) {
|
|
136
|
+
if (!completionCapture) return;
|
|
137
|
+
const line = String(value).replace(/\r$/u, "");
|
|
138
|
+
if (!completionCapture.started) {
|
|
139
|
+
if (level !== "warn" || !line.startsWith(PEG_NATIVE_OUTPUT_PREFIX)) return;
|
|
140
|
+
completionCapture.started = true;
|
|
141
|
+
completionCapture.lines.push(line.slice(PEG_NATIVE_OUTPUT_PREFIX.length));
|
|
142
|
+
} else if (level === "error" && line.trim() === PEG_NATIVE_FAILURE) {
|
|
143
|
+
completionCapture.complete = true;
|
|
144
|
+
return;
|
|
145
|
+
} else if (!completionCapture.complete) {
|
|
146
|
+
if (level !== "log") completionCapture.invalid = true;
|
|
147
|
+
completionCapture.lines.push(line);
|
|
148
|
+
}
|
|
149
|
+
completionCapture.characters += line.length + 1;
|
|
150
|
+
if (
|
|
151
|
+
completionCapture.characters > MAX_RECOVERED_COMPLETION_CHARACTERS
|
|
152
|
+
|| completionCapture.lines.length > MAX_RECOVERED_COMPLETION_LINES
|
|
153
|
+
) {
|
|
154
|
+
completionCapture.invalid = true;
|
|
155
|
+
completionCapture.lines.length = 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function observeLine(level, value) {
|
|
160
|
+
observeCompletionLine(level, value);
|
|
161
|
+
const line = String(value).trim();
|
|
162
|
+
if (!line) return;
|
|
163
|
+
const adapterMatch = line.match(WEBGPU_ADAPTER_PATTERN);
|
|
164
|
+
if (adapterMatch) {
|
|
165
|
+
const next = Object.freeze({
|
|
166
|
+
vendorId: Number(adapterMatch[1]),
|
|
167
|
+
vendor: adapterMatch[2],
|
|
168
|
+
architecture: adapterMatch[3],
|
|
169
|
+
deviceId: Number(adapterMatch[4]),
|
|
170
|
+
name: adapterMatch[5],
|
|
171
|
+
description: adapterMatch[6],
|
|
172
|
+
});
|
|
173
|
+
if (adapter && !same(adapter, next)) invalid = true;
|
|
174
|
+
else adapter = next;
|
|
175
|
+
}
|
|
176
|
+
const offloadMatch = line.match(GPU_OFFLOAD_PATTERN);
|
|
177
|
+
if (offloadMatch) {
|
|
178
|
+
const next = Object.freeze({
|
|
179
|
+
layers: Number(offloadMatch[1]),
|
|
180
|
+
totalLayers: Number(offloadMatch[2]),
|
|
181
|
+
});
|
|
182
|
+
if (offload && !same(offload, next)) invalid = true;
|
|
183
|
+
else offload = next;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function observe(level, args) {
|
|
188
|
+
for (const value of args) {
|
|
189
|
+
if (typeof value !== "string") continue;
|
|
190
|
+
for (const line of value.split(/\r?\n/u)) observeLine(level, line);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const wrapped = {};
|
|
195
|
+
for (const level of ["debug", "log", "warn", "error"]) {
|
|
196
|
+
wrapped[level] = (...args) => {
|
|
197
|
+
observe(level, args);
|
|
198
|
+
if (typeof logger?.[level] === "function") logger[level](...args);
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return Object.freeze({
|
|
203
|
+
logger: Object.freeze(wrapped),
|
|
204
|
+
beginCompletionCapture() {
|
|
205
|
+
if (completionCapture) {
|
|
206
|
+
throw runtimeFailure(
|
|
207
|
+
"ARCANE_AI_RUNTIME_BUSY",
|
|
208
|
+
"A Wllama completion capture is already active.",
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
const capture = {
|
|
212
|
+
started: false,
|
|
213
|
+
complete: false,
|
|
214
|
+
invalid: false,
|
|
215
|
+
characters: 0,
|
|
216
|
+
lines: [],
|
|
217
|
+
};
|
|
218
|
+
completionCapture = capture;
|
|
219
|
+
|
|
220
|
+
function release() {
|
|
221
|
+
if (completionCapture === capture) completionCapture = null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return Object.freeze({
|
|
225
|
+
recover(error, { aborted = false } = {}) {
|
|
226
|
+
release();
|
|
227
|
+
const stack = String(error?.stack ?? "");
|
|
228
|
+
if (
|
|
229
|
+
aborted
|
|
230
|
+
|| error?.name !== "Error"
|
|
231
|
+
|| error?.message !== "Invalid magic number"
|
|
232
|
+
|| !stack.includes("glueDeserialize")
|
|
233
|
+
|| !stack.includes("ProxyToWorker")
|
|
234
|
+
|| !capture.started
|
|
235
|
+
|| !capture.complete
|
|
236
|
+
|| capture.invalid
|
|
237
|
+
) return null;
|
|
238
|
+
|
|
239
|
+
const raw = capture.lines.join("\n");
|
|
240
|
+
if (!raw.startsWith(PEG_NATIVE_FINAL_PREFIX)) return null;
|
|
241
|
+
const content = raw.slice(PEG_NATIVE_FINAL_PREFIX.length);
|
|
242
|
+
if (!content.trim() || content.includes("<|")) return null;
|
|
243
|
+
return content;
|
|
244
|
+
},
|
|
245
|
+
release,
|
|
246
|
+
});
|
|
247
|
+
},
|
|
248
|
+
snapshot() {
|
|
249
|
+
return deepFreeze({ adapter, offload, invalid });
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function createStructuredStreamCapture() {
|
|
255
|
+
let content = "";
|
|
256
|
+
let invalid = false;
|
|
257
|
+
let sawContent = false;
|
|
258
|
+
let chunks = 0;
|
|
259
|
+
|
|
260
|
+
function observe(value) {
|
|
261
|
+
chunks += 1;
|
|
262
|
+
if (!value || typeof value !== "object" || !Array.isArray(value.choices)) {
|
|
263
|
+
invalid = true;
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (value.choices.length !== 1) {
|
|
267
|
+
invalid = true;
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const choice = value.choices[0];
|
|
271
|
+
const delta = choice?.delta;
|
|
272
|
+
if (
|
|
273
|
+
choice?.index !== 0
|
|
274
|
+
|| !delta
|
|
275
|
+
|| typeof delta !== "object"
|
|
276
|
+
|| Array.isArray(delta)
|
|
277
|
+
|| (delta.role !== undefined && delta.role !== "assistant")
|
|
278
|
+
|| (choice.finish_reason !== undefined && choice.finish_reason !== null)
|
|
279
|
+
|| delta.tool_calls !== undefined
|
|
280
|
+
|| delta.function_call !== undefined
|
|
281
|
+
|| delta.reasoning_content !== undefined
|
|
282
|
+
) {
|
|
283
|
+
invalid = true;
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (delta.content === undefined || delta.content === null) return;
|
|
287
|
+
if (typeof delta.content !== "string") {
|
|
288
|
+
invalid = true;
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
content += delta.content;
|
|
292
|
+
sawContent = true;
|
|
293
|
+
if (content.length > MAX_RECOVERED_COMPLETION_CHARACTERS) invalid = true;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return Object.freeze({
|
|
297
|
+
observe,
|
|
298
|
+
matches(value) {
|
|
299
|
+
return chunks > 0
|
|
300
|
+
&& !invalid
|
|
301
|
+
&& sawContent
|
|
302
|
+
&& content.length > 0
|
|
303
|
+
&& content === value;
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function validCounter(value) {
|
|
309
|
+
return Number.isSafeInteger(value) && value >= 0;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function verifyProjectedTelemetry(value) {
|
|
313
|
+
const worker = value?.worker;
|
|
314
|
+
if (
|
|
315
|
+
value?.protocol !== WEBGPU_EVIDENCE_PROTOCOL
|
|
316
|
+
|| worker?.protocol !== WEBGPU_EVIDENCE_PROTOCOL
|
|
317
|
+
|| !validCounter(worker.bufferCount)
|
|
318
|
+
|| !validCounter(worker.bufferBytes)
|
|
319
|
+
|| !validCounter(worker.queueSubmissions)
|
|
320
|
+
|| !validCounter(worker.commandBuffers)
|
|
321
|
+
|| !validCounter(worker.queueFenceRequests)
|
|
322
|
+
|| !validCounter(worker.queueFenceCompletions)
|
|
323
|
+
|| worker.queueFenceCompletions > worker.queueFenceRequests
|
|
324
|
+
|| worker.invalid === true
|
|
325
|
+
) {
|
|
326
|
+
throw runtimeFailure(
|
|
327
|
+
"ARCANE_AI_WEBGPU_EVIDENCE_INVALID",
|
|
328
|
+
"The projected Wllama WebGPU evidence was missing or invalid.",
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
return value;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function adapterEvidenceConflicts(workerAdapter, loggedAdapter) {
|
|
335
|
+
if (!workerAdapter || !loggedAdapter) return false;
|
|
336
|
+
function loggedText(value) {
|
|
337
|
+
return typeof value === "string" ? value.slice(0, 256) : "";
|
|
338
|
+
}
|
|
339
|
+
return workerAdapter.vendor !== loggedText(loggedAdapter.vendor)
|
|
340
|
+
|| workerAdapter.architecture !== loggedText(loggedAdapter.architecture)
|
|
341
|
+
|| workerAdapter.name !== loggedText(loggedAdapter.name)
|
|
342
|
+
|| workerAdapter.description !== loggedText(loggedAdapter.description);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function admittedLoadEvidence(logs, projected) {
|
|
346
|
+
const worker = projected.worker;
|
|
347
|
+
const adapter = worker.adapter;
|
|
348
|
+
const offload = logs.offload;
|
|
349
|
+
const failures = [];
|
|
350
|
+
if (logs.invalid) failures.push("conflicting-log-evidence");
|
|
351
|
+
if (!adapter) failures.push("adapter-selection");
|
|
352
|
+
else if (adapterEvidenceConflicts(adapter, logs.adapter)) failures.push("adapter-log-conflict");
|
|
353
|
+
if (!offload) failures.push("offload-log");
|
|
354
|
+
else if (
|
|
355
|
+
!Number.isSafeInteger(offload.layers)
|
|
356
|
+
|| !Number.isSafeInteger(offload.totalLayers)
|
|
357
|
+
|| offload.totalLayers < 1
|
|
358
|
+
) failures.push("offload-shape");
|
|
359
|
+
else if (offload.layers !== offload.totalLayers) {
|
|
360
|
+
failures.push(`full-offload(${offload.layers}/${offload.totalLayers})`);
|
|
361
|
+
}
|
|
362
|
+
if (worker.bufferCount < 1) failures.push(`buffer-count(${worker.bufferCount})`);
|
|
363
|
+
if (worker.bufferBytes < 1) failures.push(`buffer-bytes(${worker.bufferBytes})`);
|
|
364
|
+
if (worker.queueSubmissions < 1) failures.push(`queue-submissions(${worker.queueSubmissions})`);
|
|
365
|
+
if (worker.commandBuffers < 1) failures.push(`command-buffers(${worker.commandBuffers})`);
|
|
366
|
+
if (worker.queueFenceRequests < 1) failures.push(`fence-requests(${worker.queueFenceRequests})`);
|
|
367
|
+
if (worker.queueFenceCompletions < worker.queueFenceRequests) {
|
|
368
|
+
failures.push(`fence-completions(${worker.queueFenceCompletions}/${worker.queueFenceRequests})`);
|
|
369
|
+
}
|
|
370
|
+
if (failures.length > 0) {
|
|
371
|
+
throw runtimeFailure(
|
|
372
|
+
"ARCANE_AI_WEBGPU_REQUIRED",
|
|
373
|
+
`Wllama WebGPU admission failed: ${failures.join(", ")}.`,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
return deepFreeze({
|
|
377
|
+
observed: true,
|
|
378
|
+
adapter,
|
|
379
|
+
offload: { ...offload, allReportedModelLayers: true },
|
|
380
|
+
buffers: { count: worker.bufferCount, descriptorBytes: worker.bufferBytes },
|
|
381
|
+
queue: {
|
|
382
|
+
submissions: worker.queueSubmissions,
|
|
383
|
+
commandBuffers: worker.commandBuffers,
|
|
384
|
+
fenceRequests: worker.queueFenceRequests,
|
|
385
|
+
fenceCompletions: worker.queueFenceCompletions,
|
|
386
|
+
},
|
|
387
|
+
cpuUnusedClaimed: false,
|
|
388
|
+
gpuOnlyClaimed: false,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function initialEvidence() {
|
|
393
|
+
return deepFreeze({
|
|
394
|
+
protocol: RUNTIME_EVIDENCE_PROTOCOL,
|
|
395
|
+
state: "unloaded",
|
|
396
|
+
webgpu: {
|
|
397
|
+
observed: false,
|
|
398
|
+
apiPresent: Boolean(globalThis.navigator?.gpu),
|
|
399
|
+
},
|
|
400
|
+
cancellation: null,
|
|
401
|
+
cleanup: null,
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
85
405
|
/**
|
|
86
|
-
* Creates one packaged Wllama session. This factory has no
|
|
87
|
-
* side effects until load() is called. Runtime URLs are
|
|
88
|
-
*
|
|
406
|
+
* Creates one packaged, WebGPU-required Wllama session. This factory has no
|
|
407
|
+
* network or browser side effects until load() is called. Runtime URLs are
|
|
408
|
+
* fixed relative to this module for npm and materialized /arcane/sdk trees.
|
|
89
409
|
*/
|
|
90
410
|
export function createPackagedWllamaRuntime({ logger = console } = {}) {
|
|
91
411
|
let engine = null;
|
|
92
412
|
let pending = null;
|
|
413
|
+
let inferenceActive = false;
|
|
414
|
+
let evidenceState = initialEvidence();
|
|
93
415
|
const trackedOperations = new Set();
|
|
94
416
|
const sessionExitPromises = new WeakMap();
|
|
417
|
+
const sessionObservers = new WeakMap();
|
|
418
|
+
|
|
419
|
+
function publishEvidence(update) {
|
|
420
|
+
evidenceState = deepFreeze({
|
|
421
|
+
...evidenceState,
|
|
422
|
+
...update,
|
|
423
|
+
protocol: RUNTIME_EVIDENCE_PROTOCOL,
|
|
424
|
+
});
|
|
425
|
+
return evidenceState;
|
|
426
|
+
}
|
|
95
427
|
|
|
96
428
|
function cancellationError(reason, fallback = "The Wllama operation was cancelled.") {
|
|
97
429
|
return reason instanceof Error ? reason : new Error(reason ? String(reason) : fallback);
|
|
@@ -104,11 +436,11 @@ export function createPackagedWllamaRuntime({ logger = console } = {}) {
|
|
|
104
436
|
const cancellation = new Promise((_, reject) => {
|
|
105
437
|
rejectCancellation = reject;
|
|
106
438
|
});
|
|
107
|
-
let
|
|
439
|
+
let locallySuppressed = false;
|
|
108
440
|
const record = {
|
|
109
441
|
cancel(reason) {
|
|
110
|
-
if (
|
|
111
|
-
|
|
442
|
+
if (locallySuppressed) return false;
|
|
443
|
+
locallySuppressed = true;
|
|
112
444
|
rejectCancellation(cancellationError(reason));
|
|
113
445
|
return true;
|
|
114
446
|
},
|
|
@@ -118,102 +450,48 @@ export function createPackagedWllamaRuntime({ logger = console } = {}) {
|
|
|
118
450
|
trackedOperations.delete(record);
|
|
119
451
|
});
|
|
120
452
|
result.catch(() => undefined);
|
|
121
|
-
return Object.freeze({
|
|
453
|
+
return Object.freeze({
|
|
454
|
+
raw,
|
|
455
|
+
result,
|
|
456
|
+
cancel: record.cancel,
|
|
457
|
+
locallySuppressed: () => locallySuppressed,
|
|
458
|
+
});
|
|
122
459
|
}
|
|
123
460
|
|
|
124
461
|
function cancelTrackedOperations(reason) {
|
|
125
462
|
for (const operation of [...trackedOperations]) operation.cancel(reason);
|
|
126
463
|
}
|
|
127
464
|
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
if (!
|
|
131
|
-
throw
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
function guardLoadingSession(session) {
|
|
137
|
-
const sessionDescriptor = requireConfigurableDataProperty(session, "proxy", "proxy");
|
|
138
|
-
let proxy = sessionDescriptor.value;
|
|
139
|
-
let cancelled = null;
|
|
140
|
-
const workerGuards = new Map();
|
|
141
|
-
|
|
142
|
-
function guardProxy(nextProxy) {
|
|
143
|
-
if (!nextProxy || workerGuards.has(nextProxy)) return;
|
|
144
|
-
const descriptor = requireConfigurableDataProperty(nextProxy, "worker", "proxy worker");
|
|
145
|
-
let worker = descriptor.value;
|
|
146
|
-
Object.defineProperty(nextProxy, "worker", {
|
|
147
|
-
enumerable: descriptor.enumerable,
|
|
148
|
-
configurable: true,
|
|
149
|
-
get: () => worker,
|
|
150
|
-
set(value) {
|
|
151
|
-
if (value && cancelled) {
|
|
152
|
-
value.terminate?.();
|
|
153
|
-
throw cancelled;
|
|
154
|
-
}
|
|
155
|
-
worker = value;
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
workerGuards.set(nextProxy, () => {
|
|
159
|
-
Object.defineProperty(nextProxy, "worker", { ...descriptor, value: worker });
|
|
160
|
-
});
|
|
161
|
-
if (worker && cancelled) {
|
|
162
|
-
worker.terminate?.();
|
|
163
|
-
throw cancelled;
|
|
164
|
-
}
|
|
465
|
+
function recordCleanup(snapshot) {
|
|
466
|
+
let cleanup = snapshot?.cleanup ?? null;
|
|
467
|
+
if (!["worker-terminated", "no-worker-observed-at-exit"].includes(cleanup?.kind)) {
|
|
468
|
+
throw runtimeFailure(
|
|
469
|
+
"ARCANE_AI_WORKER_TERMINATION_UNCONFIRMED",
|
|
470
|
+
"Wllama cleanup did not confirm Worker termination.",
|
|
471
|
+
);
|
|
165
472
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (value && cancelled) {
|
|
174
|
-
value.worker?.terminate?.();
|
|
175
|
-
throw cancelled;
|
|
176
|
-
}
|
|
177
|
-
proxy = value;
|
|
178
|
-
if (value) guardProxy(value);
|
|
179
|
-
},
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
return Object.freeze({
|
|
183
|
-
cancel(reason) {
|
|
184
|
-
cancelled ||= cancellationError(reason, "The Wllama model load was cancelled.");
|
|
185
|
-
const current = proxy;
|
|
186
|
-
current?.worker?.terminate?.();
|
|
187
|
-
try {
|
|
188
|
-
current?.abort?.(cancelled.message, "");
|
|
189
|
-
} catch {
|
|
190
|
-
// The tracked operation gate remains the stable cancellation result.
|
|
191
|
-
}
|
|
192
|
-
return cancelled;
|
|
193
|
-
},
|
|
194
|
-
restore() {
|
|
195
|
-
for (const restoreWorker of workerGuards.values()) restoreWorker();
|
|
196
|
-
workerGuards.clear();
|
|
197
|
-
Object.defineProperty(session, "proxy", { ...sessionDescriptor, value: proxy });
|
|
473
|
+
publishEvidence({
|
|
474
|
+
state: "unloaded",
|
|
475
|
+
webgpu: {
|
|
476
|
+
...evidenceState.webgpu,
|
|
477
|
+
observed: false,
|
|
478
|
+
lastObservedOperational: evidenceState.webgpu?.observed === true
|
|
479
|
+
|| evidenceState.webgpu?.lastObservedOperational === true,
|
|
198
480
|
},
|
|
481
|
+
cleanup,
|
|
199
482
|
});
|
|
483
|
+
return cleanup;
|
|
200
484
|
}
|
|
201
485
|
|
|
202
|
-
function exitSession(session
|
|
203
|
-
if (!session) return Promise.resolve(
|
|
486
|
+
function exitSession(session) {
|
|
487
|
+
if (!session) return Promise.resolve(null);
|
|
204
488
|
let exitPromise = sessionExitPromises.get(session);
|
|
205
489
|
if (!exitPromise) {
|
|
206
|
-
const attempt = Promise.resolve().then(() => {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
// Public session.exit() still owns Worker termination.
|
|
211
|
-
}
|
|
212
|
-
return session.exit();
|
|
213
|
-
}).then(() => true);
|
|
490
|
+
const attempt = Promise.resolve().then(() => session.arcaneTerminate()).then((snapshot) => {
|
|
491
|
+
recordCleanup(snapshot);
|
|
492
|
+
return snapshot;
|
|
493
|
+
});
|
|
214
494
|
exitPromise = attempt.catch((error) => {
|
|
215
|
-
// A failed cleanup attempt is not proof that this session is closed.
|
|
216
|
-
// Evict only this attempt so a later exit() can retry the same handle.
|
|
217
495
|
if (sessionExitPromises.get(session) === exitPromise) {
|
|
218
496
|
sessionExitPromises.delete(session);
|
|
219
497
|
}
|
|
@@ -225,20 +503,31 @@ export function createPackagedWllamaRuntime({ logger = console } = {}) {
|
|
|
225
503
|
}
|
|
226
504
|
|
|
227
505
|
function newEngine() {
|
|
506
|
+
const observer = createEvidenceLogger(logger);
|
|
228
507
|
const next = new Wllama({ default: WASM_URL }, {
|
|
229
|
-
logger,
|
|
508
|
+
logger: observer.logger,
|
|
230
509
|
allowOffline: true,
|
|
231
510
|
});
|
|
232
511
|
next.setCompat(null);
|
|
512
|
+
for (const method of ["arcaneLoadModel", "arcaneTelemetry", "arcaneTerminate"]) {
|
|
513
|
+
if (typeof next[method] !== "function") {
|
|
514
|
+
throw new Error(`The packaged Wllama projection is missing public ${method}().`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
233
517
|
const resources = next.getWorkerResources();
|
|
234
518
|
if (resources.compat !== false || resources.wasmPath !== WASM_URL || resources.jsPath) {
|
|
235
519
|
throw new Error("The packaged Wllama resource projection was not exact.");
|
|
236
520
|
}
|
|
521
|
+
sessionObservers.set(next, observer);
|
|
237
522
|
return next;
|
|
238
523
|
}
|
|
239
524
|
|
|
240
525
|
function capabilities() {
|
|
241
|
-
return runtimeCapabilitySnapshot();
|
|
526
|
+
return runtimeCapabilitySnapshot(evidenceState);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function evidence() {
|
|
530
|
+
return evidenceState;
|
|
242
531
|
}
|
|
243
532
|
|
|
244
533
|
async function load(files, options = {}) {
|
|
@@ -248,21 +537,29 @@ export function createPackagedWllamaRuntime({ logger = console } = {}) {
|
|
|
248
537
|
if (typeof globalThis.WebAssembly !== "object") {
|
|
249
538
|
throw new Error("WebAssembly is unavailable in this browser.");
|
|
250
539
|
}
|
|
540
|
+
if (!globalThis.navigator?.gpu) {
|
|
541
|
+
throw runtimeFailure(
|
|
542
|
+
"ARCANE_AI_WEBGPU_REQUIRED",
|
|
543
|
+
"A WebGPU API is required, but navigator presence alone will not establish operational execution.",
|
|
544
|
+
);
|
|
545
|
+
}
|
|
251
546
|
if (engine || pending) {
|
|
252
547
|
throw new Error("The packaged Wllama runtime is already loaded or loading.");
|
|
253
548
|
}
|
|
254
549
|
|
|
255
|
-
// Wllama defaults to a CDN compatibility runtime. Arcane never admits it.
|
|
256
550
|
const next = newEngine();
|
|
257
|
-
|
|
258
551
|
const threads = normalizePositiveInteger(options.threads, 1, { maximum: 64 });
|
|
259
552
|
const contextTokens = normalizePositiveInteger(options.contextTokens, 4_096, {
|
|
260
553
|
maximum: 1_048_576,
|
|
261
554
|
});
|
|
555
|
+
if (options.gpuLayers !== undefined && options.gpuLayers !== FULL_GPU_LAYERS) {
|
|
556
|
+
throw new RangeError(`WebGPU-required Wllama must request exactly ${FULL_GPU_LAYERS} GPU layers.`);
|
|
557
|
+
}
|
|
558
|
+
const gpuLayers = FULL_GPU_LAYERS;
|
|
262
559
|
const loadOptions = {
|
|
263
560
|
n_threads: threads,
|
|
264
561
|
n_ctx: contextTokens,
|
|
265
|
-
n_gpu_layers:
|
|
562
|
+
n_gpu_layers: gpuLayers,
|
|
266
563
|
};
|
|
267
564
|
if (options.batchTokens !== undefined) {
|
|
268
565
|
loadOptions.n_batch = normalizePositiveInteger(options.batchTokens, 512, {
|
|
@@ -275,34 +572,56 @@ export function createPackagedWllamaRuntime({ logger = console } = {}) {
|
|
|
275
572
|
});
|
|
276
573
|
}
|
|
277
574
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
575
|
+
publishEvidence({
|
|
576
|
+
state: "loading",
|
|
577
|
+
webgpu: { observed: false, apiPresent: true },
|
|
578
|
+
cancellation: null,
|
|
579
|
+
cleanup: null,
|
|
580
|
+
});
|
|
581
|
+
const loadController = new AbortController();
|
|
582
|
+
const loadOperation = Promise.resolve().then(() => (
|
|
583
|
+
next.arcaneLoadModel(files, loadOptions, loadController.signal)
|
|
584
|
+
));
|
|
585
|
+
loadOperation.catch(() => undefined);
|
|
281
586
|
const cancel = (reason) => {
|
|
282
|
-
const error =
|
|
283
|
-
|
|
587
|
+
const error = cancellationError(reason, "The Wllama model load was cancelled.");
|
|
588
|
+
loadController.abort(error);
|
|
284
589
|
};
|
|
285
|
-
pending = Object.freeze({
|
|
286
|
-
engine: next,
|
|
287
|
-
cancel,
|
|
288
|
-
});
|
|
590
|
+
pending = Object.freeze({ engine: next, cancel });
|
|
289
591
|
const signal = options.signal ?? null;
|
|
290
592
|
const onAbort = () => cancel(signal.reason);
|
|
291
593
|
if (signal?.aborted) onAbort();
|
|
292
594
|
else signal?.addEventListener?.("abort", onAbort, { once: true });
|
|
293
595
|
try {
|
|
294
|
-
await
|
|
596
|
+
await loadOperation;
|
|
295
597
|
if (pending?.engine !== next) throw new Error("Wllama load was cancelled.");
|
|
598
|
+
if (typeof next.isModelLoaded !== "function" || next.isModelLoaded() !== true) {
|
|
599
|
+
throw runtimeFailure(
|
|
600
|
+
"ARCANE_AI_LOAD_FAILED",
|
|
601
|
+
"Wllama did not confirm a successfully loaded model.",
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
const projected = verifyProjectedTelemetry(await next.arcaneTelemetry());
|
|
605
|
+
const webgpu = admittedLoadEvidence(sessionObservers.get(next).snapshot(), projected);
|
|
296
606
|
pending = null;
|
|
297
607
|
engine = next;
|
|
608
|
+
publishEvidence({ state: "ready", webgpu, cancellation: null, cleanup: null });
|
|
298
609
|
} catch (error) {
|
|
610
|
+
let cleanupFailure = null;
|
|
299
611
|
try {
|
|
300
|
-
await exitSession(next
|
|
612
|
+
await exitSession(next);
|
|
301
613
|
if (pending?.engine === next) pending = null;
|
|
302
|
-
} catch {
|
|
303
|
-
|
|
304
|
-
|
|
614
|
+
} catch (cleanupError) {
|
|
615
|
+
cleanupFailure = cleanupError?.code === "ARCANE_AI_WORKER_TERMINATION_UNCONFIRMED"
|
|
616
|
+
? cleanupError
|
|
617
|
+
: runtimeFailure(
|
|
618
|
+
"ARCANE_AI_WORKER_TERMINATION_UNCONFIRMED",
|
|
619
|
+
"Wllama model load failed and Worker termination could not be proved.",
|
|
620
|
+
cleanupError,
|
|
621
|
+
);
|
|
622
|
+
// Preserve the pending handle if Worker termination could not be proved.
|
|
305
623
|
}
|
|
624
|
+
if (cleanupFailure) throw cleanupFailure;
|
|
306
625
|
throw error;
|
|
307
626
|
} finally {
|
|
308
627
|
signal?.removeEventListener?.("abort", onAbort);
|
|
@@ -312,32 +631,261 @@ export function createPackagedWllamaRuntime({ logger = console } = {}) {
|
|
|
312
631
|
loaded: true,
|
|
313
632
|
contextTokens,
|
|
314
633
|
threads,
|
|
634
|
+
gpuLayers,
|
|
635
|
+
evidence: evidenceState,
|
|
315
636
|
metadata: engine.getModelMetadata?.() ?? null,
|
|
316
637
|
});
|
|
317
638
|
}
|
|
318
639
|
|
|
319
640
|
function assertLoaded() {
|
|
320
|
-
if (!engine?.isModelLoaded?.()
|
|
641
|
+
if (!engine?.isModelLoaded?.() || evidenceState.state !== "ready") {
|
|
642
|
+
throw new Error("The packaged Wllama model is not loaded with admitted WebGPU evidence.");
|
|
643
|
+
}
|
|
321
644
|
return engine;
|
|
322
645
|
}
|
|
323
646
|
|
|
324
|
-
async function
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
)
|
|
647
|
+
async function terminateUnacknowledgedCancellation(session, reason) {
|
|
648
|
+
let snapshot;
|
|
649
|
+
try {
|
|
650
|
+
snapshot = await exitSession(session);
|
|
651
|
+
} catch (error) {
|
|
652
|
+
throw runtimeFailure(
|
|
653
|
+
"ARCANE_AI_WORKER_TERMINATION_UNCONFIRMED",
|
|
654
|
+
"Wllama cancellation was not acknowledged and Worker termination could not be proved.",
|
|
655
|
+
error,
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
if (engine === session) engine = null;
|
|
659
|
+
publishEvidence({
|
|
660
|
+
cancellation: deepFreeze({
|
|
661
|
+
deliverySuppressed: true,
|
|
662
|
+
upstream: {
|
|
663
|
+
kind: "worker-terminated",
|
|
664
|
+
cancellationAcknowledged: false,
|
|
665
|
+
cleanup: snapshot.cleanup,
|
|
666
|
+
},
|
|
667
|
+
nativeUnloadClaimed: false,
|
|
668
|
+
physicalVramReclamationClaimed: false,
|
|
669
|
+
}),
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async function recordInference(
|
|
674
|
+
session,
|
|
675
|
+
before,
|
|
676
|
+
{ aborted, requireCancellationAcknowledgement = false },
|
|
677
|
+
) {
|
|
678
|
+
let after;
|
|
679
|
+
try {
|
|
680
|
+
after = verifyProjectedTelemetry(await session.arcaneTelemetry());
|
|
681
|
+
} catch (error) {
|
|
682
|
+
if (!aborted && !requireCancellationAcknowledgement) throw error;
|
|
683
|
+
await terminateUnacknowledgedCancellation(session, error);
|
|
684
|
+
if (requireCancellationAcknowledgement) {
|
|
685
|
+
throw runtimeFailure(
|
|
686
|
+
"ARCANE_AI_COMPLETION_RECOVERY_UNCONFIRMED",
|
|
687
|
+
"Wllama completion recovery could not prove request settlement.",
|
|
688
|
+
error,
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
const previousSequence = before?.cancellation?.sequence ?? 0;
|
|
694
|
+
const cancellation = after.cancellation;
|
|
695
|
+
const cancellationAcknowledged = cancellation?.sequence > previousSequence
|
|
696
|
+
&& cancellation.responseName === "cncl_res"
|
|
697
|
+
&& cancellation.acknowledged === true
|
|
698
|
+
&& cancellation.failed === false;
|
|
699
|
+
if (aborted) {
|
|
700
|
+
if (cancellationAcknowledged) {
|
|
701
|
+
publishEvidence({
|
|
702
|
+
cancellation: deepFreeze({
|
|
703
|
+
deliverySuppressed: true,
|
|
704
|
+
upstream: {
|
|
705
|
+
kind: "llama-request-cancel-acknowledged",
|
|
706
|
+
sequence: cancellation.sequence,
|
|
707
|
+
requestId: cancellation.requestId,
|
|
708
|
+
responseName: cancellation.responseName,
|
|
709
|
+
acknowledged: true,
|
|
710
|
+
failed: false,
|
|
711
|
+
},
|
|
712
|
+
immediateGpuKernelPreemptionClaimed: false,
|
|
713
|
+
}),
|
|
714
|
+
});
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
await terminateUnacknowledgedCancellation(
|
|
718
|
+
session,
|
|
719
|
+
"Wllama cancellation was not acknowledged.",
|
|
720
|
+
);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
if (requireCancellationAcknowledgement && !cancellationAcknowledged) {
|
|
724
|
+
await terminateUnacknowledgedCancellation(
|
|
725
|
+
session,
|
|
726
|
+
"Wllama completion recovery could not prove cancellation acknowledgement.",
|
|
727
|
+
);
|
|
728
|
+
throw runtimeFailure(
|
|
729
|
+
"ARCANE_AI_COMPLETION_RECOVERY_UNCONFIRMED",
|
|
730
|
+
"Wllama completion recovery could not prove request settlement.",
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const submissions = after.worker.queueSubmissions - before.worker.queueSubmissions;
|
|
735
|
+
const commandBuffers = after.worker.commandBuffers - before.worker.commandBuffers;
|
|
736
|
+
const fenceRequests = after.worker.queueFenceRequests - before.worker.queueFenceRequests;
|
|
737
|
+
const fenceCompletions = after.worker.queueFenceCompletions - before.worker.queueFenceCompletions;
|
|
738
|
+
if (
|
|
739
|
+
submissions < 1
|
|
740
|
+
|| commandBuffers < 1
|
|
741
|
+
|| fenceRequests < 1
|
|
742
|
+
|| fenceCompletions < fenceRequests
|
|
743
|
+
) {
|
|
744
|
+
await exitSession(session);
|
|
745
|
+
if (engine === session) engine = null;
|
|
746
|
+
throw runtimeFailure(
|
|
747
|
+
"ARCANE_AI_WEBGPU_REQUIRED",
|
|
748
|
+
"Inference completed without positive, settled WebGPU queue evidence.",
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
publishEvidence({
|
|
752
|
+
webgpu: deepFreeze({
|
|
753
|
+
...evidenceState.webgpu,
|
|
754
|
+
queue: {
|
|
755
|
+
submissions: after.worker.queueSubmissions,
|
|
756
|
+
commandBuffers: after.worker.commandBuffers,
|
|
757
|
+
fenceRequests: after.worker.queueFenceRequests,
|
|
758
|
+
fenceCompletions: after.worker.queueFenceCompletions,
|
|
759
|
+
},
|
|
760
|
+
lastInference: { submissions, commandBuffers, fenceRequests, fenceCompletions },
|
|
761
|
+
}),
|
|
762
|
+
cancellation: requireCancellationAcknowledgement
|
|
763
|
+
? deepFreeze({
|
|
764
|
+
deliverySuppressed: false,
|
|
765
|
+
recovery: "peg-native-final-output",
|
|
766
|
+
upstream: {
|
|
767
|
+
kind: "llama-request-cancel-acknowledged",
|
|
768
|
+
sequence: cancellation.sequence,
|
|
769
|
+
requestId: cancellation.requestId,
|
|
770
|
+
responseName: cancellation.responseName,
|
|
771
|
+
acknowledged: true,
|
|
772
|
+
failed: false,
|
|
773
|
+
},
|
|
774
|
+
immediateGpuKernelPreemptionClaimed: false,
|
|
775
|
+
})
|
|
776
|
+
: null,
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
async function invalidateFatalSession(session, error) {
|
|
781
|
+
try {
|
|
782
|
+
await exitSession(session);
|
|
783
|
+
} catch (cleanupError) {
|
|
784
|
+
throw cleanupError?.code === "ARCANE_AI_WORKER_TERMINATION_UNCONFIRMED"
|
|
785
|
+
? cleanupError
|
|
786
|
+
: runtimeFailure(
|
|
787
|
+
"ARCANE_AI_WORKER_TERMINATION_UNCONFIRMED",
|
|
788
|
+
"Wllama failed and Worker termination could not be proved.",
|
|
789
|
+
cleanupError,
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
if (engine === session) engine = null;
|
|
793
|
+
publishEvidence({
|
|
794
|
+
state: "error",
|
|
795
|
+
webgpu: {
|
|
796
|
+
...evidenceState.webgpu,
|
|
797
|
+
observed: false,
|
|
798
|
+
lastObservedOperational: evidenceState.webgpu?.lastObservedOperational === true,
|
|
799
|
+
},
|
|
800
|
+
failure: deepFreeze({
|
|
801
|
+
code: typeof error?.code === "string" ? error.code : "ARCANE_AI_RUNTIME_FAILED",
|
|
802
|
+
}),
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
async function inference(options, onData = null) {
|
|
807
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
808
|
+
throw new TypeError("Wllama inference options must be an object.");
|
|
809
|
+
}
|
|
810
|
+
if (Object.hasOwn(options, "signal")) {
|
|
811
|
+
throw new TypeError("Pinned Wllama 3.6.0 accepts abortSignal, not signal.");
|
|
812
|
+
}
|
|
813
|
+
if (inferenceActive) {
|
|
814
|
+
throw runtimeFailure(
|
|
815
|
+
"ARCANE_AI_RUNTIME_BUSY",
|
|
816
|
+
"The packaged Wllama runtime admits one inference at a time.",
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
inferenceActive = true;
|
|
820
|
+
try {
|
|
821
|
+
const session = assertLoaded();
|
|
822
|
+
const observer = sessionObservers.get(session);
|
|
823
|
+
const streamCapture = createStructuredStreamCapture();
|
|
824
|
+
let capture = null;
|
|
825
|
+
let operation = null;
|
|
826
|
+
let before = null;
|
|
827
|
+
try {
|
|
828
|
+
before = verifyProjectedTelemetry(await session.arcaneTelemetry());
|
|
829
|
+
const abortSignal = options.abortSignal ?? null;
|
|
830
|
+
capture = observer.beginCompletionCapture();
|
|
831
|
+
const deliver = onData
|
|
832
|
+
? (chunk) => {
|
|
833
|
+
streamCapture.observe(chunk);
|
|
834
|
+
onData(chunk);
|
|
835
|
+
}
|
|
836
|
+
: null;
|
|
837
|
+
operation = trackOperation(Promise.resolve().then(() => session.createChatCompletion({
|
|
838
|
+
...options,
|
|
839
|
+
stream: Boolean(onData),
|
|
840
|
+
...(deliver ? { onData: deliver } : {}),
|
|
841
|
+
abortSignal,
|
|
842
|
+
})));
|
|
843
|
+
const result = await operation.result;
|
|
844
|
+
capture.release();
|
|
845
|
+
await recordInference(session, before, { aborted: false });
|
|
846
|
+
return result;
|
|
847
|
+
} catch (error) {
|
|
848
|
+
const abortSignal = options.abortSignal ?? null;
|
|
849
|
+
const locallySuppressed = operation?.locallySuppressed() === true;
|
|
850
|
+
const aborted = operation !== null
|
|
851
|
+
&& !locallySuppressed
|
|
852
|
+
&& (abortSignal?.aborted || error?.name === "AbortError");
|
|
853
|
+
const recoveredContent = locallySuppressed
|
|
854
|
+
? null
|
|
855
|
+
: capture?.recover(error, { aborted }) ?? null;
|
|
856
|
+
if (onData && recoveredContent !== null && streamCapture.matches(recoveredContent)) {
|
|
857
|
+
// Wllama has already awaited cancelRequest() before rejecting here.
|
|
858
|
+
// The streamed text is exact, but the native stop reason is unknown.
|
|
859
|
+
await recordInference(session, before, {
|
|
860
|
+
aborted: false,
|
|
861
|
+
requireCancellationAcknowledgement: true,
|
|
862
|
+
});
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
if (aborted) {
|
|
866
|
+
await operation.raw.catch(() => undefined);
|
|
867
|
+
await recordInference(session, before, { aborted: true });
|
|
868
|
+
} else if (operation === null || !locallySuppressed) {
|
|
869
|
+
await invalidateFatalSession(session, error);
|
|
870
|
+
}
|
|
871
|
+
throw error;
|
|
872
|
+
} finally {
|
|
873
|
+
capture?.release();
|
|
874
|
+
}
|
|
875
|
+
} finally {
|
|
876
|
+
inferenceActive = false;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function chat(options) {
|
|
881
|
+
return inference({ ...options, stream: false });
|
|
329
882
|
}
|
|
330
883
|
|
|
331
|
-
|
|
884
|
+
function stream(options, onData) {
|
|
332
885
|
if (typeof onData !== "function") {
|
|
333
886
|
throw new TypeError("Wllama stream() requires an onData callback.");
|
|
334
887
|
}
|
|
335
|
-
|
|
336
|
-
// abortSignal path cancels the llama.cpp request in a finally block.
|
|
337
|
-
const session = assertLoaded();
|
|
338
|
-
return trackOperation(
|
|
339
|
-
Promise.resolve().then(() => session.createChatCompletion({ ...options, stream: true, onData })),
|
|
340
|
-
).result;
|
|
888
|
+
return inference({ ...options, stream: true }, onData);
|
|
341
889
|
}
|
|
342
890
|
|
|
343
891
|
async function exit() {
|
|
@@ -348,10 +896,7 @@ export function createPackagedWllamaRuntime({ logger = console } = {}) {
|
|
|
348
896
|
cancelTrackedOperations(reason);
|
|
349
897
|
const sessions = new Set([current, loading?.engine].filter(Boolean));
|
|
350
898
|
if (!sessions.size) return false;
|
|
351
|
-
await Promise.all([...sessions].map((session) => exitSession(session
|
|
352
|
-
// Retain ownership through cleanup failure. Clear only the exact handles
|
|
353
|
-
// whose exit attempts completed successfully; replacement sessions, if
|
|
354
|
-
// any, remain owned by the runtime.
|
|
899
|
+
await Promise.all([...sessions].map((session) => exitSession(session)));
|
|
355
900
|
if (engine === current) engine = null;
|
|
356
901
|
if (pending === loading) pending = null;
|
|
357
902
|
return true;
|
|
@@ -363,14 +908,13 @@ export function createPackagedWllamaRuntime({ logger = console } = {}) {
|
|
|
363
908
|
throw new TypeError("Wllama probe args must be an array of strings.");
|
|
364
909
|
}
|
|
365
910
|
const temporary = newEngine();
|
|
366
|
-
// testBackendOps owns and closes its temporary worker. It requires browser
|
|
367
|
-
// multithreading, but that is a probe capability—not model admission.
|
|
368
911
|
const result = await temporary.testBackendOps(args);
|
|
369
912
|
return Object.freeze({
|
|
370
913
|
...result,
|
|
371
914
|
args: Object.freeze([...args]),
|
|
372
915
|
origin: globalThis.location?.origin ?? null,
|
|
373
916
|
capabilities: capabilities(),
|
|
917
|
+
evidence: evidenceState,
|
|
374
918
|
runtime: BROWSER_WASM_RUNTIME_AUTHORITY,
|
|
375
919
|
});
|
|
376
920
|
}
|
|
@@ -379,12 +923,13 @@ export function createPackagedWllamaRuntime({ logger = console } = {}) {
|
|
|
379
923
|
authority: BROWSER_WASM_RUNTIME_AUTHORITY,
|
|
380
924
|
runtimeAssets: BROWSER_WASM_RUNTIME_AUTHORITY.runtimeAssets,
|
|
381
925
|
capabilities,
|
|
926
|
+
evidence,
|
|
382
927
|
load,
|
|
383
928
|
chat,
|
|
384
929
|
stream,
|
|
385
930
|
probe,
|
|
386
931
|
exit,
|
|
387
|
-
isLoaded: () => Boolean(engine?.isModelLoaded?.()),
|
|
932
|
+
isLoaded: () => Boolean(engine?.isModelLoaded?.()) && evidenceState.state === "ready",
|
|
388
933
|
isLoading: () => Boolean(pending),
|
|
389
934
|
});
|
|
390
935
|
}
|