arcane-os 0.2.3 → 0.3.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 +17 -0
- package/README.md +8 -8
- package/browser-runtime/ARCANE_SDK_BROWSER_RELEASE.json +29 -22
- package/browser-runtime/ai/ARCANE_AI_BROWSER_SPEECH_COMPONENTS.json +203 -0
- package/browser-runtime/ai/browser-kokoro-worker.mjs +11 -2
- package/browser-runtime/ai/browser-speech-artifacts.mjs +3230 -397
- package/browser-runtime/ai/browser-speech-providers.mjs +1141 -157
- package/browser-runtime/ai/browser-speech.mjs +2 -0
- package/browser-runtime/ai/browser-whisper-worker.mjs +11 -2
- package/browser-runtime/ai/model-controller.mjs +285 -95
- package/browser-runtime/ai/speech-worker-client.mjs +247 -32
- package/browser-runtime/ai/speech-worker-runtime.mjs +2310 -167
- package/browser-runtime/event-manager.mjs +1097 -1
- package/docs/architecture.md +2 -2
- package/docs/event-manager.md +155 -27
- package/docs/reference/README.md +20 -20
- package/docs/reference/ai/browser-speech-package-authority.json +835 -0
- package/docs/reference/ai/browser-speech.md +1162 -246
- package/docs/reference/ai/browser-wasm.md +18 -7
- package/docs/reference/availability-and-normalization.md +2 -2
- package/docs/reference/behavioral-testing.md +28 -5
- package/docs/reference/cli.md +115 -7
- package/docs/reference/core/arcane-ai-contracts.md +1 -1
- package/docs/reference/event-manager.md +577 -32
- package/docs/reference/inventory/package-api.json +478 -2
- package/docs/reference/inventory/runtime-components.json +86 -37
- package/docs/reference/inventory/runtime-modules.json +126 -49
- package/docs/reference/mail.md +316 -0
- package/docs/reference/protocols.md +143 -33
- package/docs/reference/runtime-components.md +192 -80
- package/docs/reference/runtime-modules.md +584 -70
- package/docs/reference/sdk-api.md +1014 -25
- package/package.json +5 -4
- package/runtime/ARCANE_RUNTIME_RELEASE.json +145 -140
- package/runtime/arcane/components/app-bar.html +34 -13
- package/runtime/arcane/components/assistant-panel.html +110 -57
- package/runtime/arcane/components/calculator.html +7 -4
- package/runtime/arcane/components/chart.html +58 -17
- package/runtime/arcane/components/chat.html +606 -136
- package/runtime/arcane/components/conversation-view.html +13 -6
- package/runtime/arcane/components/dashboard-config.html +96 -59
- package/runtime/arcane/components/data-maintenance.html +69 -14
- package/runtime/arcane/components/data-view.html +53 -7
- package/runtime/arcane/components/directory-picker.html +118 -32
- package/runtime/arcane/components/document-inspector.html +47 -10
- package/runtime/arcane/components/file-drop.html +81 -35
- package/runtime/arcane/components/file-inspector.html +72 -22
- package/runtime/arcane/components/file-manager.html +374 -79
- package/runtime/arcane/components/integration-settings.html +12 -5
- package/runtime/arcane/components/local-ai-status.html +48 -19
- package/runtime/arcane/components/markdown-document.html +161 -68
- package/runtime/arcane/components/markdown-editor.html +110 -33
- package/runtime/arcane/components/media-embed.html +8 -5
- package/runtime/arcane/components/modal.html +15 -5
- package/runtime/arcane/components/output-panel.html +28 -23
- package/runtime/arcane/components/preferences-form.html +22 -4
- package/runtime/arcane/components/record-timeline.html +18 -2
- package/runtime/arcane/components/relationship-board.html +23 -3
- package/runtime/arcane/components/screen-capture.html +10 -4
- package/runtime/arcane/components/source-code-viewer.html +76 -9
- package/runtime/arcane/components/source-explanation.html +23 -3
- package/runtime/arcane/components/speech.html +453 -131
- package/runtime/arcane/components/summary-strip.html +22 -11
- package/runtime/arcane/components/table.html +39 -21
- package/runtime/arcane/components/task-progress.html +79 -21
- package/runtime/arcane/components/terminal-workspace.html +7 -4
- package/runtime/arcane/components/theme-editor.html +7 -3
- package/runtime/arcane/components/unified-inbox.html +9 -4
- package/runtime/arcane/components/voice-transcription.html +244 -72
- package/runtime/arcane/components/weather-widget.html +5 -3
- package/runtime/arcane/components/web-navigator.html +48 -8
- package/runtime/arcane/entities/Chat.js +1 -1
- package/runtime/arcane/entities/User.js +110 -23
- package/runtime/arcane/modules/AI.js +2109 -130
- package/runtime/arcane/modules/AIProviderRuntime.js +720 -13
- package/runtime/arcane/modules/AIRuntimeState.js +109 -52
- package/runtime/arcane/modules/ApiModelDatabase.js +390 -17
- package/runtime/arcane/modules/BrowserTestSuite.js +205 -28
- package/runtime/arcane/modules/CalculatorEngine.js +63 -3
- package/runtime/arcane/modules/CommunicationAppController.js +588 -28
- package/runtime/arcane/modules/CommunicationHub.js +590 -10
- package/runtime/arcane/modules/ComponentContracts.js +235 -37
- package/runtime/arcane/modules/ConversationTimebox.js +152 -33
- package/runtime/arcane/modules/DBLS.js +40 -7
- package/runtime/arcane/modules/DBOPFS.js +35 -11
- package/runtime/arcane/modules/DataMaintenance.js +12 -2
- package/runtime/arcane/modules/Errors.js +65 -7
- package/runtime/arcane/modules/HTMLImport.js +198 -14
- package/runtime/arcane/modules/LocalAIReadinessController.js +208 -29
- package/runtime/arcane/modules/Mail.js +738 -115
- package/runtime/arcane/modules/MailOutbox.mjs +1395 -0
- package/runtime/arcane/modules/MailTransport.mjs +197 -39
- package/runtime/arcane/modules/Ollama.js +36 -1
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +583 -7
- package/runtime/arcane/modules/PreferenceStore.js +367 -33
- package/runtime/arcane/modules/RecordReviewStore.js +322 -23
- package/runtime/arcane/modules/ScreenCapture.js +1397 -15
- package/runtime/arcane/modules/SpeechPlayback.js +438 -41
- package/runtime/arcane/modules/TerminalClient.js +277 -12
- package/runtime/arcane/modules/ThemeBootstrap.js +80 -6
- package/runtime/arcane/modules/ThemeManager.js +39 -7
- package/runtime/arcane/modules/TimeGuard.js +131 -20
- package/runtime/arcane/modules/WaitForComponent.js +386 -33
- package/schemas/arcane-lock.schema.json +2 -2
- package/src/cli/main.mjs +435 -9
- package/src/event-manager.mjs +1097 -1
- package/src/import-map.mjs +21 -3
- package/src/index.mjs +13 -0
- package/src/installed-sdk-runtime.mjs +112 -0
- package/src/mail-api.mjs +22 -0
- package/src/mail-credentials.mjs +667 -0
- package/src/mail-server.mjs +1769 -0
- package/src/mail.mjs +261 -0
- package/src/sdk-browser-runtime.mjs +85 -41
- package/src/testing-loader.mjs +7 -0
- package/src/toolchain.mjs +3 -0
- package/src/workspace.mjs +1 -1
|
@@ -1,3 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
installBrowserSpeechArtifactModuleWorker,
|
|
3
|
+
installBrowserSpeechWorker,
|
|
4
|
+
} from "./speech-worker-runtime.mjs";
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
const mode = new URL(import.meta.url).searchParams.get("arcaneSpeechWorkerMode");
|
|
7
|
+
|
|
8
|
+
if (mode === "artifact-module-worker") {
|
|
9
|
+
installBrowserSpeechArtifactModuleWorker("stt");
|
|
10
|
+
} else {
|
|
11
|
+
installBrowserSpeechWorker("stt");
|
|
12
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { createArcaneEventSource } from "arcane-os/event-manager";
|
|
2
|
+
|
|
1
3
|
export const ARCANE_AI_ADAPTER_PROTOCOL = "arcane-ai-adapter/1";
|
|
2
4
|
|
|
3
5
|
const SECURITY_KEYS = Object.freeze(["secure", "checks"]);
|
|
4
6
|
const SECURITY_CHECK_KEYS = Object.freeze(["byteLength", "sha256"]);
|
|
5
7
|
const EMPTY_SECURITY_CHECKS = Object.freeze({});
|
|
6
8
|
const EMPTY_MODEL_SECURITY = Object.freeze({ checks: EMPTY_SECURITY_CHECKS });
|
|
9
|
+
const MODEL_CONTROLLER_EVENT_TYPES = Object.freeze(["statechange", "progress"]);
|
|
10
|
+
const MODEL_CONTROLLER_EVENT_TYPE_SET = new Set(MODEL_CONTROLLER_EVENT_TYPES);
|
|
7
11
|
|
|
8
12
|
function closedSecurityRecord(value, keys, label) {
|
|
9
13
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -81,7 +85,8 @@ export function resolveModelSecurity({ app, binding, load } = {}) {
|
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
export function sameModelSecurity(left, right) {
|
|
84
|
-
return left?.
|
|
88
|
+
return left?.secure === right?.secure
|
|
89
|
+
&& left?.checks?.byteLength === right?.checks?.byteLength
|
|
85
90
|
&& left?.checks?.sha256 === right?.checks?.sha256;
|
|
86
91
|
}
|
|
87
92
|
|
|
@@ -142,11 +147,149 @@ function providerMethod(provider, name) {
|
|
|
142
147
|
return typeof provider?.[name] === "function" ? provider[name].bind(provider) : null;
|
|
143
148
|
}
|
|
144
149
|
|
|
150
|
+
function invalidStatus(cause) {
|
|
151
|
+
return new ArcaneAIError(
|
|
152
|
+
"ARCANE_AI_PROVIDER_STATUS_INVALID",
|
|
153
|
+
"The LLM provider returned an invalid status record.",
|
|
154
|
+
{ cause, operation: "status" },
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
145
158
|
function copyError(error) {
|
|
146
159
|
if (!error) return null;
|
|
160
|
+
let code;
|
|
161
|
+
let message;
|
|
162
|
+
if (typeof error === "object" || typeof error === "function") {
|
|
163
|
+
try {
|
|
164
|
+
const codeDescriptor = Object.getOwnPropertyDescriptor(error, "code");
|
|
165
|
+
const messageDescriptor = Object.getOwnPropertyDescriptor(error, "message");
|
|
166
|
+
if ((codeDescriptor && !("value" in codeDescriptor))
|
|
167
|
+
|| (messageDescriptor && !("value" in messageDescriptor))) {
|
|
168
|
+
throw invalidStatus();
|
|
169
|
+
}
|
|
170
|
+
code = codeDescriptor?.value;
|
|
171
|
+
message = messageDescriptor?.value;
|
|
172
|
+
} catch (copyErrorFailure) {
|
|
173
|
+
if (copyErrorFailure instanceof ArcaneAIError) throw copyErrorFailure;
|
|
174
|
+
throw invalidStatus(copyErrorFailure);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return Object.freeze({
|
|
178
|
+
code: String(code ?? "ARCANE_AI_REQUEST_FAILED"),
|
|
179
|
+
message: String(message ?? "The Arcane AI operation failed."),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function isModelControllerListener(value) {
|
|
184
|
+
return typeof value === "function"
|
|
185
|
+
|| Boolean(value && typeof value === "object" && typeof value.handleEvent === "function");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function copyProviderStatus(value) {
|
|
189
|
+
if (value === undefined || value === null) return {};
|
|
190
|
+
if (typeof value !== "object" || Array.isArray(value)) throw invalidStatus();
|
|
191
|
+
try {
|
|
192
|
+
const copy = {};
|
|
193
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
194
|
+
if (typeof key !== "string") throw invalidStatus();
|
|
195
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
196
|
+
if (!descriptor || !("value" in descriptor)) throw invalidStatus();
|
|
197
|
+
Object.defineProperty(copy, key, {
|
|
198
|
+
value: descriptor.value,
|
|
199
|
+
enumerable: descriptor.enumerable,
|
|
200
|
+
configurable: true,
|
|
201
|
+
writable: true,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return copy;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (error instanceof ArcaneAIError) throw error;
|
|
207
|
+
throw invalidStatus(error);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const MAX_PROGRESS_DEPTH = 8;
|
|
212
|
+
const MAX_PROGRESS_ENTRIES = 256;
|
|
213
|
+
|
|
214
|
+
function invalidProgress(cause) {
|
|
215
|
+
return new ArcaneAIError(
|
|
216
|
+
"ARCANE_AI_PROVIDER_PROGRESS_INVALID",
|
|
217
|
+
"The LLM provider returned an invalid progress record.",
|
|
218
|
+
{ cause, operation: "load" },
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function copyProgressValue(value, state, depth = 0) {
|
|
223
|
+
if (value === null || typeof value !== "object") return value;
|
|
224
|
+
if (depth > MAX_PROGRESS_DEPTH || state.seen.has(value)) {
|
|
225
|
+
throw invalidProgress();
|
|
226
|
+
}
|
|
227
|
+
const prototype = Object.getPrototypeOf(value);
|
|
228
|
+
if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {
|
|
229
|
+
throw invalidProgress();
|
|
230
|
+
}
|
|
231
|
+
state.seen.add(value);
|
|
232
|
+
const copy = Array.isArray(value) ? [] : prototype === null ? Object.create(null) : {};
|
|
233
|
+
try {
|
|
234
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
235
|
+
if (Array.isArray(value) && key === "length") continue;
|
|
236
|
+
state.entries += 1;
|
|
237
|
+
if (state.entries > MAX_PROGRESS_ENTRIES || typeof key !== "string") {
|
|
238
|
+
throw invalidProgress();
|
|
239
|
+
}
|
|
240
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
241
|
+
if (!descriptor || !("value" in descriptor)) throw invalidProgress();
|
|
242
|
+
Object.defineProperty(copy, key, {
|
|
243
|
+
value: copyProgressValue(descriptor.value, state, depth + 1),
|
|
244
|
+
enumerable: descriptor.enumerable,
|
|
245
|
+
configurable: false,
|
|
246
|
+
writable: false,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return Object.freeze(copy);
|
|
250
|
+
} finally {
|
|
251
|
+
state.seen.delete(value);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function copyProgress(progress) {
|
|
256
|
+
if (progress === undefined || progress === null) return null;
|
|
257
|
+
if (typeof progress !== "object" || Array.isArray(progress)) {
|
|
258
|
+
throw invalidProgress();
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
return copyProgressValue(progress, { seen: new WeakSet(), entries: 0 });
|
|
262
|
+
} catch (error) {
|
|
263
|
+
if (error instanceof ArcaneAIError) throw error;
|
|
264
|
+
throw invalidProgress(error);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function publicProgress(progress) {
|
|
269
|
+
if (!progress || typeof progress !== "object") return null;
|
|
270
|
+
const file = progress.file && typeof progress.file === "object"
|
|
271
|
+
? Object.freeze({
|
|
272
|
+
...(Number.isSafeInteger(progress.file.index) ? { index: progress.file.index } : {}),
|
|
273
|
+
...(Number.isSafeInteger(progress.file.count) ? { count: progress.file.count } : {}),
|
|
274
|
+
...(typeof progress.file.name === "string" ? { name: progress.file.name } : {}),
|
|
275
|
+
...(Number.isSafeInteger(progress.file.loaded) ? { loaded: progress.file.loaded } : {}),
|
|
276
|
+
...(progress.file.total === null || Number.isSafeInteger(progress.file.total)
|
|
277
|
+
? { total: progress.file.total }
|
|
278
|
+
: {}),
|
|
279
|
+
})
|
|
280
|
+
: null;
|
|
147
281
|
return Object.freeze({
|
|
148
|
-
|
|
149
|
-
|
|
282
|
+
...(typeof progress.modelId === "string" ? { modelId: progress.modelId } : {}),
|
|
283
|
+
...(typeof progress.phase === "string" ? { phase: progress.phase } : {}),
|
|
284
|
+
...(Number.isSafeInteger(progress.loaded) ? { loaded: progress.loaded } : {}),
|
|
285
|
+
...(progress.total === null || Number.isSafeInteger(progress.total)
|
|
286
|
+
? { total: progress.total }
|
|
287
|
+
: {}),
|
|
288
|
+
...(progress.percent === null
|
|
289
|
+
|| (typeof progress.percent === "number" && Number.isFinite(progress.percent))
|
|
290
|
+
? { percent: progress.percent }
|
|
291
|
+
: {}),
|
|
292
|
+
...(file ? { file } : {}),
|
|
150
293
|
});
|
|
151
294
|
}
|
|
152
295
|
|
|
@@ -183,15 +326,6 @@ function fireAndForget(callback, ...args) {
|
|
|
183
326
|
}
|
|
184
327
|
}
|
|
185
328
|
|
|
186
|
-
class ControllerEvent {
|
|
187
|
-
constructor(type, detail, target) {
|
|
188
|
-
this.type = type;
|
|
189
|
-
this.detail = detail;
|
|
190
|
-
this.target = target;
|
|
191
|
-
this.currentTarget = target;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
329
|
let generatedRequestId = 0;
|
|
196
330
|
|
|
197
331
|
function requestIdentity(value) {
|
|
@@ -225,7 +359,7 @@ export class ModelController {
|
|
|
225
359
|
#provider;
|
|
226
360
|
#loadPolicy;
|
|
227
361
|
#security;
|
|
228
|
-
#
|
|
362
|
+
#events;
|
|
229
363
|
#loadPromise = null;
|
|
230
364
|
#readyPolicyResolved = false;
|
|
231
365
|
#unloadPromise = null;
|
|
@@ -233,6 +367,7 @@ export class ModelController {
|
|
|
233
367
|
#operationGeneration = 0;
|
|
234
368
|
#disposing = false;
|
|
235
369
|
#disposed = false;
|
|
370
|
+
#disposeUnloadAdmission = false;
|
|
236
371
|
#activeStreams = new Set();
|
|
237
372
|
#fallbackState = "unloaded";
|
|
238
373
|
#progress = null;
|
|
@@ -258,47 +393,68 @@ export class ModelController {
|
|
|
258
393
|
this.#provider = provider;
|
|
259
394
|
this.#loadPolicy = loadPolicy;
|
|
260
395
|
this.#security = normalizeModelSecurity(security, "app security");
|
|
396
|
+
this.#events = createArcaneEventSource(this, {
|
|
397
|
+
source: "ai-model-controller",
|
|
398
|
+
eventTypes: MODEL_CONTROLLER_EVENT_TYPES,
|
|
399
|
+
});
|
|
261
400
|
}
|
|
262
401
|
|
|
263
402
|
status() {
|
|
264
|
-
const providerStatus =
|
|
265
|
-
|
|
266
|
-
|
|
403
|
+
const providerStatus = copyProviderStatus(
|
|
404
|
+
providerMethod(this.#provider, "status")?.(
|
|
405
|
+
Object.freeze({ security: this.#security }),
|
|
406
|
+
),
|
|
407
|
+
);
|
|
408
|
+
const progress = copyProgress(providerStatus.progress ?? this.#progress);
|
|
267
409
|
return Object.freeze({
|
|
268
410
|
...providerStatus,
|
|
269
411
|
kind: "llm",
|
|
270
412
|
state: providerStatus.state ?? this.#fallbackState,
|
|
271
|
-
progress
|
|
272
|
-
error: providerStatus.error ??
|
|
413
|
+
progress,
|
|
414
|
+
error: copyError(providerStatus.error ?? this.#error),
|
|
273
415
|
});
|
|
274
416
|
}
|
|
275
417
|
|
|
276
|
-
addEventListener(type, listener) {
|
|
277
|
-
if (
|
|
278
|
-
|
|
279
|
-
listeners.add(listener);
|
|
280
|
-
this.#listeners.set(type, listeners);
|
|
418
|
+
addEventListener(type, listener, options) {
|
|
419
|
+
if (!MODEL_CONTROLLER_EVENT_TYPE_SET.has(type) || !isModelControllerListener(listener)) return;
|
|
420
|
+
return this.#events.addEventListener(type, listener, options);
|
|
281
421
|
}
|
|
282
422
|
|
|
283
|
-
removeEventListener(type, listener) {
|
|
284
|
-
|
|
423
|
+
removeEventListener(type, listener, options) {
|
|
424
|
+
if (!MODEL_CONTROLLER_EVENT_TYPE_SET.has(type) || !isModelControllerListener(listener)) return;
|
|
425
|
+
return this.#events.removeEventListener(type, listener, options);
|
|
285
426
|
}
|
|
286
427
|
|
|
287
428
|
on(type, listener) {
|
|
288
429
|
this.addEventListener(type, listener);
|
|
289
|
-
|
|
430
|
+
const controller = this;
|
|
431
|
+
let active = true;
|
|
432
|
+
function unsubscribeModelControllerEvent() {
|
|
433
|
+
if (!active) return false;
|
|
434
|
+
active = false;
|
|
435
|
+
controller.removeEventListener(type, listener);
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
Object.defineProperty(unsubscribeModelControllerEvent, "dispose", {
|
|
439
|
+
value: unsubscribeModelControllerEvent,
|
|
440
|
+
enumerable: false,
|
|
441
|
+
configurable: false,
|
|
442
|
+
writable: false,
|
|
443
|
+
});
|
|
444
|
+
return unsubscribeModelControllerEvent;
|
|
290
445
|
}
|
|
291
446
|
|
|
292
|
-
#emit(type) {
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
447
|
+
#emit(type, operationId) {
|
|
448
|
+
const status = this.status();
|
|
449
|
+
const progress = type === "progress" ? publicProgress(status.progress) : null;
|
|
450
|
+
this.#events.dispatch(type, status, {
|
|
451
|
+
operationId,
|
|
452
|
+
publicDetail: Object.freeze({
|
|
453
|
+
...(typeof status.state === "string" ? { state: status.state } : {}),
|
|
454
|
+
...(progress ? { progress } : {}),
|
|
455
|
+
...(typeof status.error?.code === "string" ? { code: status.error.code } : {}),
|
|
456
|
+
}),
|
|
457
|
+
});
|
|
302
458
|
}
|
|
303
459
|
|
|
304
460
|
#assertOperational() {
|
|
@@ -345,60 +501,71 @@ export class ModelController {
|
|
|
345
501
|
}
|
|
346
502
|
const wasReady = state === "ready";
|
|
347
503
|
const operationGeneration = ++this.#operationGeneration;
|
|
504
|
+
const operationId = `${this.#events.instanceId}:load:${operationGeneration.toString(36)}`;
|
|
505
|
+
let resolveOperation;
|
|
506
|
+
let rejectOperation;
|
|
507
|
+
const operation = new Promise(function createModelLoadOperation(resolve, reject) {
|
|
508
|
+
resolveOperation = resolve;
|
|
509
|
+
rejectOperation = reject;
|
|
510
|
+
});
|
|
511
|
+
this.#loadPromise = operation;
|
|
348
512
|
if (!wasReady) {
|
|
349
513
|
this.#fallbackState = "loading";
|
|
350
514
|
this.#progress = null;
|
|
351
515
|
this.#error = null;
|
|
352
|
-
this.#emit("statechange");
|
|
516
|
+
this.#emit("statechange", operationId);
|
|
517
|
+
}
|
|
518
|
+
const controller = this;
|
|
519
|
+
function reportModelLoadProgress(progress) {
|
|
520
|
+
if (
|
|
521
|
+
operationGeneration !== controller.#operationGeneration
|
|
522
|
+
|| controller.#disposing
|
|
523
|
+
|| controller.#disposed
|
|
524
|
+
) return;
|
|
525
|
+
controller.#progress = copyProgress(progress);
|
|
526
|
+
controller.#emit("progress", operationId);
|
|
353
527
|
}
|
|
354
|
-
|
|
528
|
+
async function executeModelLoad() {
|
|
355
529
|
try {
|
|
356
530
|
await load(options, Object.freeze({
|
|
357
531
|
protocol: ARCANE_AI_ADAPTER_PROTOCOL,
|
|
358
532
|
kind: "llm",
|
|
359
533
|
operation: "load",
|
|
360
534
|
signal,
|
|
361
|
-
security:
|
|
362
|
-
reportProgress:
|
|
363
|
-
if (
|
|
364
|
-
operationGeneration !== this.#operationGeneration
|
|
365
|
-
|| this.#disposing
|
|
366
|
-
|| this.#disposed
|
|
367
|
-
) return;
|
|
368
|
-
this.#progress = progress;
|
|
369
|
-
this.#emit("progress");
|
|
370
|
-
},
|
|
535
|
+
security: controller.#security,
|
|
536
|
+
reportProgress: reportModelLoadProgress,
|
|
371
537
|
}));
|
|
372
538
|
if (
|
|
373
|
-
operationGeneration !==
|
|
374
|
-
||
|
|
375
|
-
||
|
|
376
|
-
) return
|
|
377
|
-
|
|
539
|
+
operationGeneration !== controller.#operationGeneration
|
|
540
|
+
|| controller.#disposing
|
|
541
|
+
|| controller.#disposed
|
|
542
|
+
) return controller.status();
|
|
543
|
+
controller.#readyPolicyResolved = true;
|
|
378
544
|
if (!wasReady) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
545
|
+
controller.#fallbackState = "ready";
|
|
546
|
+
controller.#progress = null;
|
|
547
|
+
controller.#emit("statechange", operationId);
|
|
382
548
|
}
|
|
383
|
-
return
|
|
549
|
+
return controller.status();
|
|
384
550
|
} catch (error) {
|
|
385
551
|
const normalized = normalizeArcaneAIError(error, { operation: "load", signal });
|
|
386
552
|
if (
|
|
387
|
-
operationGeneration ===
|
|
388
|
-
&& !
|
|
389
|
-
&& !
|
|
553
|
+
operationGeneration === controller.#operationGeneration
|
|
554
|
+
&& !controller.#disposing
|
|
555
|
+
&& !controller.#disposed
|
|
390
556
|
&& !wasReady
|
|
391
557
|
) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
558
|
+
controller.#fallbackState = "error";
|
|
559
|
+
controller.#error = normalized;
|
|
560
|
+
controller.#emit("statechange", operationId);
|
|
395
561
|
}
|
|
396
562
|
throw normalized;
|
|
397
563
|
} finally {
|
|
398
|
-
|
|
564
|
+
if (controller.#loadPromise === operation) controller.#loadPromise = null;
|
|
399
565
|
}
|
|
400
|
-
}
|
|
401
|
-
|
|
566
|
+
}
|
|
567
|
+
void executeModelLoad().then(resolveOperation, rejectOperation);
|
|
568
|
+
return operation;
|
|
402
569
|
}
|
|
403
570
|
|
|
404
571
|
async #ready(loadOptions = {}) {
|
|
@@ -427,53 +594,66 @@ export class ModelController {
|
|
|
427
594
|
}
|
|
428
595
|
|
|
429
596
|
async unload(options = {}) {
|
|
597
|
+
if ((this.#disposed || this.#disposing) && !this.#disposeUnloadAdmission) {
|
|
598
|
+
this.#assertOperational();
|
|
599
|
+
}
|
|
430
600
|
if (this.#unloadPromise) return this.#unloadPromise;
|
|
431
601
|
const unload = providerMethod(this.#provider, "unload");
|
|
432
602
|
if (!unload) throw new ArcaneAIError("ARCANE_AI_UNAVAILABLE", "The LLM provider cannot unload.");
|
|
433
603
|
const signal = options.signal ?? null;
|
|
434
604
|
const inFlightLoad = this.#loadPromise;
|
|
435
605
|
const operationGeneration = ++this.#operationGeneration;
|
|
606
|
+
const operationId = `${this.#events.instanceId}:unload:${operationGeneration.toString(36)}`;
|
|
436
607
|
const context = Object.freeze({
|
|
437
608
|
protocol: ARCANE_AI_ADAPTER_PROTOCOL,
|
|
438
609
|
kind: "llm",
|
|
439
610
|
operation: "unload",
|
|
440
611
|
signal,
|
|
441
612
|
});
|
|
613
|
+
let resolveOperation;
|
|
614
|
+
let rejectOperation;
|
|
615
|
+
const operation = new Promise(function createModelUnloadOperation(resolve, reject) {
|
|
616
|
+
resolveOperation = resolve;
|
|
617
|
+
rejectOperation = reject;
|
|
618
|
+
});
|
|
619
|
+
this.#unloadPromise = operation;
|
|
442
620
|
this.#fallbackState = "unloading";
|
|
443
|
-
this.#emit("statechange");
|
|
444
|
-
|
|
621
|
+
this.#emit("statechange", operationId);
|
|
622
|
+
const controller = this;
|
|
623
|
+
async function executeModelUnload() {
|
|
445
624
|
try {
|
|
446
|
-
await
|
|
625
|
+
await controller.#closeStreams("The browser-WASM model is unloading.");
|
|
447
626
|
// The first call asks the provider to cancel its in-flight load. A
|
|
448
627
|
// provider owns its public status, so wait for the captured load and
|
|
449
628
|
// reassert unload afterward; a late provider-owned `ready` state can
|
|
450
629
|
// otherwise outlive this controller's generation guard.
|
|
451
630
|
await unload(options, context);
|
|
452
631
|
if (inFlightLoad) {
|
|
453
|
-
await inFlightLoad.catch(()
|
|
632
|
+
await inFlightLoad.catch(function ignoreSupersededModelLoad() {});
|
|
454
633
|
await unload(options, context);
|
|
455
634
|
}
|
|
456
|
-
if (operationGeneration ===
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
635
|
+
if (operationGeneration === controller.#operationGeneration) {
|
|
636
|
+
controller.#fallbackState = "unloaded";
|
|
637
|
+
controller.#readyPolicyResolved = false;
|
|
638
|
+
controller.#progress = null;
|
|
639
|
+
controller.#error = null;
|
|
640
|
+
controller.#emit("statechange", operationId);
|
|
462
641
|
}
|
|
463
|
-
return
|
|
642
|
+
return controller.status();
|
|
464
643
|
} catch (error) {
|
|
465
644
|
const normalized = normalizeArcaneAIError(error, { operation: "unload", signal });
|
|
466
|
-
if (operationGeneration ===
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
645
|
+
if (operationGeneration === controller.#operationGeneration) {
|
|
646
|
+
controller.#fallbackState = "error";
|
|
647
|
+
controller.#error = normalized;
|
|
648
|
+
controller.#emit("statechange", operationId);
|
|
470
649
|
}
|
|
471
650
|
throw normalized;
|
|
472
651
|
} finally {
|
|
473
|
-
|
|
652
|
+
if (controller.#unloadPromise === operation) controller.#unloadPromise = null;
|
|
474
653
|
}
|
|
475
|
-
}
|
|
476
|
-
|
|
654
|
+
}
|
|
655
|
+
void executeModelUnload().then(resolveOperation, rejectOperation);
|
|
656
|
+
return operation;
|
|
477
657
|
}
|
|
478
658
|
|
|
479
659
|
async chat(request = {}) {
|
|
@@ -673,30 +853,40 @@ export class ModelController {
|
|
|
673
853
|
this.#operationGeneration += 1;
|
|
674
854
|
let resolveOperation;
|
|
675
855
|
let rejectOperation;
|
|
676
|
-
const operation = new Promise((resolve, reject)
|
|
856
|
+
const operation = new Promise(function createModelDisposeOperation(resolve, reject) {
|
|
677
857
|
resolveOperation = resolve;
|
|
678
858
|
rejectOperation = reject;
|
|
679
859
|
});
|
|
680
860
|
this.#disposePromise = operation;
|
|
681
|
-
|
|
861
|
+
const controller = this;
|
|
862
|
+
async function executeModelDispose() {
|
|
682
863
|
try {
|
|
683
|
-
|
|
684
|
-
|
|
864
|
+
let unloadOperation;
|
|
865
|
+
try {
|
|
866
|
+
controller.#disposeUnloadAdmission = true;
|
|
867
|
+
unloadOperation = controller.unload(options);
|
|
868
|
+
} finally {
|
|
869
|
+
controller.#disposeUnloadAdmission = false;
|
|
870
|
+
}
|
|
871
|
+
await unloadOperation;
|
|
872
|
+
const dispose = providerMethod(controller.#provider, "dispose");
|
|
685
873
|
if (dispose) await dispose(options);
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
874
|
+
controller.#disposed = true;
|
|
875
|
+
const status = controller.status();
|
|
876
|
+
controller.#events.dispose();
|
|
877
|
+
return status;
|
|
689
878
|
} catch (error) {
|
|
690
879
|
throw normalizeArcaneAIError(error, {
|
|
691
880
|
operation: "dispose",
|
|
692
881
|
signal: options.signal,
|
|
693
882
|
});
|
|
694
883
|
} finally {
|
|
695
|
-
|
|
884
|
+
controller.#disposing = false;
|
|
696
885
|
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
|
|
886
|
+
}
|
|
887
|
+
void executeModelDispose().then(resolveOperation, rejectOperation);
|
|
888
|
+
operation.catch(function resetFailedModelDispose() {
|
|
889
|
+
if (controller.#disposePromise === operation) controller.#disposePromise = null;
|
|
700
890
|
});
|
|
701
891
|
return operation;
|
|
702
892
|
}
|