arcane-os 0.53.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/NOTICE +4 -1
  3. package/README.md +1 -1
  4. package/bin/arcane-test.mjs +2 -1
  5. package/browser-runtime/ai/browser-decisions.mjs +405 -0
  6. package/browser-runtime/ai/decision-runtime.mjs +281 -0
  7. package/browser-runtime/ai/decision-worker.mjs +67 -0
  8. package/browser-runtime/dependencies/event-pubsub/dependencies/strong-type/index.js +1151 -0
  9. package/browser-runtime/dependencies/event-pubsub/dependencies/strong-type/licence +21 -0
  10. package/browser-runtime/dependencies/event-pubsub/dependencies/strong-type/package.json +61 -0
  11. package/browser-runtime/dependencies/event-pubsub/index.js +2 -2
  12. package/browser-runtime/dependencies/event-pubsub/package.json +11 -4
  13. package/browser-runtime/dependencies/ws-share/WS.js +128 -0
  14. package/browser-runtime/dependencies/ws-share/licence.md +27 -0
  15. package/browser-runtime/dependencies/ws-share/package.json +64 -0
  16. package/browser-runtime/websocket-client.mjs +1 -0
  17. package/docs/event-manager.md +7 -4
  18. package/docs/reference/ai/browser-decisions.md +184 -0
  19. package/docs/reference/inventory/package-api.json +30 -2
  20. package/docs/reference/inventory/runtime-modules.json +17 -4
  21. package/docs/reference/protocols.md +76 -1
  22. package/docs/reference/runtime-modules.md +45 -2
  23. package/docs/reference/sdk-api.md +133 -9
  24. package/docs/reference/websocket-client.md +132 -0
  25. package/node_modules/event-pubsub/CHANGELOG.md +12 -1
  26. package/node_modules/event-pubsub/MIGRATION.md +8 -5
  27. package/node_modules/event-pubsub/README.md +75 -19
  28. package/node_modules/event-pubsub/SECURITY.md +3 -3
  29. package/node_modules/event-pubsub/index.js +2 -2
  30. package/node_modules/event-pubsub/package.json +11 -4
  31. package/package.json +8 -5
  32. package/runtime/arcane/modules/HTMLImport.js +81 -26
  33. package/runtime/arcane/modules/HTMLImportScript.js +3 -0
  34. package/src/import-map.mjs +416 -59
  35. package/src/index.mjs +1 -0
  36. package/src/packager/core.mjs +24 -6
  37. package/src/packager/native-modules.mjs +458 -0
  38. package/src/testing-loader.mjs +41 -20
