@atomic-chat/core 0.19.0 → 0.20.5

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 (219) hide show
  1. package/dist/app-daemon.js +5 -0
  2. package/dist/app-daemon.js.map +1 -1
  3. package/dist/cli/bin.js +0 -0
  4. package/dist/cli/launch.js +2 -0
  5. package/dist/cli/launch.js.map +1 -1
  6. package/dist/cli/main.d.ts +1 -1
  7. package/dist/cli/main.js +5 -0
  8. package/dist/cli/main.js.map +1 -1
  9. package/dist/cloud/registry.js +1 -0
  10. package/dist/cloud/registry.js.map +1 -1
  11. package/dist/config/paths.d.ts +1 -1
  12. package/dist/config/paths.js +12 -0
  13. package/dist/config/paths.js.map +1 -1
  14. package/dist/contracts/engine-builds.d.ts +6 -1
  15. package/dist/contracts/engine-builds.js +1 -1
  16. package/dist/contracts/engine-builds.js.map +1 -1
  17. package/dist/contracts/engines.d.ts +1 -1
  18. package/dist/contracts/engines.js +2 -0
  19. package/dist/contracts/engines.js.map +1 -1
  20. package/dist/contracts/errors.d.ts +7 -1
  21. package/dist/contracts/errors.js.map +1 -1
  22. package/dist/contracts/events.d.ts +3 -1
  23. package/dist/contracts/geniex.d.ts +98 -0
  24. package/dist/contracts/geniex.js +15 -0
  25. package/dist/contracts/geniex.js.map +1 -0
  26. package/dist/contracts/index.d.ts +1 -0
  27. package/dist/contracts/index.js +1 -0
  28. package/dist/contracts/index.js.map +1 -1
  29. package/dist/contracts/model-yml.d.ts +40 -0
  30. package/dist/contracts/model-yml.js +7 -0
  31. package/dist/contracts/model-yml.js.map +1 -1
  32. package/dist/contracts/session.d.ts +6 -1
  33. package/dist/contracts/session.js.map +1 -1
  34. package/dist/contracts/settings.d.ts +12 -1
  35. package/dist/contracts/settings.js.map +1 -1
  36. package/dist/core/app-lease.d.ts +56 -0
  37. package/dist/core/app-lease.js +50 -0
  38. package/dist/core/app-lease.js.map +1 -0
  39. package/dist/core/atomic-core.d.ts +1 -1
  40. package/dist/core/create.js +109 -5
  41. package/dist/core/create.js.map +1 -1
  42. package/dist/engine-builds/engines.d.ts +28 -7
  43. package/dist/engine-builds/engines.js +49 -5
  44. package/dist/engine-builds/engines.js.map +1 -1
  45. package/dist/engine-builds/geniex.d.ts +35 -0
  46. package/dist/engine-builds/geniex.js +111 -0
  47. package/dist/engine-builds/geniex.js.map +1 -0
  48. package/dist/engine-builds/manifest.d.ts +43 -0
  49. package/dist/engine-builds/manifest.js +92 -0
  50. package/dist/engine-builds/manifest.js.map +1 -1
  51. package/dist/engine-builds/order.d.ts +2 -1
  52. package/dist/engine-builds/order.js +2 -0
  53. package/dist/engine-builds/order.js.map +1 -1
  54. package/dist/engine-builds/select.d.ts +15 -0
  55. package/dist/engine-builds/select.js +14 -0
  56. package/dist/engine-builds/select.js.map +1 -1
  57. package/dist/engine-builds/service.d.ts +5 -3
  58. package/dist/engine-builds/service.js +21 -2
  59. package/dist/engine-builds/service.js.map +1 -1
  60. package/dist/engines/service.d.ts +2 -1
  61. package/dist/engines/service.js +3 -1
  62. package/dist/engines/service.js.map +1 -1
  63. package/dist/engines/versions.js +3 -0
  64. package/dist/engines/versions.js.map +1 -1
  65. package/dist/integrations/catalog.d.ts +3 -1
  66. package/dist/integrations/catalog.js +8 -2
  67. package/dist/integrations/catalog.js.map +1 -1
  68. package/dist/integrations/config-io.d.ts +45 -4
  69. package/dist/integrations/config-io.js +91 -6
  70. package/dist/integrations/config-io.js.map +1 -1
  71. package/dist/integrations/configure/codex-repair.d.ts +75 -0
  72. package/dist/integrations/configure/codex-repair.js +254 -0
  73. package/dist/integrations/configure/codex-repair.js.map +1 -0
  74. package/dist/integrations/configure/codex.d.ts +48 -10
  75. package/dist/integrations/configure/codex.js +126 -32
  76. package/dist/integrations/configure/codex.js.map +1 -1
  77. package/dist/integrations/configure/registry.d.ts +4 -1
  78. package/dist/integrations/configure/registry.js +1 -0
  79. package/dist/integrations/configure/registry.js.map +1 -1
  80. package/dist/models/gguf/kv-cache.d.ts +5 -0
  81. package/dist/models/gguf/kv-cache.js +35 -2
  82. package/dist/models/gguf/kv-cache.js.map +1 -1
  83. package/dist/models/gguf/reader.d.ts +5 -0
  84. package/dist/models/gguf/reader.js +15 -3
  85. package/dist/models/gguf/reader.js.map +1 -1
  86. package/dist/router/resolve.d.ts +3 -2
  87. package/dist/router/resolve.js +3 -1
  88. package/dist/router/resolve.js.map +1 -1
  89. package/dist/runtime/geniex/accelerators.d.ts +20 -0
  90. package/dist/runtime/geniex/accelerators.js +53 -0
  91. package/dist/runtime/geniex/accelerators.js.map +1 -0
  92. package/dist/runtime/geniex/check.d.ts +34 -0
  93. package/dist/runtime/geniex/check.js +117 -0
  94. package/dist/runtime/geniex/check.js.map +1 -0
  95. package/dist/runtime/geniex/chip.d.ts +29 -0
  96. package/dist/runtime/geniex/chip.js +35 -0
  97. package/dist/runtime/geniex/chip.js.map +1 -0
  98. package/dist/runtime/geniex/devices.d.ts +13 -0
  99. package/dist/runtime/geniex/devices.js +32 -0
  100. package/dist/runtime/geniex/devices.js.map +1 -0
  101. package/dist/runtime/geniex/geniex-host.d.ts +17 -0
  102. package/dist/runtime/geniex/geniex-host.js +26 -0
  103. package/dist/runtime/geniex/geniex-host.js.map +1 -0
  104. package/dist/runtime/geniex/host/args.d.ts +56 -0
  105. package/dist/runtime/geniex/host/args.js +115 -0
  106. package/dist/runtime/geniex/host/args.js.map +1 -0
  107. package/dist/runtime/geniex/host/engine.d.ts +76 -0
  108. package/dist/runtime/geniex/host/engine.js +122 -0
  109. package/dist/runtime/geniex/host/engine.js.map +1 -0
  110. package/dist/runtime/geniex/host/ffi-bun.d.ts +15 -0
  111. package/dist/runtime/geniex/host/ffi-bun.js +415 -0
  112. package/dist/runtime/geniex/host/ffi-bun.js.map +1 -0
  113. package/dist/runtime/geniex/host/grammar.d.ts +11 -0
  114. package/dist/runtime/geniex/host/grammar.js +223 -0
  115. package/dist/runtime/geniex/host/grammar.js.map +1 -0
  116. package/dist/runtime/geniex/host/inspect.d.ts +25 -0
  117. package/dist/runtime/geniex/host/inspect.js +177 -0
  118. package/dist/runtime/geniex/host/inspect.js.map +1 -0
  119. package/dist/runtime/geniex/host/library.d.ts +117 -0
  120. package/dist/runtime/geniex/host/library.js +38 -0
  121. package/dist/runtime/geniex/host/library.js.map +1 -0
  122. package/dist/runtime/geniex/host/openai.d.ts +62 -0
  123. package/dist/runtime/geniex/host/openai.js +458 -0
  124. package/dist/runtime/geniex/host/openai.js.map +1 -0
  125. package/dist/runtime/geniex/host/run.d.ts +48 -0
  126. package/dist/runtime/geniex/host/run.js +208 -0
  127. package/dist/runtime/geniex/host/run.js.map +1 -0
  128. package/dist/runtime/geniex/host/server.d.ts +39 -0
  129. package/dist/runtime/geniex/host/server.js +232 -0
  130. package/dist/runtime/geniex/host/server.js.map +1 -0
  131. package/dist/runtime/geniex/host/worker-protocol.d.ts +76 -0
  132. package/dist/runtime/geniex/host/worker-protocol.js +116 -0
  133. package/dist/runtime/geniex/host/worker-protocol.js.map +1 -0
  134. package/dist/runtime/geniex/host/worker-source.generated.d.ts +1 -0
  135. package/dist/runtime/geniex/host/worker-source.generated.js +4 -0
  136. package/dist/runtime/geniex/host/worker-source.generated.js.map +1 -0
  137. package/dist/runtime/geniex/host/worker.d.ts +7 -0
  138. package/dist/runtime/geniex/host/worker.js +19 -0
  139. package/dist/runtime/geniex/host/worker.js.map +1 -0
  140. package/dist/runtime/geniex/host-process.d.ts +48 -0
  141. package/dist/runtime/geniex/host-process.js +104 -0
  142. package/dist/runtime/geniex/host-process.js.map +1 -0
  143. package/dist/runtime/geniex/index.d.ts +11 -0
  144. package/dist/runtime/geniex/index.js +12 -0
  145. package/dist/runtime/geniex/index.js.map +1 -0
  146. package/dist/runtime/geniex/model-yml.d.ts +8 -0
  147. package/dist/runtime/geniex/model-yml.js +98 -0
  148. package/dist/runtime/geniex/model-yml.js.map +1 -0
  149. package/dist/runtime/geniex/runtime.d.ts +94 -0
  150. package/dist/runtime/geniex/runtime.js +366 -0
  151. package/dist/runtime/geniex/runtime.js.map +1 -0
  152. package/dist/runtime/litert-lm/host/openai.d.ts +4 -37
  153. package/dist/runtime/litert-lm/host/openai.js +11 -96
  154. package/dist/runtime/litert-lm/host/openai.js.map +1 -1
  155. package/dist/runtime/litert-lm/host/pool.d.ts +1 -7
  156. package/dist/runtime/litert-lm/host/pool.js +1 -17
  157. package/dist/runtime/litert-lm/host/pool.js.map +1 -1
  158. package/dist/runtime/litert-lm/host/server.js +11 -53
  159. package/dist/runtime/litert-lm/host/server.js.map +1 -1
  160. package/dist/runtime/llamacpp/args.d.ts +5 -0
  161. package/dist/runtime/llamacpp/args.js +2 -0
  162. package/dist/runtime/llamacpp/args.js.map +1 -1
  163. package/dist/runtime/llamacpp/fit-margin.d.ts +44 -0
  164. package/dist/runtime/llamacpp/fit-margin.js +50 -0
  165. package/dist/runtime/llamacpp/fit-margin.js.map +1 -1
  166. package/dist/runtime/llamacpp/load-plan.d.ts +5 -0
  167. package/dist/runtime/llamacpp/load-plan.js +57 -22
  168. package/dist/runtime/llamacpp/load-plan.js.map +1 -1
  169. package/dist/runtime/llamacpp/runtime.d.ts +5 -1
  170. package/dist/runtime/llamacpp/runtime.js +6 -1
  171. package/dist/runtime/llamacpp/runtime.js.map +1 -1
  172. package/dist/runtime/shared/external-sessions.js +1 -0
  173. package/dist/runtime/shared/external-sessions.js.map +1 -1
  174. package/dist/runtime/shared/fifo-lock.d.ts +10 -0
  175. package/dist/runtime/shared/fifo-lock.js +21 -0
  176. package/dist/runtime/shared/fifo-lock.js.map +1 -0
  177. package/dist/runtime/shared/index.d.ts +2 -0
  178. package/dist/runtime/shared/index.js +2 -0
  179. package/dist/runtime/shared/index.js.map +1 -1
  180. package/dist/runtime/shared/openai-sse.d.ts +112 -0
  181. package/dist/runtime/shared/openai-sse.js +178 -0
  182. package/dist/runtime/shared/openai-sse.js.map +1 -0
  183. package/dist/server/clients.d.ts +6 -0
  184. package/dist/server/clients.js +11 -0
  185. package/dist/server/clients.js.map +1 -1
  186. package/dist/server/control/router.js +2 -0
  187. package/dist/server/control/router.js.map +1 -1
  188. package/dist/server/control/routes/geniex.d.ts +7 -0
  189. package/dist/server/control/routes/geniex.js +22 -0
  190. package/dist/server/control/routes/geniex.js.map +1 -0
  191. package/dist/server/control/types.d.ts +11 -1
  192. package/dist/server/control/types.js.map +1 -1
  193. package/dist/server/http.js +2 -0
  194. package/dist/server/http.js.map +1 -1
  195. package/dist/server/public/embedding.d.ts +8 -7
  196. package/dist/server/public/embedding.js +15 -7
  197. package/dist/server/public/embedding.js.map +1 -1
  198. package/dist/server/public/forward.js +23 -6
  199. package/dist/server/public/forward.js.map +1 -1
  200. package/dist/server/public/listing.js +1 -0
  201. package/dist/server/public/listing.js.map +1 -1
  202. package/dist/server/public/responses.js +9 -0
  203. package/dist/server/public/responses.js.map +1 -1
  204. package/dist/server/public/telemetry.d.ts +7 -1
  205. package/dist/server/public/telemetry.js +11 -3
  206. package/dist/server/public/telemetry.js.map +1 -1
  207. package/dist/server/public/trace.d.ts +8 -4
  208. package/dist/server/public/trace.js +15 -7
  209. package/dist/server/public/trace.js.map +1 -1
  210. package/dist/server/public/validate.d.ts +32 -0
  211. package/dist/server/public/validate.js +146 -0
  212. package/dist/server/public/validate.js.map +1 -0
  213. package/dist/settings/schema/geniex.json +63 -0
  214. package/dist/settings/schema/mlx.json +1 -1
  215. package/dist/settings/schema.js +11 -0
  216. package/dist/settings/schema.js.map +1 -1
  217. package/dist/version.d.ts +1 -1
  218. package/dist/version.js +1 -1
  219. package/package.json +7 -2
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The main thread's handle on the Worker that owns the library (openspec change `add-geniex-runtime`,
3
+ * design D1): one request at a time, each answered by the Worker's messages, tokens of a generation
4
+ * delivered as they arrive. The Worker is the web-standard one of the Bun runtime; the tests drive
5
+ * `WorkerEngine` with an in-process stand-in that runs the Worker's handler on a fake library.
6
+ */
7
+ import type { GeniexGenerateResult, GeniexGenerationConfig, GeniexModelSettings, GeniexTemplateInput } from './library.js';
8
+ /** What the engine needs of a Worker: the web `Worker` shape, which Bun's is. */
9
+ export interface WorkerLike {
10
+ postMessage(message: unknown): void;
11
+ onmessage: ((event: {
12
+ data: unknown;
13
+ }) => void) | null;
14
+ onerror: ((event: {
15
+ message?: string;
16
+ }) => void) | null;
17
+ terminate(): void;
18
+ }
19
+ export interface GeniexProbeAnswer {
20
+ version: string | null;
21
+ abi: number;
22
+ plugins: string[];
23
+ devices: string[];
24
+ }
25
+ /** The host's asynchronous view of the library; `WorkerEngine` is the one implementation. */
26
+ export interface GeniexEngine {
27
+ probe(libDir: string): Promise<GeniexProbeAnswer>;
28
+ create(libDir: string, settings: GeniexModelSettings): Promise<{
29
+ vision: boolean;
30
+ }>;
31
+ applyChatTemplate(input: GeniexTemplateInput): Promise<string>;
32
+ /**
33
+ * Tokens arrive through `onToken` as the library makes them; `cancel` (one `Int32`, set non-zero)
34
+ * ends the generation, which then resolves with `stopReason: 'cancelled'`.
35
+ */
36
+ generate(prompt: string, config: GeniexGenerationConfig, onToken: (token: string) => void, cancel: Int32Array): Promise<GeniexGenerateResult>;
37
+ destroy(): Promise<void>;
38
+ }
39
+ /** The Worker stopped before answering: a crash of the native library takes the Worker with it. */
40
+ export declare class GeniexWorkerGone extends Error {
41
+ constructor(message: string);
42
+ }
43
+ export declare class WorkerEngine implements GeniexEngine {
44
+ private readonly worker;
45
+ private tail;
46
+ private pending;
47
+ private onToken;
48
+ private nextId;
49
+ private gone;
50
+ constructor(worker: WorkerLike);
51
+ private receive;
52
+ /**
53
+ * One request, answered by the next non-token message; requests queue behind each other. `before`
54
+ * runs when the request's turn comes, not when it is queued: a generate's token handler must not
55
+ * replace the one of a generation still streaming.
56
+ */
57
+ private ask;
58
+ private static refused;
59
+ probe(libDir: string): Promise<GeniexProbeAnswer>;
60
+ create(libDir: string, settings: GeniexModelSettings): Promise<{
61
+ vision: boolean;
62
+ }>;
63
+ applyChatTemplate(input: GeniexTemplateInput): Promise<string>;
64
+ generate(prompt: string, config: GeniexGenerationConfig, onToken: (token: string) => void, cancel: Int32Array): Promise<GeniexGenerateResult>;
65
+ destroy(): Promise<void>;
66
+ }
67
+ /**
68
+ * The Worker of the compiled binaries, started from a `blob:` URL over the script that
69
+ * `scripts/build-geniex-worker.mjs` bundles out of `worker.ts` into `worker-source.generated.ts`.
70
+ * Not from a path: inside a compiled Windows binary the Bun that CI pins (1.3.10) reads a Worker's
71
+ * `B:\~BUN\root\worker.js` from the real disk (ENOENT; oven-sh/bun#16869, #29124, fixed in 1.4.1),
72
+ * and a bare `./worker.js` is resolved against the working directory. The generated module is imported
73
+ * here and only here, so a checkout without a build still loads this file; under Node there is no
74
+ * `Worker` global, and no host either.
75
+ */
76
+ export declare function spawnGeniexWorker(): Promise<WorkerLike>;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * The main thread's handle on the Worker that owns the library (openspec change `add-geniex-runtime`,
3
+ * design D1): one request at a time, each answered by the Worker's messages, tokens of a generation
4
+ * delivered as they arrive. The Worker is the web-standard one of the Bun runtime; the tests drive
5
+ * `WorkerEngine` with an in-process stand-in that runs the Worker's handler on a fake library.
6
+ */
7
+ import { GeniexLibraryError } from './library.js';
8
+ /** The Worker stopped before answering: a crash of the native library takes the Worker with it. */
9
+ export class GeniexWorkerGone extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = 'GeniexWorkerGone';
13
+ }
14
+ }
15
+ export class WorkerEngine {
16
+ worker;
17
+ tail = Promise.resolve();
18
+ pending = null;
19
+ onToken = null;
20
+ nextId = 1;
21
+ gone = null;
22
+ constructor(worker) {
23
+ this.worker = worker;
24
+ worker.onmessage = (event) => this.receive(event.data);
25
+ worker.onerror = (event) => {
26
+ this.gone = new GeniexWorkerGone(`the GenieX worker failed: ${event.message ?? 'unknown error'}`);
27
+ this.pending?.reject(this.gone);
28
+ this.pending = null;
29
+ };
30
+ }
31
+ receive(answer) {
32
+ if (answer.type === 'token') {
33
+ this.onToken?.(answer.text);
34
+ return;
35
+ }
36
+ const pending = this.pending;
37
+ this.pending = null;
38
+ pending?.resolve(answer);
39
+ }
40
+ /**
41
+ * One request, answered by the next non-token message; requests queue behind each other. `before`
42
+ * runs when the request's turn comes, not when it is queued: a generate's token handler must not
43
+ * replace the one of a generation still streaming.
44
+ */
45
+ ask(request, before) {
46
+ const run = this.tail.then(() => new Promise((resolve, reject) => {
47
+ if (this.gone)
48
+ return reject(this.gone);
49
+ before?.();
50
+ this.pending = { resolve, reject };
51
+ this.worker.postMessage(request);
52
+ }));
53
+ this.tail = run.catch(() => { });
54
+ return run;
55
+ }
56
+ static refused(answer, what) {
57
+ if (answer.type === 'failed' || answer.type === 'error')
58
+ throw new GeniexLibraryError(answer.type === 'failed' ? answer.stage : what, answer.message, answer.code);
59
+ throw new GeniexLibraryError(what, `unexpected answer ${answer.type}`);
60
+ }
61
+ async probe(libDir) {
62
+ const answer = await this.ask({ type: 'probe', libDir });
63
+ if (answer.type !== 'probed')
64
+ WorkerEngine.refused(answer, 'probe');
65
+ return { version: answer.version, abi: answer.abi, plugins: answer.plugins, devices: answer.devices };
66
+ }
67
+ async create(libDir, settings) {
68
+ const answer = await this.ask({ type: 'create', libDir, settings });
69
+ if (answer.type !== 'created')
70
+ WorkerEngine.refused(answer, 'create');
71
+ return { vision: answer.vision };
72
+ }
73
+ async applyChatTemplate(input) {
74
+ const answer = await this.ask({ type: 'template', id: this.nextId++, input });
75
+ if (answer.type !== 'templated')
76
+ WorkerEngine.refused(answer, 'apply_chat_template');
77
+ return answer.prompt;
78
+ }
79
+ async generate(prompt, config, onToken, cancel) {
80
+ const buffer = cancel.buffer;
81
+ if (!(buffer instanceof SharedArrayBuffer))
82
+ throw new GeniexLibraryError('generate', 'the cancel flag must live in a SharedArrayBuffer');
83
+ try {
84
+ const answer = await this.ask({ type: 'generate', id: this.nextId++, prompt, config, cancel: buffer }, () => {
85
+ this.onToken = onToken;
86
+ });
87
+ if (answer.type !== 'done')
88
+ WorkerEngine.refused(answer, 'generate');
89
+ return answer.result;
90
+ }
91
+ finally {
92
+ if (this.onToken === onToken)
93
+ this.onToken = null;
94
+ }
95
+ }
96
+ async destroy() {
97
+ try {
98
+ if (!this.gone)
99
+ await this.ask({ type: 'destroy' });
100
+ }
101
+ finally {
102
+ this.worker.terminate();
103
+ }
104
+ }
105
+ }
106
+ /**
107
+ * The Worker of the compiled binaries, started from a `blob:` URL over the script that
108
+ * `scripts/build-geniex-worker.mjs` bundles out of `worker.ts` into `worker-source.generated.ts`.
109
+ * Not from a path: inside a compiled Windows binary the Bun that CI pins (1.3.10) reads a Worker's
110
+ * `B:\~BUN\root\worker.js` from the real disk (ENOENT; oven-sh/bun#16869, #29124, fixed in 1.4.1),
111
+ * and a bare `./worker.js` is resolved against the working directory. The generated module is imported
112
+ * here and only here, so a checkout without a build still loads this file; under Node there is no
113
+ * `Worker` global, and no host either.
114
+ */
115
+ export async function spawnGeniexWorker() {
116
+ const ctor = globalThis.Worker;
117
+ if (!ctor)
118
+ throw new GeniexLibraryError('load', 'geniex-host runs only inside a compiled binary (no Worker)');
119
+ const { GENIEX_WORKER_SOURCE } = await import(/* @vite-ignore */ './worker-source.generated.js');
120
+ return new ctor(URL.createObjectURL(new Blob([GENIEX_WORKER_SOURCE], { type: 'application/javascript' })));
121
+ }
122
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../../src/runtime/geniex/host/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `GeniexLibrary` over `bun:ffi` — the third file in `src/` that may import `bun:*` (change
3
+ * `add-geniex-runtime`, design D1; allow-listed in both runtime-agnostic gates with `worker.ts`). It
4
+ * is imported by the Worker alone, so the npm package still loads under Node.
5
+ *
6
+ * The binding follows `sdk/include/geniex.h` of GenieX v0.9.3 (ABI 1 of conf's manifest). Structs
7
+ * are laid out by hand for a 64-bit little-endian target with natural alignment (Windows arm64 is
8
+ * the only one); each layout names its offsets next to the C declaration it mirrors. The token
9
+ * callback is a plain `JSCallback`: the library calls it synchronously on this very thread, from
10
+ * inside `geniex_llm_generate`, which is why the whole binding lives in the Worker (design D1).
11
+ *
12
+ * Unverified against the real library until the spike on a Snapdragon X laptop (rulings/core.md).
13
+ */
14
+ import type { LoadGeniexLibrary } from './library.js';
15
+ export declare const loadBunGeniexLibrary: LoadGeniexLibrary;
@@ -0,0 +1,415 @@
1
+ /**
2
+ * `GeniexLibrary` over `bun:ffi` — the third file in `src/` that may import `bun:*` (change
3
+ * `add-geniex-runtime`, design D1; allow-listed in both runtime-agnostic gates with `worker.ts`). It
4
+ * is imported by the Worker alone, so the npm package still loads under Node.
5
+ *
6
+ * The binding follows `sdk/include/geniex.h` of GenieX v0.9.3 (ABI 1 of conf's manifest). Structs
7
+ * are laid out by hand for a 64-bit little-endian target with natural alignment (Windows arm64 is
8
+ * the only one); each layout names its offsets next to the C declaration it mirrors. The token
9
+ * callback is a plain `JSCallback`: the library calls it synchronously on this very thread, from
10
+ * inside `geniex_llm_generate`, which is why the whole binding lives in the Worker (design D1).
11
+ *
12
+ * Unverified against the real library until the spike on a Snapdragon X laptop (rulings/core.md).
13
+ */
14
+ import { dlopen, FFIType, JSCallback, ptr, read, CString } from 'bun:ffi';
15
+ import { existsSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ import { BOUND_GENIEX_ABI, GENIEX_CANCELLED, GENIEX_POWER_MODES, GeniexLibraryError } from './library.js';
18
+ const T = FFIType;
19
+ const LIBRARY_FILE = 'geniex.dll';
20
+ /** The QAIRT runtime inside a build; told to the library so the `qairt` plugin finds it. */
21
+ const QAIRT_DIR = 'qairt';
22
+ const LOG_LEVEL_ERROR = 4;
23
+ const STOP_REASONS = ['eos', 'length', 'user', 'stop_sequence', 'context_length'];
24
+ const SYMBOLS = {
25
+ geniex_get_error_message: { args: [T.i32], returns: T.cstring },
26
+ geniex_init: { args: [], returns: T.i32 },
27
+ geniex_deinit: { args: [], returns: T.i32 },
28
+ geniex_set_log_level: { args: [T.i32], returns: T.i32 },
29
+ geniex_set_qairt_runtime_path: { args: [T.ptr], returns: T.i32 },
30
+ geniex_free: { args: [T.ptr], returns: T.void },
31
+ geniex_version: { args: [], returns: T.cstring },
32
+ geniex_get_plugin_list: { args: [T.ptr], returns: T.i32 },
33
+ geniex_get_device_list: { args: [T.ptr, T.ptr], returns: T.i32 },
34
+ geniex_resolve_device: { args: [T.ptr, T.ptr], returns: T.i32 },
35
+ geniex_llm_create: { args: [T.ptr, T.ptr], returns: T.i32 },
36
+ geniex_llm_destroy: { args: [T.ptr], returns: T.i32 },
37
+ geniex_llm_apply_chat_template: { args: [T.ptr, T.ptr, T.ptr], returns: T.i32 },
38
+ geniex_llm_generate: { args: [T.ptr, T.ptr, T.ptr], returns: T.i32 },
39
+ geniex_vlm_create: { args: [T.ptr, T.ptr], returns: T.i32 },
40
+ geniex_vlm_destroy: { args: [T.ptr], returns: T.i32 },
41
+ geniex_vlm_apply_chat_template: { args: [T.ptr, T.ptr, T.ptr], returns: T.i32 },
42
+ geniex_vlm_generate: { args: [T.ptr, T.ptr, T.ptr], returns: T.i32 },
43
+ geniex_vlm_reset: { args: [T.ptr], returns: T.i32 },
44
+ };
45
+ const cstr = (value) => Buffer.from(`${value}\0`, 'utf8');
46
+ const wide = (value) => Buffer.from(`${value}\0`, 'utf16le');
47
+ const text = (value) => {
48
+ const s = value == null ? '' : String(value);
49
+ return s === '' ? null : s;
50
+ };
51
+ /** Keeps every buffer a struct points at alive for the length of one library call. */
52
+ class Arena {
53
+ kept = [];
54
+ str(value) {
55
+ if (value === null)
56
+ return null;
57
+ const buffer = cstr(value);
58
+ this.kept.push(buffer);
59
+ return ptr(buffer);
60
+ }
61
+ /** A NUL-terminated array of C strings; `null` for an empty list. */
62
+ strings(values) {
63
+ if (values.length === 0)
64
+ return null;
65
+ const table = Buffer.alloc(8 * values.length);
66
+ values.forEach((value, index) => table.writeBigUInt64LE(BigInt(this.str(value) ?? 0), index * 8));
67
+ this.kept.push(table);
68
+ return ptr(table);
69
+ }
70
+ struct(size) {
71
+ const buffer = Buffer.alloc(size);
72
+ this.kept.push(buffer);
73
+ return buffer;
74
+ }
75
+ }
76
+ const writePtr = (buffer, offset, value) => buffer.writeBigUInt64LE(BigInt(value ?? 0), offset);
77
+ const readPtr = (buffer, offset) => {
78
+ const value = buffer.readBigUInt64LE(offset);
79
+ return value === 0n ? null : Number(value);
80
+ };
81
+ /** Windows searches the library's own folder for its DLLs only when told to. */
82
+ function prepareWindowsSearch(libDir) {
83
+ const k32 = dlopen('kernel32.dll', { SetDllDirectoryW: { args: [T.ptr], returns: T.i32 } }).symbols;
84
+ k32.SetDllDirectoryW(ptr(wide(libDir)));
85
+ }
86
+ /**
87
+ * Windows: out of EcoQoS, as `litert-lm/host/ffi-bun.ts` does. A process with no window counts as
88
+ * background work and runs on efficiency cores at low clocks; a failure only costs speed.
89
+ */
90
+ function leaveEcoQos() {
91
+ const k32 = dlopen('kernel32.dll', {
92
+ SetProcessInformation: { args: [T.i64, T.i32, T.ptr, T.u32], returns: T.i32 },
93
+ }).symbols;
94
+ const PROCESS_POWER_THROTTLING = 4;
95
+ const state = new Uint32Array([1, 1, 0]);
96
+ k32.SetProcessInformation(-1n, PROCESS_POWER_THROTTLING, ptr(state), state.byteLength);
97
+ }
98
+ export const loadBunGeniexLibrary = async (libDir) => {
99
+ const path = join(libDir, LIBRARY_FILE);
100
+ if (!existsSync(path))
101
+ throw new GeniexLibraryError('load', `${path} not found: no ${LIBRARY_FILE} in this build`);
102
+ if (process.platform === 'win32') {
103
+ prepareWindowsSearch(libDir);
104
+ leaveEcoQos();
105
+ }
106
+ let L;
107
+ try {
108
+ L = dlopen(path, SYMBOLS).symbols;
109
+ }
110
+ catch (error) {
111
+ throw new GeniexLibraryError('load', error instanceof Error ? error.message : String(error));
112
+ }
113
+ const check = (operation, code) => {
114
+ const value = Number(code);
115
+ if (value === 0)
116
+ return;
117
+ throw new GeniexLibraryError(operation, text(L.geniex_get_error_message(value)) ?? `error ${value}`, value);
118
+ };
119
+ // The QAIRT runtime the plugin needs (QnnHtp.dll, QnnSystem.dll, the HTP stubs and skels) ships in
120
+ // the SDK under `lib/qairt/htp-files` (spike S4); pinning it there keeps a `GENIEX_QAIRT_LIB` in the
121
+ // user's environment from swapping it. `lib/qairt` itself holds only the plugin's DLLs.
122
+ const qairt = join(libDir, QAIRT_DIR, 'htp-files');
123
+ if (existsSync(join(qairt, 'QnnHtp.dll'))) {
124
+ const keep = cstr(qairt);
125
+ check('set_qairt_runtime_path', L.geniex_set_qairt_runtime_path(ptr(keep)));
126
+ }
127
+ // Before `geniex_init`, as the header says: the default is TRACE, and the plugin scan at init logs
128
+ // at that level to stderr (spike S1). The level also sets QNN's in the qairt plugin.
129
+ L.geniex_set_log_level(LOG_LEVEL_ERROR);
130
+ check('init', L.geniex_init());
131
+ return bindLibrary(L, check);
132
+ };
133
+ /** A `const char**` of `count` strings the library allocated; freed after reading. */
134
+ function takeStrings(L, table, count) {
135
+ if (!table || count <= 0)
136
+ return [];
137
+ const out = [];
138
+ for (let i = 0; i < count; i++) {
139
+ const at = read.ptr(table, i * 8);
140
+ out.push(at ? new CString(at).toString() : '');
141
+ }
142
+ L.geniex_free(table);
143
+ return out;
144
+ }
145
+ function bindLibrary(L, check) {
146
+ // The library says `v0.9.3` (spike S1, 2026-10-28): the `v` is not part of the version.
147
+ const version = /^\d+\.\d+\.\d+/.exec((text(L.geniex_version()) ?? '').replace(/^v/i, ''))?.[0] ?? null;
148
+ function plugins() {
149
+ // geniex_GetPluginListOutput { const char** plugin_ids @0; int32 plugin_count @8 } = 16
150
+ const out = Buffer.alloc(16);
151
+ check('get_plugin_list', L.geniex_get_plugin_list(ptr(out)));
152
+ return takeStrings(L, readPtr(out, 0), out.readInt32LE(8));
153
+ }
154
+ function devices(plugin) {
155
+ const arena = new Arena();
156
+ // geniex_GetDeviceListInput { const char* plugin_id @0 } = 8
157
+ const input = arena.struct(8);
158
+ writePtr(input, 0, arena.str(plugin));
159
+ // geniex_GetDeviceListOutput { const char** device_ids @0; const char** device_names @8; int32 device_count @16 } = 24
160
+ const out = arena.struct(24);
161
+ check('get_device_list', L.geniex_get_device_list(ptr(input), ptr(out)));
162
+ const count = out.readInt32LE(16);
163
+ const ids = takeStrings(L, readPtr(out, 0), count);
164
+ takeStrings(L, readPtr(out, 8), count);
165
+ return ids;
166
+ }
167
+ function resolveDevice(plugin, mode) {
168
+ const arena = new Arena();
169
+ // geniex_ResolveDeviceInput { plugin_id @0; model_name @8; mode @16; int32 ngl_default @24 } = 32
170
+ const input = arena.struct(32);
171
+ writePtr(input, 0, arena.str(plugin));
172
+ writePtr(input, 8, null);
173
+ writePtr(input, 16, arena.str(mode));
174
+ input.writeInt32LE(999, 24);
175
+ // geniex_ResolveDeviceOutput { char* device_id @0; int32 ngl @8; char* warning @16 } = 24
176
+ const out = arena.struct(24);
177
+ check('resolve_device', L.geniex_resolve_device(ptr(input), ptr(out)));
178
+ const idPtr = readPtr(out, 0);
179
+ const warningPtr = readPtr(out, 16);
180
+ const deviceId = idPtr ? new CString(idPtr).toString() : null;
181
+ if (idPtr)
182
+ L.geniex_free(idPtr);
183
+ if (warningPtr)
184
+ L.geniex_free(warningPtr);
185
+ return { deviceId, ngl: out.readInt32LE(8) };
186
+ }
187
+ /** geniex_ModelConfig, 80 bytes, written into `buffer` at `at`. */
188
+ function writeModelConfig(buffer, at, arena, settings, ngl) {
189
+ // The qairt plugin refuses any n_ctx (S4: `--nctx (n_ctx) is not supported by the qairt plugin`);
190
+ // the bundle fixes its context, and 0 means "from the model".
191
+ buffer.writeInt32LE(settings.plugin === 'qairt' ? 0 : settings.nCtx, at + 0); // n_ctx
192
+ buffer.writeInt32LE(settings.threads, at + 4); // n_threads
193
+ buffer.writeInt32LE(settings.threads, at + 8); // n_threads_batch
194
+ buffer.writeInt32LE(0, at + 12); // n_batch
195
+ buffer.writeInt32LE(0, at + 16); // n_ubatch
196
+ buffer.writeInt32LE(0, at + 20); // n_seq_max
197
+ buffer.writeInt32LE(ngl, at + 24); // n_gpu_layers, as resolve_device said
198
+ writePtr(buffer, at + 32, null); // chat_template_path
199
+ writePtr(buffer, at + 40, null); // chat_template_content
200
+ writePtr(buffer, at + 48, arena.str('')); // spec_type: disabled
201
+ writePtr(buffer, at + 56, arena.str('')); // spec_draft_model
202
+ buffer.writeInt32LE(0, at + 64); // spec_n_max
203
+ buffer.writeInt32LE(0, at + 68); // spec_n_min
204
+ buffer.writeFloatLE(0, at + 72); // spec_p_min
205
+ buffer.writeInt32LE(GENIEX_POWER_MODES.indexOf(settings.powerMode), at + 76); // power_mode
206
+ }
207
+ function createModel(settings) {
208
+ const { deviceId, ngl } = resolveDevice(settings.plugin, settings.device);
209
+ const vision = settings.mmproj !== null;
210
+ // The qairt plugin resolves a bundle from the folder of the FILE it is given (spike S4: handed the
211
+ // folder, it looked for tokenizer.json in the parent); `genie_config.json` is the file every bundle has.
212
+ const modelPath = settings.plugin === 'qairt' ? join(settings.model, 'genie_config.json') : settings.model;
213
+ const arena = new Arena();
214
+ const handleOut = arena.struct(8);
215
+ if (vision) {
216
+ // geniex_VlmCreateInput { model_path @0; mmproj_path @8; config @16 (80); plugin_id @96; device_id @104; vit_device_id @112; tokenizer_path @120 } = 128
217
+ const input = arena.struct(128);
218
+ writePtr(input, 0, arena.str(modelPath));
219
+ writePtr(input, 8, arena.str(settings.mmproj));
220
+ writeModelConfig(input, 16, arena, settings, ngl);
221
+ writePtr(input, 96, arena.str(settings.plugin));
222
+ writePtr(input, 104, arena.str(deviceId));
223
+ writePtr(input, 112, null);
224
+ writePtr(input, 120, null);
225
+ check('vlm_create', L.geniex_vlm_create(ptr(input), ptr(handleOut)));
226
+ }
227
+ else {
228
+ // geniex_LlmCreateInput { model_path @0; tokenizer_path @8; config @16 (80); plugin_id @96; device_id @104 } = 112
229
+ const input = arena.struct(112);
230
+ writePtr(input, 0, arena.str(modelPath));
231
+ writePtr(input, 8, null);
232
+ writeModelConfig(input, 16, arena, settings, ngl);
233
+ writePtr(input, 96, arena.str(settings.plugin));
234
+ writePtr(input, 104, arena.str(deviceId));
235
+ check('llm_create', L.geniex_llm_create(ptr(input), ptr(handleOut)));
236
+ }
237
+ const handle = readPtr(handleOut, 0);
238
+ if (!handle)
239
+ throw new GeniexLibraryError(vision ? 'vlm_create' : 'llm_create', 'the library returned no handle');
240
+ return bindModel(L, check, handle, vision);
241
+ }
242
+ return {
243
+ version,
244
+ abi: BOUND_GENIEX_ABI,
245
+ plugins,
246
+ devices,
247
+ createModel,
248
+ close: () => {
249
+ L.geniex_deinit();
250
+ },
251
+ };
252
+ }
253
+ /** geniex_ToolCall[] { id @0; name @8; arguments @16 } = 24 each. */
254
+ function writeToolCalls(arena, calls) {
255
+ if (calls.length === 0)
256
+ return null;
257
+ const table = arena.struct(24 * calls.length);
258
+ calls.forEach((call, index) => {
259
+ writePtr(table, index * 24, arena.str(call.id));
260
+ writePtr(table, index * 24 + 8, arena.str(call.name));
261
+ writePtr(table, index * 24 + 16, arena.str(call.arguments));
262
+ });
263
+ return ptr(table);
264
+ }
265
+ function bindModel(L, check, handle, vision) {
266
+ function applyChatTemplate(input) {
267
+ const arena = new Arena();
268
+ const count = input.messages.length;
269
+ let messages;
270
+ if (vision) {
271
+ // geniex_VlmChatMessage { role @0; contents @8; int64 content_count @16; tool_calls @24; int32 tool_call_count @32; tool_call_id @40; tool_name @48 } = 56
272
+ messages = arena.struct(56 * count);
273
+ input.messages.forEach((message, index) => {
274
+ const at = index * 56;
275
+ // geniex_VlmContent[] { type @0; text @8 } = 16 each
276
+ const contents = arena.struct(16 * Math.max(1, message.content.length));
277
+ message.content.forEach((part, j) => {
278
+ writePtr(contents, j * 16, arena.str(part.type));
279
+ writePtr(contents, j * 16 + 8, arena.str(part.type === 'text' ? part.text : part.path));
280
+ });
281
+ writePtr(messages, at, arena.str(message.role));
282
+ writePtr(messages, at + 8, message.content.length > 0 ? ptr(contents) : null);
283
+ messages.writeBigInt64LE(BigInt(message.content.length), at + 16);
284
+ writePtr(messages, at + 24, writeToolCalls(arena, message.tool_calls ?? []));
285
+ messages.writeInt32LE(message.tool_calls?.length ?? 0, at + 32);
286
+ writePtr(messages, at + 40, arena.str(message.tool_call_id ?? null));
287
+ writePtr(messages, at + 48, arena.str(message.tool_name ?? null));
288
+ });
289
+ }
290
+ else {
291
+ // geniex_LlmChatMessage { role @0; content @8; tool_calls @16; int32 tool_call_count @24; tool_call_id @32; tool_name @40 } = 48
292
+ messages = arena.struct(48 * count);
293
+ input.messages.forEach((message, index) => {
294
+ const at = index * 48;
295
+ const content = message.content.map((part) => (part.type === 'text' ? part.text : '')).join('');
296
+ writePtr(messages, at, arena.str(message.role));
297
+ writePtr(messages, at + 8, arena.str(content));
298
+ writePtr(messages, at + 16, writeToolCalls(arena, message.tool_calls ?? []));
299
+ messages.writeInt32LE(message.tool_calls?.length ?? 0, at + 24);
300
+ writePtr(messages, at + 32, arena.str(message.tool_call_id ?? null));
301
+ writePtr(messages, at + 40, arena.str(message.tool_name ?? null));
302
+ });
303
+ }
304
+ // Llm: { messages @0; int32 message_count @8; tools @16; bool enable_thinking @24; bool add_generation_prompt @25 } = 32
305
+ // Vlm: { messages @0; int32 message_count @8; tools @16; bool enable_thinking @24; bool grounding @25 } = 32
306
+ const request = arena.struct(32);
307
+ writePtr(request, 0, count > 0 ? ptr(messages) : null);
308
+ request.writeInt32LE(count, 8);
309
+ writePtr(request, 16, arena.str(input.tools));
310
+ request.writeUInt8(input.enableThinking ? 1 : 0, 24);
311
+ request.writeUInt8(vision ? 0 : 1, 25);
312
+ // Output { char* formatted_text @0 } = 8
313
+ const out = arena.struct(8);
314
+ check(vision ? 'vlm_apply_chat_template' : 'llm_apply_chat_template', vision
315
+ ? L.geniex_vlm_apply_chat_template(handle, ptr(request), ptr(out))
316
+ : L.geniex_llm_apply_chat_template(handle, ptr(request), ptr(out)));
317
+ const formatted = readPtr(out, 0);
318
+ if (!formatted)
319
+ return '';
320
+ const prompt = new CString(formatted).toString();
321
+ L.geniex_free(formatted);
322
+ return prompt;
323
+ }
324
+ function generate(prompt, config, onToken) {
325
+ const arena = new Arena();
326
+ // geniex_SamplerConfig { f32 temperature @0; f32 top_p @4; i32 top_k @8; f32 min_p @12; f32 repetition_penalty @16;
327
+ // f32 presence_penalty @20; f32 frequency_penalty @24; i32 seed @28; grammar_path @32; grammar_string @40; bool ignore_eos @48 } = 56
328
+ const sampler = arena.struct(56);
329
+ const s = config.sampler;
330
+ sampler.writeFloatLE(s.temperature ?? 0.8, 0);
331
+ sampler.writeFloatLE(s.topP ?? 0.95, 4);
332
+ sampler.writeInt32LE(s.topK ?? 40, 8);
333
+ sampler.writeFloatLE(0.05, 12);
334
+ sampler.writeFloatLE(1.0, 16);
335
+ sampler.writeFloatLE(0, 20);
336
+ sampler.writeFloatLE(0, 24);
337
+ sampler.writeInt32LE(s.seed ?? -1, 28);
338
+ writePtr(sampler, 32, null);
339
+ writePtr(sampler, 40, arena.str(s.grammar));
340
+ sampler.writeUInt8(0, 48);
341
+ // geniex_GenerationConfig { i32 max_tokens @0; stop @8; i32 stop_count @16; sampler_config @24; image_paths @32;
342
+ // i32 image_count @40; audio_paths @48; i32 audio_count @56; bool sliding_window @60; i32 sliding_window_n_keep @64 } = 72
343
+ const generation = arena.struct(72);
344
+ generation.writeInt32LE(config.maxTokens, 0);
345
+ writePtr(generation, 8, arena.strings(config.stop));
346
+ generation.writeInt32LE(config.stop.length, 16);
347
+ writePtr(generation, 24, ptr(sampler));
348
+ writePtr(generation, 32, arena.strings(config.imagePaths));
349
+ generation.writeInt32LE(config.imagePaths.length, 40);
350
+ writePtr(generation, 48, null);
351
+ generation.writeInt32LE(0, 56);
352
+ generation.writeUInt8(0, 60);
353
+ generation.writeInt32LE(0, 64);
354
+ let callbackError = null;
355
+ const callback = new JSCallback((tokenPtr) => {
356
+ try {
357
+ return onToken(tokenPtr ? new CString(tokenPtr).toString() : '');
358
+ }
359
+ catch (error) {
360
+ callbackError = error;
361
+ return false;
362
+ }
363
+ }, { args: [T.ptr, T.ptr], returns: T.bool });
364
+ try {
365
+ // Llm: { prompt_utf8 @0; config @8; on_token @16; user_data @24; input_ids @32; i32 input_ids_count @40; input_embd @48; i32 count @56; i32 dim @60 } = 64
366
+ // Vlm: { prompt_utf8 @0; config @8; on_token @16; user_data @24 } = 32 (the Llm layout is a superset, zero beyond)
367
+ const input = arena.struct(64);
368
+ writePtr(input, 0, arena.str(prompt));
369
+ writePtr(input, 8, ptr(generation));
370
+ writePtr(input, 16, callback.ptr);
371
+ writePtr(input, 24, null);
372
+ // Output { char* full_text @0; geniex_ProfileData @8: i64 ttft @8, media_time @16, prompt_time @24, decode_time @32,
373
+ // prompt_tokens @40, generated_tokens @48, f64 prefill_speed @56, decoding_speed @64, i64 draft_n_total @72, draft_n_accepted @80, stop_reason @88 } = 96
374
+ const out = arena.struct(96);
375
+ // The llama_cpp VLM keeps the last generation's prefix and refuses a prompt that diverges from it
376
+ // (S2 on Gemma 4: `VLM prefix reuse failed: prompt[12:18] does not match last generation`);
377
+ // every request here is a full prompt, so the state is cleared first.
378
+ if (vision)
379
+ check('vlm_reset', L.geniex_vlm_reset(handle));
380
+ const code = Number(vision
381
+ ? L.geniex_vlm_generate(handle, ptr(input), ptr(out))
382
+ : L.geniex_llm_generate(handle, ptr(input), ptr(out)));
383
+ if (callbackError)
384
+ throw callbackError;
385
+ if (code === GENIEX_CANCELLED)
386
+ throw new GeniexLibraryError(vision ? 'vlm_generate' : 'llm_generate', 'Operation cancelled by caller', code);
387
+ check(vision ? 'vlm_generate' : 'llm_generate', code);
388
+ const fullText = readPtr(out, 0);
389
+ if (fullText)
390
+ L.geniex_free(fullText);
391
+ const stopPtr = readPtr(out, 88);
392
+ const stop = stopPtr ? new CString(stopPtr).toString() : '';
393
+ return {
394
+ stopReason: STOP_REASONS.find((reason) => reason === stop) ?? 'unknown',
395
+ promptTokens: Number(out.readBigInt64LE(40)),
396
+ generatedTokens: Number(out.readBigInt64LE(48)),
397
+ };
398
+ }
399
+ finally {
400
+ callback.close();
401
+ }
402
+ }
403
+ return {
404
+ vision,
405
+ applyChatTemplate,
406
+ generate,
407
+ destroy: () => {
408
+ if (vision)
409
+ L.geniex_vlm_destroy(handle);
410
+ else
411
+ L.geniex_llm_destroy(handle);
412
+ },
413
+ };
414
+ }
415
+ //# sourceMappingURL=ffi-bun.js.map