@camstack/types 1.2.391 → 1.2.392

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.
@@ -1,4 +1,5 @@
1
- export type ArchiveFormat = 'zip' | 'tar.gz' | 'tar.xz';
1
+ /** `tar` = an uncompressed tarball (a `.deb` payload this module already decompressed). */
2
+ export type ArchiveFormat = 'zip' | 'tar.gz' | 'tar.xz' | 'tar';
2
3
  export type ExecFile = (file: string, args: readonly string[]) => unknown;
3
4
  export interface ExtractDeps {
4
5
  readonly platform: NodeJS.Platform;
@@ -0,0 +1,34 @@
1
+ import { type ArchiveFormat } from './archive-extract.js';
2
+ /** The host-tar step, injectable so a spec can see what it is handed. */
3
+ export type TarExtract = (archivePath: string, format: ArchiveFormat, destDir: string) => void;
4
+ export interface ArMember {
5
+ readonly name: string;
6
+ readonly data: Buffer;
7
+ }
8
+ /** Every member of an `ar` archive, in order. Throws on anything that is not one. */
9
+ export declare function readArMembers(archive: Buffer): readonly ArMember[];
10
+ export type DebPayloadCompression = 'zst' | 'xz' | 'gz' | 'none';
11
+ export interface DebPayload {
12
+ readonly compression: DebPayloadCompression;
13
+ readonly data: Buffer;
14
+ }
15
+ /** The `data.tar.*` member of a `.deb`, with the compression its name declares. */
16
+ export declare function debDataMember(deb: Buffer): DebPayload;
17
+ /**
18
+ * Unpack a `.deb`'s payload into `destDir` (the package's own tree: `usr/lib/…`).
19
+ * zstd and gzip are decompressed in-process; the plain tar and the xz payload
20
+ * are expanded by the host `tar`.
21
+ */
22
+ export declare function extractDebPayload(deb: Buffer, destDir: string, workDir: string, extract?: TarExtract): void;
23
+ /**
24
+ * The first entry named exactly `name` under `dir` — a regular file OR a
25
+ * symlink: a library's soname (`libusb-1.0.so.0`) is a link to the versioned
26
+ * file in every package, so a file-only search never finds it.
27
+ */
28
+ export declare function findEntryByName(dir: string, name: string): string | null;
29
+ /**
30
+ * Copy the library `memberName` out of the `.deb` at `debPath` to `targetPath`,
31
+ * dereferencing the soname link to the real file. Throws, naming the member,
32
+ * when the package does not carry it.
33
+ */
34
+ export declare function installLibraryFromDeb(debPath: string, memberName: string, targetPath: string, workDir: string, extract?: TarExtract): void;
@@ -0,0 +1,83 @@
1
+ import type { IScopedLogger } from '../interfaces/logging.js';
2
+ /** The path override (a library PATH, never a secret). */
3
+ export declare const EDGETPU_LIB_ENV = "CAMSTACK_EDGETPU_LIB";
4
+ /** The pinned feranick release. Changing it is the delegate version change. */
5
+ export declare const EDGETPU_DELEGATE_RELEASE: '16.0TF2.17.1-1';
6
+ /** Where the hub image's installer has always put the delegate. */
7
+ export declare const LEGACY_EDGETPU_LIB = "/data/deps/edgetpu/lib/libedgetpu.so.1";
8
+ /** One file of the delegate set: where it comes from and the name it is staged as. */
9
+ export interface EdgetpuStagedFile {
10
+ readonly url: string;
11
+ /** SHA-256 of the downloaded container, verified before a byte is extracted. */
12
+ readonly sha256: string;
13
+ readonly sizeBytes: number;
14
+ readonly container: 'deb' | 'zip';
15
+ /** deb: the library's file name inside the payload; zip: its path inside the archive. */
16
+ readonly member: string;
17
+ /** The name it is staged as in `<deps>/edgetpu/lib/`. */
18
+ readonly installAs: string;
19
+ }
20
+ export type EdgetpuDelegateArtifact = {
21
+ readonly supported: true;
22
+ /** The delegate first; libusb (Linux) after it. */
23
+ readonly files: readonly EdgetpuStagedFile[];
24
+ } | {
25
+ readonly supported: false;
26
+ readonly reason: string;
27
+ };
28
+ /** The pinned delegate set for a platform and arch, or why there is none. */
29
+ export declare function getEdgetpuDelegateArtifact(platform: NodeJS.Platform, arch: string): EdgetpuDelegateArtifact;
30
+ /** The delegate's file name on a platform (what the Python pool loads). */
31
+ export declare function edgetpuDelegateFileName(platform: NodeJS.Platform): string;
32
+ /** `<dataDir>/deps/edgetpu/lib` — where the deps downloader stages the set. */
33
+ export declare function edgetpuLibDir(dataDir: string): string;
34
+ export type EdgetpuDelegateOrigin = 'override' | 'staged' | 'legacy';
35
+ export interface EdgetpuDelegateChoice {
36
+ readonly path: string;
37
+ readonly origin: EdgetpuDelegateOrigin;
38
+ }
39
+ export interface EdgetpuDelegateLookup {
40
+ readonly platform: NodeJS.Platform;
41
+ /** The node data root (`CAMSTACK_DATA`), or `undefined` when unknown. */
42
+ readonly dataDir: string | undefined;
43
+ /** `CAMSTACK_EDGETPU_LIB`, verbatim. */
44
+ readonly override: string | undefined;
45
+ readonly exists: (path: string) => boolean;
46
+ }
47
+ /**
48
+ * The delegate this node loads, or `null` when none is present. An override
49
+ * that names a missing file is NOT followed past: the operator said where it
50
+ * is, and a silent fallback would load a different build than they chose.
51
+ */
52
+ export declare function chooseEdgetpuDelegate(lookup: EdgetpuDelegateLookup): EdgetpuDelegateChoice | null;
53
+ /** The node data root the way every node process names it. */
54
+ export declare function nodeDataRootFromEnv(env: NodeJS.ProcessEnv): string | undefined;
55
+ export type EdgetpuDelegateResult = {
56
+ readonly kind: 'ready';
57
+ readonly path: string;
58
+ readonly origin: EdgetpuDelegateOrigin | 'downloaded';
59
+ } | {
60
+ readonly kind: 'unavailable';
61
+ readonly reason: string;
62
+ };
63
+ /**
64
+ * A requirements file names the native library its packages cannot run
65
+ * without on this line; `installPythonRequirements` stages it after pip.
66
+ * (`ai-edge-litert` imports without the delegate and is useless without it.)
67
+ */
68
+ export declare const EDGETPU_DELEGATE_DIRECTIVE = "# camstack-native-dep: edgetpu-delegate";
69
+ export declare function declaresEdgetpuDelegate(requirementsText: string): boolean;
70
+ export interface EnsureEdgetpuDelegateOptions {
71
+ readonly env?: NodeJS.ProcessEnv;
72
+ /** The host, for a spec; defaults to this process's. */
73
+ readonly platform?: NodeJS.Platform;
74
+ readonly arch?: string;
75
+ }
76
+ /**
77
+ * The delegate this node loads, staging the pinned set under
78
+ * `<dataDir>/deps/edgetpu/lib/` when nothing is present yet. Never throws for
79
+ * a platform without a build — it answers `unavailable` with the reason; a
80
+ * download or checksum failure throws, naming the file. The line it logs says
81
+ * which delegate (and from where) the pool will load.
82
+ */
83
+ export declare function ensureEdgetpuDelegate(dataDir: string, logger: IScopedLogger, opts?: EnsureEdgetpuDelegateOptions): Promise<EdgetpuDelegateResult>;
package/dist/node.d.ts CHANGED
@@ -3,16 +3,18 @@ export { buildBinaryPath, downloadBinary, ensureBinary, findInPath, getPlatformI
3
3
  export { ensureFfmpeg } from './deps/ffmpeg-downloader.js';
4
4
  export type { OwnerOnlyDeps } from './util/owner-only-file.js';
5
5
  export { icaclsArgs, mkdirOwnerOnlySync, OwnerOnlyError, restrictDirToOwnerSync, restrictFileToOwnerSync, writeOwnerOnlyFileSync, } from './util/owner-only-file.js';
6
- export { FFMPEG_RELEASE, getFfmpegArtifact, getFfmpegDownloadUrl } from './deps/ffmpeg-artifacts.js';
6
+ export { FFMPEG_RELEASE, getFfmpegArtifact, getFfmpegDownloadUrl, } from './deps/ffmpeg-artifacts.js';
7
7
  export { probeFfmpegBinary, parseFfmpegCapabilities, type FfmpegBinaryCapabilities, } from './ffmpeg/binary-capabilities.js';
8
8
  export { ensurePython, getPythonDownloadUrl, installPythonPackages, installPythonRequirements, PYTHON_VERSION, } from './deps/python-downloader.js';
9
+ export { chooseEdgetpuDelegate, declaresEdgetpuDelegate, EDGETPU_DELEGATE_DIRECTIVE, EDGETPU_DELEGATE_RELEASE, EDGETPU_LIB_ENV, edgetpuDelegateFileName, edgetpuLibDir, ensureEdgetpuDelegate, getEdgetpuDelegateArtifact, LEGACY_EDGETPU_LIB, nodeDataRootFromEnv, type EdgetpuDelegateArtifact, type EdgetpuDelegateChoice, type EdgetpuDelegateLookup, type EdgetpuDelegateOrigin, type EdgetpuDelegateResult, type EdgetpuStagedFile, type EnsureEdgetpuDelegateOptions, } from './deps/edgetpu-delegate.js';
10
+ export { debDataMember, extractDebPayload, installLibraryFromDeb, readArMembers, } from './deps/deb-extract.js';
9
11
  export type { Fmp4FragmentChildArgs, Fmp4FragmentChildDeps, } from './ffmpeg/fmp4-fragment-child.js';
10
12
  export { Fmp4FragmentChild } from './ffmpeg/fmp4-fragment-child.js';
11
13
  export type { Fmp4CloseReason, Fmp4Packet, Fmp4PrebufferOptions, Fmp4PrebufferStats, Fmp4SubscribeInput, Fmp4SubscriberInfo, Fmp4Subscription, } from './ffmpeg/fmp4-fragment-plane.js';
12
14
  export { Fmp4FragmentPlane } from './ffmpeg/fmp4-fragment-plane.js';
13
15
  export type { FfmpegExit, FfmpegExitClass, FfmpegProcessOptions, FfmpegSpawnFn, SpawnedFfmpegProcess, } from './ffmpeg/process.js';
14
16
  export { defaultFfmpegSpawn, FfmpegProcess } from './ffmpeg/process.js';
15
- export type { FfmpegArgvSpawn, PinnedFfmpegSpawnAnswer } from './ffmpeg/pinned-spawn.js';
17
+ export type { FfmpegArgvSpawn, PinnedFfmpegSpawnAnswer, } from './ffmpeg/pinned-spawn.js';
16
18
  export { NO_PINNED_FFMPEG_DETAIL, pinnedFfmpegSpawner, resolvePinnedFfmpegSpawner, } from './ffmpeg/pinned-spawn.js';
17
19
  export type { ChildCostClaimHandle, ChildCostClaimInput, ProcStatReader, } from './process/child-cost-registry.js';
18
20
  export { ChildCostRegistry, NO_COST_CLAIM, nodeProcStatReader, parseProcCpuSeconds, parseProcRssBytes, readProcessCost, } from './process/child-cost-registry.js';
@@ -23,6 +25,6 @@ export { containsOrEquals, physicalRootOf } from './storage/physical-root.js';
23
25
  export { canonicalHash } from './utils/canonical-hash.js';
24
26
  export { customizationBlockName } from './composition/customization-name.js';
25
27
  export type { VerifyExpiringUrlInput } from './utils/expiring-url-signature.js';
26
- export { signExpiringUrl, verifyExpiringUrl } from './utils/expiring-url-signature.js';
28
+ export { signExpiringUrl, verifyExpiringUrl, } from './utils/expiring-url-signature.js';
27
29
  export type { DeviceExportShape, ExportDelta, ExportTargetEntry, } from './utils/export-reconciler.js';
28
30
  export { canonicalDeviceFingerprint, diffExportTargets, resolveExportFingerprint, } from './utils/export-reconciler.js';
package/dist/node.js CHANGED
@@ -178,8 +178,9 @@ function extractArchive(archivePath, format, destDir, overrides = {}) {
178
178
  }
179
179
  return;
180
180
  }
181
+ const flags = format === "tar.xz" ? "-xJf" : format === "tar" ? "-xf" : "-xzf";
181
182
  deps.exec(tar, [
182
- format === "tar.xz" ? "-xJf" : "-xzf",
183
+ flags,
183
184
  archivePath,
184
185
  "-C",
185
186
  destDir
@@ -873,6 +874,354 @@ async function installPythonRequirements(pythonPath, requirementsFile, logger) {
873
874
  } });
874
875
  }
