transcribe-cpp 0.0.3 → 0.0.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.
package/README.md CHANGED
@@ -200,6 +200,45 @@ N-API addon, so `cmake-js` does not apply) and installs into `prebuilds/<tuple>/
200
200
  which the loader finds automatically. Requires `cmake` and a C/C++ toolchain.
201
201
  You can also point `TRANSCRIBE_LIBRARY` at any `libtranscribe` you built.
202
202
 
203
+ ## Packaging an app (Electron / Tauri)
204
+
205
+ The native code ships as a self-contained `@transcribe-cpp/<platform>` package
206
+ (the shared library plus its sibling ggml libs / backend modules, in one
207
+ directory). Because npm resolution is transitive, your app can locate that
208
+ directory at *build* time — there is nothing special to wire through.
209
+ `artifactDir()` returns it **without loading the library** (no dlopen), which is
210
+ exactly what a bundler/pack step needs:
211
+
212
+ ```js
213
+ import { artifactDir } from "transcribe-cpp";
214
+ import * as fs from "node:fs";
215
+
216
+ const dir = artifactDir();
217
+ const lib =
218
+ process.platform === "win32" ? "transcribe.dll"
219
+ : process.platform === "darwin" ? "libtranscribe.dylib"
220
+ : "libtranscribe.so";
221
+
222
+ // HARD-FAIL if the artifact is missing/empty: better to break the build than
223
+ // ship a silently broken installer that crashes at first transcribe() call.
224
+ if (!fs.existsSync(`${dir}/${lib}`)) {
225
+ throw new Error(`native library not found in ${dir}; the platform package is not bundled`);
226
+ }
227
+ // copy `dir` into your app resources, or feed it to your bundler config…
228
+ ```
229
+
230
+ The native files are real binaries, so they must not be packed into an asar
231
+ archive. With **electron-builder**, unpack the platform packages:
232
+
233
+ ```jsonc
234
+ // package.json → "build"
235
+ { "asarUnpack": ["node_modules/@transcribe-cpp/**"] }
236
+ ```
237
+
238
+ For a **Tauri** app whose native bits come from the Rust crate instead, see that
239
+ crate's "Packaging a distributable" section — there the artifact dir is exposed
240
+ to your `build.rs` as `DEP_TRANSCRIBE_CPP_RUNTIME_DIR`.
241
+
203
242
  ## Waived requirements
204
243
 
205
244
  - **Per-field ABI layout is not waived** (unlike Rust/Swift): TypeScript has no
@@ -211,5 +250,5 @@ You can also point `TRANSCRIBE_LIBRARY` at any `libtranscribe` you built.
211
250
 
212
251
  ## License
213
252
 
214
- MIT. Bundled native packages include third-party license texts (ggml and any
215
- bundled backend runtimes).
253
+ MIT. Bundled native packages include third-party license texts (ggml, miniz,
254
+ and any bundled backend runtimes).
@@ -1,4 +1,4 @@
1
- export declare const PUBLIC_HEADER_HASH = "2273744299e5aa65";
1
+ export declare const PUBLIC_HEADER_HASH = "ebe6a6816e34a24e";
2
2
  export declare const TRANSCRIBE_OK = 0;
3
3
  export declare const TRANSCRIBE_ERR_INVALID_ARG = 1;
4
4
  export declare const TRANSCRIBE_ERR_NOT_IMPLEMENTED = 2;
@@ -62,6 +62,10 @@ export declare const TRANSCRIBE_BACKEND_METAL = 2;
62
62
  export declare const TRANSCRIBE_BACKEND_VULKAN = 3;
63
63
  export declare const TRANSCRIBE_BACKEND_CPU_ACCEL = 4;
64
64
  export declare const TRANSCRIBE_BACKEND_CUDA = 5;
65
+ export declare const TRANSCRIBE_DEVICE_TYPE_CPU = 0;
66
+ export declare const TRANSCRIBE_DEVICE_TYPE_GPU = 1;
67
+ export declare const TRANSCRIBE_DEVICE_TYPE_IGPU = 2;
68
+ export declare const TRANSCRIBE_DEVICE_TYPE_ACCEL = 3;
65
69
  export declare const TRANSCRIBE_FEATURE_INITIAL_PROMPT = 0;
66
70
  export declare const TRANSCRIBE_FEATURE_TEMPERATURE_FALLBACK = 1;
67
71
  export declare const TRANSCRIBE_FEATURE_LONG_FORM = 2;
@@ -10,7 +10,7 @@
10
10
  // Stable digest of the ABI surface (structs, enums, macros, layout,
11
11
  // prototypes), computed by the Python oracle and pinned here so a header
12
12
  // ABI change turns this binding's drift check red for conscious review.
13
- export const PUBLIC_HEADER_HASH = "2273744299e5aa65";
13
+ export const PUBLIC_HEADER_HASH = "ebe6a6816e34a24e";
14
14
  // === enum constants ===
15
15
  export const TRANSCRIBE_OK = 0;
16
16
  export const TRANSCRIBE_ERR_INVALID_ARG = 1;
@@ -75,6 +75,10 @@ export const TRANSCRIBE_BACKEND_METAL = 2;
75
75
  export const TRANSCRIBE_BACKEND_VULKAN = 3;
76
76
  export const TRANSCRIBE_BACKEND_CPU_ACCEL = 4;
77
77
  export const TRANSCRIBE_BACKEND_CUDA = 5;
78
+ export const TRANSCRIBE_DEVICE_TYPE_CPU = 0;
79
+ export const TRANSCRIBE_DEVICE_TYPE_GPU = 1;
80
+ export const TRANSCRIBE_DEVICE_TYPE_IGPU = 2;
81
+ export const TRANSCRIBE_DEVICE_TYPE_ACCEL = 3;
78
82
  export const TRANSCRIBE_FEATURE_INITIAL_PROMPT = 0;
79
83
  export const TRANSCRIBE_FEATURE_TEMPERATURE_FALLBACK = 1;
80
84
  export const TRANSCRIBE_FEATURE_LONG_FORM = 2;
@@ -98,7 +102,7 @@ export const TRANSCRIBE_EXT_KIND_VOXTRAL_REALTIME_STREAM = 1414746710;
98
102
  export const TRANSCRIBE_EXT_KIND_WHISPER_RUN = 1314015319;
99
103
  export const STRUCT_LAYOUT = {
100
104
  '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 } },
105
+ 'transcribe_backend_device': { size: 64, align: 8, offsets: { 'struct_size': 0, 'name': 8, 'description': 16, 'kind': 24, 'device_id': 32, 'memory_total': 40, 'memory_free': 48, 'device_type': 56 } },
102
106
  'transcribe_model_load_params': { size: 16, align: 8, offsets: { 'struct_size': 0, 'backend': 8, 'gpu_device': 12 } },
103
107
  'transcribe_session_params': { size: 24, align: 8, offsets: { 'struct_size': 0, 'n_threads': 8, 'kv_type': 12, 'n_ctx': 16 } },
104
108
  '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 } },
@@ -138,7 +142,7 @@ export const ABI_STRUCT_IDS = {
138
142
  export function defineTypes(koffi) {
139
143
  const T = {};
140
144
  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 *' });
145
+ T['transcribe_backend_device'] = koffi.struct({ struct_size: 'uint64_t', name: 'char *', description: 'char *', kind: 'char *', device_id: 'char *', memory_total: 'uint64_t', memory_free: 'uint64_t', device_type: 'int' });
142
146
  T['transcribe_model_load_params'] = koffi.struct({ struct_size: 'uint64_t', backend: 'int', gpu_device: 'int' });
143
147
  T['transcribe_session_params'] = koffi.struct({ struct_size: 'uint64_t', n_threads: 'int', kv_type: 'int', n_ctx: 'int32_t' });
144
148
  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' });
@@ -198,6 +202,7 @@ export const FUNCTION_SIGNATURES = {
198
202
  'transcribe_model_backend': { ret: 'const char *', args: ['const struct transcribe_model *'] },
199
203
  'transcribe_model_free': { ret: 'void', args: ['struct transcribe_model *'] },
200
204
  'transcribe_model_get_capabilities': { ret: 'transcribe_status', args: ['const struct transcribe_model *', 'struct transcribe_capabilities *'] },
205
+ 'transcribe_model_get_device': { ret: 'transcribe_status', args: ['const struct transcribe_model *', 'struct transcribe_backend_device *'] },
201
206
  'transcribe_model_load_file': { ret: 'transcribe_status', args: ['const char *', 'const struct transcribe_model_load_params *', 'struct transcribe_model **'] },
202
207
  'transcribe_model_load_params_init': { ret: 'void', args: ['struct transcribe_model_load_params *'] },
203
208
  'transcribe_model_supports': { ret: '_Bool', args: ['const struct transcribe_model *', 'transcribe_feature'] },
package/dist/ffi.js CHANGED
@@ -51,6 +51,10 @@ export function bindLibrary(libraryPath) {
51
51
  modelArch: lib.func("transcribe_model_arch_string", "str", ["void *"]),
52
52
  modelVariant: lib.func("transcribe_model_variant_string", "str", ["void *"]),
53
53
  modelBackend: lib.func("transcribe_model_backend", "str", ["void *"]),
54
+ modelGetDevice: lib.func("transcribe_model_get_device", "int", [
55
+ "void *",
56
+ iop(T.transcribe_backend_device),
57
+ ]),
54
58
  modelSupports: lib.func("transcribe_model_supports", "bool", ["void *", "int"]),
55
59
  tokenize: lib.func("transcribe_tokenize", "int", ["void *", "str", "int32_t *", "size_t"]),
56
60
  capabilitiesInit: lib.func("transcribe_capabilities_init", "void", [
package/dist/index.d.ts CHANGED
@@ -17,6 +17,19 @@ export declare function version(): {
17
17
  headerHash: string;
18
18
  };
19
19
  export declare function libraryPath(): string;
20
+ /**
21
+ * The directory holding the native library and its sibling ggml libs / backend
22
+ * modules — resolved WITHOUT loading the library (no dlopen, no ABI check, no
23
+ * backend init), unlike every other entry point here.
24
+ *
25
+ * This is the build/packaging hook: call it from a bundler config or pack step
26
+ * to copy the native artifacts into your installer (Electron `asarUnpack`, Tauri
27
+ * `resources`, etc.). Resolution follows the same order as the runtime loader
28
+ * (`TRANSCRIBE_LIBRARY` → the `@transcribe-cpp/<platform>` package → a local
29
+ * prebuild → the dev tree) and throws if nothing is found. For runtime use,
30
+ * `libraryPath()` returns the resolved library path of the *loaded* binding.
31
+ */
32
+ export declare function artifactDir(): string;
20
33
  export declare function getAvailableBackends(): BackendInfo[];
21
34
  export declare function backendAvailable(backend: Backend): boolean;
22
35
  export declare class Session {
@@ -80,6 +93,10 @@ export declare class TranscribeModel {
80
93
  get arch(): string;
81
94
  get variant(): string;
82
95
  get backend(): string;
96
+ /** The compute device this model is running on. `memoryFree` is a live
97
+ * snapshot, so read this again to poll how much device memory is left
98
+ * after the model loaded. */
99
+ get device(): BackendInfo;
83
100
  dispose(): void;
84
101
  [Symbol.dispose](): void;
85
102
  }
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * backend discovery, log routing, and cooperative cancellation.
7
7
  */
8
8
  import { native, setLogHandler } from "./native.js";
9
+ import { resolveLibrary } from "./loader.js";
9
10
  import * as g from "./_generated.js";
10
11
  import { Aborted, Busy, exceptionForStatus, InvalidArgument, ModelLoadError, NotImplementedByModel, OutputTruncated, TranscribeError, UnsupportedRequest, } from "./errors.js";
11
12
  export * from "./types.js";
@@ -216,6 +217,42 @@ export function version() {
216
217
  export function libraryPath() {
217
218
  return native().libraryPath;
218
219
  }
220
+ /**
221
+ * The directory holding the native library and its sibling ggml libs / backend
222
+ * modules — resolved WITHOUT loading the library (no dlopen, no ABI check, no
223
+ * backend init), unlike every other entry point here.
224
+ *
225
+ * This is the build/packaging hook: call it from a bundler config or pack step
226
+ * to copy the native artifacts into your installer (Electron `asarUnpack`, Tauri
227
+ * `resources`, etc.). Resolution follows the same order as the runtime loader
228
+ * (`TRANSCRIBE_LIBRARY` → the `@transcribe-cpp/<platform>` package → a local
229
+ * prebuild → the dev tree) and throws if nothing is found. For runtime use,
230
+ * `libraryPath()` returns the resolved library path of the *loaded* binding.
231
+ */
232
+ export function artifactDir() {
233
+ return resolveLibrary().artifactDir;
234
+ }
235
+ const DEVICE_TYPE_NAMES = {
236
+ [g.TRANSCRIBE_DEVICE_TYPE_CPU]: "cpu",
237
+ [g.TRANSCRIBE_DEVICE_TYPE_GPU]: "gpu",
238
+ [g.TRANSCRIBE_DEVICE_TYPE_IGPU]: "igpu",
239
+ [g.TRANSCRIBE_DEVICE_TYPE_ACCEL]: "accel",
240
+ };
241
+ // Decode a koffi-filled transcribe_backend_device struct into a BackendInfo.
242
+ // memory_* are uint64 (bigint from koffi) but stay well under 2^53 for any
243
+ // real device, so num() narrows them losslessly.
244
+ function deviceFromRaw(dev, index = null) {
245
+ return {
246
+ name: dev.name ?? "",
247
+ description: dev.description ?? "",
248
+ kind: dev.kind ?? "",
249
+ deviceType: DEVICE_TYPE_NAMES[dev.device_type] ?? "unknown",
250
+ deviceId: dev.device_id ?? null,
251
+ memoryTotal: num(dev.memory_total),
252
+ memoryFree: num(dev.memory_free),
253
+ index,
254
+ };
255
+ }
219
256
  export function getAvailableBackends() {
220
257
  const n = native();
221
258
  const count = n.F.backendDeviceCount();
@@ -224,7 +261,7 @@ export function getAvailableBackends() {
224
261
  const dev = {};
225
262
  n.F.backendDeviceInit(dev);
226
263
  check(n, n.F.getBackendDevice(i, dev), `reading backend device ${i}`);
227
- out.push({ name: dev.name ?? "", description: dev.description ?? "", kind: dev.kind ?? "" });
264
+ out.push(deviceFromRaw(dev, i));
228
265
  }
229
266
  return out;
230
267
  }
@@ -1049,6 +1086,15 @@ export class TranscribeModel {
1049
1086
  get backend() {
1050
1087
  return this.#n.F.modelBackend(this.handle) ?? "";
1051
1088
  }
1089
+ /** The compute device this model is running on. `memoryFree` is a live
1090
+ * snapshot, so read this again to poll how much device memory is left
1091
+ * after the model loaded. */
1092
+ get device() {
1093
+ const dev = {};
1094
+ this.#n.F.backendDeviceInit(dev);
1095
+ check(this.#n, this.#n.F.modelGetDevice(this.handle, dev), "reading model device");
1096
+ return deviceFromRaw(dev);
1097
+ }
1052
1098
  dispose() {
1053
1099
  if (this.#disposed)
1054
1100
  return;
package/dist/types.d.ts CHANGED
@@ -65,15 +65,37 @@ export interface TranscriptionResult {
65
65
  aborted: boolean;
66
66
  truncated: boolean;
67
67
  }
68
+ /** Vendor-agnostic device class, orthogonal to {@link BackendInfo.kind}.
69
+ * `"unknown"` is reported for a device-type value newer than this binding —
70
+ * distinguish such devices by {@link BackendInfo.deviceId} / name, not this axis. */
71
+ export type DeviceType = "cpu" | "gpu" | "igpu" | "accel" | "unknown";
68
72
  export interface BackendInfo {
69
73
  name: string;
70
74
  description: string;
71
75
  kind: string;
76
+ /** The CPU/GPU/IGPU/ACCEL axis, orthogonal to `kind`. */
77
+ deviceType: DeviceType;
78
+ /** Stable hardware id (PCI bus id) when the backend reports one, else null
79
+ * (e.g. Metal). */
80
+ deviceId: string | null;
81
+ /** Reported device memory capacity in bytes, or 0 if unreported. */
82
+ memoryTotal: number;
83
+ /** Available device memory in bytes — a snapshot at query time, or 0 if
84
+ * unreported. Re-query (via {@link getAvailableBackends} or `model.device`)
85
+ * to refresh; backend-defined and not comparable across device kinds. */
86
+ memoryFree: number;
87
+ /** Registry index of this device — the value to pass as
88
+ * {@link ModelOptions.gpuDevice} to select it (0 selects the auto / first
89
+ * device). `null` when this came from `model.device`, since
90
+ * `transcribe_model_get_device` does not expose an index; correlate such a
91
+ * device back to {@link getAvailableBackends} by `deviceId` / `name`
92
+ * instead. Order-dependent and not stable across driver updates or hosts. */
93
+ index: number | null;
72
94
  }
73
95
  export interface ModelOptions {
74
96
  /** "auto" (default), or an explicit backend. */
75
97
  backend?: Backend;
76
- /** GPU device ordinal for multi-GPU hosts. */
98
+ /** GPU device registry index. 0 means auto / first matching device. */
77
99
  gpuDevice?: number;
78
100
  }
79
101
  export interface SessionOptions {
@@ -96,9 +118,6 @@ export interface TranscribeOptions {
96
118
  /** A run-slot family extension (e.g. whisper). */
97
119
  family?: FamilyExtension;
98
120
  }
99
- /** A native compute device the runtime discovered. */
100
- export interface DeviceInfo extends BackendInfo {
101
- }
102
121
  /** One result of a batch run: success carries the transcript, failure the error.
103
122
  * On failure, `error.utteranceIndex` is set, and `error.partialResult` carries any
104
123
  * recovered transcript when the failure was an abort/truncation. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "transcribe-cpp",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "TypeScript/Node.js bindings for transcribe.cpp — a C/C++ speech-to-text library built on ggml",
5
5
  "type": "module",
6
6
  "exports": {
@@ -51,11 +51,11 @@
51
51
  "koffi": "^3.0.2"
52
52
  },
53
53
  "optionalDependencies": {
54
- "@transcribe-cpp/darwin-arm64-metal": "0.0.3",
55
- "@transcribe-cpp/darwin-x64-cpu": "0.0.3",
56
- "@transcribe-cpp/linux-x64-cpu-vulkan": "0.0.3",
57
- "@transcribe-cpp/linux-arm64-cpu-vulkan": "0.0.3",
58
- "@transcribe-cpp/win32-x64-cpu-vulkan": "0.0.3"
54
+ "@transcribe-cpp/darwin-arm64-metal": "0.0.5",
55
+ "@transcribe-cpp/darwin-x64-cpu": "0.0.5",
56
+ "@transcribe-cpp/linux-x64-cpu-vulkan": "0.0.5",
57
+ "@transcribe-cpp/linux-arm64-cpu-vulkan": "0.0.5",
58
+ "@transcribe-cpp/win32-x64-cpu-vulkan": "0.0.5"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@types/node": "^22.0.0",