@@ -0,0 +1,281 @@
1
+ import Is from '../dependencies/strong-type/index.js';
2
+
3
+ const is = new Is(false);
4
+ const questionTypes = {choice: 0, score: 1, noul: 2};
5
+
6
+ function decisionError(code, message, outputs) {
7
+ const error = new Error(message);
8
+ error.code = code;
9
+ if (outputs !== undefined) error.outputs = outputs;
10
+ return error;
11
+ }
12
+
13
+ function modelToken(tokenizer, property, token) {
14
+ const id = tokenizer[property] ?? tokenizer.convert_tokens_to_ids(token);
15
+ if (!is.integer(id)) {
16
+ throw decisionError('ARCANE_DECISION_TOKENIZER_INCOMPATIBLE',
17
+ `The selected tokenizer does not define ${token}.`);
18
+ }
19
+ return id;
20
+ }
21
+
22
+ /** Model framing only: none of the caller's strings are trimmed or replaced. */
23
+ export function encodeDecisionRows(tokenizer, rows, family) {
24
+ if (!is.array(rows)) throw new TypeError('Decision rows must be an array.');
25
+ const laya = family === 'laya';
26
+ const start = modelToken(tokenizer, 'cls_token_id', laya ? '[CLS]' : '<bos>');
27
+ const separator = modelToken(tokenizer, 'sep_token_id', laya ? '[SEP]' : '<eos>');
28
+ const marker = modelToken(tokenizer, 'mask_token_id', laya ? '[MASK]' : '<mask>');
29
+ const pad = modelToken(tokenizer, 'pad_token_id', laya ? '[PAD]' : '<pad>');
30
+ const encoded = [];
31
+ const stateTokens = new Map();
32
+ function isDecisionOption(option) {
33
+ return is.string(option);
34
+ }
35
+ for (const row of rows) {
36
+ const type = row?.type ?? 'choice';
37
+ if (!Object.hasOwn(questionTypes, type)) {
38
+ throw new TypeError('A decision type must be choice, score, or noul.');
39
+ }
40
+ if (!is.string(row?.state) || !is.string(row?.question)
41
+ || !is.array(row?.options) || row.options.length === 0
42
+ || !row.options.every(isDecisionOption)) {
43
+ throw new TypeError('A decision row requires string state/question and string options.');
44
+ }
45
+ if (type === 'noul' && row.options.length !== 2) {
46
+ throw new TypeError('A noul decision requires its false and true options in that order.');
47
+ }
48
+ const ids = [start];
49
+ // Encode each complete framed field in one call: BPE boundaries depend
50
+ // on the prefix. encode() avoids the upstream batch truncation path.
51
+ const head = tokenizer.encode(
52
+ `${type} question: ${row.question}`,
53
+ {add_special_tokens: false}
54
+ );
55
+ for (const id of head) ids.push(id);
56
+ ids.push(separator);
57
+ const markers = [];
58
+ for (const option of row.options) {
59
+ markers.push(ids.length);
60
+ ids.push(marker);
61
+ const optionIds = tokenizer.encode(
62
+ ` ${option}`,
63
+ {add_special_tokens: false}
64
+ );
65
+ for (const id of optionIds) ids.push(id);
66
+ }
67
+ ids.push(separator);
68
+ let stateIds = stateTokens.get(row.state);
69
+ if (stateIds === undefined) {
70
+ stateIds = tokenizer.encode(
71
+ row.state,
72
+ {add_special_tokens: false}
73
+ );
74
+ stateTokens.set(row.state, stateIds);
75
+ }
76
+ for (const id of stateIds) ids.push(id);
77
+ ids.push(separator);
78
+ encoded.push(
79
+ {ids, markers, qtype: questionTypes[type]}
80
+ );
81
+ }
82
+ return {encoded, pad};
83
+ }
84
+
85
+ function decisionInputs(Tensor, encoded, pad) {
86
+ let sequenceLength = 0;
87
+ let optionCount = 0;
88
+ for (const row of encoded) {
89
+ sequenceLength = Math.max(sequenceLength, row.ids.length);
90
+ optionCount = Math.max(optionCount, row.markers.length);
91
+ }
92
+ const batch = encoded.length;
93
+ const ids = new BigInt64Array(batch * sequenceLength).fill(BigInt(pad));
94
+ const attention = new BigInt64Array(batch * sequenceLength);
95
+ const positions = new BigInt64Array(batch * optionCount);
96
+ const mask = new Uint8Array(batch * optionCount);
97
+ const types = new BigInt64Array(batch);
98
+ for (let rowIndex = 0; rowIndex < batch; rowIndex += 1) {
99
+ const row = encoded[rowIndex];
100
+ for (let index = 0; index < row.ids.length; index += 1) {
101
+ ids[rowIndex * sequenceLength + index] = BigInt(row.ids[index]);
102
+ attention[rowIndex * sequenceLength + index] = 1n;
103
+ }
104
+ for (let index = 0; index < row.markers.length; index += 1) {
105
+ positions[rowIndex * optionCount + index] = BigInt(row.markers[index]);
106
+ mask[rowIndex * optionCount + index] = 1;
107
+ }
108
+ types[rowIndex] = BigInt(row.qtype);
109
+ }
110
+ return {
111
+ input_ids: new Tensor(
112
+ 'int64', ids,
113
+ [batch, sequenceLength]
114
+ ),
115
+ attention_mask: new Tensor(
116
+ 'int64', attention,
117
+ [batch, sequenceLength]
118
+ ),
119
+ marker_pos: new Tensor(
120
+ 'int64', positions,
121
+ [batch, optionCount]
122
+ ),
123
+ marker_mask: new Tensor(
124
+ 'bool', mask,
125
+ [batch, optionCount]
126
+ ),
127
+ qtype: new Tensor(
128
+ 'int64', types,
129
+ [batch]
130
+ )
131
+ };
132
+ }
133
+
134
+ function softmax(logits) {
135
+ let peak = -Infinity;
136
+ for (const value of logits) peak = Math.max(peak, value);
137
+ const weights = logits.map(
138
+ function exponentiateLogit(value) {
139
+ return Math.exp(value - peak);
140
+ }
141
+ );
142
+ const sum = weights.reduce(
143
+ function addWeight(total, value) {
144
+ return total + value;
145
+ },
146
+ 0
147
+ );
148
+ return weights.map(
149
+ function probability(weight) {
150
+ return weight / sum;
151
+ }
152
+ );
153
+ }
154
+
155
+ export function decodeDecisionOutputs(rows, outputs) {
156
+ const tensor = outputs.logits;
157
+ if (!tensor || tensor.dims.length !== 2 || tensor.dims[0] !== rows.length) {
158
+ throw decisionError(
159
+ 'ARCANE_DECISION_OUTPUT_INCOMPATIBLE',
160
+ 'The selected graph did not return one option-logit row per decision.',
161
+ outputs
162
+ );
163
+ }
164
+ const width = tensor.dims[1];
165
+ function decodeDecision(row, rowIndex) {
166
+ if (width < row.options.length) {
167
+ throw decisionError(
168
+ 'ARCANE_DECISION_OUTPUT_INCOMPATIBLE',
169
+ 'The selected graph omitted option logits.',
170
+ outputs
171
+ );
172
+ }
173
+ const logits = [];
174
+ for (let index = 0; index < row.options.length; index += 1) {
175
+ logits.push(tensor.data[rowIndex * width + index]);
176
+ }
177
+ const probabilities = softmax(logits);
178
+ let answerIndex = 0;
179
+ for (let index = 1; index < logits.length; index += 1) {
180
+ if (logits[index] > logits[answerIndex]) answerIndex = index;
181
+ }
182
+ const type = row.type ?? 'choice';
183
+ let value = row.options[answerIndex];
184
+ if (type === 'noul') value = probabilities[1];
185
+ if (type === 'score') {
186
+ value = probabilities.reduce(
187
+ function expectedScore(total, probability, index) {
188
+ return total + probability * index;
189
+ },
190
+ 0
191
+ );
192
+ }
193
+ const decision = {row, logits, probabilities, answerIndex, value};
194
+ const action = outputs.act_logits;
195
+ if (action) {
196
+ const actionLogits = [];
197
+ for (let index = 0; index < action.dims[1]; index += 1) {
198
+ actionLogits.push(action.data[rowIndex * action.dims[1] + index]);
199
+ }
200
+ decision.actionLogits = actionLogits;
201
+ decision.actionProbabilities = softmax(actionLogits);
202
+ decision.actProbability = decision.actionProbabilities[0];
203
+ }
204
+ return decision;
205
+ }
206
+ const decisions = rows.map(decodeDecision);
207
+ // Retain every graph output, including padding and future named tensors.
208
+ // Probabilities above are raw softmax, not a claimed calibrated confidence.
209
+ return {decisions, outputs};
210
+ }
211
+
212
+ export async function loadDecisionRuntime(configuration, report) {
213
+ const {family, model: repository, revision = 'main', device = 'webgpu', runtime} = configuration;
214
+ report(
215
+ {phase: 'loading-runtime'}
216
+ );
217
+ const namespace = await import(runtime.moduleUrl);
218
+ if (runtime.wasmPaths !== undefined) {
219
+ namespace.env.backends.onnx.wasm.wasmPaths = runtime.wasmPaths;
220
+ }
221
+ const {AutoTokenizer, AutoModel, PreTrainedModel, Tensor} = namespace;
222
+ const options = {revision, device, dtype: family === 'laya' ? 'fp16' : 'fp32'};
223
+ // No progress_callback: upstream 4.3 otherwise performs file-size
224
+ // aggregation. This owner reports actual semantic lifecycle transitions.
225
+ report(
226
+ {phase: 'loading-tokenizer'}
227
+ );
228
+ const tokenizerPromise = AutoTokenizer.from_pretrained(
229
+ repository,
230
+ {revision}
231
+ );
232
+ report(
233
+ {phase: 'loading-model'}
234
+ );
235
+ const modelPromise = family === 'laya'
236
+ ? AutoModel.from_pretrained(
237
+ repository,
238
+ {...options, use_external_data_format: true}
239
+ )
240
+ : PreTrainedModel.from_pretrained(
241
+ repository,
242
+ {
243
+ ...options,
244
+ config: {model_type: 'custom'},
245
+ subfolder: '',
246
+ model_file_name: 'model',
247
+ use_external_data_format: false,
248
+ session_options: {
249
+ externalData: [
250
+ {path: 'model.onnx.data', data: 'model.onnx.data'}
251
+ ]
252
+ }
253
+ }
254
+ );
255
+ const [tokenizer, model] = await Promise.all(
256
+ [tokenizerPromise, modelPromise]
257
+ );
258
+ report(
259
+ {phase: 'ready'}
260
+ );
261
+ async function evaluate(rows) {
262
+ const {encoded, pad} = encodeDecisionRows(tokenizer, rows, family);
263
+ if (encoded.length === 0) return {decisions: [], outputs: {}};
264
+ const inputs = decisionInputs(Tensor, encoded, pad);
265
+ let output;
266
+ try {
267
+ output = await model(inputs);
268
+ const completeOutputs = {};
269
+ for (const [name, tensor] of Object.entries(output)) {
270
+ const data = is.function(tensor.getData) ? await tensor.getData() : tensor.data;
271
+ // Own the complete result before releasing the inference tensors.
272
+ completeOutputs[name] = {type: tensor.type, dims: [...tensor.dims], data: data.slice()};
273
+ }
274
+ return decodeDecisionOutputs(rows, completeOutputs);
275
+ } finally {
276
+ for (const tensor of Object.values(inputs)) tensor.dispose?.();
277
+ for (const tensor of Object.values(output ?? {})) tensor.dispose?.();
278
+ }
279
+ }
280
+ return {evaluate};
281
+ }
@@ -0,0 +1,67 @@
1
+ import Is from '../dependencies/strong-type/index.js';
2
+ import {loadDecisionRuntime} from './decision-runtime.mjs';
3
+
4
+ const is = new Is(false);
5
+ let engine;
6
+
7
+ function errorRecord(error, seen = new Map()) {
8
+ if (!is.error(error)) return error;
9
+ if (seen.has(error)) return seen.get(error);
10
+ const record = {name: error.name, message: error.message, stack: error.stack};
11
+ seen.set(error, record);
12
+ for (const key of Object.getOwnPropertyNames(error)) {
13
+ if (key === 'cause') {
14
+ record[key] = errorRecord(error[key], seen);
15
+ } else if (key === 'errors' && is.array(error[key])) {
16
+ record[key] = error[key].map(
17
+ function recordNestedError(nested) {
18
+ return errorRecord(nested, seen);
19
+ }
20
+ );
21
+ } else {
22
+ record[key] = error[key];
23
+ }
24
+ }
25
+ return record;
26
+ }
27
+
28
+ async function handleDecisionOperation(event) {
29
+ const {id, op, payload} = event.data;
30
+ try {
31
+ if (op === 'load') {
32
+ engine = await loadDecisionRuntime(
33
+ payload,
34
+ function reportDecisionProgress(progress) {
35
+ globalThis.postMessage(
36
+ {id, progress}
37
+ );
38
+ }
39
+ );
40
+ globalThis.postMessage(
41
+ {id, result: {loaded: true}}
42
+ );
43
+ return;
44
+ }
45
+ if (op === 'evaluate') {
46
+ if (!engine) throw new Error('The decision model has not loaded.');
47
+ globalThis.postMessage(
48
+ {id, progress: {phase: 'evaluating'}}
49
+ );
50
+ const result = await engine.evaluate(payload);
51
+ globalThis.postMessage(
52
+ {id, progress: {phase: 'complete'}}
53
+ );
54
+ globalThis.postMessage(
55
+ {id, result}
56
+ );
57
+ return;
58
+ }
59
+ throw new Error(`Unknown decision worker operation: ${op}.`);
60
+ } catch (error) {
61
+ globalThis.postMessage(
62
+ {id, error: errorRecord(error)}
63
+ );
64
+ }
65
+ }
66
+
67
+ globalThis.addEventListener('message', handleDecisionOperation);