diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1948223..d42842a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,6 +11,18 @@ updates: directory: / schedule: interval: weekly + ignore: + # Pinned and patched speech runtime: upgrade deliberately, see docs/development.md. + - dependency-name: '@huggingface/transformers' + - dependency-name: onnxruntime-web + - dependency-name: onnxruntime-node + # typescript-eslint and astro check need TypeScript 6's JavaScript API; + # TypeScript 7 is installed separately as typescript-native. + - dependency-name: typescript + update-types: ['version-update:semver-major'] + # Types track the oldest supported Node.js (22.18), not the newest. + - dependency-name: '@types/node' + update-types: ['version-update:semver-major'] groups: npm: patterns: ['*'] diff --git a/CHANGELOG.md b/CHANGELOG.md index 22e2444..58a375b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to Echo are documented here. The format follows [Keep a Chan ## [Unreleased] +- Changed: the speech runtime is now Transformers.js 4.3.2 with ONNX Runtime 1.30 (native) and 1.31 (browser). Word timings and transcripts were checked against the previous runtime on real speech, and models still load only from their pinned versions, including offline. + ## [1.0.0] - 2026-11-01 The first public release: Echo as an open-source, self-hosted product. diff --git a/docs/development.md b/docs/development.md index e494e70..80c3ac4 100644 --- a/docs/development.md +++ b/docs/development.md @@ -81,6 +81,13 @@ Markdownlint uses `.markdownlint-cli2.jsonc` for root `*.md` and `docs/**/*.md`, Use Node.js 22.18 or newer, which runs `.ts` files through built-in type stripping. Browser, extension, script, and test projects extend `tsconfig.base.json`; relative TypeScript imports include `.ts`. TypeScript 6.0.3 provides the JavaScript API for editors and tooling, while `typescript-native` pins 7.0.2 for `pnpm typecheck`; `pnpm typecheck:compat` checks compiler drift. Both scripts use explicit compiler paths because both packages publish a `tsc` binary. Both compilers require declarations for cross-project references, so solution builds emit declarations and build metadata under `node_modules/.cache/tsbuild/`; esbuild owns JavaScript assets. +**The speech runtime is pinned and patched.** `@huggingface/transformers` is pinned to 4.3.2, and `onnxruntime-web` and `onnxruntime-node` to the exact versions it depends on, so one copy of each runtime is installed and `scripts/copy-wasm.ts` ships the WebAssembly that matches the bundled JavaScript. Two 4.3.2 defects are worked around: + +- `patches/@huggingface__transformers@4.3.2.patch` (applied by pnpm through `patchedDependencies` in `pnpm-workspace.yaml`) makes `pipeline()`, `AutoTokenizer` and the processor lookup pass `revision`, `cache_dir` and `local_files_only` to the files they probe. Unpatched, they probe the repository's `main` branch, which fetches unpinned files and breaks loading offline. +- `web/whisper-alignment.ts` restores 3.8.1's word timestamps: word mode does not predict timestamp tokens, and token times come from a port of 3.8.1's DTW, cropped to the real audio. 4.3.2's own word timestamps are one token late and hallucinate text at chunk boundaries. + +Dependabot ignores these three packages. To upgrade them, drop or re-create the patch, then run real Whisper on real speech (in Node and in Chromium) with the current and new versions and compare the word timings before merging. Unit tests stub the models, so they cannot catch alignment regressions. + Tests are grouped into `tests/unit/`, `tests/integration/`, and `tests/e2e/`, with shared fixtures in `tests/helpers/`. Run one unit test with `node --test tests/unit/foo.test.ts`. Integration files run serially to isolate server and extension fixtures; `scripts/test-browser.ts` preserves the browser suite's defined order. Scripts and tests execute directly as TypeScript; generated browser bundle names remain unchanged. ## Design tokens, fonts and icons diff --git a/docs/licenses.md b/docs/licenses.md index 0d73688..5905385 100644 --- a/docs/licenses.md +++ b/docs/licenses.md @@ -18,6 +18,8 @@ The runtime JavaScript dependencies: | `onnxruntime-node` | MIT | The native runtime for Whisper Large V3 | | `@scure/bip39` | MIT | The recovery phrase word list | +`web/whisper-alignment.ts` contains code ported from Transformers.js 3.8.1 (Copyright Hugging Face, Apache-2.0), and `patches/` holds a patch to Transformers.js 4.3.2 under the same licence. + Development-only packages (build tools, linters, test runners) are not shipped with Echo. CI runs `pnpm audit --prod` on every push. ## Fonts and icons diff --git a/extension/src/pairing.ts b/extension/src/pairing.ts index 7b46183..dfcb3e3 100644 --- a/extension/src/pairing.ts +++ b/extension/src/pairing.ts @@ -71,8 +71,7 @@ async function showApprovalTab(link: Waiting): Promise { } async function closeApprovalTab(requestId: string): Promise { const tab = (await chrome.storage.session.get('approvalTab'))['approvalTab'] as - | ApprovalTab - | undefined; + ApprovalTab | undefined; if (tab?.requestId !== requestId) return; await chrome.storage.session.remove('approvalTab'); const current = await chrome.tabs.get(tab.id).catch(() => undefined); // Closed by the user. @@ -283,8 +282,7 @@ interface ConnectRequest { } export async function pendingConnect() { const pending = (await chrome.storage.session.get('connectRequest'))['connectRequest'] as - | ConnectRequest - | undefined; + ConnectRequest | undefined; if (!pending) return undefined; const link = await readLink(); return { @@ -295,8 +293,7 @@ export async function pendingConnect() { } export async function confirmConnect(id: string, previous?: string) { const pending = (await chrome.storage.session.get('connectRequest'))['connectRequest'] as - | ConnectRequest - | undefined; + ConnectRequest | undefined; const view = await pendingConnect(); if (!pending || !view || pending.id !== id || view.previous !== previous) throw new Error('The connection request changed. Review it and try again.'); diff --git a/extension/src/setup.ts b/extension/src/setup.ts index d2c4a7c..b848c3e 100644 --- a/extension/src/setup.ts +++ b/extension/src/setup.ts @@ -73,8 +73,7 @@ async function refreshState() { if (state.recoveryError) showError(new Error(state.recoveryError)); if (!initialized) { const saved = (await chrome.storage.local.get('onboardingChoices'))['onboardingChoices'] as - | Partial - | undefined; + Partial | undefined; choices = { callOnly: saved?.callOnly === true }; initialized = true; } diff --git a/package.json b/package.json index db9294b..ad88c33 100644 --- a/package.json +++ b/package.json @@ -26,18 +26,18 @@ "lint:md": "markdownlint-cli2" }, "dependencies": { - "@huggingface/transformers": "3.8.1", - "@scure/bip39": "2.2.0", - "onnxruntime-node": "1.21.0", - "onnxruntime-web": "1.22.0-dev.20250409-89f8206ba4" + "@huggingface/transformers": "4.3.2", + "@scure/bip39": "2.4.0", + "onnxruntime-node": "1.30.0", + "onnxruntime-web": "1.31.0-dev.20260914-8d85527a0" }, "devDependencies": { "@axe-core/playwright": "^4.13.0", "@eslint/js": "10.0.1", "@playwright/test": "^1.55.0", "@types/chrome": "0.3.7", - "@types/node": "22.18.0", - "esbuild": "^0.25.0", + "@types/node": "22.20.5", + "esbuild": "^0.28.2", "eslint": "10.14.0", "eslint-config-prettier": "10.1.8", "globals": "17.13.0", @@ -45,7 +45,7 @@ "knip": "6.44.0", "lucide-static": "1.66.0", "markdownlint-cli2": "0.24.0", - "prettier": "3.6.2", + "prettier": "3.9.10", "stylelint": "17.16.0", "stylelint-config-standard": "40.0.0", "typescript": "6.0.3", diff --git a/patches/@huggingface__transformers@4.3.2.patch b/patches/@huggingface__transformers@4.3.2.patch new file mode 100644 index 0000000..2313246 --- /dev/null +++ b/patches/@huggingface__transformers@4.3.2.patch @@ -0,0 +1,285 @@ +diff --git a/dist/transformers.node.cjs b/dist/transformers.node.cjs +index 4d58c96fa30a65d99494a97f58ad9c6296f29dc1..77087013eea9a9b8af46324711311546949795a2 100644 +--- a/dist/transformers.node.cjs ++++ b/dist/transformers.node.cjs +@@ -16204,11 +16204,11 @@ function quantize_embeddings(tensor, precision) { + } + + // src/utils/model_registry/get_tokenizer_files.js +-async function get_tokenizer_files(modelId) { ++async function get_tokenizer_files(modelId, { cache_dir = null, local_files_only = false, revision = "main" } = {}) { + if (!modelId) { + throw new Error("modelId is required for get_tokenizer_files"); + } +- const metadata = await get_file_metadata(modelId, "tokenizer_config.json", {}); ++ const metadata = await get_file_metadata(modelId, "tokenizer_config.json", { cache_dir, local_files_only, revision }); + if (metadata.exists) { + return ["tokenizer.json", "tokenizer_config.json"]; + } +@@ -16217,7 +16217,7 @@ async function get_tokenizer_files(modelId) { + + // src/tokenization_utils.js + async function loadTokenizer(pretrained_model_name_or_path, options) { +- const tokenizerFiles = await get_tokenizer_files(pretrained_model_name_or_path); ++ const tokenizerFiles = await get_tokenizer_files(pretrained_model_name_or_path, options); + return await Promise.all( + tokenizerFiles.map((file) => getModelJSON(pretrained_model_name_or_path, file, true, options)) + ); +@@ -26496,8 +26496,8 @@ function get_config(modelId, { config = null, cache_dir = null, local_files_only + () => AutoConfig.from_pretrained(modelId, { config, cache_dir, local_files_only, revision }) + ); + } +-async function get_model_files(modelId, { config = null, dtype: overrideDtype = null, device: overrideDevice = null, model_file_name = null } = {}) { +- config = await get_config(modelId, { config }); ++async function get_model_files(modelId, { config = null, dtype: overrideDtype = null, device: overrideDevice = null, model_file_name = null, cache_dir = null, local_files_only = false, revision = "main" } = {}) { ++ config = await get_config(modelId, { config, cache_dir, local_files_only, revision }); + const files = [ + // Add config.json (always loaded) + "config.json" +@@ -35680,11 +35680,11 @@ var TASK_ALIASES = Object.freeze({ + }); + + // src/utils/model_registry/get_processor_files.js +-async function get_processor_files(modelId) { ++async function get_processor_files(modelId, { cache_dir = null, local_files_only = false, revision = "main" } = {}) { + if (!modelId) { + throw new Error("modelId is required"); + } +- const metadata = await get_file_metadata(modelId, IMAGE_PROCESSOR_NAME, {}); ++ const metadata = await get_file_metadata(modelId, IMAGE_PROCESSOR_NAME, { cache_dir, local_files_only, revision }); + return metadata.exists ? [IMAGE_PROCESSOR_NAME] : []; + } + +@@ -35695,15 +35695,19 @@ async function get_files(modelId, { + device = null, + model_file_name = null, + include_tokenizer = true, +- include_processor = true ++ include_processor = true, ++ cache_dir = null, ++ local_files_only = false, ++ revision = "main" + } = {}) { +- const files = await get_model_files(modelId, { config, dtype, device, model_file_name }); ++ const lookup = { cache_dir, local_files_only, revision }; ++ const files = await get_model_files(modelId, { config, dtype, device, model_file_name, ...lookup }); + if (include_tokenizer) { +- const tokenizerFiles = await get_tokenizer_files(modelId); ++ const tokenizerFiles = await get_tokenizer_files(modelId, lookup); + files.push(...tokenizerFiles); + } + if (include_processor) { +- const processorFiles = await get_processor_files(modelId); ++ const processorFiles = await get_processor_files(modelId, lookup); + files.push(...processorFiles); + } + return files; +@@ -35766,12 +35770,16 @@ async function pipeline(task, model = null, { + } + const expected_files = await get_pipeline_files(task, model, { + device, +- dtype ++ dtype, ++ config, ++ cache_dir, ++ local_files_only, ++ revision + }); + let files_loading = {}; + if (progress_callback) { + try { +- const metadata = await Promise.all(expected_files.map(async (file) => get_file_metadata(model, file))); ++ const metadata = await Promise.all(expected_files.map(async (file) => get_file_metadata(model, file, { cache_dir, local_files_only, revision }))); + metadata.forEach((m, i) => { + if (m.exists) { + files_loading[expected_files[i]] = { +diff --git a/dist/transformers.node.mjs b/dist/transformers.node.mjs +index 34fa052a869e29fa8bc4c4bb7bfd086d0657e950..fb0442fbdd6919f10ad8728d133c53e551d68b2f 100644 +--- a/dist/transformers.node.mjs ++++ b/dist/transformers.node.mjs +@@ -15205,11 +15205,11 @@ function quantize_embeddings(tensor, precision) { + } + + // src/utils/model_registry/get_tokenizer_files.js +-async function get_tokenizer_files(modelId) { ++async function get_tokenizer_files(modelId, { cache_dir = null, local_files_only = false, revision = "main" } = {}) { + if (!modelId) { + throw new Error("modelId is required for get_tokenizer_files"); + } +- const metadata = await get_file_metadata(modelId, "tokenizer_config.json", {}); ++ const metadata = await get_file_metadata(modelId, "tokenizer_config.json", { cache_dir, local_files_only, revision }); + if (metadata.exists) { + return ["tokenizer.json", "tokenizer_config.json"]; + } +@@ -15218,7 +15218,7 @@ async function get_tokenizer_files(modelId) { + + // src/tokenization_utils.js + async function loadTokenizer(pretrained_model_name_or_path, options) { +- const tokenizerFiles = await get_tokenizer_files(pretrained_model_name_or_path); ++ const tokenizerFiles = await get_tokenizer_files(pretrained_model_name_or_path, options); + return await Promise.all( + tokenizerFiles.map((file) => getModelJSON(pretrained_model_name_or_path, file, true, options)) + ); +@@ -25497,8 +25497,8 @@ function get_config(modelId, { config = null, cache_dir = null, local_files_only + () => AutoConfig.from_pretrained(modelId, { config, cache_dir, local_files_only, revision }) + ); + } +-async function get_model_files(modelId, { config = null, dtype: overrideDtype = null, device: overrideDevice = null, model_file_name = null } = {}) { +- config = await get_config(modelId, { config }); ++async function get_model_files(modelId, { config = null, dtype: overrideDtype = null, device: overrideDevice = null, model_file_name = null, cache_dir = null, local_files_only = false, revision = "main" } = {}) { ++ config = await get_config(modelId, { config, cache_dir, local_files_only, revision }); + const files = [ + // Add config.json (always loaded) + "config.json" +@@ -34681,11 +34681,11 @@ var TASK_ALIASES = Object.freeze({ + }); + + // src/utils/model_registry/get_processor_files.js +-async function get_processor_files(modelId) { ++async function get_processor_files(modelId, { cache_dir = null, local_files_only = false, revision = "main" } = {}) { + if (!modelId) { + throw new Error("modelId is required"); + } +- const metadata = await get_file_metadata(modelId, IMAGE_PROCESSOR_NAME, {}); ++ const metadata = await get_file_metadata(modelId, IMAGE_PROCESSOR_NAME, { cache_dir, local_files_only, revision }); + return metadata.exists ? [IMAGE_PROCESSOR_NAME] : []; + } + +@@ -34696,15 +34696,19 @@ async function get_files(modelId, { + device = null, + model_file_name = null, + include_tokenizer = true, +- include_processor = true ++ include_processor = true, ++ cache_dir = null, ++ local_files_only = false, ++ revision = "main" + } = {}) { +- const files = await get_model_files(modelId, { config, dtype, device, model_file_name }); ++ const lookup = { cache_dir, local_files_only, revision }; ++ const files = await get_model_files(modelId, { config, dtype, device, model_file_name, ...lookup }); + if (include_tokenizer) { +- const tokenizerFiles = await get_tokenizer_files(modelId); ++ const tokenizerFiles = await get_tokenizer_files(modelId, lookup); + files.push(...tokenizerFiles); + } + if (include_processor) { +- const processorFiles = await get_processor_files(modelId); ++ const processorFiles = await get_processor_files(modelId, lookup); + files.push(...processorFiles); + } + return files; +@@ -34767,12 +34771,16 @@ async function pipeline(task, model = null, { + } + const expected_files = await get_pipeline_files(task, model, { + device, +- dtype ++ dtype, ++ config, ++ cache_dir, ++ local_files_only, ++ revision + }); + let files_loading = {}; + if (progress_callback) { + try { +- const metadata = await Promise.all(expected_files.map(async (file) => get_file_metadata(model, file))); ++ const metadata = await Promise.all(expected_files.map(async (file) => get_file_metadata(model, file, { cache_dir, local_files_only, revision }))); + metadata.forEach((m, i) => { + if (m.exists) { + files_loading[expected_files[i]] = { +diff --git a/dist/transformers.web.js b/dist/transformers.web.js +index a86f15e936b5b03d960037ed3e82a175dd5a0ffb..f7aff40d1c5822ef8c5f4c6bbad87b10fc86d564 100644 +--- a/dist/transformers.web.js ++++ b/dist/transformers.web.js +@@ -11224,11 +11224,11 @@ function quantize_embeddings(tensor, precision) { + } + + // src/utils/model_registry/get_tokenizer_files.js +-async function get_tokenizer_files(modelId) { ++async function get_tokenizer_files(modelId, { cache_dir = null, local_files_only = false, revision = "main" } = {}) { + if (!modelId) { + throw new Error("modelId is required for get_tokenizer_files"); + } +- const metadata = await get_file_metadata(modelId, "tokenizer_config.json", {}); ++ const metadata = await get_file_metadata(modelId, "tokenizer_config.json", { cache_dir, local_files_only, revision }); + if (metadata.exists) { + return ["tokenizer.json", "tokenizer_config.json"]; + } +@@ -11237,7 +11237,7 @@ async function get_tokenizer_files(modelId) { + + // src/tokenization_utils.js + async function loadTokenizer(pretrained_model_name_or_path, options) { +- const tokenizerFiles = await get_tokenizer_files(pretrained_model_name_or_path); ++ const tokenizerFiles = await get_tokenizer_files(pretrained_model_name_or_path, options); + return await Promise.all( + tokenizerFiles.map((file) => getModelJSON(pretrained_model_name_or_path, file, true, options)) + ); +@@ -21525,8 +21525,8 @@ function get_config(modelId, { config = null, cache_dir = null, local_files_only + () => AutoConfig.from_pretrained(modelId, { config, cache_dir, local_files_only, revision }) + ); + } +-async function get_model_files(modelId, { config = null, dtype: overrideDtype = null, device: overrideDevice = null, model_file_name = null } = {}) { +- config = await get_config(modelId, { config }); ++async function get_model_files(modelId, { config = null, dtype: overrideDtype = null, device: overrideDevice = null, model_file_name = null, cache_dir = null, local_files_only = false, revision = "main" } = {}) { ++ config = await get_config(modelId, { config, cache_dir, local_files_only, revision }); + const files = [ + // Add config.json (always loaded) + "config.json" +@@ -30709,11 +30709,11 @@ var TASK_ALIASES = Object.freeze({ + }); + + // src/utils/model_registry/get_processor_files.js +-async function get_processor_files(modelId) { ++async function get_processor_files(modelId, { cache_dir = null, local_files_only = false, revision = "main" } = {}) { + if (!modelId) { + throw new Error("modelId is required"); + } +- const metadata = await get_file_metadata(modelId, IMAGE_PROCESSOR_NAME, {}); ++ const metadata = await get_file_metadata(modelId, IMAGE_PROCESSOR_NAME, { cache_dir, local_files_only, revision }); + return metadata.exists ? [IMAGE_PROCESSOR_NAME] : []; + } + +@@ -30724,15 +30724,19 @@ async function get_files(modelId, { + device = null, + model_file_name = null, + include_tokenizer = true, +- include_processor = true ++ include_processor = true, ++ cache_dir = null, ++ local_files_only = false, ++ revision = "main" + } = {}) { +- const files = await get_model_files(modelId, { config, dtype, device, model_file_name }); ++ const lookup = { cache_dir, local_files_only, revision }; ++ const files = await get_model_files(modelId, { config, dtype, device, model_file_name, ...lookup }); + if (include_tokenizer) { +- const tokenizerFiles = await get_tokenizer_files(modelId); ++ const tokenizerFiles = await get_tokenizer_files(modelId, lookup); + files.push(...tokenizerFiles); + } + if (include_processor) { +- const processorFiles = await get_processor_files(modelId); ++ const processorFiles = await get_processor_files(modelId, lookup); + files.push(...processorFiles); + } + return files; +@@ -30795,12 +30799,16 @@ async function pipeline2(task, model = null, { + } + const expected_files = await get_pipeline_files(task, model, { + device, +- dtype ++ dtype, ++ config, ++ cache_dir, ++ local_files_only, ++ revision + }); + let files_loading = {}; + if (progress_callback) { + try { +- const metadata = await Promise.all(expected_files.map(async (file) => get_file_metadata(model, file))); ++ const metadata = await Promise.all(expected_files.map(async (file) => get_file_metadata(model, file, { cache_dir, local_files_only, revision }))); + metadata.forEach((m, i) => { + if (m.exists) { + files_loading[expected_files[i]] = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba966c2..c71eea7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -165,22 +165,25 @@ settings: overrides: sharp: ^0.35.4 +patchedDependencies: + '@huggingface/transformers@4.3.2': 7f8171c54d34d5657a60f4d517b8af76a22c8e11272392880310de75f6269f68 + importers: .: dependencies: '@huggingface/transformers': - specifier: 3.8.1 - version: 3.8.1(@types/node@22.18.0) + specifier: 4.3.2 + version: 4.3.2(patch_hash=7f8171c54d34d5657a60f4d517b8af76a22c8e11272392880310de75f6269f68)(@types/node@22.20.5) '@scure/bip39': - specifier: 2.2.0 - version: 2.2.0 + specifier: 2.4.0 + version: 2.4.0 onnxruntime-node: - specifier: 1.21.0 - version: 1.21.0 + specifier: 1.30.0 + version: 1.30.0 onnxruntime-web: - specifier: 1.22.0-dev.20250409-89f8206ba4 - version: 1.22.0-dev.20250409-89f8206ba4 + specifier: 1.31.0-dev.20260914-8d85527a0 + version: 1.31.0-dev.20260914-8d85527a0 devDependencies: '@axe-core/playwright': specifier: ^4.13.0 @@ -195,11 +198,11 @@ importers: specifier: 0.3.7 version: 0.3.7 '@types/node': - specifier: 22.18.0 - version: 22.18.0 + specifier: 22.20.5 + version: 22.20.5 esbuild: - specifier: ^0.25.0 - version: 0.25.12 + specifier: ^0.28.2 + version: 0.28.2 eslint: specifier: 10.14.0 version: 10.14.0(jiti@2.7.0)(supports-color@10.2.2) @@ -222,8 +225,8 @@ importers: specifier: 0.24.0 version: 0.24.0(supports-color@10.2.2) prettier: - specifier: 3.6.2 - version: 3.6.2 + specifier: 3.9.10 + version: 3.9.10 stylelint: specifier: 17.16.0 version: 17.16.0(supports-color@10.2.2)(typescript@6.0.3) @@ -250,17 +253,17 @@ importers: version: 3.7.4 '@astrojs/starlight': specifier: 0.42.8 - version: 0.42.8(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(astro@7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.18.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1))(supports-color@10.2.2)(typescript@6.0.3) + version: 0.42.8(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(astro@7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1))(supports-color@10.2.2)(typescript@6.0.3) astro: specifier: 7.4.4 - version: 7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.18.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1) + version: 7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1) github-slugger: specifier: 2.0.0 version: 2.0.0 devDependencies: '@astrojs/check': specifier: 0.9.12 - version: 0.9.12(@emnapi/runtime@1.11.3)(prettier@3.6.2)(typescript@6.0.3) + version: 0.9.12(@emnapi/runtime@1.11.3)(prettier@3.9.10)(typescript@6.0.3) typescript: specifier: 6.0.3 version: 6.0.3 @@ -590,312 +593,156 @@ packages: '@emnapi/wasi-threads@1.2.3': resolution: {integrity: sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==} - '@esbuild/aix-ppc64@0.25.12': - resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.28.2': resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.12': - resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.28.2': resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.12': - resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.28.2': resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.12': - resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.28.2': resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.12': - resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.28.2': resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.12': - resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.28.2': resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.12': - resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.28.2': resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.12': - resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.28.2': resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.12': - resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.28.2': resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.12': - resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.28.2': resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.12': - resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.28.2': resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.12': - resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.28.2': resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.12': - resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.28.2': resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.12': - resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.28.2': resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.12': - resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.28.2': resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.12': - resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.28.2': resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.12': - resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.28.2': resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.12': - resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.28.2': resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.12': - resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.28.2': resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.12': - resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.28.2': resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.12': - resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.28.2': resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.12': - resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - '@esbuild/openharmony-arm64@0.28.2': resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.12': - resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.28.2': resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.12': - resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.28.2': resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.12': - resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.28.2': resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.12': - resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.28.2': resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} engines: {node: '>=18'} @@ -957,8 +804,11 @@ packages: resolution: {integrity: sha512-YzEzwM5oQf4r0vs7ifWQ+yuVR1YkAHrjERQ+a8WRsStHHp4NRBg+NesjiFc+X2AFUpRvvrIeHDS1xFy9jY/IjQ==} engines: {node: '>=18'} - '@huggingface/transformers@3.8.1': - resolution: {integrity: sha512-tsTk4zVjImqdqjS8/AOZg2yNLd1z9S5v+7oUPpXaasDRwEDhB+xnglK1k5cad26lL5/ZIaeREgWWy0bs9y9pPA==} + '@huggingface/tokenizers@0.2.0': + resolution: {integrity: sha512-LidMHe1FpcSYH4vcSjXooda34pC0M8m1gmDOv7SQi6Iv+ib1zX7J3sHBCC9/3FPCkn2k1b55FneVqMRxrr99pg==} + + '@huggingface/transformers@4.3.2': + resolution: {integrity: sha512-V3+dqf3iWlRYJ9I9GreM3zGQd0lAXnnoPeddmkOQYWAEE4l/+Fi6p7tx/qVUJlKQgLXGim2W76rTOzz+eC0i7g==} '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} @@ -1142,10 +992,6 @@ packages: cpu: [x64] os: [win32] - '@isaacs/fs-minipass@4.0.1': - resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} - engines: {node: '>=18.0.0'} - '@jridgewell/sourcemap-codec@1.6.0': resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} @@ -1175,8 +1021,8 @@ packages: '@emnapi/core': ^1.7.1 || 2.0.0-beta.2 '@emnapi/runtime': ^1.7.1 || 2.0.0-beta.2 - '@noble/hashes@2.2.0': - resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} + '@noble/hashes@2.4.0': + resolution: {integrity: sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==} engines: {node: '>= 20.19.0'} '@nodelib/fs.scandir@2.1.5': @@ -1594,11 +1440,8 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - '@scure/base@2.2.0': - resolution: {integrity: sha512-b8XEupJibegiXV+tDUseI8oLQc8ei3d/4Jkb2RpbHh3MfE054ov3uIz2dhFkB3FI8iwYkEh0gGCApkrYggkPNg==} - - '@scure/bip39@2.2.0': - resolution: {integrity: sha512-T/Bj/YvYMNkIPq6EENO6/rcs2e7qTNuyoUXf0KBFDmp0ZDu0H2X4Lq6yC3i0c8PcWkov5EbW+yQZZbdMmk154A==} + '@scure/bip39@2.4.0': + resolution: {integrity: sha512-82dxFbZUYboyOf0AXiydsQrFQ5Q4h9mX+O2UkE91ROYmsc0BKMGZLwDmy96Jpa2+vrtoxomjUhy1RPIgH/r2nA==} '@shikijs/core@4.5.0': resolution: {integrity: sha512-6CIgwq+bt2JZ3OSjb/nBkkwEf/CfpHcbam+Mg5Pllco7HAGRQ2/EFhZrrJtbyNTgKwknfVS+llsXGygiDGwJWw==} @@ -1683,11 +1526,14 @@ packages: '@types/node@22.18.0': resolution: {integrity: sha512-m5ObIqwsUp6BZzyiy4RdZpzWGub9bqLJMvZDD0QMXhxjqMHMENlj+SqF5QxoUwaQNFe+8kz8XM8ZQhqkQPTgMQ==} + '@types/node@22.20.5': + resolution: {integrity: sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==} + '@types/node@24.19.2': resolution: {integrity: sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==} - '@types/node@26.6.5': - resolution: {integrity: sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==} + '@types/node@26.6.6': + resolution: {integrity: sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog==} '@types/sax@1.2.7': resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} @@ -1931,6 +1777,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + adm-zip@0.6.1: + resolution: {integrity: sha512-Xwrja8nx9e5o2N1my4DsKCeKpdrnACyr1wtbPxBDgGzKzKyE9kRtBFA8mWldI+RVlD7CBZNWY/wQ2+ydwOR6kQ==} + engines: {node: '>=14.0'} + ajv-draft-04@1.0.0: resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} peerDependencies: @@ -2037,10 +1887,6 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - boolean@3.2.0: - resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - brace-expansion@5.0.12: resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} engines: {node: 20 || >=22} @@ -2075,10 +1921,6 @@ packages: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} - chownr@3.0.0: - resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} - engines: {node: '>=18'} - ci-info@4.4.0: resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} engines: {node: '>=8'} @@ -2210,9 +2052,6 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - detect-node@2.1.0: - resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - devalue@6.0.3: resolution: {integrity: sha512-3woMEITftG1wKeO/4d/QXH1DVKctTSZg3jNGPpjp9hNr5Hos8MCFF5McTML5ZtgKDObm2R591B0spRp2IVernA==} engines: {node: '>=18'} @@ -2284,20 +2123,12 @@ packages: es-module-lexer@3.0.4: resolution: {integrity: sha512-BldNY5mwkVu+cQ5Ymn3e6hB9Sx8XHx4ZevWbiUyZbfKxJxYO27Hf0wfzkxIaD5CAJMaGl3YspKELj0ff/7nCMA==} - es6-error@4.1.1: - resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} - esast-util-from-estree@2.0.0: resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} esast-util-from-js@2.0.1: resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} - esbuild@0.25.12: - resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.28.2: resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} engines: {node: '>=18'} @@ -2507,8 +2338,8 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - global-agent@3.0.0: - resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + global-agent@4.1.6: + resolution: {integrity: sha512-V9u791atI/bS7RryfRTK2oixDWo6/RCrIikjwuQ4kADPRZZml1C6LUJXc7y6qRhaAF8geo3bIvE/siAGJCw38A==} engines: {node: '>=10.0'} global-modules@2.0.0: @@ -2750,9 +2581,6 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - jsonc-parser@2.3.1: resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} @@ -2922,8 +2750,8 @@ packages: resolution: {integrity: sha512-kzsCV/rPWRFn3jUhcB919YF4Qgeh0g9tcogoXFadGSo6Zxj6662YOqWXv0jDn8nv5KCCxDfMKJZv6N4HkNkvTg==} engines: {node: '>=22'} - matcher@3.0.0: - resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + matcher@4.0.0: + resolution: {integrity: sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==} engines: {node: '>=10'} mathml-tag-names@4.0.0: @@ -3122,14 +2950,6 @@ packages: resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} - minipass@7.1.3: - resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} - engines: {node: '>=16 || 14 >=14.17'} - - minizlib@3.1.0: - resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} - engines: {node: '>= 18'} - mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -3185,18 +3005,18 @@ packages: oniguruma-to-es@4.3.6: resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} - onnxruntime-common@1.21.0: - resolution: {integrity: sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ==} + onnxruntime-common@1.30.0: + resolution: {integrity: sha512-7fdVWjAID1dVhH/G8qK3APARunV4VkBFoCQAP7qp4Wkab0mrorvmc+sqiT+mKXOzDqdjN5j+/Z9nb4gzNPWcyA==} - onnxruntime-common@1.22.0-dev.20250409-89f8206ba4: - resolution: {integrity: sha512-vDJMkfCfb0b1A836rgHj+ORuZf4B4+cc2bASQtpeoJLueuFc5DuYwjIZUBrSvx/fO5IrLjLz+oTrB3pcGlhovQ==} + onnxruntime-common@1.31.0-dev.20260911-2a43ec07e: + resolution: {integrity: sha512-gBuF6U32YErKIAt+yD7DeGBpjRxhJ1uJwako6ygjokSZULDU6Pd+KWComX8Tumk1hYVHRxjPj7mdMnD0ryAPOw==} - onnxruntime-node@1.21.0: - resolution: {integrity: sha512-NeaCX6WW2L8cRCSqy3bInlo5ojjQqu2fD3D+9W5qb5irwxhEyWKXeH2vZ8W9r6VxaMPUan+4/7NDwZMtouZxEw==} + onnxruntime-node@1.30.0: + resolution: {integrity: sha512-twhs1C2C/BFkz1yc5OY0KIU2GUq6DURO7hD4bx5Q2Qy3nAMJwRXW8xU3NVczE29VA9lolLOYepoD8fjTGOfIqw==} os: [win32, darwin, linux] - onnxruntime-web@1.22.0-dev.20250409-89f8206ba4: - resolution: {integrity: sha512-0uS76OPgH0hWCPrFKlL8kYVV7ckM7t/36HfbgoFw6Nd0CZVVbQC4PkrR8mBX8LtNUFZO25IQBqV2Hx2ho3FlbQ==} + onnxruntime-web@1.31.0-dev.20260914-8d85527a0: + resolution: {integrity: sha512-Iy7rtadoBgxS/LLvDr3QW38DB1PNXRnr0GJMcL0TAt7c9qjgVQl83UlGCVyeAnK2InpmW8Uc3PL8XIuqtDeF6g==} optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} @@ -3318,11 +3138,6 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} - engines: {node: '>=14'} - hasBin: true - prettier@3.9.10: resolution: {integrity: sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==} engines: {node: '>=14'} @@ -3459,10 +3274,6 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - roarr@2.15.4: - resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} - engines: {node: '>=8.0'} - rolldown@1.2.16: resolution: {integrity: sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3478,16 +3289,13 @@ packages: resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==} engines: {node: '>=11.0.0'} - semver-compare@1.0.0: - resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} - semver@7.8.5: resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} hasBin: true - serialize-error@7.0.1: - resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + serialize-error@8.1.0: + resolution: {integrity: sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==} engines: {node: '>=10'} sharp@0.35.5: @@ -3550,9 +3358,6 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - sprintf-js@1.1.3: - resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - std-env@4.3.0: resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} @@ -3629,10 +3434,6 @@ packages: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} - tar@7.5.22: - resolution: {integrity: sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==} - engines: {node: '>=18'} - tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} @@ -3674,8 +3475,8 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-fest@0.13.1: - resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} typesafe-path@0.2.2: @@ -4046,10 +3847,6 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - yallist@5.0.0: - resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} - engines: {node: '>=18'} - yaml-language-server@1.23.0: resolution: {integrity: sha512-3qVyCOexLCWw06PQa5kRPwvMWMZ/eZeCRWUvgD6a0OkqL/4iCnxy2WumbWifa937Uo5xhyWJ0uxlU39ljhNh7A==} hasBin: true @@ -4095,9 +3892,9 @@ snapshots: transitivePeerDependencies: - '@emnapi/runtime' - '@astrojs/check@0.9.12(@emnapi/runtime@1.11.3)(prettier@3.6.2)(typescript@6.0.3)': + '@astrojs/check@0.9.12(@emnapi/runtime@1.11.3)(prettier@3.9.10)(typescript@6.0.3)': dependencies: - '@astrojs/language-server': 2.17.3(@emnapi/runtime@1.11.3)(prettier@3.6.2)(typescript@6.0.3) + '@astrojs/language-server': 2.17.3(@emnapi/runtime@1.11.3)(prettier@3.9.10)(typescript@6.0.3) chokidar: 5.0.0 kleur: 4.1.5 typescript: 6.0.3 @@ -4169,7 +3966,7 @@ snapshots: unified: 11.0.5 yaml: 2.9.1 - '@astrojs/language-server@2.17.3(@emnapi/runtime@1.11.3)(prettier@3.6.2)(typescript@6.0.3)': + '@astrojs/language-server@2.17.3(@emnapi/runtime@1.11.3)(prettier@3.9.10)(typescript@6.0.3)': dependencies: '@astrojs/astro2tsx': 0.1.3(@emnapi/runtime@1.11.3) '@astrojs/yaml2ts': 0.2.4 @@ -4182,14 +3979,14 @@ snapshots: volar-service-css: 0.0.71(@volar/language-service@2.4.30(typescript@6.0.3)) volar-service-emmet: 0.0.71(@volar/language-service@2.4.30(typescript@6.0.3)) volar-service-html: 0.0.71(@volar/language-service@2.4.30(typescript@6.0.3)) - volar-service-prettier: 0.0.71(@volar/language-service@2.4.30(typescript@6.0.3))(prettier@3.6.2) + volar-service-prettier: 0.0.71(@volar/language-service@2.4.30(typescript@6.0.3))(prettier@3.9.10) volar-service-typescript: 0.0.71(@volar/language-service@2.4.30(typescript@6.0.3))(typescript@6.0.3) volar-service-typescript-twoslash-queries: 0.0.71(@volar/language-service@2.4.30(typescript@6.0.3))(typescript@6.0.3) volar-service-yaml: 0.0.71(@volar/language-service@2.4.30(typescript@6.0.3)) vscode-html-languageservice: 5.6.2 vscode-uri: 3.2.0 optionalDependencies: - prettier: 3.6.2 + prettier: 3.9.10 transitivePeerDependencies: - '@emnapi/runtime' - typescript @@ -4228,11 +4025,11 @@ snapshots: github-slugger: 2.0.0 satteri: 0.10.5 - '@astrojs/mdx@8.1.2(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@astrojs/markdown-satteri@0.4.6)(astro@7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.18.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1))': + '@astrojs/mdx@8.1.2(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@astrojs/markdown-satteri@0.4.6)(astro@7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1))': dependencies: '@astrojs/internal-helpers': 0.13.0 '@astrojs/markdown-satteri': 0.4.6 - astro: 7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.18.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1) + astro: 7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1) es-module-lexer: 3.0.4 optionalDependencies: '@astrojs/markdown-remark': 7.3.5(supports-color@10.2.2) @@ -4246,17 +4043,17 @@ snapshots: sitemap: 9.0.1 zod: 4.6.7 - '@astrojs/starlight@0.42.8(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(astro@7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.18.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1))(supports-color@10.2.2)(typescript@6.0.3)': + '@astrojs/starlight@0.42.8(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(astro@7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1))(supports-color@10.2.2)(typescript@6.0.3)': dependencies: '@astrojs/markdown-satteri': 0.4.6 - '@astrojs/mdx': 8.1.2(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@astrojs/markdown-satteri@0.4.6)(astro@7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.18.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1)) + '@astrojs/mdx': 8.1.2(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@astrojs/markdown-satteri@0.4.6)(astro@7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1)) '@astrojs/sitemap': 3.7.4 '@pagefind/default-ui': 1.5.2 '@types/hast': 3.0.5 '@types/js-yaml': 4.0.9 '@types/mdast': 4.0.4 - astro: 7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.18.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1) - astro-expressive-code: 0.44.3(astro@7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.18.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1)) + astro: 7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1) + astro-expressive-code: 0.44.3(astro@7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1)) bcp-47: 2.1.2 hast-util-format: 1.1.0 hast-util-select: 6.0.4 @@ -4470,159 +4267,81 @@ snapshots: dependencies: tslib: 2.8.1 - '@esbuild/aix-ppc64@0.25.12': - optional: true - '@esbuild/aix-ppc64@0.28.2': optional: true - '@esbuild/android-arm64@0.25.12': - optional: true - '@esbuild/android-arm64@0.28.2': optional: true - '@esbuild/android-arm@0.25.12': - optional: true - '@esbuild/android-arm@0.28.2': optional: true - '@esbuild/android-x64@0.25.12': - optional: true - '@esbuild/android-x64@0.28.2': optional: true - '@esbuild/darwin-arm64@0.25.12': - optional: true - '@esbuild/darwin-arm64@0.28.2': optional: true - '@esbuild/darwin-x64@0.25.12': - optional: true - '@esbuild/darwin-x64@0.28.2': optional: true - '@esbuild/freebsd-arm64@0.25.12': - optional: true - '@esbuild/freebsd-arm64@0.28.2': optional: true - '@esbuild/freebsd-x64@0.25.12': - optional: true - '@esbuild/freebsd-x64@0.28.2': optional: true - '@esbuild/linux-arm64@0.25.12': - optional: true - '@esbuild/linux-arm64@0.28.2': optional: true - '@esbuild/linux-arm@0.25.12': - optional: true - '@esbuild/linux-arm@0.28.2': optional: true - '@esbuild/linux-ia32@0.25.12': - optional: true - '@esbuild/linux-ia32@0.28.2': optional: true - '@esbuild/linux-loong64@0.25.12': - optional: true - '@esbuild/linux-loong64@0.28.2': optional: true - '@esbuild/linux-mips64el@0.25.12': - optional: true - '@esbuild/linux-mips64el@0.28.2': optional: true - '@esbuild/linux-ppc64@0.25.12': - optional: true - '@esbuild/linux-ppc64@0.28.2': optional: true - '@esbuild/linux-riscv64@0.25.12': - optional: true - '@esbuild/linux-riscv64@0.28.2': optional: true - '@esbuild/linux-s390x@0.25.12': - optional: true - '@esbuild/linux-s390x@0.28.2': optional: true - '@esbuild/linux-x64@0.25.12': - optional: true - '@esbuild/linux-x64@0.28.2': optional: true - '@esbuild/netbsd-arm64@0.25.12': - optional: true - '@esbuild/netbsd-arm64@0.28.2': optional: true - '@esbuild/netbsd-x64@0.25.12': - optional: true - '@esbuild/netbsd-x64@0.28.2': optional: true - '@esbuild/openbsd-arm64@0.25.12': - optional: true - '@esbuild/openbsd-arm64@0.28.2': optional: true - '@esbuild/openbsd-x64@0.25.12': - optional: true - '@esbuild/openbsd-x64@0.28.2': optional: true - '@esbuild/openharmony-arm64@0.25.12': - optional: true - '@esbuild/openharmony-arm64@0.28.2': optional: true - '@esbuild/sunos-x64@0.25.12': - optional: true - '@esbuild/sunos-x64@0.28.2': optional: true - '@esbuild/win32-arm64@0.25.12': - optional: true - '@esbuild/win32-arm64@0.28.2': optional: true - '@esbuild/win32-ia32@0.25.12': - optional: true - '@esbuild/win32-ia32@0.28.2': optional: true - '@esbuild/win32-x64@0.25.12': - optional: true - '@esbuild/win32-x64@0.28.2': optional: true @@ -4687,12 +4406,15 @@ snapshots: '@huggingface/jinja@0.5.11': {} - '@huggingface/transformers@3.8.1(@types/node@22.18.0)': + '@huggingface/tokenizers@0.2.0': {} + + '@huggingface/transformers@4.3.2(patch_hash=7f8171c54d34d5657a60f4d517b8af76a22c8e11272392880310de75f6269f68)(@types/node@22.20.5)': dependencies: '@huggingface/jinja': 0.5.11 - onnxruntime-node: 1.21.0 - onnxruntime-web: 1.22.0-dev.20250409-89f8206ba4 - sharp: 0.35.5(@types/node@22.18.0) + '@huggingface/tokenizers': 0.2.0 + onnxruntime-node: 1.30.0 + onnxruntime-web: 1.31.0-dev.20260914-8d85527a0 + sharp: 0.35.5(@types/node@22.20.5) transitivePeerDependencies: - '@types/node' @@ -4818,10 +4540,6 @@ snapshots: '@img/sharp-win32-x64@0.35.5': optional: true - '@isaacs/fs-minipass@4.0.1': - dependencies: - minipass: 7.1.3 - '@jridgewell/sourcemap-codec@1.6.0': {} '@keyv/bigmap@1.3.1(keyv@5.6.0)': @@ -4889,7 +4607,7 @@ snapshots: '@tybys/wasm-util': 0.10.4 optional: true - '@noble/hashes@2.2.0': {} + '@noble/hashes@2.4.0': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -5121,12 +4839,9 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} - '@scure/base@2.2.0': {} - - '@scure/bip39@2.2.0': + '@scure/bip39@2.4.0': dependencies: - '@noble/hashes': 2.2.0 - '@scure/base': 2.2.0 + '@noble/hashes': 2.4.0 '@shikijs/core@4.5.0': dependencies: @@ -5218,11 +4933,15 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@22.20.5': + dependencies: + undici-types: 6.21.0 + '@types/node@24.19.2': dependencies: undici-types: 7.24.6 - '@types/node@26.6.5': + '@types/node@26.6.6': dependencies: undici-types: 8.9.0 @@ -5450,6 +5169,8 @@ snapshots: acorn@8.19.0: {} + adm-zip@0.6.1: {} + ajv-draft-04@1.0.0(ajv@8.20.0): optionalDependencies: ajv: 8.20.0 @@ -5505,13 +5226,13 @@ snapshots: astring@1.9.0: {} - astro-expressive-code@0.44.3(astro@7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.18.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1)): + astro-expressive-code@0.44.3(astro@7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1)): dependencies: - astro: 7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.18.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1) + astro: 7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1) rehype-expressive-code: 0.44.3 url-extras: 0.1.0 - astro@7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.18.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1): + astro@7.4.4(@astrojs/markdown-remark@7.3.5(supports-color@10.2.2))(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1): dependencies: '@astrojs/compiler-rs': 0.5.2 '@astrojs/internal-helpers': 0.13.0 @@ -5560,14 +5281,14 @@ snapshots: unifont: 1.0.3 unstorage: 1.17.6 verkit: 0.5.0 - vite: 8.3.5(@types/node@22.18.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1) - vitefu: 1.1.3(vite@8.3.5(@types/node@22.18.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1)) + vite: 8.3.5(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1) + vitefu: 1.1.3(vite@8.3.5(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1)) xxhash-wasm: 1.1.0 yargs-parser: 22.0.0 zod: 4.6.7 optionalDependencies: '@astrojs/markdown-remark': 7.3.5(supports-color@10.2.2) - sharp: 0.35.5(@types/node@22.18.0) + sharp: 0.35.5(@types/node@22.20.5) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -5619,8 +5340,6 @@ snapshots: boolbase@1.0.0: {} - boolean@3.2.0: {} - brace-expansion@5.0.12: dependencies: balanced-match: 4.0.4 @@ -5653,8 +5372,6 @@ snapshots: dependencies: readdirp: 5.1.1 - chownr@3.0.0: {} - ci-info@4.4.0: {} cliui@9.0.1: @@ -5768,8 +5485,6 @@ snapshots: detect-libc@2.1.2: {} - detect-node@2.1.0: {} - devalue@6.0.3: {} devlop@1.1.0: @@ -5827,8 +5542,6 @@ snapshots: es-module-lexer@3.0.4: {} - es6-error@4.1.1: {} - esast-util-from-estree@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 @@ -5843,35 +5556,6 @@ snapshots: esast-util-from-estree: 2.0.0 vfile-message: 4.0.3 - esbuild@0.25.12: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.12 - '@esbuild/android-arm': 0.25.12 - '@esbuild/android-arm64': 0.25.12 - '@esbuild/android-x64': 0.25.12 - '@esbuild/darwin-arm64': 0.25.12 - '@esbuild/darwin-x64': 0.25.12 - '@esbuild/freebsd-arm64': 0.25.12 - '@esbuild/freebsd-x64': 0.25.12 - '@esbuild/linux-arm': 0.25.12 - '@esbuild/linux-arm64': 0.25.12 - '@esbuild/linux-ia32': 0.25.12 - '@esbuild/linux-loong64': 0.25.12 - '@esbuild/linux-mips64el': 0.25.12 - '@esbuild/linux-ppc64': 0.25.12 - '@esbuild/linux-riscv64': 0.25.12 - '@esbuild/linux-s390x': 0.25.12 - '@esbuild/linux-x64': 0.25.12 - '@esbuild/netbsd-arm64': 0.25.12 - '@esbuild/netbsd-x64': 0.25.12 - '@esbuild/openbsd-arm64': 0.25.12 - '@esbuild/openbsd-x64': 0.25.12 - '@esbuild/openharmony-arm64': 0.25.12 - '@esbuild/sunos-x64': 0.25.12 - '@esbuild/win32-arm64': 0.25.12 - '@esbuild/win32-ia32': 0.25.12 - '@esbuild/win32-x64': 0.25.12 - esbuild@0.28.2: optionalDependencies: '@esbuild/aix-ppc64': 0.28.2 @@ -5900,7 +5584,6 @@ snapshots: '@esbuild/win32-arm64': 0.28.2 '@esbuild/win32-ia32': 0.28.2 '@esbuild/win32-x64': 0.28.2 - optional: true escalade@3.2.0: {} @@ -6127,14 +5810,12 @@ snapshots: dependencies: is-glob: 4.0.3 - global-agent@3.0.0: + global-agent@4.1.6: dependencies: - boolean: 3.2.0 - es6-error: 4.1.1 - matcher: 3.0.0 - roarr: 2.15.4 + globalthis: 1.0.4 + matcher: 4.0.0 semver: 7.8.5 - serialize-error: 7.0.1 + serialize-error: 8.1.0 global-modules@2.0.0: dependencies: @@ -6467,8 +6148,6 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} - json-stringify-safe@5.0.1: {} - jsonc-parser@2.3.1: {} jsonc-parser@3.3.1: {} @@ -6634,7 +6313,7 @@ snapshots: transitivePeerDependencies: - supports-color - matcher@3.0.0: + matcher@4.0.0: dependencies: escape-string-regexp: 4.0.0 @@ -7134,12 +6813,6 @@ snapshots: dependencies: brace-expansion: 5.0.12 - minipass@7.1.3: {} - - minizlib@3.1.0: - dependencies: - minipass: 7.1.3 - mrmime@2.0.1: {} ms@2.1.3: {} @@ -7184,22 +6857,22 @@ snapshots: regex: 6.1.0 regex-recursion: 6.0.2 - onnxruntime-common@1.21.0: {} + onnxruntime-common@1.30.0: {} - onnxruntime-common@1.22.0-dev.20250409-89f8206ba4: {} + onnxruntime-common@1.31.0-dev.20260911-2a43ec07e: {} - onnxruntime-node@1.21.0: + onnxruntime-node@1.30.0: dependencies: - global-agent: 3.0.0 - onnxruntime-common: 1.21.0 - tar: 7.5.22 + adm-zip: 0.6.1 + global-agent: 4.1.6 + onnxruntime-common: 1.30.0 - onnxruntime-web@1.22.0-dev.20250409-89f8206ba4: + onnxruntime-web@1.31.0-dev.20260914-8d85527a0: dependencies: flatbuffers: 25.9.23 guid-typescript: 1.0.9 long: 5.3.2 - onnxruntime-common: 1.22.0-dev.20250409-89f8206ba4 + onnxruntime-common: 1.31.0-dev.20260911-2a43ec07e platform: 1.3.6 protobufjs: 7.6.6 @@ -7369,8 +7042,6 @@ snapshots: prelude-ls@1.2.1: {} - prettier@3.6.2: {} - prettier@3.9.10: {} prismjs@1.30.0: {} @@ -7390,7 +7061,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.2 - '@types/node': 26.6.5 + '@types/node': 26.6.6 long: 5.3.2 punycode.js@2.3.1: {} @@ -7566,15 +7237,6 @@ snapshots: reusify@1.1.0: {} - roarr@2.15.4: - dependencies: - boolean: 3.2.0 - detect-node: 2.1.0 - globalthis: 1.0.4 - json-stringify-safe: 5.0.1 - semver-compare: 1.0.0 - sprintf-js: 1.1.3 - rolldown@1.2.16: dependencies: '@oxc-project/types': 0.156.0 @@ -7619,15 +7281,13 @@ snapshots: sax@1.6.1: {} - semver-compare@1.0.0: {} - semver@7.8.5: {} - serialize-error@7.0.1: + serialize-error@8.1.0: dependencies: - type-fest: 0.13.1 + type-fest: 0.20.2 - sharp@0.35.5(@types/node@22.18.0): + sharp@0.35.5(@types/node@22.20.5): dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 @@ -7658,7 +7318,7 @@ snapshots: '@img/sharp-win32-arm64': 0.35.5 '@img/sharp-win32-ia32': 0.35.5 '@img/sharp-win32-x64': 0.35.5 - '@types/node': 22.18.0 + '@types/node': 22.20.5 shebang-command@2.0.0: dependencies: @@ -7706,8 +7366,6 @@ snapshots: space-separated-tokens@2.0.2: {} - sprintf-js@1.1.3: {} - std-env@4.3.0: {} string-width@4.2.3: @@ -7831,14 +7489,6 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - tar@7.5.22: - dependencies: - '@isaacs/fs-minipass': 4.0.1 - chownr: 3.0.0 - minipass: 7.1.3 - minizlib: 3.1.0 - yallist: 5.0.0 - tiny-inflate@1.0.3: {} tinyclip@1.0.3: {} @@ -7870,7 +7520,7 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-fest@0.13.1: {} + type-fest@0.20.2: {} typesafe-path@0.2.2: {} @@ -8028,7 +7678,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@8.3.5(@types/node@22.18.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1): + vite@8.3.5(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 @@ -8036,15 +7686,15 @@ snapshots: rolldown: 1.2.16 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 22.18.0 + '@types/node': 22.20.5 esbuild: 0.28.2 fsevents: 2.3.3 jiti: 2.7.0 yaml: 2.9.1 - vitefu@1.1.3(vite@8.3.5(@types/node@22.18.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1)): + vitefu@1.1.3(vite@8.3.5(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1)): optionalDependencies: - vite: 8.3.5(@types/node@22.18.0)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1) + vite: 8.3.5(@types/node@22.20.5)(esbuild@0.28.2)(jiti@2.7.0)(yaml@2.9.1) volar-service-css@0.0.71(@volar/language-service@2.4.30(typescript@6.0.3)): dependencies: @@ -8071,12 +7721,12 @@ snapshots: optionalDependencies: '@volar/language-service': 2.4.30(typescript@6.0.3) - volar-service-prettier@0.0.71(@volar/language-service@2.4.30(typescript@6.0.3))(prettier@3.6.2): + volar-service-prettier@0.0.71(@volar/language-service@2.4.30(typescript@6.0.3))(prettier@3.9.10): dependencies: vscode-uri: 3.2.0 optionalDependencies: '@volar/language-service': 2.4.30(typescript@6.0.3) - prettier: 3.6.2 + prettier: 3.9.10 volar-service-typescript-twoslash-queries@0.0.71(@volar/language-service@2.4.30(typescript@6.0.3))(typescript@6.0.3): dependencies: @@ -8182,8 +7832,6 @@ snapshots: y18n@5.0.8: {} - yallist@5.0.0: {} - yaml-language-server@1.23.0: dependencies: '@vscode/l10n': 0.0.18 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0fbe2e6..7d086fe 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,3 +7,5 @@ allowBuilds: esbuild: true onnxruntime-node: true protobufjs: false +patchedDependencies: + '@huggingface/transformers@4.3.2': patches/@huggingface__transformers@4.3.2.patch diff --git a/scripts/native-speech.ts b/scripts/native-speech.ts index 3f298ce..dc272d1 100644 --- a/scripts/native-speech.ts +++ b/scripts/native-speech.ts @@ -46,7 +46,11 @@ import { constants } from 'node:fs'; import { createDecipheriv } from 'node:crypto'; import { pathToFileURL } from 'node:url'; import { inspect } from 'node:util'; -import { correctWhisperAlignment, validatedWhisperWords } from '../web/whisper-alignment.ts'; +import { + correctWhisperAlignment, + validatedWhisperWords, + type TensorOps, +} from '../web/whisper-alignment.ts'; export const MODEL_ID = 'onnx-community/whisper-large-v3'; export const CHECKPOINT = 'Xenova/whisper-large-v3'; @@ -78,16 +82,11 @@ const LARGE_V3_ALIGNMENT_HEADS = [ [25, 6], ]; -// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- Preserve the wrapped model method’s concrete input and output types when replacing it. -export function correctPinnedAlignment(model: { - _extract_token_timestamps: ( - outputs: Output, - heads: number[][], - frames?: number, - precision?: number, - ) => Result; -}) { - correctWhisperAlignment(model, LARGE_V3_ALIGNMENT_HEADS); +export function correctPinnedAlignment( + model: Parameters[0], + ops: TensorOps, +) { + correctWhisperAlignment(model, ops, LARGE_V3_ALIGNMENT_HEADS); } const LANGUAGES: Record = { @@ -417,7 +416,8 @@ async function runNative(input: unknown, emit: (message: unknown) => void) { if (!downloading) { audio = await readEncryptedPCM(request); } - const { env, pipeline } = await import('@huggingface/transformers'); + const { env, pipeline, Tensor, cat, mean, stack, std_mean } = + await import('@huggingface/transformers'); env.cacheDir = request.cacheDir; env.useFSCache = true; env.useBrowserCache = false; @@ -465,7 +465,7 @@ async function runNative(input: unknown, emit: (message: unknown) => void) { ) ) throw new Error('The pinned native model cannot create word timestamps.'); - correctPinnedAlignment(speechModel(transcriber.model)); + correctPinnedAlignment(speechModel(transcriber.model), { Tensor, cat, mean, stack, std_mean }); if (downloading) { await validateWeights(request.cacheDir); const forwarded = new Set(); diff --git a/scripts/smoke-speakers.ts b/scripts/smoke-speakers.ts index ac10e0d..1215aec 100644 --- a/scripts/smoke-speakers.ts +++ b/scripts/smoke-speakers.ts @@ -42,7 +42,7 @@ async function main() { const buffer = await response.arrayBuffer(), view = new DataView(buffer); let format, channels, rate, bits, start, length; - for (let cursor = 12; cursor + 8 <= view.byteLength; ) { + for (let cursor = 12; cursor + 8 <= view.byteLength;) { const tag = String.fromCharCode(...new Uint8Array(buffer, cursor, 4)); const size = view.getUint32(cursor + 4, true); if (tag === 'fmt ') { diff --git a/tests/unit/extension-live.test.ts b/tests/unit/extension-live.test.ts index 385010e..1e6cd75 100644 --- a/tests/unit/extension-live.test.ts +++ b/tests/unit/extension-live.test.ts @@ -832,7 +832,14 @@ void test('actual live worker stays warm, loads no speaker models, and validates MODEL_MANIFEST_CACHE: 'manifest', modelManifestUrl() {}, wasmThreadCount: () => 1, - correctWhisperAlignment() {}, + Tensor() {}, + cat() {}, + mean() {}, + stack() {}, + std_mean() {}, + correctWhisperAlignment(_model: unknown, ops: Record) { + assert.deepEqual(Object.keys(ops).sort(), ['Tensor', 'cat', 'mean', 'stack', 'std_mean']); + }, validatedWhisperWords, AutoModelForAudioFrameClassification: { from_pretrained: () => { diff --git a/tests/unit/native-speech.test.ts b/tests/unit/native-speech.test.ts index e6dc950..e5063ab 100644 --- a/tests/unit/native-speech.test.ts +++ b/tests/unit/native-speech.test.ts @@ -135,18 +135,37 @@ void test('native output requires actual finite ordered word timestamps within t }); void test('pinned Large V3 alignment uses its original heads and actual stride-two encoder frame count', () => { - const calls: unknown[] = [], - model = { - _extract_token_timestamps(...args: unknown[]) { - calls.push(args); - return 'aligned'; - }, - }; - correctPinnedAlignment(model); - const outputs = { actual: true }; - assert.equal(model._extract_token_timestamps(outputs, [[9, 19]], 1301, 0.02), 'aligned'); - assert.deepEqual(calls[0], [ - outputs, + const slices: unknown[] = []; + const layer = (index: number) => ({ + slice: (...range: unknown[]) => { + slices.push([index, ...range]); + return {}; + }, + }); + const ops = { + cat: (tensors: { slice: unknown }[]) => defined(tensors[0]), + stack: () => { + throw new Error('heads recorded'); + }, + } as unknown as Parameters[1]; + const model = { + config: { decoder_layers: 32 }, + generate: () => Promise.resolve(), + _extract_token_timestamps: () => { + throw new Error('the 4.3.2 extraction must be replaced'); + }, + } as unknown as Parameters[0]; + correctPinnedAlignment(model, ops); + const outputs = { + cross_attentions: [Array.from({ length: 32 }, (_, index) => layer(index))], + sequences: {}, + } as unknown as Parameters[0]; + assert.throws( + () => model._extract_token_timestamps(outputs, [[9, 19]], 1301, 0.02), + /heads recorded/, + ); + assert.deepEqual( + slices, [ [7, 0], [10, 17], @@ -158,10 +177,8 @@ void test('pinned Large V3 alignment uses its original heads and actual stride-t [21, 4], [24, 1], [25, 6], - ], - 650, - 0.02, - ]); + ].map(([index, head]) => [index, null, head, null, [0, 650]]), + ); assert.throws(() => model._extract_token_timestamps(outputs, [], undefined), /actual audio/); }); diff --git a/tests/unit/whisper-alignment.test.ts b/tests/unit/whisper-alignment.test.ts index 70bcbd3..2cbf5af 100644 --- a/tests/unit/whisper-alignment.test.ts +++ b/tests/unit/whisper-alignment.test.ts @@ -3,27 +3,48 @@ import { defined } from '../helpers/assertions.ts'; import { array } from '../helpers/assertions.ts'; import test from 'node:test'; import assert from 'node:assert/strict'; -import { Tensor, WhisperForConditionalGeneration } from '@huggingface/transformers'; +import { + Tensor, + WhisperForConditionalGeneration, + cat, + mean, + stack, + std_mean, +} from '@huggingface/transformers'; import { correctWhisperAlignment, validatedWhisperWords } from '../../web/whisper-alignment.ts'; -void test('Whisper alignment preserves checkpoint heads and crops stride-two audio positions once', () => { - const heads = [ - [2, 4], - [3, 11], - ], - calls: unknown = []; +const ops = { Tensor, cat, mean, stack, std_mean }; + +void test('Whisper word mode keeps 3.8.1 decoding: no timestamp tokens, other calls unchanged', async () => { + const calls: unknown[] = []; const model = { - _extract_token_timestamps(...args: unknown[]) { - array(calls).push(args); - return 'aligned'; + config: { decoder_layers: 1 }, + generate: (options: unknown) => { + calls.push(options); + return Promise.resolve('generated'); }, + _extract_token_timestamps: WhisperForConditionalGeneration.prototype._extract_token_timestamps, + }; + correctWhisperAlignment(model, ops); + const generate = model.generate as (options: unknown) => Promise; + assert.equal( + await generate({ return_token_timestamps: true, return_timestamps: true, num_frames: 1301 }), + 'generated', + ); + await generate({ max_new_tokens: 1, language: 'english' }); + assert.deepEqual(calls, [ + { return_token_timestamps: true, return_timestamps: false, num_frames: 1301 }, + { max_new_tokens: 1, language: 'english' }, + ]); + const outputs = { + cross_attentions: [], + sequences: new Tensor('int64', new BigInt64Array(1), [1, 1]), }; - correctWhisperAlignment(model); - assert.equal(model._extract_token_timestamps('actual-attentions', heads, 1301, 0.02), 'aligned'); - assert.deepEqual(calls, [['actual-attentions', heads, 650, 0.02]]); + for (const frames of [undefined, 1, 1.5, Number.NaN]) + assert.throws(() => model._extract_token_timestamps(outputs, [[0, 0]], frames), /actual audio/); }); -void test('the cropped extraction runs actual Transformers DTW and returns distinct finite token times', () => { +void test('the cropped extraction reproduces Transformers.js 3.8.1 DTW token times on 4.3.2 tensors', () => { const rows = 8, positions = 1500; function outputs() { @@ -40,12 +61,14 @@ void test('the cropped extraction runs actual Transformers DTW and returns disti } const model = { config: { decoder_layers: 1, median_filter_width: 3 }, + generate: () => Promise.resolve(), _extract_token_timestamps: WhisperForConditionalGeneration.prototype._extract_token_timestamps, }; const heads = [[0, 0]]; - correctWhisperAlignment(model); + correctWhisperAlignment(model, ops); const list: unknown = model._extract_token_timestamps(outputs(), heads, 60).tolist(); const normal = numericVector(array(list)[0]); + // Produced by Transformers.js 3.8.1's own extraction for this input. const expected = [0, 0, 0.04, 0.12, 0.2, 0.28, 0.36, 0.44, 0.52]; assert.ok( normal.every((time: number, index: number) => Math.abs(time - defined(expected[index])) < 1e-6), diff --git a/web/files.ts b/web/files.ts index f65a1ed..fe66c73 100644 --- a/web/files.ts +++ b/web/files.ts @@ -1,8 +1,6 @@ import { errorInfo, parseJson } from './guards.ts'; export type JsonFileResult = - | { cancelled: true } - | { name: string; value: unknown } - | { error: string | undefined }; + { cancelled: true } | { name: string; value: unknown } | { error: string | undefined }; /** Open a connected file input so native browser pickers can reliably target it. */ export function chooseJsonFile() { return new Promise((resolve) => { diff --git a/web/guards.ts b/web/guards.ts index a08a853..0e56d78 100644 --- a/web/guards.ts +++ b/web/guards.ts @@ -376,7 +376,7 @@ export function heartbeat(value: unknown): { restored?: string[] } { } type RuntimeSpeechModel = Pick< WhisperForConditionalGeneration, - 'generate' | '_extract_token_timestamps' + 'config' | 'generate' | '_extract_token_timestamps' > & { sessions: Record & { outputNames: readonly string[] }>; }; @@ -386,6 +386,8 @@ export function speechModel(value: unknown): RuntimeSpeechModel { if ( typeof v['generate'] !== 'function' || typeof v['_extract_token_timestamps'] !== 'function' || + typeof v['config'] !== 'object' || + v['config'] === null || !['model', 'decoder_model_merged'].every( (key) => typeof record(sessions[key])['run'] === 'function', ) || diff --git a/web/inference-worker.ts b/web/inference-worker.ts index c2600f9..3bbb813 100644 --- a/web/inference-worker.ts +++ b/web/inference-worker.ts @@ -17,6 +17,11 @@ import { AutoProcessor, AutoModelForAudioFrameClassification, AutoModelForXVector, + Tensor, + cat, + mean, + stack, + std_mean, } from '@huggingface/transformers'; import { assertModel, @@ -190,7 +195,7 @@ async function handleMessage(event: MessageEvent) { }); const speech = speechModel(transcriber.model); assertWordTimestampSupport(speech.sessions['decoder_model_merged']?.outputNames); - correctWhisperAlignment(speech); + correctWhisperAlignment(speech, { Tensor, cat, mean, stack, std_mean }); loadedModel = modelId; } if (!download) { diff --git a/web/types/worker.ts b/web/types/worker.ts index 8ac9a50..8de491a 100644 --- a/web/types/worker.ts +++ b/web/types/worker.ts @@ -23,11 +23,7 @@ export interface TranscriptResult { duration?: number; } export type WorkerOperation = - | 'download' - | 'download-companions' - | 'transcribe' - | 'diarize' - | 'transcribe-live'; + 'download' | 'download-companions' | 'transcribe' | 'diarize' | 'transcribe-live'; export type WorkerRequest = | { id: string; @@ -49,5 +45,4 @@ export type WorkerReply = | { id: string; type: 'result'; result?: LiveResult | TranscriptResult }; export type WorkerEnvelope = - | Exclude - | { id: string; type: 'result'; result?: unknown }; + Exclude | { id: string; type: 'result'; result?: unknown }; diff --git a/web/whisper-alignment.ts b/web/whisper-alignment.ts index 10daadc..0b22e63 100644 --- a/web/whisper-alignment.ts +++ b/web/whisper-alignment.ts @@ -1,27 +1,220 @@ import type { Word } from './types/domain.ts'; +import type { Tensor, cat, mean, stack, std_mean } from '@huggingface/transformers'; import { record } from './guards.ts'; -/** Correct the mel-frame count passed by pinned Transformers.js 3.8.1 to DTW. */ -// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- Preserve the wrapped model method’s concrete input and output types when replacing it. -export function correctWhisperAlignment( + +/** + * Transformers.js tensor operations used by the alignment. Callers pass the module's + * own exports so this file stays importable on the main thread without the runtime. + */ +export interface TensorOps { + Tensor: typeof Tensor; + cat: typeof cat; + mean: typeof mean; + stack: typeof stack; + std_mean: typeof std_mean; +} + +interface GenerateOutputs { + cross_attentions: Tensor[][]; + sequences: Tensor; +} + +/** + * Restore Transformers.js 3.8.1 word alignment on the pinned 4.3.2 runtime. + * + * 4.3.2 changed word timestamps in two ways that broke Echo on real speech: + * - word mode now also predicts timestamp tokens, which hallucinated text at + * chunk boundaries and produced words past the end of the audio; + * - its DTW gives token t the alignment of decoder row t, but row t is where + * the model predicts token t + 1, so every word took the next word's time. + * Echo therefore keeps 3.8.1's behaviour: word mode does not predict timestamp + * tokens, and token timestamps come from the 3.8.1 DTW below, cropped to the + * actual audio as HF Python generation_whisper.py does (num_frames // 2). + */ +export function correctWhisperAlignment( model: { + config: unknown; + generate: (options: never) => Promise; _extract_token_timestamps: ( - outputs: Output, + outputs: GenerateOutputs, exportedHeads: number[][], numFrames?: number, precision?: number, - ) => Result; + numInputIds?: number, + ) => Tensor; }, + ops: TensorOps, alignmentHeads?: number[][], ) { - const extract = model._extract_token_timestamps.bind(model); - model._extract_token_timestamps = (outputs, exportedHeads, numFrames, precision) => { - // Whisper's stride-two encoder has half as many positions as mel frames. - // HF Python generation_whisper.py crops attentions to num_frames // 2; - // Transformers.js 3.8.1 passes the unconverted mel-frame count here. + const generate = model.generate.bind(model) as (options: unknown) => Promise; + model.generate = (options: unknown) => + generate( + record(options)['return_token_timestamps'] === true + ? { ...record(options), return_timestamps: false } + : options, + ); + model._extract_token_timestamps = (outputs, exportedHeads, numFrames, precision = 0.02) => { + // Whisper's stride-two encoder has half as many positions as mel frames, + // and the pipeline passes the chunk's unconverted mel-frame count. if (typeof numFrames !== 'number' || !Number.isSafeInteger(numFrames) || numFrames < 2) throw new Error('Word alignment requires the actual audio frame count.'); - return extract(outputs, alignmentHeads ?? exportedHeads, Math.floor(numFrames / 2), precision); + return tokenTimestamps( + ops, + model.config, + outputs, + alignmentHeads ?? exportedHeads, + Math.floor(numFrames / 2), + precision, + ); + }; +} + +// The functions below are ported from Transformers.js 3.8.1 +// (WhisperForConditionalGeneration._extract_token_timestamps, medianFilter and +// dynamic_time_warping), Copyright Hugging Face, Apache License 2.0. + +function tokenTimestamps( + ops: TensorOps, + config: unknown, + outputs: GenerateOutputs, + heads: number[][], + positions: number, + precision: number, +): Tensor { + const settings = record(config); + const layers = settings['decoder_layers']; + const width = settings['median_filter_width'] ?? 7; + if ( + typeof layers !== 'number' || + typeof width !== 'number' || + !Array.isArray(outputs.cross_attentions) + ) + throw new Error('The speech model returned no word timestamps.'); + // One tensor per decoder layer: (batch, attention heads, output length, input length). + const crossAttentions = Array.from({ length: layers }, (_, layer) => + ops.cat( + outputs.cross_attentions.map((step) => { + const tensor = step[layer]; + if (!tensor) throw new Error('The speech model returned no word timestamps.'); + return tensor; + }), + 2, + ), + ); + const weights = ops + .stack( + heads.map(([layer, head]) => { + const attention = layer === undefined ? undefined : crossAttentions[layer]; + if (!attention || head === undefined) + throw new Error('The speech model returned no word timestamps.'); + return attention.slice(null, head, null, [0, positions]); + }), + ) + .permute(1, 0, 2, 3); + const [batches = 0, headCount = 0, rows = 0, frames = 0] = weights.dims; + if (batches !== 1) throw new Error('Word alignment supports one recording at a time.'); + const [deviation, average] = ops.std_mean(weights, -2, 0, true); + if (!deviation || !average) throw new Error('The speech model returned no word timestamps.'); + const smoothed = Float32Array.from(weights.data as Float32Array); + const std = deviation.data as Float32Array, + means = average.data as Float32Array; + for (let head = 0; head < headCount; ++head) + for (let row = 0; row < rows; ++row) { + const offset = (head * rows + row) * frames; + const values = smoothed.subarray(offset, offset + frames); + for (let frame = 0; frame < frames; ++frame) { + const at = head * frames + frame; + values[frame] = ((values[frame] ?? 0) - (means[at] ?? 0)) / (std[at] ?? 1); + } + values.set(medianFilter(values, width)); + } + // Average the alignment heads, then align tokens to audio positions. + const matrix: unknown = ops + .mean(new ops.Tensor('float32', smoothed, weights.dims), 1) + .neg() + .squeeze_(0) + .tolist(); + const [textIndices, timeIndices] = dynamicTimeWarping(matrix as number[][]); + const jumpTimes: number[] = []; + textIndices.forEach((index, i) => { + if (i === 0 || index !== textIndices[i - 1]) jumpTimes.push((timeIndices[i] ?? 0) * precision); + }); + const shape = outputs.sequences.dims; + const length = (shape[0] ?? 0) * (shape[1] ?? 0); + if (jumpTimes.length + 1 > length) + throw new Error('The speech model returned no word timestamps.'); + const timestamps = new Float32Array(length); + // Token t is predicted from decoder row t - 1, so it takes that row's start. + timestamps.set(jumpTimes, 1); + return new ops.Tensor('float32', timestamps, shape); +} + +function medianFilter(data: Float32Array, windowSize: number): Float32Array { + if (windowSize % 2 === 0 || windowSize <= 0) + throw new Error('Window size must be a positive odd number'); + const output = new Float32Array(data.length); + const buffer = new Float32Array(windowSize); + const half = Math.floor(windowSize / 2); + for (let i = 0; i < data.length; ++i) { + let count = 0; + for (let j = -half; j <= half; ++j) { + let index = i + j; + if (index < 0) index = Math.abs(index); + else if (index >= data.length) index = 2 * (data.length - 1) - index; + buffer[count++] = data[index] ?? 0; + } + // Typed-array sort is numeric, as in 3.8.1. + buffer.sort(); + output[i] = buffer[half] ?? 0; + } + return output; +} + +function dynamicTimeWarping(matrix: number[][]): [number[], number[]] { + const outputLength = matrix.length; + const inputLength = matrix[0]?.length ?? 0; + const cost = Array.from({ length: outputLength + 1 }, () => + new Array(inputLength + 1).fill(Infinity), + ); + const trace = Array.from({ length: outputLength + 1 }, () => + new Array(inputLength + 1).fill(-1), + ); + const at = (grid: number[][], i: number, j: number) => grid[i]?.[j] ?? Infinity; + const put = (grid: number[][], i: number, j: number, value: number) => { + const row = grid[i]; + if (row) row[j] = value; }; + put(cost, 0, 0, 0); + for (let j = 1; j <= inputLength; ++j) + for (let i = 1; i <= outputLength; ++i) { + const c0 = at(cost, i - 1, j - 1), + c1 = at(cost, i - 1, j), + c2 = at(cost, i, j - 1); + let c: number, t: number; + if (c0 < c1 && c0 < c2) [c, t] = [c0, 0]; + else if (c1 < c0 && c1 < c2) [c, t] = [c1, 1]; + else [c, t] = [c2, 2]; + put(cost, i, j, (matrix[i - 1]?.[j - 1] ?? 0) + c); + put(trace, i, j, t); + } + for (let j = 0; j <= inputLength; ++j) put(trace, 0, j, 2); + for (let i = 0; i <= outputLength; ++i) put(trace, i, 0, 1); + let i = outputLength, + j = inputLength; + const textIndices: number[] = [], + timeIndices: number[] = []; + while (i > 0 || j > 0) { + textIndices.push(i - 1); + timeIndices.push(j - 1); + const step = trace[i]?.[j]; + if (step === 0) { + --i; + --j; + } else if (step === 1) --i; + else if (step === 2) --j; + else throw new Error('Word alignment failed in dynamic time warping.'); + } + return [textIndices.reverse(), timeIndices.reverse()]; } /** Reject structural alignment failures before speaker assignment or persistence. */