arcane-os 0.56.0 → 0.58.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +6 -2
  3. package/browser-runtime/ai/core-local.mjs +306 -0
  4. package/browser-runtime/ai/core-onnx.mjs +96 -0
  5. package/browser-runtime/ai/onnx-tensors.mjs +85 -0
  6. package/browser-runtime/core/client.mjs +151 -16
  7. package/docs/architecture.md +5 -2
  8. package/docs/platform-targets.md +30 -13
  9. package/docs/reference/README.md +38 -3
  10. package/docs/reference/availability-and-normalization.md +9 -6
  11. package/docs/reference/cli.md +29 -9
  12. package/docs/reference/core-client.md +80 -0
  13. package/docs/reference/core-native-packaging.md +304 -12
  14. package/docs/reference/core-preferences.md +49 -0
  15. package/docs/reference/diarization.md +206 -0
  16. package/docs/reference/git-text-snapshot.md +104 -0
  17. package/docs/reference/inventory/package-api.json +330 -22
  18. package/docs/reference/local-ai.md +331 -0
  19. package/docs/reference/protocols.md +97 -20
  20. package/docs/reference/pwa.md +5 -2
  21. package/docs/reference/runtime-modules.md +6 -5
  22. package/docs/reference/sdk-api.md +611 -63
  23. package/package.json +15 -3
  24. package/runtime/arcane/modules/LocalAIReadiness.js +3 -1
  25. package/schemas/arcane-app.schema.json +55 -1
  26. package/schemas/arcane-package.schema.json +44 -0
  27. package/schemas/native-build-plan.schema.json +27 -1
  28. package/schemas/target-adapter.schema.json +2 -0
  29. package/src/app-descriptor.mjs +18 -2
  30. package/src/cli/main.mjs +20 -4
  31. package/src/constants.mjs +2 -0
  32. package/src/core/host.mjs +23 -0
  33. package/src/core/hosts/macos/ArcaneCoreProcess.h +85 -0
  34. package/src/core/hosts/macos/ArcaneCoreProcess.m +357 -0
  35. package/src/core/hosts/macos/ArcaneHost.h +86 -0
  36. package/src/core/hosts/macos/ArcaneHost.m +455 -0
  37. package/src/core/hosts/macos/ArcaneLauncher.m +432 -0
  38. package/src/core/hosts/windows/ArcaneCoreProcess.cs +3 -3
  39. package/src/core/hosts/windows/ArcaneHost.cs +695 -0
  40. package/src/core/hosts/windows/ArcaneHost.manifest +22 -0
  41. package/src/core/hosts/windows/ArcaneLauncher.cs +239 -0
  42. package/src/core/services/local-ai.mjs +621 -0
  43. package/src/core/services/packaged-web.mjs +89 -0
  44. package/src/core/services/preferences.mjs +201 -0
  45. package/src/core-classic-source.mjs +2 -2
  46. package/src/dev-server.mjs +9 -5
  47. package/src/diarization/build.mjs +35 -0
  48. package/src/diarization/index.mjs +432 -0
  49. package/src/diarization/native/CMakeLists.txt +9 -0
  50. package/src/diarization/native/main.cpp +269 -0
  51. package/src/diarization/service.mjs +129 -0
  52. package/src/git-text-snapshot.mjs +265 -0
  53. package/src/import-map.mjs +2 -0
  54. package/src/index.mjs +1 -0
  55. package/src/local-ai/archive.mjs +52 -0
  56. package/src/local-ai/config.mjs +62 -0
  57. package/src/local-ai/dev.mjs +390 -0
  58. package/src/local-ai/discover.mjs +71 -0
  59. package/src/local-ai/http.mjs +106 -0
  60. package/src/local-ai/install.mjs +459 -0
  61. package/src/local-ai/onnx-worker.mjs +85 -0
  62. package/src/local-ai/onnx.mjs +368 -0
  63. package/src/local-ai/server.mjs +262 -0
  64. package/src/native/macos-provider.mjs +291 -0
  65. package/src/native/portable-layout.mjs +37 -4
  66. package/src/native/portable-provider.mjs +44 -8
  67. package/src/native/windows-provider.mjs +262 -0
  68. package/src/native-plan.mjs +9 -1
  69. package/src/native-provider-loader.mjs +10 -2
  70. package/src/packager/core.mjs +6 -2
  71. package/src/process.mjs +184 -20
  72. package/src/scaffold.mjs +1 -1
  73. package/src/targets/index.mjs +11 -1
  74. package/src/templates/workspace-template.mjs +6 -5
  75. package/src/toolchain.mjs +33 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.58.0
