transcribe-cpp 0.0.0 → 0.0.3

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.
package/README.md CHANGED
@@ -1,17 +1,215 @@
1
1
  # transcribe-cpp
2
2
 
3
3
  TypeScript/Node.js bindings for [transcribe.cpp](https://github.com/handy-computer/transcribe.cpp),
4
- a C/C++ speech-to-text library built on ggml.
4
+ a C/C++ speech-to-text library built on ggml — Whisper, Parakeet, Moonshine,
5
+ Voxtral, and more, on CPU, Metal, CUDA, and Vulkan.
5
6
 
6
- > **Status: placeholder.** This release reserves the `transcribe-cpp` name on
7
- > npm while the first-party bindings are developed. It ships no functionality
8
- > yet. Watch the repository for the first functional release.
7
+ ```sh
8
+ npm install transcribe-cpp
9
+ ```
10
+
11
+ A matching prebuilt native package is selected automatically for your
12
+ platform (`@transcribe-cpp/<platform>`); there is nothing to compile and no
13
+ environment variables to set.
14
+
15
+ ## Quickstart
16
+
17
+ ```ts
18
+ import { TranscribeModel } from "transcribe-cpp";
19
+
20
+ const model = await TranscribeModel.load("whisper-tiny-Q5_K_M.gguf");
21
+
22
+ // 16 kHz mono float32 PCM (bring your own decoder).
23
+ const result = await model.transcribe(pcm, { timestamps: "segment" });
24
+
25
+ console.log(result.text);
26
+ console.log(result.language); // detected or requested
27
+ for (const seg of result.segments) {
28
+ console.log(`[${seg.t0Ms}–${seg.t1Ms}ms] ${seg.text}`);
29
+ }
30
+
31
+ model.dispose();
32
+ ```
33
+
34
+ ### Streaming
35
+
36
+ ```ts
37
+ const session = model.createSession();
38
+ const stream = await session.stream({ commitPolicy: "stable_prefix" });
39
+
40
+ for (const chunk of pcmChunks) {
41
+ await stream.feed(chunk);
42
+ const { committed, tentative } = stream.text; // committed is append-only/stable
43
+ render(committed, tentative);
44
+ }
45
+ await stream.finalize();
46
+ stream.reset();
47
+ ```
48
+
49
+ ### Batch
50
+
51
+ ```ts
52
+ const items = await session.runBatch([pcmA, pcmB]);
53
+ for (const item of items) {
54
+ if (item.ok) console.log(item.result.text);
55
+ else console.error(item.error.message); // per-utterance failure
56
+ }
57
+ ```
58
+
59
+ ### Cancellation
60
+
61
+ ```ts
62
+ const ac = new AbortController();
63
+ setTimeout(() => ac.abort(), 5000);
64
+ try {
65
+ await model.transcribe(pcm, { signal: ac.signal });
66
+ } catch (e) {
67
+ if (e instanceof Aborted) console.log("partial:", e.partialResult?.text);
68
+ }
69
+ ```
70
+
71
+ ### Family extensions
72
+
73
+ Per-family options are a typed, discriminated union passed as `family`:
74
+
75
+ ```ts
76
+ // Whisper is a run-slot extension:
77
+ await model.transcribe(pcm, { family: { kind: "whisper", initialPrompt: "…" } });
78
+
79
+ // Streaming families (moonshine, parakeet, voxtral) are stream-slot:
80
+ const stream = await session.stream({ family: { kind: "moonshine" } });
81
+
82
+ model.accepts({ kind: "whisper" }); // does this model take that extension?
83
+ ```
84
+
85
+ ### Resource management
86
+
87
+ `TranscribeModel`, `Session`, and `Stream` all implement `Symbol.dispose`, so
88
+ `using` works (TypeScript 5.2+ / Node 22+):
9
89
 
10
90
  ```ts
11
- import { version } from "transcribe-cpp";
91
+ using model = await TranscribeModel.load("model.gguf");
92
+ using session = model.createSession();
93
+ // disposed automatically at block exit
94
+ ```
95
+
96
+ Disposing a model disposes its sessions; disposing a stream resets it (releasing
97
+ the model lease). Disposal is idempotent and order-independent.
98
+
99
+ ## Backend selection
100
+
101
+ ```ts
102
+ import { getAvailableBackends, backendAvailable } from "transcribe-cpp";
103
+
104
+ getAvailableBackends(); // [{ kind: "metal", name: "MTL0", description: "…" }, …]
105
+ backendAvailable("cuda"); // boolean — never throws
106
+
107
+ const model = await TranscribeModel.load("model.gguf", { backend: "metal" });
108
+ ```
109
+
110
+ `backend` defaults to `"auto"` (best accelerator, else CPU). A missing Vulkan
111
+ loader/driver degrades silently to CPU; an explicit backend that cannot be
112
+ satisfied raises a `BackendError`.
113
+
114
+ ## Thread-safety
115
+
116
+ Your JavaScript runs on one thread, but `run`/`feed`/`finalize` dispatch the
117
+ native compute to a **libuv worker thread** (via koffi's async calls), so the
118
+ event loop stays responsive while inference runs.
119
+
120
+ The C library allows **one compute in flight per model** — a `run`, a `runBatch`,
121
+ or an *active stream* — across all of its sessions. The binding enforces this:
122
+ every compute call serializes through an internal model-wide mutex, and an active
123
+ stream holds a model-wide lease for its whole lifetime. While a stream is active
124
+ (after `stream()`, before `finalize()`/`reset()`), a `run`/`runBatch`/`stream` on
125
+ any session of that model is refused with a `Busy` error rather than allowed to
126
+ race. So to parallelize, load one model per worker; to share a model, finalize or
127
+ reset the stream first. A single `Session` is single-use-at-a-time — don't call
128
+ `run`/`feed` on the same session concurrently.
129
+
130
+ Hand-offs are ordered, not racy: `finalize()`/`reset()`/`dispose()` release the
131
+ lease only after the native teardown runs on the shared queue, so the slot is
132
+ never freed early. Issue the teardown before the next `stream()`/`run()` and it
133
+ is correctly serialized — `stream.reset(); const next = await session.stream()`
134
+ works without awaiting the (void) `reset()`. The reverse order — beginning before
135
+ the teardown — is refused with `Busy`, by design.
136
+
137
+ Because the compute is genuinely on another thread, **do not touch a session
138
+ while a call against it is in flight** — it is single-threaded in the C library:
139
+
140
+ - Reading a stream's `text`/`state`/`revision`/`lastStatus`, or a session's
141
+ `limits`/`wasAborted`, during an un-awaited `feed`/`finalize`/`run` **throws**.
142
+ - `reset()` and `dispose()` are safe to call any time: the native teardown is
143
+ deferred behind any in-flight call, so it never frees a session mid-compute.
144
+ - Disposing a `Session` or `TranscribeModel` while a stream is still active
145
+ releases the lease and invalidates the stream — its later calls throw rather
146
+ than touch the freed handle.
147
+ - The **input PCM is borrowed, not copied**: `run`/`runBatch`/`feed` hand the
148
+ buffer to native code that reads it on the worker thread, so do not mutate it
149
+ (e.g. reuse a scratch/capture buffer) until the returned promise resolves.
150
+ Pass a fresh buffer per call, or `await` before overwriting.
151
+
152
+ The normal pattern is safe — `await` first, then read:
12
153
 
13
- console.log(version);
154
+ ```ts
155
+ await stream.feed(chunk);
156
+ const { committed, tentative } = stream.text; // ✓ feed has completed
157
+
158
+ const p = stream.feed(chunk);
159
+ stream.text; // ✗ throws: read while feed in flight
160
+ await p;
161
+ ```
162
+
163
+ Since JavaScript has no destructors, a stream holds the model's lease until you
164
+ `finalize()`, `reset()`, or let `using` dispose it — drop the reference without
165
+ one and the model stays `Busy` until you dispose the session or model. Always
166
+ finalize/reset (or use `using`).
167
+
168
+ Result strings are copied at the boundary; the objects you get back are fully
169
+ owned and outlive the model.
170
+
171
+ ## Runtime support
172
+
173
+ The binding is one native artifact (koffi, an N-API addon) plus plain ESM, so any
174
+ runtime with N-API support runs the same package — there is no per-runtime build.
175
+
176
+ - **Node ≥ 22** — the primary, fully tested target.
177
+ - **Deno ≥ 2** — runs the package unmodified via `deno run` (use `--allow-ffi
178
+ --allow-read --allow-env`, or `-A`). Note: `deno compile` does not bundle native
179
+ addons, so the compiled-binary path is unsupported.
180
+ - **Bun** — not yet. Bun loads the addon and runs, but currently crashes in its
181
+ own N-API finalizer (`napi_reference_unref`); this is an upstream Bun bug, fixed
182
+ on Bun's side, not here.
183
+
184
+ ## Building from source
185
+
186
+ The universal fallback when no prebuilt package exists for your platform (a new
187
+ arch, a custom backend, or a single self-contained library):
188
+
189
+ ```sh
190
+ # from a transcribe.cpp checkout:
191
+ npm run build:native -- --source /path/to/transcribe.cpp
192
+ # add --self-contained to link ggml into one libtranscribe
193
+
194
+ # from a consumer project (the script ships in the package):
195
+ node node_modules/transcribe-cpp/scripts/build-from-source.mjs --source /path/to/transcribe.cpp
14
196
  ```
15
197
 
16
- - Package: `transcribe-cpp`
17
- - License: MIT
198
+ This drives CMake directly (the binding is FFI over a shared library, not an
199
+ N-API addon, so `cmake-js` does not apply) and installs into `prebuilds/<tuple>/`,
200
+ which the loader finds automatically. Requires `cmake` and a C/C++ toolchain.
201
+ You can also point `TRANSCRIBE_LIBRARY` at any `libtranscribe` you built.
202
+
203
+ ## Waived requirements
204
+
205
+ - **Per-field ABI layout is not waived** (unlike Rust/Swift): TypeScript has no
206
+ compiler reading the C headers, so — like the Python ctypes binding — the
207
+ generated struct layout is verified at load against both the captured
208
+ C-compiler layout and the live library (`transcribe_abi_struct_size/_align`).
209
+ - **Buffer model load** awaits `transcribe_model_load_buffer` in the C API; only
210
+ file load is exposed today.
211
+
212
+ ## License
213
+
214
+ MIT. Bundled native packages include third-party license texts (ggml and any
215
+ bundled backend runtimes).
@@ -0,0 +1,97 @@
1
+ export declare const PUBLIC_HEADER_HASH = "2273744299e5aa65";
2
+ export declare const TRANSCRIBE_OK = 0;
3
+ export declare const TRANSCRIBE_ERR_INVALID_ARG = 1;
4
+ export declare const TRANSCRIBE_ERR_NOT_IMPLEMENTED = 2;
5
+ export declare const TRANSCRIBE_ERR_FILE_NOT_FOUND = 3;
6
+ export declare const TRANSCRIBE_ERR_GGUF = 4;
7
+ export declare const TRANSCRIBE_ERR_UNSUPPORTED_ARCH = 5;
8
+ export declare const TRANSCRIBE_ERR_UNSUPPORTED_VARIANT = 6;
9
+ export declare const TRANSCRIBE_ERR_OOM = 7;
10
+ export declare const TRANSCRIBE_ERR_BACKEND = 8;
11
+ export declare const TRANSCRIBE_ERR_SAMPLE_RATE = 9;
12
+ export declare const TRANSCRIBE_ERR_UNSUPPORTED_LANGUAGE = 10;
13
+ export declare const TRANSCRIBE_ERR_UNSUPPORTED_TASK = 11;
14
+ export declare const TRANSCRIBE_ERR_UNSUPPORTED_TIMESTAMPS = 12;
15
+ export declare const TRANSCRIBE_ERR_ABORTED = 13;
16
+ export declare const TRANSCRIBE_ERR_BAD_STRUCT_SIZE = 14;
17
+ export declare const TRANSCRIBE_ERR_UNSUPPORTED_PNC = 15;
18
+ export declare const TRANSCRIBE_ERR_UNSUPPORTED_ITN = 16;
19
+ export declare const TRANSCRIBE_ERR_INPUT_TOO_LONG = 17;
20
+ export declare const TRANSCRIBE_ERR_OUTPUT_TRUNCATED = 18;
21
+ export declare const TRANSCRIBE_ABI_MODEL_LOAD_PARAMS = 0;
22
+ export declare const TRANSCRIBE_ABI_SESSION_PARAMS = 1;
23
+ export declare const TRANSCRIBE_ABI_RUN_PARAMS = 2;
24
+ export declare const TRANSCRIBE_ABI_STREAM_PARAMS = 3;
25
+ export declare const TRANSCRIBE_ABI_CAPABILITIES = 4;
26
+ export declare const TRANSCRIBE_ABI_TIMINGS = 5;
27
+ export declare const TRANSCRIBE_ABI_SEGMENT = 6;
28
+ export declare const TRANSCRIBE_ABI_WORD = 7;
29
+ export declare const TRANSCRIBE_ABI_TOKEN = 8;
30
+ export declare const TRANSCRIBE_ABI_STREAM_UPDATE = 9;
31
+ export declare const TRANSCRIBE_ABI_STREAM_TEXT = 10;
32
+ export declare const TRANSCRIBE_ABI_SESSION_LIMITS = 11;
33
+ export declare const TRANSCRIBE_ABI_EXT = 12;
34
+ export declare const TRANSCRIBE_ABI_BACKEND_DEVICE = 13;
35
+ export declare const TRANSCRIBE_LOG_LEVEL_NONE = 0;
36
+ export declare const TRANSCRIBE_LOG_LEVEL_INFO = 1;
37
+ export declare const TRANSCRIBE_LOG_LEVEL_WARN = 2;
38
+ export declare const TRANSCRIBE_LOG_LEVEL_ERROR = 3;
39
+ export declare const TRANSCRIBE_LOG_LEVEL_DEBUG = 4;
40
+ export declare const TRANSCRIBE_LOG_LEVEL_CONT = 5;
41
+ export declare const TRANSCRIBE_TASK_TRANSCRIBE = 0;
42
+ export declare const TRANSCRIBE_TASK_TRANSLATE = 1;
43
+ export declare const TRANSCRIBE_TIMESTAMPS_NONE = 0;
44
+ export declare const TRANSCRIBE_TIMESTAMPS_AUTO = 1;
45
+ export declare const TRANSCRIBE_TIMESTAMPS_SEGMENT = 2;
46
+ export declare const TRANSCRIBE_TIMESTAMPS_WORD = 3;
47
+ export declare const TRANSCRIBE_TIMESTAMPS_TOKEN = 4;
48
+ export declare const TRANSCRIBE_KV_TYPE_AUTO = 0;
49
+ export declare const TRANSCRIBE_KV_TYPE_F32 = 1;
50
+ export declare const TRANSCRIBE_KV_TYPE_F16 = 2;
51
+ export declare const TRANSCRIBE_PNC_MODE_DEFAULT = 0;
52
+ export declare const TRANSCRIBE_PNC_MODE_OFF = 1;
53
+ export declare const TRANSCRIBE_PNC_MODE_ON = 2;
54
+ export declare const TRANSCRIBE_ITN_MODE_DEFAULT = 0;
55
+ export declare const TRANSCRIBE_ITN_MODE_OFF = 1;
56
+ export declare const TRANSCRIBE_ITN_MODE_ON = 2;
57
+ export declare const TRANSCRIBE_EXT_SLOT_RUN = 0;
58
+ export declare const TRANSCRIBE_EXT_SLOT_STREAM = 1;
59
+ export declare const TRANSCRIBE_BACKEND_AUTO = 0;
60
+ export declare const TRANSCRIBE_BACKEND_CPU = 1;
61
+ export declare const TRANSCRIBE_BACKEND_METAL = 2;
62
+ export declare const TRANSCRIBE_BACKEND_VULKAN = 3;
63
+ export declare const TRANSCRIBE_BACKEND_CPU_ACCEL = 4;
64
+ export declare const TRANSCRIBE_BACKEND_CUDA = 5;
65
+ export declare const TRANSCRIBE_FEATURE_INITIAL_PROMPT = 0;
66
+ export declare const TRANSCRIBE_FEATURE_TEMPERATURE_FALLBACK = 1;
67
+ export declare const TRANSCRIBE_FEATURE_LONG_FORM = 2;
68
+ export declare const TRANSCRIBE_FEATURE_CANCELLATION = 3;
69
+ export declare const TRANSCRIBE_FEATURE_PNC = 4;
70
+ export declare const TRANSCRIBE_FEATURE_ITN = 5;
71
+ export declare const TRANSCRIBE_STREAM_IDLE = 0;
72
+ export declare const TRANSCRIBE_STREAM_ACTIVE = 1;
73
+ export declare const TRANSCRIBE_STREAM_FINISHED = 2;
74
+ export declare const TRANSCRIBE_STREAM_FAILED = 3;
75
+ export declare const TRANSCRIBE_STREAM_COMMIT_AUTO = 0;
76
+ export declare const TRANSCRIBE_STREAM_COMMIT_ON_FINALIZE = 1;
77
+ export declare const TRANSCRIBE_STREAM_COMMIT_STABLE_PREFIX = 2;
78
+ export declare const TRANSCRIBE_WHISPER_PROMPT_FIRST_SEGMENT = 0;
79
+ export declare const TRANSCRIBE_WHISPER_PROMPT_ALL_SEGMENTS = 1;
80
+ export declare const TRANSCRIBE_EXT_KIND_MOONSHINE_STREAMING_STREAM = 1414746957;
81
+ export declare const TRANSCRIBE_EXT_KIND_PARAKEET_BUFFERED_STREAM = 1396853584;
82
+ export declare const TRANSCRIBE_EXT_KIND_PARAKEET_STREAM = 1414744912;
83
+ export declare const TRANSCRIBE_EXT_KIND_VOXTRAL_REALTIME_STREAM = 1414746710;
84
+ export declare const TRANSCRIBE_EXT_KIND_WHISPER_RUN = 1314015319;
85
+ export interface StructLayout {
86
+ size: number;
87
+ align: number;
88
+ offsets: Record<string, number>;
89
+ }
90
+ export declare const STRUCT_LAYOUT: Record<string, StructLayout>;
91
+ export declare const ABI_STRUCT_IDS: Record<string, number>;
92
+ export declare function defineTypes(koffi: any): Record<string, any>;
93
+ export interface FnSig {
94
+ ret: string;
95
+ args: string[];
96
+ }
97
+ export declare const FUNCTION_SIGNATURES: Record<string, FnSig>;
@@ -0,0 +1,251 @@
1
+ // Low-level koffi FFI surface for transcribe.cpp — AUTOGENERATED. DO NOT EDIT.
2
+ //
3
+ // Regenerate with:
4
+ // uv run --no-project --with 'libclang==18.1.1' \
5
+ // bindings/python/_generate/generate.py
6
+ //
7
+ // Source: include/transcribe/extensions.h
8
+ // libclang: 18.1.1
9
+ /* eslint-disable */
10
+ // Stable digest of the ABI surface (structs, enums, macros, layout,
11
+ // prototypes), computed by the Python oracle and pinned here so a header
12
+ // ABI change turns this binding's drift check red for conscious review.
13
+ export const PUBLIC_HEADER_HASH = "2273744299e5aa65";
14
+ // === enum constants ===
15
+ export const TRANSCRIBE_OK = 0;
16
+ export const TRANSCRIBE_ERR_INVALID_ARG = 1;
17
+ export const TRANSCRIBE_ERR_NOT_IMPLEMENTED = 2;
18
+ export const TRANSCRIBE_ERR_FILE_NOT_FOUND = 3;
19
+ export const TRANSCRIBE_ERR_GGUF = 4;
20
+ export const TRANSCRIBE_ERR_UNSUPPORTED_ARCH = 5;
21
+ export const TRANSCRIBE_ERR_UNSUPPORTED_VARIANT = 6;
22
+ export const TRANSCRIBE_ERR_OOM = 7;
23
+ export const TRANSCRIBE_ERR_BACKEND = 8;
24
+ export const TRANSCRIBE_ERR_SAMPLE_RATE = 9;
25
+ export const TRANSCRIBE_ERR_UNSUPPORTED_LANGUAGE = 10;
26
+ export const TRANSCRIBE_ERR_UNSUPPORTED_TASK = 11;
27
+ export const TRANSCRIBE_ERR_UNSUPPORTED_TIMESTAMPS = 12;
28
+ export const TRANSCRIBE_ERR_ABORTED = 13;
29
+ export const TRANSCRIBE_ERR_BAD_STRUCT_SIZE = 14;
30
+ export const TRANSCRIBE_ERR_UNSUPPORTED_PNC = 15;
31
+ export const TRANSCRIBE_ERR_UNSUPPORTED_ITN = 16;
32
+ export const TRANSCRIBE_ERR_INPUT_TOO_LONG = 17;
33
+ export const TRANSCRIBE_ERR_OUTPUT_TRUNCATED = 18;
34
+ export const TRANSCRIBE_ABI_MODEL_LOAD_PARAMS = 0;
35
+ export const TRANSCRIBE_ABI_SESSION_PARAMS = 1;
36
+ export const TRANSCRIBE_ABI_RUN_PARAMS = 2;
37
+ export const TRANSCRIBE_ABI_STREAM_PARAMS = 3;
38
+ export const TRANSCRIBE_ABI_CAPABILITIES = 4;
39
+ export const TRANSCRIBE_ABI_TIMINGS = 5;
40
+ export const TRANSCRIBE_ABI_SEGMENT = 6;
41
+ export const TRANSCRIBE_ABI_WORD = 7;
42
+ export const TRANSCRIBE_ABI_TOKEN = 8;
43
+ export const TRANSCRIBE_ABI_STREAM_UPDATE = 9;
44
+ export const TRANSCRIBE_ABI_STREAM_TEXT = 10;
45
+ export const TRANSCRIBE_ABI_SESSION_LIMITS = 11;
46
+ export const TRANSCRIBE_ABI_EXT = 12;
47
+ export const TRANSCRIBE_ABI_BACKEND_DEVICE = 13;
48
+ export const TRANSCRIBE_LOG_LEVEL_NONE = 0;
49
+ export const TRANSCRIBE_LOG_LEVEL_INFO = 1;
50
+ export const TRANSCRIBE_LOG_LEVEL_WARN = 2;
51
+ export const TRANSCRIBE_LOG_LEVEL_ERROR = 3;
52
+ export const TRANSCRIBE_LOG_LEVEL_DEBUG = 4;
53
+ export const TRANSCRIBE_LOG_LEVEL_CONT = 5;
54
+ export const TRANSCRIBE_TASK_TRANSCRIBE = 0;
55
+ export const TRANSCRIBE_TASK_TRANSLATE = 1;
56
+ export const TRANSCRIBE_TIMESTAMPS_NONE = 0;
57
+ export const TRANSCRIBE_TIMESTAMPS_AUTO = 1;
58
+ export const TRANSCRIBE_TIMESTAMPS_SEGMENT = 2;
59
+ export const TRANSCRIBE_TIMESTAMPS_WORD = 3;
60
+ export const TRANSCRIBE_TIMESTAMPS_TOKEN = 4;
61
+ export const TRANSCRIBE_KV_TYPE_AUTO = 0;
62
+ export const TRANSCRIBE_KV_TYPE_F32 = 1;
63
+ export const TRANSCRIBE_KV_TYPE_F16 = 2;
64
+ export const TRANSCRIBE_PNC_MODE_DEFAULT = 0;
65
+ export const TRANSCRIBE_PNC_MODE_OFF = 1;
66
+ export const TRANSCRIBE_PNC_MODE_ON = 2;
67
+ export const TRANSCRIBE_ITN_MODE_DEFAULT = 0;
68
+ export const TRANSCRIBE_ITN_MODE_OFF = 1;
69
+ export const TRANSCRIBE_ITN_MODE_ON = 2;
70
+ export const TRANSCRIBE_EXT_SLOT_RUN = 0;
71
+ export const TRANSCRIBE_EXT_SLOT_STREAM = 1;
72
+ export const TRANSCRIBE_BACKEND_AUTO = 0;
73
+ export const TRANSCRIBE_BACKEND_CPU = 1;
74
+ export const TRANSCRIBE_BACKEND_METAL = 2;
75
+ export const TRANSCRIBE_BACKEND_VULKAN = 3;
76
+ export const TRANSCRIBE_BACKEND_CPU_ACCEL = 4;
77
+ export const TRANSCRIBE_BACKEND_CUDA = 5;
78
+ export const TRANSCRIBE_FEATURE_INITIAL_PROMPT = 0;
79
+ export const TRANSCRIBE_FEATURE_TEMPERATURE_FALLBACK = 1;
80
+ export const TRANSCRIBE_FEATURE_LONG_FORM = 2;
81
+ export const TRANSCRIBE_FEATURE_CANCELLATION = 3;
82
+ export const TRANSCRIBE_FEATURE_PNC = 4;
83
+ export const TRANSCRIBE_FEATURE_ITN = 5;
84
+ export const TRANSCRIBE_STREAM_IDLE = 0;
85
+ export const TRANSCRIBE_STREAM_ACTIVE = 1;
86
+ export const TRANSCRIBE_STREAM_FINISHED = 2;
87
+ export const TRANSCRIBE_STREAM_FAILED = 3;
88
+ export const TRANSCRIBE_STREAM_COMMIT_AUTO = 0;
89
+ export const TRANSCRIBE_STREAM_COMMIT_ON_FINALIZE = 1;
90
+ export const TRANSCRIBE_STREAM_COMMIT_STABLE_PREFIX = 2;
91
+ export const TRANSCRIBE_WHISPER_PROMPT_FIRST_SEGMENT = 0;
92
+ export const TRANSCRIBE_WHISPER_PROMPT_ALL_SEGMENTS = 1;
93
+ // === macro constants (integer object-like macros) ===
94
+ export const TRANSCRIBE_EXT_KIND_MOONSHINE_STREAMING_STREAM = 1414746957;
95
+ export const TRANSCRIBE_EXT_KIND_PARAKEET_BUFFERED_STREAM = 1396853584;
96
+ export const TRANSCRIBE_EXT_KIND_PARAKEET_STREAM = 1414744912;
97
+ export const TRANSCRIBE_EXT_KIND_VOXTRAL_REALTIME_STREAM = 1414746710;
98
+ export const TRANSCRIBE_EXT_KIND_WHISPER_RUN = 1314015319;
99
+ export const STRUCT_LAYOUT = {
100
+ 'transcribe_ext': { size: 16, align: 8, offsets: { 'size': 0, 'kind': 8 } },
101
+ 'transcribe_backend_device': { size: 32, align: 8, offsets: { 'struct_size': 0, 'name': 8, 'description': 16, 'kind': 24 } },
102
+ 'transcribe_model_load_params': { size: 16, align: 8, offsets: { 'struct_size': 0, 'backend': 8, 'gpu_device': 12 } },
103
+ 'transcribe_session_params': { size: 24, align: 8, offsets: { 'struct_size': 0, 'n_threads': 8, 'kv_type': 12, 'n_ctx': 16 } },
104
+ 'transcribe_run_params': { size: 64, align: 8, offsets: { 'struct_size': 0, 'task': 8, 'timestamps': 12, 'pnc': 16, 'itn': 20, 'language': 24, 'target_language': 32, 'keep_special_tags': 40, 'family': 48, 'spec_k_drafts': 56 } },
105
+ 'transcribe_capabilities': { size: 40, align: 8, offsets: { 'struct_size': 0, 'native_sample_rate': 8, 'n_languages': 12, 'languages': 16, 'max_timestamp_kind': 24, 'supports_language_detect': 28, 'supports_translate': 29, 'supports_streaming': 30, 'supports_spec_decode': 31, 'max_audio_ms': 32 } },
106
+ 'transcribe_session_limits': { size: 32, align: 8, offsets: { 'struct_size': 0, 'effective_n_ctx': 8, 'effective_max_audio_ms': 16, 'max_kv_bytes': 24 } },
107
+ 'transcribe_stream_params': { size: 24, align: 8, offsets: { 'struct_size': 0, 'family': 8, 'commit_policy': 16, 'stable_prefix_agreement_n': 20 } },
108
+ 'transcribe_stream_update': { size: 48, align: 8, offsets: { 'struct_size': 0, 'result_changed': 8, 'is_final': 9, 'revision': 12, 'input_received_ms': 16, 'audio_committed_ms': 24, 'buffered_ms': 32, 'committed_changed': 40, 'tentative_changed': 41 } },
109
+ 'transcribe_stream_text': { size: 64, align: 8, offsets: { 'struct_size': 0, 'full_text': 8, 'full_text_bytes': 16, 'committed_text': 24, 'committed_text_bytes': 32, 'tentative_text': 40, 'tentative_text_bytes': 48, 'raw_tentative_start_bytes': 56 } },
110
+ 'transcribe_timings': { size: 24, align: 8, offsets: { 'struct_size': 0, 'load_ms': 8, 'mel_ms': 12, 'encode_ms': 16, 'decode_ms': 20 } },
111
+ 'transcribe_segment': { size: 48, align: 8, offsets: { 'struct_size': 0, 't0_ms': 8, 't1_ms': 16, 'first_word': 24, 'n_words': 28, 'first_token': 32, 'n_tokens': 36, 'text': 40 } },
112
+ 'transcribe_word': { size: 48, align: 8, offsets: { 'struct_size': 0, 't0_ms': 8, 't1_ms': 16, 'seg_index': 24, 'first_token': 28, 'n_tokens': 32, 'text': 40 } },
113
+ 'transcribe_token': { size: 48, align: 8, offsets: { 'struct_size': 0, 'id': 8, 'p': 12, 't0_ms': 16, 't1_ms': 24, 'seg_index': 32, 'word_index': 36, 'text': 40 } },
114
+ 'transcribe_moonshine_streaming_stream_ext': { size: 24, align: 8, offsets: { 'ext': 0, 'min_decode_interval_ms': 16 } },
115
+ 'transcribe_parakeet_stream_ext': { size: 24, align: 8, offsets: { 'ext': 0, 'att_context_right': 16 } },
116
+ 'transcribe_parakeet_buffered_stream_ext': { size: 32, align: 8, offsets: { 'ext': 0, 'left_ms': 16, 'chunk_ms': 20, 'right_ms': 24 } },
117
+ 'transcribe_voxtral_realtime_stream_ext': { size: 24, align: 8, offsets: { 'ext': 0, 'num_delay_tokens': 16, 'min_decode_interval_ms': 20 } },
118
+ 'transcribe_whisper_run_ext': { size: 80, align: 8, offsets: { 'ext': 0, 'initial_prompt': 16, 'prompt_tokens': 24, 'n_prompt_tokens': 32, 'prompt_condition': 40, 'condition_on_prev_tokens': 44, 'max_prev_context_tokens': 48, 'temperature': 52, 'temperature_inc': 56, 'compression_ratio_thold': 60, 'logprob_thold': 64, 'no_speech_thold': 68, 'seed': 72, 'max_initial_timestamp': 76 } },
119
+ 'transcribe_whisper_chunk_trace': { size: 48, align: 8, offsets: { 'struct_size': 0, 't0_ms': 8, 't1_ms': 16, 'temperature_used': 24, 'compression_ratio': 28, 'avg_logprob': 32, 'no_speech_prob': 36, 'no_speech_triggered': 40, 'n_fallbacks': 44 } },
120
+ };
121
+ export const ABI_STRUCT_IDS = {
122
+ 'transcribe_ext': 12,
123
+ 'transcribe_backend_device': 13,
124
+ 'transcribe_model_load_params': 0,
125
+ 'transcribe_session_params': 1,
126
+ 'transcribe_run_params': 2,
127
+ 'transcribe_capabilities': 4,
128
+ 'transcribe_session_limits': 11,
129
+ 'transcribe_stream_params': 3,
130
+ 'transcribe_stream_update': 9,
131
+ 'transcribe_stream_text': 10,
132
+ 'transcribe_timings': 5,
133
+ 'transcribe_segment': 6,
134
+ 'transcribe_word': 7,
135
+ 'transcribe_token': 8,
136
+ };
137
+ // Build koffi struct types; returns a name -> koffi.IKoffiCType map.
138
+ export function defineTypes(koffi) {
139
+ const T = {};
140
+ T['transcribe_ext'] = koffi.struct({ size: 'uint64_t', kind: 'uint32_t' });
141
+ T['transcribe_backend_device'] = koffi.struct({ struct_size: 'uint64_t', name: 'char *', description: 'char *', kind: 'char *' });
142
+ T['transcribe_model_load_params'] = koffi.struct({ struct_size: 'uint64_t', backend: 'int', gpu_device: 'int' });
143
+ T['transcribe_session_params'] = koffi.struct({ struct_size: 'uint64_t', n_threads: 'int', kv_type: 'int', n_ctx: 'int32_t' });
144
+ T['transcribe_run_params'] = koffi.struct({ struct_size: 'uint64_t', task: 'int', timestamps: 'int', pnc: 'int', itn: 'int', language: 'char *', target_language: 'char *', keep_special_tags: 'bool', family: 'void *', spec_k_drafts: 'int32_t' });
145
+ T['transcribe_capabilities'] = koffi.struct({ struct_size: 'uint64_t', native_sample_rate: 'int32_t', n_languages: 'int', languages: 'void *', max_timestamp_kind: 'int', supports_language_detect: 'bool', supports_translate: 'bool', supports_streaming: 'bool', supports_spec_decode: 'bool', max_audio_ms: 'int64_t' });
146
+ T['transcribe_session_limits'] = koffi.struct({ struct_size: 'uint64_t', effective_n_ctx: 'int32_t', effective_max_audio_ms: 'int64_t', max_kv_bytes: 'int64_t' });
147
+ T['transcribe_stream_params'] = koffi.struct({ struct_size: 'uint64_t', family: 'void *', commit_policy: 'int', stable_prefix_agreement_n: 'uint32_t' });
148
+ T['transcribe_stream_update'] = koffi.struct({ struct_size: 'uint64_t', result_changed: 'bool', is_final: 'bool', revision: 'int32_t', input_received_ms: 'int64_t', audio_committed_ms: 'int64_t', buffered_ms: 'int64_t', committed_changed: 'bool', tentative_changed: 'bool' });
149
+ T['transcribe_stream_text'] = koffi.struct({ struct_size: 'uint64_t', full_text: 'char *', full_text_bytes: 'uint64_t', committed_text: 'char *', committed_text_bytes: 'uint64_t', tentative_text: 'char *', tentative_text_bytes: 'uint64_t', raw_tentative_start_bytes: 'uint64_t' });
150
+ T['transcribe_timings'] = koffi.struct({ struct_size: 'uint64_t', load_ms: 'float', mel_ms: 'float', encode_ms: 'float', decode_ms: 'float' });
151
+ T['transcribe_segment'] = koffi.struct({ struct_size: 'uint64_t', t0_ms: 'int64_t', t1_ms: 'int64_t', first_word: 'int', n_words: 'int', first_token: 'int', n_tokens: 'int', text: 'char *' });
152
+ T['transcribe_word'] = koffi.struct({ struct_size: 'uint64_t', t0_ms: 'int64_t', t1_ms: 'int64_t', seg_index: 'int', first_token: 'int', n_tokens: 'int', text: 'char *' });
153
+ T['transcribe_token'] = koffi.struct({ struct_size: 'uint64_t', id: 'int', p: 'float', t0_ms: 'int64_t', t1_ms: 'int64_t', seg_index: 'int', word_index: 'int', text: 'char *' });
154
+ T['transcribe_moonshine_streaming_stream_ext'] = koffi.struct({ ext: T['transcribe_ext'], min_decode_interval_ms: 'int32_t' });
155
+ T['transcribe_parakeet_stream_ext'] = koffi.struct({ ext: T['transcribe_ext'], att_context_right: 'int32_t' });
156
+ T['transcribe_parakeet_buffered_stream_ext'] = koffi.struct({ ext: T['transcribe_ext'], left_ms: 'int32_t', chunk_ms: 'int32_t', right_ms: 'int32_t' });
157
+ T['transcribe_voxtral_realtime_stream_ext'] = koffi.struct({ ext: T['transcribe_ext'], num_delay_tokens: 'int32_t', min_decode_interval_ms: 'int32_t' });
158
+ T['transcribe_whisper_run_ext'] = koffi.struct({ ext: T['transcribe_ext'], initial_prompt: 'char *', prompt_tokens: 'void *', n_prompt_tokens: 'size_t', prompt_condition: 'int', condition_on_prev_tokens: 'bool', max_prev_context_tokens: 'int32_t', temperature: 'float', temperature_inc: 'float', compression_ratio_thold: 'float', logprob_thold: 'float', no_speech_thold: 'float', seed: 'uint32_t', max_initial_timestamp: 'float' });
159
+ T['transcribe_whisper_chunk_trace'] = koffi.struct({ struct_size: 'uint64_t', t0_ms: 'int64_t', t1_ms: 'int64_t', temperature_used: 'float', compression_ratio: 'float', avg_logprob: 'float', no_speech_prob: 'float', no_speech_triggered: 'bool', n_fallbacks: 'int32_t' });
160
+ return T;
161
+ }
162
+ export const FUNCTION_SIGNATURES = {
163
+ 'transcribe_abi_struct_align': { ret: 'size_t', args: ['transcribe_abi_struct'] },
164
+ 'transcribe_abi_struct_size': { ret: 'size_t', args: ['transcribe_abi_struct'] },
165
+ 'transcribe_backend_available': { ret: '_Bool', args: ['transcribe_backend_request'] },
166
+ 'transcribe_backend_device_count': { ret: 'int', args: [] },
167
+ 'transcribe_backend_device_init': { ret: 'void', args: ['struct transcribe_backend_device *'] },
168
+ 'transcribe_batch_detected_language': { ret: 'const char *', args: ['const struct transcribe_session *', 'int'] },
169
+ 'transcribe_batch_full_text': { ret: 'const char *', args: ['const struct transcribe_session *', 'int'] },
170
+ 'transcribe_batch_get_segment': { ret: 'transcribe_status', args: ['const struct transcribe_session *', 'int', 'int', 'struct transcribe_segment *'] },
171
+ 'transcribe_batch_get_timings': { ret: 'transcribe_status', args: ['const struct transcribe_session *', 'int', 'struct transcribe_timings *'] },
172
+ 'transcribe_batch_get_token': { ret: 'transcribe_status', args: ['const struct transcribe_session *', 'int', 'int', 'struct transcribe_token *'] },
173
+ 'transcribe_batch_get_word': { ret: 'transcribe_status', args: ['const struct transcribe_session *', 'int', 'int', 'struct transcribe_word *'] },
174
+ 'transcribe_batch_n_results': { ret: 'int', args: ['const struct transcribe_session *'] },
175
+ 'transcribe_batch_n_segments': { ret: 'int', args: ['const struct transcribe_session *', 'int'] },
176
+ 'transcribe_batch_n_tokens': { ret: 'int', args: ['const struct transcribe_session *', 'int'] },
177
+ 'transcribe_batch_n_words': { ret: 'int', args: ['const struct transcribe_session *', 'int'] },
178
+ 'transcribe_batch_returned_timestamp_kind': { ret: 'transcribe_timestamp_kind', args: ['const struct transcribe_session *', 'int'] },
179
+ 'transcribe_batch_status': { ret: 'transcribe_status', args: ['const struct transcribe_session *', 'int'] },
180
+ 'transcribe_capabilities_init': { ret: 'void', args: ['struct transcribe_capabilities *'] },
181
+ 'transcribe_close': { ret: 'void', args: ['struct transcribe_session *'] },
182
+ 'transcribe_detected_language': { ret: 'const char *', args: ['const struct transcribe_session *'] },
183
+ 'transcribe_ext_check': { ret: 'transcribe_status', args: ['const struct transcribe_ext *', 'uint32_t', 'uint64_t'] },
184
+ 'transcribe_full_text': { ret: 'const char *', args: ['const struct transcribe_session *'] },
185
+ 'transcribe_get_backend_device': { ret: 'transcribe_status', args: ['int', 'struct transcribe_backend_device *'] },
186
+ 'transcribe_get_model': { ret: 'const struct transcribe_model *', args: ['const struct transcribe_session *'] },
187
+ 'transcribe_get_segment': { ret: 'transcribe_status', args: ['const struct transcribe_session *', 'int', 'struct transcribe_segment *'] },
188
+ 'transcribe_get_timings': { ret: 'transcribe_status', args: ['const struct transcribe_session *', 'struct transcribe_timings *'] },
189
+ 'transcribe_get_token': { ret: 'transcribe_status', args: ['const struct transcribe_session *', 'int', 'struct transcribe_token *'] },
190
+ 'transcribe_get_whisper_chunk_count': { ret: 'int', args: ['const struct transcribe_session *'] },
191
+ 'transcribe_get_whisper_chunk_trace': { ret: 'transcribe_status', args: ['const struct transcribe_session *', 'int', 'struct transcribe_whisper_chunk_trace *'] },
192
+ 'transcribe_get_word': { ret: 'transcribe_status', args: ['const struct transcribe_session *', 'int', 'struct transcribe_word *'] },
193
+ 'transcribe_init_backends': { ret: 'transcribe_status', args: ['const char *'] },
194
+ 'transcribe_init_backends_default': { ret: 'transcribe_status', args: [] },
195
+ 'transcribe_log_set': { ret: 'void', args: ['transcribe_log_callback', 'void *'] },
196
+ 'transcribe_model_accepts_ext_kind': { ret: '_Bool', args: ['const struct transcribe_model *', 'transcribe_ext_slot', 'uint32_t'] },
197
+ 'transcribe_model_arch_string': { ret: 'const char *', args: ['const struct transcribe_model *'] },
198
+ 'transcribe_model_backend': { ret: 'const char *', args: ['const struct transcribe_model *'] },
199
+ 'transcribe_model_free': { ret: 'void', args: ['struct transcribe_model *'] },
200
+ 'transcribe_model_get_capabilities': { ret: 'transcribe_status', args: ['const struct transcribe_model *', 'struct transcribe_capabilities *'] },
201
+ 'transcribe_model_load_file': { ret: 'transcribe_status', args: ['const char *', 'const struct transcribe_model_load_params *', 'struct transcribe_model **'] },
202
+ 'transcribe_model_load_params_init': { ret: 'void', args: ['struct transcribe_model_load_params *'] },
203
+ 'transcribe_model_supports': { ret: '_Bool', args: ['const struct transcribe_model *', 'transcribe_feature'] },
204
+ 'transcribe_model_variant_string': { ret: 'const char *', args: ['const struct transcribe_model *'] },
205
+ 'transcribe_moonshine_streaming_stream_ext_init': { ret: 'void', args: ['struct transcribe_moonshine_streaming_stream_ext *'] },
206
+ 'transcribe_n_segments': { ret: 'int', args: ['const struct transcribe_session *'] },
207
+ 'transcribe_n_tokens': { ret: 'int', args: ['const struct transcribe_session *'] },
208
+ 'transcribe_n_words': { ret: 'int', args: ['const struct transcribe_session *'] },
209
+ 'transcribe_open': { ret: 'transcribe_status', args: ['const char *', 'const struct transcribe_model_load_params *', 'const struct transcribe_session_params *', 'struct transcribe_session **'] },
210
+ 'transcribe_parakeet_buffered_stream_ext_init': { ret: 'void', args: ['struct transcribe_parakeet_buffered_stream_ext *'] },
211
+ 'transcribe_parakeet_stream_ext_init': { ret: 'void', args: ['struct transcribe_parakeet_stream_ext *'] },
212
+ 'transcribe_print_timings': { ret: 'void', args: ['const struct transcribe_session *'] },
213
+ 'transcribe_reset_timings': { ret: 'void', args: ['struct transcribe_session *'] },
214
+ 'transcribe_returned_timestamp_kind': { ret: 'transcribe_timestamp_kind', args: ['const struct transcribe_session *'] },
215
+ 'transcribe_run': { ret: 'transcribe_status', args: ['struct transcribe_session *', 'const float *', 'int', 'const struct transcribe_run_params *'] },
216
+ 'transcribe_run_batch': { ret: 'transcribe_status', args: ['struct transcribe_session *', 'const float *const *', 'const int *', 'int', 'const struct transcribe_run_params *'] },
217
+ 'transcribe_run_params_init': { ret: 'void', args: ['struct transcribe_run_params *'] },
218
+ 'transcribe_segment_init': { ret: 'void', args: ['struct transcribe_segment *'] },
219
+ 'transcribe_session_free': { ret: 'void', args: ['struct transcribe_session *'] },
220
+ 'transcribe_session_get_limits': { ret: 'transcribe_status', args: ['const struct transcribe_session *', 'struct transcribe_session_limits *'] },
221
+ 'transcribe_session_init': { ret: 'transcribe_status', args: ['struct transcribe_model *', 'const struct transcribe_session_params *', 'struct transcribe_session **'] },
222
+ 'transcribe_session_limits_init': { ret: 'void', args: ['struct transcribe_session_limits *'] },
223
+ 'transcribe_session_params_init': { ret: 'void', args: ['struct transcribe_session_params *'] },
224
+ 'transcribe_set_abort_callback': { ret: 'void', args: ['struct transcribe_session *', 'transcribe_abort_callback', 'void *'] },
225
+ 'transcribe_status_string': { ret: 'const char *', args: ['int'] },
226
+ 'transcribe_stream_begin': { ret: 'transcribe_status', args: ['struct transcribe_session *', 'const struct transcribe_run_params *', 'const struct transcribe_stream_params *'] },
227
+ 'transcribe_stream_feed': { ret: 'transcribe_status', args: ['struct transcribe_session *', 'const float *', 'int', 'struct transcribe_stream_update *'] },
228
+ 'transcribe_stream_finalize': { ret: 'transcribe_status', args: ['struct transcribe_session *', 'struct transcribe_stream_update *'] },
229
+ 'transcribe_stream_get_state': { ret: 'enum transcribe_stream_state', args: ['const struct transcribe_session *'] },
230
+ 'transcribe_stream_get_text': { ret: 'transcribe_status', args: ['const struct transcribe_session *', 'struct transcribe_stream_text *'] },
231
+ 'transcribe_stream_last_status': { ret: 'transcribe_status', args: ['const struct transcribe_session *'] },
232
+ 'transcribe_stream_n_committed_segments': { ret: 'int', args: ['const struct transcribe_session *'] },
233
+ 'transcribe_stream_n_committed_tokens': { ret: 'int', args: ['const struct transcribe_session *'] },
234
+ 'transcribe_stream_n_committed_words': { ret: 'int', args: ['const struct transcribe_session *'] },
235
+ 'transcribe_stream_params_init': { ret: 'void', args: ['struct transcribe_stream_params *'] },
236
+ 'transcribe_stream_reset': { ret: 'void', args: ['struct transcribe_session *'] },
237
+ 'transcribe_stream_revision': { ret: 'int', args: ['const struct transcribe_session *'] },
238
+ 'transcribe_stream_text_init': { ret: 'void', args: ['struct transcribe_stream_text *'] },
239
+ 'transcribe_stream_update_init': { ret: 'void', args: ['struct transcribe_stream_update *'] },
240
+ 'transcribe_timings_init': { ret: 'void', args: ['struct transcribe_timings *'] },
241
+ 'transcribe_token_init': { ret: 'void', args: ['struct transcribe_token *'] },
242
+ 'transcribe_tokenize': { ret: 'int', args: ['const struct transcribe_model *', 'const char *', 'int32_t *', 'size_t'] },
243
+ 'transcribe_version': { ret: 'const char *', args: [] },
244
+ 'transcribe_version_commit': { ret: 'const char *', args: [] },
245
+ 'transcribe_voxtral_realtime_stream_ext_init': { ret: 'void', args: ['struct transcribe_voxtral_realtime_stream_ext *'] },
246
+ 'transcribe_was_aborted': { ret: '_Bool', args: ['const struct transcribe_session *'] },
247
+ 'transcribe_was_truncated': { ret: '_Bool', args: ['const struct transcribe_session *'] },
248
+ 'transcribe_whisper_chunk_trace_init': { ret: 'void', args: ['struct transcribe_whisper_chunk_trace *'] },
249
+ 'transcribe_whisper_run_ext_init': { ret: 'void', args: ['struct transcribe_whisper_run_ext *'] },
250
+ 'transcribe_word_init': { ret: 'void', args: ['struct transcribe_word *'] },
251
+ };
package/dist/abi.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Load-time ABI verification. TypeScript has no C compiler to check struct
3
+ * layout at build time (unlike Rust/Swift), so — like the Python ctypes
4
+ * binding — we verify the generated layout twice before constructing anything:
5
+ *
6
+ * 1. self-check: koffi's computed size/align/offsets vs the layout the C
7
+ * compiler captured at generation time (STRUCT_LAYOUT).
8
+ * 2. native-agreement: koffi's size/align vs the loaded library via
9
+ * transcribe_abi_struct_size/_align (catches a binding/library skew).
10
+ */
11
+ import type { Bound } from "./ffi.js";
12
+ export declare function verifyLayouts({ koffi, T, F }: Bound): void;
package/dist/abi.js ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Load-time ABI verification. TypeScript has no C compiler to check struct
3
+ * layout at build time (unlike Rust/Swift), so — like the Python ctypes
4
+ * binding — we verify the generated layout twice before constructing anything:
5
+ *
6
+ * 1. self-check: koffi's computed size/align/offsets vs the layout the C
7
+ * compiler captured at generation time (STRUCT_LAYOUT).
8
+ * 2. native-agreement: koffi's size/align vs the loaded library via
9
+ * transcribe_abi_struct_size/_align (catches a binding/library skew).
10
+ */
11
+ import { ABI_STRUCT_IDS, STRUCT_LAYOUT } from "./_generated.js";
12
+ import * as g from "./_generated.js";
13
+ import { AbiError } from "./errors.js";
14
+ export function verifyLayouts({ koffi, T, F }) {
15
+ const mismatches = [];
16
+ for (const [name, lo] of Object.entries(STRUCT_LAYOUT)) {
17
+ const type = T[name];
18
+ if (!type)
19
+ continue;
20
+ const size = koffi.sizeof(type);
21
+ const align = koffi.alignof(type);
22
+ if (size !== lo.size)
23
+ mismatches.push(`${name}: size ${size} != captured ${lo.size}`);
24
+ if (align !== lo.align)
25
+ mismatches.push(`${name}: align ${align} != captured ${lo.align}`);
26
+ for (const [field, off] of Object.entries(lo.offsets)) {
27
+ const actual = koffi.offsetof(type, field);
28
+ if (actual !== off) {
29
+ mismatches.push(`${name}.${field}: offset ${actual} != captured ${off}`);
30
+ }
31
+ }
32
+ }
33
+ for (const [name, id] of Object.entries(ABI_STRUCT_IDS)) {
34
+ const type = T[name];
35
+ if (!type)
36
+ continue;
37
+ const nativeSize = Number(F.abiStructSize(id));
38
+ const nativeAlign = Number(F.abiStructAlign(id));
39
+ if (nativeSize === 0) {
40
+ mismatches.push(`${name}: native size 0 (loaded library is older than this binding)`);
41
+ continue;
42
+ }
43
+ const size = koffi.sizeof(type);
44
+ const align = koffi.alignof(type);
45
+ if (size !== nativeSize)
46
+ mismatches.push(`${name}: size ${size} != native ${nativeSize}`);
47
+ if (align !== nativeAlign)
48
+ mismatches.push(`${name}: align ${align} != native ${nativeAlign}`);
49
+ }
50
+ if (mismatches.length) {
51
+ throw new AbiError("transcribe_cpp ABI layout check failed — this is a version/build skew between the " +
52
+ "binding and the native library:\n " +
53
+ mismatches.join("\n "), g.TRANSCRIBE_ERR_BAD_STRUCT_SIZE);
54
+ }
55
+ }