arcane-os 0.71.1 → 0.71.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/browser-runtime/ai/browser-decisions.mjs +90 -13
- package/browser-runtime/ai/kokoro-complete-input.mjs +116 -0
- package/browser-runtime/ai/speech-worker-runtime.mjs +62 -45
- package/docs/reference/ai/browser-decisions.md +26 -11
- package/docs/reference/ai/browser-speech.md +26 -5
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.71.2
|
|
4
|
+
|
|
5
|
+
- Preserve complete browser Kokoro synthesis input through the selected runtime's
|
|
6
|
+
phonemization and tokenizer. Process every token in ordered model-capacity
|
|
7
|
+
segments and retain all resulting audio, with one model owner and request-local
|
|
8
|
+
cancellation. Keep short-input behavior, voice, speed and the public API intact.
|
|
9
|
+
- Include the browser decision runtime entry in an explicitly configured DBOPFS
|
|
10
|
+
model store. Retain the original resource URL across activations, import the
|
|
11
|
+
complete stored self-contained module, and join cancellation cleanup before
|
|
12
|
+
settling callers. Preserve direct loading when no store is configured.
|
|
13
|
+
|
|
3
14
|
## 0.71.1
|
|
4
15
|
|
|
5
16
|
- Correct explicit disabled PWA lifecycle in standalone source development by
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ selected SDK projection or an explicit live SDK source mount. Both profiles
|
|
|
20
20
|
share the theme, packaging, event, cancellation,
|
|
21
21
|
and browser run contracts while retaining their selected application layout.
|
|
22
22
|
|
|
23
|
-
This checkout defines the `0.71.
|
|
23
|
+
This checkout defines the `0.71.2` SDK contract. Development applications declare
|
|
24
24
|
`arcane-os` as `latest` and retain their lockfile. Exact SDK pins begin when Roshi
|
|
25
25
|
designates a production build. Follow [Consumer updates](docs/consumer-updates.md)
|
|
26
26
|
to refresh and compare the installed SDK and lockfile with the published channel;
|
|
@@ -204,6 +204,9 @@ class BrowserDecisionModel {
|
|
|
204
204
|
pending: new Map(),
|
|
205
205
|
evaluations: new Set(),
|
|
206
206
|
resources: null,
|
|
207
|
+
runtimeController: null,
|
|
208
|
+
runtimePreparation: null,
|
|
209
|
+
runtimeUrl: null,
|
|
207
210
|
cleanup: null,
|
|
208
211
|
reason: undefined
|
|
209
212
|
};
|
|
@@ -263,9 +266,58 @@ class BrowserDecisionModel {
|
|
|
263
266
|
}
|
|
264
267
|
const id = this.#nextId++;
|
|
265
268
|
activation.pending.set(id, activation.load);
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
+
if (this.#store) {
|
|
270
|
+
activation.runtimeController = new AbortController();
|
|
271
|
+
// Retain the operation before a store progress callback can
|
|
272
|
+
// synchronously unload this activation.
|
|
273
|
+
activation.runtimePreparation = Promise.resolve().then(
|
|
274
|
+
async function prepareDecisionRuntimeEntry() {
|
|
275
|
+
if (!model.#isCurrent(activation)) return;
|
|
276
|
+
const configuration = model.#configuration;
|
|
277
|
+
const sourceUrl = new URL(configuration.runtime.moduleUrl, import.meta.url).href;
|
|
278
|
+
const response = await model.#store.fetchResource(sourceUrl, {
|
|
279
|
+
signal: activation.runtimeController.signal,
|
|
280
|
+
onProgress: function reportRuntimeEntryProgress(progress) {
|
|
281
|
+
if (!model.#isCurrent(activation)) return;
|
|
282
|
+
model.#progress = progress;
|
|
283
|
+
model.#publish();
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
if (!model.#isCurrent(activation)) return;
|
|
287
|
+
if (response.status < 200 || response.status >= 300) {
|
|
288
|
+
const error = new Error(
|
|
289
|
+
`The decision runtime returned HTTP ${response.status} ${response.statusText}.`
|
|
290
|
+
);
|
|
291
|
+
error.code = 'ARCANE_DECISION_RUNTIME_DOWNLOAD_FAILED';
|
|
292
|
+
error.response = response;
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
// Import the complete stored module without source
|
|
296
|
+
// rewriting. This activation owns its URL until stop.
|
|
297
|
+
activation.runtimeUrl = URL.createObjectURL(
|
|
298
|
+
new Blob([response.file], {type: 'text/javascript'})
|
|
299
|
+
);
|
|
300
|
+
worker.postMessage({
|
|
301
|
+
id,
|
|
302
|
+
op: 'load',
|
|
303
|
+
payload: {
|
|
304
|
+
...configuration,
|
|
305
|
+
runtime: {...configuration.runtime, moduleUrl: activation.runtimeUrl}
|
|
306
|
+
},
|
|
307
|
+
storedResources: true
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
);
|
|
311
|
+
activation.runtimePreparation.catch(
|
|
312
|
+
function stopFailedRuntimePreparation(error) {
|
|
313
|
+
if (model.#isCurrent(activation)) model.#stop(activation, error, 'error');
|
|
314
|
+
}
|
|
315
|
+
);
|
|
316
|
+
} else {
|
|
317
|
+
worker.postMessage(
|
|
318
|
+
{id, op: 'load', payload: this.#configuration, storedResources: false}
|
|
319
|
+
);
|
|
320
|
+
}
|
|
269
321
|
} catch (error) {
|
|
270
322
|
this.#stop(activation, error, 'error');
|
|
271
323
|
}
|
|
@@ -333,11 +385,42 @@ class BrowserDecisionModel {
|
|
|
333
385
|
this.#state = state;
|
|
334
386
|
this.#progress = null;
|
|
335
387
|
this.#error = reason;
|
|
336
|
-
|
|
388
|
+
const cleanup = [];
|
|
389
|
+
activation.runtimeController?.abort(reason);
|
|
390
|
+
if (activation.runtimePreparation) {
|
|
391
|
+
cleanup.push(activation.runtimePreparation.catch(
|
|
392
|
+
function preserveRuntimePreparationFailure(error) {
|
|
393
|
+
if (error === reason || error?.name === 'AbortError') return;
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
396
|
+
));
|
|
397
|
+
}
|
|
398
|
+
if (activation.resources) cleanup.push(activation.resources.close(reason));
|
|
399
|
+
if (activation.worker) {
|
|
400
|
+
for (const [type, listener] of Object.entries(activation.listeners ?? {})) {
|
|
401
|
+
activation.worker.removeEventListener(type, listener);
|
|
402
|
+
}
|
|
403
|
+
activation.worker.terminate();
|
|
404
|
+
}
|
|
405
|
+
if (activation.runtimeUrl !== null) {
|
|
406
|
+
try {
|
|
407
|
+
URL.revokeObjectURL(activation.runtimeUrl);
|
|
408
|
+
} catch (error) {
|
|
409
|
+
cleanup.push(Promise.reject(error));
|
|
410
|
+
}
|
|
411
|
+
activation.runtimeUrl = null;
|
|
412
|
+
}
|
|
413
|
+
if (cleanup.length > 0) {
|
|
337
414
|
const model = this;
|
|
338
|
-
activation.cleanup =
|
|
339
|
-
function
|
|
340
|
-
|
|
415
|
+
activation.cleanup = Promise.allSettled(cleanup).then(
|
|
416
|
+
function settleDecisionResourceCleanup(results) {
|
|
417
|
+
const failures = results.filter(function failedCleanup(result) {
|
|
418
|
+
return result.status === 'rejected';
|
|
419
|
+
}).map(function cleanupReason(result) {
|
|
420
|
+
return result.reason;
|
|
421
|
+
});
|
|
422
|
+
if (failures.length === 0) return;
|
|
423
|
+
activation.reason = new AggregateError([reason, ...failures], 'Unable to settle decision model resources.');
|
|
341
424
|
throw activation.reason;
|
|
342
425
|
}
|
|
343
426
|
);
|
|
@@ -350,12 +433,6 @@ class BrowserDecisionModel {
|
|
|
350
433
|
}
|
|
351
434
|
);
|
|
352
435
|
}
|
|
353
|
-
if (activation.worker) {
|
|
354
|
-
for (const [type, listener] of Object.entries(activation.listeners ?? {})) {
|
|
355
|
-
activation.worker.removeEventListener(type, listener);
|
|
356
|
-
}
|
|
357
|
-
activation.worker.terminate();
|
|
358
|
-
}
|
|
359
436
|
activation.load.reject(reason);
|
|
360
437
|
for (const operation of activation.pending.values()) operation.reject(reason);
|
|
361
438
|
activation.pending.clear();
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import Is from '../dependencies/strong-type/index.js';
|
|
2
|
+
|
|
3
|
+
const is = new Is(false);
|
|
4
|
+
|
|
5
|
+
function throwIfCancelled(signal) {
|
|
6
|
+
if (signal?.aborted) {
|
|
7
|
+
throw signal.reason ?? new DOMException('Speech synthesis was cancelled.', 'AbortError');
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Compose the selected Kokoro public classes without changing loaded instances.
|
|
13
|
+
* The original synthesizer remains the sole owner of its model and disposal.
|
|
14
|
+
*/
|
|
15
|
+
export async function createCompleteKokoroSynthesis({
|
|
16
|
+
KokoroTTS,
|
|
17
|
+
synthesizer,
|
|
18
|
+
repository,
|
|
19
|
+
signal,
|
|
20
|
+
onProgress,
|
|
21
|
+
}) {
|
|
22
|
+
throwIfCancelled(signal);
|
|
23
|
+
const Tokenizer = synthesizer.tokenizer.constructor;
|
|
24
|
+
class CompleteKokoroTokenizer extends Tokenizer {
|
|
25
|
+
_call(text, options = {}) {
|
|
26
|
+
return super._call(text, { ...options, truncation: false });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Kokoro 1.2.1's factory uses the tokenizer's default revision. Reuse that
|
|
30
|
+
// same public loading contract through the existing Worker resource router.
|
|
31
|
+
const tokenizer = await CompleteKokoroTokenizer.from_pretrained(repository, {
|
|
32
|
+
progress_callback: onProgress,
|
|
33
|
+
});
|
|
34
|
+
throwIfCancelled(signal);
|
|
35
|
+
const contextLength = tokenizer.model_max_length;
|
|
36
|
+
if (!is.safeInteger(contextLength) || contextLength < 3) {
|
|
37
|
+
throw new Error('The selected Kokoro tokenizer does not expose its model context length.');
|
|
38
|
+
}
|
|
39
|
+
const contentCapacity = contextLength - 2;
|
|
40
|
+
function boundaryIds(text) {
|
|
41
|
+
const encoded = tokenizer(text, {
|
|
42
|
+
add_special_tokens: false,
|
|
43
|
+
return_tensor: false,
|
|
44
|
+
truncation: false,
|
|
45
|
+
});
|
|
46
|
+
return new Set(encoded.input_ids.map(function tokenId(id) { return BigInt(id); }));
|
|
47
|
+
}
|
|
48
|
+
const punctuation = boundaryIds(';:,.!?—…');
|
|
49
|
+
const spaces = boundaryIds(' ');
|
|
50
|
+
|
|
51
|
+
function segmentEnd(tokens, start, limit, contentEnd) {
|
|
52
|
+
if (limit === contentEnd) return limit;
|
|
53
|
+
for (const boundaries of [punctuation, spaces]) {
|
|
54
|
+
for (let index = limit - 1; index >= start; index -= 1) {
|
|
55
|
+
if (boundaries.has(tokens[index])) return index + 1;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return limit;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class CompleteKokoroTTS extends KokoroTTS {
|
|
62
|
+
#signal;
|
|
63
|
+
|
|
64
|
+
constructor(requestSignal) {
|
|
65
|
+
super(synthesizer.model, tokenizer);
|
|
66
|
+
this.#signal = requestSignal;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async generate_from_ids(inputIds, options = {}) {
|
|
70
|
+
throwIfCancelled(this.#signal);
|
|
71
|
+
if (inputIds.dims.at(-1) <= contextLength) {
|
|
72
|
+
const output = await super.generate_from_ids(inputIds, options);
|
|
73
|
+
throwIfCancelled(this.#signal);
|
|
74
|
+
return output;
|
|
75
|
+
}
|
|
76
|
+
const Tensor = inputIds.constructor;
|
|
77
|
+
const tokens = inputIds.data;
|
|
78
|
+
const contentEnd = tokens.length - 1;
|
|
79
|
+
const outputs = [];
|
|
80
|
+
let sampleCount = 0;
|
|
81
|
+
for (let start = 1; start < contentEnd;) {
|
|
82
|
+
throwIfCancelled(this.#signal);
|
|
83
|
+
const end = segmentEnd(tokens, start, Math.min(start + contentCapacity, contentEnd), contentEnd);
|
|
84
|
+
// Only the original outer padding positions are replaced. All
|
|
85
|
+
// content tokens, including boundary punctuation/space, survive.
|
|
86
|
+
const data = new BigInt64Array(end - start + 2);
|
|
87
|
+
data[0] = tokens[0];
|
|
88
|
+
data.set(tokens.subarray(start, end), 1);
|
|
89
|
+
data[data.length - 1] = tokens[tokens.length - 1];
|
|
90
|
+
const ids = new Tensor('int64', data, [1, data.length]);
|
|
91
|
+
const output = await super.generate_from_ids(ids, options);
|
|
92
|
+
throwIfCancelled(this.#signal);
|
|
93
|
+
outputs.push(output);
|
|
94
|
+
sampleCount += output.audio.length;
|
|
95
|
+
start = end;
|
|
96
|
+
}
|
|
97
|
+
const audio = new Float32Array(sampleCount);
|
|
98
|
+
let offset = 0;
|
|
99
|
+
for (const output of outputs) {
|
|
100
|
+
audio.set(output.audio, offset);
|
|
101
|
+
offset += output.audio.length;
|
|
102
|
+
}
|
|
103
|
+
throwIfCancelled(this.#signal);
|
|
104
|
+
const RawAudio = outputs[0].constructor;
|
|
105
|
+
return new RawAudio(audio, outputs[0].sampling_rate);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return async function synthesizeCompleteKokoro(text, { voice, speed, signal: requestSignal } = {}) {
|
|
110
|
+
throwIfCancelled(requestSignal);
|
|
111
|
+
// Each invocation retains its own signal while sharing the one loaded
|
|
112
|
+
// model. Inherited generate() still owns all upstream phonemization.
|
|
113
|
+
const request = new CompleteKokoroTTS(requestSignal);
|
|
114
|
+
return request.generate(text, { voice, speed });
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Is from "../dependencies/strong-type/index.js";
|
|
2
2
|
import { createModelResourceClient, createModelResourceHost } from './model-resource-bridge.mjs';
|
|
3
|
+
import { createCompleteKokoroSynthesis } from './kokoro-complete-input.mjs';
|
|
3
4
|
|
|
4
5
|
const is = new Is(false);
|
|
5
6
|
|
|
@@ -1493,54 +1494,70 @@ async function createWhisperEngine(namespace, configuration, signal, report) {
|
|
|
1493
1494
|
}
|
|
1494
1495
|
|
|
1495
1496
|
async function createKokoroEngine(namespace, configuration, signal, report) {
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1497
|
+
if (!is.function(namespace?.KokoroTTS?.from_pretrained)) {
|
|
1498
|
+
throw workerError(
|
|
1499
|
+
'ARCANE_AI_PROVIDER_UNAVAILABLE',
|
|
1500
|
+
'The Kokoro runtime does not export KokoroTTS.',
|
|
1501
|
+
undefined,
|
|
1502
|
+
'kokoro-tts-constructor-export-missing',
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
throwIfAborted(signal, 'tts-load-cancelled');
|
|
1506
|
+
const synthesizer = await namespace.KokoroTTS.from_pretrained(
|
|
1507
|
+
configuration.model.repository,
|
|
1508
|
+
{
|
|
1509
|
+
device: configuration.execution?.device ?? 'wasm',
|
|
1510
|
+
dtype: configuration.model.dtype ?? 'q8',
|
|
1511
|
+
revision: configuration.model.revision,
|
|
1512
|
+
progress_callback: report,
|
|
1513
|
+
},
|
|
1502
1514
|
);
|
|
1503
|
-
|
|
1504
|
-
throwIfAborted(signal, "tts-load-cancelled");
|
|
1505
|
-
const synthesizer = await namespace.KokoroTTS.from_pretrained(
|
|
1506
|
-
configuration.model.repository,
|
|
1507
|
-
{
|
|
1508
|
-
device: configuration.execution?.device ?? "wasm",
|
|
1509
|
-
dtype: configuration.model.dtype ?? "q8",
|
|
1510
|
-
revision: configuration.model.revision,
|
|
1511
|
-
progress_callback: report,
|
|
1512
|
-
},
|
|
1513
|
-
);
|
|
1514
|
-
try {
|
|
1515
|
-
throwIfAborted(signal, "tts-load-cancelled");
|
|
1516
|
-
} catch (error) {
|
|
1515
|
+
let synthesize;
|
|
1517
1516
|
try {
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1517
|
+
throwIfAborted(signal, 'tts-load-cancelled');
|
|
1518
|
+
synthesize = await createCompleteKokoroSynthesis({
|
|
1519
|
+
KokoroTTS: namespace.KokoroTTS,
|
|
1520
|
+
synthesizer,
|
|
1521
|
+
repository: configuration.model.repository,
|
|
1522
|
+
signal,
|
|
1523
|
+
onProgress: report,
|
|
1524
|
+
});
|
|
1525
|
+
throwIfAborted(signal, 'tts-load-cancelled');
|
|
1526
|
+
} catch (error) {
|
|
1527
|
+
try {
|
|
1528
|
+
await disposeEngine(synthesizer);
|
|
1529
|
+
} catch (cleanupError) {
|
|
1530
|
+
throw new AggregateError([error, cleanupError], 'Kokoro preparation and model cleanup failed.', { cause: error });
|
|
1531
|
+
}
|
|
1532
|
+
throwIfAborted(signal, 'tts-load-cancelled');
|
|
1533
|
+
throw error;
|
|
1521
1534
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1535
|
+
return completeValue({
|
|
1536
|
+
async synthesize(input, { signal: requestSignal } = {}) {
|
|
1537
|
+
throwIfAborted(requestSignal, 'tts-synthesis-cancelled');
|
|
1538
|
+
let output;
|
|
1539
|
+
try {
|
|
1540
|
+
output = await synthesize(input.text, {
|
|
1541
|
+
voice: input.voice,
|
|
1542
|
+
speed: input.speed,
|
|
1543
|
+
signal: requestSignal,
|
|
1544
|
+
});
|
|
1545
|
+
} catch (error) {
|
|
1546
|
+
throwIfAborted(requestSignal, 'tts-synthesis-cancelled');
|
|
1547
|
+
throw error;
|
|
1548
|
+
}
|
|
1549
|
+
throwIfAborted(requestSignal, 'tts-synthesis-cancelled');
|
|
1550
|
+
const audio = is.instanceCheck(output?.audio, Float32Array)
|
|
1551
|
+
? output.audio
|
|
1552
|
+
: new Float32Array(output?.audio ?? []);
|
|
1553
|
+
return completeValue({
|
|
1554
|
+
audio,
|
|
1555
|
+
sampleRate: output?.sampling_rate,
|
|
1556
|
+
voice: input.voice,
|
|
1557
|
+
});
|
|
1558
|
+
},
|
|
1559
|
+
dispose: function disposeKokoroEngine() { return disposeEngine(synthesizer); },
|
|
1560
|
+
});
|
|
1544
1561
|
}
|
|
1545
1562
|
|
|
1546
1563
|
function callerVoiceIds(configuration) {
|
|
@@ -68,9 +68,9 @@ it does not substitute a quantized graph or another model.
|
|
|
68
68
|
| `revision` | Upstream revision, default `main`; select a stable revision when repeatable model selection is needed. |
|
|
69
69
|
| `device` | Transformers.js backend, default `webgpu`; an explicit supported alternative is caller-owned. No automatic backend fallback. |
|
|
70
70
|
| `dtype` | Exact upstream precision selection; defaults to `fp16` for Laya and `fp32` for Julia. For Laya FP32, supply `dtype: 'fp32'`. Unsupported selections fail at the selected upstream loader rather than being replaced. |
|
|
71
|
-
| `runtime.moduleUrl` | Defaults to the self-contained CDN entry `https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.3.0/dist/transformers.min.js`. The `.web.js` build expects a bundler to resolve its bare ONNX Runtime import and is unsuitable for a native module Worker. An explicit compatible public runtime URL may be supplied. |
|
|
71
|
+
| `runtime.moduleUrl` | Defaults to the self-contained CDN entry `https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.3.0/dist/transformers.min.js`. The `.web.js` build expects a bundler to resolve its bare ONNX Runtime import and is unsuitable for a native module Worker. An explicit compatible public runtime URL may be supplied. With `store`, the SDK stores and materializes the complete selected entry for that activation. |
|
|
72
72
|
| `runtime.wasmPaths` | Optional upstream ONNX WASM location passed to the selected runtime's public environment API. |
|
|
73
|
-
| `store` | Optional existing SDK DBOPFS model or speech artifact store exposing `fetchResource()`. The store remains outside Worker configuration; the SDK bridges the loader's actual resource requests to it. |
|
|
73
|
+
| `store` | Optional existing SDK DBOPFS model or speech artifact store exposing `fetchResource()`. The store remains outside Worker configuration; the SDK uses it for the runtime entry and bridges the loader's actual resource requests to it. |
|
|
74
74
|
|
|
75
75
|
Without `store`, the runtime, tokenizer and weights use normal upstream loading
|
|
76
76
|
and caching. With `store`, the selected loader still owns filenames and model
|
|
@@ -110,17 +110,27 @@ await decisions.load();
|
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
The same `store` may serve other SDK model clients. The application supplies no
|
|
113
|
-
tokenizer/configuration/graph manifest and performs no file routing.
|
|
114
|
-
|
|
113
|
+
tokenizer/configuration/graph manifest and performs no file routing. Explicit
|
|
114
|
+
activation first opens `runtime.moduleUrl` through `store.fetchResource()`.
|
|
115
|
+
The SDK materializes the complete stored entry as a JavaScript object URL for
|
|
116
|
+
the dedicated Worker, without rewriting its content. The configured source URL
|
|
117
|
+
is retained for later activations, so they reuse the same stored resource rather
|
|
118
|
+
than saving a temporary object URL. Construction still starts no download.
|
|
119
|
+
|
|
120
|
+
Inside the dedicated decision Worker, Transformers.js's public `env.fetch` hook routes the
|
|
115
121
|
actual selected tokenizer, configuration, ONNX graph and external-data requests
|
|
116
122
|
to `store.fetchResource(input, options)`. Its competing browser, custom and
|
|
117
123
|
filesystem model caches are disabled only for this explicit stored mode. The
|
|
118
124
|
default upstream ONNX WASM binary and factory preloads use this same hook.
|
|
119
125
|
Native support-file fetches in the dedicated Worker also use the store. The
|
|
120
|
-
runtime
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
default runtime entry is self-contained. Entry materialization does not rewrite
|
|
127
|
+
dependency imports in custom modules or preserve their original `import.meta.url`
|
|
128
|
+
base. Custom stored entries must work from an object URL; relative imports or
|
|
129
|
+
support-file URLs resolved against that module's original location require a
|
|
130
|
+
compatible self-contained entry. Native absolute ESM dependencies and an upstream
|
|
131
|
+
custom factory import outside its preload path remain browser-owned. No second
|
|
132
|
+
application cache or model substitution is introduced. Without `store`, the
|
|
133
|
+
configured runtime URL keeps its direct native module-loading behavior.
|
|
124
134
|
|
|
125
135
|
The store returns `{file, status, statusText, headers, url, redirected}`. The
|
|
126
136
|
Worker reconstructs the complete response with its actual HTTP status and
|
|
@@ -138,9 +148,14 @@ url}`. `completed` counts closed persistent shards; `total` is `null` until the
|
|
|
138
148
|
resource finishes. This progress carries no network-size, rate or ETA fields.
|
|
139
149
|
Cancellation aborts the owned fetch and joins reader/writer cleanup before the
|
|
140
150
|
resource owner becomes available to a later request. Interrupted closed shards
|
|
141
|
-
are retained.
|
|
142
|
-
|
|
143
|
-
|
|
151
|
+
are retained. Each activation owns its runtime object URL and revokes it after
|
|
152
|
+
terminating the Worker on cancellation, failure, unload or disposal. A cancelled
|
|
153
|
+
runtime-entry fetch is joined before its waiting calls settle; a late response
|
|
154
|
+
cannot activate a replacement Worker. Runtime-entry HTTP failures retain the
|
|
155
|
+
complete stored response in `error.response` for developer diagnostics.
|
|
156
|
+
`unload()` and `dispose()` still acknowledge their lifecycle transition
|
|
157
|
+
synchronously; outstanding `load()`/`evaluate()` calls settle after their
|
|
158
|
+
resource cleanup.
|
|
144
159
|
|
|
145
160
|
## Complete rows and results
|
|
146
161
|
|
|
@@ -1065,11 +1065,32 @@ owners. Only `'plain'` selects exact input; omission and other values retain
|
|
|
1065
1065
|
the existing narration cleanup. Applications never supply the internal
|
|
1066
1066
|
`speechInputPrepared` argument.
|
|
1067
1067
|
|
|
1068
|
-
This selection governs SDK text treatment, not
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1068
|
+
This selection governs SDK text treatment, not pronunciation. The browser Kokoro
|
|
1069
|
+
adapter preserves the selected runtime's own text normalization and phonemization,
|
|
1070
|
+
then uses a genuine tokenizer subclass with truncation disabled. Its Kokoro
|
|
1071
|
+
subclass sends every resulting content token through the public
|
|
1072
|
+
`generate_from_ids()` method. Short inputs retain one inference; longer inputs
|
|
1073
|
+
use ordered segments within the selected engine's inference capacity and return
|
|
1074
|
+
all audio in order. This is token-based engine transport, not a character limit,
|
|
1075
|
+
text estimate, summary, or input discard.
|
|
1076
|
+
|
|
1077
|
+
The [selected Kokoro model](https://huggingface.co/onnx-community/Kokoro-82M-v1.0-ONNX/blob/1939ad2a8e416c0acfeecc08a694d14ef25f2231/README.md#python)
|
|
1078
|
+
accepts 510 content tokens plus its two outer padding positions per inference.
|
|
1079
|
+
Each segment receives that padding independently. Upstream voice-style selection,
|
|
1080
|
+
speed, waveform generation and sample rate remain upstream-owned. The SDK reuses
|
|
1081
|
+
one loaded model; it does not copy the private phonemizer, replace instance methods,
|
|
1082
|
+
load a second model, or import another Transformers runtime. Its tokenizer
|
|
1083
|
+
reconstruction uses the same effective selection as the
|
|
1084
|
+
[Kokoro 1.2.1 factory](https://github.com/hexgrad/kokoro/blob/664c76a704021239ba59c84dcbaa4d3dece01fe9/kokoro.js/src/kokoro.js).
|
|
1085
|
+
That factory ignores a `revision` option; this change does not establish revision
|
|
1086
|
+
pinning that the upstream factory does not implement.
|
|
1087
|
+
|
|
1088
|
+
Cancellation is request-local and checked between inference segments and before
|
|
1089
|
+
returning audio. As with the existing Worker contract, cancellation prevents later
|
|
1090
|
+
work and output while an already-running upstream inference may finish. Unload
|
|
1091
|
+
and disposal retain their existing Worker-termination boundary. Source and
|
|
1092
|
+
controlled-fixture coverage of token/audio preservation are distinct from speech
|
|
1093
|
+
quality or execution evidence for a particular model, browser and device.
|
|
1073
1094
|
|
|
1074
1095
|
## Choose a device or reduce memory use
|
|
1075
1096
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcane-os",
|
|
3
|
-
"version": "0.71.
|
|
3
|
+
"version": "0.71.2",
|
|
4
4
|
"description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"test": "npm run test:unit && npm run test:functional && npm run test:integration && npm run test:regression",
|
|
124
124
|
"test:release": "node ./bin/arcane-test.mjs test/npm-release.test.mjs",
|
|
125
125
|
"test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.test.mjs test/app-selection.test.mjs test/contracts.test.mjs test/conversation-closing-report.test.mjs test/doctor.test.mjs test/mail-credentials.test.mjs test/mail-outbox.test.mjs test/mail-public-api.test.mjs test/mail-send.test.mjs test/mail-transport.test.mjs test/native-windows-icon.test.mjs test/targets.test.mjs test/workspace-operation-lock.test.mjs",
|
|
126
|
-
"test:functional": "node ./bin/arcane-test.mjs test/ai-ollama-readiness.test.mjs test/core-ollama-lifecycle.test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-decisions.test.mjs test/browser-speech-providers.test.mjs test/browser-speech-artifacts.test.mjs test/browser-speech-resource-bridge.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-input.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/chat-transient-retention.test.mjs test/cli.test.mjs test/continuous-voice-capture.test.mjs test/codex-app-server.test.mjs test/mcp-stdio-server.test.mjs test/core-classic-source.test.mjs test/core-client.test.mjs test/core-client-installation.test.mjs test/core-ai-installation.test.mjs test/core-llama-lifecycle.test.mjs test/core-development.test.mjs test/core-host.test.mjs test/core-shared-host.test.mjs test/core-model-assets.test.mjs test/core-native-services.test.mjs test/core-packaged-web.test.mjs test/core-runtime.test.mjs test/core-stdio.test.mjs test/dbopfs-committed-events.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs-model-resources.test.mjs test/dbopfs-png.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/file-preview.test.mjs test/import-map.test.mjs test/local-image-editing.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/markdown-editor.test.mjs test/markdown-media.test.mjs test/markdown-source-map.test.mjs test/modal.test.mjs test/model-resource-bridge.test.mjs test/native-decisions.test.mjs test/native-onnx.test.mjs test/native-module-packaging.test.mjs test/native-module-resource-references.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/rendered-print.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/theme-manager.test.mjs test/twin-cloud-images.test.mjs test/twin-cloud.test.mjs test/websocket-client-import-map.test.mjs test/websocket.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
|
|
126
|
+
"test:functional": "node ./bin/arcane-test.mjs test/ai-ollama-readiness.test.mjs test/core-ollama-lifecycle.test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-decisions.test.mjs test/browser-kokoro-complete-input.test.mjs test/browser-speech-providers.test.mjs test/browser-speech-artifacts.test.mjs test/browser-speech-resource-bridge.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-input.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/chat-transient-retention.test.mjs test/cli.test.mjs test/continuous-voice-capture.test.mjs test/codex-app-server.test.mjs test/mcp-stdio-server.test.mjs test/core-classic-source.test.mjs test/core-client.test.mjs test/core-client-installation.test.mjs test/core-ai-installation.test.mjs test/core-llama-lifecycle.test.mjs test/core-development.test.mjs test/core-host.test.mjs test/core-shared-host.test.mjs test/core-model-assets.test.mjs test/core-native-services.test.mjs test/core-packaged-web.test.mjs test/core-runtime.test.mjs test/core-stdio.test.mjs test/dbopfs-committed-events.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs-model-resources.test.mjs test/dbopfs-png.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/file-preview.test.mjs test/import-map.test.mjs test/local-image-editing.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/markdown-editor.test.mjs test/markdown-media.test.mjs test/markdown-source-map.test.mjs test/modal.test.mjs test/model-resource-bridge.test.mjs test/native-decisions.test.mjs test/native-onnx.test.mjs test/native-module-packaging.test.mjs test/native-module-resource-references.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/rendered-print.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/theme-manager.test.mjs test/twin-cloud-images.test.mjs test/twin-cloud.test.mjs test/websocket-client-import-map.test.mjs test/websocket.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
|
|
127
127
|
"test:integration": "node ./bin/arcane-test.mjs test/installed-package-runtime.test.mjs test/root-app-layout.test.mjs test/integrated-shared.test.mjs test/integrated-workspace.test.mjs test/mail-browser.test.mjs test/native-macos-provider.test.mjs test/native-plan.test.mjs test/native-portable-provider.test.mjs test/native-provider-loader.test.mjs test/npm-release.test.mjs test/release-bundle.test.mjs test/release-capability-smoke.test.mjs test/shared-payload-batch.test.mjs test/tarball.test.mjs test/browser-wasm-cpu.test.mjs test/wllama-webgpu-runtime.test.mjs",
|
|
128
128
|
"test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/html-import-registration.test.mjs test/html-import-packaged-script.test.mjs test/logging-regression.test.mjs test/markdown-speech.test.mjs test/prepared-speech.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
|
|
129
129
|
"check": "node tools/check-source.mjs && npm test",
|