4
+
5
+ - Add native ONNX sessions through `arcane-os/local-ai/onnx` and browser Core
6
+ access through `arcane-os/ai/core-onnx`. Explicit runtime selection uses the
7
+ upstream Node runtime distribution, retained per-session workers, complete
8
+ tensor transport, authoritative loaded state and owned cancellation/exit.
9
+ Apps select their graphs, feeds and processing; no model is chosen or fetched.
10
+ - Add native Nemotron diarization runtime preparation, helper build and Core
11
+ integration. Preserve complete framed input/output, independent streams,
12
+ cancellation and process drain. Runtime/model selection remains explicit.
13
+ - Add the reusable Core preferences service and Git text snapshots, preserving
14
+ complete caller data and existing application-owned storage and repository
15
+ choices. Extend raw process I/O without changing existing UTF-8 defaults.
16
+ - Add shared WebKit document lifecycle and macOS native host composition source,
17
+ portable target contracts, and explicit packaged HTTP serving through the
18
+ existing HTTP server. Default HTTPS and ordinary browser behavior remain.
19
+ macOS executable build and platform acceptance remain separately pending.
20
+ - No automatic runtime/model installation, application default switch or saved
21
+ data migration is introduced. Native host archive delivery is separate from
22
+ npm publication; this release does not claim an available Windows/Mac archive.
23
+
24
+ ## 0.57.0
25
+
26
+ - Add local model chat through explicit llama.cpp/Ollama runtime requirements,
27
+ reusable installation and bundling, a retained Core local-AI service, and an
28
+ optional browser-to-Core provider. Preserve complete requests and streamed
29
+ responses, model readiness, cancellation and owned process shutdown. Apps own
30
+ model selection and defaults; preparation does not block ordinary page startup.
31
+ - Add authored Core service selection and launch context. Factories receive
32
+ authored options and the separate app-root/runtime context; explicit launch
33
+ context overrides remain supported. Portable packages can bundle explicitly
34
+ selected local-AI runtimes without changing browser-only output.
35
+ - Add Windows x64 executable packaging through the public Windows native
36
+ provider and the matching release's retained WebView2/Node SEA host asset.
37
+ Client injection precedes app scripts, service requests retain complete JSON,
38
+ and accepted service work drains before the owned Core process exits.
39
+ - Add `runProcess` cancellation mode `close-input` for owned graceful shutdown;
40
+ the existing `terminate-tree` default remains unchanged. Includes separately
41
+ delivered macOS process source, without claiming a complete macOS host.
42
+ - Correct the documented Node document-library subpath and lexical-search
43
+ example. No preferences service, Nemotron diarization, native ONNX, native
44
+ Kokoro or local image generation is introduced in this release.
45
+
3
46
  ## 0.56.0
4
47
 
5
48
  - Add explicit `package.nativeResources` selection for native-only application
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.56.0` SDK contract. Development applications declare
23
+ This checkout defines the `0.58.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;
@@ -131,7 +131,7 @@ each device. Keep these local files ignored by Git. The same certificate pair
131
131
  works for any selected app in that workspace. The command reports missing TLS
132
132
  files instead of starting an HTTP listener.
133
133
 
