@atomic-chat/core 0.20.5 → 0.20.7

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 (175) hide show
  1. package/dist/audio/args.d.ts +19 -0
  2. package/dist/audio/args.js +39 -0
  3. package/dist/audio/args.js.map +1 -0
  4. package/dist/audio/capabilities.d.ts +24 -0
  5. package/dist/audio/capabilities.js +96 -0
  6. package/dist/audio/capabilities.js.map +1 -0
  7. package/dist/audio/config.d.ts +40 -0
  8. package/dist/audio/config.js +57 -0
  9. package/dist/audio/config.js.map +1 -0
  10. package/dist/audio/errors.d.ts +23 -0
  11. package/dist/audio/errors.js +52 -0
  12. package/dist/audio/errors.js.map +1 -0
  13. package/dist/audio/gallery.d.ts +30 -0
  14. package/dist/audio/gallery.js +152 -0
  15. package/dist/audio/gallery.js.map +1 -0
  16. package/dist/audio/http.d.ts +26 -0
  17. package/dist/audio/http.js +56 -0
  18. package/dist/audio/http.js.map +1 -0
  19. package/dist/audio/idle.d.ts +22 -0
  20. package/dist/audio/idle.js +44 -0
  21. package/dist/audio/idle.js.map +1 -0
  22. package/dist/audio/index.d.ts +26 -0
  23. package/dist/audio/index.js +27 -0
  24. package/dist/audio/index.js.map +1 -0
  25. package/dist/audio/install.d.ts +57 -0
  26. package/dist/audio/install.js +196 -0
  27. package/dist/audio/install.js.map +1 -0
  28. package/dist/audio/jobs.d.ts +77 -0
  29. package/dist/audio/jobs.js +344 -0
  30. package/dist/audio/jobs.js.map +1 -0
  31. package/dist/audio/model-files.d.ts +12 -0
  32. package/dist/audio/model-files.js +42 -0
  33. package/dist/audio/model-files.js.map +1 -0
  34. package/dist/audio/output.d.ts +32 -0
  35. package/dist/audio/output.js +93 -0
  36. package/dist/audio/output.js.map +1 -0
  37. package/dist/audio/parse.d.ts +15 -0
  38. package/dist/audio/parse.js +149 -0
  39. package/dist/audio/parse.js.map +1 -0
  40. package/dist/audio/recipe.d.ts +22 -0
  41. package/dist/audio/recipe.js +92 -0
  42. package/dist/audio/recipe.js.map +1 -0
  43. package/dist/audio/request.d.ts +24 -0
  44. package/dist/audio/request.js +86 -0
  45. package/dist/audio/request.js.map +1 -0
  46. package/dist/audio/server-process.d.ts +51 -0
  47. package/dist/audio/server-process.js +202 -0
  48. package/dist/audio/server-process.js.map +1 -0
  49. package/dist/audio/service.d.ts +107 -0
  50. package/dist/audio/service.js +404 -0
  51. package/dist/audio/service.js.map +1 -0
  52. package/dist/audio/session.d.ts +73 -0
  53. package/dist/audio/session.js +228 -0
  54. package/dist/audio/session.js.map +1 -0
  55. package/dist/audio/state.d.ts +70 -0
  56. package/dist/audio/state.js +90 -0
  57. package/dist/audio/state.js.map +1 -0
  58. package/dist/audio/types.d.ts +52 -0
  59. package/dist/audio/types.js +8 -0
  60. package/dist/audio/types.js.map +1 -0
  61. package/dist/audio/wav.d.ts +22 -0
  62. package/dist/audio/wav.js +56 -0
  63. package/dist/audio/wav.js.map +1 -0
  64. package/dist/audio/wiring.d.ts +31 -0
  65. package/dist/audio/wiring.js +41 -0
  66. package/dist/audio/wiring.js.map +1 -0
  67. package/dist/backend/install/service.js +4 -2
  68. package/dist/backend/install/service.js.map +1 -1
  69. package/dist/cli/bin.js +0 -0
  70. package/dist/cli/main.d.ts +1 -1
  71. package/dist/client/control-client.d.ts +21 -1
  72. package/dist/client/control-client.js +50 -0
  73. package/dist/client/control-client.js.map +1 -1
  74. package/dist/client/index.d.ts +1 -0
  75. package/dist/client/index.js +1 -0
  76. package/dist/client/index.js.map +1 -1
  77. package/dist/client/model-catalog.d.ts +40 -0
  78. package/dist/client/model-catalog.js +149 -0
  79. package/dist/client/model-catalog.js.map +1 -0
  80. package/dist/config/paths.d.ts +21 -0
  81. package/dist/config/paths.js +12 -0
  82. package/dist/config/paths.js.map +1 -1
  83. package/dist/contracts/audio.d.ts +290 -0
  84. package/dist/contracts/audio.js +24 -0
  85. package/dist/contracts/audio.js.map +1 -0
  86. package/dist/contracts/embedding.js +1 -0
  87. package/dist/contracts/embedding.js.map +1 -1
  88. package/dist/contracts/engine-builds.d.ts +5 -4
  89. package/dist/contracts/engine-builds.js +10 -2
  90. package/dist/contracts/engine-builds.js.map +1 -1
  91. package/dist/contracts/engines.d.ts +1 -1
  92. package/dist/contracts/engines.js +2 -0
  93. package/dist/contracts/engines.js.map +1 -1
  94. package/dist/contracts/errors.d.ts +7 -1
  95. package/dist/contracts/errors.js +29 -0
  96. package/dist/contracts/errors.js.map +1 -1
  97. package/dist/contracts/events.d.ts +10 -0
  98. package/dist/contracts/index.d.ts +1 -0
  99. package/dist/contracts/index.js +1 -0
  100. package/dist/contracts/index.js.map +1 -1
  101. package/dist/core/atomic-core.d.ts +6 -1
  102. package/dist/core/atomic-core.js +7 -1
  103. package/dist/core/atomic-core.js.map +1 -1
  104. package/dist/core/create.js +30 -5
  105. package/dist/core/create.js.map +1 -1
  106. package/dist/core/gpu-residency.d.ts +8 -2
  107. package/dist/core/gpu-residency.js +9 -1
  108. package/dist/core/gpu-residency.js.map +1 -1
  109. package/dist/core/types.d.ts +3 -0
  110. package/dist/core/types.js.map +1 -1
  111. package/dist/diffusion/install.d.ts +6 -3
  112. package/dist/diffusion/install.js +5 -4
  113. package/dist/diffusion/install.js.map +1 -1
  114. package/dist/embedding/index.js +1 -0
  115. package/dist/embedding/index.js.map +1 -1
  116. package/dist/engine-builds/engines.d.ts +5 -3
  117. package/dist/engine-builds/engines.js +71 -11
  118. package/dist/engine-builds/engines.js.map +1 -1
  119. package/dist/engine-builds/index.d.ts +3 -2
  120. package/dist/engine-builds/index.js +3 -2
  121. package/dist/engine-builds/index.js.map +1 -1
  122. package/dist/engine-builds/manifest.d.ts +52 -5
  123. package/dist/engine-builds/manifest.js +60 -9
  124. package/dist/engine-builds/manifest.js.map +1 -1
  125. package/dist/engine-builds/order.d.ts +5 -0
  126. package/dist/engine-builds/order.js +11 -0
  127. package/dist/engine-builds/order.js.map +1 -1
  128. package/dist/engine-builds/select.d.ts +28 -11
  129. package/dist/engine-builds/select.js +50 -16
  130. package/dist/engine-builds/select.js.map +1 -1
  131. package/dist/engine-builds/service.d.ts +14 -8
  132. package/dist/engine-builds/service.js +10 -7
  133. package/dist/engine-builds/service.js.map +1 -1
  134. package/dist/engines/service.d.ts +2 -2
  135. package/dist/engines/service.js +2 -2
  136. package/dist/lock/process-journal.d.ts +3 -3
  137. package/dist/models/compatibility/rules-baseline.js +1 -0
  138. package/dist/models/compatibility/rules-baseline.js.map +1 -1
  139. package/dist/models/compatibility/rules.js +1 -0
  140. package/dist/models/compatibility/rules.js.map +1 -1
  141. package/dist/models/hf.d.ts +3 -0
  142. package/dist/models/hf.js +11 -4
  143. package/dist/models/hf.js.map +1 -1
  144. package/dist/server/control/index.d.ts +1 -1
  145. package/dist/server/control/router.js +2 -0
  146. package/dist/server/control/router.js.map +1 -1
  147. package/dist/server/control/routes/audio.d.ts +15 -0
  148. package/dist/server/control/routes/audio.js +59 -0
  149. package/dist/server/control/routes/audio.js.map +1 -0
  150. package/dist/server/control/types.d.ts +25 -1
  151. package/dist/server/control/types.js.map +1 -1
  152. package/dist/server/http.js +2 -0
  153. package/dist/server/http.js.map +1 -1
  154. package/dist/server/public/audio-speech-params.d.ts +47 -0
  155. package/dist/server/public/audio-speech-params.js +177 -0
  156. package/dist/server/public/audio-speech-params.js.map +1 -0
  157. package/dist/server/public/audio-speech.d.ts +14 -0
  158. package/dist/server/public/audio-speech.js +118 -0
  159. package/dist/server/public/audio-speech.js.map +1 -0
  160. package/dist/server/public/index.d.ts +1 -1
  161. package/dist/server/public/index.js +7 -0
  162. package/dist/server/public/index.js.map +1 -1
  163. package/dist/server/public/trace.js +2 -0
  164. package/dist/server/public/trace.js.map +1 -1
  165. package/dist/server/public/types.d.ts +29 -1
  166. package/dist/telemetry/reports.d.ts +12 -0
  167. package/dist/telemetry/reports.js +38 -0
  168. package/dist/telemetry/reports.js.map +1 -1
  169. package/dist/telemetry/subscribe.d.ts +3 -3
  170. package/dist/telemetry/subscribe.js +8 -3
  171. package/dist/telemetry/subscribe.js.map +1 -1
  172. package/dist/telemetry/types.d.ts +1 -1
  173. package/dist/version.d.ts +1 -1
  174. package/dist/version.js +1 -1
  175. package/package.json +5 -1
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Session lifecycle: bringing `audiocpp_server` up from a spec, tearing it down, and describing the
3
+ * result as the status the client reads. Every state change here emits `audio:state`. The shape of
4
+ * image generation's `session.ts`, without its offload and CPU fallbacks.
5
+ */
6
+ import { samePath } from '../diffusion/index.js';
7
+ import { audioError, audioErrorBody, toAudioError } from './errors.js';
8
+ import { listInstalledAudioBuilds } from './install.js';
9
+ /** CUDA needs a moment after another engine's process dies before the driver reports the VRAM as free. */
10
+ export const GPU_SETTLE_MS = 500;
11
+ /** Owner exit: a short grace, then the kill. */
12
+ export const SHUTDOWN_GRACE_MS = 2_000;
13
+ export function modelNotLoadedError() {
14
+ return audioError('MODEL_NOT_LOADED', 'No audio model is loaded. Load one first.');
15
+ }
16
+ /** Which GPUs an `audiocpp_server` holds: every card on a device backend, none on the CPU backend. */
17
+ export function audioGpuCards(spec) {
18
+ return spec.backend === 'cpu' ? [] : 'all';
19
+ }
20
+ /** The install the status reports: the resident spec's tree when there is one, otherwise the newest. */
21
+ export async function currentAudioInstall(deps) {
22
+ const records = await listInstalledAudioBuilds(deps.state.paths.backendsDir, deps.platform);
23
+ let chosen = records[0];
24
+ const spec = deps.state.spec;
25
+ if (spec)
26
+ for (const record of records)
27
+ if (await samePath(record.dir, spec.binaryDir, deps.platform)) {
28
+ chosen = record;
29
+ break;
30
+ }
31
+ if (!chosen)
32
+ return { state: 'not-installed' };
33
+ return {
34
+ state: 'installed',
35
+ backend: chosen.backend,
36
+ tag: chosen.tag,
37
+ backendId: chosen.backendId,
38
+ dir: chosen.dir,
39
+ };
40
+ }
41
+ /** Snapshot for `status` and the `state` event. */
42
+ export async function buildAudioStatus(deps) {
43
+ const { state } = deps;
44
+ const model = {
45
+ state: state.modelState,
46
+ loaded: state.modelState === 'loaded' && state.session ? { ...state.session.info } : null,
47
+ };
48
+ if (state.modelError)
49
+ model.error = state.modelError;
50
+ return {
51
+ install: await currentAudioInstall(deps),
52
+ model,
53
+ activeJob: state.activeJob(),
54
+ outputDir: state.outputDir(),
55
+ idleUnloadSecs: state.idleUnloadSecs,
56
+ };
57
+ }
58
+ export async function emitAudioState(deps, reason) {
59
+ deps.emit('audio:state', { status: await buildAudioStatus(deps), reason });
60
+ }
61
+ export function emitAudioError(deps, jobId, error) {
62
+ const payload = { code: error.code, message: error.message };
63
+ if (jobId !== undefined)
64
+ payload.jobId = jobId;
65
+ if (error.details !== undefined)
66
+ payload.details = error.details;
67
+ deps.emit('audio:error', payload);
68
+ }
69
+ /** What the loaded model takes, as its server declared it when it last came up. */
70
+ export function audioCapabilities(state) {
71
+ if (!state.spec || !state.capabilities)
72
+ throw modelNotLoadedError();
73
+ return structuredClone(state.capabilities);
74
+ }
75
+ /**
76
+ * A load failure everywhere it is seen: the model state, the `state` event and the `error` event. A
77
+ * load that `signal` stopped (an unload, a shutdown, a cancelled job's respawn) is no failure: the
78
+ * model is `unloaded` and the error is `CANCELLED`, with no `error` event.
79
+ */
80
+ export async function failLoad(deps, raw, reason, signal) {
81
+ if (signal?.aborted) {
82
+ deps.state.setModelState('unloaded');
83
+ await emitAudioState(deps, 'load-cancelled');
84
+ throw audioError('CANCELLED', 'The audio model load was stopped.');
85
+ }
86
+ const error = toAudioError(raw);
87
+ const body = audioErrorBody(error);
88
+ deps.state.setModelState('failed', body);
89
+ await emitAudioState(deps, reason);
90
+ emitAudioError(deps, undefined, body);
91
+ throw error;
92
+ }
93
+ /**
94
+ * Spawn the server for `spec` and make it the resident session. The caller holds the load lock, and
95
+ * any previous session is already gone — or still exiting after a cancel or a crash outside the lock,
96
+ * which is awaited before the claim and the spawn so two servers of this module never share the GPU.
97
+ */
98
+ export async function loadFromSpec(deps, spec, reason, signal) {
99
+ const { state } = deps;
100
+ state.modelGeneration += 1;
101
+ state.setModelState('loading');
102
+ await emitAudioState(deps, reason);
103
+ let server;
104
+ try {
105
+ // `done` never rejects: an unconfirmed exit puts the server back as the session instead.
106
+ await state.stopping?.done;
107
+ // From the moment the claim is granted this server holds the GPU as starting.
108
+ const hold = () => {
109
+ state.starting = spec;
110
+ };
111
+ if (deps.claimGpu)
112
+ await deps.claimGpu(spec, signal, hold);
113
+ hold();
114
+ if (spec.backend === 'cuda')
115
+ await deps.sleep(GPU_SETTLE_MS);
116
+ server = await deps.spawn(spec, signal);
117
+ }
118
+ catch (raw) {
119
+ return failLoad(deps, raw, 'load-failed', signal);
120
+ }
121
+ finally {
122
+ state.starting = undefined;
123
+ }
124
+ const info = {
125
+ modelId: spec.modelId,
126
+ displayName: spec.displayName,
127
+ mode: spec.mode,
128
+ family: spec.family,
129
+ task: spec.task,
130
+ backend: spec.backend,
131
+ tag: spec.tag,
132
+ backendId: spec.backendId,
133
+ port: server.port,
134
+ pid: server.pid,
135
+ loadedAtMs: deps.now(),
136
+ };
137
+ state.session = { server, info, spec, baseUrl: `http://127.0.0.1:${server.port}` };
138
+ state.spec = spec;
139
+ state.capabilities = server.capabilities;
140
+ state.setModelState('loaded');
141
+ state.touchIdle();
142
+ await emitAudioState(deps, 'loaded');
143
+ return { ...info };
144
+ }
145
+ /**
146
+ * Take the session out of the state and terminate it. Whether one was running. Touches neither the
147
+ * spec nor the model state: callers decide what the teardown means.
148
+ */
149
+ export async function takeDownSession(deps, graceMs) {
150
+ const { state } = deps;
151
+ // Another caller may already be stopping this server outside the load lock (a cancel or crash).
152
+ while (state.stopping)
153
+ await state.stopping.done;
154
+ const session = state.session;
155
+ if (!session)
156
+ return false;
157
+ state.session = undefined;
158
+ // Still on the GPU until its exit is confirmed: GPU residency sees it as stopping until then.
159
+ const stopping = { spec: session.spec, done: Promise.resolve() };
160
+ state.stopping = stopping;
161
+ const exit = Promise.resolve().then(async () => {
162
+ try {
163
+ await session.server.terminate(graceMs);
164
+ await deps.onServerGone?.(session.server.pid);
165
+ }
166
+ catch (error) {
167
+ // Not confirmed: a server that may still be running stays the session, never forgotten.
168
+ if (session.server.exitStatus() === undefined && state.session === undefined)
169
+ state.session = session;
170
+ throw error;
171
+ }
172
+ finally {
173
+ if (state.stopping === stopping)
174
+ state.stopping = undefined;
175
+ }
176
+ });
177
+ stopping.done = exit.catch(() => undefined);
178
+ await exit;
179
+ return true;
180
+ }
181
+ /** Explicit unload: stop the server, forget the spec, report `unloaded`. */
182
+ export async function unloadSession(deps, reason) {
183
+ const { state } = deps;
184
+ state.modelGeneration += 1;
185
+ if (state.modelState === 'loaded') {
186
+ state.setModelState('unloading');
187
+ await emitAudioState(deps, reason);
188
+ }
189
+ await takeDownSession(deps);
190
+ state.spec = undefined;
191
+ state.capabilities = undefined;
192
+ state.clearIdle();
193
+ state.setModelState('unloaded');
194
+ await emitAudioState(deps, reason);
195
+ }
196
+ /**
197
+ * A new engine build is active: a resident server, or a spec kept after a cancel or a crash, that
198
+ * runs from another tree is unloaded, so no job respawns the old binary. The caller holds the load
199
+ * lock and has cancelled any running job.
200
+ */
201
+ export async function activateAudioInstall(deps, record) {
202
+ const spec = deps.state.spec;
203
+ if (!spec)
204
+ return;
205
+ if (spec.tag === record.tag && (await samePath(spec.binaryDir, record.dir, deps.platform)))
206
+ return;
207
+ await unloadSession(deps, 'engine-updated');
208
+ }
209
+ /** The server was stopped but the spec stays: the next job respawns it. */
210
+ export async function stopKeepingSpec(deps, reason, error) {
211
+ const { state } = deps;
212
+ const generation = state.modelGeneration;
213
+ await takeDownSession(deps);
214
+ // A new load/unload owns the state now; this teardown only stopped its original server.
215
+ if (state.closing || state.modelGeneration !== generation)
216
+ return;
217
+ state.clearIdle();
218
+ if (error)
219
+ state.setModelState('failed', error);
220
+ else
221
+ state.setModelState('unloaded');
222
+ await emitAudioState(deps, reason);
223
+ }
224
+ /** Owner exit: no events, no waiting beyond a short grace. */
225
+ export async function shutdownAudioSession(deps) {
226
+ await takeDownSession(deps, SHUTDOWN_GRACE_MS);
227
+ }
228
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/audio/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * What the audio module remembers between calls: one resident server at most, the spec that can bring
3
+ * it back, the model's state as the client sees it, a bounded job history and the idle deadline.
4
+ * Plain data behind small methods; the locks live with the code that holds them.
5
+ */
6
+ import type { AudioPaths } from '../config/index.js';
7
+ import type { AudioCapabilities, AudioErrorBody, AudioJob, AudioJobState, AudioModelState, LoadedAudioModel } from '../contracts/index.js';
8
+ import type { AudioServerHandle, AudioServerSpec } from './types.js';
9
+ export interface AudioSession {
10
+ server: AudioServerHandle;
11
+ info: LoadedAudioModel;
12
+ spec: AudioServerSpec;
13
+ baseUrl: string;
14
+ }
15
+ export interface AudioJobRecord {
16
+ job: AudioJob;
17
+ cancel: {
18
+ requested: boolean;
19
+ /** Aborts the job's request to the engine, or its wait for the server to come back. */
20
+ abort: AbortController;
21
+ };
22
+ /** Settles when the runner is done with the job. */
23
+ done?: Promise<unknown>;
24
+ }
25
+ /** Jobs kept in memory for `getJob`; the gallery is the durable record. */
26
+ export declare const JOB_HISTORY = 50;
27
+ export declare const isTerminalAudioJobState: (state: AudioJobState) => boolean;
28
+ export declare class AudioState {
29
+ readonly paths: AudioPaths;
30
+ private readonly now;
31
+ /** `null` restores the default folder. */
32
+ outputDirOverride: string | null;
33
+ idleUnloadSecs: number;
34
+ session: AudioSession | undefined;
35
+ /** The last spec that loaded; the respawn source after a cancel or a crash. Cleared only by an unload. */
36
+ spec: AudioServerSpec | undefined;
37
+ /** What the server declared for `spec` when it last came up; cleared with it. */
38
+ capabilities: AudioCapabilities | undefined;
39
+ /** A server whose GPU claim succeeded and that is starting: it holds the GPU before it is a session. */
40
+ starting: AudioServerSpec | undefined;
41
+ /** A teardown in flight, through child exit and journal removal; the next load waits for it. */
42
+ stopping: {
43
+ spec: AudioServerSpec;
44
+ done: Promise<void>;
45
+ } | undefined;
46
+ modelState: AudioModelState;
47
+ modelError: AudioErrorBody | undefined;
48
+ /** A load or unload supersedes the state changes of an older asynchronous teardown. */
49
+ modelGeneration: number;
50
+ activeJobId: string | undefined;
51
+ /** Set while the owner shuts down: nothing starts any more. */
52
+ closing: boolean;
53
+ private readonly jobs;
54
+ private readonly jobOrder;
55
+ private idleDeadline;
56
+ constructor(paths: AudioPaths, now?: () => number);
57
+ outputDir(): string;
58
+ setModelState(state: AudioModelState, error?: AudioErrorBody): void;
59
+ /** Reset the idle-unload deadline; 0 seconds means never. */
60
+ touchIdle(): void;
61
+ clearIdle(): void;
62
+ idleExpired(): boolean;
63
+ /** A job's copy. */
64
+ job(id: string): AudioJob | undefined;
65
+ record(id: string): AudioJobRecord | undefined;
66
+ /** The active job while it is not over, for the status. */
67
+ activeJob(): AudioJob | null;
68
+ insertJob(record: AudioJobRecord): void;
69
+ updateJob(id: string, update: (job: AudioJob) => void): AudioJob | undefined;
70
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * What the audio module remembers between calls: one resident server at most, the spec that can bring
3
+ * it back, the model's state as the client sees it, a bounded job history and the idle deadline.
4
+ * Plain data behind small methods; the locks live with the code that holds them.
5
+ */
6
+ import { DEFAULT_IDLE_UNLOAD_SECS } from './types.js';
7
+ /** Jobs kept in memory for `getJob`; the gallery is the durable record. */
8
+ export const JOB_HISTORY = 50;
9
+ const TERMINAL = new Set(['completed', 'failed', 'cancelled']);
10
+ export const isTerminalAudioJobState = (state) => TERMINAL.has(state);
11
+ export class AudioState {
12
+ paths;
13
+ now;
14
+ /** `null` restores the default folder. */
15
+ outputDirOverride = null;
16
+ idleUnloadSecs = DEFAULT_IDLE_UNLOAD_SECS;
17
+ session;
18
+ /** The last spec that loaded; the respawn source after a cancel or a crash. Cleared only by an unload. */
19
+ spec;
20
+ /** What the server declared for `spec` when it last came up; cleared with it. */
21
+ capabilities;
22
+ /** A server whose GPU claim succeeded and that is starting: it holds the GPU before it is a session. */
23
+ starting;
24
+ /** A teardown in flight, through child exit and journal removal; the next load waits for it. */
25
+ stopping;
26
+ modelState = 'unloaded';
27
+ modelError;
28
+ /** A load or unload supersedes the state changes of an older asynchronous teardown. */
29
+ modelGeneration = 0;
30
+ activeJobId;
31
+ /** Set while the owner shuts down: nothing starts any more. */
32
+ closing = false;
33
+ jobs = new Map();
34
+ jobOrder = [];
35
+ idleDeadline;
36
+ constructor(paths, now = Date.now) {
37
+ this.paths = paths;
38
+ this.now = now;
39
+ }
40
+ outputDir() {
41
+ const chosen = this.outputDirOverride?.trim();
42
+ return chosen ? chosen : this.paths.defaultOutputDir;
43
+ }
44
+ setModelState(state, error) {
45
+ this.modelState = state;
46
+ this.modelError = error;
47
+ }
48
+ /** Reset the idle-unload deadline; 0 seconds means never. */
49
+ touchIdle() {
50
+ this.idleDeadline = this.idleUnloadSecs === 0 ? undefined : this.now() + this.idleUnloadSecs * 1000;
51
+ }
52
+ clearIdle() {
53
+ this.idleDeadline = undefined;
54
+ }
55
+ idleExpired() {
56
+ return this.idleDeadline !== undefined && this.now() >= this.idleDeadline;
57
+ }
58
+ /** A job's copy. */
59
+ job(id) {
60
+ const record = this.jobs.get(id);
61
+ return record ? structuredClone(record.job) : undefined;
62
+ }
63
+ record(id) {
64
+ return this.jobs.get(id);
65
+ }
66
+ /** The active job while it is not over, for the status. */
67
+ activeJob() {
68
+ if (this.activeJobId === undefined)
69
+ return null;
70
+ const job = this.job(this.activeJobId);
71
+ return job && !isTerminalAudioJobState(job.state) ? job : null;
72
+ }
73
+ insertJob(record) {
74
+ this.jobOrder.push(record.job.id);
75
+ while (this.jobOrder.length > JOB_HISTORY) {
76
+ const old = this.jobOrder.shift();
77
+ if (old !== undefined)
78
+ this.jobs.delete(old);
79
+ }
80
+ this.jobs.set(record.job.id, record);
81
+ }
82
+ updateJob(id, update) {
83
+ const record = this.jobs.get(id);
84
+ if (!record)
85
+ return undefined;
86
+ update(record.job);
87
+ return structuredClone(record.job);
88
+ }
89
+ }
90
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/audio/state.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,OAAO,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AAqBrD,2EAA2E;AAC3E,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,CAAA;AAE7B,MAAM,QAAQ,GAA+B,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAA;AAC1F,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,KAAoB,EAAW,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;AAE7F,MAAM,OAAO,UAAU;IAyBV;IACQ;IAzBnB,0CAA0C;IAC1C,iBAAiB,GAAkB,IAAI,CAAA;IACvC,cAAc,GAAG,wBAAwB,CAAA;IACzC,OAAO,CAA0B;IACjC,0GAA0G;IAC1G,IAAI,CAA6B;IACjC,iFAAiF;IACjF,YAAY,CAA+B;IAC3C,wGAAwG;IACxG,QAAQ,CAA6B;IACrC,gGAAgG;IAChG,QAAQ,CAA4D;IACpE,UAAU,GAAoB,UAAU,CAAA;IACxC,UAAU,CAA4B;IACtC,uFAAuF;IACvF,eAAe,GAAG,CAAC,CAAA;IACnB,WAAW,CAAoB;IAC/B,+DAA+D;IAC/D,OAAO,GAAG,KAAK,CAAA;IACE,IAAI,GAAG,IAAI,GAAG,EAA0B,CAAA;IACxC,QAAQ,GAAa,EAAE,CAAA;IAChC,YAAY,CAAoB;IAExC,YACW,KAAiB,EACT,MAAoB,IAAI,CAAC,GAAG;QADpC,UAAK,GAAL,KAAK,CAAY;QACT,QAAG,GAAH,GAAG,CAAyB;IAC5C,CAAC;IAEJ,SAAS;QACP,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAA;QAC7C,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAA;IACtD,CAAC;IAED,aAAa,CAAC,KAAsB,EAAE,KAAsB;QAC1D,IAAI,CAAC,UAAU,GAAG,KAAK,CAAA;QACvB,IAAI,CAAC,UAAU,GAAG,KAAK,CAAA;IACzB,CAAC;IAED,6DAA6D;IAC7D,SAAS;QACP,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,cAAc,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,cAAc,GAAG,IAAI,CAAA;IACrG,CAAC;IAED,SAAS;QACP,IAAI,CAAC,YAAY,GAAG,SAAS,CAAA;IAC/B,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,YAAY,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,YAAY,CAAA;IAC3E,CAAC;IAED,oBAAoB;IACpB,GAAG,CAAC,EAAU;QACZ,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAChC,OAAO,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACzD,CAAC;IAED,MAAM,CAAC,EAAU;QACf,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IAC1B,CAAC;IAED,2DAA2D;IAC3D,SAAS;QACP,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;YAAE,OAAO,IAAI,CAAA;QAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACtC,OAAO,GAAG,IAAI,CAAC,uBAAuB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;IAChE,CAAC;IAED,SAAS,CAAC,MAAsB;QAC9B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACjC,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC;YAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAA;YACjC,IAAI,GAAG,KAAK,SAAS;gBAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC9C,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;IACtC,CAAC;IAED,SAAS,CAAC,EAAU,EAAE,MAA+B;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAChC,IAAI,CAAC,MAAM;YAAE,OAAO,SAAS,CAAA;QAC7B,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAClB,OAAO,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACpC,CAAC;CACF"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * What the audio module keeps for itself; nothing here crosses the wire as such.
3
+ */
4
+ import type { AudioBackend, AudioCapabilities, AudioMode, AudioOptions, AudioTask } from '../contracts/index.js';
5
+ import type { ExitInfo } from '../runtime/llamacpp/index.js';
6
+ /**
7
+ * Everything needed to (re)spawn `audiocpp_server` for one model. Kept after a cancel or a crash took
8
+ * the process down, so the next job brings it back without the client repeating the load; cleared
9
+ * only by an unload.
10
+ */
11
+ export interface AudioServerSpec {
12
+ /** Directory holding `audiocpp_server`; also the child's working directory. */
13
+ binaryDir: string;
14
+ /** What `--backend` says: the build's backend, or `cpu` when `ATOMIC_AUDIO_BACKEND=cpu` forces it. */
15
+ backend: AudioBackend;
16
+ /** The backend the build was made for. */
17
+ buildBackend: AudioBackend;
18
+ backendId: string;
19
+ tag: string;
20
+ modelId: string;
21
+ displayName: string;
22
+ mode: AudioMode;
23
+ family: string;
24
+ task: AudioTask;
25
+ /** The model file or package directory, absolute. */
26
+ modelPath: string;
27
+ loadOptions?: AudioOptions;
28
+ sessionOptions?: AudioOptions;
29
+ defaultRequestOptions?: AudioOptions;
30
+ defaultVoice?: string;
31
+ threads?: number;
32
+ startupTimeoutMs: number;
33
+ }
34
+ /** A running `audiocpp_server`, as the session and the job runner see it; the real one wraps a child process. */
35
+ export interface AudioServerHandle {
36
+ pid: number;
37
+ port: number;
38
+ exe: string;
39
+ /** What the server declared about the model once it was ready. */
40
+ capabilities: AudioCapabilities;
41
+ /** The last lines the server printed, oldest first. */
42
+ tail(): string[];
43
+ /** Non-blocking liveness: the exit, once it has happened. */
44
+ exitStatus(): ExitInfo | undefined;
45
+ exited: Promise<ExitInfo>;
46
+ /** SIGTERM, a grace period, then SIGKILL; resolves once the exit is confirmed. */
47
+ terminate(graceMs?: number): Promise<ExitInfo>;
48
+ }
49
+ export declare const DEFAULT_STARTUP_TIMEOUT_SECS = 600;
50
+ export declare const DEFAULT_IDLE_UNLOAD_SECS = 600;
51
+ /** A reference voice larger than this is refused before it reaches the engine. */
52
+ export declare const MAX_VOICE_REF_BYTES: number;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * What the audio module keeps for itself; nothing here crosses the wire as such.
3
+ */
4
+ export const DEFAULT_STARTUP_TIMEOUT_SECS = 600;
5
+ export const DEFAULT_IDLE_UNLOAD_SECS = 600;
6
+ /** A reference voice larger than this is refused before it reaches the engine. */
7
+ export const MAX_VOICE_REF_BYTES = 5 * 1024 * 1024;
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/audio/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAwDH,MAAM,CAAC,MAAM,4BAA4B,GAAG,GAAG,CAAA;AAC/C,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAA;AAC3C,kFAAkF;AAClF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAA"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The only structural check the core makes on a clip: a RIFF/WAVE header with a `fmt ` chunk and a
3
+ * `data` chunk, read for the sample rate, the channels and the length. Pure.
4
+ */
5
+ export interface WavInfo {
6
+ sampleRate: number;
7
+ channels: number;
8
+ bitsPerSample: number;
9
+ /** Bytes of sample data. */
10
+ dataBytes: number;
11
+ /** Length of the clip, rounded to the millisecond. */
12
+ audioMs: number;
13
+ }
14
+ /** The payload of a base64 string that may carry a data-URL prefix. */
15
+ export declare function stripAudioDataUrl(value: string): string;
16
+ /**
17
+ * The header of a WAV, from the whole file or from its first bytes. `fileSize` is the size on disk
18
+ * when only the start was read: a `data` chunk that runs to the end of the file (a streamed WAV says
19
+ * `0xFFFFFFFF`, or the read stopped short) is measured from it. `undefined` when the bytes are not a
20
+ * PCM-style WAV.
21
+ */
22
+ export declare function parseWav(bytes: Buffer, fileSize?: number): WavInfo | undefined;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The only structural check the core makes on a clip: a RIFF/WAVE header with a `fmt ` chunk and a
3
+ * `data` chunk, read for the sample rate, the channels and the length. Pure.
4
+ */
5
+ /** `data:audio/wav;base64,` and friends, ahead of a base64 payload. */
6
+ const DATA_URL_PREFIX = /^data:[^,]*;base64,/i;
7
+ /** The payload of a base64 string that may carry a data-URL prefix. */
8
+ export function stripAudioDataUrl(value) {
9
+ return value.trim().replace(DATA_URL_PREFIX, '');
10
+ }
11
+ /**
12
+ * The header of a WAV, from the whole file or from its first bytes. `fileSize` is the size on disk
13
+ * when only the start was read: a `data` chunk that runs to the end of the file (a streamed WAV says
14
+ * `0xFFFFFFFF`, or the read stopped short) is measured from it. `undefined` when the bytes are not a
15
+ * PCM-style WAV.
16
+ */
17
+ export function parseWav(bytes, fileSize = bytes.length) {
18
+ if (bytes.length < 12)
19
+ return undefined;
20
+ if (bytes.toString('latin1', 0, 4) !== 'RIFF' || bytes.toString('latin1', 8, 12) !== 'WAVE')
21
+ return undefined;
22
+ let at = 12;
23
+ let format;
24
+ while (at + 8 <= bytes.length) {
25
+ const id = bytes.toString('latin1', at, at + 4);
26
+ const size = bytes.readUInt32LE(at + 4);
27
+ const body = at + 8;
28
+ if (id === 'fmt ') {
29
+ if (size < 16 || body + 16 > bytes.length)
30
+ return undefined;
31
+ format = {
32
+ channels: bytes.readUInt16LE(body + 2),
33
+ sampleRate: bytes.readUInt32LE(body + 4),
34
+ byteRate: bytes.readUInt32LE(body + 8),
35
+ bitsPerSample: bytes.readUInt16LE(body + 14),
36
+ };
37
+ }
38
+ else if (id === 'data') {
39
+ if (!format || format.channels === 0 || format.sampleRate === 0 || format.byteRate === 0)
40
+ return undefined;
41
+ const available = Math.max(fileSize - body, 0);
42
+ const dataBytes = size === 0xffff_ffff || size > available ? available : size;
43
+ return {
44
+ sampleRate: format.sampleRate,
45
+ channels: format.channels,
46
+ bitsPerSample: format.bitsPerSample,
47
+ dataBytes,
48
+ audioMs: Math.round((dataBytes / format.byteRate) * 1000),
49
+ };
50
+ }
51
+ // Chunks are word-aligned: an odd size is followed by a pad byte.
52
+ at = body + size + (size % 2);
53
+ }
54
+ return undefined;
55
+ }
56
+ //# sourceMappingURL=wav.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wav.js","sourceRoot":"","sources":["../../src/audio/wav.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAYH,uEAAuE;AACvE,MAAM,eAAe,GAAG,sBAAsB,CAAA;AAE9C,uEAAuE;AACvE,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAA;AAClD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAa,EAAE,QAAQ,GAAG,KAAK,CAAC,MAAM;IAC7D,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE;QAAE,OAAO,SAAS,CAAA;IACvC,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,MAAM;QACzF,OAAO,SAAS,CAAA;IAClB,IAAI,EAAE,GAAG,EAAE,CAAA;IACX,IAAI,MAAqG,CAAA;IACzG,OAAO,EAAE,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QAC9B,MAAM,EAAE,GAAG,KAAK,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC,CAAA;QAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,YAAY,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;QACvC,MAAM,IAAI,GAAG,EAAE,GAAG,CAAC,CAAA;QACnB,IAAI,EAAE,KAAK,MAAM,EAAE,CAAC;YAClB,IAAI,IAAI,GAAG,EAAE,IAAI,IAAI,GAAG,EAAE,GAAG,KAAK,CAAC,MAAM;gBAAE,OAAO,SAAS,CAAA;YAC3D,MAAM,GAAG;gBACP,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,CAAC;gBACtC,UAAU,EAAE,KAAK,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,CAAC;gBACxC,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,CAAC;gBACtC,aAAa,EAAE,KAAK,CAAC,YAAY,CAAC,IAAI,GAAG,EAAE,CAAC;aAC7C,CAAA;QACH,CAAC;aAAM,IAAI,EAAE,KAAK,MAAM,EAAE,CAAC;YACzB,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,IAAI,MAAM,CAAC,UAAU,KAAK,CAAC,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC;gBACtF,OAAO,SAAS,CAAA;YAClB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,IAAI,EAAE,CAAC,CAAC,CAAA;YAC9C,MAAM,SAAS,GAAG,IAAI,KAAK,WAAW,IAAI,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA;YAC7E,OAAO;gBACL,UAAU,EAAE,MAAM,CAAC,UAAU;gBAC7B,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,aAAa,EAAE,MAAM,CAAC,aAAa;gBACnC,SAAS;gBACT,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;aAC1D,CAAA;QACH,CAAC;QACD,kEAAkE;QAClE,EAAE,GAAG,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAA;IAC/B,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * How the owner builds the audio service: the child journal adapter (an `audiocpp_server` is
3
+ * journalled like any backend, under `provider: 'audio'`, right after it spawns) and the event bus.
4
+ * Kept out of `create.ts`, which stays a list of `await`s.
5
+ */
6
+ import type { DataLayout } from '../config/index.js';
7
+ import type { CoreEvents } from '../contracts/index.js';
8
+ import type { ProcessJournal } from '../lock/index.js';
9
+ import type { BackendOutputSink, GpuClaimHook } from '../runtime/shared/index.js';
10
+ import { AudioService } from './service.js';
11
+ import type { AudioServiceDeps } from './service.js';
12
+ /** The provider id audio generation journals its server and claims the GPU under. */
13
+ export declare const AUDIO_PROVIDER = "audio";
14
+ export interface WireAudioOptions {
15
+ layout: DataLayout;
16
+ journal: Pick<ProcessJournal, 'add' | 'remove'>;
17
+ instanceId: string;
18
+ emit: <K extends keyof CoreEvents>(name: K, payload: CoreEvents[K]) => void;
19
+ log: (level: 'info' | 'warn' | 'debug', msg: string) => void;
20
+ /** Every stdout/stderr line `audiocpp_server` prints, for the life of the session. */
21
+ backendOutput?: BackendOutputSink;
22
+ platform?: NodeJS.Platform;
23
+ env?: NodeJS.ProcessEnv;
24
+ /** Core's GPU residency, asked before every `audiocpp_server` spawn. */
25
+ claimGpu?: GpuClaimHook;
26
+ /** Test seams, straight through to the service. */
27
+ overrides?: Pick<AudioServiceDeps, 'http' | 'spawn' | 'timings' | 'idleTickMs' | 'now' | 'sleep' | 'drawSeed'>;
28
+ }
29
+ /** The journal entry an `audiocpp_server` gets the moment it starts, so a crashed owner's successor can reap it. */
30
+ export declare function audioJournal(journal: Pick<ProcessJournal, 'add' | 'remove'>, instanceId: string, now?: () => number): NonNullable<AudioServiceDeps['journal']>;
31
+ export declare function wireAudio(options: WireAudioOptions): AudioService;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * How the owner builds the audio service: the child journal adapter (an `audiocpp_server` is
3
+ * journalled like any backend, under `provider: 'audio'`, right after it spawns) and the event bus.
4
+ * Kept out of `create.ts`, which stays a list of `await`s.
5
+ */
6
+ import { processStartId } from '../lock/index.js';
7
+ import { AudioService } from './service.js';
8
+ /** The provider id audio generation journals its server and claims the GPU under. */
9
+ export const AUDIO_PROVIDER = 'audio';
10
+ /** The journal entry an `audiocpp_server` gets the moment it starts, so a crashed owner's successor can reap it. */
11
+ export function audioJournal(journal, instanceId, now = Date.now) {
12
+ return {
13
+ add: async (pid, port, exe, modelId) => journal.add({
14
+ instance_id: instanceId,
15
+ pid,
16
+ process_start_id: (await processStartId(pid)) ?? null,
17
+ exe,
18
+ provider: AUDIO_PROVIDER,
19
+ model_id: modelId,
20
+ port,
21
+ started_at: new Date(now()).toISOString(),
22
+ }),
23
+ remove: (pid) => journal.remove(pid),
24
+ };
25
+ }
26
+ export function wireAudio(options) {
27
+ const service = new AudioService({
28
+ paths: options.layout.audio,
29
+ emit: options.emit,
30
+ log: options.log,
31
+ journal: audioJournal(options.journal, options.instanceId),
32
+ ...(options.platform ? { platform: options.platform } : {}),
33
+ ...(options.env ? { env: options.env } : {}),
34
+ ...(options.claimGpu ? { claimGpu: options.claimGpu } : {}),
35
+ ...(options.backendOutput ? { backendOutput: options.backendOutput } : {}),
36
+ ...options.overrides,
37
+ });
38
+ service.start();
39
+ return service;
40
+ }
41
+ //# sourceMappingURL=wiring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wiring.js","sourceRoot":"","sources":["../../src/audio/wiring.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAGjD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAG3C,qFAAqF;AACrF,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAA;AAqBrC,oHAAoH;AACpH,MAAM,UAAU,YAAY,CAC1B,OAA+C,EAC/C,UAAkB,EAClB,MAAoB,IAAI,CAAC,GAAG;IAE5B,OAAO;QACL,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,CACrC,OAAO,CAAC,GAAG,CAAC;YACV,WAAW,EAAE,UAAU;YACvB,GAAG;YACH,gBAAgB,EAAE,CAAC,MAAM,cAAc,CAAC,GAAG,CAAC,CAAC,IAAI,IAAI;YACrD,GAAG;YACH,QAAQ,EAAE,cAAc;YACxB,QAAQ,EAAE,OAAO;YACjB,IAAI;YACJ,UAAU,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,WAAW,EAAE;SAC1C,CAAC;QACJ,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC;KACrC,CAAA;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAyB;IACjD,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC;QAC/B,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK;QAC3B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC;QAC1D,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5C,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1E,GAAG,OAAO,CAAC,SAAS;KACrB,CAAC,CAAA;IACF,OAAO,CAAC,KAAK,EAAE,CAAA;IACf,OAAO,OAAO,CAAA;AAChB,CAAC"}
@@ -267,12 +267,14 @@ export class BackendService {
267
267
  await extractArchive(archive, staging);
268
268
  await rm(archive, { force: true });
269
269
  }
270
- await normalizeBackendLayout(staging, llamaServerExeName(platform));
271
- await mergeCudartIntoBin(staging);
270
+ // Companions go first: a flat pack (PrismML's Windows CUDA zip) is normalised by moving every
271
+ // entry at the staging root into `build/bin`, the companion archive still waiting there with them.
272
272
  for (const companion of plan.companions ?? []) {
273
273
  await mergeCompanionIntoBin(staging, companion);
274
274
  await rm(companion, { force: true });
275
275
  }
276
+ await normalizeBackendLayout(staging, llamaServerExeName(platform));
277
+ await mergeCudartIntoBin(staging);
276
278
  const verify = this.deps.provider === 'atomic-prism'
277
279
  ? (this.deps.verifyPrismBackend ?? ((dir, tag) => verifyPrismBackendBinary(dir, tag, platform)))
278
280
  : this.deps.provider === 'llamacpp-upstream' && platform === 'darwin'