875
876
  //#endregion
877
+ //#region src/deps/deb-extract.ts
878
+ /**
879
+ * Pull one shared library out of a Debian package without `dpkg-deb`.
880
+ *
881
+ * A `.deb` is an `ar` archive holding `debian-binary`, `control.tar.*` and
882
+ * `data.tar.*`; the payload's compression is the packager's choice. The two
883
+ * debs the Coral delegate needs show both live ones: feranick's libedgetpu
884
+ * ships `data.tar.xz`, Ubuntu's libusb (24.04 and every release since 21.10)
885
+ * ships `data.tar.zst` — which `dpkg-deb` older than 1.21.18 and a stock
886
+ * macOS `tar` without a `zstd` binary cannot open. So the ar container and a
887
+ * zstd/gzip payload are unpacked IN-PROCESS (`node:zlib` has zstd since Node
888
+ * 22.15, which the hub and the desktop's Electron both run); only an xz
889
+ * payload goes through `tar -xJf`, which every POSIX host has.
890
+ *
891
+ * POSIX only: the delegate is never staged on Windows (no build exists).
892
+ */
893
+ var AR_MAGIC = "!<arch>\n";
894
+ var AR_HEADER_BYTES = 60;
895
+ /** Every member of an `ar` archive, in order. Throws on anything that is not one. */
896
+ function readArMembers(archive) {
897
+ if (archive.subarray(0, 8).toString("latin1") !== AR_MAGIC) throw new Error("not an ar archive (a .deb starts with \"!<arch>\")");
898
+ const members = [];
899
+ let offset = 8;
900
+ while (offset + AR_HEADER_BYTES <= archive.length) {
901
+ const header = archive.subarray(offset, offset + AR_HEADER_BYTES).toString("latin1");
902
+ if (header.slice(58, 60) !== "`\n") throw new Error(`corrupt ar member header at byte ${offset}`);
903
+ const name = header.slice(0, 16).trim().replace(/\/$/, "");
904
+ const size = Number.parseInt(header.slice(48, 58).trim(), 10);
905
+ if (!Number.isInteger(size) || size < 0) throw new Error(`ar member "${name}" has no valid size`);
906
+ const start = offset + AR_HEADER_BYTES;
907
+ if (start + size > archive.length) throw new Error(`ar member "${name}" runs past the end of the archive`);
908
+ members.push({
909
+ name,
910
+ data: archive.subarray(start, start + size)
911
+ });
912
+ offset = start + size + size % 2;
913
+ }
914
+ return members;
915
+ }
916
+ /** The `data.tar.*` member of a `.deb`, with the compression its name declares. */
917
+ function debDataMember(deb) {
918
+ const member = readArMembers(deb).find((m) => m.name.startsWith("data.tar"));
919
+ if (member === void 0) throw new Error(".deb has no data.tar member");
920
+ const suffix = member.name.slice(8);
921
+ const compression = suffix === ".zst" ? "zst" : suffix === ".xz" ? "xz" : suffix === ".gz" ? "gz" : suffix === "" ? "none" : null;
922
+ if (compression === null) throw new Error(`.deb payload ${member.name} uses a compression this extractor does not read`);
923
+ return {
924
+ compression,
925
+ data: member.data
926
+ };
927
+ }
928
+ /**
929
+ * Unpack a `.deb`'s payload into `destDir` (the package's own tree: `usr/lib/…`).
930
+ * zstd and gzip are decompressed in-process; the plain tar and the xz payload
931
+ * are expanded by the host `tar`.
932
+ */
933
+ function extractDebPayload(deb, destDir, workDir, extract = extractArchive) {
934
+ const payload = debDataMember(deb);
935
+ (0, node_fs.mkdirSync)(destDir, { recursive: true });
936
+ (0, node_fs.mkdirSync)(workDir, { recursive: true });
937
+ if (payload.compression === "xz") {
938
+ const xzPath = (0, node_path.join)(workDir, "data.tar.xz");
939
+ (0, node_fs.writeFileSync)(xzPath, payload.data);
940
+ extract(xzPath, "tar.xz", destDir);
941
+ return;
942
+ }
943
+ const tar = payload.compression === "zst" ? (0, node_zlib.zstdDecompressSync)(payload.data) : payload.compression === "gz" ? (0, node_zlib.gunzipSync)(payload.data) : payload.data;
944
+ const tarPath = (0, node_path.join)(workDir, "data.tar");
945
+ (0, node_fs.writeFileSync)(tarPath, tar);
946
+ extract(tarPath, "tar", destDir);
947
+ }
948
+ /**
949
+ * The first entry named exactly `name` under `dir` — a regular file OR a
950
+ * symlink: a library's soname (`libusb-1.0.so.0`) is a link to the versioned
951
+ * file in every package, so a file-only search never finds it.
952
+ */
953
+ function findEntryByName(dir, name) {
954
+ for (const entry of (0, node_fs.readdirSync)(dir, { withFileTypes: true })) {
955
+ const full = (0, node_path.join)(dir, entry.name);
956
+ if ((entry.isFile() || entry.isSymbolicLink()) && entry.name === name) return full;
957
+ if (entry.isDirectory()) {
958
+ const nested = findEntryByName(full, name);
959
+ if (nested !== null) return nested;
960
+ }
961
+ }
962
+ return null;
963
+ }
964
+ /**
965
+ * Copy the library `memberName` out of the `.deb` at `debPath` to `targetPath`,
966
+ * dereferencing the soname link to the real file. Throws, naming the member,
967
+ * when the package does not carry it.
968
+ */
969
+ function installLibraryFromDeb(debPath, memberName, targetPath, workDir, extract = extractArchive) {
970
+ const tree = (0, node_path.join)(workDir, "tree");
971
+ try {
972
+ extractDebPayload((0, node_fs.readFileSync)(debPath), tree, workDir, extract);
973
+ const found = findEntryByName(tree, memberName);
974
+ if (found === null) throw new Error(`${memberName} not found inside ${debPath}`);
975
+ (0, node_fs.copyFileSync)(found, targetPath);
976
+ } finally {
977
+ (0, node_fs.rmSync)(workDir, {
978
+ recursive: true,
979
+ force: true
980
+ });
981
+ }
982
+ }
983
+ //#endregion
984
+ //#region src/deps/edgetpu-delegate.ts
985
+ /**
986
+ * The Coral Edge TPU delegate (libedgetpu) this node loads, and how the deps
987
+ * downloader stages it.
988
+ *
989
+ * The delegate is a native shared library, not a pip package, and its TF
990
+ * version MUST match the interpreter: ai-edge-litert 2.1.6 pairs with
991
+ * feranick's libedgetpu rebuilt against TF 2.17.1 (release
992
+ * `16.0TF2.17.1-1`); Google's stock 16.0 "grouper" build fails at invoke.
993
+ * Every artifact below was fetched and hashed on 2026-10-22 and inspected:
994
+ *
995
+ * - linux x64 / arm64 — the `libedgetpu1-std` deb for Ubuntu 24.04 (the same
996
+ * build `edgetpu-install.sh` installs on the hub image; it needs glibc
997
+ * 2.38), payload `data.tar.xz`. The std delegate links libusb even for a
998
+ * PCIe Coral, so Ubuntu 24.04's `libusb-1.0-0` deb is staged beside it —
999
+ * payload `data.tar.zst`, which is why `deb-extract.ts` reads zstd itself.
1000
+ * - darwin arm64 — the `MacOS_Silicon` zip, `throttled/` (the std clock, as on
1001
+ * Linux). `otool -L` shows no libusb: it is linked in, nothing else needed.
1002
+ * - darwin x64 — REFUSED: ai-edge-litert 2.1.6 publishes no macOS x86_64
1003
+ * wheel, and the Intel delegate links MacPorts' `/opt/local/lib/libusb`.
1004
+ * - win32 — REFUSED: no TF 2.17.1 libedgetpu exists for Windows; Google's old
1005
+ * Windows delegate is built for a TFLite ai-edge-litert 2.1.6 cannot load.
1006
+ *
1007
+ * Resolution order, mirrored by `edgetpu_device.edgetpu_library_path` in the
1008
+ * Python pool: the `CAMSTACK_EDGETPU_LIB` path override → the copy staged
1009
+ * under `<CAMSTACK_DATA>/deps/edgetpu/lib/` → the hub's historical
1010
+ * `/data/deps/edgetpu/lib/libedgetpu.so.1`. On the hub image
1011
+ * `CAMSTACK_DATA=/data`, so the staged path and the historical one are the
1012
+ * same file and nothing moves.
1013
+ */
1014
+ /** The path override (a library PATH, never a secret). */
1015
+ var EDGETPU_LIB_ENV = "CAMSTACK_EDGETPU_LIB";
1016
+ /** The pinned feranick release. Changing it is the delegate version change. */
1017
+ var EDGETPU_DELEGATE_RELEASE = "16.0TF2.17.1-1";
1018
+ /** Where the hub image's installer has always put the delegate. */
1019
+ var LEGACY_EDGETPU_LIB = "/data/deps/edgetpu/lib/libedgetpu.so.1";
1020
+ var FERANICK = `https://github.com/feranick/libedgetpu/releases/download/${EDGETPU_DELEGATE_RELEASE}`;
1021
+ function linuxSet(deb, debSha, debSize, libusbUrl, libusbSha, libusbSize) {
1022
+ return {
1023
+ supported: true,
1024
+ files: [{
1025
+ url: `${FERANICK}/${deb}`,
1026
+ sha256: debSha,
1027
+ sizeBytes: debSize,
1028
+ container: "deb",
1029
+ member: "libedgetpu.so.1",
1030
+ installAs: "libedgetpu.so.1"
1031
+ }, {
1032
+ url: libusbUrl,
1033
+ sha256: libusbSha,
1034
+ sizeBytes: libusbSize,
1035
+ container: "deb",
1036
+ member: "libusb-1.0.so.0",
1037
+ installAs: "libusb-1.0.so.0"
1038
+ }]
1039
+ };
1040
+ }
1041
+ /** The pinned delegate set for a platform and arch, or why there is none. */
1042
+ function getEdgetpuDelegateArtifact(platform, arch) {
1043
+ if (platform === "linux" && arch === "x64") return linuxSet("libedgetpu1-std_16.0tf2.17.1-1.ubuntu24.04_amd64.deb", "3cd98ae42d26a0ab601673d2162eb968921f751d3879ef960687637cad1cba96", 389600, "http://archive.ubuntu.com/ubuntu/pool/main/libu/libusb-1.0/libusb-1.0-0_1.0.27-1_amd64.deb", "d59e679a82ee2b724c556397e831e879936adbe8a007705872b65a5e1ec887b5", 54008);
1044
+ if (platform === "linux" && arch === "arm64") return linuxSet("libedgetpu1-std_16.0tf2.17.1-1.ubuntu24.04_arm64.deb", "6d53b6f6f9a19a6685a488e0c262aed2b11b6ff8c953f10e0aa14a16122f0d37", 338416, "http://ports.ubuntu.com/ubuntu-ports/pool/main/libu/libusb-1.0/libusb-1.0-0_1.0.27-1_arm64.deb", "c8c34ef4385b6be34fe94aabd4de06c7bbd5ae617e80e9306f412f4c979aa5e4", 54082);
1045
+ if (platform === "darwin" && arch === "arm64") return {
1046
+ supported: true,
1047
+ files: [{
1048
+ url: `${FERANICK}/libedgetpu-16.0-tf2.17.1-1_MacOS_Silicon.zip`,
1049
+ sha256: "5d57b4ff80bfe70d1d9306a3a8a83f52b38e8a151c35aecf68d807744eacf1f4",
1050
+ sizeBytes: 760105,
1051
+ container: "zip",
1052
+ member: "libedgetpu-16.0-tf2.17.1-1_MacOS_Silicon/throttled/darwin_arm64/libedgetpu.1.0.dylib",
1053
+ installAs: "libedgetpu.1.dylib"
1054
+ }]
1055
+ };
1056
+ if (platform === "darwin") return {
1057
+ supported: false,
1058
+ reason: "ai-edge-litert 2.1.6 publishes no macOS x86_64 wheel, and the Intel delegate links MacPorts libusb at /opt/local/lib"
1059
+ };
1060
+ if (platform === "win32") return {
1061
+ supported: false,
1062
+ reason: "no libedgetpu built against TF 2.17.1 exists for Windows (feranick ships Linux and macOS only)"
1063
+ };
1064
+ return {
1065
+ supported: false,
1066
+ reason: `no pinned libedgetpu build for ${platform}-${arch}`
1067
+ };
1068
+ }
1069
+ /** The delegate's file name on a platform (what the Python pool loads). */
1070
+ function edgetpuDelegateFileName(platform) {
1071
+ return platform === "darwin" ? "libedgetpu.1.dylib" : "libedgetpu.so.1";
1072
+ }
1073
+ /** `<dataDir>/deps/edgetpu/lib` — where the deps downloader stages the set. */
1074
+ function edgetpuLibDir(dataDir) {
1075
+ return (0, node_path.join)(dataDir, "deps", "edgetpu", "lib");
1076
+ }
1077
+ /**
1078
+ * The delegate this node loads, or `null` when none is present. An override
1079
+ * that names a missing file is NOT followed past: the operator said where it
1080
+ * is, and a silent fallback would load a different build than they chose.
1081
+ */
1082
+ function chooseEdgetpuDelegate(lookup) {
1083
+ const override = lookup.override?.trim();
1084
+ if (override !== void 0 && override.length > 0) return lookup.exists(override) ? {
1085
+ path: override,
1086
+ origin: "override"
1087
+ } : null;
1088
+ if (lookup.dataDir !== void 0 && lookup.dataDir.length > 0) {
1089
+ const staged = (0, node_path.join)(edgetpuLibDir(lookup.dataDir), edgetpuDelegateFileName(lookup.platform));
1090
+ if (lookup.exists(staged)) return {
1091
+ path: staged,
1092
+ origin: "staged"
1093
+ };
1094
+ }
1095
+ if (lookup.exists("/data/deps/edgetpu/lib/libedgetpu.so.1")) return {
1096
+ path: LEGACY_EDGETPU_LIB,
1097
+ origin: "legacy"
1098
+ };
1099
+ return null;
1100
+ }
1101
+ /** The node data root the way every node process names it. */
1102
+ function nodeDataRootFromEnv(env) {
1103
+ return env["CAMSTACK_DATA"] ?? env["CAMSTACK_DATA_DIR"];
1104
+ }
1105
+ /** Under 1 MB each: held whole, written once, hashed from disk before use. */
1106
+ async function download(url, target) {
1107
+ const response = await fetch(url, { redirect: "follow" });
1108
+ if (!response.ok) throw new Error(`download failed: ${response.status} ${response.statusText} (${url})`);
1109
+ (0, node_fs.writeFileSync)(target, Buffer.from(await response.arrayBuffer()));
1110
+ }
1111
+ async function stageFile(file, libDir, workRoot) {
1112
+ const target = (0, node_path.join)(libDir, file.installAs);
1113
+ if ((0, node_fs.existsSync)(target)) return;
1114
+ const work = (0, node_path.join)(workRoot, file.installAs);
1115
+ (0, node_fs.rmSync)(work, {
1116
+ recursive: true,
1117
+ force: true
1118
+ });
1119
+ (0, node_fs.mkdirSync)(work, { recursive: true });
1120
+ const container = (0, node_path.join)(work, `download.${file.container}`);
1121
+ const partial = `${target}.partial`;
1122
+ try {
1123
+ await download(file.url, container);
1124
+ verifySha256(container, file.sha256, file.installAs, file.url);
1125
+ if (file.container === "deb") installLibraryFromDeb(container, file.member, partial, (0, node_path.join)(work, "deb"));
1126
+ else {
1127
+ const tree = (0, node_path.join)(work, "tree");
1128
+ (0, node_fs.mkdirSync)(tree, { recursive: true });
1129
+ extractArchive(container, "zip", tree);
1130
+ const inner = (0, node_path.join)(tree, file.member);
1131
+ const found = (0, node_fs.existsSync)(inner) ? inner : findEntryByName(tree, file.member.split("/").pop() ?? file.member);
1132
+ if (found === null) throw new Error(`${file.member} not found inside ${file.url}`);
1133
+ (0, node_fs.copyFileSync)(found, partial);
1134
+ }
1135
+ (0, node_fs.renameSync)(partial, target);
1136
+ } finally {
1137
+ (0, node_fs.rmSync)(work, {
1138
+ recursive: true,
1139
+ force: true
1140
+ });
1141
+ (0, node_fs.rmSync)(partial, { force: true });
1142
+ }
1143
+ }
1144
+ /**
1145
+ * A requirements file names the native library its packages cannot run
1146
+ * without on this line; `installPythonRequirements` stages it after pip.
1147
+ * (`ai-edge-litert` imports without the delegate and is useless without it.)
1148
+ */
1149
+ var EDGETPU_DELEGATE_DIRECTIVE = "# camstack-native-dep: edgetpu-delegate";
1150
+ function declaresEdgetpuDelegate(requirementsText) {
1151
+ return requirementsText.split(/\r?\n/).some((line) => line.trim() === EDGETPU_DELEGATE_DIRECTIVE);
1152
+ }
1153
+ /**
1154
+ * The delegate this node loads, staging the pinned set under
1155
+ * `<dataDir>/deps/edgetpu/lib/` when nothing is present yet. Never throws for
1156
+ * a platform without a build — it answers `unavailable` with the reason; a
1157
+ * download or checksum failure throws, naming the file. The line it logs says
1158
+ * which delegate (and from where) the pool will load.
1159
+ */
1160
+ async function ensureEdgetpuDelegate(dataDir, logger, opts = {}) {
1161
+ const env = opts.env ?? process.env;
1162
+ const platform = opts.platform ?? process.platform;
1163
+ const arch = opts.arch ?? process.arch;
1164
+ const override = env[EDGETPU_LIB_ENV]?.trim();
1165
+ const present = chooseEdgetpuDelegate({
1166
+ platform,
1167
+ dataDir,
1168
+ override,
1169
+ exists: node_fs.existsSync
1170
+ });
1171
+ if (present !== null) {
1172
+ logger.info("Coral delegate resolved", { meta: {
1173
+ path: present.path,
1174
+ origin: present.origin
1175
+ } });
1176
+ return {
1177
+ kind: "ready",
1178
+ path: present.path,
1179
+ origin: present.origin
1180
+ };
1181
+ }
1182
+ if (override !== void 0 && override.length > 0) {
1183
+ const reason = `${EDGETPU_LIB_ENV} names a file that does not exist: ${override}`;
1184
+ logger.error("Coral delegate not staged", { meta: { reason } });
1185
+ return {
1186
+ kind: "unavailable",
1187
+ reason
1188
+ };
1189
+ }
1190
+ const artifact = getEdgetpuDelegateArtifact(platform, arch);
1191
+ if (!artifact.supported) {
1192
+ logger.warn("Coral delegate not staged", { meta: { reason: artifact.reason } });
1193
+ return {
1194
+ kind: "unavailable",
1195
+ reason: artifact.reason
1196
+ };
1197
+ }
1198
+ const libDir = edgetpuLibDir(dataDir);
1199
+ (0, node_fs.mkdirSync)(libDir, { recursive: true });
1200
+ logger.info("Coral delegate is not on this node yet — downloading the pinned set", { meta: {
1201
+ release: EDGETPU_DELEGATE_RELEASE,
1202
+ files: artifact.files.map((f) => f.installAs).join(",")
1203
+ } });
1204
+ const workRoot = (0, node_path.join)(dataDir, "deps", ".edgetpu-staging");
1205
+ try {
1206
+ for (const file of artifact.files) await stageFile(file, libDir, workRoot);
1207
+ } finally {
1208
+ (0, node_fs.rmSync)(workRoot, {
1209
+ recursive: true,
1210
+ force: true
1211
+ });
1212
+ }
1213
+ const path = (0, node_path.join)(libDir, edgetpuDelegateFileName(platform));
1214
+ logger.info("Coral delegate resolved", { meta: {
1215
+ path,
1216
+ origin: "downloaded"
1217
+ } });
1218
+ return {
1219
+ kind: "ready",
1220
+ path,
1221
+ origin: "downloaded"
1222
+ };
1223
+ }
1224
+ //#endregion
876
1225
  //#region src/ffmpeg/fmp4-fragment-child.ts