134
- HTTPS is the default for development and required for packaged browser previews.
134
+ HTTPS is the default for development and packaged browser previews.
135
135
  Plain `npm run dev` binds to localhost with the same workspace certificate pair.
136
136
  An explicit `--host` overrides the bind address, and `--port` selects the HTTPS port.
137
137
  The paired HTTP listener returns 308 redirects and uses an OS-assigned port
@@ -152,6 +152,10 @@ This selects one HTTP content listener and prints HTTP network URLs. It reads
152
152
  no certificate pair and serves the same application and generated PWA routes.
153
153
  PWA and storage availability still depend on the browser's secure-context
154
154
  rules; see [HTTP development](docs/reference/cli.md#explicit-http-development).
155
+ API callers may also explicitly select `http:true` with `mode:'packaged'` to
156
+ serve the selected packaged web root through the same listener owner. See
157
+ [`startDevServer()`](docs/reference/sdk-api.md#startdevserver) for readiness,
158
+ start-URL and shutdown behavior.
155
159
 
156
160
  Open the URL printed by the server. The generated page owns its import map and
157
161
  Arcane theme; its application module is `modules/App.js` at the repository root.
@@ -0,0 +1,306 @@
1
+ import Is from '../dependencies/strong-type/index.js';
2
+ import {getInstalledCoreClient} from '../core/client.mjs';
3
+
4
+ const is = new Is(false);
5
+ const PROVIDER_PROTOCOL = 'arcane-ai-provider/2';
6
+ const AUTHORITY_PROTOCOL = 'arcane-ai-model-authority/1';
7
+
8
+ function localAIError(code, message, cause) {
9
+ const error = cause === undefined ? new Error(message) : new Error(message, {cause});
10
+ error.code = code;
11
+ return error;
12
+ }
13
+
14
+ function cancelled(reason) {
15
+ const error = localAIError('ARCANE_AI_REQUEST_ABORTED', 'The local AI request was cancelled.', reason);
16
+ error.name = 'AbortError';
17
+ return error;
18
+ }
19
+
20
+ /**
21
+ * Browser-safe llama.cpp access through an existing Core connection. The app
22
+ * selects its model; Core owns the running engine and its loaded sessions.
23
+ * Creating this provider neither connects a transport nor installs a runtime.
24
+ */
25
+ export function createCoreLocalAIProvider({client = getInstalledCoreClient(), id = 'llama.cpp'} = {}) {
26
+ if (!is.string(id) || !id.trim()) throw new TypeError('The Core AI provider id must be a nonempty string.');
27
+ if (client !== null && (!is.function(client?.invoke) || !is.function(client?.events?.on))) {
28
+ throw new TypeError('The Core AI client must expose invoke and events.on.');
29
+ }
30
+
31
+ let runtime = null;
32
+ let models = [];
33
+ let selection = null;
34
+ let disposed = false;
35
+ let loading = false;
36
+ let sequence = 0;
37
+ let lifecycleRevision = 0;
38
+ const requests = new Set();
39
+
40
+ function requireClient() {
41
+ if (disposed) throw localAIError('ARCANE_AI_PROVIDER_DISPOSED', 'The Core AI provider is disposed.');
42
+ if (!client) throw localAIError('ARCANE_AI_CORE_UNAVAILABLE', 'Local llama.cpp requires an available Core connection.');
43
+ return client;
44
+ }
45
+
46
+ function requireSelection(value) {
47
+ if (value?.providerId !== id || !is.string(value?.modelId) || !value.modelId
48
+ || value.localOnly !== true) {
49
+ throw localAIError('ARCANE_AI_MODEL_AUTHORITY_REQUIRED', 'The local provider requires an explicit matching provider and model.');
50
+ }
51
+ return value;
52
+ }
53
+
54
+ function loadedModel() {
55
+ return selection && models.find(function selectedModel(model) {
56
+ return model.id === selection.modelId && model.loaded === true;
57
+ });
58
+ }
59
+
60
+ function modelReady() {
61
+ return runtime?.available === true && runtime.state === 'ready' && Boolean(loadedModel());
62
+ }
63
+
64
+ function status() {
65
+ const loaded = !disposed && modelReady();
66
+ return {
67
+ state: disposed ? 'disposed' : loading ? 'loading' : loaded ? 'ready'
68
+ : runtime?.error ? 'error' : runtime?.available === true ? 'unloaded' : 'unavailable',
69
+ loaded,
70
+ busy: loading || requests.size > 0,
71
+ modelId: selection?.modelId ?? null,
72
+ available: !disposed && runtime?.available === true,
73
+ installed: runtime?.installed === true,
74
+ error: runtime?.error ?? null
75
+ };
76
+ }
77
+
78
+ function acceptRuntime(value) {
79
+ runtime = value ?? null;
80
+ models = is.array(runtime?.models) ? runtime.models : [];
81
+ for (const request of requests) {
82
+ const model = models.find(function requestModel(record) { return record.id === request.modelId; });
83
+ if (model?.loaded === true) request.loaded = true;
84
+ if (runtime?.available !== true || (request.loaded && (runtime.state !== 'ready' || model?.loaded !== true))) {
85
+ request.controller.abort(localAIError('ARCANE_AI_MODEL_NOT_READY', 'The requested local model is no longer ready.', runtime?.error));
86
+ }
87
+ }
88
+ return status();
89
+ }
90
+
91
+ const unsubscribe = client?.events.on('localai.state', function localRuntimeChanged(snapshot) {
92
+ if (disposed || !is.array(snapshot?.runtimes)) return;
93
+ lifecycleRevision += 1;
94
+ acceptRuntime(snapshot.runtimes.find(function llamaRuntime(value) {
95
+ return value.id === 'llama.cpp';
96
+ }));
97
+ }) ?? function noCoreSubscription() {};
98
+
99
+ function authority(value) {
100
+ return {protocol: AUTHORITY_PROTOCOL, providerId: id, modelId: value.modelId};
101
+ }
102
+
103
+ async function inspect(value, {signal} = {}) {
104
+ requireSelection(value);
105
+ if (signal?.aborted) throw cancelled(signal.reason);
106
+ try {
107
+ const core = requireClient();
108
+ const statusRevision = lifecycleRevision;
109
+ const current = await core.invoke('llama.status', {}, {signal});
110
+ if (lifecycleRevision === statusRevision) acceptRuntime(current);
111
+ if (runtime?.available !== true) {
112
+ return {
113
+ available: false,
114
+ code: 'ARCANE_AI_PROVIDER_UNAVAILABLE',
115
+ message: 'The local llama.cpp runtime is unavailable.',
116
+ error: runtime?.error ?? null
117
+ };
118
+ }
119
+ if (!models.some(function matchesModel(model) { return model.id === value.modelId; })) {
120
+ return {
121
+ available: false,
122
+ code: 'ARCANE_AI_MODEL_UNAVAILABLE',
123
+ message: 'The selected model is unavailable in the local llama.cpp runtime.'
124
+ };
125
+ }
126
+ return {available: true, authority: authority(value)};
127
+ } catch (error) {
128
+ if (signal?.aborted || error?.name === 'AbortError') throw error;
129
+ acceptRuntime({...runtime, available: false, state: 'error', error});
130
+ return {available: false, code: error.code ?? 'ARCANE_AI_CORE_UNAVAILABLE', message: error.message, error};
131
+ }
132
+ }
133
+
134
+ async function load({selection: value, signal, progress} = {}) {
135
+ const core = requireClient();
136
+ requireSelection(value);
137
+ if (signal?.aborted) throw cancelled(signal.reason);
138
+ selection = value;
139
+ loading = true;
140
+ try {
141
+ progress?.({phase: 'loading', modelId: value.modelId});
142
+ const revision = lifecycleRevision;
143
+ const current = await core.invoke('llama.load', {model: value.modelId}, {signal});
144
+ if (lifecycleRevision === revision) acceptRuntime(current);
145
+ if (!modelReady()) throw localAIError('ARCANE_AI_MODEL_NOT_READY', 'Core did not report the selected model loaded and ready.', current?.error);
146
+ progress?.({phase: 'ready', modelId: value.modelId});
147
+ return {authority: authority(value), status: {...status(), state: 'ready', busy: false}};
148
+ } finally {
149
+ loading = false;
150
+ }
151
+ }
152
+
153
+ function ownRequest(signal, modelId) {
154
+ const controller = new AbortController();
155
+ function forwardAbort() { controller.abort(signal.reason); }
156
+ signal?.addEventListener('abort', forwardAbort, {once: true});
157
+ if (signal?.aborted) forwardAbort();
158
+ const request = {controller, result: null, modelId, loaded: models.some(function loadedTarget(model) {
159
+ return model.id === modelId && model.loaded === true;
160
+ })};
161
+ requests.add(request);
162
+ request.release = function releaseRequest() {
163
+ signal?.removeEventListener('abort', forwardAbort);
164
+ requests.delete(request);
165
+ };
166
+ return request;
167
+ }
168
+
169
+ function streamRequest(core, parameters, signal) {
170
+ const request = ownRequest(signal, parameters.model);
171
+ const chunks = [];
172
+ const readers = [];
173
+ let complete = false;
174
+ let failure = null;
175
+ sequence += 1;
176
+ const streamId = is.function(core.uuid) ? core.uuid()
177
+ : globalThis.crypto?.randomUUID?.() ?? `${id}:${Date.now()}:${sequence}:${Math.random()}`;
178
+
179
+ function flush() {
180
+ while (readers.length && chunks.length && !request.controller.signal.aborted) {
181
+ readers.shift().resolve({value: chunks.shift(), done: false});
182
+ }
183
+ if (!complete || (chunks.length && !request.controller.signal.aborted)) return;
184
+ while (readers.length) {
185
+ const reader = readers.shift();
186
+ if (failure) reader.reject(failure);
187
+ else reader.resolve({value: undefined, done: true});
188
+ }
189
+ }
190
+
191
+ const stopChunks = core.events.on('llama.chunk', function receiveChunk(event) {
192
+ if (event?.streamId !== streamId || complete || request.controller.signal.aborted) return;
193
+ chunks.push(event.chunk);
194
+ flush();
195
+ });
196
+
197
+ request.result = Promise.resolve().then(function invokeStream() {
198
+ return core.invoke('llama.chat', {...parameters, stream: true, streamId}, {signal: request.controller.signal});
199
+ }).then(function streamCompleted(result) {
200
+ if (request.controller.signal.aborted) throw cancelled(request.controller.signal.reason);
201
+ return result;
202
+ }).catch(function streamFailed(error) {
203
+ failure = error;
204
+ throw error;
205
+ }).finally(function releaseStream() {
206
+ complete = true;
207
+ stopChunks();
208
+ request.release();
209
+ flush();
210
+ });
211
+ request.result.catch(function observeRetainedStreamFailure() {});
212
+
213
+ function cancel(reason) {
214
+ if (!complete) request.controller.abort(reason);
215
+ return request.result.then(function cancelledAfterSuccess() {}, function cancelledAfterFailure() {});
216
+ }
217
+
218
+ return {
219
+ result: request.result,
220
+ cancel,
221
+ [Symbol.asyncIterator]() {
222
+ return {
223
+ next() {
224
+ if (request.controller.signal.aborted) return Promise.reject(failure ?? cancelled(request.controller.signal.reason));
225
+ if (chunks.length) return Promise.resolve({value: chunks.shift(), done: false});
226
+ if (complete) return failure ? Promise.reject(failure) : Promise.resolve({value: undefined, done: true});
227
+ return new Promise(function awaitChunk(resolve, reject) { readers.push({resolve, reject}); });
228
+ },
229
+ async return() {
230
+ await cancel();
231
+ return {value: undefined, done: true};
232
+ }
233
+ };
234
+ }
235
+ };
236
+ }
237
+
238
+ async function request({selection: value, operation, payload, signal} = {}) {
239
+ const core = requireClient();
240
+ requireSelection(value);
241
+ if (signal?.aborted) throw cancelled(signal.reason);
242
+ if (loading || selection?.modelId !== value.modelId) {
243
+ throw localAIError('ARCANE_AI_MODEL_NOT_READY', 'Load the selected local model before requesting inference.');
244
+ }
245
+ const model = payload?.model ?? value.modelId;
246
+ if (!models.some(function knownModel(record) { return record.id === model; })) {
247
+ const revision = lifecycleRevision;
248
+ const current = await core.invoke('llama.status', {}, {signal});
249
+ if (lifecycleRevision === revision) acceptRuntime(current);
250
+ }
251
+ if (!models.some(function knownModel(record) { return record.id === model; })) {
252
+ throw localAIError('ARCANE_AI_MODEL_UNAVAILABLE', 'The requested model is unavailable in the local llama.cpp runtime.');
253
+ }
254
+ if (runtime?.available !== true || runtime.state !== 'ready'
255
+ || (model === value.modelId && !modelReady())) {
256
+ throw localAIError('ARCANE_AI_MODEL_NOT_READY', 'The requested local runtime or selected model is not ready.');
257
+ }
258
+ const parameters = {model, payload};
259
+ if (operation === 'stream') return streamRequest(core, parameters, signal);
260
+ if (operation !== 'chat') throw localAIError('ARCANE_AI_PROVIDER_OPERATION_UNAVAILABLE', 'The local llama.cpp provider supports chat and stream.');
261
+ const pending = ownRequest(signal, model);
262
+ pending.result = Promise.resolve().then(function invokeChat() {
263
+ return core.invoke('llama.chat', {...parameters, stream: false}, {signal: pending.controller.signal});
264
+ }).then(function chatCompleted(result) {
265
+ if (pending.controller.signal.aborted) throw cancelled(pending.controller.signal.reason);
266
+ return result;
267
+ }).finally(pending.release);
268
+ return pending.result;
269
+ }
270
+
271
+ async function unload({selection: value = selection, signal} = {}) {
272
+ const core = requireClient();
273
+ if (signal?.aborted) throw cancelled(signal.reason);
274
+ for (const request of requests) request.controller.abort();
275
+ await Promise.allSettled([...requests].map(function activeResult(request) { return request.result; }));
276
+ if (!value) return status();
277
+ requireSelection(value);
278
+ const revision = lifecycleRevision;
279
+ const current = await core.invoke('llama.unload', {model: value.modelId}, {signal});
280
+ if (lifecycleRevision === revision) acceptRuntime(current);
281
+ selection = null;
282
+ return status();
283
+ }
284
+
285
+ async function dispose(options = {}) {
286
+ if (disposed) return status();
287
+ if (selection || requests.size) await unload(options);
288
+ disposed = true;
289
+ unsubscribe();
290
+ return status();
291
+ }
292
+
293
+ return {
294
+ protocol: PROVIDER_PROTOCOL,
295
+ role: 'llm',
296
+ id,
297
+ localOnly: true,
298
+ catalog: function catalog() { return [...models]; },
299
+ status,
300
+ inspect,
301
+ load,
302
+ request,
303
+ unload,
304
+ dispose
305
+ };
306
+ }
@@ -0,0 +1,96 @@
1
+ import {getInstalledCoreClient} from '../core/client.mjs';
2
+ import {createArcaneEventSource} from '../event-manager.mjs';
3
+ import {encodeTensorMap, decodeTensorMap, encodeTensorFetches} from './onnx-tensors.mjs';
4
+
5
+ function unavailable(message) {
6
+ const error = new Error(message);
7
+ error.code = 'ARCANE_ONNX_CORE_UNAVAILABLE';
8
+ return error;
9
+ }
10
+
11
+ /** Optional system ONNX access. Construction neither connects nor installs. */
12
+ export function createCoreONNXRuntime({client = getInstalledCoreClient()} = {}) {
13
+ const owner = {};
14
+ const events = createArcaneEventSource(owner, {source: 'core-onnx', eventTypes: ['onnx.state']});
15
+ const lifetime = new AbortController();
16
+ const pending = new Set();
17
+ let state = {id: 'onnx', installed: false, available: false, state: 'unknown', models: []};
18
+ let revision = 0;
19
+ let closed = false;
20
+
21
+ function current() { return {...state, busy: pending.size > 0, closed}; }
22
+ function publish() { events.dispatch('onnx.state', current()); }
23
+ function accept(value) {
24
+ state = value ?? {id: 'onnx', installed: false, available: false, state: 'unavailable', models: []};
25
+ revision += 1;
26
+ publish();
27
+ }
28
+
29
+ const unsubscribe = client?.events.on('localai.state', function localStateChanged(snapshot) {
30
+ if (!closed) accept(snapshot?.runtimes?.find(function onnxRuntime(runtime) { return runtime.id === 'onnx'; }));
31
+ }) ?? function noCoreSubscription() {};
32
+
33
+ async function invoke(method, parameters, {signal, timeoutMs = 0} = {}) {
34
+ if (closed) throw unavailable('The Core ONNX accessor is closed.');
35
+ if (!client) throw unavailable('Local ONNX requires an available Core connection.');
36
+ const request = {};
37
+ const operationSignal = signal ? AbortSignal.any([signal, lifetime.signal]) : lifetime.signal;
38
+ operationSignal.throwIfAborted();
39
+ pending.add(request);
40
+ publish();
41
+ try {
42
+ return await client.invoke(method, parameters, {signal: operationSignal, timeoutMs});
43
+ } finally {
44
+ pending.delete(request);
45
+ if (!closed) publish();
46
+ }
47
+ }
48
+
49
+ async function inspect(options) {
50
+ const before = revision;
51
+ try {
52
+ const result = await invoke('onnx.status', {}, options);
53
+ if (!closed && revision === before) accept(result);
54
+ return current();
55
+ } catch (error) {
56
+ if (!closed && !options?.signal?.aborted && revision === before) {
57
+ accept({...state, available: false, state: 'unavailable', error});
58
+ }
59
+ throw error;
60
+ }
61
+ }
62
+
63
+ async function load({id, model, sessionOptions, signal, timeoutMs} = {}) {
64
+ return invoke('onnx.load', {id, model, sessionOptions}, {signal, timeoutMs});
65
+ }
66
+
67
+ async function run({id, feeds, fetches, runOptions, signal, timeoutMs} = {}) {
68
+ const result = await invoke('onnx.run', {
69
+ id, feeds: encodeTensorMap(feeds),
70
+ fetches: encodeTensorFetches(fetches), runOptions
71
+ }, {signal, timeoutMs});
72
+ return decodeTensorMap(result);
73
+ }
74
+
75
+ function unload({id, signal, timeoutMs} = {}) {
76
+ return invoke('onnx.unload', {id}, {signal, timeoutMs});
77
+ }
78
+
79
+ function subscribe(listener) {
80
+ const stop = events.on('onnx.state', function changed(event) { listener(event.detail); });
81
+ try { listener(current()); } catch (error) { stop(); throw error; }
82
+ return stop;
83
+ }
84
+
85
+ function close() {
86
+ if (closed) return;
87
+ closed = true;
88
+ lifetime.abort();
89
+ unsubscribe();
90
+ state = {...state, available: false, state: 'closed'};
91
+ publish();
92
+ events.dispose();
93
+ }
94
+
95
+ return {load, run, unload, inspect, current, subscribe, close};
96
+ }
@@ -0,0 +1,85 @@
1
+ import Is from '../dependencies/strong-type/index.js';
2
+
3
+ const is = new Is(false);
4
+ const typedArrays = {
5
+ Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array,
6
+ Int32Array, Uint32Array, Float32Array, Float64Array,
7
+ BigInt64Array, BigUint64Array, Float16Array: globalThis.Float16Array
8
+ };
9
+
10
+ /** Tensor framing belongs at JSON transport boundaries, outside inference. */
11
+ export function encodeTensorMap(tensors) {
12
+ if (tensors === undefined || tensors === null) return tensors;
13
+ return Object.fromEntries(Object.entries(tensors).map(function encodeEntry([name, tensor]) {
14
+ return [name, tensor === null ? null : {
15
+ type: tensor.type, dims: tensor.dims, data: encodeTensorData(tensor.data)
16
+ }];
17
+ }));
18
+ }
19
+
20
+ export function decodeTensorMap(tensors) {
21
+ if (tensors === undefined || tensors === null) return tensors;
22
+ return Object.fromEntries(Object.entries(tensors).map(function decodeEntry([name, tensor]) {
23
+ return [name, tensor === null ? null : {
24
+ type: tensor.type, dims: tensor.dims, data: decodeTensorData(tensor.data)
25
+ }];
26
+ }));
27
+ }
28
+
29
+ export function encodeTensorFetches(fetches) {
30
+ return is.array(fetches) ? fetches : encodeTensorMap(fetches);
31
+ }
32
+
33
+ export function decodeTensorFetches(fetches) {
34
+ return is.array(fetches) ? fetches : decodeTensorMap(fetches);
35
+ }
36
+
37
+ function encodeTensorData(data) {
38
+ if (is.typedArray(data)) {
39
+ const arrayType = Object.prototype.toString.call(data).slice(8, -1);
40
+ const content = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
41
+ return {encoding: 'base64', arrayType, content: encodeBase64(content)};
42
+ }
43
+ if (is.array(data)) return data.map(encodeScalar);
44
+ return data;
45
+ }
46
+
47
+ function decodeTensorData(data) {
48
+ if (data?.encoding === 'base64') {
49
+ const Constructor = typedArrays[data.arrayType];
50
+ if (!is.function(Constructor)) {
51
+ throw new TypeError(`Tensor data representation ${data.arrayType} is unavailable in this runtime.`);
52
+ }
53
+ const content = decodeBase64(data.content);
54
+ return new Constructor(content.buffer);
55
+ }
56
+ if (is.array(data)) return data.map(decodeScalar);
57
+ return data;
58
+ }
59
+
60
+ function encodeScalar(value) {
61
+ if (is.bigint(value)) return {encoding: 'bigint', value: String(value)};
62
+ if (is.negativeZero(value)) return {encoding: 'number', value: '-0'};
63
+ if (is.number(value) && !is.finite(value)) return {encoding: 'number', value: String(value)};
64
+ return value;
65
+ }
66
+
67
+ function decodeScalar(value) {
68
+ if (value?.encoding === 'bigint') return BigInt(value.value);
69
+ if (value?.encoding === 'number') return Number(value.value);
70
+ return value;
71
+ }
72
+
73
+ function encodeBase64(content) {
74
+ if (is.function(content.toBase64)) return content.toBase64();
75
+ let binary = '';
76
+ for (const value of content) binary += String.fromCharCode(value);
77
+ return btoa(binary);
78
+ }
79
+
80
+ function decodeBase64(content) {
81
+ if (is.function(Uint8Array.fromBase64)) return Uint8Array.fromBase64(content);
82
+ return Uint8Array.from(atob(content), function characterCode(character) {
83
+ return character.charCodeAt(0);
84
+ });
85
+ }