arcane-os 0.77.0 → 0.79.0
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 +40 -0
- package/README.md +6 -1
- package/browser-runtime/ai/core-onnx.mjs +6 -2
- package/browser-runtime/ai/dbopfs-model-resources.mjs +1 -1
- package/browser-runtime/ai/speech-worker-runtime.mjs +79 -3
- package/browser-runtime/core/contracts.mjs +10 -0
- package/docs/publishing.md +20 -3
- package/docs/reference/ai/browser-speech.md +59 -2
- package/docs/reference/execution-devices.md +157 -0
- package/docs/reference/inventory/package-api.json +39 -11
- package/docs/reference/inventory/runtime-modules.json +9 -8
- package/docs/reference/local-ai.md +104 -10
- package/docs/reference/native-decisions.md +65 -10
- package/docs/reference/runtime-modules.md +78 -11
- package/package.json +4 -2
- package/runtime/arcane/modules/AI.js +30 -7
- package/runtime/arcane/modules/DBOPFS.js +24 -6
- package/runtime/arcane/modules/PreparedSpeech.js +111 -6
- package/src/core/services/decisions.mjs +51 -15
- package/src/core/services/execution-devices.mjs +32 -0
- package/src/core/services/local-ai.mjs +12 -5
- package/src/local-ai/decisions.mjs +93 -15
- package/src/local-ai/dev.mjs +7 -3
- package/src/local-ai/execution-devices-windows.ps1 +833 -0
- package/src/local-ai/execution-devices.mjs +288 -0
- package/src/local-ai/onnx-worker.mjs +149 -19
- package/src/local-ai/onnx.mjs +87 -27
- package/src/native/portable-layout.mjs +8 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.79.0
|
|
4
|
+
|
|
5
|
+
- Add public `createExecutionDeviceCatalog()` and `createExecutionDeviceService()`
|
|
6
|
+
with lazy Windows CPU/GPU/NPU discovery, stable host-local identities,
|
|
7
|
+
complete discovery issues, explicit refresh, cancellation and disposal.
|
|
8
|
+
`localai.devices` and `localai.resolveTarget` use the existing Core transport
|
|
9
|
+
without loading models or changing saved preferences.
|
|
10
|
+
- Share one catalog per local-AI Core lifetime. Native ONNX and Laya accept
|
|
11
|
+
`executionTarget` for a physical CPU or mapped DirectML GPU. Automatic GPU
|
|
12
|
+
selection can use the largest reported dedicated capacity among addressable
|
|
13
|
+
hardware GPUs; integrated GPUs remain eligible. Unsupported or unavailable
|
|
14
|
+
selections are reported without silently choosing another physical device.
|
|
15
|
+
- Preserve caller options and complete execution diagnostics. Distinguish the
|
|
16
|
+
requested, resolved and configured target from observed execution; successful
|
|
17
|
+
configuration is not GPU/NPU execution proof. Laya target replacement cancels
|
|
18
|
+
and drains only its retiring activation before the successor starts.
|
|
19
|
+
- Keep discovery independent from model readiness and preserve existing engine
|
|
20
|
+
behavior when a target is omitted. Browser Wllama, native llama.cpp, image and
|
|
21
|
+
Whisper device routing are unchanged; this release adds no driver, model or
|
|
22
|
+
native runtime dependency.
|
|
23
|
+
|
|
24
|
+
## 0.78.0
|
|
25
|
+
|
|
26
|
+
- Add `AI.readPreparedTTS()` and shared `readPreparedSpeech()` for complete
|
|
27
|
+
cache-only narration lookup. Return a ready playback handle or `null` without
|
|
28
|
+
loading a model, unmuting, synthesizing, writing storage or repairing partial
|
|
29
|
+
audio. Preserve complete semantic matching and genuine storage errors.
|
|
30
|
+
- Keep lookup cancellation independent from a successfully returned handle.
|
|
31
|
+
Replay uses the existing prepared-playback owner and can borrow the caller's
|
|
32
|
+
silent reusable audio output; no autoplay permission is implied.
|
|
33
|
+
- Add optional existing-only DBOPFS table/file reads while preserving ordinary
|
|
34
|
+
lazy-creation defaults. Cache-only lookup and later audio reads leave missing
|
|
35
|
+
tables absent.
|
|
36
|
+
- Give Kokoro voice caching a real Worker-lifetime response cache after runtime
|
|
37
|
+
import, while DBOPFS remains the durable model/resource owner. Preserve saved
|
|
38
|
+
native caches and clear transient responses on unload.
|
|
39
|
+
- Report a complete stored model resource as cached loading rather than a new
|
|
40
|
+
network download. This release changes no model selection, native engine or
|
|
41
|
+
physical-device routing and makes no live-model execution claim.
|
|
42
|
+
|
|
3
43
|
## 0.77.0
|
|
4
44
|
|
|
5
45
|
- Add `AI.prepareAudioOutput()` for synchronous, silent preparation of a reusable
|
package/README.md
CHANGED
|
@@ -20,12 +20,17 @@ 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.
|
|
23
|
+
This checkout defines the `0.79.0` 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;
|
|
27
27
|
registry state is deliberately not baked into application artifacts.
|
|
28
28
|
|
|
29
|
+
[Physical execution devices](docs/reference/execution-devices.md) exposes Windows
|
|
30
|
+
CPU/GPU/NPU discovery and native ONNX/Laya target selection through the existing
|
|
31
|
+
Core host, independently of model loading. Device presence, configured provider
|
|
32
|
+
placement and observed model execution remain separate facts.
|
|
33
|
+
|
|
29
34
|
Standalone browser apps use their repository root and installed npm package directly, without a
|
|
30
35
|
generated workspace `arcane/` directory or `arcane.lock.json`. Select the
|
|
31
36
|
[four installed-package routes](docs/reference/protocols.md#installed-package-browser-routes)
|
|
@@ -106,9 +106,13 @@ export function createCoreONNXRuntime({client: suppliedClient} = {}) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
async function load({id, model, sessionOptions, signal, timeoutMs} = {}) {
|
|
109
|
+
async function load({id, model, sessionOptions, executionPreference, executionTarget, signal, timeoutMs} = {}) {
|
|
110
110
|
const currentConnection = connection;
|
|
111
|
-
const result = await invoke(
|
|
111
|
+
const result = await invoke(
|
|
112
|
+
'onnx.load',
|
|
113
|
+
{id, model, sessionOptions, executionPreference, executionTarget},
|
|
114
|
+
{signal, timeoutMs}
|
|
115
|
+
);
|
|
112
116
|
currentConnection.controller.signal.throwIfAborted();
|
|
113
117
|
return result;
|
|
114
118
|
}
|
|
@@ -96,7 +96,7 @@ export function createDbopfsResourceStore({dbopfs, tableName, fetchImpl = global
|
|
|
96
96
|
const file = await parts.read(entry.name, {signal});
|
|
97
97
|
if (file) {
|
|
98
98
|
const state = await parts.state(entry.name, {signal});
|
|
99
|
-
onProgress?.({phase: '
|
|
99
|
+
onProgress?.({phase: 'load', message: 'Reading stored model resource', completed: state.parts.length,
|
|
100
100
|
total: state.parts.length, unit: 'shards', cached: true, url: request.url});
|
|
101
101
|
return {...entry.metadata, file};
|
|
102
102
|
}
|
|
@@ -997,6 +997,7 @@ function createOrdinaryArtifactModuleRouter(
|
|
|
997
997
|
role,
|
|
998
998
|
reader,
|
|
999
999
|
originalWorker,
|
|
1000
|
+
responseCaches,
|
|
1000
1001
|
) {
|
|
1001
1002
|
const nestedWorkers = new Set();
|
|
1002
1003
|
const router = completeValue({
|
|
@@ -1015,8 +1016,8 @@ function createOrdinaryArtifactModuleRouter(
|
|
|
1015
1016
|
return reader.fetchResource(input, init, modulePath);
|
|
1016
1017
|
},
|
|
1017
1018
|
|
|
1018
|
-
|
|
1019
|
-
|
|
1019
|
+
openCache(modulePath, name) {
|
|
1020
|
+
return responseCaches.open(name);
|
|
1020
1021
|
},
|
|
1021
1022
|
|
|
1022
1023
|
createWorker(modulePath, specifier, options = {}) {
|
|
@@ -1143,6 +1144,63 @@ function installOrdinaryArtifactModuleRouter(scope, configuration, role, send, s
|
|
|
1143
1144
|
const resourceClient = createModelResourceClient({ send });
|
|
1144
1145
|
let requestSignal = null;
|
|
1145
1146
|
let reportProgress = null;
|
|
1147
|
+
let cacheClosed = false;
|
|
1148
|
+
const responseCacheEntries = new Map();
|
|
1149
|
+
const responseCaches = {
|
|
1150
|
+
async open(name) {
|
|
1151
|
+
assertCacheActive(requestSignal);
|
|
1152
|
+
const cacheName = String(name);
|
|
1153
|
+
let cache = responseCacheEntries.get(cacheName);
|
|
1154
|
+
if (!cache) {
|
|
1155
|
+
const responses = new Map();
|
|
1156
|
+
cache = {
|
|
1157
|
+
responses,
|
|
1158
|
+
api: {
|
|
1159
|
+
async match(input) {
|
|
1160
|
+
assertCacheActive(requestSignal);
|
|
1161
|
+
const stored = responses.get(cacheRequestKey(input));
|
|
1162
|
+
if (!stored) return undefined;
|
|
1163
|
+
const response = new Response(stored.body, stored);
|
|
1164
|
+
Object.defineProperties(response, {
|
|
1165
|
+
url: { value: stored.url },
|
|
1166
|
+
redirected: { value: stored.redirected },
|
|
1167
|
+
type: { value: stored.type },
|
|
1168
|
+
});
|
|
1169
|
+
return response;
|
|
1170
|
+
},
|
|
1171
|
+
async put(input, response) {
|
|
1172
|
+
const signal = requestSignal;
|
|
1173
|
+
assertCacheActive(signal);
|
|
1174
|
+
const key = cacheRequestKey(input);
|
|
1175
|
+
const copy = response.clone();
|
|
1176
|
+
const body = await copy.blob();
|
|
1177
|
+
assertCacheActive(signal);
|
|
1178
|
+
responses.set(key, {
|
|
1179
|
+
body,
|
|
1180
|
+
status: response.status,
|
|
1181
|
+
statusText: response.statusText,
|
|
1182
|
+
headers: [...response.headers],
|
|
1183
|
+
url: response.url,
|
|
1184
|
+
redirected: response.redirected,
|
|
1185
|
+
type: response.type,
|
|
1186
|
+
});
|
|
1187
|
+
},
|
|
1188
|
+
},
|
|
1189
|
+
};
|
|
1190
|
+
responseCacheEntries.set(cacheName, cache);
|
|
1191
|
+
}
|
|
1192
|
+
return cache.api;
|
|
1193
|
+
},
|
|
1194
|
+
};
|
|
1195
|
+
function assertCacheActive(signal) {
|
|
1196
|
+
throwIfAborted(signal, 'speech-resource-cache-cancelled');
|
|
1197
|
+
if (cacheClosed) throw new Error('The speech Worker response cache was unloaded.');
|
|
1198
|
+
}
|
|
1199
|
+
function cacheRequestKey(input) {
|
|
1200
|
+
const RequestConstructor = scope.Request ?? Request;
|
|
1201
|
+
const request = new RequestConstructor(input);
|
|
1202
|
+
return JSON.stringify([request.method, request.url]);
|
|
1203
|
+
}
|
|
1146
1204
|
if (!is.function(originalFetch)) {
|
|
1147
1205
|
throw workerError(
|
|
1148
1206
|
"ARCANE_AI_PROVIDER_UNAVAILABLE",
|
|
@@ -1166,8 +1224,9 @@ function installOrdinaryArtifactModuleRouter(scope, configuration, role, send, s
|
|
|
1166
1224
|
role,
|
|
1167
1225
|
reader,
|
|
1168
1226
|
originalWorker,
|
|
1227
|
+
responseCaches,
|
|
1169
1228
|
);
|
|
1170
|
-
const originalProperties = ['fetch', 'Worker', MODULE_ROUTER_NAME].map(
|
|
1229
|
+
const originalProperties = ['fetch', 'Worker', 'caches', MODULE_ROUTER_NAME].map(
|
|
1171
1230
|
function originalWorkerProperty(name) {
|
|
1172
1231
|
return { name, descriptor: Object.getOwnPropertyDescriptor(scope, name) };
|
|
1173
1232
|
},
|
|
@@ -1240,7 +1299,22 @@ function installOrdinaryArtifactModuleRouter(scope, configuration, role, send, s
|
|
|
1240
1299
|
receive: resourceClient.receive,
|
|
1241
1300
|
setSignal(signal) { requestSignal = signal; },
|
|
1242
1301
|
setProgress(report) { reportProgress = report; },
|
|
1302
|
+
enableResponseCache() {
|
|
1303
|
+
// Transformers captures cache capability during import. Kokoro then uses
|
|
1304
|
+
// this real, Worker-lifetime match/put cache for voices; fetch remains the
|
|
1305
|
+
// sole route to durable DBOPFS resources. No native saved cache is touched.
|
|
1306
|
+
assertCacheActive(requestSignal);
|
|
1307
|
+
Object.defineProperty(scope, 'caches', {
|
|
1308
|
+
configurable: true,
|
|
1309
|
+
enumerable: true,
|
|
1310
|
+
writable: true,
|
|
1311
|
+
value: responseCaches,
|
|
1312
|
+
});
|
|
1313
|
+
},
|
|
1243
1314
|
async cleanup() {
|
|
1315
|
+
cacheClosed = true;
|
|
1316
|
+
for (const cache of responseCacheEntries.values()) cache.responses.clear();
|
|
1317
|
+
responseCacheEntries.clear();
|
|
1244
1318
|
resourceClient.close(new Error('The speech Worker resource owner was unloaded.'));
|
|
1245
1319
|
let cleanupFailure = null;
|
|
1246
1320
|
try {
|
|
@@ -1773,6 +1847,7 @@ export function createSpeechWorkerRuntime({ role, scope = globalThis, send } = {
|
|
|
1773
1847
|
);
|
|
1774
1848
|
const namespace = await import(entry.moduleUrl);
|
|
1775
1849
|
throwIfAborted(signal, `${role}-load-cancelled`);
|
|
1850
|
+
environment.enableResponseCache();
|
|
1776
1851
|
restoreNamespace = configureRuntimeNamespace(
|
|
1777
1852
|
namespace,
|
|
1778
1853
|
configuration,
|
|
@@ -2071,6 +2146,7 @@ export function installBrowserSpeechArtifactModuleWorker(role, scope = globalThi
|
|
|
2071
2146
|
target?.sourceUrl ?? request.targetUrl,
|
|
2072
2147
|
);
|
|
2073
2148
|
await import(target?.moduleUrl ?? request.targetUrl);
|
|
2149
|
+
environment.enableResponseCache();
|
|
2074
2150
|
scope.removeEventListener("message", bootstrap);
|
|
2075
2151
|
await new Promise((resolve) => queueMicrotask(resolve));
|
|
2076
2152
|
for (const queuedEvent of queued.splice(0)) replayWorkerMessage(scope, queuedEvent);
|
|
@@ -32,6 +32,16 @@ export const CORE_METHOD_CONTRACTS={
|
|
|
32
32
|
'preferences.set':{input:'{key,value}',output:'{key,value}'},
|
|
33
33
|
'preferences.setMany':{input:'{entries}',output:'preference-batch-result'},
|
|
34
34
|
'preferences.delete':{input:'{key}',output:'{key,deleted}'},
|
|
35
|
+
'localai.devices': {
|
|
36
|
+
input: '{refresh?}',
|
|
37
|
+
output: '{platform,state,devices,issues}',
|
|
38
|
+
meaning: 'Physical host inventory, independent of model startup. Presence does not establish engine support or observed execution.'
|
|
39
|
+
},
|
|
40
|
+
'localai.resolveTarget': {
|
|
41
|
+
input: '{executionTarget:{deviceId}|null,refresh?}',
|
|
42
|
+
output: '{requestedTarget,resolvedDevice,resolution,reason}',
|
|
43
|
+
meaning: 'Resolve a saved physical identity. Explicit null selects engine-owned automatic resolution; it does not claim a configured or observed device.'
|
|
44
|
+
},
|
|
35
45
|
'window.setTheme': {
|
|
36
46
|
input: '{backgroundColor?,textColor?}: null reset or {red,green,blue,alpha}',
|
|
37
47
|
output: '{platform,supported,applied,unsupported}',
|
package/docs/publishing.md
CHANGED
|
@@ -53,6 +53,22 @@ and reports npm's response. It tolerates npm's publish-time scanning. If
|
|
|
53
53
|
scanning or manual review remains pending, the workflow reports that state and
|
|
54
54
|
a rerun safely resumes without republishing the version.
|
|
55
55
|
|
|
56
|
+
Registry verification requires both the selected version/channel metadata and
|
|
57
|
+
an ordinary complete GET of that exact version's published `dist.tarball` URL.
|
|
58
|
+
Metadata visibility alone is not downloadable-package delivery. The verifier
|
|
59
|
+
reads exact-version metadata, follows its tarball URL, and consumes the full
|
|
60
|
+
successful response before reporting completion. Missing metadata, an HTTP
|
|
61
|
+
failure, or an interrupted body remains pending within the existing bounded
|
|
62
|
+
retry window; the remaining window also bounds the metadata/tarball requests.
|
|
63
|
+
The existing npm metadata subprocesses retain their current behavior. If they
|
|
64
|
+
consume the window, including a zero-wait invocation, the verifier reports
|
|
65
|
+
pending without starting a new download observation.
|
|
66
|
+
It records the actual failure and exits unsuccessfully if availability does not
|
|
67
|
+
arrive. It does not save a second artifact, repack, republish, alter registry
|
|
68
|
+
tags, add cache-bypass parameters, or impose a content-size or hash comparison.
|
|
69
|
+
Requester notices follow successful version/channel and download observation;
|
|
70
|
+
consumer installation and application verification remain separately owned.
|
|
71
|
+
|
|
56
72
|
The public `0.3.2` release is fixed to package-source commit
|
|
57
73
|
`445bd2d982f12e6ef8dd2b615c70512000cc5224`, selected Check run
|
|
58
74
|
`33264677687`, and publication/registry run `33264829711`. Its numeric Git tag
|
|
@@ -77,9 +93,10 @@ The npm package already has its trusted-publishing relationship. Each later
|
|
|
77
93
|
release therefore follows the same direct selected-artifact path: push the
|
|
78
94
|
intended `main` source, manually run Check for that exact revision, review the
|
|
79
95
|
resulting package inventory and legal notices, then manually dispatch
|
|
80
|
-
publication with that Check run and artifact. Confirm the selected version
|
|
81
|
-
dist-tag
|
|
82
|
-
publication authority, and never substitute
|
|
96
|
+
publication with that Check run and artifact. Confirm the selected version,
|
|
97
|
+
dist-tag, and complete published tarball availability after publication. Never
|
|
98
|
+
rebuild or repack the artifact under publication authority, and never substitute
|
|
99
|
+
a different source revision.
|
|
83
100
|
|
|
84
101
|
Generated app CI uses `npm ci --ignore-scripts`, so its lock must exist and its
|
|
85
102
|
dependency source must be reachable by the runner. External application CI uses
|
|
@@ -727,6 +727,54 @@ pause delays the next queued audio on the existing `AudioContext` clock; it
|
|
|
727
727
|
does not delay the preceding promise after that passage's last buffer ends.
|
|
728
728
|
The promise is a playback result, not a listener acknowledgement.
|
|
729
729
|
|
|
730
|
+
## Read stored narration without starting generation
|
|
731
|
+
|
|
732
|
+
`await ai.readPreparedTTS({parts,textFormat,storage,identity,signal,onState})`
|
|
733
|
+
returns a complete prepared handle or `null`. It uses the same complete inputs,
|
|
734
|
+
selected speech configuration, formatting treatment, segmentation, storage key,
|
|
735
|
+
and semantic `identity` as `prepareTTS()`. Supply the same options used to
|
|
736
|
+
prepare the audio. A changed selection or input is a cache miss, not permission
|
|
737
|
+
to generate replacement audio.
|
|
738
|
+
|
|
739
|
+
This operation never activates or unmutes a provider, synthesizes speech,
|
|
740
|
+
creates stored records, repairs partial audio, or joins pending generation.
|
|
741
|
+
With `storage`, it reads the existing manifest and every ordered audio file.
|
|
742
|
+
Missing or incomplete audio returns `null`; unreadable metadata and other
|
|
743
|
+
storage errors reject. Without storage, it finds only already-complete audio
|
|
744
|
+
retained by this same AI instance. A miss leaves all existing data unchanged.
|
|
745
|
+
|
|
746
|
+
```javascript
|
|
747
|
+
async function readNarration(text, key, signal) {
|
|
748
|
+
return ai.readPreparedTTS({
|
|
749
|
+
parts: [{
|
|
750
|
+
input: text,
|
|
751
|
+
voice: speechSelection.model.defaultVoice,
|
|
752
|
+
speed: 1,
|
|
753
|
+
pauseAfterMs: 0
|
|
754
|
+
}],
|
|
755
|
+
storage: {db: dbopfs, table: 'saved_narration', key},
|
|
756
|
+
identity: {
|
|
757
|
+
model: speechSelection.model,
|
|
758
|
+
runtime: speechSelection.runtime
|
|
759
|
+
},
|
|
760
|
+
signal
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
A returned handle has the ordinary `segments`, `state`, `ready`, `getAudio(index)`,
|
|
766
|
+
and `cancel()` contract and can be passed to `ai.playPreparedTTS()` from the
|
|
767
|
+
application's playback action, including with its existing `audioOutput`.
|
|
768
|
+
The lookup signal governs only the read and rejects cancellation as
|
|
769
|
+
`AbortError`. After a successful return, aborting that signal does not invalidate
|
|
770
|
+
the retained handle; explicit handle cancellation and playback cancellation
|
|
771
|
+
remain separate. A successful read does not promise future autoplay permission
|
|
772
|
+
or prevent a later application-owned removal of stored files.
|
|
773
|
+
|
|
774
|
+
Applications decide which saved narration candidates to inspect. For example,
|
|
775
|
+
newest-first lookup can skip a newer partial preparation and then inspect an
|
|
776
|
+
older complete candidate without activating any model.
|
|
777
|
+
|
|
730
778
|
## Prepare narration once and replay stored audio
|
|
731
779
|
|
|
732
780
|
Use `ai.prepareTTS()` when preparation must continue independently of playback,
|
|
@@ -1631,8 +1679,13 @@ as graph runtimes:
|
|
|
1631
1679
|
Inside the dedicated speech Worker, native CacheStorage is temporarily
|
|
1632
1680
|
unavailable before runtime import, so the selected runtime takes its existing
|
|
1633
1681
|
fetch path. Transformers' public browser/custom/filesystem cache switches are
|
|
1634
|
-
disabled there.
|
|
1635
|
-
|
|
1682
|
+
disabled there. After the runtime import, Kokoro's voice `caches.open()` calls
|
|
1683
|
+
use a Worker-local response cache with actual `match`/`put` results. It retains
|
|
1684
|
+
complete response bodies and metadata in memory for that Worker only; a miss
|
|
1685
|
+
does not fetch, and ordinary fetch still owns durable DBOPFS persistence. This
|
|
1686
|
+
temporary response retention is separate from Kokoro's own in-memory voice
|
|
1687
|
+
arrays. Unload clears it, and an unfinished put cannot repopulate it afterward.
|
|
1688
|
+
The SDK neither clears nor changes saved native caches. The original Worker
|
|
1636
1689
|
environment is restored on unload or failed import. Existing explicit
|
|
1637
1690
|
`removeBrowserSpeechModelCache()` remains a separate caller-selected operation.
|
|
1638
1691
|
|
|
@@ -1646,6 +1699,10 @@ closed resource parts survive cancellation. Existing whole-file caches remain
|
|
|
1646
1699
|
readable without migration. HTTP resume is used only when the server returns
|
|
1647
1700
|
the requested range; otherwise the complete resource is downloaded anew.
|
|
1648
1701
|
Worker termination aborts and joins its parent-owned resource operations.
|
|
1702
|
+
An existing complete resource reports `phase:'load'`, `cached:true`, and
|
|
1703
|
+
`message:'Reading stored model resource'` with its complete shard progress.
|
|
1704
|
+
That event describes a DBOPFS read, not a network download or model-session
|
|
1705
|
+
reload. The resource owner does not fetch that stored response again.
|
|
1649
1706
|
|
|
1650
1707
|
Routing discovery is best effort and is not an admission gate. If the scanner
|
|
1651
1708
|
cannot interpret a module, that module is left unchanged and follows its native
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Physical execution devices
|
|
2
|
+
|
|
3
|
+
The local-AI Core host exposes physical device discovery independently of model
|
|
4
|
+
installation, loading and inference. A device being present does not establish
|
|
5
|
+
that a particular engine can use it. The engine resolves that second question
|
|
6
|
+
when the application explicitly loads its selected model.
|
|
7
|
+
|
|
8
|
+
## List devices without loading a model
|
|
9
|
+
|
|
10
|
+
Use the application's existing Core client:
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
const catalog = await client.invoke('localai.devices', {refresh: false}, {signal});
|
|
14
|
+
console.log(catalog.devices);
|
|
15
|
+
console.log(catalog.issues);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The inventory has `{platform, state, devices, issues}`. `platform` uses Node's
|
|
19
|
+
platform name. `state` is `ready`, `partial` or `unavailable`. Incomplete or failed
|
|
20
|
+
discovery is reported in `issues`; it is not evidence that the machine has no
|
|
21
|
+
devices. This initial discovery implementation uses Windows host APIs. Other
|
|
22
|
+
platforms report this capability as unavailable while retaining their existing
|
|
23
|
+
AI runtime behavior.
|
|
24
|
+
|
|
25
|
+
Each device has the following fields:
|
|
26
|
+
|
|
27
|
+
| Field | Meaning |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| `deviceId` | Stable host-local selection key, or `null` when identity could not be established. |
|
|
30
|
+
| `identitySource` | `windows-pnp-instance`, `host-cpu`, or `null`. |
|
|
31
|
+
| `kind` | `cpu`, `gpu` or `npu`. |
|
|
32
|
+
| `name` | Complete reported display name. Names are not selection identities. |
|
|
33
|
+
| `present` | Whether the queried device is present. |
|
|
34
|
+
| `isHardware`, `isIntegrated` | Reported hardware facts, or `null` when unavailable. |
|
|
35
|
+
| `dedicatedMemoryMiB` | Reported dedicated capacity, or `null`; used only for supported automatic GPU selection. |
|
|
36
|
+
| `addresses` | Observed adapter addresses described below. These are not persistent selection keys. |
|
|
37
|
+
| `processors` | Actual processor records for the aggregate CPU option; an empty array for GPU/NPU records. |
|
|
38
|
+
|
|
39
|
+
GPU and NPU identities are actual, uniquely correlated Windows PnP instance IDs.
|
|
40
|
+
An adapter whose stable identity cannot be established remains in the inventory
|
|
41
|
+
with `deviceId: null` and a corresponding issue. The SDK does not invent a key
|
|
42
|
+
from a name, enumeration order, hardware model number or adapter LUID.
|
|
43
|
+
|
|
44
|
+
The CPU choice uses `deviceId: 'cpu'` and `identitySource: 'host-cpu'`. It selects
|
|
45
|
+
the whole-host CPU execution target, without claiming socket or core affinity.
|
|
46
|
+
Its `processors` entries contain `{deviceId, name, socketDesignation, cores,
|
|
47
|
+
logicalProcessors}`; unavailable optional facts are `null`.
|
|
48
|
+
|
|
49
|
+
`addresses` contains:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
{
|
|
53
|
+
adapterLuid: {highPart, lowPart}, // or null
|
|
54
|
+
pci: {bus, device, function: pciFunction}, // or null
|
|
55
|
+
dxgiAdapterIndex // or null
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The DXGI index is the actual `EnumAdapters` index joined to the adapter's LUID.
|
|
60
|
+
It is never inferred from DXCore or PnP enumeration order. It supplies a possible
|
|
61
|
+
DirectML address, not proof of provider availability or successful execution.
|
|
62
|
+
No CUDA ordinal is inferred from these Windows addresses.
|
|
63
|
+
|
|
64
|
+
An issue contains `{source, code, message, deviceId}`. `source` identifies `cpu`,
|
|
65
|
+
`gpu`, `npu`, `identity` or `dxgi`; `deviceId` is `null` when no stable identity
|
|
66
|
+
was established. Keep the complete diagnostic available to developers rather
|
|
67
|
+
than substituting an empty inventory.
|
|
68
|
+
|
|
69
|
+
## Resolve a saved selection
|
|
70
|
+
|
|
71
|
+
Store the selected physical ID with the application's existing preferences,
|
|
72
|
+
keyed by provider and model. The SDK introduces no new preference store and
|
|
73
|
+
does not change the six-slot model preference tuple.
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
const resolution = await client.invoke('localai.resolveTarget', {
|
|
77
|
+
executionTarget: {deviceId: savedDeviceId}
|
|
78
|
+
}, {signal});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The result contains `{requestedTarget, resolvedDevice, resolution, reason}`:
|
|
82
|
+
|
|
83
|
+
- `matched`: the complete device record was found. Engine support is still a
|
|
84
|
+
separate question.
|
|
85
|
+
- `unavailable` with `device-not-present`: complete relevant discovery did not
|
|
86
|
+
find that saved identity. Preserve the saved selection so the application can
|
|
87
|
+
show that it is currently unavailable.
|
|
88
|
+
- `unsupported` with `inventory-unavailable` or `identity-unresolved`: discovery
|
|
89
|
+
cannot establish the requested identity. Do not describe it as an absent device.
|
|
90
|
+
- `automatic` with `engine-default-required`: an explicit `executionTarget: null`
|
|
91
|
+
asks the selected engine to choose at its activation boundary. The physical
|
|
92
|
+
resolver returns `resolvedDevice: null` because it cannot establish the
|
|
93
|
+
engine's routing capabilities.
|
|
94
|
+
|
|
95
|
+
Calling the resolver does not load a model, change preferences or replace an
|
|
96
|
+
existing activation. A model load that omits `executionTarget` retains that
|
|
97
|
+
API's existing constructor, session-option and default behavior. Explicit
|
|
98
|
+
`null` selects automatic resolution for the new activation; it does not erase
|
|
99
|
+
or relabel a running session.
|
|
100
|
+
|
|
101
|
+
## Engine configuration and execution evidence
|
|
102
|
+
|
|
103
|
+
The physical-target integration described here applies to native ONNX and
|
|
104
|
+
native Laya through Core. Listing host devices does not add physical-device
|
|
105
|
+
selection to browser WebGPU/Wllama or to the separate native llama.cpp, image
|
|
106
|
+
and Whisper owners. Their existing backend options remain unchanged. A browser
|
|
107
|
+
uses the existing Core connection for this inventory; the catalog does not
|
|
108
|
+
create a native connection or replace a browser provider.
|
|
109
|
+
|
|
110
|
+
An engine receives the saved selection as `executionTarget: {deviceId}`. It
|
|
111
|
+
owns provider availability, the physical-to-provider address mapping and the
|
|
112
|
+
actual session configuration. Its public state distinguishes the requested
|
|
113
|
+
target, physical resolution, configured target and observed execution. A
|
|
114
|
+
successful session configuration alone is not evidence that every operation ran
|
|
115
|
+
on a GPU or NPU.
|
|
116
|
+
|
|
117
|
+
Automatic selection can prefer the hardware GPU with the highest reported
|
|
118
|
+
dedicated capacity among supported targets with established routing. An
|
|
119
|
+
integrated GPU remains eligible when it meets those same conditions. An unavailable saved
|
|
120
|
+
target is not silently replaced by another device or a cloud route. See
|
|
121
|
+
[native ONNX](local-ai.md#native-onnx-sessions) and [native decisions](native-decisions.md)
|
|
122
|
+
for the selected engine's load, replacement, cancellation and status contracts.
|
|
123
|
+
|
|
124
|
+
## Catalog and Core ownership
|
|
125
|
+
|
|
126
|
+
SDK development local-AI composition and generated native composition with
|
|
127
|
+
`localAI` selected register one lazy catalog and its independent
|
|
128
|
+
`execution-devices` service. An empty runtime selection still permits device
|
|
129
|
+
discovery. Hosts without local-AI composition retain their existing service
|
|
130
|
+
selection. A manually composed Core can register the service explicitly:
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
import {createExecutionDeviceCatalog} from 'arcane-os/local-ai/execution-devices';
|
|
134
|
+
import {createExecutionDeviceService} from 'arcane-os/core/execution-devices';
|
|
135
|
+
import {createLocalAIService} from 'arcane-os/core/local-ai';
|
|
136
|
+
|
|
137
|
+
const executionDevices = createExecutionDeviceCatalog({signal});
|
|
138
|
+
const services = [
|
|
139
|
+
createExecutionDeviceService({catalog: executionDevices}),
|
|
140
|
+
createLocalAIService(localAI, {appRoot, runtimes, signal, executionDevices})
|
|
141
|
+
];
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Pass these definitions to the existing Core host. The independent service owns
|
|
145
|
+
catalog disposal. The local-AI engine borrows the same catalog; it does not
|
|
146
|
+
dispose a supplied owner or create another discovery process per model.
|
|
147
|
+
|
|
148
|
+
For direct Node use, `createExecutionDeviceCatalog({signal})` returns
|
|
149
|
+
`devices({refresh, signal})`, `resolveTarget({executionTarget, refresh, signal})`
|
|
150
|
+
and `dispose()`. The direct caller owns disposal. Construction performs no
|
|
151
|
+
discovery I/O. Calls reuse/coalesce the host's discovery; `refresh: true` requests
|
|
152
|
+
an explicit new observation. There is no background polling. Closing the owner
|
|
153
|
+
releases its discovery work and native query resources.
|
|
154
|
+
|
|
155
|
+
The catalog queries installed operating-system facilities. It does not install
|
|
156
|
+
drivers, download models, alter system policy or introduce a hardware admission
|
|
157
|
+
rule. A host capability failure remains an honest unavailable/partial result.
|