877
1226
  var DEFAULT_FIRST_UNIT_TIMEOUT_MS = 12e3;
878
1227
  /** Heartbeat cadence — ~2 minutes of 4 s fragments. */
@@ -2274,11 +2623,15 @@ function resolveExportFingerprint(input) {
2274
2623
  }
2275
2624
  //#endregion
2276
2625
  exports.ChildCostRegistry = ChildCostRegistry;
2626
+ exports.EDGETPU_DELEGATE_DIRECTIVE = EDGETPU_DELEGATE_DIRECTIVE;
2627
+ exports.EDGETPU_DELEGATE_RELEASE = EDGETPU_DELEGATE_RELEASE;
2628
+ exports.EDGETPU_LIB_ENV = EDGETPU_LIB_ENV;
2277
2629
  exports.FFMPEG_RELEASE = require_composition.FFMPEG_RELEASE;
2278
2630
  exports.FfmpegProcess = FfmpegProcess;
2279
2631
  exports.FilesystemStorageProvider = FilesystemStorageProvider;
2280
2632
  exports.Fmp4FragmentChild = Fmp4FragmentChild;
2281
2633
  exports.Fmp4FragmentPlane = Fmp4FragmentPlane;
2634
+ exports.LEGACY_EDGETPU_LIB = LEGACY_EDGETPU_LIB;
2282
2635
  exports.NO_COST_CLAIM = NO_COST_CLAIM;
