arcane-os 0.60.0 → 0.61.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.
@@ -121,7 +121,7 @@ function collectCompletion(completion, chunk) {
121
121
  }
122
122
 
123
123
  /** Core owns native processes; applications own runtime and model selection. */
124
- export function createLocalAIService(configuration, {appRoot, runtimes = [], signal, onEvent} = {}) {
124
+ export function createLocalAIService(configuration, {appRoot, runtimes = [], signal, onEvent, prepare} = {}) {
125
125
  const config = normalizeLocalAIConfig(configuration) ?? {runtimes: []};
126
126
  const lifetime = new AbortController();
127
127
  const lifetimeSignal = signal ? AbortSignal.any([signal, lifetime.signal]) : lifetime.signal;
@@ -136,7 +136,7 @@ export function createLocalAIService(configuration, {appRoot, runtimes = [], sig
136
136
  let onnxRecoveryError;
137
137
  let stopONNXSubscription;
138
138
  const onnxSelected = config.runtimes.some(function selectedONNX(requirement) { return requirement.id === 'onnx'; });
139
- const onnxRecord = runtimes.find(function installedONNX(runtime) { return runtime.id === 'onnx'; });
139
+ let onnxRecord = runtimes.find(function installedONNX(runtime) { return runtime.id === 'onnx'; });
140
140
 
141
141
  function onnxStatus() {
142
142
  const current = onnx?.current();
@@ -493,8 +493,13 @@ export function createLocalAIService(configuration, {appRoot, runtimes = [], sig
493
493
  const service = {
494
494
  name: 'local-ai',
495
495
  current: snapshot,
496
- start(currentContext) {
496
+ async start(currentContext) {
497
497
  context = currentContext;
498
+ if (prepare) {
499
+ runtimes = await prepare({signal: lifetimeSignal, onEvent});
500
+ lifetimeSignal.throwIfAborted();
501
+ onnxRecord = runtimes.find(function installedONNX(runtime) { return runtime.id === 'onnx'; });
502
+ }
498
503
  for (const requirement of config.runtimes) {
499
504
  if (!['llama.cpp', 'ollama', 'onnx'].includes(requirement.id)) {
500
505
  throw failure('LOCAL_AI_RUNTIME_UNAVAILABLE', `Core runtime ${requirement.id} is not available in this service.`);
@@ -51,6 +51,8 @@ const SDK_BROWSER_SELF_IMPORTS=new Map([
51
51
  ['arcane-os/ai/browser-wasm',SDK_BROWSER_AI_ENTRY],
52
52
  ['arcane-os/ai/core-local','sdk/ai/core-local.mjs'],
53
53
  ['arcane-os/ai/core-onnx','sdk/ai/core-onnx.mjs'],
54
+ ['arcane-os/ai/core-image','sdk/ai/core-image.mjs'],
55
+ ['arcane-os/ai/core-model-assets','sdk/ai/core-model-assets.mjs'],
54
56
  ['arcane-os/ai/browser-decisions','sdk/ai/browser-decisions.mjs'],
55
57
  ['arcane-os/ai/tool-text-stream','sdk/ai/tool-text-stream.mjs'],
56
58
  ['arcane-os/ai/twin-cloud','sdk/ai/twin-cloud.mjs'],
@@ -2,9 +2,48 @@ import {readFile, writeFile} from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import Is from 'strong-type';
4
4
  import {ArcaneError, ERROR_CODES, throwIfAborted} from '../errors.mjs';
5
+ import {SD14_MODEL} from './image-models/sd14.mjs';
5
6
 
6
7
  const is = new Is(false);
7
- export const LOCAL_AI_RUNTIME_IDS = ['llama.cpp', 'ollama', 'onnx'];
8
+ export const LOCAL_AI_RUNTIME_IDS = ['llama.cpp', 'ollama', 'onnx', 'stable-diffusion.cpp'];
9
+
10
+ /** Image requirements describe assets without acquiring or loading a model. */
11
+ export function normalizeImageRuntimeRequirement(record) {
12
+ const backend = record.backend ?? 'auto';
13
+ if (!['auto', 'metal', 'cpu'].includes(backend)) {
14
+ throw new ArcaneError(ERROR_CODES.usage, `Unknown stable-diffusion.cpp backend: ${String(backend)}.`);
15
+ }
16
+ if (record.models !== undefined && !is.array(record.models)) {
17
+ throw new ArcaneError(ERROR_CODES.usage, 'stable-diffusion.cpp models must be an array of model IDs or descriptors.');
18
+ }
19
+ const models = (record.models ?? []).map(
20
+ function selectedImageModel(value) {
21
+ const model = value === 'sd14' ? SD14_MODEL : value;
22
+ if (!model || !is.object(model) || is.array(model) || !is.string(model.id) || !model.id) {
23
+ throw new ArcaneError(ERROR_CODES.usage, 'An image model needs a descriptor with an id, or the built-in sd14 ID.');
24
+ }
25
+ if (!model.resources || !is.object(model.resources) || is.array(model.resources)) {
26
+ throw new ArcaneError(ERROR_CODES.usage, `Image model ${model.id} needs resources keyed by model role.`);
27
+ }
28
+ if (model.operations !== undefined && !is.array(model.operations)) {
29
+ throw new ArcaneError(ERROR_CODES.usage, `Image model ${model.id} operations must be an array.`);
30
+ }
31
+ return {
32
+ ...model,
33
+ resources: {...model.resources},
34
+ context: {...model.context},
35
+ defaults: {...model.defaults},
36
+ operations: [...(model.operations ?? [])]
37
+ };
38
+ }
39
+ );
40
+ return {
41
+ ...record,
42
+ version: record.version ?? 'master-929-3f8527a',
43
+ backend,
44
+ models
45
+ };
46
+ }
8
47
 
9
48
  /** Runtime requirements are application configuration, separate from models. */
10
49
  export function normalizeLocalAIConfig(value) {
@@ -17,6 +56,7 @@ export function normalizeLocalAIConfig(value) {
17
56
  if (!record || !LOCAL_AI_RUNTIME_IDS.includes(record.id)) {
18
57
  throw new ArcaneError(ERROR_CODES.usage, `Unknown local AI runtime: ${String(record?.id ?? item)}.`);
19
58
  }
59
+ if (record.id === 'stable-diffusion.cpp') return normalizeImageRuntimeRequirement(record);
20
60
  return {...record};
21
61
  });
22
62
  return {...value, runtimes};
@@ -1,6 +1,8 @@
1
1
  import Is from 'strong-type';
2
2
  import {createCoreRuntime} from '../core/runtime.mjs';
3
3
  import {createLocalAIService} from '../core/services/local-ai.mjs';
4
+ import {createLocalImageService} from '../core/services/image.mjs';
5
+ import {createModelAssetService} from '../core/services/model-assets.mjs';
4
6
  import {CORE_PROTOCOL, CoreError, serializeCoreError} from '../../browser-runtime/core/contracts.mjs';
5
7
  import {normalizeLocalAIConfig} from './config.mjs';
6
8
  import {discoverLocalAIRuntimes} from './discover.mjs';
@@ -81,6 +83,8 @@ export function createDevelopmentLocalAI({config, appRoot, directory, applicatio
81
83
  const incoming = new Set();
82
84
  let runtime;
83
85
  let service;
86
+ let imageService;
87
+ let modelAssets;
84
88
  let stopFrames;
85
89
  let preparationError = null;
86
90
  let closing = null;
@@ -157,17 +161,49 @@ export function createDevelopmentLocalAI({config, appRoot, directory, applicatio
157
161
  writeFrame(connection, event('core.state', current));
158
162
  for (const record of current.services) writeFrame(connection, event('core.service.state', record));
159
163
  writeFrame(connection, event('localai.state', service.current()));
164
+ if (imageService) writeFrame(connection, event('image.state', imageService.current()));
165
+ if (modelAssets) writeFrame(connection, event('modelAssets.state', modelAssets.current()));
160
166
  }
161
167
 
162
- const ready = Promise.resolve().then(async function prepareLocalAI() {
168
+ const ready = Promise.resolve().then(function prepareLocalAI() {
163
169
  operationSignal.throwIfAborted();
164
- const discovered = await discoverLocalAIRuntimes({config: selected, appRoot, signal: operationSignal});
165
- operationSignal.throwIfAborted();
166
- const installed = await ensureLocalAIRuntimes({runtimes: discovered.missing, directory, signal: operationSignal, onEvent});
167
- const runtimes = [...discovered.available, ...installed];
168
- operationSignal.throwIfAborted();
169
- service = createLocalAIService(selected, {appRoot, runtimes, signal: operationSignal, onEvent});
170
- runtime = createCoreRuntime({application, version, services: [service]});
170
+ const localConfig = {
171
+ ...selected,
172
+ runtimes: selected.runtimes.filter(function selectedChatRuntime(requirement) {
173
+ return requirement.id !== 'stable-diffusion.cpp';
174
+ })
175
+ };
176
+ service = createLocalAIService(
177
+ localConfig,
178
+ {
179
+ appRoot, signal: operationSignal, onEvent,
180
+ async prepare({signal, onEvent: report}) {
181
+ const discovered = await discoverLocalAIRuntimes({config: localConfig, appRoot, signal});
182
+ const installed = await ensureLocalAIRuntimes({runtimes: discovered.missing, directory, signal, onEvent: report});
183
+ return [...discovered.available, ...installed];
184
+ }
185
+ }
186
+ );
187
+ const services = [service];
188
+ if (selected.runtimes.some(function selectedImageRuntime(requirement) {
189
+ return requirement.id === 'stable-diffusion.cpp';
190
+ })) {
191
+ modelAssets = createModelAssetService({appRoot});
192
+ imageService = createLocalImageService(
193
+ selected,
194
+ {
195
+ appRoot, modelAssets, signal: operationSignal, onEvent,
196
+ async prepare({requirement, signal, onEvent: report}) {
197
+ const installed = await ensureLocalAIRuntimes(
198
+ {runtimes: [requirement], directory, signal, onEvent: report}
199
+ );
200
+ return installed[0];
201
+ }
202
+ }
203
+ );
204
+ services.push(modelAssets, imageService);
205
+ }
206
+ runtime = createCoreRuntime({application, version, services});
171
207
  stopFrames = runtime.onFrame(routeFrame);
172
208
  runtime.start();
173
209
  return runtime;
@@ -350,7 +386,8 @@ export function createDevelopmentLocalAI({config, appRoot, directory, applicatio
350
386
  function current() {
351
387
  return {state: closed ? 'closed' : preparationError ? 'error' : runtime ? 'ready' : 'preparing',
352
388
  error: preparationError ? serializeCoreError(preparationError) : null,
353
- core: runtime?.current() ?? null, localAI: service?.current() ?? null};
389
+ core: runtime?.current() ?? null, localAI: service?.current() ?? null,
390
+ image: imageService?.current() ?? null};
354
391
  }
355
392
 
356
393
  function close() {