@atomic-chat/core 0.20.5 → 0.20.6

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 +340 -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 +402 -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 +216 -0
  54. package/dist/audio/session.js.map +1 -0
  55. package/dist/audio/state.d.ts +68 -0
  56. package/dist/audio/state.js +88 -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,19 @@
1
+ /**
2
+ * `audiocpp_server`'s command line and environment. Pure.
3
+ *
4
+ * `--backend` is always passed (audio.cpp defaults to `cuda`, which a Metal or Vulkan build does not
5
+ * carry) and so is `--no-ui` (the embedded WebUI is on by default once a config is given). `--log` and
6
+ * `--load-progress 1` make the server print the model facts and the load phases the core reads.
7
+ */
8
+ import type { AudioBackend } from '../contracts/index.js';
9
+ /** `ATOMIC_AUDIO_BACKEND=cpu` runs every build on its CPU backend (a device backend that misbehaves). */
10
+ export declare const AUDIO_BACKEND_ENV = "ATOMIC_AUDIO_BACKEND";
11
+ /** The backend a build runs on: its own, unless the environment forces the CPU. */
12
+ export declare function resolveAudioBackend(buildBackend: AudioBackend, env: NodeJS.ProcessEnv): AudioBackend;
13
+ export declare function buildAudioServerArgs(configFile: string, port: number, backend: AudioBackend, threads?: number): string[];
14
+ /**
15
+ * What the child gets on top of the core's environment: audio.cpp unpacks each GGUF package's configs
16
+ * and tokenizer under `$TMPDIR/audiocpp-gguf/` (~37 MB a model), so its temporary folder is the
17
+ * core's own `<data>/audio/tmp` rather than the system's.
18
+ */
19
+ export declare function audioChildEnv(platform: NodeJS.Platform, tmpDir: string): Record<string, string>;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * `audiocpp_server`'s command line and environment. Pure.
3
+ *
4
+ * `--backend` is always passed (audio.cpp defaults to `cuda`, which a Metal or Vulkan build does not
5
+ * carry) and so is `--no-ui` (the embedded WebUI is on by default once a config is given). `--log` and
6
+ * `--load-progress 1` make the server print the model facts and the load phases the core reads.
7
+ */
8
+ /** `ATOMIC_AUDIO_BACKEND=cpu` runs every build on its CPU backend (a device backend that misbehaves). */
9
+ export const AUDIO_BACKEND_ENV = 'ATOMIC_AUDIO_BACKEND';
10
+ /** The backend a build runs on: its own, unless the environment forces the CPU. */
11
+ export function resolveAudioBackend(buildBackend, env) {
12
+ return env[AUDIO_BACKEND_ENV]?.trim().toLowerCase() === 'cpu' ? 'cpu' : buildBackend;
13
+ }
14
+ export function buildAudioServerArgs(configFile, port, backend, threads) {
15
+ return [
16
+ '--config',
17
+ configFile,
18
+ '--no-ui',
19
+ '--host',
20
+ '127.0.0.1',
21
+ '--port',
22
+ String(port),
23
+ '--backend',
24
+ backend,
25
+ '--log',
26
+ '--load-progress',
27
+ '1',
28
+ ...(threads !== undefined && threads > 0 ? ['--threads', String(threads)] : []),
29
+ ];
30
+ }
31
+ /**
32
+ * What the child gets on top of the core's environment: audio.cpp unpacks each GGUF package's configs
33
+ * and tokenizer under `$TMPDIR/audiocpp-gguf/` (~37 MB a model), so its temporary folder is the
34
+ * core's own `<data>/audio/tmp` rather than the system's.
35
+ */
36
+ export function audioChildEnv(platform, tmpDir) {
37
+ return platform === 'win32' ? { TMPDIR: tmpDir, TEMP: tmpDir, TMP: tmpDir } : { TMPDIR: tmpDir };
38
+ }
39
+ //# sourceMappingURL=args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/audio/args.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,yGAAyG;AACzG,MAAM,CAAC,MAAM,iBAAiB,GAAG,sBAAsB,CAAA;AAEvD,mFAAmF;AACnF,MAAM,UAAU,mBAAmB,CAAC,YAA0B,EAAE,GAAsB;IACpF,OAAO,GAAG,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAA;AACtF,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,UAAkB,EAClB,IAAY,EACZ,OAAqB,EACrB,OAAgB;IAEhB,OAAO;QACL,UAAU;QACV,UAAU;QACV,SAAS;QACT,QAAQ;QACR,WAAW;QACX,QAAQ;QACR,MAAM,CAAC,IAAI,CAAC;QACZ,WAAW;QACX,OAAO;QACP,OAAO;QACP,iBAAiB;QACjB,GAAG;QACH,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KAChF,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,QAAyB,EAAE,MAAc;IACrE,OAAO,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAA;AAClG,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * What a running `audiocpp_server` says about its model: the entry `GET /v1/models?include_params=true`
3
+ * lists for it, and the client-facing `AudioCapabilities` built from that entry and the facts the
4
+ * loader traced. Pure.
5
+ */
6
+ import type { AudioCapabilities, AudioModelParam } from '../contracts/index.js';
7
+ import type { AudioModelFacts } from './output.js';
8
+ import type { AudioServerSpec } from './types.js';
9
+ /** One model of `/v1/models`, as far as the core reads it. */
10
+ export interface ServedAudioModel {
11
+ id: string;
12
+ family: string;
13
+ task: string;
14
+ loaded: boolean;
15
+ params: AudioModelParam[];
16
+ }
17
+ /** One declared parameter, or `undefined` when it has no name. Unknown keys are dropped. */
18
+ export declare function parseModelParam(raw: unknown): AudioModelParam | undefined;
19
+ /** Every model of a `/v1/models` body; an entry without an id is skipped, a body that is not a list gives none. */
20
+ export declare function parseServedModels(body: unknown): ServedAudioModel[];
21
+ /** The voice ids of `GET /v1/audio/voices`: strings, or objects naming an `id`, `voice_id` or `name`. */
22
+ export declare function parseVoiceList(body: unknown): string[];
23
+ /** The capabilities of `spec`'s model, as the server that serves it declared them. */
24
+ export declare function buildCapabilities(spec: Pick<AudioServerSpec, 'modelId' | 'mode' | 'family' | 'task'>, served: ServedAudioModel, facts: AudioModelFacts, listedVoices?: readonly string[]): AudioCapabilities;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * What a running `audiocpp_server` says about its model: the entry `GET /v1/models?include_params=true`
3
+ * lists for it, and the client-facing `AudioCapabilities` built from that entry and the facts the
4
+ * loader traced. Pure.
5
+ */
6
+ const isRecord = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
7
+ const isOptionValue = (value) => typeof value === 'string' ||
8
+ typeof value === 'boolean' ||
9
+ (typeof value === 'number' && Number.isFinite(value));
10
+ /** One declared parameter, or `undefined` when it has no name. Unknown keys are dropped. */
11
+ export function parseModelParam(raw) {
12
+ if (!isRecord(raw) || typeof raw['name'] !== 'string' || raw['name'] === '')
13
+ return undefined;
14
+ const param = {
15
+ name: raw['name'],
16
+ type: typeof raw['type'] === 'string' ? raw['type'] : 'string',
17
+ required: raw['required'] === true,
18
+ };
19
+ if (typeof raw['description'] === 'string')
20
+ param.description = raw['description'];
21
+ if (isOptionValue(raw['default']))
22
+ param.default = raw['default'];
23
+ if (typeof raw['min'] === 'number' && Number.isFinite(raw['min']))
24
+ param.min = raw['min'];
25
+ if (typeof raw['max'] === 'number' && Number.isFinite(raw['max']))
26
+ param.max = raw['max'];
27
+ if (Array.isArray(raw['values']))
28
+ param.values = raw['values'].filter((value) => typeof value === 'string');
29
+ return param;
30
+ }
31
+ /** Every model of a `/v1/models` body; an entry without an id is skipped, a body that is not a list gives none. */
32
+ export function parseServedModels(body) {
33
+ const data = isRecord(body) ? body['data'] : undefined;
34
+ if (!Array.isArray(data))
35
+ return [];
36
+ const out = [];
37
+ for (const entry of data) {
38
+ if (!isRecord(entry) || typeof entry['id'] !== 'string')
39
+ continue;
40
+ const params = Array.isArray(entry['params']) ? entry['params'] : [];
41
+ out.push({
42
+ id: entry['id'],
43
+ family: typeof entry['family'] === 'string' ? entry['family'] : '',
44
+ task: typeof entry['task'] === 'string' ? entry['task'] : '',
45
+ loaded: entry['loaded'] === true,
46
+ params: params.map(parseModelParam).filter((param) => param !== undefined),
47
+ });
48
+ }
49
+ return out;
50
+ }
51
+ /** The voice ids of `GET /v1/audio/voices`: strings, or objects naming an `id`, `voice_id` or `name`. */
52
+ export function parseVoiceList(body) {
53
+ const voices = isRecord(body) ? body['voices'] : undefined;
54
+ if (!Array.isArray(voices))
55
+ return [];
56
+ const out = [];
57
+ for (const voice of voices) {
58
+ if (typeof voice === 'string')
59
+ out.push(voice);
60
+ else if (isRecord(voice))
61
+ for (const key of ['id', 'voice_id', 'name'])
62
+ if (typeof voice[key] === 'string') {
63
+ out.push(voice[key]);
64
+ break;
65
+ }
66
+ }
67
+ return out;
68
+ }
69
+ /**
70
+ * Families whose variant speaks only in a cloned voice. Qwen3-TTS Base refuses a request without a
71
+ * reference ("Qwen3 base TTS requires voice clone reference audio") although its `voice_id` still
72
+ * lists the CustomVoice speakers.
73
+ */
74
+ const CLONE_ONLY_VARIANTS = { qwen3_tts: /(^|[-_ ])base$/i };
75
+ /** The capabilities of `spec`'s model, as the server that serves it declared them. */
76
+ export function buildCapabilities(spec, served, facts, listedVoices = []) {
77
+ const speech = spec.mode === 'speech';
78
+ const voiceParam = served.params.find((param) => param.name === 'voice_id');
79
+ const cloneOnly = speech && facts.variant !== undefined && (CLONE_ONLY_VARIANTS[spec.family]?.test(facts.variant) ?? false);
80
+ const declared = voiceParam?.values && voiceParam.values.length > 0 ? voiceParam.values : listedVoices;
81
+ const supportsVoiceRef = speech &&
82
+ (cloneOnly ||
83
+ facts.supportsSpeakerReference === true ||
84
+ served.params.some((param) => param.name === 'reference_text'));
85
+ return {
86
+ modelId: spec.modelId,
87
+ mode: spec.mode,
88
+ family: spec.family,
89
+ task: spec.task,
90
+ params: served.params.map((param) => structuredClone(param)),
91
+ voices: speech && !cloneOnly ? [...declared] : [],
92
+ supportsVoiceRef,
93
+ requiresVoiceRef: cloneOnly,
94
+ };
95
+ }
96
+ //# sourceMappingURL=capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../src/audio/capabilities.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAeH,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAoC,EAAE,CACpE,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAEtE,MAAM,aAAa,GAAG,CAAC,KAAc,EAA6B,EAAE,CAClE,OAAO,KAAK,KAAK,QAAQ;IACzB,OAAO,KAAK,KAAK,SAAS;IAC1B,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;AAEvD,4FAA4F;AAC5F,MAAM,UAAU,eAAe,CAAC,GAAY;IAC1C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE;QAAE,OAAO,SAAS,CAAA;IAC7F,MAAM,KAAK,GAAoB;QAC7B,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC;QACjB,IAAI,EAAE,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ;QAC9D,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAC,KAAK,IAAI;KACnC,CAAA;IACD,IAAI,OAAO,GAAG,CAAC,aAAa,CAAC,KAAK,QAAQ;QAAE,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC,aAAa,CAAC,CAAA;IAClF,IAAI,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAAE,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC,CAAA;IACjE,IAAI,OAAO,GAAG,CAAC,KAAK,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAAE,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAA;IACzF,IAAI,OAAO,GAAG,CAAC,KAAK,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAAE,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAA;IACzF,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC9B,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAA;IAC5F,OAAO,KAAK,CAAA;AACd,CAAC;AAED,mHAAmH;AACnH,MAAM,UAAU,iBAAiB,CAAC,IAAa;IAC7C,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACtD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAA;IACnC,MAAM,GAAG,GAAuB,EAAE,CAAA;IAClC,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;QACzB,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,QAAQ;YAAE,SAAQ;QACjE,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QACpE,GAAG,CAAC,IAAI,CAAC;YACP,EAAE,EAAE,KAAK,CAAC,IAAI,CAAC;YACf,MAAM,EAAE,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;YAClE,IAAI,EAAE,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;YAC5D,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,IAAI;YAChC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAA4B,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC;SACrG,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,yGAAyG;AACzG,MAAM,UAAU,cAAc,CAAC,IAAa;IAC1C,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAC1D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAA;IACrC,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;aACzC,IAAI,QAAQ,CAAC,KAAK,CAAC;YACtB,KAAK,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC;gBAC1C,IAAI,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,QAAQ,EAAE,CAAC;oBACnC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;oBACpB,MAAK;gBACP,CAAC;IACP,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;GAIG;AACH,MAAM,mBAAmB,GAAqC,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAA;AAE9F,sFAAsF;AACtF,MAAM,UAAU,iBAAiB,CAC/B,IAAmE,EACnE,MAAwB,EACxB,KAAsB,EACtB,eAAkC,EAAE;IAEpC,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAA;IACrC,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC,CAAA;IAC3E,MAAM,SAAS,GACb,MAAM,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,CAAA;IAC3G,MAAM,QAAQ,GAAG,UAAU,EAAE,MAAM,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAA;IACtG,MAAM,gBAAgB,GACpB,MAAM;QACN,CAAC,SAAS;YACR,KAAK,CAAC,wBAAwB,KAAK,IAAI;YACvC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,gBAAgB,CAAC,CAAC,CAAA;IACnE,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;QAC5D,MAAM,EAAE,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;QACjD,gBAAgB;QAChB,gBAAgB,EAAE,SAAS;KAC5B,CAAA;AACH,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The `server.json` the core writes for each `audiocpp_server` it starts: one model, loaded before
3
+ * the server listens (`lazy_load: false`), never unloaded by the engine (`idle_unload_ms: 0` — the
4
+ * core owns the idle unload) and never joined by another (`max_loaded_models: 1`). audio.cpp declares
5
+ * models only in its config: `/v1/models/load` needs `--ui-management`, which the core never passes.
6
+ * Pure.
7
+ */
8
+ import type { AudioMode, AudioOptions, AudioTask } from '../contracts/index.js';
9
+ import type { AudioServerSpec } from './types.js';
10
+ /** The audio.cpp task a mode is served by: speech is `tts`, music and effects are `gen`. */
11
+ export declare function taskForMode(mode: AudioMode): AudioTask;
12
+ /**
13
+ * The model a load names, absolute. An absolute path is taken as it is; a relative one is resolved
14
+ * against the models folder and refused when it climbs out of it.
15
+ */
16
+ export declare function resolveModelPath(file: string, modelsDir: string, platform: NodeJS.Platform): string;
17
+ export interface AudioServerModelEntry {
18
+ id: string;
19
+ family: string;
20
+ path: string;
21
+ task: AudioTask;
22
+ mode: 'offline';
23
+ load_options?: AudioOptions;
24
+ session_options?: AudioOptions;
25
+ default_request_options?: AudioOptions;
26
+ default_voice_preset?: {
27
+ voice_id: string;
28
+ };
29
+ }
30
+ export interface AudioServerConfig {
31
+ host: '127.0.0.1';
32
+ port: number;
33
+ backend: string;
34
+ lazy_load: false;
35
+ max_loaded_models: 1;
36
+ idle_unload_ms: 0;
37
+ models: AudioServerModelEntry[];
38
+ }
39
+ /** The config for `spec` served on `port`. */
40
+ export declare function buildServerConfig(spec: AudioServerSpec, port: number): AudioServerConfig;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The `server.json` the core writes for each `audiocpp_server` it starts: one model, loaded before
3
+ * the server listens (`lazy_load: false`), never unloaded by the engine (`idle_unload_ms: 0` — the
4
+ * core owns the idle unload) and never joined by another (`max_loaded_models: 1`). audio.cpp declares
5
+ * models only in its config: `/v1/models/load` needs `--ui-management`, which the core never passes.
6
+ * Pure.
7
+ */
8
+ import { posix, win32 } from 'node:path';
9
+ import { audioError } from './errors.js';
10
+ /** The audio.cpp task a mode is served by: speech is `tts`, music and effects are `gen`. */
11
+ export function taskForMode(mode) {
12
+ return mode === 'speech' ? 'tts' : 'gen';
13
+ }
14
+ /**
15
+ * The model a load names, absolute. An absolute path is taken as it is; a relative one is resolved
16
+ * against the models folder and refused when it climbs out of it.
17
+ */
18
+ export function resolveModelPath(file, modelsDir, platform) {
19
+ const path = platform === 'win32' ? win32 : posix;
20
+ if (file.trim() === '')
21
+ throw audioError('INVALID_REQUEST', 'The load names no model file.');
22
+ if (path.isAbsolute(file))
23
+ return path.normalize(file);
24
+ const resolved = path.resolve(modelsDir, file);
25
+ const rel = path.relative(modelsDir, resolved);
26
+ if (rel === '' || rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel))
27
+ throw audioError('INVALID_REQUEST', 'The model file is outside the audio models folder.', file);
28
+ return resolved;
29
+ }
30
+ /** The config for `spec` served on `port`. */
31
+ export function buildServerConfig(spec, port) {
32
+ const model = {
33
+ id: spec.modelId,
34
+ family: spec.family,
35
+ path: spec.modelPath,
36
+ task: spec.task,
37
+ mode: 'offline',
38
+ };
39
+ if (spec.loadOptions && Object.keys(spec.loadOptions).length > 0)
40
+ model.load_options = { ...spec.loadOptions };
41
+ if (spec.sessionOptions && Object.keys(spec.sessionOptions).length > 0)
42
+ model.session_options = { ...spec.sessionOptions };
43
+ if (spec.defaultRequestOptions && Object.keys(spec.defaultRequestOptions).length > 0)
44
+ model.default_request_options = { ...spec.defaultRequestOptions };
45
+ if (spec.defaultVoice !== undefined && spec.defaultVoice !== '')
46
+ model.default_voice_preset = { voice_id: spec.defaultVoice };
47
+ return {
48
+ host: '127.0.0.1',
49
+ port,
50
+ backend: spec.backend,
51
+ lazy_load: false,
52
+ max_loaded_models: 1,
53
+ idle_unload_ms: 0,
54
+ models: [model],
55
+ };
56
+ }
57
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/audio/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AAExC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGxC,4FAA4F;AAC5F,MAAM,UAAU,WAAW,CAAC,IAAe;IACzC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAA;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,SAAiB,EAAE,QAAyB;IACzF,MAAM,IAAI,GAAG,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAA;IACjD,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,MAAM,UAAU,CAAC,iBAAiB,EAAE,+BAA+B,CAAC,CAAA;IAC5F,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAA;IAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAA;IAC9C,IAAI,GAAG,KAAK,EAAE,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QACvF,MAAM,UAAU,CAAC,iBAAiB,EAAE,oDAAoD,EAAE,IAAI,CAAC,CAAA;IACjG,OAAO,QAAQ,CAAA;AACjB,CAAC;AAwBD,8CAA8C;AAC9C,MAAM,UAAU,iBAAiB,CAAC,IAAqB,EAAE,IAAY;IACnE,MAAM,KAAK,GAA0B;QACnC,EAAE,EAAE,IAAI,CAAC,OAAO;QAChB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,IAAI,EAAE,IAAI,CAAC,SAAS;QACpB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,SAAS;KAChB,CAAA;IACD,IAAI,IAAI,CAAC,WAAW,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC;QAC9D,KAAK,CAAC,YAAY,GAAG,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;IAC9C,IAAI,IAAI,CAAC,cAAc,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,MAAM,GAAG,CAAC;QACpE,KAAK,CAAC,eAAe,GAAG,EAAE,GAAG,IAAI,CAAC,cAAc,EAAE,CAAA;IACpD,IAAI,IAAI,CAAC,qBAAqB,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,MAAM,GAAG,CAAC;QAClF,KAAK,CAAC,uBAAuB,GAAG,EAAE,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAA;IACnE,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS,IAAI,IAAI,CAAC,YAAY,KAAK,EAAE;QAC7D,KAAK,CAAC,oBAAoB,GAAG,EAAE,QAAQ,EAAE,IAAI,CAAC,YAAY,EAAE,CAAA;IAC9D,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,IAAI;QACJ,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,SAAS,EAAE,KAAK;QAChB,iBAAiB,EAAE,CAAC;QACpB,cAAc,EAAE,CAAC;QACjB,MAAM,EAAE,CAAC,KAAK,CAAC;KAChB,CAAA;AACH,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Audio-generation failures, carried by `AtomicCoreError` so the control server's envelope and the
3
+ * job records show one shape, as image generation's do (`src/diffusion/errors.ts`).
4
+ */
5
+ import { AtomicCoreError } from '../contracts/index.js';
6
+ import type { AudioErrorBody, AudioErrorCode } from '../contracts/index.js';
7
+ export declare function audioError(code: AudioErrorCode, message: string, details?: string): AtomicCoreError;
8
+ export declare function audioInternalError(message: string, details?: string): AtomicCoreError;
9
+ /** libuv reports a full disk as `ENOSPC` everywhere, Windows' two "disk full" codes included. */
10
+ export declare function isAudioDiskFull(error: unknown): boolean;
11
+ /**
12
+ * An I/O failure on the closest code: a full disk is something the user can act on, everything else
13
+ * is internal, with `context` as the message.
14
+ */
15
+ export declare function audioIoError(context: string, error: unknown): AtomicCoreError;
16
+ export declare function isAudioErrorCode(code: unknown): code is AudioErrorCode;
17
+ /**
18
+ * Any failure as a job record or an event carries it. A code from outside this surface becomes
19
+ * `INTERNAL`, its original code kept in the details.
20
+ */
21
+ export declare function audioErrorBody(error: unknown): AudioErrorBody;
22
+ /** The thrown form of `audioErrorBody`: what a caller awaiting the operation receives. */
23
+ export declare function toAudioError(error: unknown): AtomicCoreError;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Audio-generation failures, carried by `AtomicCoreError` so the control server's envelope and the
3
+ * job records show one shape, as image generation's do (`src/diffusion/errors.ts`).
4
+ */
5
+ import { AtomicCoreError, AUDIO_ERROR_CODES } from '../contracts/index.js';
6
+ export function audioError(code, message, details) {
7
+ return new AtomicCoreError(code, message, details);
8
+ }
9
+ export function audioInternalError(message, details) {
10
+ return audioError('INTERNAL', message, details);
11
+ }
12
+ /** libuv reports a full disk as `ENOSPC` everywhere, Windows' two "disk full" codes included. */
13
+ export function isAudioDiskFull(error) {
14
+ return error?.code === 'ENOSPC';
15
+ }
16
+ /**
17
+ * An I/O failure on the closest code: a full disk is something the user can act on, everything else
18
+ * is internal, with `context` as the message.
19
+ */
20
+ export function audioIoError(context, error) {
21
+ const reason = error instanceof Error ? error.message : String(error);
22
+ if (isAudioDiskFull(error))
23
+ return audioError('DISK_FULL', 'The disk is full.', `${context}: ${reason}`);
24
+ return audioInternalError(context, reason);
25
+ }
26
+ export function isAudioErrorCode(code) {
27
+ return typeof code === 'string' && AUDIO_ERROR_CODES.includes(code);
28
+ }
29
+ /**
30
+ * Any failure as a job record or an event carries it. A code from outside this surface becomes
31
+ * `INTERNAL`, its original code kept in the details.
32
+ */
33
+ export function audioErrorBody(error) {
34
+ if (error instanceof AtomicCoreError) {
35
+ if (isAudioErrorCode(error.code)) {
36
+ return error.details === undefined
37
+ ? { code: error.code, message: error.message }
38
+ : { code: error.code, message: error.message, details: error.details };
39
+ }
40
+ const details = error.details === undefined ? error.code : `${error.code}: ${error.details}`;
41
+ return { code: 'INTERNAL', message: error.message, details };
42
+ }
43
+ return { code: 'INTERNAL', message: error instanceof Error ? error.message : String(error) };
44
+ }
45
+ /** The thrown form of `audioErrorBody`: what a caller awaiting the operation receives. */
46
+ export function toAudioError(error) {
47
+ if (error instanceof AtomicCoreError && isAudioErrorCode(error.code))
48
+ return error;
49
+ const body = audioErrorBody(error);
50
+ return audioError(body.code, body.message, body.details);
51
+ }
52
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/audio/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAG1E,MAAM,UAAU,UAAU,CAAC,IAAoB,EAAE,OAAe,EAAE,OAAgB;IAChF,OAAO,IAAI,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;AACpD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAe,EAAE,OAAgB;IAClE,OAAO,UAAU,CAAC,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;AACjD,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,OAAQ,KAA2C,EAAE,IAAI,KAAK,QAAQ,CAAA;AACxE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,KAAc;IAC1D,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACrE,IAAI,eAAe,CAAC,KAAK,CAAC;QAAE,OAAO,UAAU,CAAC,WAAW,EAAE,mBAAmB,EAAE,GAAG,OAAO,KAAK,MAAM,EAAE,CAAC,CAAA;IACxG,OAAO,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;AAC5C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAa;IAC5C,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAK,iBAAuC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;AAC5F,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,YAAY,eAAe,EAAE,CAAC;QACrC,IAAI,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,OAAO,KAAK,CAAC,OAAO,KAAK,SAAS;gBAChC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE;gBAC9C,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAA;QAC1E,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAA;QAC5F,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,CAAA;IAC9D,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAA;AAC9F,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,KAAK,YAAY,eAAe,IAAI,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAA;IAClF,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,CAAA;IAClC,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;AAC1D,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The audio gallery on disk: `<outputDir>/<jobId>.wav` with its recipe as `<jobId>.json` beside it.
3
+ * The core checks the WAV header, writes the bytes as they came, and reads the clip's length from the
4
+ * header again when it lists it.
5
+ *
6
+ * A `.wav` without a parseable recipe is somebody else's: it is never listed and never deleted, even
7
+ * when it sits in the folder under a name that looks like ours.
8
+ */
9
+ import type { AudioGalleryItem, AudioGalleryListOptions, AudioGalleryPage, AudioRecipe } from '../contracts/index.js';
10
+ export declare const AUDIO_SUFFIX = ".wav";
11
+ export declare const RECIPE_SUFFIX = ".json";
12
+ /** `^[a-f0-9]{32}$`: the job id, which is also the file stem. */
13
+ export declare function isValidAudioId(id: string): boolean;
14
+ export declare function audioPath(dir: string, id: string): string;
15
+ export declare function audioRecipePath(dir: string, id: string): string;
16
+ /** The clips of one folder. Every clip has a unique name, so no lock is needed. */
17
+ export declare class AudioGallery {
18
+ private readonly log;
19
+ constructor(log?: (level: 'info' | 'warn', msg: string) => void);
20
+ /** Write the recipe, then the clip, and answer the item. The WAV must be one. */
21
+ save(dir: string, recipe: AudioRecipe, wav: Buffer): Promise<AudioGalleryItem>;
22
+ get(dir: string, id: string): Promise<AudioGalleryItem | null>;
23
+ /** Our clips, newest first, `limit` of them from `offset`. */
24
+ list(dir: string, options: AudioGalleryListOptions): Promise<AudioGalleryPage>;
25
+ /**
26
+ * Remove a clip and its recipe. A clip that is already gone is not an error; a WAV without our
27
+ * recipe is foreign and refused.
28
+ */
29
+ delete(dir: string, id: string): Promise<void>;
30
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * The audio gallery on disk: `<outputDir>/<jobId>.wav` with its recipe as `<jobId>.json` beside it.
3
+ * The core checks the WAV header, writes the bytes as they came, and reads the clip's length from the
4
+ * header again when it lists it.
5
+ *
6
+ * A `.wav` without a parseable recipe is somebody else's: it is never listed and never deleted, even
7
+ * when it sits in the folder under a name that looks like ours.
8
+ */
9
+ import { mkdir, open, readdir, readFile, rm, stat } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import { isWithin, writeAtomic } from '../diffusion/index.js';
12
+ import { audioError, audioIoError } from './errors.js';
13
+ import { parseAudioRecipe, serializeAudioRecipe } from './recipe.js';
14
+ import { parseWav } from './wav.js';
15
+ export const AUDIO_SUFFIX = '.wav';
16
+ export const RECIPE_SUFFIX = '.json';
17
+ /** How much of a WAV a listing reads for its header. */
18
+ const HEADER_BYTES = 64 * 1024;
19
+ /** How many clips a listing reads at once. */
20
+ const SCAN_CONCURRENCY = 16;
21
+ const WRITE_MESSAGES = {
22
+ write: 'Could not write to the audio folder.',
23
+ finish: 'Could not finish writing the clip.',
24
+ };
25
+ /** `^[a-f0-9]{32}$`: the job id, which is also the file stem. */
26
+ export function isValidAudioId(id) {
27
+ return /^[a-f0-9]{32}$/.test(id);
28
+ }
29
+ export function audioPath(dir, id) {
30
+ return join(dir, `${id}${AUDIO_SUFFIX}`);
31
+ }
32
+ export function audioRecipePath(dir, id) {
33
+ return join(dir, `${id}${RECIPE_SUFFIX}`);
34
+ }
35
+ function checkedId(dir, id) {
36
+ if (!isValidAudioId(id))
37
+ throw audioError('INVALID_REQUEST', 'That is not a gallery clip id.', id);
38
+ if (!isWithin(audioPath(dir, id), dir))
39
+ throw audioError('INVALID_REQUEST', 'That clip is outside the audio folder.', id);
40
+ }
41
+ async function readRecipe(dir, id) {
42
+ const text = await readFile(audioRecipePath(dir, id), 'utf8').catch(() => undefined);
43
+ const recipe = text === undefined ? undefined : parseAudioRecipe(text);
44
+ // A recipe that names another job is not this clip's.
45
+ return recipe?.jobId === id ? recipe : undefined;
46
+ }
47
+ /** The header of the WAV at `path`, from its first bytes; `undefined` when it is missing or not a WAV. */
48
+ async function readWavHeader(path) {
49
+ const handle = await open(path, 'r').catch(() => undefined);
50
+ if (!handle)
51
+ return undefined;
52
+ try {
53
+ const { size } = await handle.stat();
54
+ const buffer = Buffer.alloc(Math.min(size, HEADER_BYTES));
55
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
56
+ const info = parseWav(buffer.subarray(0, bytesRead), size);
57
+ return info ? { info, size } : undefined;
58
+ }
59
+ catch {
60
+ return undefined;
61
+ }
62
+ finally {
63
+ await handle.close().catch(() => { });
64
+ }
65
+ }
66
+ function itemOf(id, path, info, size, recipe) {
67
+ return {
68
+ id,
69
+ path,
70
+ sampleRate: info.sampleRate,
71
+ channels: info.channels,
72
+ audioMs: info.audioMs,
73
+ sizeBytes: size,
74
+ createdAtMs: recipe.createdAtMs,
75
+ recipe,
76
+ };
77
+ }
78
+ async function itemFromDisk(dir, id) {
79
+ const recipe = await readRecipe(dir, id);
80
+ if (!recipe)
81
+ return undefined;
82
+ const path = audioPath(dir, id);
83
+ const header = await readWavHeader(path);
84
+ return header ? itemOf(id, path, header.info, header.size, recipe) : undefined;
85
+ }
86
+ /** The clips of one folder. Every clip has a unique name, so no lock is needed. */
87
+ export class AudioGallery {
88
+ log;
89
+ constructor(log = () => { }) {
90
+ this.log = log;
91
+ }
92
+ /** Write the recipe, then the clip, and answer the item. The WAV must be one. */
93
+ async save(dir, recipe, wav) {
94
+ const id = recipe.jobId;
95
+ checkedId(dir, id);
96
+ const info = parseWav(wav);
97
+ if (!info)
98
+ throw audioError('INVALID_OUTPUT', 'The audio engine returned something that is not a WAV.');
99
+ await mkdir(dir, { recursive: true }).catch((error) => {
100
+ throw audioIoError('Could not create the audio folder.', error);
101
+ });
102
+ // The recipe first: a clip without one is foreign, so a crash between the two leaves nothing listed.
103
+ await writeAtomic(audioRecipePath(dir, id), serializeAudioRecipe(recipe), WRITE_MESSAGES);
104
+ await writeAtomic(audioPath(dir, id), wav, WRITE_MESSAGES);
105
+ return itemOf(id, audioPath(dir, id), info, wav.length, recipe);
106
+ }
107
+ async get(dir, id) {
108
+ checkedId(dir, id);
109
+ return (await itemFromDisk(dir, id)) ?? null;
110
+ }
111
+ /** Our clips, newest first, `limit` of them from `offset`. */
112
+ async list(dir, options) {
113
+ const names = await readdir(dir).catch(() => []);
114
+ const ids = names
115
+ .filter((name) => name.endsWith(AUDIO_SUFFIX))
116
+ .map((name) => name.slice(0, -AUDIO_SUFFIX.length))
117
+ .filter(isValidAudioId);
118
+ const all = [];
119
+ for (let at = 0; at < ids.length; at += SCAN_CONCURRENCY) {
120
+ const found = await Promise.all(ids.slice(at, at + SCAN_CONCURRENCY).map((id) => itemFromDisk(dir, id)));
121
+ for (const item of found)
122
+ if (item)
123
+ all.push(item);
124
+ }
125
+ all.sort((a, b) => b.createdAtMs - a.createdAtMs || (a.id < b.id ? 1 : a.id > b.id ? -1 : 0));
126
+ const items = all.slice(options.offset, options.offset + Math.max(options.limit, 1));
127
+ return { items, hasMore: options.offset + items.length < all.length, total: all.length };
128
+ }
129
+ /**
130
+ * Remove a clip and its recipe. A clip that is already gone is not an error; a WAV without our
131
+ * recipe is foreign and refused.
132
+ */
133
+ async delete(dir, id) {
134
+ checkedId(dir, id);
135
+ const path = audioPath(dir, id);
136
+ const exists = await stat(path).then((s) => s.isFile(), () => false);
137
+ const ours = (await readRecipe(dir, id)) !== undefined;
138
+ if (exists) {
139
+ if (!ours) {
140
+ this.log('warn', `refusing to delete foreign clip ${id}`);
141
+ throw audioError('INVALID_REQUEST', 'That file is not an Atomic Chat clip.', id);
142
+ }
143
+ await rm(path).catch((error) => {
144
+ throw audioIoError('Could not delete the clip.', error);
145
+ });
146
+ }
147
+ // A recipe left behind by a clip already gone goes too; a JSON that is not our recipe stays.
148
+ if (ours)
149
+ await rm(audioRecipePath(dir, id), { force: true }).catch(() => { });
150
+ }
151
+ }
152
+ //# sourceMappingURL=gallery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gallery.js","sourceRoot":"","sources":["../../src/audio/gallery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The HTTP the audio module needs against `audiocpp_server`: a GET or a JSON POST with a deadline and
3
+ * an abort signal, status, content type and the body as bytes back (a speech answer is a WAV, a task
4
+ * answer a JSON document that can run past 100 MB for a long song). Over the raw-socket client rather
5
+ * than the global `fetch`, which in the Bun-compiled binary honours `HTTP_PROXY` even for loopback
6
+ * (ADR 2026-09-15-proxied-downloads-use-a-raw-socket-client), as image generation does.
7
+ */
8
+ export interface AudioResponse {
9
+ status: number;
10
+ contentType: string;
11
+ body: Buffer;
12
+ }
13
+ export interface AudioRequestOptions {
14
+ timeoutMs: number;
15
+ /** Stops the exchange (a cancel); the promise rejects with the abort. */
16
+ signal?: AbortSignal;
17
+ }
18
+ export interface AudioHttpClient {
19
+ get(url: string, options: AudioRequestOptions): Promise<AudioResponse>;
20
+ post(url: string, json: unknown, options: AudioRequestOptions): Promise<AudioResponse>;
21
+ }
22
+ /** Thrown when the deadline, not the caller, ended the exchange. */
23
+ export declare class AudioTimeoutError extends Error {
24
+ constructor(timeoutMs: number);
25
+ }
26
+ export declare function createAudioHttpClient(fetchImpl?: typeof fetch): AudioHttpClient;