2283
2636
  exports.NO_PINNED_FFMPEG_DETAIL = NO_PINNED_FFMPEG_DETAIL;
2284
2637
  exports.OwnerOnlyError = OwnerOnlyError;
@@ -2286,23 +2639,33 @@ exports.PYTHON_VERSION = PYTHON_VERSION;
2286
2639
  exports.buildBinaryPath = buildBinaryPath;
2287
2640
  exports.canonicalDeviceFingerprint = canonicalDeviceFingerprint;
2288
2641
  exports.canonicalHash = require_composition.canonicalHash;
2642
+ exports.chooseEdgetpuDelegate = chooseEdgetpuDelegate;
2289
2643
  exports.containsOrEquals = containsOrEquals;
2290
2644
  exports.customizationBlockName = customizationBlockName;
2645
+ exports.debDataMember = debDataMember;
2646
+ exports.declaresEdgetpuDelegate = declaresEdgetpuDelegate;
2291
2647
  exports.defaultFfmpegSpawn = defaultFfmpegSpawn;
2292
2648
  exports.diffExportTargets = diffExportTargets;
2293
2649
  exports.downloadBinary = downloadBinary;
2650
+ exports.edgetpuDelegateFileName = edgetpuDelegateFileName;
2651
+ exports.edgetpuLibDir = edgetpuLibDir;
2294
2652
  exports.ensureBinary = ensureBinary;
