arcane-os 0.80.0 → 0.81.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 +25 -0
- package/README.md +1 -1
- package/browser-runtime/ai/browser-wasm-llm-provider.mjs +97 -19
- package/browser-runtime/ai/dbopfs-model-parts.mjs +12 -5
- package/browser-runtime/ai/dbopfs-model-resources.mjs +2 -5
- package/docs/reference/ai/browser-speech.md +11 -0
- package/docs/reference/ai/browser-wasm.md +21 -10
- package/docs/reference/core-native-packaging.md +9 -0
- package/docs/reference/inventory/package-api.json +8 -8
- package/docs/reference/native-decisions.md +74 -16
- package/docs/reference/sdk-api.md +22 -6
- package/package.json +1 -1
- package/runtime/arcane/modules/AI.js +10 -5
- package/src/core/hosts/windows/ArcaneHost.cs +21 -5
- package/src/core/services/decisions.mjs +16 -2
- package/src/local-ai/decision-tokenizer-worker.mjs +1 -1
- package/src/local-ai/decision-tokenizer.mjs +2 -2
- package/src/local-ai/decisions.mjs +33 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.81.0
|
|
4
|
+
|
|
5
|
+
- Add per-activation native decision `family`, `model`, `revision`, and `dtype`
|
|
6
|
+
selection through the direct runtime and Core service. Support Laya FP16 and
|
|
7
|
+
Julia 1 FP32 with their own model companions and tokenizer family, preserving
|
|
8
|
+
Laya FP32 defaults, complete input rows and actual output tensors. Keep
|
|
9
|
+
requested replacement metadata separate from the active model and retain
|
|
10
|
+
prepared files through actual worker exit.
|
|
11
|
+
- Report cached model loading as real stored-part progress and preserve known
|
|
12
|
+
GGUF shard totals. Forward complete progress details and cancellation through
|
|
13
|
+
cache reads without presenting cached data as a download or inventing totals
|
|
14
|
+
for an unfinished network stream.
|
|
15
|
+
- Add object-form `AI.streamRequest({speech:false})` for text-only operations.
|
|
16
|
+
Completion, failure and cancellation leave unrelated prepared, native and
|
|
17
|
+
buffered speech alone. Existing defaults, positional methods and explicit
|
|
18
|
+
speech APIs retain their behavior.
|
|
19
|
+
- Close and drain the Windows host after WebView2 `BrowserProcessExited`,
|
|
20
|
+
preserving its full diagnostic instead of leaving a blank window awaiting
|
|
21
|
+
manual closure. Existing renderer/Core-only handling and saved profiles stay
|
|
22
|
+
unchanged. This source correction does not establish the cause of the browser
|
|
23
|
+
exit; executable consumers require the matching rebuilt host archive.
|
|
24
|
+
- Keep dependencies, app-control, native GGUF activation and native Kokoro
|
|
25
|
+
routing unchanged. Source/package verification is separate from actual model,
|
|
26
|
+
audio or application execution.
|
|
27
|
+
|
|
3
28
|
## 0.80.0
|
|
4
29
|
|
|
5
30
|
- Add `assetProjectionId` and `resourcePaths` to native decision factories and
|
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.
|
|
23
|
+
This checkout defines the `0.81.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;
|
|
@@ -667,19 +667,21 @@ export function createDbopfsModelStore({
|
|
|
667
667
|
}
|
|
668
668
|
}
|
|
669
669
|
|
|
670
|
-
function
|
|
670
|
+
function createModelProgressReporter(members, onProgress, retainFailure) {
|
|
671
671
|
const shards = members.map(function initialMemberProgress(member) {
|
|
672
672
|
return {name: member.name, completed: 0, total: null, unit: 'shards'};
|
|
673
673
|
});
|
|
674
674
|
let completed = 0;
|
|
675
|
+
let phase = "cache-check";
|
|
675
676
|
let activeTransfers = 0;
|
|
676
677
|
let transferMode = members.length === 1 ? "probing" : "files";
|
|
677
678
|
let timer = null;
|
|
678
679
|
let lastPublishedAt = 0;
|
|
680
|
+
let progressFailure = null;
|
|
679
681
|
|
|
680
682
|
function progressRecord() {
|
|
681
683
|
return completeValue({
|
|
682
|
-
phase
|
|
684
|
+
phase,
|
|
683
685
|
completed,
|
|
684
686
|
total: members.length,
|
|
685
687
|
unit: "files",
|
|
@@ -713,18 +715,28 @@ export function createDbopfsModelStore({
|
|
|
713
715
|
return true;
|
|
714
716
|
} catch (error) {
|
|
715
717
|
stopTimer();
|
|
718
|
+
progressFailure ??= error;
|
|
716
719
|
retainFailure(error);
|
|
717
720
|
return false;
|
|
718
721
|
}
|
|
719
722
|
}
|
|
720
723
|
|
|
724
|
+
function assertProgress() {
|
|
725
|
+
if (progressFailure !== null) throw progressFailure;
|
|
726
|
+
}
|
|
727
|
+
|
|
721
728
|
return completeValue({
|
|
722
729
|
reportShards(memberIndex, value) {
|
|
730
|
+
assertProgress();
|
|
723
731
|
shards[memberIndex] = {...value, name: members[memberIndex].name};
|
|
732
|
+
if (value.phase === "download" || activeTransfers > 0) phase = "download";
|
|
733
|
+
else if (value.phase === "load") phase = "load";
|
|
724
734
|
publishSafely({force: true});
|
|
735
|
+
if (value.phase === "load") assertProgress();
|
|
725
736
|
},
|
|
726
737
|
beginTransfer() {
|
|
727
738
|
activeTransfers += 1;
|
|
739
|
+
phase = "download";
|
|
728
740
|
publishSafely({ force: true });
|
|
729
741
|
},
|
|
730
742
|
completeMember() {
|
|
@@ -745,14 +757,17 @@ export function createDbopfsModelStore({
|
|
|
745
757
|
publishSafely({ force: true });
|
|
746
758
|
},
|
|
747
759
|
restoreMember(memberIndex) {
|
|
748
|
-
|
|
760
|
+
assertProgress();
|
|
761
|
+
shards[memberIndex] = {...shards[memberIndex], phase: 'load', cached: true, state: 'cached'};
|
|
762
|
+
if (activeTransfers === 0) phase = "load";
|
|
749
763
|
completed += 1;
|
|
750
764
|
publishSafely({ force: true });
|
|
765
|
+
assertProgress();
|
|
751
766
|
},
|
|
752
767
|
start() {
|
|
753
768
|
if (!publishSafely({ force: true })) return;
|
|
754
769
|
if (onProgress !== null && timer === null) {
|
|
755
|
-
timer = setInterval(function
|
|
770
|
+
timer = setInterval(function publishModelProgressTick() {
|
|
756
771
|
publishSafely();
|
|
757
772
|
}, MODEL_DOWNLOAD_PROGRESS_INTERVAL_MS);
|
|
758
773
|
}
|
|
@@ -795,6 +810,9 @@ export function createDbopfsModelStore({
|
|
|
795
810
|
const partFiles = new Array(ranges.length);
|
|
796
811
|
const pendingRangeIndexes = [];
|
|
797
812
|
let completedParts = 0;
|
|
813
|
+
progress?.reportShards(memberIndex, {
|
|
814
|
+
phase: 'load', completed: 0, total: ranges.length, unit: 'shards', cached: true,
|
|
815
|
+
});
|
|
798
816
|
for (let rangeIndex = 0; rangeIndex < ranges.length; rangeIndex += 1) {
|
|
799
817
|
throwIfAborted(signal, "install");
|
|
800
818
|
const range = ranges[rangeIndex];
|
|
@@ -804,15 +822,20 @@ export function createDbopfsModelStore({
|
|
|
804
822
|
if (partFile?.size === expected) {
|
|
805
823
|
partFiles[rangeIndex] = partFile;
|
|
806
824
|
completedParts += 1;
|
|
825
|
+
progress?.reportShards(memberIndex, {
|
|
826
|
+
phase: 'load', completed: completedParts, total: ranges.length, unit: 'shards', cached: true,
|
|
827
|
+
});
|
|
807
828
|
} else {
|
|
808
829
|
if (partFile) await removeEntry(partName);
|
|
809
830
|
pendingRangeIndexes.push(rangeIndex);
|
|
810
831
|
}
|
|
811
832
|
}
|
|
812
833
|
progress?.reportShards(memberIndex, {
|
|
834
|
+
phase: 'load',
|
|
813
835
|
completed: completedParts,
|
|
814
836
|
total: ranges.length,
|
|
815
|
-
unit: 'shards'
|
|
837
|
+
unit: 'shards',
|
|
838
|
+
cached: true,
|
|
816
839
|
});
|
|
817
840
|
const linked = linkAbortSignal(signal);
|
|
818
841
|
const downloadSignal = linked.controller.signal;
|
|
@@ -868,6 +891,7 @@ export function createDbopfsModelStore({
|
|
|
868
891
|
throwIfAborted(downloadSignal, "install");
|
|
869
892
|
completedParts += 1;
|
|
870
893
|
progress?.reportShards(memberIndex, {
|
|
894
|
+
phase: 'download',
|
|
871
895
|
completed: completedParts,
|
|
872
896
|
total: ranges.length,
|
|
873
897
|
unit: 'shards'
|
|
@@ -1120,13 +1144,27 @@ export function createDbopfsModelStore({
|
|
|
1120
1144
|
});
|
|
1121
1145
|
}
|
|
1122
1146
|
|
|
1123
|
-
async function availableModelFiles(names, signal) {
|
|
1147
|
+
async function availableModelFiles(names, signal, progress) {
|
|
1124
1148
|
const modelFiles = [];
|
|
1125
|
-
for (
|
|
1149
|
+
for (let memberIndex = 0; memberIndex < names.models.length; memberIndex += 1) {
|
|
1150
|
+
const entry = names.models[memberIndex];
|
|
1126
1151
|
throwIfAborted(signal, "install");
|
|
1127
1152
|
// Existing whole-file caches remain readable; new downloads use parts.
|
|
1128
|
-
|
|
1153
|
+
let modelFile = await parts.read(entry.name, {
|
|
1154
|
+
signal,
|
|
1155
|
+
onProgress: function reportStoredShards(value) {
|
|
1156
|
+
progress?.reportShards(memberIndex, value);
|
|
1157
|
+
},
|
|
1158
|
+
});
|
|
1159
|
+
if (!modelFile) {
|
|
1160
|
+
modelFile = await file(entry.name);
|
|
1161
|
+
if (modelFile) progress?.reportShards(memberIndex, {
|
|
1162
|
+
phase: 'load', completed: 1, total: 1, unit: 'shards', cached: true,
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1129
1165
|
modelFiles.push(modelFile);
|
|
1166
|
+
if (modelFile) progress?.restoreMember(memberIndex);
|
|
1167
|
+
throwIfAborted(signal, "install");
|
|
1130
1168
|
}
|
|
1131
1169
|
return modelFiles;
|
|
1132
1170
|
}
|
|
@@ -1136,9 +1174,16 @@ export function createDbopfsModelStore({
|
|
|
1136
1174
|
ranges,
|
|
1137
1175
|
signal,
|
|
1138
1176
|
availableNames = null,
|
|
1177
|
+
onProgress = null,
|
|
1139
1178
|
) {
|
|
1140
1179
|
const partFiles = [];
|
|
1141
1180
|
let lastModified = 0;
|
|
1181
|
+
function reportStoredRange() {
|
|
1182
|
+
throwIfAborted(signal, "install");
|
|
1183
|
+
onProgress?.({phase: 'load', completed: partFiles.length,
|
|
1184
|
+
total: ranges.length, unit: 'shards', cached: true});
|
|
1185
|
+
}
|
|
1186
|
+
reportStoredRange();
|
|
1142
1187
|
for (const range of ranges) {
|
|
1143
1188
|
throwIfAborted(signal, "install");
|
|
1144
1189
|
const partName = rangePartName(modelName, range);
|
|
@@ -1151,10 +1196,12 @@ export function createDbopfsModelStore({
|
|
|
1151
1196
|
return null;
|
|
1152
1197
|
}
|
|
1153
1198
|
partFiles.push(partFile);
|
|
1199
|
+
reportStoredRange();
|
|
1154
1200
|
if (is.finite(partFile.lastModified)) {
|
|
1155
1201
|
lastModified = Math.max(lastModified, partFile.lastModified);
|
|
1156
1202
|
}
|
|
1157
1203
|
}
|
|
1204
|
+
throwIfAborted(signal, "install");
|
|
1158
1205
|
return completeValue({
|
|
1159
1206
|
file: new Blob(partFiles, { type: "application/octet-stream" }),
|
|
1160
1207
|
lastModified,
|
|
@@ -1168,6 +1215,7 @@ export function createDbopfsModelStore({
|
|
|
1168
1215
|
total,
|
|
1169
1216
|
signal,
|
|
1170
1217
|
availableNames = null,
|
|
1218
|
+
onProgress = null,
|
|
1171
1219
|
) {
|
|
1172
1220
|
const ranges = rangePlanForInstall(total, signal);
|
|
1173
1221
|
return storedRangeCandidateForPlan(
|
|
@@ -1175,10 +1223,11 @@ export function createDbopfsModelStore({
|
|
|
1175
1223
|
ranges,
|
|
1176
1224
|
signal,
|
|
1177
1225
|
availableNames,
|
|
1226
|
+
onProgress,
|
|
1178
1227
|
);
|
|
1179
1228
|
}
|
|
1180
1229
|
|
|
1181
|
-
async function storedRangeMember(member, modelName, signal) {
|
|
1230
|
+
async function storedRangeMember(member, modelName, signal, onProgress) {
|
|
1182
1231
|
const names = await memberRangePartNames(modelName);
|
|
1183
1232
|
const availableNames = names === null ? null : new Set(names);
|
|
1184
1233
|
const declaredTotal = is.safeInteger(member.bytes) && member.bytes > 0
|
|
@@ -1190,6 +1239,7 @@ export function createDbopfsModelStore({
|
|
|
1190
1239
|
declaredTotal,
|
|
1191
1240
|
signal,
|
|
1192
1241
|
availableNames,
|
|
1242
|
+
onProgress,
|
|
1193
1243
|
);
|
|
1194
1244
|
if (declared) {
|
|
1195
1245
|
await removeStaleRangePartsAfterCompletion(
|
|
@@ -1214,6 +1264,7 @@ export function createDbopfsModelStore({
|
|
|
1214
1264
|
total,
|
|
1215
1265
|
signal,
|
|
1216
1266
|
availableNames,
|
|
1267
|
+
onProgress,
|
|
1217
1268
|
);
|
|
1218
1269
|
if (
|
|
1219
1270
|
candidate
|
|
@@ -1224,16 +1275,20 @@ export function createDbopfsModelStore({
|
|
|
1224
1275
|
) selected = candidate;
|
|
1225
1276
|
}
|
|
1226
1277
|
if (selected === null) return null;
|
|
1278
|
+
throwIfAborted(signal, "install");
|
|
1227
1279
|
await removeStaleRangePartsAfterCompletion(
|
|
1228
1280
|
modelName,
|
|
1229
1281
|
selected.ranges,
|
|
1230
1282
|
);
|
|
1283
|
+
onProgress?.({phase: 'load', completed: selected.ranges.length,
|
|
1284
|
+
total: selected.ranges.length, unit: 'shards', cached: true});
|
|
1285
|
+
throwIfAborted(signal, "install");
|
|
1231
1286
|
return selected.file;
|
|
1232
1287
|
}
|
|
1233
1288
|
|
|
1234
|
-
async function availableResumableModelFiles(source, names, signal) {
|
|
1289
|
+
async function availableResumableModelFiles(source, names, signal, progress) {
|
|
1235
1290
|
const members = sourceMetadata(source).files;
|
|
1236
|
-
const modelFiles = await availableModelFiles(names, signal);
|
|
1291
|
+
const modelFiles = await availableModelFiles(names, signal, progress);
|
|
1237
1292
|
await removeRangePartsBehindWholeFiles(members, names, modelFiles);
|
|
1238
1293
|
for (let memberIndex = 0; memberIndex < modelFiles.length; memberIndex += 1) {
|
|
1239
1294
|
if (modelFiles[memberIndex]) continue;
|
|
@@ -1241,20 +1296,40 @@ export function createDbopfsModelStore({
|
|
|
1241
1296
|
members[memberIndex],
|
|
1242
1297
|
names.models[memberIndex].name,
|
|
1243
1298
|
signal,
|
|
1299
|
+
function reportStoredRangeShards(value) {
|
|
1300
|
+
progress?.reportShards(memberIndex, value);
|
|
1301
|
+
},
|
|
1244
1302
|
);
|
|
1303
|
+
if (modelFiles[memberIndex]) progress?.restoreMember(memberIndex);
|
|
1304
|
+
throwIfAborted(signal, "install");
|
|
1245
1305
|
}
|
|
1246
1306
|
return modelFiles;
|
|
1247
1307
|
}
|
|
1248
1308
|
|
|
1249
1309
|
async function openCached(source, {
|
|
1250
1310
|
signal,
|
|
1311
|
+
onProgress = null,
|
|
1251
1312
|
} = {}) {
|
|
1252
1313
|
const names = storageName(source);
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
files: completeValue(modelFiles),
|
|
1314
|
+
let failure = null;
|
|
1315
|
+
const progress = createModelProgressReporter(sourceMetadata(source).files, onProgress, function retainProgressFailure(error) {
|
|
1316
|
+
failure ??= error;
|
|
1257
1317
|
});
|
|
1318
|
+
try {
|
|
1319
|
+
progress.start();
|
|
1320
|
+
if (failure !== null) throw failure;
|
|
1321
|
+
const modelFiles = await availableResumableModelFiles(source, names, signal, progress);
|
|
1322
|
+
if (failure !== null) throw failure;
|
|
1323
|
+
if (!modelFiles.every(Boolean)) return null;
|
|
1324
|
+
progress.finish();
|
|
1325
|
+
if (failure !== null) throw failure;
|
|
1326
|
+
throwIfAborted(signal, "install");
|
|
1327
|
+
return completeValue({files: completeValue(modelFiles)});
|
|
1328
|
+
} catch (error) {
|
|
1329
|
+
throw failure ?? error;
|
|
1330
|
+
} finally {
|
|
1331
|
+
progress.dispose();
|
|
1332
|
+
}
|
|
1258
1333
|
}
|
|
1259
1334
|
|
|
1260
1335
|
async function install(source, { signal, onProgress = null } = {}) {
|
|
@@ -1263,7 +1338,7 @@ export function createDbopfsModelStore({
|
|
|
1263
1338
|
}
|
|
1264
1339
|
const names = storageName(source);
|
|
1265
1340
|
const members = sourceMetadata(source).files;
|
|
1266
|
-
|
|
1341
|
+
let modelFiles;
|
|
1267
1342
|
const linked = linkAbortSignal(signal);
|
|
1268
1343
|
const downloadSignal = linked.controller.signal;
|
|
1269
1344
|
let nextMemberIndex = 0;
|
|
@@ -1273,7 +1348,7 @@ export function createDbopfsModelStore({
|
|
|
1273
1348
|
if (failure === null) failure = error;
|
|
1274
1349
|
}
|
|
1275
1350
|
|
|
1276
|
-
const progress =
|
|
1351
|
+
const progress = createModelProgressReporter(members, onProgress, retainFailure);
|
|
1277
1352
|
|
|
1278
1353
|
async function installMember(memberIndex) {
|
|
1279
1354
|
let modelFile = null;
|
|
@@ -1338,12 +1413,14 @@ export function createDbopfsModelStore({
|
|
|
1338
1413
|
|
|
1339
1414
|
try {
|
|
1340
1415
|
progress.start();
|
|
1416
|
+
if (failure !== null) throw failure;
|
|
1417
|
+
modelFiles = await availableResumableModelFiles(source, names, downloadSignal, progress);
|
|
1418
|
+
if (failure !== null) throw failure;
|
|
1341
1419
|
let pendingMembers = members.length;
|
|
1342
1420
|
for (let memberIndex = 0; memberIndex < modelFiles.length; memberIndex += 1) {
|
|
1343
1421
|
const modelFile = modelFiles[memberIndex];
|
|
1344
1422
|
if (!modelFile) continue;
|
|
1345
1423
|
pendingMembers -= 1;
|
|
1346
|
-
progress.restoreMember(memberIndex);
|
|
1347
1424
|
}
|
|
1348
1425
|
const workers = [];
|
|
1349
1426
|
const workerCount = Math.min(workerLimit, pendingMembers);
|
|
@@ -1392,10 +1469,11 @@ export function createDbopfsModelStore({
|
|
|
1392
1469
|
unit: "files",
|
|
1393
1470
|
heartbeat: false,
|
|
1394
1471
|
}));
|
|
1395
|
-
const cached = await openCached(source, { signal });
|
|
1472
|
+
const cached = await openCached(source, { signal, onProgress });
|
|
1396
1473
|
if (cached) {
|
|
1397
1474
|
const storage = await storagePolicy({ cached: true });
|
|
1398
1475
|
onCapabilityPolicy?.(storage);
|
|
1476
|
+
throwIfAborted(signal, "install");
|
|
1399
1477
|
return completeValue({ ...cached, cache: "cached", storage });
|
|
1400
1478
|
}
|
|
1401
1479
|
if (offline) {
|
|
@@ -178,13 +178,20 @@ export function createDbopfsModelPartStore({dbopfs, tableName}) {
|
|
|
178
178
|
throw error;
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
|
-
async function assemble(record, signal) {
|
|
181
|
+
async function assemble(record, signal, onProgress) {
|
|
182
182
|
const parts = [];
|
|
183
|
+
function reportStoredPart() {
|
|
184
|
+
throwIfModelAborted(signal);
|
|
185
|
+
onProgress?.({phase: 'load', message: 'Reading stored model resource',
|
|
186
|
+
completed: parts.length, total: record.parts.length, unit: 'shards', cached: true});
|
|
187
|
+
}
|
|
188
|
+
reportStoredPart();
|
|
183
189
|
for (const name of record.parts) {
|
|
184
190
|
throwIfModelAborted(signal);
|
|
185
191
|
const part = await file(name);
|
|
186
192
|
if (!part) return null;
|
|
187
193
|
parts.push(part);
|
|
194
|
+
reportStoredPart();
|
|
188
195
|
}
|
|
189
196
|
throwIfModelAborted(signal);
|
|
190
197
|
return new Blob(parts);
|
|
@@ -209,18 +216,18 @@ export function createDbopfsModelPartStore({dbopfs, tableName}) {
|
|
|
209
216
|
return readState(name);
|
|
210
217
|
}, signal);
|
|
211
218
|
}
|
|
212
|
-
function read(name, {signal} = {}) {
|
|
219
|
+
function read(name, {signal, onProgress} = {}) {
|
|
213
220
|
return locked(name, async function readCompleteModelParts() {
|
|
214
221
|
throwIfModelAborted(signal);
|
|
215
222
|
const record = await readState(name);
|
|
216
|
-
return record?.complete ? assemble(record, signal) : null;
|
|
223
|
+
return record?.complete ? assemble(record, signal, onProgress) : null;
|
|
217
224
|
}, signal);
|
|
218
225
|
}
|
|
219
|
-
function readPartial(name, {signal} = {}) {
|
|
226
|
+
function readPartial(name, {signal, onProgress} = {}) {
|
|
220
227
|
return locked(name, async function readClosedModelParts() {
|
|
221
228
|
throwIfModelAborted(signal);
|
|
222
229
|
const record = await readState(name);
|
|
223
|
-
return record ? assemble(record, signal) : null;
|
|
230
|
+
return record ? assemble(record, signal, onProgress) : null;
|
|
224
231
|
}, signal);
|
|
225
232
|
}
|
|
226
233
|
function remove(name, {signal} = {}) {
|
|
@@ -93,11 +93,8 @@ export function createDbopfsResourceStore({dbopfs, tableName, fetchImpl = global
|
|
|
93
93
|
const reusable = request.method === 'GET' && !request.headers.has('range')
|
|
94
94
|
&& !['reload', 'no-cache', 'no-store'].includes(request.cache);
|
|
95
95
|
if (reusable && entry.metadata?.status === 200) {
|
|
96
|
-
const file = await parts.read(entry.name, {signal});
|
|
96
|
+
const file = await parts.read(entry.name, {signal, onProgress: reportResourceProgress});
|
|
97
97
|
if (file) {
|
|
98
|
-
const state = await parts.state(entry.name, {signal});
|
|
99
|
-
onProgress?.({phase: 'load', message: 'Reading stored model resource', completed: state.parts.length,
|
|
100
|
-
total: state.parts.length, unit: 'shards', cached: true, url: request.url});
|
|
101
98
|
return {...entry.metadata, file};
|
|
102
99
|
}
|
|
103
100
|
}
|
|
@@ -111,7 +108,7 @@ export function createDbopfsResourceStore({dbopfs, tableName, fetchImpl = global
|
|
|
111
108
|
const encoding = previousHeaders.get('content-encoding');
|
|
112
109
|
const partial = reusable && entry.metadata?.status === 200
|
|
113
110
|
&& (!encoding || encoding === 'identity')
|
|
114
|
-
? await parts.readPartial(entry.name, {signal}) : null;
|
|
111
|
+
? await parts.readPartial(entry.name, {signal, onProgress: reportResourceProgress}) : null;
|
|
115
112
|
try {
|
|
116
113
|
if (partial?.size) {
|
|
117
114
|
const headers = new Headers(request.headers);
|
|
@@ -1116,6 +1116,17 @@ prepared playback without invoking `finishTTS()` or `streamTTS()`. Unmuted
|
|
|
1116
1116
|
completion keeps its ordinary speech flush. Explicit calls to either public
|
|
1117
1117
|
speech method retain their existing behavior.
|
|
1118
1118
|
|
|
1119
|
+
For a text-only request, pass `speech:false` to the object-form
|
|
1120
|
+
`ai.streamRequest({messages, speech:false, onChunk, signal})`. Its completion,
|
|
1121
|
+
failure, and cancellation leave buffered speech, prepared playback, and native
|
|
1122
|
+
playback alone. The option controls only the request's automatic speech
|
|
1123
|
+
finalization and stopping; it is not sent to the language provider and does not
|
|
1124
|
+
mute or unload TTS. Explicit speech calls made by your callbacks still run with
|
|
1125
|
+
their ordinary behavior. Omitted `speech` or `speech:true` preserves the
|
|
1126
|
+
automatic lifecycle above, and positional `streamMessage(...)` is unchanged.
|
|
1127
|
+
Explicit `streamTTS()`, `finishTTS()`, and global `stopAudio()` keep their existing
|
|
1128
|
+
scope.
|
|
1129
|
+
|
|
1119
1130
|
## Automatic speech-input formatting cleanup
|
|
1120
1131
|
|
|
1121
1132
|
The shared streaming, audio-file, and `SpeechPlayback` paths remove repeated
|
|
@@ -115,13 +115,17 @@ The controller emits `statechange` and `progress` through
|
|
|
115
115
|
current mutable status snapshot. Provider states are `unloaded`, `loading`, `ready`,
|
|
116
116
|
`unloading`, and `error`.
|
|
117
117
|
|
|
118
|
-
Browser-WASM model loads report `cache-check`, `download`, and `initialize`
|
|
119
|
-
phases.
|
|
118
|
+
Browser-WASM model loads report `cache-check`, `load`, `download`, and `initialize`
|
|
119
|
+
phases. Cache-read and download records preserve the ordered model-file fields `completed`,
|
|
120
120
|
`total`, and `unit:'files'`. Their ordered `members` records contain each file's
|
|
121
121
|
`name`, committed-part `completed`, `total`, and `unit:'shards'`. A streaming
|
|
122
122
|
response has `total:null` until its end is observed; completion then supplies
|
|
123
|
-
the actual committed-part count.
|
|
124
|
-
`
|
|
123
|
+
the actual committed-part count. Reading saved parts reports `phase:'load'`,
|
|
124
|
+
`cached:true`, and the known saved-part total before the first read and after
|
|
125
|
+
each part is opened. An existing HTTP Range plan likewise supplies its actual
|
|
126
|
+
shard total. Previously complete members identify `state:'cached'`; their
|
|
127
|
+
per-member records remain cache reads even if another member is downloading.
|
|
128
|
+
A fully cached activation emits no download phase. `activeTransfers` is the current transfer-worker count,
|
|
125
129
|
`transferLimit` is the selected concurrency, and `transferMode` identifies
|
|
126
130
|
`probing`, `files`, `ranges`, or `single`. No byte progress, rate, or estimated
|
|
127
131
|
completion time is produced. Closed-part and lifecycle changes publish promptly;
|
|
@@ -136,7 +140,7 @@ These messages do not change the runtime's log level or replace its complete
|
|
|
136
140
|
diagnostic output.
|
|
137
141
|
|
|
138
142
|
Initialization has `total:null` and omits `completed` and `unit`, so its progress
|
|
139
|
-
bar is indeterminate.
|
|
143
|
+
bar is indeterminate. Cached or downloaded file completion is not activation completion.
|
|
140
144
|
The metadata tensor count and GPU layer assignment are descriptive only:
|
|
141
145
|
upstream reports assigned layers before model-weight loading has finished.
|
|
142
146
|
The pinned runtime does not expose a measured overall initialization fraction
|
|
@@ -658,10 +662,13 @@ contains `kind`, `tableName`, `downloadConcurrency`, the original `adapter`,
|
|
|
658
662
|
and `ready`, `install`, `ensure`, `remove`, and `fetchResource`. `ensure()` preserves the complete
|
|
659
663
|
ordered model set and reports whether it was cached or installed.
|
|
660
664
|
`install(source,{signal,onProgress})` and
|
|
661
|
-
`ensure(source,{signal,onProgress,offline})` publish `cache-check
|
|
662
|
-
`download` records using ordered file counts and per-member closed-part progress
|
|
665
|
+
`ensure(source,{signal,onProgress,offline})` publish `cache-check`, cache-reading
|
|
666
|
+
`load`, and actual network `download` records using ordered file counts and per-member closed-part progress
|
|
663
667
|
when `onProgress` is supplied. Each member reports `completed`, `total`, and
|
|
664
|
-
`unit:'shards'`; streaming totals remain `null` until completion.
|
|
668
|
+
`unit:'shards'`; streaming totals remain `null` until completion. Cache reads
|
|
669
|
+
publish the known stored-part plan from zero through each opened part, with
|
|
670
|
+
`cached:true`; the existing Range plan supplies known totals during resume.
|
|
671
|
+
No model length is converted into product progress. Records retain
|
|
665
672
|
`activeTransfers`, `transferLimit`, and `transferMode`, with prompt part/lifecycle
|
|
666
673
|
updates and an existing 250 ms heartbeat. They contain no transfer rate or ETA.
|
|
667
674
|
Multi-file sources use up to that many concurrent member workers
|
|
@@ -693,8 +700,12 @@ loaders share this owner for actual configuration, tokenizer, graph,
|
|
|
693
700
|
external-data and support-file requests. Complete successful GET resources may
|
|
694
701
|
be reused. Cancellation joins reader/writer cleanup and preserves closed parts
|
|
695
702
|
for supported HTTP resume; a full response restarts that resource without
|
|
696
|
-
duplicating its saved prefix. Resource progress uses `unit:'shards'
|
|
697
|
-
`total:null` until completion
|
|
703
|
+
duplicating its saved prefix. Resource progress uses `unit:'shards'`. A network
|
|
704
|
+
response without a known shard plan retains `total:null` until completion;
|
|
705
|
+
opening a saved complete or partial resource reports `phase:'load'`,
|
|
706
|
+
`cached:true`, and the known number of stored parts before and during the read.
|
|
707
|
+
The resource URL accompanies every update. These counts describe resource
|
|
708
|
+
preparation, never engine activation. Independent resource requests remain concurrent.
|
|
698
709
|
|
|
699
710
|
### Availability and normalization
|
|
700
711
|
|
|
@@ -592,6 +592,15 @@ before the window closes; complete engineering errors go to the supplied
|
|
|
592
592
|
diagnostic/error callbacks. Callbacks must not synchronously wait for the
|
|
593
593
|
window's lifetime promises.
|
|
594
594
|
|
|
595
|
+
A WebView2 `BrowserProcessExited` failure retains its complete diagnostic,
|
|
596
|
+
fails a pending `Ready`, and begins the existing Core drain and window close.
|
|
597
|
+
It does not wait for a user to close a blank window or invoke document callbacks
|
|
598
|
+
and script cleanup in the closed browser. The launcher reports the failure
|
|
599
|
+
after that drain. Main-renderer exits and Core-only transport failures retain
|
|
600
|
+
their existing handling. This terminal-flow correction does not identify or
|
|
601
|
+
prevent the underlying browser-process exit, restart the application, or change
|
|
602
|
+
its profile and saved data.
|
|
603
|
+
|
|
595
604
|
`ArcaneLauncher.cs` supplies the executable entry point for that composition.
|
|
596
605
|
`tools/build-core-windows-host.mjs` builds the selected SDK host output from the
|
|
597
606
|
approved WebView2 package, existing .NET Framework compiler and selected Node
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"sdkVersion": "0.
|
|
3
|
+
"sdkVersion": "0.80.0",
|
|
4
4
|
"environment": {
|
|
5
5
|
"runtime": "Node.js for Node entrypoints; browser for browser-only entrypoints",
|
|
6
6
|
"minimumVersion": "22.23.2 for Node entrypoints",
|
|
@@ -45,14 +45,14 @@
|
|
|
45
45
|
"name": "createNativeDecisionModel",
|
|
46
46
|
"displayName": "createNativeDecisionModel()",
|
|
47
47
|
"kind": "function",
|
|
48
|
-
"signature": "createNativeDecisionModel({onnx,modelAssets,workingDirectory,paths,assetProjectionId,resourcePaths,model='onnx-community/laya-typed-decisions-ONNX',revision='main',dtype='fp32',sessionOptions,executionPreference='gpu',executionTarget,signal}={})",
|
|
48
|
+
"signature": "createNativeDecisionModel({onnx,modelAssets,workingDirectory,paths,assetProjectionId,resourcePaths,family='laya',model='onnx-community/laya-typed-decisions-ONNX',revision='main',dtype='fp32',sessionOptions,executionPreference='gpu',executionTarget,signal}={})",
|
|
49
49
|
"entrypoints": ["arcane-os/local-ai/decisions"],
|
|
50
50
|
"primaryImport": "arcane-os/local-ai/decisions",
|
|
51
51
|
"group": "Native typed decisions",
|
|
52
|
-
"summary": "Explicit native Laya FP32 option scoring through an existing ONNX owner and a tokenizer Worker.",
|
|
52
|
+
"summary": "Explicit native Laya FP32/FP16 or Julia FP32 option scoring through an existing ONNX owner and a tokenizer Worker.",
|
|
53
53
|
"availability": "Node on Windows/Linux/macOS with selected ONNX Runtime and model files; Android needs host adaptation",
|
|
54
|
-
"protocol": "Shared browser/native decision encoding with native named tensor results",
|
|
55
|
-
"normalization": "Returns load/evaluate/classify/current/status/subscribe/unload/dispose; preserves complete separate state/question/options/type and original rows, raw scores and every graph output. load({assetProjectionId,resourcePaths,executionTarget,signal}) retains omitted or undefined source, mapping and target fields from the latest selection, initially the factory configuration. A supplied ready projection takes precedence over configured paths and upstream settings; the existing modelAssets owner retains it directly without upstream preparation or fetching. resourcePaths maps model, tokenizer and tokenizerConfig to exact projection member.path values; complete companion files remain at their original relative paths. Missing projections or mapped members report their actual failure. assetProjectionId:null returns to configured paths or existing upstream preparation when no paths were configured. Separately, executionTarget:null requests automatic resolution, while {deviceId} forwards the saved physical selection to the existing ONNX owner. With no target selection, GPU preference still attempts advertised providers then CPU and explicit session providers retain precedence. Equal source, complete mapping and target selections share activation; a changed selection cancels the earlier request and joins its actual ONNX/tokenizer cleanup before replacement. Incoming projection use is retained before retiring the previous activation; caller preparation ownership is independent, and native files remain retained through actual ONNX and tokenizer Worker exit. Unload ends native use; loading again after all ownership is released requires a fresh projection. pendingActivation reports the requested replacement and projected source/mapping separately; current().execution remains the complete ONNX load record, with requested/resolved/configured/observed target diagnostics in current().execution.execution. Configuration alone does not prove physical execution. Cancellation retires the shared activation. No preference writes, chat history or duplicate inference engine."
|
|
54
|
+
"protocol": "Shared browser/native decision encoding with native named tensor results. Factory and each load accept family/model/revision/dtype; omission or undefined retains the selected value. Laya FP32 defaults remain. Laya FP16 selects onnx/model_fp16.onnx and onnx/model_fp16.onnx_data; Julia FP32 selects model.onnx and model.onnx.data, with its own tokenizer files. Complete new-family selection and matching files are caller-owned. All model fields participate in activation equality; top-level metadata stays attributable to the retiring activation while pendingActivation records its requested successor. Decoder preserves raw probabilities, every original row and every actual output without inventing Julia action logits.",
|
|
55
|
+
"normalization": "Returns load/evaluate/classify/current/status/subscribe/unload/dispose; preserves complete separate state/question/options/type and original rows, raw scores and every graph output. load({family,model,revision,dtype,assetProjectionId,resourcePaths,executionTarget,signal}) retains omitted or undefined family, model, revision, dtype, source, mapping and target fields from the latest selection, initially the factory configuration. A supplied ready projection takes precedence over configured paths and upstream settings; the existing modelAssets owner retains it directly without upstream preparation or fetching. resourcePaths maps model, tokenizer and tokenizerConfig to exact projection member.path values; complete companion files remain at their original relative paths. Missing projections or mapped members report their actual failure. assetProjectionId:null returns to configured paths or existing upstream preparation when no paths were configured. Separately, executionTarget:null requests automatic resolution, while {deviceId} forwards the saved physical selection to the existing ONNX owner. With no target selection, GPU preference still attempts advertised providers then CPU and explicit session providers retain precedence. Equal family, model, revision, dtype, source, complete mapping and target selections share activation; a changed selection cancels the earlier request and joins its actual ONNX/tokenizer cleanup before replacement. Incoming projection use is retained before retiring the previous activation; caller preparation ownership is independent, and native files remain retained through actual ONNX and tokenizer Worker exit. Unload ends native use; loading again after all ownership is released requires a fresh projection. pendingActivation reports the requested replacement and projected source/mapping separately; current().execution remains the complete ONNX load record, with requested/resolved/configured/observed target diagnostics in current().execution.execution. Configuration alone does not prove physical execution. Cancellation retires the shared activation. No preference writes, chat history or duplicate inference engine."
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
"id": "core-decisions:createNativeDecisionService",
|
|
@@ -63,10 +63,10 @@
|
|
|
63
63
|
"entrypoints": ["arcane-os/core/decisions"],
|
|
64
64
|
"primaryImport": "arcane-os/core/decisions",
|
|
65
65
|
"group": "Native typed decisions",
|
|
66
|
-
"summary": "Renderer-independent native Laya service reusing the registered local-AI and model-assets owners.",
|
|
66
|
+
"summary": "Renderer-independent native Laya/Julia service reusing the registered local-AI and model-assets owners.",
|
|
67
67
|
"availability": "Node Core with selected native ONNX and model files",
|
|
68
|
-
"protocol": "arcane/1 decisions.status/load/evaluate/unload and decisions.state",
|
|
69
|
-
"normalization": "Registration starts no model or tokenizer. Explicit load obtains dependency-ready same-process owners and reacquires getONNXRuntime after recovery. Factory configuration accepts assetProjectionId, resourcePaths and executionTarget; direct load({assetProjectionId,resourcePaths,executionTarget,signal}) and Core decisions.load({assetProjectionId,resourcePaths,executionTarget}) preserve omitted/undefined selections, with the Core request signal forwarded separately. resourcePaths maps model, tokenizer and tokenizerConfig to exact ready projection member.path values; complete companion members retain their relative paths. Supplied projections take precedence over configured paths/upstream settings, are retained through the existing model-assets owner, and never invoke upstream preparation or fetching; unavailable projections or mapped members fail directly. assetProjectionId:null returns to configured paths or existing upstream preparation, independently of executionTarget:null requesting automatic device selection. Equal source, complete mapping and target selections coalesce; changed selections cancel pending work and join the prior model's disposal before replacement. Incoming retained ownership protects projection files while the previous activation retires; caller preparation ownership remains independent, and native use retains files through actual ONNX/tokenizer exit. pendingActivation includes a projected source/mapping separately from the full activation execution record and its nested execution diagnostics. Native load/evaluate/classify/current/subscribe/unload/dispose preserve complete results; RPC uses the existing tensor codec. Unload has accepted service lifetime; host shutdown cancels request-lifetime load/evaluation."
|
|
68
|
+
"protocol": "arcane/1 decisions.status/load/evaluate/unload and decisions.state. Factory, direct load and decisions.load accept family/model/revision/dtype with the direct model's exact variant, omission and replacement semantics. The service persists across explicit model changes; top-level activation metadata and pendingActivation distinguish the retiring and requested selections.",
|
|
69
|
+
"normalization": "Registration starts no model or tokenizer. Explicit load obtains dependency-ready same-process owners and reacquires getONNXRuntime after recovery. Factory configuration accepts family, model, revision, dtype, assetProjectionId, resourcePaths and executionTarget; direct load({family,model,revision,dtype,assetProjectionId,resourcePaths,executionTarget,signal}) and Core decisions.load({family,model,revision,dtype,assetProjectionId,resourcePaths,executionTarget}) preserve omitted/undefined selections, with the Core request signal forwarded separately. resourcePaths maps model, tokenizer and tokenizerConfig to exact ready projection member.path values; complete companion members retain their relative paths. Supplied projections take precedence over configured paths/upstream settings, are retained through the existing model-assets owner, and never invoke upstream preparation or fetching; unavailable projections or mapped members fail directly. assetProjectionId:null returns to configured paths or existing upstream preparation, independently of executionTarget:null requesting automatic device selection. Equal family, model, revision, dtype, source, complete mapping and target selections coalesce; changed selections cancel pending work and join the prior model's disposal before replacement. Incoming retained ownership protects projection files while the previous activation retires; caller preparation ownership remains independent, and native use retains files through actual ONNX/tokenizer exit. pendingActivation includes a projected source/mapping separately from the full activation execution record and its nested execution diagnostics. Native load/evaluate/classify/current/subscribe/unload/dispose preserve complete results; RPC uses the existing tensor codec. Unload has accepted service lifetime; host shutdown cancels request-lifetime load/evaluation."
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
"id": "core-decisions:default",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# Native
|
|
1
|
+
# Native typed decisions
|
|
2
2
|
|
|
3
3
|
`arcane-os/local-ai/decisions` and `arcane-os/core/decisions` run the selected
|
|
4
|
-
Laya FP32 graph through the SDK's existing native ONNX owner. Tokenization runs
|
|
4
|
+
Laya FP32, Laya FP16 or Julia FP32 graph through the SDK's existing native ONNX owner. Tokenization runs
|
|
5
5
|
in its own Node Worker. A Core service can load and evaluate decisions without
|
|
6
6
|
a renderer, browser session, WebGPU browser context or chat provider.
|
|
7
7
|
|
|
@@ -58,7 +58,7 @@ name to `decisions`. It exposes `load`, `evaluate`, `classify` (an alias for
|
|
|
58
58
|
| Method | Parameters and result |
|
|
59
59
|
| --- | --- |
|
|
60
60
|
| `decisions.status` | Current lifecycle snapshot; does not load anything. |
|
|
61
|
-
| `decisions.load` | `{assetProjectionId?, resourcePaths?, executionTarget?}`; explicit activation or source/device replacement, returning the ready lifecycle snapshot. The request signal cancels activation. A target is `{deviceId:string}` or `null`. |
|
|
61
|
+
| `decisions.load` | `{family?, model?, revision?, dtype?, assetProjectionId?, resourcePaths?, executionTarget?}`; explicit activation or model/source/device replacement, returning the ready lifecycle snapshot. The request signal cancels activation. A target is `{deviceId:string}` or `null`. |
|
|
62
62
|
| `decisions.evaluate` | `{rows, runOptions?}`; returns `{decisions, outputs}`. The request signal cancels the active model operation. |
|
|
63
63
|
| `decisions.unload` | Releases the activation. Service-lifetime cleanup continues after the caller disconnects. |
|
|
64
64
|
|
|
@@ -94,11 +94,18 @@ const decisions = createNativeDecisionModel({
|
|
|
94
94
|
These direct paths are native filesystem paths. Keep the graph's external data
|
|
95
95
|
beside it under the exact filename required by the graph. The Core service
|
|
96
96
|
resolves configured `paths` relative to `appRoot`; it does not copy or remove
|
|
97
|
-
caller-owned files.
|
|
98
|
-
`onnx-community/laya-typed-decisions-ONNX`, revision
|
|
97
|
+
caller-owned files. Factory defaults remain `family:'laya'`,
|
|
98
|
+
`model:'onnx-community/laya-typed-decisions-ONNX'`, `revision:'main'` and
|
|
99
|
+
`dtype:'fp32'`. SDK upstream preparation selects these exact layouts:
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
| Family and dtype | Graph | Complete external data |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| `laya`, `fp32` | `onnx/model.onnx` | `onnx/model.onnx_data` |
|
|
104
|
+
| `laya`, `fp16` | `onnx/model_fp16.onnx` | `onnx/model_fp16.onnx_data` |
|
|
105
|
+
| `julia`, `fp32` | `model.onnx` | `model.onnx.data` |
|
|
106
|
+
|
|
107
|
+
Each selection uses `tokenizer.json` and `tokenizer_config.json` from its own
|
|
108
|
+
selected repository. Julia's public repository is `SupersonicLabs/Julia-1-ONNX`.
|
|
102
109
|
|
|
103
110
|
## Load complete files already stored in DBOPFS
|
|
104
111
|
|
|
@@ -144,7 +151,7 @@ After loading, invoke `decisions.evaluate` with the complete `rows`, then
|
|
|
144
151
|
`decisions.unload` when that native activation is no longer needed.
|
|
145
152
|
|
|
146
153
|
Direct model and service calls accept
|
|
147
|
-
`load({assetProjectionId, resourcePaths, executionTarget, signal})`; their
|
|
154
|
+
`load({family, model, revision, dtype, assetProjectionId, resourcePaths, executionTarget, signal})`; their
|
|
148
155
|
factory configuration accepts the same source selection. A direct model needs
|
|
149
156
|
the existing `modelAssets` owner when selecting a projection. `resourcePaths`
|
|
150
157
|
maps all three roles (`model`, `tokenizer`, `tokenizerConfig`) to exact
|
|
@@ -161,7 +168,8 @@ Omitted or `undefined` source fields retain the selected source and mappings.
|
|
|
161
168
|
`assetProjectionId: null` returns to the factory's configured `paths`, or its
|
|
162
169
|
existing upstream preparation when no paths were configured. Selecting another
|
|
163
170
|
projection or changing any resource mapping replaces the activation even when
|
|
164
|
-
the execution target stays the same. Equal
|
|
171
|
+
the execution target stays the same. Equal family, model, revision, dtype,
|
|
172
|
+
source, mapping and device selections
|
|
165
173
|
coalesce. The incoming retain is acquired before the prior activation retires,
|
|
166
174
|
so device replacement can reuse a projection after the caller has released its
|
|
167
175
|
preparation ownership. Superseded pending retains are released as their cleanup
|
|
@@ -178,8 +186,12 @@ model store. The approved `@huggingface/tokenizers@0.2.0` dependency reads the
|
|
|
178
186
|
selected tokenizer files in its Worker. No Transformers or second ONNX runtime
|
|
179
187
|
is loaded by this API.
|
|
180
188
|
|
|
181
|
-
|
|
182
|
-
|
|
189
|
+
Native graph selections support Laya `fp32` and `fp16`, and Julia `fp32`.
|
|
190
|
+
Other family/precision combinations report an unsupported selection. Laya FP16
|
|
191
|
+
uses the published mixed-precision graph, which retains selected operations
|
|
192
|
+
and outputs in FP32; it does not claim every graph operation uses FP16. Julia
|
|
193
|
+
uses its typed five-input ONNX graph and raw option logits, without the upstream
|
|
194
|
+
display-rounded `predict()` wrapper or invented action outputs.
|
|
183
195
|
`sessionOptions` and `runOptions` pass to the existing native ONNX API unchanged.
|
|
184
196
|
With no `executionTarget` selection, `executionPreference` defaults to `gpu`
|
|
185
197
|
here: actual advertised native GPU provider creation is attempted, followed by
|
|
@@ -189,6 +201,49 @@ continues to default to CPU. Accepted provider configuration is not evidence
|
|
|
189
201
|
that GPU nodes executed. Full provider-attempt diagnostics remain in
|
|
190
202
|
`execution`; see [native ONNX sessions](local-ai.md#native-onnx-sessions).
|
|
191
203
|
|
|
204
|
+
## Select an activation's model
|
|
205
|
+
|
|
206
|
+
Both factories and each direct/Core `load` accept `family`, `model`, `revision`
|
|
207
|
+
and `dtype`. The same service persists while its selected activation changes:
|
|
208
|
+
|
|
209
|
+
```js
|
|
210
|
+
await client.invoke('decisions.load', {
|
|
211
|
+
family: 'julia',
|
|
212
|
+
model: 'SupersonicLabs/Julia-1-ONNX',
|
|
213
|
+
revision: 'main',
|
|
214
|
+
dtype: 'fp32',
|
|
215
|
+
assetProjectionId: juliaProjection.id,
|
|
216
|
+
resourcePaths: {
|
|
217
|
+
model: 'model.onnx',
|
|
218
|
+
tokenizer: 'tokenizer.json',
|
|
219
|
+
tokenizerConfig: 'tokenizer_config.json'
|
|
220
|
+
}
|
|
221
|
+
}, {signal, timeoutMs: 0});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Here `juliaProjection` is the caller's ready projection containing Julia's
|
|
225
|
+
complete graph, `model.onnx.data` and its own tokenizer files. The caller may
|
|
226
|
+
release its preparation ownership after loading, as in the projection example.
|
|
227
|
+
For upstream preparation without configured paths, select the same four model
|
|
228
|
+
fields with `assetProjectionId: null`.
|
|
229
|
+
|
|
230
|
+
Omitting a model field, or supplying `undefined`, retains its most recent
|
|
231
|
+
selection, initially the factory default. The selection survives unload for a
|
|
232
|
+
later explicit load. Changing any field replaces the activation; equal complete
|
|
233
|
+
selections share it. Supply the complete new model selection when changing
|
|
234
|
+
family, including its matching projection/mapping or configured native files.
|
|
235
|
+
Changing `family` alone does not infer a repository, change precision, replace
|
|
236
|
+
configured files, or rewrite a projection. Metadata describes the caller's
|
|
237
|
+
selection; the SDK does not inspect weights to establish checkpoint identity.
|
|
238
|
+
Configured `paths` and a selected projection keep their existing precedence
|
|
239
|
+
over upstream preparation.
|
|
240
|
+
|
|
241
|
+
Top-level `current().family`, `model`, `revision` and `dtype` follow the
|
|
242
|
+
activation being loaded, used or retired. While replacement waits for prior
|
|
243
|
+
cleanup, those fields and `execution` still describe the prior activation;
|
|
244
|
+
`pendingActivation` carries the requested successor separately. The new
|
|
245
|
+
selection takes ownership when its activation begins.
|
|
246
|
+
|
|
192
247
|
## Select an activation's execution device
|
|
193
248
|
|
|
194
249
|
Both factories accept `executionTarget: {deviceId:string}` or `null` in their
|
|
@@ -228,15 +283,16 @@ resolution, accepted configuration and actual execution evidence. An unknown
|
|
|
228
283
|
`observedTarget` remains `null`; successful configuration alone does not establish
|
|
229
284
|
which device executed graph nodes.
|
|
230
285
|
|
|
231
|
-
`pendingActivation` is separately `null` or
|
|
232
|
-
|
|
286
|
+
`pendingActivation` is separately `null` or a selection containing `family`,
|
|
287
|
+
`model`, `revision`, `dtype` and `executionTarget` while a load or replacement
|
|
288
|
+
is pending; its `executionTarget` property is omitted at JSON
|
|
233
289
|
transport when the selection is `undefined`. A projected selection also includes
|
|
234
290
|
`assetProjectionId` and its complete `resourcePaths` mapping. A pending target never overwrites the previous
|
|
235
291
|
activation's `execution` record. The old record remains attributable to the
|
|
236
292
|
retiring activation until its cleanup completes, then the new activation owns
|
|
237
293
|
its own record. A request is not a claim that the requested device is running.
|
|
238
294
|
|
|
239
|
-
Concurrent loads for the same source, resource mapping and target share their
|
|
295
|
+
Concurrent loads for the same family, model, revision, dtype, source, resource mapping and target share their
|
|
240
296
|
pending activation. A different selection supersedes the earlier pending request, which rejects with
|
|
241
297
|
cancellation instead of returning another target's ready result. Replacement
|
|
242
298
|
joins this model's actual tokenizer/native cleanup before creating its
|
|
@@ -280,7 +336,8 @@ progress includes model preparation, tokenization and evaluation; downloaded
|
|
|
280
336
|
members report files, not byte progress. Complete technical errors belong in
|
|
281
337
|
developer diagnostics, while the application owns its user-facing status.
|
|
282
338
|
|
|
283
|
-
Concurrent callers selecting the same
|
|
339
|
+
Concurrent callers selecting the same family, model, revision, dtype, source,
|
|
340
|
+
mapping and target share one explicit activation.
|
|
284
341
|
Cancelling a load or evaluation cancels that activation and its outstanding operations, because the
|
|
285
342
|
native session and tokenizer have one owner. Use separately owned models when
|
|
286
343
|
independent cancellation is required. The construction `signal` governs the
|
|
@@ -304,5 +361,6 @@ availability. Android requires the host's supported native adaptation. Static
|
|
|
304
361
|
source review and synthetic authored fixtures are not model execution or a
|
|
305
362
|
claim of tested platform, precision or accelerator behavior.
|
|
306
363
|
|
|
307
|
-
Upstream selection: [Laya
|
|
364
|
+
Upstream selection: [Laya model files](https://huggingface.co/onnx-community/laya-typed-decisions-ONNX/tree/main),
|
|
365
|
+
[Julia-1 model files](https://huggingface.co/SupersonicLabs/Julia-1-ONNX/tree/main),
|
|
308
366
|
[Tokenizer package](https://www.npmjs.com/package/@huggingface/tokenizers).
|
|
@@ -69,7 +69,7 @@ runtime layouts.
|
|
|
69
69
|
| `arcane-os/core/local-ai` | Native llama.cpp, Ollama and ONNX Core service, model readiness, complete responses and owned lifecycle. See [local AI through Core](local-ai.md). |
|
|
70
70
|
| `arcane-os/local-ai` | Official runtime installation and native artifact bundling for selected llama.cpp/Ollama/ONNX/image requirements and NeMo libraries. |
|
|
71
71
|
| `arcane-os/local-ai/onnx` | Retained native ONNX worker sessions with explicit graph loading and complete tensor inference. |
|
|
72
|
-
| `arcane-os/local-ai/decisions` | Explicit native Laya FP32 decisions using the existing ONNX owner and a tokenizer Worker. |
|
|
72
|
+
| `arcane-os/local-ai/decisions` | Explicit native Laya FP32/FP16 and Julia FP32 decisions using the existing ONNX owner and a tokenizer Worker. |
|
|
73
73
|
| `arcane-os/core/decisions` | Renderer-independent typed-decision service composed with existing local-AI and model-assets owners. |
|
|
74
74
|
| `arcane-os/ai/core-onnx` | Optional browser access to system ONNX through an existing Core connection. |
|
|
75
75
|
| `arcane-os/local-ai/image` | Retained native stable-diffusion.cpp context, complete PNG generation and owned worker lifetime. |
|
|
@@ -186,7 +186,7 @@ The remaining data-only subpaths are eight JSON Schemas and package metadata.
|
|
|
186
186
|
| `acceptWebSocket()` | function | `arcane-os`, `arcane-os/websocket` | WebSocket protocol | Node; browser-native client interoperability |
|
|
187
187
|
| `WS` | class | `arcane-os`, `arcane-os/websocket-client` | Shared WebSocket clients | Node and native-WebSocket browser hosts |
|
|
188
188
|
| `createBrowserDecisionModel()` | function | `arcane-os/ai/browser-decisions` | Browser typed decisions | Browser module Workers and the caller-selected inference backend |
|
|
189
|
-
| `createNativeDecisionModel()` | function | `arcane-os/local-ai/decisions` | Native typed decisions | Node with an existing native ONNX owner and selected Laya FP32 files |
|
|
189
|
+
| `createNativeDecisionModel()` | function | `arcane-os/local-ai/decisions` | Native typed decisions | Node with an existing native ONNX owner and selected Laya FP32/FP16 or Julia FP32 files |
|
|
190
190
|
| `createNativeDecisionService()` | function | `arcane-os/core/decisions` | Native typed decisions | Node Core with existing local-AI and model-assets services |
|
|
191
191
|
| `createNativeDecisionService default export` | function | `arcane-os/core/decisions` | Native typed decisions | Same native decision service factory |
|
|
192
192
|
| `fetchRequest()` | function | `arcane-os/ai/twin-cloud` | TWiN Cloud requests | Node and Browser; remote HTTPS provider |
|
|
@@ -9505,23 +9505,35 @@ native installation eligibility and URL-bar promotion.
|
|
|
9505
9505
|
|
|
9506
9506
|
### Overview
|
|
9507
9507
|
|
|
9508
|
-
Runs explicitly selected Laya FP32 option scoring through an existing native
|
|
9508
|
+
Runs explicitly selected Laya FP32/FP16 or Julia FP32 option scoring through an existing native
|
|
9509
9509
|
ONNX owner, with tokenization in a Node Worker. It creates no second inference
|
|
9510
9510
|
engine and starts no model download at construction.
|
|
9511
9511
|
|
|
9512
9512
|
### Signature and result
|
|
9513
9513
|
|
|
9514
9514
|
```text
|
|
9515
|
-
createNativeDecisionModel({onnx,modelAssets,workingDirectory,paths,model='onnx-community/laya-typed-decisions-ONNX',revision='main',dtype='fp32',sessionOptions,executionPreference='gpu',signal}={})
|
|
9515
|
+
createNativeDecisionModel({onnx,modelAssets,workingDirectory,paths,assetProjectionId,resourcePaths,family='laya',model='onnx-community/laya-typed-decisions-ONNX',revision='main',dtype='fp32',sessionOptions,executionPreference='gpu',executionTarget,signal}={})
|
|
9516
9516
|
```
|
|
9517
9517
|
|
|
9518
9518
|
Import from `arcane-os/local-ai/decisions`. Supply the existing `onnx` owner and
|
|
9519
9519
|
either `modelAssets` plus `workingDirectory`, or native `paths` for the model,
|
|
9520
|
-
tokenizer and tokenizer configuration
|
|
9520
|
+
tokenizer and tokenizer configuration, or a ready `assetProjectionId` with
|
|
9521
|
+
`resourcePaths` for those three roles. Supported selections are Laya `fp32`,
|
|
9522
|
+
Laya `fp16` and Julia `fp32`; constructor defaults remain Laya FP32. The
|
|
9521
9523
|
handle exposes `load`, `evaluate`, `classify`, `current`, `status`, `subscribe`,
|
|
9522
9524
|
`unload` and terminal `dispose`; `classify` aliases `evaluate` and `status`
|
|
9523
9525
|
aliases `current`.
|
|
9524
9526
|
|
|
9527
|
+
`load({family,model,revision,dtype,assetProjectionId,resourcePaths,executionTarget,signal})`
|
|
9528
|
+
selects each activation. Omitted or `undefined` fields retain their prior
|
|
9529
|
+
selection. A changed model field, source, mapping or device replaces the
|
|
9530
|
+
activation after actual cleanup; equal complete selections coalesce. Supply
|
|
9531
|
+
all matching model fields and files when changing family: metadata does not
|
|
9532
|
+
infer a repository or rewrite a supplied projection. Top-level model metadata
|
|
9533
|
+
continues to describe the retiring activation while `pendingActivation` records
|
|
9534
|
+
its successor. See [model selection](native-decisions.md#select-an-activations-model)
|
|
9535
|
+
for exact upstream graph/companion layouts and retained projection ownership.
|
|
9536
|
+
|
|
9525
9537
|
`evaluate(rows,{signal,runOptions})` preserves each complete `state`, `question`,
|
|
9526
9538
|
`options` and optional `type` separately. It returns `{decisions,outputs}` with
|
|
9527
9539
|
original rows, typed values, raw logits, unrounded probabilities and every
|
|
@@ -9574,7 +9586,11 @@ reacquires `getONNXRuntime()` after any recovery. Native members are `load`,
|
|
|
9574
9586
|
`start(context)`.
|
|
9575
9587
|
|
|
9576
9588
|
RPC exposes `decisions.status`, `decisions.load`, `decisions.evaluate` and
|
|
9577
|
-
`decisions.unload`.
|
|
9589
|
+
`decisions.unload`. Load accepts optional `family`, `model`, `revision`, `dtype`,
|
|
9590
|
+
`assetProjectionId`, `resourcePaths` and `executionTarget`, with the same
|
|
9591
|
+
per-activation selection and omission behavior as direct native load; the Core
|
|
9592
|
+
request signal owns cancellation. The service persists across model changes.
|
|
9593
|
+
Evaluation accepts `{rows,runOptions?}` and returns complete
|
|
9578
9594
|
`{decisions,outputs}` using the existing tensor codec at the JSON boundary.
|
|
9579
9595
|
`decisions.state` and replaying `subscribe(listener,{emitCurrent:true,signal})`
|
|
9580
9596
|
report lifecycle. Unload is accepted service-lifetime cleanup; host shutdown
|
package/package.json
CHANGED
|
@@ -5018,6 +5018,7 @@ class AI {
|
|
|
5018
5018
|
model,
|
|
5019
5019
|
structuredOutput=false,
|
|
5020
5020
|
localOnly=false,
|
|
5021
|
+
speech=true,
|
|
5021
5022
|
onChunk=function ignoreStreamChunk(){},
|
|
5022
5023
|
toolText,
|
|
5023
5024
|
onToolText,
|
|
@@ -5062,6 +5063,9 @@ class AI {
|
|
|
5062
5063
|
if(localOnly!==true&&localOnly!==false){
|
|
5063
5064
|
throw new TypeError('AI localOnly must be a boolean.');
|
|
5064
5065
|
}
|
|
5066
|
+
if(speech!==true&&speech!==false){
|
|
5067
|
+
throw new TypeError('AI speech must be a boolean.');
|
|
5068
|
+
}
|
|
5065
5069
|
if(localOnly&&!this.#hasLocalRoute('llm',this.llmService)){
|
|
5066
5070
|
const error=new Error(
|
|
5067
5071
|
'This AI request requires a configured local model.'
|
|
@@ -5163,7 +5167,7 @@ class AI {
|
|
|
5163
5167
|
if(signal?.aborted){
|
|
5164
5168
|
throw normalizeAIRequestAbort();
|
|
5165
5169
|
}
|
|
5166
|
-
this.#finishStreamingSpeech();
|
|
5170
|
+
if(speech)this.#finishStreamingSpeech();
|
|
5167
5171
|
return result;
|
|
5168
5172
|
}catch(error){
|
|
5169
5173
|
if(handle){
|
|
@@ -5171,7 +5175,7 @@ class AI {
|
|
|
5171
5175
|
function retainProviderStreamCleanupFailure() {}
|
|
5172
5176
|
);
|
|
5173
5177
|
}
|
|
5174
|
-
this.stopAudio();
|
|
5178
|
+
if(speech)this.stopAudio();
|
|
5175
5179
|
throw isAIRequestAbort(error,signal)
|
|
5176
5180
|
?normalizeAIRequestAbort(error)
|
|
5177
5181
|
:error;
|
|
@@ -5223,10 +5227,10 @@ class AI {
|
|
|
5223
5227
|
if(signal?.aborted){
|
|
5224
5228
|
throw normalizeAIRequestAbort();
|
|
5225
5229
|
}
|
|
5226
|
-
this.#finishStreamingSpeech();
|
|
5230
|
+
if(speech)this.#finishStreamingSpeech();
|
|
5227
5231
|
return result;
|
|
5228
5232
|
}catch(error){
|
|
5229
|
-
this.stopAudio();
|
|
5233
|
+
if(speech)this.stopAudio();
|
|
5230
5234
|
throw isAIRequestAbort(error,signal)
|
|
5231
5235
|
?normalizeAIRequestAbort(error)
|
|
5232
5236
|
:error;
|
|
@@ -6026,7 +6030,8 @@ class AI {
|
|
|
6026
6030
|
}
|
|
6027
6031
|
throw error;
|
|
6028
6032
|
}finally{
|
|
6029
|
-
|
|
6033
|
+
// Provider transports leave speech lifecycle to their public caller.
|
|
6034
|
+
if(finishSpeech&&!speechTurnCompleted){
|
|
6030
6035
|
this.stopAudio();
|
|
6031
6036
|
}
|
|
6032
6037
|
}
|
|
@@ -124,6 +124,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
124
124
|
private bool started;
|
|
125
125
|
private bool documentReady;
|
|
126
126
|
private bool deliveryScheduled;
|
|
127
|
+
private bool browserProcessExited;
|
|
127
128
|
private bool closing;
|
|
128
129
|
private bool closeAllowed;
|
|
129
130
|
|
|
@@ -551,11 +552,26 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
551
552
|
{
|
|
552
553
|
error = new AggregateException("Reading WebView2 process-failure diagnostics failed.", error, diagnosticError);
|
|
553
554
|
}
|
|
554
|
-
|
|
555
|
+
if (kind == CoreWebView2ProcessFailedKind.BrowserProcessExited)
|
|
556
|
+
{
|
|
557
|
+
// WebView2 has closed this browser. Complete the owned host
|
|
558
|
+
// lifetime without trying to notify its vanished document.
|
|
559
|
+
browserProcessExited = true;
|
|
560
|
+
lock (stateLock)
|
|
561
|
+
{
|
|
562
|
+
if (transportFailure == null) transportFailure = error;
|
|
563
|
+
}
|
|
564
|
+
Report(error);
|
|
565
|
+
ready.TrySetException(error);
|
|
566
|
+
if (bridge != null) bridge.Stop(error);
|
|
567
|
+
if (activeBridge != null) activeBridge.Stop(error);
|
|
568
|
+
BeginClose();
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
// Loss of the top-level renderer ends its bridge.
|
|
555
572
|
// GPU/utility/plugin helpers recover independently; subframe loss,
|
|
556
573
|
// unresponsiveness and unspecified failures remain full diagnostics.
|
|
557
|
-
if (kind == CoreWebView2ProcessFailedKind.
|
|
558
|
-
|| kind == CoreWebView2ProcessFailedKind.RenderProcessExited) CoreFailed(error);
|
|
574
|
+
if (kind == CoreWebView2ProcessFailedKind.RenderProcessExited) CoreFailed(error);
|
|
559
575
|
else Report(error);
|
|
560
576
|
}
|
|
561
577
|
|
|
@@ -665,7 +681,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
665
681
|
|
|
666
682
|
private void NotifyTransportFailure()
|
|
667
683
|
{
|
|
668
|
-
if (!documentReady || webView.CoreWebView2 == null || transportFailure == null
|
|
684
|
+
if (closing || browserProcessExited || !documentReady || webView.CoreWebView2 == null || transportFailure == null) return;
|
|
669
685
|
if (failureNotifiedGeneration == generation) return;
|
|
670
686
|
failureNotifiedGeneration = generation;
|
|
671
687
|
Task failureNotification = NotifyTransportFailureAsync(transportFailure);
|
|
@@ -761,7 +777,7 @@ namespace Arcane.Core.Hosts.Windows
|
|
|
761
777
|
ready.TrySetCanceled();
|
|
762
778
|
try
|
|
763
779
|
{
|
|
764
|
-
if (webView.CoreWebView2 != null)
|
|
780
|
+
if (!browserProcessExited && webView.CoreWebView2 != null)
|
|
765
781
|
{
|
|
766
782
|
webView.CoreWebView2.RemoveHostObjectFromScript("arcaneBridge");
|
|
767
783
|
if (injectedScript != null) webView.CoreWebView2.RemoveScriptToExecuteOnDocumentCreated(injectedScript);
|
|
@@ -10,6 +10,7 @@ function sameExecutionTarget(left, right) {
|
|
|
10
10
|
|
|
11
11
|
function sameActivation(left, right) {
|
|
12
12
|
if (!left || !right || !sameExecutionTarget(left.executionTarget, right.executionTarget) || left.assetProjectionId !== right.assetProjectionId) return false;
|
|
13
|
+
if (left.family !== right.family || left.model !== right.model || left.revision !== right.revision || left.dtype !== right.dtype) return false;
|
|
13
14
|
if (left.resourcePaths === right.resourcePaths) return true;
|
|
14
15
|
if (!left.resourcePaths || !right.resourcePaths) return false;
|
|
15
16
|
const entries = Object.entries(left.resourcePaths);
|
|
@@ -32,6 +33,10 @@ export function createNativeDecisionService(configuration = {}, {appRoot = proce
|
|
|
32
33
|
let closing;
|
|
33
34
|
let unsubscribe;
|
|
34
35
|
let selectedActivation = {
|
|
36
|
+
family: configuration.family === undefined ? 'laya' : configuration.family,
|
|
37
|
+
model: configuration.model === undefined ? 'onnx-community/laya-typed-decisions-ONNX' : configuration.model,
|
|
38
|
+
revision: configuration.revision === undefined ? 'main' : configuration.revision,
|
|
39
|
+
dtype: configuration.dtype === undefined ? 'fp32' : configuration.dtype,
|
|
35
40
|
executionTarget: configuration.executionTarget,
|
|
36
41
|
...(configuration.assetProjectionId == null ? {} : {assetProjectionId: configuration.assetProjectionId, resourcePaths: configuration.resourcePaths && {...configuration.resourcePaths}})
|
|
37
42
|
};
|
|
@@ -40,7 +45,7 @@ export function createNativeDecisionService(configuration = {}, {appRoot = proce
|
|
|
40
45
|
|
|
41
46
|
function current() {
|
|
42
47
|
const snapshot = model?.current() ?? {
|
|
43
|
-
family: 'laya', model: configuration.model ?? 'onnx-community/laya-typed-decisions-ONNX',
|
|
48
|
+
family: configuration.family ?? 'laya', model: configuration.model ?? 'onnx-community/laya-typed-decisions-ONNX',
|
|
44
49
|
revision: configuration.revision ?? 'main', dtype: configuration.dtype ?? 'fp32',
|
|
45
50
|
state: closing ? 'disposed' : 'unloaded', loaded: false, busy: Boolean(loading),
|
|
46
51
|
activeRequests: 0, progress: null, error: null, execution: null
|
|
@@ -66,12 +71,19 @@ export function createNativeDecisionService(configuration = {}, {appRoot = proce
|
|
|
66
71
|
|
|
67
72
|
function operationSignal(signal) { return signal ? AbortSignal.any([signal, lifetime.signal]) : lifetime.signal; }
|
|
68
73
|
|
|
69
|
-
function load({
|
|
74
|
+
function load({
|
|
75
|
+
family: requestedFamily, model: requestedModel, revision: requestedRevision, dtype: requestedDtype,
|
|
76
|
+
signal, executionTarget: requestedTarget, assetProjectionId: requestedProjection, resourcePaths: requestedPaths
|
|
77
|
+
} = {}) {
|
|
70
78
|
operationSignal(signal).throwIfAborted();
|
|
71
79
|
if (!context) throw new CoreError({code: 'CORE_NOT_READY', message: 'Register and start the decision service with Core before loading.'});
|
|
72
80
|
const projection = requestedProjection === undefined ? selectedActivation.assetProjectionId : requestedProjection;
|
|
73
81
|
const mapping = requestedPaths === undefined ? selectedActivation.resourcePaths : requestedPaths;
|
|
74
82
|
const selection = {
|
|
83
|
+
family: requestedFamily === undefined ? selectedActivation.family : requestedFamily,
|
|
84
|
+
model: requestedModel === undefined ? selectedActivation.model : requestedModel,
|
|
85
|
+
revision: requestedRevision === undefined ? selectedActivation.revision : requestedRevision,
|
|
86
|
+
dtype: requestedDtype === undefined ? selectedActivation.dtype : requestedDtype,
|
|
75
87
|
executionTarget: requestedTarget === undefined ? selectedActivation.executionTarget : requestedTarget,
|
|
76
88
|
...(projection == null ? {} : {assetProjectionId: projection, resourcePaths: mapping && typeof mapping === 'object' && !Array.isArray(mapping) ? {...mapping} : mapping})
|
|
77
89
|
};
|
|
@@ -116,6 +128,7 @@ export function createNativeDecisionService(configuration = {}, {appRoot = proce
|
|
|
116
128
|
);
|
|
117
129
|
model = createNativeDecisionModel({
|
|
118
130
|
...configuration, paths, onnx: localAI.getONNXRuntime(), modelAssets,
|
|
131
|
+
family: selection.family, model: selection.model, revision: selection.revision, dtype: selection.dtype,
|
|
119
132
|
executionTarget: selection.executionTarget, assetProjectionId: selection.assetProjectionId,
|
|
120
133
|
resourcePaths: selection.resourcePaths, signal: lifetime.signal
|
|
121
134
|
});
|
|
@@ -221,6 +234,7 @@ export function createNativeDecisionService(configuration = {}, {appRoot = proce
|
|
|
221
234
|
'decisions.status': current,
|
|
222
235
|
'decisions.load': function loadRequest(parameters, request) {
|
|
223
236
|
return load({
|
|
237
|
+
family: parameters?.family, model: parameters?.model, revision: parameters?.revision, dtype: parameters?.dtype,
|
|
224
238
|
executionTarget: parameters?.executionTarget, assetProjectionId: parameters?.assetProjectionId,
|
|
225
239
|
resourcePaths: parameters?.resourcePaths, signal: request.signal
|
|
226
240
|
});
|
|
@@ -17,7 +17,7 @@ try {
|
|
|
17
17
|
};
|
|
18
18
|
parentPort.on('message', function encodeRequest({id, rows}) {
|
|
19
19
|
try {
|
|
20
|
-
const {encoded, pad} = encodeDecisionRows(interfaceAdapter, rows,
|
|
20
|
+
const {encoded, pad} = encodeDecisionRows(interfaceAdapter, rows, workerData.family);
|
|
21
21
|
const inputs = createDecisionInputs(encoded, pad);
|
|
22
22
|
parentPort.postMessage({id, result: inputs});
|
|
23
23
|
} catch (error) {
|
|
@@ -6,7 +6,7 @@ function cancelled() {
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
/** One tokenizer worker owned by one native model activation. */
|
|
9
|
-
export function createDecisionTokenizer({tokenizerPath, tokenizerConfigPath, signal, onError}) {
|
|
9
|
+
export function createDecisionTokenizer({family = 'laya', tokenizerPath, tokenizerConfigPath, signal, onError}) {
|
|
10
10
|
signal?.throwIfAborted();
|
|
11
11
|
const pending = new Map();
|
|
12
12
|
let nextId = 0;
|
|
@@ -14,7 +14,7 @@ export function createDecisionTokenizer({tokenizerPath, tokenizerConfigPath, sig
|
|
|
14
14
|
let terminalError;
|
|
15
15
|
let exited = false;
|
|
16
16
|
const worker = new Worker(new URL('./decision-tokenizer-worker.mjs', import.meta.url), {
|
|
17
|
-
workerData: {tokenizerPath, tokenizerConfigPath}
|
|
17
|
+
workerData: {family, tokenizerPath, tokenizerConfigPath}
|
|
18
18
|
});
|
|
19
19
|
let resolveExit;
|
|
20
20
|
const exit = new Promise(function tokenizerExit(resolve) { resolveExit = resolve; });
|
|
@@ -5,7 +5,13 @@ import {decodeDecisionOutputs} from '../../browser-runtime/ai/decision-runtime.m
|
|
|
5
5
|
import {createDecisionTokenizer} from './decision-tokenizer.mjs';
|
|
6
6
|
|
|
7
7
|
const STATE_EVENT = 'ai.decisions.state';
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
function selectedGraph(family, dtype) {
|
|
10
|
+
if (family === 'laya' && dtype === 'fp32') return 'onnx/model.onnx';
|
|
11
|
+
if (family === 'laya' && dtype === 'fp16') return 'onnx/model_fp16.onnx';
|
|
12
|
+
if (family === 'julia' && dtype === 'fp32') return 'model.onnx';
|
|
13
|
+
throw new TypeError('Native decisions support Laya fp32/fp16 and Julia fp32 graph selections.');
|
|
14
|
+
}
|
|
9
15
|
|
|
10
16
|
function cancellation(message) {
|
|
11
17
|
return new CoreError({name: 'AbortError', code: 'ARCANE_AI_REQUEST_ABORTED', message});
|
|
@@ -18,6 +24,7 @@ function sameExecutionTarget(left, right) {
|
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
function sameActivation(left, right) {
|
|
27
|
+
if (left.family !== right.family || left.model !== right.model || left.revision !== right.revision || left.dtype !== right.dtype) return false;
|
|
21
28
|
if (!sameExecutionTarget(left.executionTarget, right.executionTarget) || left.assetProjectionId !== right.assetProjectionId) return false;
|
|
22
29
|
if (left.resourcePaths === right.resourcePaths) return true;
|
|
23
30
|
if (!left.resourcePaths || !right.resourcePaths) return false;
|
|
@@ -27,15 +34,15 @@ function sameActivation(left, right) {
|
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
/**
|
|
30
|
-
* One explicitly activated
|
|
37
|
+
* One explicitly activated typed-decision model using an existing native ONNX owner.
|
|
31
38
|
* It does not create another inference runtime or load on construction.
|
|
32
39
|
*/
|
|
33
40
|
export function createNativeDecisionModel({
|
|
34
41
|
onnx, modelAssets, workingDirectory, paths, assetProjectionId, resourcePaths,
|
|
35
|
-
model = 'onnx-community/laya-typed-decisions-ONNX', revision = 'main',
|
|
42
|
+
family = 'laya', model = 'onnx-community/laya-typed-decisions-ONNX', revision = 'main',
|
|
36
43
|
dtype = 'fp32', sessionOptions, executionPreference = 'gpu', executionTarget, signal
|
|
37
44
|
} = {}) {
|
|
38
|
-
|
|
45
|
+
selectedGraph(family, dtype);
|
|
39
46
|
const owner = {};
|
|
40
47
|
const events = createArcaneEventSource(owner, {source: 'native-decisions', eventTypes: [STATE_EVENT]});
|
|
41
48
|
const loads = new Set();
|
|
@@ -49,15 +56,17 @@ export function createNativeDecisionModel({
|
|
|
49
56
|
let lastError = null;
|
|
50
57
|
let execution = null;
|
|
51
58
|
let selectedActivation = {
|
|
52
|
-
executionTarget,
|
|
59
|
+
family, model, revision, dtype, executionTarget,
|
|
53
60
|
...(assetProjectionId == null ? {} : {assetProjectionId, resourcePaths: resourcePaths && {...resourcePaths}})
|
|
54
61
|
};
|
|
62
|
+
let activeSelection = selectedActivation;
|
|
55
63
|
let pendingActivation;
|
|
56
64
|
|
|
57
65
|
function current() {
|
|
58
66
|
const pending = pendingActivation && (state !== 'ready' || pendingActivation.entry !== activation) ? pendingActivation : null;
|
|
59
67
|
return {
|
|
60
|
-
family:
|
|
68
|
+
family: activeSelection.family, model: activeSelection.model,
|
|
69
|
+
revision: activeSelection.revision, dtype: activeSelection.dtype, state,
|
|
61
70
|
loaded: state === 'ready', busy: state === 'loading' || Boolean(pending) || Boolean(activation?.jobs.size),
|
|
62
71
|
activeRequests: activation?.jobs.size ?? 0, progress,
|
|
63
72
|
error: lastError ? serializeCoreError(lastError) : null,
|
|
@@ -163,10 +172,14 @@ export function createNativeDecisionModel({
|
|
|
163
172
|
}
|
|
164
173
|
if (paths) return paths;
|
|
165
174
|
if (!modelAssets) throw new CoreError({code: 'ARCANE_DECISION_ASSETS_UNAVAILABLE', message: 'Native decisions need prepared paths or the Core model-assets owner.'});
|
|
166
|
-
const
|
|
175
|
+
const {family: selectedFamily, model: selectedModel, revision: selectedRevision, dtype: selectedDtype} = entry.selection;
|
|
176
|
+
const graph = selectedGraph(selectedFamily, selectedDtype);
|
|
177
|
+
const companion = selectedFamily === 'laya' ? `${graph}_data` : `${graph}.data`;
|
|
178
|
+
const members = [graph, companion, 'tokenizer.json', 'tokenizer_config.json'];
|
|
179
|
+
const base = `https://huggingface.co/${selectedModel}/resolve/${encodeURIComponent(selectedRevision)}/`;
|
|
167
180
|
const projection = await modelAssets.prepare({
|
|
168
181
|
id: entry.id, workingDirectory,
|
|
169
|
-
members:
|
|
182
|
+
members: members.map(function selectFile(path) { return {path, url: new URL(path, base).href}; }),
|
|
170
183
|
signal: entry.controller.signal,
|
|
171
184
|
onProgress(value) { updateProgress(entry, value); }
|
|
172
185
|
});
|
|
@@ -176,7 +189,7 @@ export function createNativeDecisionModel({
|
|
|
176
189
|
await modelAssets.release(entry.id);
|
|
177
190
|
entry.prepared = false;
|
|
178
191
|
const files = Object.fromEntries(projection.members.map(function memberFile(member) { return [member.path, member.nativePath]; }));
|
|
179
|
-
return {model: files[
|
|
192
|
+
return {model: files[graph], tokenizer: files['tokenizer.json'], tokenizerConfig: files['tokenizer_config.json']};
|
|
180
193
|
}
|
|
181
194
|
|
|
182
195
|
function beginActivation(request) {
|
|
@@ -187,6 +200,7 @@ export function createNativeDecisionModel({
|
|
|
187
200
|
request.retained = undefined;
|
|
188
201
|
request.entry = entry;
|
|
189
202
|
activation = entry;
|
|
203
|
+
activeSelection = entry.selection;
|
|
190
204
|
state = 'loading';
|
|
191
205
|
progress = {phase: 'preparing-model'};
|
|
192
206
|
lastError = null;
|
|
@@ -196,6 +210,7 @@ export function createNativeDecisionModel({
|
|
|
196
210
|
entry.controller.signal.throwIfAborted();
|
|
197
211
|
updateProgress(entry, {phase: 'loading-model'});
|
|
198
212
|
entry.tokenizer = createDecisionTokenizer({
|
|
213
|
+
family: entry.selection.family,
|
|
199
214
|
tokenizerPath: files.tokenizer, tokenizerConfigPath: files.tokenizerConfig,
|
|
200
215
|
signal: entry.controller.signal,
|
|
201
216
|
onError(error) {
|
|
@@ -258,14 +273,22 @@ export function createNativeDecisionModel({
|
|
|
258
273
|
}).finally(function detachLoadCaller() { operationSignal?.removeEventListener('abort', abort); });
|
|
259
274
|
}
|
|
260
275
|
|
|
261
|
-
async function load({
|
|
276
|
+
async function load({
|
|
277
|
+
family: requestedFamily, model: requestedModel, revision: requestedRevision, dtype: requestedDtype,
|
|
278
|
+
signal: operationSignal, executionTarget: requestedTarget, assetProjectionId: requestedProjection, resourcePaths: requestedPaths
|
|
279
|
+
} = {}) {
|
|
262
280
|
assertOpen(operationSignal);
|
|
263
281
|
const projection = requestedProjection === undefined ? selectedActivation.assetProjectionId : requestedProjection;
|
|
264
282
|
const mapping = requestedPaths === undefined ? selectedActivation.resourcePaths : requestedPaths;
|
|
265
283
|
const selection = {
|
|
284
|
+
family: requestedFamily === undefined ? selectedActivation.family : requestedFamily,
|
|
285
|
+
model: requestedModel === undefined ? selectedActivation.model : requestedModel,
|
|
286
|
+
revision: requestedRevision === undefined ? selectedActivation.revision : requestedRevision,
|
|
287
|
+
dtype: requestedDtype === undefined ? selectedActivation.dtype : requestedDtype,
|
|
266
288
|
executionTarget: requestedTarget === undefined ? selectedActivation.executionTarget : requestedTarget,
|
|
267
289
|
...(projection == null ? {} : {assetProjectionId: projection, resourcePaths: mapping && typeof mapping === 'object' && !Array.isArray(mapping) ? {...mapping} : mapping})
|
|
268
290
|
};
|
|
291
|
+
selectedGraph(selection.family, selection.dtype);
|
|
269
292
|
if (pendingActivation && !pendingActivation.controller.signal.aborted && sameActivation(pendingActivation.selection, selection)) {
|
|
270
293
|
return observeLoad(pendingActivation, operationSignal);
|
|
271
294
|
}
|