2653
+ exports.ensureEdgetpuDelegate = ensureEdgetpuDelegate;
2295
2654
  exports.ensureFfmpeg = ensureFfmpeg;
2296
2655
  exports.ensurePython = ensurePython;
2656
+ exports.extractDebPayload = extractDebPayload;
2297
2657
  exports.findInPath = findInPath;
2658
+ exports.getEdgetpuDelegateArtifact = getEdgetpuDelegateArtifact;
2298
2659
  exports.getFfmpegArtifact = require_composition.getFfmpegArtifact;
2299
2660
  exports.getFfmpegDownloadUrl = require_composition.getFfmpegDownloadUrl;
2300
2661
  exports.getPlatformInfo = getPlatformInfo;
2301
2662
  exports.getPythonDownloadUrl = getPythonDownloadUrl;
2302
2663
  exports.icaclsArgs = icaclsArgs;
2664
+ exports.installLibraryFromDeb = installLibraryFromDeb;
2303
2665
  exports.installPythonPackages = installPythonPackages;
2304
2666
  exports.installPythonRequirements = installPythonRequirements;
2305
2667
  exports.mkdirOwnerOnlySync = mkdirOwnerOnlySync;
2668
+ exports.nodeDataRootFromEnv = nodeDataRootFromEnv;
2306
2669
  exports.nodeProcStatReader = nodeProcStatReader;
2307
2670
  exports.parseFfmpegCapabilities = parseFfmpegCapabilities;
2308
2671
  exports.parseProcCpuSeconds = parseProcCpuSeconds;
@@ -2310,6 +2673,7 @@ exports.parseProcRssBytes = parseProcRssBytes;
2310
2673
  exports.physicalRootOf = physicalRootOf;
2311
2674
  exports.pinnedFfmpegSpawner = pinnedFfmpegSpawner;
2312
2675
  exports.probeFfmpegBinary = probeFfmpegBinary;
2676
+ exports.readArMembers = readArMembers;
2313
2677
  exports.readProcessCost = readProcessCost;
2314
2678
  exports.resolveExportFingerprint = resolveExportFingerprint;
2315
2679
  exports.resolvePinnedFfmpegSpawner = resolvePinnedFfmpegSpawner;
package/dist/node.mjs CHANGED
@@ -2,13 +2,13 @@ import { At as Fmp4BoxSplitter, Ct as FFMPEG_PATH_ENV, Dt as getFfmpegArtifact,
2
2
  import { t as errMsg } from "./err-msg-BXKzRBwg.mjs";
3
3
  import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
4
4
  import * as fs from "node:fs";
5
- import { chmodSync, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
5
+ import { chmodSync, copyFileSync, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
6
6
  import * as path$1 from "node:path";
7
7
  import path, { basename, dirname, join, resolve, sep } from "node:path";
8
8
  import { pipeline } from "node:stream/promises";
9
9
  import { Readable } from "node:stream";
10
10
  import { execFile, execFileSync, spawn } from "node:child_process";
11
- import { crc32, inflateRawSync } from "node:zlib";
11
+ import { crc32, gunzipSync, inflateRawSync, zstdDecompressSync } from "node:zlib";
12
12
  import { userInfo } from "node:os";
13
13
  import { readFile } from "node:fs/promises";
14
14
  //#region src/deps/zip-extract.ts
@@ -155,8 +155,9 @@ function extractArchive(archivePath, format, destDir, overrides = {}) {
155
155
  }
156
156
  return;
157
157
  }
158
+ const flags = format === "tar.xz" ? "-xJf" : format === "tar" ? "-xf" : "-xzf";
158
159
  deps.exec(tar, [
159
- format === "tar.xz" ? "-xJf" : "-xzf",
160
+ flags,
160
161
  archivePath,
161
162
  "-C",
162
163
  destDir
@@ -850,6 +851,354 @@ async function installPythonRequirements(pythonPath, requirementsFile, logger) {
850
851
  } });
851
852
  }
852
853
  //#endregion
854
+ //#region src/deps/deb-extract.ts
855
+ /**
856
+ * Pull one shared library out of a Debian package without `dpkg-deb`.
857
+ *
858
+ * A `.deb` is an `ar` archive holding `debian-binary`, `control.tar.*` and
859
+ * `data.tar.*`; the payload's compression is the packager's choice. The two
860
+ * debs the Coral delegate needs show both live ones: feranick's libedgetpu
861
+ * ships `data.tar.xz`, Ubuntu's libusb (24.04 and every release since 21.10)
862
+ * ships `data.tar.zst` — which `dpkg-deb` older than 1.21.18 and a stock
863
+ * macOS `tar` without a `zstd` binary cannot open. So the ar container and a
864
+ * zstd/gzip payload are unpacked IN-PROCESS (`node:zlib` has zstd since Node
865
+ * 22.15, which the hub and the desktop's Electron both run); only an xz
866
+ * payload goes through `tar -xJf`, which every POSIX host has.
867
+ *
868
+ * POSIX only: the delegate is never staged on Windows (no build exists).
869
+ */
870
+ var AR_MAGIC = "!<arch>\n";
871
+ var AR_HEADER_BYTES = 60;
872
+ /** Every member of an `ar` archive, in order. Throws on anything that is not one. */
873
+ function readArMembers(archive) {
874
+ if (archive.subarray(0, 8).toString("latin1") !== AR_MAGIC) throw new Error("not an ar archive (a .deb starts with \"!<arch>\")");
875
+ const members = [];
876
+ let offset = 8;
877
+ while (offset + AR_HEADER_BYTES <= archive.length) {
878
+ const header = archive.subarray(offset, offset + AR_HEADER_BYTES).toString("latin1");
879
+ if (header.slice(58, 60) !== "`\n") throw new Error(`corrupt ar member header at byte ${offset}`);
880
+ const name = header.slice(0, 16).trim().replace(/\/$/, "");
881
+ const size = Number.parseInt(header.slice(48, 58).trim(), 10);
882
+ if (!Number.isInteger(size) || size < 0) throw new Error(`ar member "${name}" has no valid size`);
883
+ const start = offset + AR_HEADER_BYTES;
884
+ if (start + size > archive.length) throw new Error(`ar member "${name}" runs past the end of the archive`);
885
+ members.push({
886
+ name,
887
+ data: archive.subarray(start, start + size)
888
+ });
889
+ offset = start + size + size % 2;
890
+ }
891
+ return members;
892
+ }
893
+ /** The `data.tar.*` member of a `.deb`, with the compression its name declares. */
894
+ function debDataMember(deb) {
895
+ const member = readArMembers(deb).find((m) => m.name.startsWith("data.tar"));
896
+ if (member === void 0) throw new Error(".deb has no data.tar member");
897
+ const suffix = member.name.slice(8);
898
+ const compression = suffix === ".zst" ? "zst" : suffix === ".xz" ? "xz" : suffix === ".gz" ? "gz" : suffix === "" ? "none" : null;
899
+ if (compression === null) throw new Error(`.deb payload ${member.name} uses a compression this extractor does not read`);
900
+ return {
901
+ compression,
902
+ data: member.data
903
+ };
904
+ }
905
+ /**
906
+ * Unpack a `.deb`'s payload into `destDir` (the package's own tree: `usr/lib/…`).
907
+ * zstd and gzip are decompressed in-process; the plain tar and the xz payload
908
+ * are expanded by the host `tar`.
909
+ */
910
+ function extractDebPayload(deb, destDir, workDir, extract = extractArchive) {
911
+ const payload = debDataMember(deb);
912
+ mkdirSync(destDir, { recursive: true });
913
+ mkdirSync(workDir, { recursive: true });
914
+ if (payload.compression === "xz") {
915
+ const xzPath = join(workDir, "data.tar.xz");
916
+ writeFileSync(xzPath, payload.data);
917
+ extract(xzPath, "tar.xz", destDir);
918
+ return;
919
+ }
920
+ const tar = payload.compression === "zst" ? zstdDecompressSync(payload.data) : payload.compression === "gz" ? gunzipSync(payload.data) : payload.data;
921
+ const tarPath = join(workDir, "data.tar");
922
+ writeFileSync(tarPath, tar);
923
+ extract(tarPath, "tar", destDir);
924
+ }
925
+ /**
926
+ * The first entry named exactly `name` under `dir` — a regular file OR a
927
+ * symlink: a library's soname (`libusb-1.0.so.0`) is a link to the versioned
928
+ * file in every package, so a file-only search never finds it.
929
+ */
930
+ function findEntryByName(dir, name) {
931
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
932
+ const full = join(dir, entry.name);
933
+ if ((entry.isFile() || entry.isSymbolicLink()) && entry.name === name) return full;
934
+ if (entry.isDirectory()) {
935
+ const nested = findEntryByName(full, name);
936
+ if (nested !== null) return nested;
937
+ }
938
+ }
939
+ return null;
940
+ }
941
+ /**
942
+ * Copy the library `memberName` out of the `.deb` at `debPath` to `targetPath`,
943
+ * dereferencing the soname link to the real file. Throws, naming the member,
944
+ * when the package does not carry it.
945
+ */
946
+ function installLibraryFromDeb(debPath, memberName, targetPath, workDir, extract = extractArchive) {
947
+ const tree = join(workDir, "tree");
948
+ try {
949
+ extractDebPayload(readFileSync(debPath), tree, workDir, extract);
950
+ const found = findEntryByName(tree, memberName);
951
+ if (found === null) throw new Error(`${memberName} not found inside ${debPath}`);
952
+ copyFileSync(found, targetPath);
953
+ } finally {
954
+ rmSync(workDir, {
955
+ recursive: true,
956
+ force: true
957
+ });
958
+ }
959
+ }
960
+ //#endregion
961
+ //#region src/deps/edgetpu-delegate.ts
962
+ /**
963
+ * The Coral Edge TPU delegate (libedgetpu) this node loads, and how the deps
964
+ * downloader stages it.
965
+ *
966
+ * The delegate is a native shared library, not a pip package, and its TF
967
+ * version MUST match the interpreter: ai-edge-litert 2.1.6 pairs with
968
+ * feranick's libedgetpu rebuilt against TF 2.17.1 (release
969
+ * `16.0TF2.17.1-1`); Google's stock 16.0 "grouper" build fails at invoke.
970
+ * Every artifact below was fetched and hashed on 2026-10-22 and inspected:
971
+ *
972
+ * - linux x64 / arm64 — the `libedgetpu1-std` deb for Ubuntu 24.04 (the same
973
+ * build `edgetpu-install.sh` installs on the hub image; it needs glibc
974
+ * 2.38), payload `data.tar.xz`. The std delegate links libusb even for a
975
+ * PCIe Coral, so Ubuntu 24.04's `libusb-1.0-0` deb is staged beside it —
976
+ * payload `data.tar.zst`, which is why `deb-extract.ts` reads zstd itself.
977
+ * - darwin arm64 — the `MacOS_Silicon` zip, `throttled/` (the std clock, as on
978
+ * Linux). `otool -L` shows no libusb: it is linked in, nothing else needed.
979
+ * - darwin x64 — REFUSED: ai-edge-litert 2.1.6 publishes no macOS x86_64
980
+ * wheel, and the Intel delegate links MacPorts' `/opt/local/lib/libusb`.
981
+ * - win32 — REFUSED: no TF 2.17.1 libedgetpu exists for Windows; Google's old
982
+ * Windows delegate is built for a TFLite ai-edge-litert 2.1.6 cannot load.
983
+ *
984
+ * Resolution order, mirrored by `edgetpu_device.edgetpu_library_path` in the
985
+ * Python pool: the `CAMSTACK_EDGETPU_LIB` path override → the copy staged
986
+ * under `<CAMSTACK_DATA>/deps/edgetpu/lib/` → the hub's historical
987
+ * `/data/deps/edgetpu/lib/libedgetpu.so.1`. On the hub image
988
+ * `CAMSTACK_DATA=/data`, so the staged path and the historical one are the
989
+ * same file and nothing moves.
990
+ */
991
+ /** The path override (a library PATH, never a secret). */
992
+ var EDGETPU_LIB_ENV = "CAMSTACK_EDGETPU_LIB";
993
+ /** The pinned feranick release. Changing it is the delegate version change. */
994
+ var EDGETPU_DELEGATE_RELEASE = "16.0TF2.17.1-1";
995
+ /** Where the hub image's installer has always put the delegate. */
996
+ var LEGACY_EDGETPU_LIB = "/data/deps/edgetpu/lib/libedgetpu.so.1";
997
+ var FERANICK = `https://github.com/feranick/libedgetpu/releases/download/${EDGETPU_DELEGATE_RELEASE}`;
998
+ function linuxSet(deb, debSha, debSize, libusbUrl, libusbSha, libusbSize) {
999
+ return {
1000
+ supported: true,
1001
+ files: [{
1002
+ url: `${FERANICK}/${deb}`,
1003
+ sha256: debSha,
1004
+ sizeBytes: debSize,
1005
+ container: "deb",
1006
+ member: "libedgetpu.so.1",
1007
+ installAs: "libedgetpu.so.1"
1008
+ }, {
1009
+ url: libusbUrl,
1010
+ sha256: libusbSha,
1011
+ sizeBytes: libusbSize,
1012
+ container: "deb",
1013
+ member: "libusb-1.0.so.0",
1014
+ installAs: "libusb-1.0.so.0"
1015
+ }]
1016
+ };
1017
+ }
1018
+ /** The pinned delegate set for a platform and arch, or why there is none. */
1019
+ function getEdgetpuDelegateArtifact(platform, arch) {
1020
+ if (platform === "linux" && arch === "x64") return linuxSet("libedgetpu1-std_16.0tf2.17.1-1.ubuntu24.04_amd64.deb", "3cd98ae42d26a0ab601673d2162eb968921f751d3879ef960687637cad1cba96", 389600, "http://archive.ubuntu.com/ubuntu/pool/main/libu/libusb-1.0/libusb-1.0-0_1.0.27-1_amd64.deb", "d59e679a82ee2b724c556397e831e879936adbe8a007705872b65a5e1ec887b5", 54008);
1021
+ if (platform === "linux" && arch === "arm64") return linuxSet("libedgetpu1-std_16.0tf2.17.1-1.ubuntu24.04_arm64.deb", "6d53b6f6f9a19a6685a488e0c262aed2b11b6ff8c953f10e0aa14a16122f0d37", 338416, "http://ports.ubuntu.com/ubuntu-ports/pool/main/libu/libusb-1.0/libusb-1.0-0_1.0.27-1_arm64.deb", "c8c34ef4385b6be34fe94aabd4de06c7bbd5ae617e80e9306f412f4c979aa5e4", 54082);
1022
+ if (platform === "darwin" && arch === "arm64") return {
1023
+ supported: true,
1024
+ files: [{
1025
+ url: `${FERANICK}/libedgetpu-16.0-tf2.17.1-1_MacOS_Silicon.zip`,
1026
+ sha256: "5d57b4ff80bfe70d1d9306a3a8a83f52b38e8a151c35aecf68d807744eacf1f4",
1027
+ sizeBytes: 760105,
1028
+ container: "zip",
1029
+ member: "libedgetpu-16.0-tf2.17.1-1_MacOS_Silicon/throttled/darwin_arm64/libedgetpu.1.0.dylib",
1030
+ installAs: "libedgetpu.1.dylib"
1031
+ }]
1032
+ };
1033
+ if (platform === "darwin") return {
1034
+ supported: false,
1035
+ reason: "ai-edge-litert 2.1.6 publishes no macOS x86_64 wheel, and the Intel delegate links MacPorts libusb at /opt/local/lib"
1036
+ };
1037
+ if (platform === "win32") return {
1038
+ supported: false,
1039
+ reason: "no libedgetpu built against TF 2.17.1 exists for Windows (feranick ships Linux and macOS only)"
1040
+ };
1041
+ return {
1042
+ supported: false,
1043
+ reason: `no pinned libedgetpu build for ${platform}-${arch}`
1044
+ };
1045
+ }
1046
+ /** The delegate's file name on a platform (what the Python pool loads). */
1047
+ function edgetpuDelegateFileName(platform) {
1048
+ return platform === "darwin" ? "libedgetpu.1.dylib" : "libedgetpu.so.1";
1049
+ }
1050
+ /** `<dataDir>/deps/edgetpu/lib` — where the deps downloader stages the set. */
1051
+ function edgetpuLibDir(dataDir) {
1052
+ return join(dataDir, "deps", "edgetpu", "lib");
1053
+ }
1054
+ /**
1055
+ * The delegate this node loads, or `null` when none is present. An override
1056
+ * that names a missing file is NOT followed past: the operator said where it
1057
+ * is, and a silent fallback would load a different build than they chose.
1058
+ */
1059
+ function chooseEdgetpuDelegate(lookup) {
1060
+ const override = lookup.override?.trim();
1061
+ if (override !== void 0 && override.length > 0) return lookup.exists(override) ? {
1062
+ path: override,
1063
+ origin: "override"
1064
+ } : null;
1065
+ if (lookup.dataDir !== void 0 && lookup.dataDir.length > 0) {
1066
+ const staged = join(edgetpuLibDir(lookup.dataDir), edgetpuDelegateFileName(lookup.platform));
1067
+ if (lookup.exists(staged)) return {
1068
+ path: staged,
1069
+ origin: "staged"
1070
+ };
1071
+ }
1072
+ if (lookup.exists("/data/deps/edgetpu/lib/libedgetpu.so.1")) return {
1073
+ path: LEGACY_EDGETPU_LIB,
1074
+ origin: "legacy"
1075
+ };
1076
+ return null;
1077
+ }
1078
+ /** The node data root the way every node process names it. */
1079
+ function nodeDataRootFromEnv(env) {
1080
+ return env["CAMSTACK_DATA"] ?? env["CAMSTACK_DATA_DIR"];
1081
+ }
1082
+ /** Under 1 MB each: held whole, written once, hashed from disk before use. */
1083
+ async function download(url, target) {
1084
+ const response = await fetch(url, { redirect: "follow" });
1085
+ if (!response.ok) throw new Error(`download failed: ${response.status} ${response.statusText} (${url})`);
1086
+ writeFileSync(target, Buffer.from(await response.arrayBuffer()));
1087
+ }
1088
+ async function stageFile(file, libDir, workRoot) {
1089
+ const target = join(libDir, file.installAs);
1090
+ if (existsSync(target)) return;
1091
+ const work = join(workRoot, file.installAs);
1092
+ rmSync(work, {
1093
+ recursive: true,
1094
+ force: true
1095
+ });
1096
+ mkdirSync(work, { recursive: true });
1097
+ const container = join(work, `download.${file.container}`);
1098
+ const partial = `${target}.partial`;
1099
+ try {
1100
+ await download(file.url, container);
1101
+ verifySha256(container, file.sha256, file.installAs, file.url);
1102
+ if (file.container === "deb") installLibraryFromDeb(container, file.member, partial, join(work, "deb"));
1103
+ else {
1104
+ const tree = join(work, "tree");
1105
+ mkdirSync(tree, { recursive: true });
1106
+ extractArchive(container, "zip", tree);
1107
+ const inner = join(tree, file.member);
1108
+ const found = existsSync(inner) ? inner : findEntryByName(tree, file.member.split("/").pop() ?? file.member);
1109
+ if (found === null) throw new Error(`${file.member} not found inside ${file.url}`);
1110
+ copyFileSync(found, partial);
1111
+ }
1112
+ renameSync(partial, target);
1113
+ } finally {
1114
+ rmSync(work, {
1115
+ recursive: true,
1116
+ force: true
1117
+ });
1118
+ rmSync(partial, { force: true });
1119
+ }
1120
+ }
1121
+ /**
1122
+ * A requirements file names the native library its packages cannot run
1123
+ * without on this line; `installPythonRequirements` stages it after pip.
1124
+ * (`ai-edge-litert` imports without the delegate and is useless without it.)
1125
+ */
1126
+ var EDGETPU_DELEGATE_DIRECTIVE = "# camstack-native-dep: edgetpu-delegate";
1127
+ function declaresEdgetpuDelegate(requirementsText) {
1128
+ return requirementsText.split(/\r?\n/).some((line) => line.trim() === EDGETPU_DELEGATE_DIRECTIVE);
1129
+ }
1130
+ /**
1131
+ * The delegate this node loads, staging the pinned set under
1132
+ * `<dataDir>/deps/edgetpu/lib/` when nothing is present yet. Never throws for
1133
+ * a platform without a build — it answers `unavailable` with the reason; a
1134
+ * download or checksum failure throws, naming the file. The line it logs says
1135
+ * which delegate (and from where) the pool will load.
1136
+ */
1137
+ async function ensureEdgetpuDelegate(dataDir, logger, opts = {}) {
1138
+ const env = opts.env ?? process.env;
1139
+ const platform = opts.platform ?? process.platform;
1140
+ const arch = opts.arch ?? process.arch;
1141
+ const override = env[EDGETPU_LIB_ENV]?.trim();
1142
+ const present = chooseEdgetpuDelegate({
1143
+ platform,
1144
+ dataDir,
1145
+ override,
1146
+ exists: existsSync
1147
+ });
1148
+ if (present !== null) {
1149
+ logger.info("Coral delegate resolved", { meta: {
1150
+ path: present.path,
1151
+ origin: present.origin
1152
+ } });
1153
+ return {
1154
+ kind: "ready",
1155
+ path: present.path,
1156
+ origin: present.origin
1157
+ };
1158
+ }
1159
+ if (override !== void 0 && override.length > 0) {
1160
+ const reason = `${EDGETPU_LIB_ENV} names a file that does not exist: ${override}`;
1161
+ logger.error("Coral delegate not staged", { meta: { reason } });
1162
+ return {
1163
+ kind: "unavailable",
1164
+ reason
1165
+ };
1166
+ }
1167
+ const artifact = getEdgetpuDelegateArtifact(platform, arch);
1168
+ if (!artifact.supported) {
1169
+ logger.warn("Coral delegate not staged", { meta: { reason: artifact.reason } });
1170
+ return {
1171
+ kind: "unavailable",
1172
+ reason: artifact.reason
1173
+ };
1174
+ }
1175
+ const libDir = edgetpuLibDir(dataDir);
1176
+ mkdirSync(libDir, { recursive: true });
1177
+ logger.info("Coral delegate is not on this node yet — downloading the pinned set", { meta: {
1178
+ release: EDGETPU_DELEGATE_RELEASE,
1179
+ files: artifact.files.map((f) => f.installAs).join(",")
1180
+ } });
1181
+ const workRoot = join(dataDir, "deps", ".edgetpu-staging");
1182
+ try {
1183
+ for (const file of artifact.files) await stageFile(file, libDir, workRoot);
1184
+ } finally {
1185
+ rmSync(workRoot, {
1186
+ recursive: true,
1187
+ force: true
1188
+ });
1189
+ }
1190
+ const path = join(libDir, edgetpuDelegateFileName(platform));
1191
+ logger.info("Coral delegate resolved", { meta: {
1192
+ path,
1193
+ origin: "downloaded"
1194
+ } });
1195
+ return {
1196
+ kind: "ready",
1197
+ path,
1198
+ origin: "downloaded"
1199
+ };
1200
+ }
1201
+ //#endregion
853
1202
  //#region src/ffmpeg/fmp4-fragment-child.ts
854
1203
  var DEFAULT_FIRST_UNIT_TIMEOUT_MS = 12e3;
855
1204
  /** Heartbeat cadence — ~2 minutes of 4 s fragments. */
@@ -2250,4 +2599,4 @@ function resolveExportFingerprint(input) {
2250
2599
  return input.persisted ?? input.fresh;
2251
2600
  }
2252
2601
  //#endregion
2253
- export { ChildCostRegistry, FFMPEG_RELEASE, FfmpegProcess, FilesystemStorageProvider, Fmp4FragmentChild, Fmp4FragmentPlane, NO_COST_CLAIM, NO_PINNED_FFMPEG_DETAIL, OwnerOnlyError, PYTHON_VERSION, buildBinaryPath, canonicalDeviceFingerprint, canonicalHash, containsOrEquals, customizationBlockName, defaultFfmpegSpawn, diffExportTargets, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegArtifact, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, icaclsArgs, installPythonPackages, installPythonRequirements, mkdirOwnerOnlySync, nodeProcStatReader, parseFfmpegCapabilities, parseProcCpuSeconds, parseProcRssBytes, physicalRootOf, pinnedFfmpegSpawner, probeFfmpegBinary, readProcessCost, resolveExportFingerprint, resolvePinnedFfmpegSpawner, restrictDirToOwnerSync, restrictFileToOwnerSync, signExpiringUrl, verifyExpiringUrl, writeFileAtomic, writeOwnerOnlyFileSync };
2602
+ export { ChildCostRegistry, EDGETPU_DELEGATE_DIRECTIVE, EDGETPU_DELEGATE_RELEASE, EDGETPU_LIB_ENV, FFMPEG_RELEASE, FfmpegProcess, FilesystemStorageProvider, Fmp4FragmentChild, Fmp4FragmentPlane, LEGACY_EDGETPU_LIB, NO_COST_CLAIM, NO_PINNED_FFMPEG_DETAIL, OwnerOnlyError, PYTHON_VERSION, buildBinaryPath, canonicalDeviceFingerprint, canonicalHash, chooseEdgetpuDelegate, containsOrEquals, customizationBlockName, debDataMember, declaresEdgetpuDelegate, defaultFfmpegSpawn, diffExportTargets, downloadBinary, edgetpuDelegateFileName, edgetpuLibDir, ensureBinary, ensureEdgetpuDelegate, ensureFfmpeg, ensurePython, extractDebPayload, findInPath, getEdgetpuDelegateArtifact, getFfmpegArtifact, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, icaclsArgs, installLibraryFromDeb, installPythonPackages, installPythonRequirements, mkdirOwnerOnlySync, nodeDataRootFromEnv, nodeProcStatReader, parseFfmpegCapabilities, parseProcCpuSeconds, parseProcRssBytes, physicalRootOf, pinnedFfmpegSpawner, probeFfmpegBinary, readArMembers, readProcessCost, resolveExportFingerprint, resolvePinnedFfmpegSpawner, restrictDirToOwnerSync, restrictFileToOwnerSync, signExpiringUrl, verifyExpiringUrl, writeFileAtomic, writeOwnerOnlyFileSync };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/types",
3
- "version": "1.2.391",
3
+ "version": "1.2.392",
4
4
  "description": "Shared types, interfaces, and model catalogs for the CamStack detection ecosystem",
5
5
  "keywords": [
6
6
  "camstack",