diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..8af59dd8c --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +RUST_TEST_THREADS = "1" diff --git a/.claude/rules/paddler-cache-dir.md b/.claude/rules/paddler-cache-dir.md index 780ff411a..c6085e91e 100644 --- a/.claude/rules/paddler-cache-dir.md +++ b/.claude/rules/paddler-cache-dir.md @@ -5,7 +5,7 @@ paths: # Paddler Cache Directory Context -- `paddler_cache_dir` is a root Paddler crate, it must not depend on any other Paddler crate +- `paddler_cache_dir` is a root Paddler crate, it must not depend on any other Paddler crate, except dependency-free utility crates such as `paddler_sha256_hex` - `paddler_cache_dir` manages Paddler's global cache directory, and all its nuances - `paddler_cache_dir` resolves OS-related differences internally - `paddler_cache_dir` must use cache directory patterns idiomatic to the specific operating system diff --git a/.claude/rules/paddler-client-python.md b/.claude/rules/paddler-client-python.md deleted file mode 100644 index 076b143d9..000000000 --- a/.claude/rules/paddler-client-python.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -paths: - - "paddler_client_python/**" ---- - -# Paddler Client Context - -- `paddler_client_python` provides JavaScript client that connects to `paddler_balancer` -- It must provide a way to connect to only, specifically balancer's inference address (without the need to connect to management service at the same time) -- It must provide a way to connect to only, specifically balancer's management address (without the need to connect to inference service at the same time) -- Paddler client must support all Paddler's native endpoints -- It must not implement OpenAI compatibility client diff --git a/.claude/rules/paddler-frame-writer.md b/.claude/rules/paddler-frame-writer.md new file mode 100644 index 000000000..d7d95b81e --- /dev/null +++ b/.claude/rules/paddler-frame-writer.md @@ -0,0 +1,9 @@ +--- +paths: + - "paddler_frame_writer/**" +--- + +# Paddler Frame Writer Context + +- `paddler_frame_writer` is a root Paddler crate, it must not depend on any other Paddler crate +- `paddler_frame_writer` writes queued frames to a connection's sink until the queue ends, a send fails, or the connection closes, so `paddler_agent` and `paddler_balancer` share one way to abandon the writes their peer stopped reading diff --git a/.claude/rules/paddler-opencode-tests.md b/.claude/rules/paddler-opencode-tests.md index ad119f43b..953fde22b 100644 --- a/.claude/rules/paddler-opencode-tests.md +++ b/.claude/rules/paddler-opencode-tests.md @@ -9,4 +9,4 @@ paths: - `paddler_opencode_tests` must reuse the cluster harness from `paddler_tests` / `paddler_test_cluster_harness` to start Paddler; it must not reimplement cluster startup. - The OpenCode binary path is always provided by the user via the `PADDLER_OPENCODE_BINARY` environment variable and is never guessed. - The end-to-end tests are gated behind both the `tests_that_use_opencode` and `tests_that_use_llms` features; when they run without `PADDLER_OPENCODE_BINARY`, they fail rather than skip. -- The model is always `qwen3_5_0_8b` via the shared harness; only the OpenCode binary path is configurable. +- The model is always `ministral_3_3b_reasoning` via the shared harness, since it is the smallest harness model that drives OpenCode 2's Code Mode `execute` tool reliably; only the OpenCode binary path is configurable. diff --git a/.claude/rules/paddler-sha256-hex.md b/.claude/rules/paddler-sha256-hex.md new file mode 100644 index 000000000..6c5fa295d --- /dev/null +++ b/.claude/rules/paddler-sha256-hex.md @@ -0,0 +1,9 @@ +--- +paths: + - "paddler_sha256_hex/**" +--- + +# Paddler SHA-256 Hex Context + +- `paddler_sha256_hex` is a root Paddler crate, it must not depend on any other Paddler crate +- `paddler_sha256_hex` encodes the SHA-256 digest of bytes as lowercase hexadecimal, so every crate that names something after a digest names it the same way diff --git a/.claude/rules/paddler-spawn-resuming-panic.md b/.claude/rules/paddler-spawn-resuming-panic.md new file mode 100644 index 000000000..7e3e8a0d7 --- /dev/null +++ b/.claude/rules/paddler-spawn-resuming-panic.md @@ -0,0 +1,9 @@ +--- +paths: + - "paddler_spawn_resuming_panic/**" +--- + +# Paddler Spawn Resuming Panic Context + +- `paddler_spawn_resuming_panic` is a root Paddler crate, it must not depend on any other Paddler crate +- `paddler_spawn_resuming_panic` runs work on a spawned or blocking tokio task, and resumes the task's panic in the task that awaits it, so the work fails exactly as if it ran inline diff --git a/.claude/rules/paddler-state-database.md b/.claude/rules/paddler-state-database.md index f76f8892d..868b9d2e5 100644 --- a/.claude/rules/paddler-state-database.md +++ b/.claude/rules/paddler-state-database.md @@ -6,5 +6,5 @@ paths: # Paddler State Database Context - `paddler_state_database` persists the balancer desired state, in a file or in memory -- `paddler_state_database` depends only on `paddler_messaging` among the Paddler crates +- `paddler_state_database` depends only on `paddler_messaging` and the root `paddler_spawn_resuming_panic` among the Paddler crates - `paddler_state_database` requires absolute file paths and treats an unreadable or corrupt state file as an error instead of replacing it with defaults diff --git a/.claude/rules/paddler-tcp-buffer-limits.md b/.claude/rules/paddler-tcp-buffer-limits.md new file mode 100644 index 000000000..cba59be60 --- /dev/null +++ b/.claude/rules/paddler-tcp-buffer-limits.md @@ -0,0 +1,9 @@ +--- +paths: + - "paddler_tcp_buffer_limits/**" +--- + +# Paddler TCP Buffer Limits Context + +- `paddler_tcp_buffer_limits` is a test fixture crate, it must not depend on any other Paddler crate +- `paddler_tcp_buffer_limits` reads the kernel TCP buffer limits, so tests can size the data that outlasts both socket buffers of a loopback connection instead of guessing diff --git a/.claude/skills/running-all-tests/SKILL.md b/.claude/skills/running-all-tests/SKILL.md index b541bb8fa..6a76f335c 100644 --- a/.claude/skills/running-all-tests/SKILL.md +++ b/.claude/skills/running-all-tests/SKILL.md @@ -32,27 +32,27 @@ Copy this checklist and tick each item as the suite completes: - [ ] JS client - [ ] JS client LLM - [ ] Python lint -- [ ] Python client -- [ ] Python client LLM - [ ] OpenAI Python client LLM +- [ ] TypeSafe Python client LLM +- [ ] kev converter Python LLM - [ ] Rust unit - [ ] Rust integration +- [ ] OpenCode integration ``` -| # | Suite | Command (from the repo root) | -|---|--------------------------|-------------------------------------------------------| -| 1 | JS client | `TEST_DEVICE=$DEVICE make test.client.js` | -| 2 | JS client LLM | `TEST_DEVICE=$DEVICE make test.client.js.llm` | -| 3 | Python lint | `make lint.client.python lint.openai.python` | -| 4 | Python client | `TEST_DEVICE=$DEVICE make test.client.python` | -| 5 | Python client LLM | `TEST_DEVICE=$DEVICE make test.client.python.llm` | -| 6 | OpenAI Python client LLM | `TEST_DEVICE=$DEVICE make test.openai.python.llm` | -| 7 | Rust unit | `TEST_DEVICE=$DEVICE make test.unit` | -| 8 | Rust integration | `TEST_DEVICE=$DEVICE make test.integration` | - -Run them in this order. Cheap suites (1, 3, 4, 7) surface bugs quickly; the GPU-bound suites (2, 5, 6, 8) load models. - -On NixOS the pinned `ruff` wheel is dynamically linked, so suite 3 needs `nix-ld`. +| # | Suite | Command (from the repo root) | +|---|----------------------------|--------------------------------------------------------------------------------------------------| +| 1 | JS client | `TEST_DEVICE=$DEVICE make test.client.js` | +| 2 | JS client LLM | `TEST_DEVICE=$DEVICE make test.client.js.llm` | +| 3 | Python lint | `make lint.openai.python lint.typesafe.python lint.kev_converter.python` | +| 4 | OpenAI Python client LLM | `TEST_DEVICE=$DEVICE make test.openai.python.llm` | +| 5 | TypeSafe Python client LLM | `TEST_DEVICE=$DEVICE make test.typesafe.python.llm` | +| 6 | kev converter Python LLM | `make test.kev_converter.python.llm` | +| 7 | Rust unit | `TEST_DEVICE=$DEVICE make test.unit` | +| 8 | Rust integration | `TEST_DEVICE=$DEVICE make test.integration` | +| 9 | OpenCode integration | `PADDLER_OPENCODE_BINARY=$(command -v opencode) TEST_DEVICE=$DEVICE make test.integration.opencode` | + +Run them in this order. Cheap suites (1, 3, 7) surface bugs quickly; the GPU-bound suites (2, 4, 5, 6, 8, 9) load models. ## Step 3: rules during the run diff --git a/.claude/skills/running-coverage/SKILL.md b/.claude/skills/running-coverage/SKILL.md index 607ee65f9..c74d1428c 100644 --- a/.claude/skills/running-coverage/SKILL.md +++ b/.claude/skills/running-coverage/SKILL.md @@ -30,15 +30,15 @@ Copy this checklist and tick each item as the suite completes: ``` - [ ] Rust -- [ ] Python client +- [ ] kev converter Python - [ ] JS client ``` -| # | Suite | Command (from the repo root) | -|---|---------------|--------------------------------------------------------| -| 1 | Rust | `TEST_DEVICE=$DEVICE make test.coverage` | -| 2 | Python client | `TEST_DEVICE=$DEVICE make test.client.python.coverage` | -| 3 | JS client | `TEST_DEVICE=$DEVICE make test.client.js.coverage` | +| # | Suite | Command (from the repo root) | +|---|----------------------|-----------------------------------------------------------------------------------------| +| 1 | Rust | `PADDLER_OPENCODE_BINARY=$(command -v opencode) TEST_DEVICE=$DEVICE make test.coverage` | +| 2 | kev converter Python | `make test.kev_converter.python.llm` | +| 3 | JS client | `TEST_DEVICE=$DEVICE make test.client.js.coverage` | ## Step 3: rules during the run diff --git a/.config/nextest.toml b/.config/nextest.toml index 731e90d13..f3b420207 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -3,6 +3,10 @@ serial = { max-threads = 1 } [profile.default] slow-timeout = { period = "10s", terminate-after = 3 } +test-threads = 1 + +[profile.cpu] +test-threads = "num-cpus" [[profile.default.overrides]] filter = '(package(=paddler_tests) | package(=paddler_cli_tests)) and kind(test)' diff --git a/.dockerignore b/.dockerignore index 69782076f..a34664e22 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,6 @@ **/.coverage **/.mypy_cache **/.pytest_cache -**/.ruff_cache **/.venv **/__pycache__ **/dist diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 40bac7256..0c268670e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [macos-latest, windows-latest] + os: [macos-latest] steps: - name: checkout code @@ -32,10 +32,5 @@ jobs: - uses: Swatinem/rust-cache@v2 - - name: Install GNU Make - if: runner.os == 'Windows' - run: choco install make --version=4.4.1 --no-progress - - name: Build the cli binary run: make target/debug/paddler - shell: bash diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index de496e26c..f5e5b2e00 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,13 +46,11 @@ jobs: - name: build the cli binary run: make target/release/paddler - shell: bash env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: rename the cli binary run: mv ./target/release/paddler ./${{ matrix.artifact_name }} - shell: bash - name: assert cli version matches tag uses: intentee/release-action/assert-version@8125f727e97f72bfef24299d7c6d3217af1453bc @@ -66,61 +64,9 @@ jobs: name: ${{ matrix.artifact_name }} path: ./${{ matrix.artifact_name }} - build-gui: - name: build-gui - needs: resolve-version - runs-on: ${{ matrix.os }} - strategy: - matrix: - include: - - os: ubuntu-22.04 - artifact_name: paddler-gui-bin-linux-libc2.35-amd64 - - - os: ubuntu-latest - artifact_name: paddler-gui-bin-linux-libc2.39-amd64 - - - os: macos-latest - artifact_name: paddler-gui-bin-macos-arm - - steps: - - name: checkout code - uses: actions/checkout@v4 - - - name: install paddler_gui system dependencies (linux) - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y \ - libvulkan-dev libxkbcommon-dev libwayland-dev libgl-dev \ - libx11-dev libxcursor-dev libxrandr-dev libxi-dev \ - libxcb1-dev libfontconfig-dev pkg-config - shell: bash - - - name: build the gui binary - run: make target/release/paddler_gui - shell: bash - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: rename the gui binary - run: mv ./target/release/paddler_gui ./${{ matrix.artifact_name }} - shell: bash - - - name: assert gui version matches tag - uses: intentee/release-action/assert-version@8125f727e97f72bfef24299d7c6d3217af1453bc - with: - filename: "./${{ matrix.artifact_name }}" - expected-version: "${{ needs.resolve-version.outputs.version }}" - - - name: upload gui artifact - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.artifact_name }} - path: ./${{ matrix.artifact_name }} - release: name: release - needs: [resolve-version, build-cli, build-gui] + needs: [resolve-version, build-cli] runs-on: ubuntu-latest permissions: contents: write diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6497c8006..cf79b21f4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -108,19 +108,15 @@ jobs: - name: Install Poetry run: pip install poetry==2.4.3 - - name: Lint the Python client - run: make lint.client.python - - name: Lint the OpenAI Python client suite run: make lint.openai.python - python: - name: python (${{ matrix.python-version }}) - needs: paddler-binary + - name: Lint the TypeSafe Python client suite + run: make lint.typesafe.python + + kev-converter-lint: + name: kev converter lint runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.11", "3.13"] steps: - name: checkout code @@ -129,21 +125,10 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: ${{ matrix.python-version }} + python-version: "3.13" - name: Install Poetry run: pip install poetry==2.4.3 - - name: Download the Paddler binary - uses: actions/download-artifact@v4 - with: - name: paddler-binary - path: target/debug - - - name: Mark the downloaded Paddler binary as built and executable - run: | - chmod +x target/debug/paddler - touch target/debug/paddler - - - name: Run Python client tests - run: make test.client.python + - name: Lint the kev converter + run: make lint.kev_converter.python diff --git a/Cargo.lock b/Cargo.lock index 2bb5989e9..5566d095c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,30 +2,14 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "ab_glyph" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" -dependencies = [ - "ab_glyph_rasterizer", - "owned_ttf_parser", -] - -[[package]] -name = "ab_glyph_rasterizer" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" - [[package]] name = "actix-codec" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +checksum = "4c13df95297bcf9014dc89162b0cc69431e192e34e3b419612fc124cfcd45dbf" dependencies = [ - "bitflags 2.11.0", - "bytes 1.11.1", + "bitflags 2.13.2", + "bytes", "futures-core", "futures-sink", "memchr", @@ -37,9 +21,9 @@ dependencies = [ [[package]] name = "actix-cors" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa239b93927be1ff123eebada5a3ff23e89f0124ccb8609234e5103d5a5ae6d" +checksum = "2aff07ada3254fc02618cb7850da91dceb23b5dbda53c6676ccbb28ba504f150" dependencies = [ "actix-utils", "actix-web", @@ -52,25 +36,23 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.12.1" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93acb4a42f64936f9b8cae4a433b237599dd6eb6ed06124eb67132ef8cc90662" +checksum = "b796ba3c974e5133f6ca1a2c4e97c5941ea1a46d202df83344afaa18219f1a62" dependencies = [ "actix-codec", - "actix-rt", "actix-service", "actix-utils", - "base64", - "bitflags 2.11.0", + "base64 0.22.1", + "bitflags 2.13.2", "brotli", - "bytes 1.11.1", + "bytes", "bytestring", "derive_more", "encoding_rs", "flate2", - "foldhash 0.1.5", + "foldhash", "futures-core", - "h2 0.3.27", "http 0.2.12", "httparse", "httpdate", @@ -80,7 +62,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand 0.10.1", + "rand 0.10.3", "sha1 0.11.0", "smallvec", "tokio", @@ -91,12 +73,13 @@ dependencies = [ [[package]] name = "actix-macros" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +checksum = "367f814ad4afbac74f07df5001214da65f65e185c90ef56c4dd8df23f8695b9b" dependencies = [ + "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -120,16 +103,15 @@ version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5f794807f82bbd36430c12cd600c73bbab0f52fdde4f0ed49978df113f4807f" dependencies = [ - "actix-macros", "futures-core", "tokio", ] [[package]] name = "actix-server" -version = "2.9.7" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db2cf6f034e227c0c79825fbf11f3cad48569109515f62542b84d8abedb9376" +checksum = "5e296908c4d6f3a191e6b9873dbdfec8023350a89129d2b470f61d17233931ee" dependencies = [ "actix-rt", "actix-service", @@ -153,9 +135,9 @@ dependencies = [ [[package]] name = "actix-utils" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +checksum = "0128396dd7313f697ad05b21b1a7be7d4cbb81888704f55996e4a27db196bb4d" dependencies = [ "local-waker", "pin-project-lite", @@ -163,9 +145,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.13.0" +version = "4.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" +checksum = "81f8da0d2ae4200dde8773d72ba47a65c220b251ba7b9248db5bd263c247e959" dependencies = [ "actix-codec", "actix-http", @@ -176,16 +158,16 @@ dependencies = [ "actix-service", "actix-utils", "actix-web-codegen", - "bytes 1.11.1", + "bytes", "bytestring", "cfg-if", "cookie", "derive_more", "encoding_rs", - "foldhash 0.1.5", + "foldhash", "futures-core", "futures-util", - "impl-more 0.1.9", + "impl-more", "itoa", "language-tags", "log", @@ -200,50 +182,50 @@ dependencies = [ "smallvec", "socket2", "time", + "tokio", "tracing", "url", ] [[package]] name = "actix-web-codegen" -version = "4.3.0" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +checksum = "f7c9f4c52b954a73c76757a3952d3437fca00a2fbcb49704f8a6ea9f767e73f0" dependencies = [ "actix-router", "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] name = "actix-web-lab" -version = "0.26.0" +version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a89606b86fd2f8c0ddd72ef0be171e58c919ee0b6914aa7b52354063e5ee361" +checksum = "1d634f96d3f94f293999b6acb4bd3c98b00e6f598be7a9b5649a35aecb90de1a" dependencies = [ "actix-http", "actix-router", "actix-service", - "actix-utils", "actix-web", "actix-web-lab-derive", "ahash", "arc-swap", - "bytes 1.11.1", + "bytes", "bytestring", "csv", "derive_more", "form_urlencoded", - "futures-core", "futures-util", "http 0.2.12", - "impl-more 0.3.1", - "itertools 0.14.0", + "impl-more", + "itertools 0.15.0", "local-channel", "mime", "pin-project-lite", "regex", + "russe", "serde", "serde_html_form", "serde_json", @@ -256,12 +238,12 @@ dependencies = [ [[package]] name = "actix-web-lab-derive" -version = "0.26.0" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ac4d6e04e97fe707286509b4f338e99c5fb7249c770e1da074af5e27faa96b3" +checksum = "f3e4b5439e27c9786f334a4ca6b9dc4d148fae4d6683e1e4f6d36575a61d616d" dependencies = [ "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -302,9 +284,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] @@ -329,15 +311,15 @@ dependencies = [ [[package]] name = "alloc-no-stdlib" -version = "2.0.4" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" +checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a" [[package]] name = "alloc-stdlib" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1" dependencies = [ "alloc-no-stdlib", ] @@ -348,45 +330,11 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" -[[package]] -name = "android-activity" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" -dependencies = [ - "android-properties", - "bitflags 2.11.0", - "cc", - "jni 0.22.4", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "num_enum", - "thiserror 2.0.18", -] - -[[package]] -name = "android-build" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cac4c64175d504608cf239756339c07f6384a476f97f20a7043f92920b0b8fd" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "android-properties" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" - [[package]] name = "android_system_properties" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" dependencies = [ "libc", ] @@ -443,21 +391,30 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "approx" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] [[package]] name = "arbitrary" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +checksum = "3bc62ac97cc33321f50863d514c3bc38a453947a8f9e781137e47c7401020aed" [[package]] name = "arc-swap" -version = "1.9.0" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6" +checksum = "380962b3d93b99642e50b9febf72c071e97e8b32d86eae9cf4ef582bb960ae6e" dependencies = [ "rustversion", ] @@ -470,7 +427,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -487,15 +444,9 @@ checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "as-raw-xcb-connection" -version = "1.0.1" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" [[package]] name = "as-slice" @@ -506,22 +457,13 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "ash" -version = "0.38.0+1.3.281" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" -dependencies = [ - "libloading", -] - [[package]] name = "askama" -version = "0.14.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" +checksum = "6024d73179f43f15ccd2b881bfea6fee7f3a46ec53f33b52210dea749ebebaa4" dependencies = [ - "askama_derive", + "askama_macros", "itoa", "percent-encoding", "serde", @@ -530,120 +472,64 @@ dependencies = [ [[package]] name = "askama_derive" -version = "0.14.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" +checksum = "071ee5ebf2138e3ad180e0aacf6940c2cab5e6d8333741d9925c7bee2b153f39" dependencies = [ "askama_parser", "basic-toml", + "glob", "memchr", "proc-macro2", "quote", - "rustc-hash 2.1.2", - "serde", - "serde_derive", - "syn", -] - -[[package]] -name = "askama_parser" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" -dependencies = [ - "memchr", + "rustc-hash", "serde", "serde_derive", - "winnow 0.7.15", -] - -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", + "syn 3.0.9", ] [[package]] -name = "async-io" -version = "2.6.0" +name = "askama_macros" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +checksum = "643e1c7cbb6aec1d920332fe51a7c0d8219e273dcb8602db03f5263e4d16487b" dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix 1.1.4", - "slab", - "windows-sys 0.61.2", + "askama_derive", ] [[package]] -name = "async-lock" -version = "3.4.2" +name = "askama_parser" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +checksum = "2c5ae75772275d268b03ab8bdccdd12117b6169ee23256942b34e46c9f476583" dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", + "rustc-hash", + "serde", + "serde_derive", + "unicode-ident", + "winnow", ] [[package]] name = "async-openai" -version = "0.40.3" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "278af84d3d19995d440ea7b401a4b121c780c8d37f5eb6e883d987c17b523aa4" +checksum = "174e979f1e2ffe94b88d584740c11fd071f6d829c29120b1ba88bcaeb3374063" dependencies = [ "async-openai-macros", - "base64", - "bytes 1.11.1", + "base64 0.22.1", + "bytes", "derive_builder", "eventsource-stream", - "futures 0.3.32", + "futures", "getrandom 0.3.4", - "rand 0.9.4", - "reqwest 0.13.4", + "rand 0.9.5", + "reqwest", "secrecy", "serde", "serde_json", "serde_urlencoded", - "thiserror 2.0.18", + "thiserror", "tokio", "tokio-stream", "tokio-util", @@ -660,93 +546,18 @@ checksum = "492a944774207eed3acf425214eadbd6ce84a2b89331164ff1c11bae92b26302" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "async-process" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix 1.1.4", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-signal" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix 1.1.4", - "signal-hook-registry", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "syn 2.0.119", ] -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - [[package]] name = "async-trait" -version = "0.1.89" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -757,9 +568,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "av-scenechange" @@ -776,7 +587,7 @@ dependencies = [ "num-traits", "pastey", "rayon", - "thiserror 2.0.18", + "thiserror", "v_frame", "y4m", ] @@ -797,18 +608,18 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" dependencies = [ "arrayvec", ] [[package]] name = "aws-lc-rs" -version = "1.18.1" +version = "1.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" +checksum = "faac5829c2b74c28f830747e7818ccfb684261b5f48a1118b1e2a13d36dfab13" dependencies = [ "aws-lc-sys", "zeroize", @@ -816,9 +627,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.45.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" +checksum = "f1622d8446a2d4b2ce0c7eefc73dd43a99779028d5ee5c2dd8073a658ba8a2bc" dependencies = [ "cc", "cmake", @@ -833,6 +644,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "basic-toml" version = "0.1.10" @@ -848,18 +665,18 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.2", "cexpr", "clang-sys", "itertools 0.13.0", "log", - "prettyplease", + "prettyplease 0.2.37", "proc-macro2", "quote", "regex", - "rustc-hash 2.1.2", + "rustc-hash", "shlex", - "syn", + "syn 2.0.119", ] [[package]] @@ -891,24 +708,31 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "bitstream-io" -version = "4.9.0" +version = "4.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" dependencies = [ - "core2", + "no_std_io2", ] [[package]] -name = "block" -version = "0.1.6" +name = "blake3" +version = "1.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae" +dependencies = [ + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.1", +] [[package]] name = "block-buffer" @@ -921,42 +745,34 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", ] [[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2 0.5.2", -] - -[[package]] -name = "block2" -version = "0.6.2" +name = "bon" +version = "3.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +checksum = "a05442cd7005532d0cfedec676b5b9333d2b4c7c3afabe2b1a9ce2742fbe2ee6" dependencies = [ - "objc2 0.6.4", + "bon-macros", ] [[package]] -name = "blocking" -version = "1.6.2" +name = "bon-macros" +version = "3.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +checksum = "93833e97ac7c46ba97045afcf247238c27057538fcf2f06344662ddd5f567be6" dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", + "darling 0.24.1", + "ident_case", + "prettyplease 0.3.0", + "proc-macro2", + "quote", + "syn 3.0.9", ] [[package]] @@ -967,9 +783,9 @@ checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" [[package]] name = "brotli" -version = "8.0.2" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -978,25 +794,36 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "5.0.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr", + "regex-automata", + "serde_core", +] + [[package]] name = "built" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytecount" @@ -1006,30 +833,24 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "bytemuck" -version = "1.25.0" +version = "1.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.10.2" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "byteorder-lite" version = "0.1.0" @@ -1038,89 +859,28 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -dependencies = [ - "byteorder", - "iovec", -] - -[[package]] -name = "bytes" -version = "1.11.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "bytestring" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +checksum = "86566c496f2f47d9b8147a4c8b02ffdb69c919fe0c2b2e7195d22cbba0e635c9" dependencies = [ - "bytes 1.11.1", + "bytes", ] [[package]] name = "cadence" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7aff0c323415907f37007d645d7499c378df47efb3e33ffc1f397fa4e549b2e" +checksum = "5ca08f6db9f0c963249cf23a27820f9d973d2acaad4d6bfaeb858b58ebd58a6a" dependencies = [ "crossbeam-channel", ] -[[package]] -name = "calloop" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" -dependencies = [ - "bitflags 2.11.0", - "log", - "polling", - "rustix 0.38.44", - "slab", - "thiserror 1.0.69", -] - -[[package]] -name = "calloop" -version = "0.14.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" -dependencies = [ - "bitflags 2.11.0", - "polling", - "rustix 1.1.4", - "slab", - "tracing", -] - -[[package]] -name = "calloop-wayland-source" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" -dependencies = [ - "calloop 0.13.0", - "rustix 0.38.44", - "wayland-backend", - "wayland-client", -] - -[[package]] -name = "calloop-wayland-source" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" -dependencies = [ - "calloop 0.14.4", - "rustix 1.1.4", - "wayland-backend", - "wayland-client", -] - [[package]] name = "cc" version = "1.2.58" @@ -1133,12 +893,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cexpr" version = "0.6.0" @@ -1150,43 +904,56 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "chacha20" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", + "cpufeatures 0.3.1", "rand_core 0.10.1", ] [[package]] -name = "clang-sys" -version = "1.8.1" +name = "chrono" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ - "glob", - "libc", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clang-sys" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" +dependencies = [ + "glob", + "libc", "libloading", ] [[package]] name = "clap" -version = "4.6.0" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" dependencies = [ "clap_builder", "clap_derive", @@ -1194,9 +961,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.6.0" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" dependencies = [ "anstream", "anstyle", @@ -1206,60 +973,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] name = "clap_lex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - -[[package]] -name = "clipboard-win" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" -dependencies = [ - "error-code", -] - -[[package]] -name = "clipboard_macos" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7f4aaa047ba3c3630b080bb9860894732ff23e2aee290a418909aa6d5df38f" -dependencies = [ - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "clipboard_wayland" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003f886bc4e2987729d10c1db3424e7f80809f3fc22dbc16c685738887cb37b8" -dependencies = [ - "smithay-clipboard", -] - -[[package]] -name = "clipboard_x11" -version = "0.4.3" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd63e33452ffdafd39924c4f05a5dd1e94db646c779c6bd59148a3d95fff5ad4" -dependencies = [ - "thiserror 2.0.18", - "x11rb", -] +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" [[package]] name = "cmake" @@ -1270,17 +998,6 @@ dependencies = [ "cc", ] -[[package]] -name = "codespan-reporting" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" -dependencies = [ - "serde", - "termcolor", - "unicode-width", -] - [[package]] name = "color_quant" version = "1.1.0" @@ -1293,21 +1010,30 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "combine" -version = "4.6.7" +version = "4.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92" dependencies = [ - "bytes 1.11.1", + "bytes", "memchr", ] [[package]] name = "command_handler" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49385f359e523e20d614c5f92195db2ba8e042e5ce8dadafb6174c2abd95adbb" +checksum = "fc373209739ae365edb7c8c78f832f02919b32fb02cb603dd37cf2d4457d6ccc" dependencies = [ "anyhow", "async-trait", @@ -1317,32 +1043,31 @@ dependencies = [ ] [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "const-oid" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] -name = "console" -version = "0.15.11" +name = "const-str" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f12cc9948ed9604230cdddc7c86e270f9401ccbe3c2e98a4378c5e7632212f" + +[[package]] +name = "const_panic" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +checksum = "9603f79528ece8163c496f8932121cb36cfe46259e9c907bb3d8205139d7caa3" dependencies = [ - "encode_unicode", - "libc", - "once_cell", - "unicode-width", - "windows-sys 0.59.0", + "typewit", ] [[package]] -name = "const-oid" -version = "0.10.2" +name = "constant_time_eq" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "convert_case" @@ -1391,80 +1116,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "core-graphics" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "core-graphics-types 0.1.3", - "foreign-types 0.5.0", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.11.0", - "core-foundation 0.10.1", - "libc", -] - -[[package]] -name = "core2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" -dependencies = [ - "memchr", -] - -[[package]] -name = "core_maths" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" -dependencies = [ - "libm", -] - -[[package]] -name = "cosmic-text" -version = "0.15.0" +name = "countio" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173852283a9a57a3cbe365d86e74dc428a09c50421477d5ad6fe9d9509e37737" +checksum = "b9702aee5d1d744c01d82f6915644f950f898e014903385464c773b96fefdecb" dependencies = [ - "bitflags 2.11.0", - "fontdb", - "harfrust", - "linebender_resource_handle", - "log", - "rangemap", - "rustc-hash 1.1.0", - "self_cell", - "skrifa 0.37.0", - "smol_str", - "swash", - "sys-locale", - "unicode-bidi", - "unicode-linebreak", - "unicode-script", - "unicode-segmentation", + "futures-io", ] [[package]] @@ -1478,36 +1135,36 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.5.0" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "0ba106e844045ddc02c20768ac4b0b7cb45f2cc2ecbd833ac9f5dacb183c2055" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.15" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -1524,9 +1181,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crunchy" @@ -1534,19 +1191,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" -[[package]] -name = "cryoglyph" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc795bdbccdbd461736fb163930a009da6597b226d6f6fce33e7a8eb6ec519" -dependencies = [ - "cosmic-text", - "etagere", - "lru", - "rustc-hash 2.1.2", - "wgpu", -] - [[package]] name = "crypto-common" version = "0.1.7" @@ -1559,9 +1203,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "hybrid-array", ] @@ -1588,25 +1232,33 @@ dependencies = [ ] [[package]] -name = "ctor-lite" -version = "0.1.2" +name = "ctor" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e162d0c2e2068eb736b71e5597eff0b9944e6b973cd9f37b6a288ab9bf20e300" +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" +dependencies = [ + "link-section", + "linktime-proc-macro", +] [[package]] -name = "cursor-icon" -version = "1.2.0" +name = "darling" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] [[package]] name = "darling" -version = "0.20.11" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.24.1", + "darling_macro 0.24.1", ] [[package]] @@ -1620,7 +1272,20 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.119", +] + +[[package]] +name = "darling_core" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 3.0.9", ] [[package]] @@ -1629,30 +1294,41 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" +dependencies = [ + "darling_core 0.24.1", "quote", - "syn", + "syn 3.0.9", ] [[package]] name = "dashmap" -version = "6.1.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "cfg-if", "crossbeam-utils", "hashbrown 0.14.5", - "lock_api", + "lock_api 0.4.14", "once_cell", - "parking_lot_core", + "parking_lot_core 0.9.12", ] [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" [[package]] name = "data-url" @@ -1661,15 +1337,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" [[package]] -name = "deranged" -version = "0.5.8" +name = "defmt" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "e4a0795ac740cb54d7b3775c9c17345f6aaecf884c01733b39d6017738e0e3e1" dependencies = [ - "powerfmt", - "serde_core", + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "840bb056bd99c495c9188198a258ab787c79f306d864db3e6e8451442d6d8366" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "defmt-parser" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71958f69fb2de856c5dd51ac9377e986ca1aa5919f931bf5395a807277f889a0" +dependencies = [ + "thiserror", ] +[[package]] +name = "deranged" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e" + [[package]] name = "derive_builder" version = "0.20.2" @@ -1685,10 +1388,10 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling", + "darling 0.20.11", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1698,7 +1401,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn", + "syn 2.0.119", ] [[package]] @@ -1720,7 +1423,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.119", "unicode-xid", ] @@ -1736,15 +1439,9 @@ dependencies = [ "bytemuck_derive", "hashbrown 0.15.5", "regex-syntax", - "strum", + "strum 0.27.2", ] -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - [[package]] name = "digest" version = "0.10.7" @@ -1757,13 +1454,13 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.12.0", + "block-buffer 0.12.1", "const-oid", - "crypto-common 0.2.1", + "crypto-common 0.2.2", ] [[package]] @@ -1787,63 +1484,27 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - [[package]] name = "dispatch2" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", + "bitflags 2.13.2", + "objc2", ] [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "dlib" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" -dependencies = [ - "libloading", + "syn 3.0.9", ] -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" - [[package]] name = "dunce" version = "1.0.5" @@ -1852,9 +1513,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "either" -version = "1.15.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" [[package]] name = "email_address" @@ -1865,12 +1526,6 @@ dependencies = [ "serde", ] -[[package]] -name = "encode_unicode" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" - [[package]] name = "encoding_rs" version = "0.8.35" @@ -1881,12 +1536,6 @@ dependencies = [ "serde", ] -[[package]] -name = "endi" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" - [[package]] name = "enumflags2" version = "0.7.12" @@ -1894,7 +1543,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" dependencies = [ "enumflags2_derive", - "serde", ] [[package]] @@ -1905,14 +1553,14 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "env_filter" -version = "1.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ "log", "regex", @@ -1920,9 +1568,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ "anstream", "anstyle", @@ -1948,14 +1596,14 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "equivalent" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" [[package]] name = "errno" @@ -1967,37 +1615,16 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "error-code" -version = "3.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" - [[package]] name = "esbuild-metafile" -version = "0.5.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "309f09d28539a42ca467019db5a74893fc755a921558635964a4b729ea5f5f96" +checksum = "ced883606dbccc023d30958eb4382e85fdea3bbd87462087ea8f566c01c9ea89" dependencies = [ - "actix-utils", - "actix-web", - "anyhow", - "askama", - "dashmap", - "once_cell", - "pretty_assertions", + "log", "serde", "serde_json", -] - -[[package]] -name = "etagere" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc89bf99e5dc15954a60f707c1e09d7540e5cd9af85fa75caa0b510bc08c5342" -dependencies = [ - "euclid", - "svg_fmt", + "thiserror", ] [[package]] @@ -2010,29 +1637,8 @@ dependencies = [ ] [[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "eventsource-stream" -version = "0.2.3" +name = "eventsource-stream" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" dependencies = [ @@ -2043,14 +1649,16 @@ dependencies = [ [[package]] name = "exr" -version = "1.74.0" +version = "1.74.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" +checksum = "711fe42c9964295e01ee3fba3f9fe0e1d24b98886950d68efe81b1c76e21adf3" dependencies = [ "bit_field", "half", "lebe", - "miniz_oxide", + "miniz_oxide 0.8.9", + "num-complex", + "pulp", "rayon-core", "smallvec", "zune-inflate", @@ -2058,9 +1666,9 @@ dependencies = [ [[package]] name = "fancy-regex" -version = "0.16.2" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" +checksum = "d301f5bf187b3c295fce6468d3875037a0bccc5f6b151c63cac2f85babf21912" dependencies = [ "bit-set", "regex-automata", @@ -2069,29 +1677,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "fax" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab" -dependencies = [ - "fax_derive", -] - -[[package]] -name = "fax_derive" -version = "0.2.0" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" [[package]] name = "fdeflate" @@ -2104,9 +1698,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" [[package]] name = "find_cuda_helper" @@ -2119,12 +1713,13 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.9.1", + "zlib-rs", ] [[package]] @@ -2161,12 +1756,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -2175,18 +1764,9 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "font-types" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39a654f404bbcbd48ea58c617c2993ee91d1cb63727a37bf2323a4edeed1b8c5" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "font-types" -version = "0.11.3" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7" +checksum = "835ae9d781464a79026cde387f9cbc650c9a4909d7cf7e658b127e3c35d2f35d" dependencies = [ "bytemuck", ] @@ -2202,60 +1782,17 @@ dependencies = [ [[package]] name = "fontdb" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" +checksum = "2660c5e9157bf76d2db1294e4a9feba604ef610819a3b591088d0d8392a3290f" dependencies = [ "fontconfig-parser", "log", "memmap2", "slotmap", "tinyvec", - "ttf-parser", -] - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared 0.1.1", -] - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared 0.3.1", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn", ] -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -2267,12 +1804,12 @@ dependencies = [ [[package]] name = "fraction" -version = "0.15.3" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7" +checksum = "e246562084dde8ebbcc943b261c406ce4f68e5032ec28029a251a47d6a295500" dependencies = [ - "lazy_static", "num", + "num-bigint", ] [[package]] @@ -2293,15 +1830,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" - -[[package]] -name = "futures" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -2314,9 +1845,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -2324,15 +1855,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -2341,51 +1872,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-lite" -version = "2.6.1" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -2396,9 +1914,14 @@ dependencies = [ "memchr", "pin-project-lite", "slab", - "tokio-io", ] +[[package]] +name = "gearhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616e8f476a586f1b078d9eece21f7a071f27997e709e2b471f5697deea53bda9" + [[package]] name = "generic-array" version = "0.14.7" @@ -2409,16 +1932,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "gethostname" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" -dependencies = [ - "rustix 1.1.4", - "windows-link", -] - [[package]] name = "getrandom" version = "0.2.17" @@ -2428,7 +1941,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -2448,176 +1961,79 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", - "wasip2", - "wasip3", "wasm-bindgen", ] [[package]] name = "gif" -version = "0.13.3" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" dependencies = [ "color_quant", "weezl", ] [[package]] -name = "gif" -version = "0.14.1" +name = "git-version" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e" +checksum = "1ad568aa3db0fcbc81f2f116137f263d7304f512a1209b35b85150d3ef88ad19" dependencies = [ - "color_quant", - "weezl", + "git-version-macro", ] [[package]] -name = "gl_generator" -version = "0.14.0" +name = "git-version-macro" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ - "khronos_api", - "log", - "xml-rs", + "proc-macro2", + "quote", + "syn 2.0.119", ] -[[package]] -name = "glam" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" - [[package]] name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] -name = "glow" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" -dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "glutin_wgl_sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" -dependencies = [ - "gl_generator", -] - -[[package]] -name = "gpu-alloc" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" -dependencies = [ - "bitflags 2.11.0", - "gpu-alloc-types", -] - -[[package]] -name = "gpu-alloc-types" -version = "0.3.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" -dependencies = [ - "bitflags 2.11.0", -] +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] -name = "gpu-allocator" -version = "0.27.0" +name = "globset" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd" +checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" dependencies = [ + "aho-corasick", + "bstr", "log", - "presser", - "thiserror 1.0.69", - "windows 0.58.0", -] - -[[package]] -name = "gpu-descriptor" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" -dependencies = [ - "bitflags 2.11.0", - "gpu-descriptor-types", - "hashbrown 0.15.5", -] - -[[package]] -name = "gpu-descriptor-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" -dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "guillotiere" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782" -dependencies = [ - "euclid", - "svg_fmt", -] - -[[package]] -name = "h2" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" -dependencies = [ - "bytes 1.11.1", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", + "regex-automata", + "regex-syntax", ] [[package]] name = "h2" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" +checksum = "7d29020232d6aa3fb1daca64c1127cf662cf97f254ae16c18c05b8ab635fc118" dependencies = [ "atomic-waker", - "bytes 1.11.1", + "bytes", "fnv", "futures-core", "futures-sink", - "http 1.4.0", + "http 1.5.0", "indexmap", "slab", "tokio", @@ -2633,20 +2049,18 @@ checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", - "num-traits", "zerocopy", ] [[package]] name = "harfrust" -version = "0.3.2" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c020db12c71d8a12a3fe7607873cade3a01a6287e29d540c8723276221b9d8" +checksum = "c03d949a14aa089bbb282f7dd76a498a7f684428e4257202efc119ec010376f9" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.2", "bytemuck", - "core_maths", - "read-fonts 0.35.0", + "read-fonts", "smallvec", ] @@ -2661,43 +2075,40 @@ name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", -] [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.2.0", + "foldhash", ] [[package]] name = "hashlink" -version = "0.11.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" +checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb" dependencies = [ - "hashbrown 0.16.1", + "hashbrown 0.17.1", ] [[package]] name = "headers" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" +checksum = "fc2700e3fe555c3310aa7286cac6167449f2c87e0eb58769c9208a1c58a1d106" dependencies = [ - "base64", - "bytes 1.11.1", + "base64 0.22.1", + "bytes", "headers-core", - "http 1.4.0", + "http 1.5.0", "httpdate", "mime", - "sha1 0.10.6", + "sha1 0.10.7", ] [[package]] @@ -2706,55 +2117,73 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http 1.4.0", + "http 1.5.0", ] [[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" +name = "heapify" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "0049b265b7f201ca9ab25475b22b47fe444060126a51abe00f77d986fc5cc52e" [[package]] -name = "hex" -version = "0.4.3" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "hexf-parse" -version = "0.2.1" +name = "hf-hub" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +checksum = "e7ccb6bcc85dec15413ef5949879f9a5497ca4568ed702547eb83fac23376e5c" +dependencies = [ + "base64 0.22.1", + "bon", + "bytes", + "futures", + "getrandom 0.2.17", + "globset", + "hf-xet", + "hyper", + "pathdiff", + "percent-encoding", + "reqwest", + "serde", + "serde_json", + "sha2", + "thiserror", + "tokio", + "tokio-retry", + "tokio-util", + "tracing", + "url", + "wasm-bindgen-futures", +] [[package]] -name = "hf-hub" -version = "0.4.3" +name = "hf-xet" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629d8f3bbeda9d148036d6b0de0a3ab947abd08ce90626327fc3547a49d59d97" +checksum = "f24754aef4f6c3d57bc4a0dc2648ca74487050eda01c361b3eefa11feeafd662" dependencies = [ - "dirs", - "futures 0.3.32", - "http 1.4.0", - "indicatif", - "libc", - "log", - "native-tls", - "num_cpus", - "rand 0.9.4", - "reqwest 0.12.28", + "anyhow", + "async-trait", + "bytes", + "http 1.5.0", + "more-asserts", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror", "tokio", - "ureq", - "windows-sys 0.60.2", + "tokio-util", + "tokio_with_wasm", + "tracing", + "uuid", + "xet-client", + "xet-core-structures", + "xet-data", + "xet-runtime", ] [[package]] @@ -2763,40 +2192,40 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ - "bytes 1.11.1", + "bytes", "fnv", "itoa", ] [[package]] name = "http" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ - "bytes 1.11.1", + "bytes", "itoa", ] [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ - "bytes 1.11.1", - "http 1.4.0", + "bytes", + "http 1.5.0", ] [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ - "bytes 1.11.1", + "bytes", "futures-core", - "http 1.4.0", + "http 1.5.0", "http-body", "pin-project-lite", ] @@ -2813,27 +2242,33 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" + [[package]] name = "hybrid-array" -version = "0.4.10" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ "typenum", ] [[package]] name = "hyper" -version = "1.9.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" dependencies = [ "atomic-waker", - "bytes 1.11.1", + "bytes", "futures-channel", "futures-core", - "h2 0.4.19", - "http 1.4.0", + "h2", + "http 1.5.0", "http-body", "httparse", "itoa", @@ -2845,48 +2280,32 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" dependencies = [ - "http 1.4.0", + "http 1.5.0", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", ] [[package]] -name = "hyper-tls" -version = "0.6.0" +name = "hyper-util" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" dependencies = [ - "bytes 1.11.1", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64", - "bytes 1.11.1", - "futures-channel", - "futures-util", - "http 1.4.0", - "http-body", + "base64 0.23.1", + "bytes", + "futures-channel", + "futures-util", + "http 1.5.0", + "http-body", + "httparse", "hyper", "ipnet", "libc", @@ -2901,226 +2320,34 @@ dependencies = [ ] [[package]] -name = "iced" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000e01026c93ba643f8357a3db3ada0e6555265a377f6f9291c472f6dd701fb3" -dependencies = [ - "iced_core", - "iced_debug", - "iced_futures", - "iced_renderer", - "iced_runtime", - "iced_widget", - "iced_winit", - "image", - "thiserror 2.0.18", -] - -[[package]] -name = "iced_core" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ab1937d699403e7e69252ae743a902bcee9f4ab2052cc4c9a46fcf34729d85" -dependencies = [ - "bitflags 2.11.0", - "bytes 1.11.1", - "glam", - "lilt", - "log", - "num-traits", - "rustc-hash 2.1.2", - "smol_str", - "thiserror 2.0.18", - "web-time", -] - -[[package]] -name = "iced_debug" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25035ab0215a620e53f4103e36fc4e59a1fb2817e4bfc38a30ad27b4202ea0be" -dependencies = [ - "iced_core", - "iced_futures", - "log", -] - -[[package]] -name = "iced_futures" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c0c85ccad42dfbec7293c36c018af0ea0dbcc52d137a4a9a0b0f6822a3fdf0a" -dependencies = [ - "futures 0.3.32", - "iced_core", - "log", - "rustc-hash 2.1.2", - "tokio", - "wasm-bindgen-futures", - "wasmtimer", -] - -[[package]] -name = "iced_graphics" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234ca1c2cec4155055f68fa5fad1b5242c496ac8238d80a259bca382fb44a102" -dependencies = [ - "bitflags 2.11.0", - "bytemuck", - "cosmic-text", - "half", - "iced_core", - "iced_futures", - "image", - "kamadak-exif", - "log", - "raw-window-handle", - "rustc-hash 2.1.2", - "thiserror 2.0.18", - "unicode-segmentation", -] - -[[package]] -name = "iced_program" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dfafec2947cda688d8eb00dac337ba11aa60f9ef6335aed343e189d26e4a673" -dependencies = [ - "iced_graphics", - "iced_runtime", -] - -[[package]] -name = "iced_renderer" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250cc0802408e8c077986ec56c7d07c65f423ee658a4b9fd795a1f2aae5dac05" -dependencies = [ - "iced_graphics", - "iced_tiny_skia", - "iced_wgpu", - "log", - "thiserror 2.0.18", -] - -[[package]] -name = "iced_runtime" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1889b819ce4c06674183242e336c8d49465665441396914dc07cc86f44fa8d4" -dependencies = [ - "bytes 1.11.1", - "iced_core", - "iced_futures", - "raw-window-handle", - "thiserror 2.0.18", -] - -[[package]] -name = "iced_selector" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1442ccc30959cc48b3a624233521ef7f3bc3e06528b6fe306c3a775fbddc4fe" -dependencies = [ - "iced_core", -] - -[[package]] -name = "iced_test" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b56c26cc6e63958ce6be06a68fa3136fd5f646da6302ee12abf1a0f311dd0166" -dependencies = [ - "iced_futures", - "iced_program", - "iced_renderer", - "iced_runtime", - "iced_selector", - "nom 8.0.0", - "png 0.18.1", - "sha2", - "thiserror 2.0.18", -] - -[[package]] -name = "iced_tiny_skia" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0acf8b75a3bc914aff5f2329fdffc1b36eeaea29dda0e4bd232f1c62e9cc3d" -dependencies = [ - "bytemuck", - "cosmic-text", - "iced_debug", - "iced_graphics", - "kurbo 0.10.4", - "log", - "resvg 0.45.1", - "rustc-hash 2.1.2", - "softbuffer", - "tiny-skia", -] - -[[package]] -name = "iced_wgpu" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff144a999b0ca0f8a10257934500060240825c42e950ec0ebee9c8ae30561c13" -dependencies = [ - "bitflags 2.11.0", - "bytemuck", - "cryoglyph", - "futures 0.3.32", - "glam", - "guillotiere", - "iced_debug", - "iced_graphics", - "log", - "resvg 0.45.1", - "rustc-hash 2.1.2", - "thiserror 2.0.18", - "wgpu", -] - -[[package]] -name = "iced_widget" -version = "0.14.2" +name = "iana-time-zone" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1596afa0d3109c2618e8bc12bae6c11d3064df8f95c42dfce570397dbe957ab" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ - "iced_renderer", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", "log", - "num-traits", - "rustc-hash 2.1.2", - "thiserror 2.0.18", - "unicode-segmentation", + "wasm-bindgen", + "windows-core", ] [[package]] -name = "iced_winit" -version = "0.14.0" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7dbedc47562d1de3b9707d939f678b88c382004b7ab5a18f7a7dd723162d75" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "iced_debug", - "iced_program", - "log", - "mundy", - "rustc-hash 2.1.2", - "thiserror 2.0.18", - "tracing", - "wasm-bindgen-futures", - "web-sys", - "window_clipboard", - "winit", + "cc", ] [[package]] name = "icu_collections" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd" dependencies = [ "displaydoc", "potential_utf", @@ -3132,9 +2359,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0" dependencies = [ "displaydoc", "litemap", @@ -3145,30 +2372,34 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599" dependencies = [ "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", + "utf16_iter", + "utf8_iter", + "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc" [[package]] name = "icu_properties" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -3179,15 +2410,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf" [[package]] name = "icu_provider" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514" dependencies = [ "displaydoc", "icu_locale_core", @@ -3198,12 +2429,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "ident_case" version = "1.0.1" @@ -3223,24 +2448,14 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", ] -[[package]] -name = "if-addrs" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b2eeee38fef3aa9b4cc5f1beea8a2444fc00e7377cafae396de3f5c2065e24" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - [[package]] name = "image" version = "0.25.10" @@ -3251,18 +2466,18 @@ dependencies = [ "byteorder-lite", "color_quant", "exr", - "gif 0.14.1", + "gif", "image-webp", "moxcms", "num-traits", - "png 0.18.1", + "png", "qoi", "ravif", "rayon", "rgb", "tiff", - "zune-core 0.5.1", - "zune-jpeg 0.5.15", + "zune-core", + "zune-jpeg", ] [[package]] @@ -3277,64 +2492,37 @@ dependencies = [ [[package]] name = "imagesize" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285" - -[[package]] -name = "imagesize" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" +checksum = "65b27460c2c92b037f3f94c538ed9a3342f3fdf923606781629ccb35f82d042a" [[package]] name = "imgref" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" - -[[package]] -name = "impl-more" -version = "0.1.9" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" +checksum = "6e44b0a4eaa4c82f441d50a963f2d5f05a787240aeee097597033e72accfd22f" [[package]] name = "impl-more" -version = "0.3.1" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a84fd5aa25fae5c0f4a33d9cac2ca017fc622cbd089be2229993514990f870" +checksum = "2d3a73c82a0b0747dba739b380c046a140b5ae747234bf701df3460282da7193" [[package]] name = "indexmap" -version = "2.13.1" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", -] - -[[package]] -name = "indicatif" -version = "0.17.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" -dependencies = [ - "console", - "number_prefix", - "portable-atomic", - "unicode-width", - "web-time", + "hashbrown 0.17.1", ] [[package]] name = "indoc" -version = "2.0.7" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +checksum = "a37b2691796cffeb8a8cd305ac66e65841559f147f4e63231d0eafa4db5384d1" dependencies = [ "rustversion", ] @@ -3347,52 +2535,14 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", + "syn 2.0.119", ] [[package]] name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] name = "is_terminal_polyfill" @@ -3418,6 +2568,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -3426,10 +2585,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383" dependencies = [ + "defmt", + "jiff-core", "jiff-static", "log", "portable-atomic", @@ -3438,30 +2599,25 @@ dependencies = [ ] [[package]] -name = "jiff-static" -version = "0.2.23" +name = "jiff-core" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" dependencies = [ - "proc-macro2", - "quote", - "syn", + "defmt", + "log", ] [[package]] -name = "jni" -version = "0.21.1" +name = "jiff-static" +version = "0.2.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c" dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", ] [[package]] @@ -3473,10 +2629,10 @@ dependencies = [ "cfg-if", "combine", "jni-macros", - "jni-sys 0.4.1", + "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.18", + "thiserror", "walkdir", "windows-link", ] @@ -3491,16 +2647,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn", -] - -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", + "syn 2.0.119", ] [[package]] @@ -3519,36 +2666,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.94" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] [[package]] name = "jsonschema" -version = "0.37.4" +version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73c9ffb2b5c56d58030e1b532d8e8389da94590515f118cf35b5cb68e4764a7e" +checksum = "65de752665c4a06528af6759955eecf60ba73ae7442d35349963a09c8031cd71" dependencies = [ "ahash", "bytecount", @@ -3557,75 +2703,73 @@ dependencies = [ "fancy-regex", "fraction", "getrandom 0.3.4", - "idna", "itoa", + "jsonschema-regex", + "jsonschema-value", "num-cmp", "num-traits", "percent-encoding", - "referencing 0.37.4", + "referencing 0.60.1", "regex", - "regex-syntax", "serde", "serde_json", + "strum 0.28.0", "unicode-general-category", "uuid-simd", ] [[package]] -name = "kamadak-exif" -version = "0.6.1" +name = "jsonschema-regex" +version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1130d80c7374efad55a117d715a3af9368f0fa7a2c54573afc15a188cd984837" +checksum = "6f8a848331614144c03e815fb931f238c9e342bd7ab501b29e35d647e7cd5688" dependencies = [ - "mutate_once", + "regex-syntax", ] [[package]] -name = "khronos-egl" -version = "6.0.0" +name = "jsonschema-value" +version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" +checksum = "5a5a947ef798eeacacb43edf29b1d0ab64bdf5e2c9fbcaad10ee8663d922b544" dependencies = [ - "libc", - "libloading", - "pkg-config", + "ahash", + "bytecount", + "fraction", + "getrandom 0.3.4", + "memchr", + "num-cmp", + "num-traits", + "serde_json", + "zmij", ] [[package]] -name = "khronos_api" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" - -[[package]] -name = "kurbo" -version = "0.10.4" +name = "konst" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1618d4ebd923e97d67e7cd363d80aef35fe961005cbbbb3d2dad8bdd1bc63440" +checksum = "f660d5f887e3562f9ab6f4a14988795b694099d66b4f5dedc02d197ba9becb1d" dependencies = [ - "arrayvec", - "smallvec", + "const_panic", + "konst_proc_macros", + "typewit", ] [[package]] -name = "kurbo" -version = "0.11.3" +name = "konst_proc_macros" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" -dependencies = [ - "arrayvec", - "euclid", - "smallvec", -] +checksum = "e037a2e1d8d5fdbd49b16a4ea09d5d6401c1f29eca5ff29d03d3824dba16256a" [[package]] name = "kurbo" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7564e90fe3c0d5771e1f0bc95322b21baaeaa0d9213fa6a0b61c99f8b17b3bfb" +checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" dependencies = [ "arrayvec", "euclid", + "polycool", "smallvec", ] @@ -3637,15 +2781,9 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] name = "lazy_static" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" [[package]] name = "lebe" @@ -3655,15 +2793,15 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" [[package]] name = "libfuzzer-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" dependencies = [ "arbitrary", "cc", @@ -3687,56 +2825,24 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" dependencies = [ - "bitflags 2.11.0", "libc", - "plain", - "redox_syscall 0.7.4", -] - -[[package]] -name = "lilt" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67562e5eff6b20553fa9be1c503356768420994e28f67e3eafe6f41910e57ad" -dependencies = [ - "web-time", -] - -[[package]] -name = "linebender_resource_handle" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" - -[[package]] -name = "linkme" -version = "0.3.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e3283ed2d0e50c06dd8602e0ab319bb048b6325d0bba739db64ed8205179898" -dependencies = [ - "linkme-impl", ] [[package]] -name = "linkme-impl" -version = "0.3.35" +name = "link-section" +version = "0.19.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5cec0ec4228b4853bb129c84dbf093a27e6c7a20526da046defc334a1b017f7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "39c29a617ce3df32c08497bdc1ab6e2376e0b17948ac166a2fbe5977c5954cd9" [[package]] -name = "linux-raw-sys" -version = "0.4.15" +name = "linktime-proc-macro" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "7e57c38c1e860fd37c604281cdfb1dd2216977fd76a50f85ba2f388ef3219616" [[package]] name = "linux-raw-sys" @@ -3746,21 +2852,15 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "litrs" -version = "1.0.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "llama-cpp-bindings" -version = "0.16.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0cab532a93dac3f03c6e3923fd24d91434bdf4142dcec89af08842a5e791ad7" +checksum = "8cba492d27e5561fe423205eb14ab51854151e48bf732bbc717f7986a5f64e56" dependencies = [ "encoding_rs", "enumflags2", @@ -3776,89 +2876,89 @@ dependencies = [ "once_cell", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror", "toktrie", ] [[package]] name = "llama-cpp-bindings-build" -version = "0.16.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b281e0ae2b5ad140756ebe47c5e00e5e17f1a08e890cdb7601d856a2f2da59b4" +checksum = "31cc4171a52ead66ab2480630dc2d589b6ca6aca1ed43949cda23abcd429943c" dependencies = [ "bindgen", "cc", "cmake", "find_cuda_helper", "llama-cpp-wrapper-sources", - "thiserror 2.0.18", + "thiserror", ] [[package]] name = "llama-cpp-bindings-sys" -version = "0.16.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe502d4a74ec5d93a2a78917c9c4223826e2b9466b2c5b869357e51fb7116a8" +checksum = "7b45cb652a6c60f065b22e29be57a72c38b9d13247bb15c6edd150b18d4f9dc1" dependencies = [ "llama-cpp-bindings-build", ] [[package]] name = "llama-cpp-bindings-types" -version = "0.16.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002e2c1fc09748fb2b9914c660a8661368cfef18d1b205d2fd14f33a979e5b48" +checksum = "f200360a50320173ea1eb8063cf51ef2ba5d0fee6168d528a6431ed5fc6e7bb2" dependencies = [ "serde", "serde_json", - "thiserror 2.0.18", + "thiserror", ] [[package]] name = "llama-cpp-error-recorder" -version = "0.16.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d181159c5b293f11fddb23d3fd012277110a8405e24a6c0a454329ce859627fe" +checksum = "7de19c6c96c4d610b79fe1cfbbd40adff64e867cd8bf6bf6c69f75fcc4cc6d23" dependencies = [ "log", ] [[package]] name = "llama-cpp-ffi-status" -version = "0.16.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1c9ab4956a5f38e11df94e3064a77af2dc3d66ec900720e9409d43435e315e" +checksum = "942ab7989e058a65410f3cd332bed2a75c4c05f64b41392c1fb93d53f4c3e593" dependencies = [ "llama-cpp-bindings-sys", - "thiserror 2.0.18", + "thiserror", ] [[package]] name = "llama-cpp-gbnf" -version = "0.16.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "484337250ef83f370320f9a262cb98074fc7777545b13363dfa50925b518c785" +checksum = "9456faa4fcb02bf221f176d088e2a0146cc9a56c35015cac89413a4f6056487b" dependencies = [ "llama-cpp-bindings-sys", "llama-cpp-ffi-status", - "thiserror 2.0.18", + "thiserror", ] [[package]] name = "llama-cpp-log-decoder" -version = "0.16.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af82bb7e5f57250f5d4cf0b7ae93730f0f38676367360eb512994c1c823f8132" +checksum = "a8129eec443ccdb124fc172cf33db7d0e87f975868493b8b0279b6d6407cc334" [[package]] name = "llama-cpp-wrapper-sources" -version = "0.16.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c490620f948a5f26cde3ce443c70fe9e91d04042bd5a195d0e6924340566130" +checksum = "d7f93c691c97cef03d185f7f46815bbcb00f00c21bdcd5d16b62754f189e682c" dependencies = [ "serde", "serde_json", - "thiserror 2.0.18", + "thiserror", ] [[package]] @@ -3904,6 +3004,15 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lock_api" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65d91b22da179613dcf4d6ec8349563f52a363f6849e03b00dd0a185ec4a59af" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" @@ -3919,35 +3028,28 @@ dependencies = [ "imgref", ] -[[package]] -name = "lru" -version = "0.16.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" - [[package]] name = "lru-slab" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" [[package]] -name = "macro_registry" -version = "0.6.9" +name = "lz4_flex" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c03fc749d06e1000766283015673e91aea121f30c60b7445681f2248e4994c" +checksum = "7ef0d4ed8669f8f8826eb00dc878084aa8f253506c4fd5e8f58f5bce72ddb97e" dependencies = [ - "module_path_extractor", - "syn", + "twox-hash", ] [[package]] -name = "malloc_buf" -version = "0.0.6" +name = "matchers" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "libc", + "regex-automata", ] [[package]] @@ -3962,25 +3064,19 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memmap2" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" +checksum = "6b0ca1155a9ecc25aa12e8313a119f32edf0255fea2f44839c4ac0a12b2ac157" dependencies = [ "libc", ] -[[package]] -name = "memo-map" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" - [[package]] name = "memoffset" version = "0.9.1" @@ -3991,19 +3087,10 @@ dependencies = [ ] [[package]] -name = "metal" -version = "0.32.0" +name = "micromap" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" -dependencies = [ - "bitflags 2.11.0", - "block", - "core-graphics-types 0.2.0", - "foreign-types 0.5.0", - "log", - "objc", - "paste", -] +checksum = "c2a86d3146ed3995b5913c414f6664344b9617457320782e64f0bb44afd49d74" [[package]] name = "mime" @@ -4023,25 +3110,22 @@ dependencies = [ [[package]] name = "minijinja" -version = "2.19.0" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "805bfd7352166bae857ee569628b52bcd85a1cecf7810861ebceb1686b72b75d" +checksum = "90086695417a271bc96e6cb50618555614ca48fa3bb0d9aa96e3468b0f84431e" dependencies = [ - "memo-map", "serde", - "serde_json", ] [[package]] name = "minijinja-contrib" -version = "2.19.0" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45092d80391870622fcf3bd82f5d2af18f99533ea60debb4bc9db0c76f0e809a" +checksum = "9c8e63b8c511fbb281cc19a1f731acd0903886ab6fbaf8399240ca68e5626568" dependencies = [ + "jiff", "minijinja", - "serde", "textwrap", - "time", "unicode_categories", ] @@ -4061,29 +3145,33 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" -version = "1.2.0" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.61.2", ] [[package]] -name = "moddef" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e519fd9c6131c1c9a4a67f8bdc4f32eb4105b16c1468adea1b8e68c98c85ec4" - -[[package]] -name = "module_path_extractor" -version = "0.6.9" +name = "more-asserts" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ec8ded8dfe4b676fb3dc4341830262395371d2113117bec13b40e830a1dea7" +checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" [[package]] name = "moxcms" @@ -4095,117 +3183,13 @@ dependencies = [ "pxfm", ] -[[package]] -name = "mundy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523813c9e194ec43693805214eb112551f99382115b67f38600d724a692e7e8b" -dependencies = [ - "android-build", - "async-io", - "cfg-if", - "dispatch", - "futures-channel", - "futures-lite", - "jni 0.21.1", - "ndk-context", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-foundation 0.3.2", - "pin-project-lite", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows 0.62.2", - "zbus", -] - -[[package]] -name = "mutate_once" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" - -[[package]] -name = "naga" -version = "27.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "066cf25f0e8b11ee0df221219010f213ad429855f57c494f995590c861a9a7d8" -dependencies = [ - "arrayvec", - "bit-set", - "bitflags 2.11.0", - "cfg-if", - "cfg_aliases", - "codespan-reporting", - "half", - "hashbrown 0.16.1", - "hexf-parse", - "indexmap", - "libm", - "log", - "num-traits", - "once_cell", - "rustc-hash 1.1.0", - "spirv", - "thiserror 2.0.18", - "unicode-ident", -] - [[package]] name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand 0.8.6", -] - -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.11.0", - "jni-sys 0.3.1", - "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +checksum = "8628de41fe064cc3f0cf07f3d299ee3e73521adaff72278731d5c8cae3797873" dependencies = [ - "jni-sys 0.3.1", + "rand 0.9.5", ] [[package]] @@ -4216,14 +3200,24 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nix" -version = "0.30.1" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.2", "cfg-if", "cfg_aliases", "libc", + "memoffset", +] + +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", ] [[package]] @@ -4251,6 +3245,24 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num" version = "0.4.3" @@ -4267,9 +3279,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", @@ -4287,14 +3299,15 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ + "bytemuck", "num-traits", ] [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-derive" @@ -4304,25 +3317,24 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" dependencies = [ "num-traits", ] [[package]] name = "num-iter" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -4345,584 +3357,249 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", ] [[package]] -name = "num_enum" -version = "0.7.6" +name = "objc2" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8" dependencies = [ - "num_enum_derive", - "rustversion", + "objc2-encode", ] [[package]] -name = "num_enum_derive" -version = "0.7.6" +name = "objc2-core-foundation" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", + "bitflags 2.13.2", + "dispatch2", + "objc2", ] [[package]] -name = "number_prefix" -version = "0.4.0" +name = "objc2-encode" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] -name = "objc" -version = "0.2.7" +name = "objc2-foundation" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "malloc_buf", + "bitflags 2.13.2", + "objc2", ] [[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - -[[package]] -name = "objc2" -version = "0.5.2" +name = "objc2-io-kit" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" dependencies = [ - "objc-sys", - "objc2-encode", + "libc", + "objc2-core-foundation", ] [[package]] -name = "objc2" -version = "0.6.4" +name = "objc2-open-directory" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +checksum = "bb82bed227edf5201dfedf072bba4015a33d3d4a98519837295a90f0a23f676d" dependencies = [ - "objc2-encode", + "objc2", + "objc2-core-foundation", + "objc2-foundation", ] [[package]] -name = "objc2-app-kit" -version = "0.2.2" +name = "objc2-system-configuration" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" dependencies = [ - "bitflags 2.11.0", - "block2 0.5.1", - "libc", - "objc2 0.5.2", - "objc2-core-data 0.2.2", - "objc2-core-image 0.2.2", - "objc2-foundation 0.2.2", - "objc2-quartz-core 0.2.2", + "objc2-core-foundation", ] [[package]] -name = "objc2-app-kit" -version = "0.3.2" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.11.0", - "block2 0.6.2", - "libc", - "objc2 0.6.4", - "objc2-cloud-kit 0.3.2", - "objc2-core-data 0.3.2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image 0.3.2", - "objc2-core-text", - "objc2-core-video", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", -] +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "objc2-cloud-kit" -version = "0.2.2" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" -dependencies = [ - "bitflags 2.11.0", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation 0.2.2", -] +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] -name = "objc2-cloud-kit" -version = "0.3.2" +name = "oneshot" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" -dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", - "objc2-foundation 0.3.2", -] +checksum = "269bca4c2591a28585d6bf10d9ed0332b7d76900a1b02bec41bdc3a2cdcda107" [[package]] -name = "objc2-contacts" -version = "0.2.2" +name = "openssl-probe" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] -name = "objc2-core-data" -version = "0.2.2" +name = "option-ext" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" -dependencies = [ - "bitflags 2.11.0", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] -name = "objc2-core-data" -version = "0.3.2" +name = "os_str_bytes" +version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", - "objc2-foundation 0.3.2", + "memchr", ] [[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.11.0", - "dispatch2", - "objc2 0.6.4", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags 2.11.0", - "dispatch2", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal", -] - -[[package]] -name = "objc2-core-image" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" -dependencies = [ - "objc2 0.6.4", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-core-location" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-contacts", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-core-graphics", -] - -[[package]] -name = "objc2-core-video" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" -dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-io-surface", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" -dependencies = [ - "bitflags 2.11.0", - "block2 0.5.1", - "dispatch", - "libc", - "objc2 0.5.2", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.11.0", - "block2 0.6.2", - "libc", - "objc2 0.6.4", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-link-presentation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-metal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" -dependencies = [ - "bitflags 2.11.0", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags 2.11.0", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.11.0", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-symbols" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" -dependencies = [ - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" -dependencies = [ - "bitflags 2.11.0", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-cloud-kit 0.2.2", - "objc2-core-data 0.2.2", - "objc2-core-image 0.2.2", - "objc2-core-location", - "objc2-foundation 0.2.2", - "objc2-link-presentation", - "objc2-quartz-core 0.2.2", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" -dependencies = [ - "bitflags 2.11.0", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "open" -version = "5.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3bab717c29a857abf75fcef718d441ec7cb2725f937343c734740a985d37fd" -dependencies = [ - "is-wsl", - "libc", - "pathdiff", -] - -[[package]] -name = "openssl" -version = "0.10.81" +name = "outref" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" -dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "foreign-types 0.3.2", - "libc", - "openssl-macros", - "openssl-sys", -] +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" [[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +name = "paddler_agent" +version = "5.0.0" dependencies = [ - "proc-macro2", - "quote", - "syn", + "anyhow", + "async-trait", + "futures-util", + "http 1.5.0", + "log", + "nix", + "paddler_agent_decision", + "paddler_agent_embeddings", + "paddler_agent_runtime", + "paddler_agent_status", + "paddler_agent_text_generation", + "paddler_blocking_thread_fixture", + "paddler_cache_dir", + "paddler_download_manager", + "paddler_frame_writer", + "paddler_inference_parameters", + "paddler_local_http_fixture", + "paddler_messaging", + "paddler_model_source", + "paddler_outbox", + "paddler_request_registry", + "paddler_spawn_resuming_panic", + "serde", + "serde_json", + "tempfile", + "thiserror", + "tokio", + "tokio-tungstenite", + "tokio-util", + "trzcina", ] [[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "openssl-sys" -version = "0.9.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" +name = "paddler_agent_decision" +version = "5.0.0" dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", + "llama-cpp-bindings", + "log", + "paddler_agent_pointer_head", + "paddler_agent_runtime", + "paddler_agent_status", + "paddler_inference_parameters", + "paddler_messaging", + "thiserror", + "tokio", + "tokio-util", ] [[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "orbclient" -version = "0.3.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59aed3b33578edcfa1bc96a321d590d31832b6ad55a26f0313362ce687e9abd6" +name = "paddler_agent_embeddings" +version = "5.0.0" dependencies = [ - "libc", - "libredox", + "llama-cpp-bindings", + "log", + "paddler_agent_runtime", + "paddler_agent_status", + "paddler_inference_parameters", + "paddler_messaging", + "thiserror", + "tokio", + "tokio-util", ] [[package]] -name = "ordered-float" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" +name = "paddler_agent_pointer_head" +version = "5.0.0" dependencies = [ - "num-traits", + "icu_normalizer", + "llama-cpp-bindings", + "thiserror", ] [[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +name = "paddler_agent_runtime" +version = "5.0.0" dependencies = [ - "futures-core", - "pin-project-lite", + "llama-cpp-bindings", + "llama-cpp-bindings-sys", + "log", + "paddler_agent_status", + "paddler_inference_parameters", + "paddler_messaging", + "paddler_spawn_resuming_panic", + "parking_lot 0.13.0", + "thiserror", + "tokio", + "tokio-util", ] [[package]] -name = "outref" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" - -[[package]] -name = "owned_ttf_parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +name = "paddler_agent_status" +version = "5.0.0" dependencies = [ - "ttf-parser", + "dashmap", + "paddler_messaging", + "parking_lot 0.13.0", + "tokio", + "tokio-test", ] [[package]] -name = "paddler_agent" -version = "4.1.0" +name = "paddler_agent_text_generation" +version = "5.0.0" dependencies = [ - "anyhow", - "async-stream", - "async-trait", - "bytes 1.11.1", - "dashmap", "encoding_rs", - "futures 0.3.32", - "futures-util", - "hf-hub", - "indoc", "llama-cpp-bindings", - "llama-cpp-bindings-sys", - "llama-cpp-bindings-types", "log", "minijinja", "minijinja-contrib", - "nanoid", + "paddler_agent_runtime", "paddler_agent_status", - "paddler_cache_dir", - "paddler_download_manager", "paddler_image_decoder", "paddler_inference_parameters", - "paddler_local_http_fixture", "paddler_messaging", - "paddler_model_source", - "paddler_request_registry", + "paddler_sha256_hex", "paddler_tool_call_validator", - "parking_lot", - "rand 0.9.4", - "reqwest 0.12.28", - "serde", + "rand 0.10.3", "serde_json", - "shellexpand", - "tempfile", - "thiserror 2.0.18", + "thiserror", "tokio", - "tokio-stream", - "tokio-test", - "tokio-tungstenite", "tokio-util", - "trzcina", - "url", -] - -[[package]] -name = "paddler_agent_status" -version = "4.1.0" -dependencies = [ - "dashmap", - "paddler_messaging", - "parking_lot", - "tokio", - "tokio-test", ] [[package]] name = "paddler_balancer" -version = "4.1.0" +version = "5.0.0" dependencies = [ "actix-cors", "actix-web", @@ -4930,80 +3607,92 @@ dependencies = [ "actix-ws", "anyhow", "askama", - "async-stream", "async-trait", - "bytes 1.11.1", + "bytes", "cadence", "dashmap", - "esbuild-metafile", - "futures 0.3.32", + "futures", "futures-util", "indoc", "llama-cpp-bindings-types", "log", "mime_guess", "nanoid", + "paddler_blocking_thread_fixture", + "paddler_dashboard_assets", + "paddler_frame_writer", "paddler_inference_parameters", "paddler_messaging", "paddler_openai_response_format_validator", + "paddler_openai_translation", + "paddler_outbox", "paddler_request_registry", + "paddler_spawn_resuming_panic", "paddler_state_database", - "parking_lot", + "paddler_typesafe_translation", + "parking_lot 0.13.0", + "rand 0.10.3", "rust-embed", "serde", "serde_json", "socket2", "tempfile", - "thiserror 2.0.18", "tokio", "tokio-stream", - "tokio-test", "tokio-util", "trzcina", ] +[[package]] +name = "paddler_blocking_thread_fixture" +version = "5.0.0" +dependencies = [ + "tokio", +] + [[package]] name = "paddler_bootstrap" -version = "4.1.0" +version = "5.0.0" dependencies = [ "actix-web", "anyhow", - "async-trait", "log", "nanoid", "paddler_agent", - "paddler_agent_status", + "paddler_agent_runtime", "paddler_balancer", - "paddler_inference_parameters", + "paddler_client", "paddler_messaging", + "paddler_model_source", + "paddler_outbox", "paddler_state_database", "paddler_test_cluster_harness", - "reqwest 0.12.28", + "reqwest", "serde_json", "tempfile", - "thiserror 2.0.18", + "thiserror", "tokio", "tokio-util", "trzcina", + "url", ] [[package]] name = "paddler_cache_dir" -version = "4.1.0" +version = "5.0.0" dependencies = [ "fslock", - "sha2", + "paddler_sha256_hex", "tempfile", - "thiserror 2.0.18", + "thiserror", "tokio", "tokio-util", ] [[package]] name = "paddler_cli" -version = "4.1.0" +version = "5.0.0" dependencies = [ - "actix-rt", "actix-web", "anyhow", "async-trait", @@ -5012,6 +3701,7 @@ dependencies = [ "env_logger", "paddler_balancer", "paddler_bootstrap", + "paddler_model_source", "paddler_state_database", "tokio-util", "trzcina", @@ -5019,43 +3709,52 @@ dependencies = [ [[package]] name = "paddler_cli_tests" -version = "4.1.0" +version = "5.0.0" dependencies = [ + "actix-web", "anyhow", "async-trait", "futures-util", - "log", + "libc", + "llama-cpp-bindings", "nix", + "paddler_agent_text_generation", "paddler_balancer", + "paddler_bootstrap", "paddler_cli", "paddler_client", "paddler_inference_parameters", "paddler_messaging", + "paddler_openai_response_format_validator", "paddler_test_cluster_harness", - "reqwest 0.12.28", + "paddler_tests", + "reqwest", + "seccompiler", "serde_json", - "thiserror 2.0.18", + "tempfile", + "thiserror", "tokio", + "tokio-tungstenite", "tokio-util", + "trzcina", "url", ] [[package]] name = "paddler_client" -version = "4.1.0" +version = "5.0.0" dependencies = [ - "bytes 1.11.1", + "bytes", "futures-util", - "http 1.4.0", + "http 1.5.0", "log", "nanoid", - "paddler_local_http_fixture", "paddler_messaging", - "parking_lot", - "reqwest 0.12.28", + "parking_lot 0.13.0", + "reqwest", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror", "tokio", "tokio-stream", "tokio-tungstenite", @@ -5063,118 +3762,92 @@ dependencies = [ "url", ] +[[package]] +name = "paddler_dashboard_assets" +version = "5.0.0" +dependencies = [ + "esbuild-metafile", + "tempfile", + "thiserror", +] + [[package]] name = "paddler_download_manager" -version = "4.1.0" +version = "5.0.0" dependencies = [ - "bytes 1.11.1", + "bytes", "futures-util", "headers", - "http 1.4.0", + "http 1.5.0", "paddler_local_http_fixture", - "reqwest 0.12.28", + "reqwest", "tempfile", - "thiserror 2.0.18", + "thiserror", "tokio", "tokio-util", "url", ] [[package]] -name = "paddler_gui" -version = "4.1.0" -dependencies = [ - "async-stream", - "clap", - "command_handler", - "env_logger", - "iced", - "if-addrs", - "log", - "open", - "paddler_balancer", - "paddler_bootstrap", - "paddler_inference_parameters", - "paddler_messaging", - "paddler_state_database", - "paddler_test_cluster_harness", - "statum", - "thiserror 2.0.18", - "tokio", - "tokio-util", - "trzcina", -] - -[[package]] -name = "paddler_gui_tests" -version = "4.1.0" +name = "paddler_frame_writer" +version = "5.0.0" dependencies = [ - "futures 0.3.32", - "iced_test", - "paddler_balancer", - "paddler_bootstrap", - "paddler_gui", - "paddler_messaging", - "paddler_test_cluster_harness", - "reqwest 0.12.28", - "thiserror 2.0.18", + "futures", + "futures-util", "tokio", "tokio-util", - "trzcina", ] [[package]] name = "paddler_image_decoder" -version = "4.1.0" +version = "5.0.0" dependencies = [ - "base64", + "base64 0.23.1", "image", "log", "paddler_messaging", - "resvg 0.46.0", - "thiserror 2.0.18", + "resvg", + "thiserror", ] [[package]] name = "paddler_inference_parameters" -version = "4.1.0" +version = "5.0.0" dependencies = [ + "llama-cpp-bindings-types", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror", ] [[package]] name = "paddler_local_http_fixture" -version = "4.1.0" +version = "5.0.0" dependencies = [ - "http 1.4.0", + "http 1.5.0", "httparse", + "nix", + "paddler_tcp_buffer_limits", "tokio", ] [[package]] name = "paddler_messaging" -version = "4.1.0" +version = "5.0.0" dependencies = [ - "base64", - "encoding_rs", "llama-cpp-bindings-types", - "log", - "nanoid", "paddler_inference_parameters", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror", "tokio", "url", ] [[package]] name = "paddler_model_source" -version = "4.1.0" +version = "5.0.0" dependencies = [ - "async-trait", "hf-hub", "log", "paddler_agent_status", @@ -5182,9 +3855,10 @@ dependencies = [ "paddler_download_manager", "paddler_local_http_fixture", "paddler_messaging", - "reqwest 0.12.28", + "percent-encoding", + "reqwest", "tempfile", - "thiserror 2.0.18", + "thiserror", "tokio", "tokio-util", "url", @@ -5192,74 +3866,118 @@ dependencies = [ [[package]] name = "paddler_openai_response_format_validator" -version = "4.1.0" +version = "5.0.0" dependencies = [ "jsonschema", "serde_json", - "thiserror 2.0.18", + "thiserror", "yaml-rust2", ] +[[package]] +name = "paddler_openai_translation" +version = "5.0.0" +dependencies = [ + "llama-cpp-bindings-types", + "nanoid", + "paddler_inference_parameters", + "paddler_messaging", + "paddler_openai_response_format_validator", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "paddler_opencode_tests" -version = "4.1.0" +version = "5.0.0" dependencies = [ - "anyhow", - "paddler_test_cluster_harness", "paddler_tests", "serde_json", "tempfile", - "thiserror 2.0.18", + "thiserror", "tokio", "url", ] +[[package]] +name = "paddler_outbox" +version = "5.0.0" +dependencies = [ + "parking_lot 0.13.0", + "tokio", +] + [[package]] name = "paddler_request_registry" -version = "4.1.0" +version = "5.0.0" dependencies = [ "dashmap", + "nanoid", + "tokio", +] + +[[package]] +name = "paddler_sha256_hex" +version = "5.0.0" +dependencies = [ + "sha2", +] + +[[package]] +name = "paddler_spawn_resuming_panic" +version = "5.0.0" +dependencies = [ "tokio", ] [[package]] name = "paddler_state_database" -version = "4.1.0" +version = "5.0.0" dependencies = [ - "async-trait", - "log", - "paddler_inference_parameters", "paddler_messaging", - "parking_lot", + "paddler_spawn_resuming_panic", + "parking_lot 0.13.0", "serde", "serde_json", "shellexpand", "tempfile", - "thiserror 2.0.18", + "thiserror", "tokio", "url", ] +[[package]] +name = "paddler_tcp_buffer_limits" +version = "5.0.0" +dependencies = [ + "tempfile", + "thiserror", +] + [[package]] name = "paddler_test_cluster_harness" -version = "4.1.0" +version = "5.0.0" dependencies = [ "anyhow", "async-openai", "async-trait", - "base64", + "base64 0.23.1", "data-url", + "futures", "futures-util", "hf-hub", + "paddler_agent_status", "paddler_balancer", "paddler_client", "paddler_inference_parameters", "paddler_messaging", + "paddler_model_source", "quick-xml", - "serde", + "reqwest", "serde_json", "tempfile", - "thiserror 2.0.18", + "thiserror", "tokio", "tokio-tungstenite", "tokio-util", @@ -5269,49 +3987,72 @@ dependencies = [ [[package]] name = "paddler_tests" -version = "4.1.0" +version = "5.0.0" dependencies = [ "anyhow", "async-openai", "async-trait", + "clap", "futures-util", - "http 1.4.0", + "http 1.5.0", + "llama-cpp-bindings", + "llama-cpp-bindings-sys", "llama-cpp-bindings-types", "log", + "paddler_agent", + "paddler_agent_decision", + "paddler_agent_embeddings", + "paddler_agent_pointer_head", + "paddler_agent_runtime", + "paddler_agent_status", + "paddler_agent_text_generation", "paddler_balancer", + "paddler_blocking_thread_fixture", "paddler_bootstrap", + "paddler_cache_dir", "paddler_client", + "paddler_download_manager", "paddler_inference_parameters", "paddler_local_http_fixture", "paddler_messaging", + "paddler_model_source", "paddler_openai_response_format_validator", + "paddler_openai_translation", "paddler_state_database", + "paddler_tcp_buffer_limits", "paddler_test_cluster_harness", - "reqwest 0.12.28", + "paddler_tool_call_validator", + "paddler_typesafe_translation", + "reqwest", + "serde", "serde_json", + "syn 3.0.9", "tempfile", "tokio", "tokio-tungstenite", "tokio-util", - "trzcina", ] [[package]] name = "paddler_tool_call_validator" -version = "4.1.0" +version = "5.0.0" dependencies = [ "jsonschema", "llama-cpp-bindings-types", "paddler_messaging", "serde_json", - "thiserror 2.0.18", + "thiserror", ] [[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +name = "paddler_typesafe_translation" +version = "5.0.0" +dependencies = [ + "paddler_messaging", + "serde", + "serde_json", + "thiserror", +] [[package]] name = "parking_lot" @@ -5319,8 +4060,18 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ - "lock_api", - "parking_lot_core", + "lock_api 0.4.14", + "parking_lot_core 0.9.12", +] + +[[package]] +name = "parking_lot" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9cf937ca4e1d0b7578784559063422fc39ae7c5a00f02471f2614d5486fd226" +dependencies = [ + "lock_api 0.5.0", + "parking_lot_core 0.10.0", ] [[package]] @@ -5331,7 +4082,19 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "parking_lot_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cfff060068cc18aa7815010fcaf80c68c7fb9e85fc7f5c0f9bf142c21d19826" +dependencies = [ + "libc", + "redox_syscall 0.9.5", "smallvec", "windows-link", ] @@ -5368,22 +4131,22 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -5392,47 +4155,11 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - [[package]] name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - -[[package]] -name = "png" -version = "0.17.16" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "png" @@ -5440,56 +4167,51 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.2", "crc32fast", "fdeflate", "flate2", - "miniz_oxide", + "miniz_oxide 0.8.9", ] [[package]] -name = "polling" -version = "3.11.0" +name = "polycool" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix 1.1.4", - "windows-sys 0.61.2", + "arrayvec", ] [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "portable-atomic-util" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" dependencies = [ "portable-atomic", ] [[package]] name = "potential_utf" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ "zerovec", ] [[package]] name = "powerfmt" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" [[package]] name = "ppv-lite86" @@ -5500,22 +4222,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "presser" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" - -[[package]] -name = "pretty_assertions" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" -dependencies = [ - "diff", - "yansi", -] - [[package]] name = "prettyplease" version = "0.2.37" @@ -5523,51 +4229,75 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.119", ] [[package]] -name = "proc-macro-crate" -version = "3.5.0" +name = "prettyplease" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" dependencies = [ - "toml_edit", + "proc-macro2", + "syn 3.0.9", ] [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" dependencies = [ "quote", - "syn", + "syn 2.0.119", +] + +[[package]] +name = "pulp" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "paste", + "pulp-wasm-simd-flag", + "raw-cpuid", + "reborrow", + "version_check", ] +[[package]] +name = "pulp-wasm-simd-flag" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740" + [[package]] name = "pxfm" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" [[package]] name = "qoi" @@ -5586,28 +4316,28 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.39.2" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" dependencies = [ "memchr", ] [[package]] name = "quinn" -version = "0.11.9" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" dependencies = [ - "bytes 1.11.1", + "bytes", "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.2", + "rustc-hash", "rustls", "socket2", - "thiserror 2.0.18", + "thiserror", "tokio", "tracing", "web-time", @@ -5615,22 +4345,22 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.18" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9746dbde176634f4f2f1faf2404e30a31b2bc1e9cafb5329c95d8177a18c9fc" +checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe" dependencies = [ "aws-lc-rs", - "bytes 1.11.1", - "getrandom 0.4.2", + "bytes", + "getrandom 0.4.3", "lru-slab", - "rand 0.10.1", + "rand 0.10.3", "rand_pcg", "ring", - "rustc-hash 2.1.2", + "rustc-hash", "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.18", + "thiserror", "tinyvec", "tracing", "web-time", @@ -5638,23 +4368,23 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.14" +version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -5673,46 +4403,25 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" dependencies = [ - "rand_chacha 0.9.0", + "rand_chacha", "rand_core 0.9.5", ] [[package]] name = "rand" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" dependencies = [ "chacha20", - "getrandom 0.4.2", + "getrandom 0.4.3", "rand_core 0.10.1", ] -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - [[package]] name = "rand_chacha" version = "0.9.0" @@ -5723,15 +4432,6 @@ dependencies = [ "rand_core 0.9.5", ] -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - [[package]] name = "rand_core" version = "0.9.5" @@ -5756,18 +4456,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "range-alloc" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" - -[[package]] -name = "rangemap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" - [[package]] name = "rav1e" version = "0.8.1" @@ -5795,10 +4483,10 @@ dependencies = [ "num-traits", "paste", "profiling", - "rand 0.9.4", - "rand_chacha 0.9.0", + "rand 0.9.5", + "rand_chacha", "simd_helpers", - "thiserror 2.0.18", + "thiserror", "v_frame", "wasm-bindgen", ] @@ -5819,16 +4507,19 @@ dependencies = [ ] [[package]] -name = "raw-window-handle" -version = "0.6.2" +name = "raw-cpuid" +version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.13.2", +] [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -5846,32 +4537,28 @@ dependencies = [ [[package]] name = "read-fonts" -version = "0.35.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" +checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" dependencies = [ "bytemuck", - "core_maths", - "font-types 0.10.1", + "font-types", + "once_cell", ] [[package]] -name = "read-fonts" -version = "0.37.0" +name = "reborrow" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" -dependencies = [ - "bytemuck", - "font-types 0.11.3", -] +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" [[package]] -name = "redox_syscall" -version = "0.4.1" +name = "redb" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "4ba239c1c1693315d3cc0e601db3b3965543afbf48c41730fdca2f069f510f4a" dependencies = [ - "bitflags 1.3.2", + "libc", ] [[package]] @@ -5880,47 +4567,46 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.2", ] [[package]] name = "redox_syscall" -version = "0.7.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +checksum = "580c875845b41ecb1564ad1ae4703044cef774145e3d7a94dc2b3fa7c97868fd" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.2", ] [[package]] name = "redox_users" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" dependencies = [ - "getrandom 0.2.17", "libredox", - "thiserror 2.0.18", + "thiserror", ] [[package]] name = "ref-cast" -version = "1.0.25" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.25" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -5932,31 +4618,33 @@ dependencies = [ "ahash", "fluent-uri 0.3.2", "once_cell", - "parking_lot", + "parking_lot 0.12.5", "percent-encoding", "serde_json", ] [[package]] name = "referencing" -version = "0.37.4" +version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4283168a506f0dcbdce31c9f9cce3129c924da4c6bca46e46707fcb746d2d70c" +checksum = "922268812b6f9e20b4ab0f1efe8e07036958eed7346ceddb31c777b5531fd11f" dependencies = [ "ahash", "fluent-uri 0.4.1", "getrandom 0.3.4", - "hashbrown 0.16.1", - "parking_lot", + "hashbrown 0.17.1", + "itoa", + "micromap", + "parking_lot 0.12.5", "percent-encoding", "serde_json", ] [[package]] name = "regex" -version = "1.12.3" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -5966,9 +4654,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -5983,77 +4671,31 @@ checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - -[[package]] -name = "renderdoc-sys" -version = "1.1.0" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" dependencies = [ - "base64", - "bytes 1.11.1", + "base64 0.23.1", + "bytes", "encoding_rs", "futures-core", "futures-util", - "h2 0.4.19", - "http 1.4.0", + "h2", + "http 1.5.0", "http-body", "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams 0.4.2", - "web-sys", -] - -[[package]] -name = "reqwest" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" -dependencies = [ - "base64", - "bytes 1.11.1", - "futures-core", - "futures-util", - "http 1.4.0", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", "mime_guess", "percent-encoding", "pin-project-lite", @@ -6074,42 +4716,40 @@ dependencies = [ "url", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-streams 0.5.0", + "wasm-streams", "web-sys", ] [[package]] -name = "resvg" -version = "0.45.1" +name = "reqwest-middleware" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8928798c0a55e03c9ca6c4c6846f76377427d2c1e1f7e6de3c06ae57942df43" +checksum = "07bc3f1384cffa4f274dad2d4ddd73aed32fed8f786d96c6be8aa4e5fd3c3b58" dependencies = [ - "gif 0.13.3", - "image-webp", - "log", - "pico-args", - "rgb", - "svgtypes 0.15.3", - "tiny-skia", - "usvg 0.45.1", - "zune-jpeg 0.4.21", + "anyhow", + "async-trait", + "http 1.5.0", + "reqwest", + "thiserror", + "tower-service", ] [[package]] name = "resvg" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b563218631706d614e23059436526d005b50ab5f2d506b55a17eb65c5eb83419" +checksum = "1572844b6727a1bf842190e9947641a68ac44bed83f50afb4bd1407a2564bee8" dependencies = [ - "gif 0.14.1", + "bytemuck", + "gif", "image-webp", "log", "pico-args", "rgb", - "svgtypes 0.16.1", + "svgtypes", "tiny-skia", - "usvg 0.46.0", - "zune-jpeg 0.5.15", + "usvg", + "zune-jpeg", ] [[package]] @@ -6143,18 +4783,36 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "roxmltree" -version = "0.21.1" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da7f59328b09405b181fee1018925fad50f157afb43c0c0f5c522c256dd5907" +dependencies = [ + "memchr", +] + +[[package]] +name = "russe" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" +checksum = "56b278184c4cf1f499ad5020876c9d31961b6166526fc7415ad11f113166f91b" dependencies = [ + "aho-corasick", + "bytes", + "bytestring", + "futures-util", + "impl-more", + "itoa", "memchr", + "tokio", + "tokio-util", + "tracing", ] [[package]] name = "rust-embed" -version = "8.11.0" +version = "8.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27" +checksum = "19afa5b4b6a611de00bd1bdae6ae6f39084c9399f0679c3f52d8469cf335cc23" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -6163,23 +4821,24 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.11.0" +version = "8.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa" +checksum = "e0d8afda6374eac59e066abee06d265247ebbaf3006cf878e2879e8356e34053" dependencies = [ + "mime_guess", "proc-macro2", "quote", "rust-embed-utils", "shellexpand", - "syn", + "syn 2.0.119", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.11.0" +version = "8.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" +checksum = "0d84e8ba78bd384263e5922f084cbe1b081c3b7e69add59c8fb097b879ba968a" dependencies = [ "sha2", "walkdir", @@ -6187,15 +4846,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -6208,40 +4861,25 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.11.0", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustix" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.2", "errno", "libc", - "linux-raw-sys 0.12.1", + "linux-raw-sys", "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.45" +version = "0.23.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" dependencies = [ "aws-lc-rs", - "log", "once_cell", - "ring", "rustls-pki-types", "rustls-webpki", "subtle", @@ -6250,9 +4888,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +checksum = "4a073f5dc7469f984c52ad2752b63b0807745133b6de880b7b64c1ac4c48aec4" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -6262,9 +4900,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "web-time", "zeroize", @@ -6272,13 +4910,13 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys", - "jni 0.22.4", + "jni", "log", "once_cell", "rustls", @@ -6293,15 +4931,15 @@ dependencies = [ [[package]] name = "rustls-platform-verifier-android" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" +checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" [[package]] name = "rustls-webpki" -version = "0.103.15" +version = "0.103.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" dependencies = [ "aws-lc-rs", "ring", @@ -6311,27 +4949,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "rustybuzz" -version = "0.20.1" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" -dependencies = [ - "bitflags 2.11.0", - "bytemuck", - "core_maths", - "log", - "smallvec", - "ttf-parser", - "unicode-bidi-mirroring", - "unicode-ccc", - "unicode-properties", - "unicode-script", -] +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" @@ -6339,6 +4959,12 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "safe-transmute" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944826ff8fa8093089aba3acb4ef44b9446a99a16f3bf4e74af3f77d340ab7d" + [[package]] name = "same-file" version = "1.0.6" @@ -6350,19 +4976,13 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +checksum = "9cb23c424d8ee841bb4d813677ca237e2a610067d594fec130f87d6093495ab5" dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -6370,16 +4990,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "sctk-adwaita" -version = "0.10.1" +name = "seccompiler" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" +checksum = "a4ae55de56877481d112a559bbc12667635fdaf5e005712fd4e2b2fa50ffc884" dependencies = [ - "ab_glyph", - "log", - "memmap2", - "smithay-client-toolkit 0.19.2", - "tiny-skia", + "libc", ] [[package]] @@ -6398,7 +5014,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.2", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -6415,17 +5031,11 @@ dependencies = [ "libc", ] -[[package]] -name = "self_cell" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" - [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -6454,20 +5064,20 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "serde_html_form" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0946d52b4b7e28823148aebbeceb901012c595ad737920d504fa8634bb099e6f" +checksum = "8f0346d7a342ab90f405cfc08f25d15075f944f42fcabbc5eac923829fa6d228" dependencies = [ "form_urlencoded", "indexmap", "itoa", - "ryu", "serde_core", + "zmij", ] [[package]] @@ -6497,13 +5107,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.20" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] @@ -6520,9 +5130,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -6536,19 +5146,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.2", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] name = "sha2" -version = "0.10.9" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", + "cpufeatures 0.3.1", + "digest 0.11.3", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", ] [[package]] @@ -6557,7 +5176,9 @@ version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" dependencies = [ + "bstr", "dirs", + "os_str_bytes", ] [[package]] @@ -6578,15 +5199,15 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "simd_cesu8" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" dependencies = [ "rustc_version", "simdutf8", @@ -6618,28 +5239,18 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" - -[[package]] -name = "skrifa" -version = "0.37.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841" -dependencies = [ - "bytemuck", - "read-fonts 0.35.0", -] +checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" [[package]] name = "skrifa" -version = "0.40.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" +checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" dependencies = [ "bytemuck", - "read-fonts 0.37.0", + "read-fonts", ] [[package]] @@ -6659,147 +5270,24 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.1" +version = "1.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" [[package]] name = "smawk" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" - -[[package]] -name = "smithay-client-toolkit" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" -dependencies = [ - "bitflags 2.11.0", - "calloop 0.13.0", - "calloop-wayland-source 0.3.0", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 0.38.44", - "thiserror 1.0.69", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", -] - -[[package]] -name = "smithay-client-toolkit" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" -dependencies = [ - "bitflags 2.11.0", - "calloop 0.14.4", - "calloop-wayland-source 0.4.1", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 1.1.4", - "thiserror 2.0.18", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-experimental", - "wayland-protocols-misc", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", -] - -[[package]] -name = "smithay-clipboard" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" -dependencies = [ - "libc", - "smithay-client-toolkit 0.20.0", - "wayland-backend", -] - -[[package]] -name = "smol_str" -version = "0.2.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" -dependencies = [ - "serde", -] +checksum = "e8e2fb0f499abb4d162f2bedad68f5ef91a1682b5a03596ddb67efd37768d100" [[package]] name = "socket2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "socks" -version = "0.3.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ - "byteorder", "libc", - "winapi", -] - -[[package]] -name = "softbuffer" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" -dependencies = [ - "as-raw-xcb-connection", - "bytemuck", - "fastrand", - "js-sys", - "memmap2", - "ndk", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", - "raw-window-handle", - "redox_syscall 0.5.18", - "rustix 1.1.4", - "tiny-xlib", - "tracing", - "wasm-bindgen", - "wayland-backend", - "wayland-client", - "wayland-sys", - "web-sys", "windows-sys 0.61.2", - "x11rb", -] - -[[package]] -name = "spirv" -version = "0.3.0+sdk-1.3.268.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" -dependencies = [ - "bitflags 2.11.0", ] [[package]] @@ -6815,36 +5303,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "statum" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7b355d3d23dd91652df5621cc0a41b3e831e40a8376a50b4e06020cf4922f01" -dependencies = [ - "statum-core", - "statum-macros", -] - -[[package]] -name = "statum-core" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e80c81d094f726bee8a08b3acb08119c56ee9e03e9c96dc62bd0685757fc6b04" -dependencies = [ - "futures 0.3.32", - "linkme", -] - -[[package]] -name = "statum-macros" -version = "0.6.9" +name = "statrs" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5843996c8d6017fb8046be3ccb3779c69ad41940b8d5c53421200688e18ca7" +checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" dependencies = [ - "macro_registry", - "moddef", - "proc-macro2", - "quote", - "syn", + "approx", + "num-traits", ] [[package]] @@ -6868,7 +5333,16 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ - "strum_macros", + "strum_macros 0.27.2", +] + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros 0.28.0", ] [[package]] @@ -6880,57 +5354,59 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] -name = "subtle" -version = "2.6.1" +name = "strum_macros" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] [[package]] -name = "svg_fmt" -version = "0.4.5" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "svgtypes" -version = "0.15.3" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" +checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" dependencies = [ - "kurbo 0.11.3", + "kurbo", "siphasher", ] [[package]] -name = "svgtypes" -version = "0.16.1" +name = "symlink" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" -dependencies = [ - "kurbo 0.13.0", - "siphasher", -] +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" [[package]] -name = "swash" -version = "0.2.7" +name = "syn" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "842f3cd369c2ba38966204f983eaa5e54a8e84a7d7159ed36ade2b6c335aae64" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ - "skrifa 0.40.0", - "yazi", - "zeno", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] name = "syn" -version = "2.0.117" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" dependencies = [ "proc-macro2", "quote", @@ -6948,22 +5424,28 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] -name = "sys-locale" -version = "0.3.2" +name = "sysinfo" +version = "0.39.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +checksum = "d2071df9448915b71c4fe6d25deaf1c22f12bd234f01540b77312bb8e41361e6" dependencies = [ "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "objc2-open-directory", + "windows", ] [[package]] @@ -6972,7 +5454,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.2", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -6994,37 +5476,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "textwrap" -version = "0.16.2" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +checksum = "6ecfad6c3abc80a577f2b91c1e412ee57e7a060d430b553c1b0c940974ebcd49" dependencies = [ "smawk", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", + "unicode-width", ] [[package]] @@ -7033,29 +5498,27 @@ version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] -name = "thiserror-impl" -version = "2.0.18" +name = "thread_local" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" dependencies = [ - "proc-macro2", - "quote", - "syn", + "cfg-if", ] [[package]] @@ -7069,17 +5532,16 @@ dependencies = [ "half", "quick-error", "weezl", - "zune-jpeg 0.5.15", + "zune-jpeg", ] [[package]] name = "time" -version = "0.3.47" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde_core", @@ -7089,15 +5551,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -7105,48 +5567,35 @@ dependencies = [ [[package]] name = "tiny-skia" -version = "0.11.4" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" +checksum = "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea" dependencies = [ "arrayref", "arrayvec", "bytemuck", "cfg-if", "log", - "png 0.17.16", + "png", "tiny-skia-path", ] [[package]] name = "tiny-skia-path" -version = "0.11.4" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" +checksum = "edca365c3faccca67d06593c5980fa6c57687de727a03131735bb85f01fdeeb9" dependencies = [ "arrayref", "bytemuck", "strict-num", ] -[[package]] -name = "tiny-xlib" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" -dependencies = [ - "as-raw-xcb-connection", - "ctor-lite", - "libloading", - "pkg-config", - "tracing", -] - [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a" dependencies = [ "displaydoc", "zerovec", @@ -7154,29 +5603,20 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" [[package]] name = "tokio" -version = "1.51.0" +version = "1.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" dependencies = [ - "bytes 1.11.1", + "bytes", "libc", "mio", - "parking_lot", + "parking_lot 0.12.5", "pin-project-lite", "signal-hook-registry", "socket2", @@ -7184,43 +5624,33 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "tokio-io" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.31", - "log", -] - [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] -name = "tokio-native-tls" -version = "0.3.1" +name = "tokio-retry" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +checksum = "4a129d95275ebf4c493ec53bf0f8cd95f5ac161bc4f381700809a54f595d4470" dependencies = [ - "native-tls", + "pin-project-lite", + "rand 0.10.3", "tokio", ] [[package]] name = "tokio-rustls" -version = "0.26.4" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" dependencies = [ "rustls", "tokio", @@ -7228,9 +5658,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" dependencies = [ "futures-core", "pin-project-lite", @@ -7240,9 +5670,9 @@ dependencies = [ [[package]] name = "tokio-test" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6d24790a10a7af737693a3e8f1d03faef7e6ca0cc99aae5066f533766de545" +checksum = "12bc5db7778a8e08ee255090861e0e7aac55e784300077e7ed293c7be36168b2" dependencies = [ "futures-core", "tokio", @@ -7251,71 +5681,70 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.28.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +checksum = "17a073bfed563fa236697a068031408a93cd9522e08abf9933ead3e73411bd71" dependencies = [ "futures-util", "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", "tokio", + "tokio-rustls", "tungstenite", ] [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" dependencies = [ - "bytes 1.11.1", + "bytes", "futures-core", "futures-sink", "futures-util", + "libc", "pin-project-lite", "tokio", ] [[package]] -name = "toktrie" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0310776df61c26b98e6ed7c3629de5131e0c8731439350b79152b7f52468229a" -dependencies = [ - "anyhow", - "bytemuck", - "bytemuck_derive", - "serde", - "serde_json", -] - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" +name = "tokio_with_wasm" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +checksum = "34e40fbbbd95441133fe9483f522db15dbfd26dc636164ebd8f2dd28759a6aa6" dependencies = [ - "serde_core", + "js-sys", + "tokio", + "tokio_with_wasm_proc", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] -name = "toml_edit" -version = "0.25.11+spec-1.1.0" +name = "tokio_with_wasm_proc" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d01145a2c788d6aae4cd653afec1e8332534d7d783d01897cefcafe4428de992" dependencies = [ - "indexmap", - "toml_datetime", - "toml_parser", - "winnow 1.0.1", + "quote", + "syn 2.0.119", ] [[package]] -name = "toml_parser" -version = "1.1.2+spec-1.1.0" +name = "toktrie" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +checksum = "0310776df61c26b98e6ed7c3629de5131e0c8731439350b79152b7f52468229a" dependencies = [ - "winnow 1.0.1", + "anyhow", + "bytemuck", + "bytemuck_derive", + "serde", + "serde_json", ] [[package]] @@ -7337,20 +5766,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.11.0", - "bytes 1.11.1", + "bitflags 2.13.2", + "bytes", "futures-util", - "http 1.4.0", + "http 1.5.0", "http-body", - "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -7377,6 +5806,19 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +dependencies = [ + "crossbeam-channel", + "symlink", + "thiserror", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.31" @@ -7385,16 +5827,59 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", ] [[package]] -name = "tracing-core" -version = "0.1.36" +name = "tracing-subscriber" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ + "matchers", + "nu-ansi-term", "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", ] [[package]] @@ -7405,66 +5890,60 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "trzcina" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58d685f2fd0c04ea9c0dcff193161de5aebf1eba5e1267807e5ae2a8562cd93" +checksum = "0105cd7bb068e2c2befb3910c55ccf33b07b7ce0592161be5044cc6233366d4f" dependencies = [ "anyhow", "async-trait", "futures-util", "log", + "thiserror", "tokio", "tokio-util", ] -[[package]] -name = "ttf-parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" -dependencies = [ - "core_maths", -] - [[package]] name = "tungstenite" -version = "0.28.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +checksum = "e48ac77174b19c110a50ab2128b24215ac9cb40e0e12e093fb602d175c569d22" dependencies = [ - "bytes 1.11.1", + "bytes", "data-encoding", - "http 1.4.0", + "http 1.5.0", "httparse", "log", - "rand 0.9.4", - "sha1 0.10.6", - "thiserror 2.0.18", - "utf-8", + "rand 0.10.3", + "rustls", + "rustls-pki-types", + "sha1 0.11.0", + "thiserror", ] +[[package]] +name = "twox-hash" +version = "2.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a801b3cea342a06d468c8710662aa29e5e05e4f5c0d62f00bbb7f2ad7941c2" + [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] -name = "uds_windows" -version = "1.2.1" +name = "typewit" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" -dependencies = [ - "memoffset", - "tempfile", - "windows-sys 0.61.2", -] +checksum = "214ca0b2191785cbc06209b9ca1861e048e39b5ba33574b3cedd58363d5bb5f6" [[package]] name = "unicase" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" +checksum = "357cc3acc6a036009fd6c973ed009037c732d60d0b4f6c673e9041497482a28f" [[package]] name = "unicode-bidi" @@ -7472,18 +5951,6 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" -[[package]] -name = "unicode-bidi-mirroring" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" - -[[package]] -name = "unicode-ccc" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" - [[package]] name = "unicode-general-category" version = "1.1.0" @@ -7492,21 +5959,9 @@ checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" [[package]] name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-linebreak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" - -[[package]] -name = "unicode-properties" -version = "0.1.4" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" [[package]] name = "unicode-script" @@ -7516,9 +5971,9 @@ checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-vo" @@ -7550,26 +6005,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" -[[package]] -name = "ureq" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" -dependencies = [ - "base64", - "flate2", - "log", - "native-tls", - "once_cell", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "socks", - "url", - "webpki-roots 0.26.11", -] - [[package]] name = "url" version = "2.5.8" @@ -7584,52 +6019,32 @@ dependencies = [ ] [[package]] -name = "usvg" -version = "0.45.1" +name = "urlencoding" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80be9b06fbae3b8b303400ab20778c80bbaf338f563afe567cf3c9eea17b47ef" -dependencies = [ - "base64", - "data-url", - "flate2", - "fontdb", - "imagesize 0.13.0", - "kurbo 0.11.3", - "log", - "pico-args", - "roxmltree 0.20.0", - "rustybuzz", - "simplecss", - "siphasher", - "strict-num", - "svgtypes 0.15.3", - "tiny-skia-path", - "unicode-bidi", - "unicode-script", - "unicode-vo", - "xmlwriter", -] +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "usvg" -version = "0.46.0" +version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e419dff010bb12512b0ae9e3d2f318dfbdf0167fde7eb05465134d4e8756076f" +checksum = "d09b73ee83641d390ad7ffd9000f347b16eb128e437675b6ff277ea5c7fcf22c" dependencies = [ - "base64", + "base64 0.23.1", "data-url", "flate2", "fontdb", - "imagesize 0.14.0", - "kurbo 0.13.0", + "harfrust", + "imagesize", + "kurbo", "log", "pico-args", - "roxmltree 0.21.1", - "rustybuzz", + "roxmltree 0.21.2", "simplecss", "siphasher", + "skrifa", "strict-num", - "svgtypes 0.16.1", + "svgtypes", "tiny-skia-path", "unicode-bidi", "unicode-script", @@ -7638,10 +6053,10 @@ dependencies = [ ] [[package]] -name = "utf-8" -version = "0.7.6" +name = "utf16_iter" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[package]] name = "utf8_iter" @@ -7657,12 +6072,12 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.1" +version = "1.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514" dependencies = [ + "getrandom 0.4.3", "js-sys", - "serde_core", "wasm-bindgen", ] @@ -7688,10 +6103,10 @@ dependencies = [ ] [[package]] -name = "vcpkg" -version = "0.2.15" +name = "valuable" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "version_check" @@ -7717,9 +6132,9 @@ dependencies = [ [[package]] name = "want" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" dependencies = [ "try-lock", ] @@ -7730,29 +6145,38 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen", ] [[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +name = "wasite" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42" dependencies = [ - "wit-bindgen", + "wasi 0.14.7+wasi-0.2.4", ] [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ "cfg-if", "once_cell", @@ -7762,457 +6186,106 @@ dependencies = [ ] [[package]] -name = "wasm-bindgen-futures" -version = "0.4.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasm-streams" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wasm-streams" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - -[[package]] -name = "wasmtimer" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" -dependencies = [ - "futures 0.3.32", - "js-sys", - "parking_lot", - "pin-utils", - "slab", - "wasm-bindgen", -] - -[[package]] -name = "wayland-backend" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" -dependencies = [ - "cc", - "downcast-rs", - "rustix 1.1.4", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.31.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" -dependencies = [ - "bitflags 2.11.0", - "rustix 1.1.4", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-csd-frame" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" -dependencies = [ - "bitflags 2.11.0", - "cursor-icon", - "wayland-backend", -] - -[[package]] -name = "wayland-cursor" -version = "0.31.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" -dependencies = [ - "rustix 1.1.4", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-protocols" -version = "0.32.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" -dependencies = [ - "bitflags 2.11.0", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-experimental" -version = "20250721.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" -dependencies = [ - "bitflags 2.11.0", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-misc" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" -dependencies = [ - "bitflags 2.11.0", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-plasma" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" -dependencies = [ - "bitflags 2.11.0", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-wlr" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" -dependencies = [ - "bitflags 2.11.0", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.31.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" -dependencies = [ - "proc-macro2", - "quick-xml", - "quote", -] - -[[package]] -name = "wayland-sys" -version = "0.31.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" -dependencies = [ - "dlib", - "log", - "once_cell", - "pkg-config", -] - -[[package]] -name = "web-sys" -version = "0.3.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" +name = "wasm-bindgen-futures" +version = "0.4.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" dependencies = [ "js-sys", + "tokio", "wasm-bindgen", ] [[package]] -name = "webpki-root-certs" -version = "1.0.7" +name = "wasm-bindgen-macro" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ - "rustls-pki-types", + "quote", + "wasm-bindgen-macro-support", ] [[package]] -name = "webpki-roots" -version = "0.26.11" +name = "wasm-bindgen-macro-support" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ - "webpki-roots 1.0.6", + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.9", + "wasm-bindgen-shared", ] [[package]] -name = "webpki-roots" -version = "1.0.6" +name = "wasm-bindgen-shared" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" dependencies = [ - "rustls-pki-types", + "unicode-ident", ] [[package]] -name = "weezl" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" - -[[package]] -name = "wgpu" -version = "27.0.1" +name = "wasm-streams" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe68bac7cde125de7a731c3400723cadaaf1703795ad3f4805f187459cd7a77" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ - "arrayvec", - "bitflags 2.11.0", - "cfg-if", - "cfg_aliases", - "document-features", - "hashbrown 0.16.1", + "futures-util", "js-sys", - "log", - "naga", - "parking_lot", - "portable-atomic", - "profiling", - "raw-window-handle", - "smallvec", - "static_assertions", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu-core", - "wgpu-hal", - "wgpu-types", ] [[package]] -name = "wgpu-core" -version = "27.0.3" +name = "web-sys" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27a75de515543b1897b26119f93731b385a19aea165a1ec5f0e3acecc229cae7" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" dependencies = [ - "arrayvec", - "bit-set", - "bit-vec", - "bitflags 2.11.0", - "bytemuck", - "cfg_aliases", - "document-features", - "hashbrown 0.16.1", - "indexmap", - "log", - "naga", - "once_cell", - "parking_lot", - "portable-atomic", - "profiling", - "raw-window-handle", - "rustc-hash 1.1.0", - "smallvec", - "thiserror 2.0.18", - "wgpu-core-deps-apple", - "wgpu-core-deps-emscripten", - "wgpu-core-deps-windows-linux-android", - "wgpu-hal", - "wgpu-types", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "wgpu-core-deps-apple" -version = "27.0.0" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0772ae958e9be0c729561d5e3fd9a19679bcdfb945b8b1a1969d9bfe8056d233" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "wgpu-hal", + "js-sys", + "wasm-bindgen", ] [[package]] -name = "wgpu-core-deps-emscripten" -version = "27.0.0" +name = "webpki-root-certs" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06ac3444a95b0813ecfd81ddb2774b66220b264b3e2031152a4a29fda4da6b5" +checksum = "e49d0e65abc00c142e8f9428da991da9f1a250c39be97459901eb12b9b49f62a" dependencies = [ - "wgpu-hal", + "rustls-pki-types", ] [[package]] -name = "wgpu-core-deps-windows-linux-android" -version = "27.0.0" +name = "weezl" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71197027d61a71748e4120f05a9242b2ad142e3c01f8c1b47707945a879a03c3" -dependencies = [ - "wgpu-hal", -] +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] -name = "wgpu-hal" -version = "27.0.4" +name = "whoami" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b21cb61c57ee198bc4aff71aeadff4cbb80b927beb912506af9c780d64313ce" +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" dependencies = [ - "android_system_properties", - "arrayvec", - "ash", - "bit-set", - "bitflags 2.11.0", - "block", - "bytemuck", - "cfg-if", - "cfg_aliases", - "core-graphics-types 0.2.0", - "glow", - "glutin_wgl_sys", - "gpu-alloc", - "gpu-allocator", - "gpu-descriptor", - "hashbrown 0.16.1", - "js-sys", - "khronos-egl", "libc", - "libloading", - "log", - "metal", - "naga", - "ndk-sys", - "objc", - "once_cell", - "ordered-float", - "parking_lot", - "portable-atomic", - "portable-atomic-util", - "profiling", - "range-alloc", - "raw-window-handle", - "renderdoc-sys", - "smallvec", - "thiserror 2.0.18", - "wasm-bindgen", - "web-sys", - "wgpu-types", - "windows 0.58.0", - "windows-core 0.58.0", -] - -[[package]] -name = "wgpu-types" -version = "27.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afdcf84c395990db737f2dd91628706cb31e86d72e53482320d368e52b5da5eb" -dependencies = [ - "bitflags 2.11.0", - "bytemuck", - "js-sys", - "log", - "thiserror 2.0.18", + "libredox", + "objc2-system-configuration", + "wasite", "web-sys", ] @@ -8247,30 +6320,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "window_clipboard" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5654226305eaf2dde8853fb482861d28e5dcecbbd40cb88e8393d94bb80d733" -dependencies = [ - "clipboard-win", - "clipboard_macos", - "clipboard_wayland", - "clipboard_x11", - "raw-window-handle", - "thiserror 2.0.18", -] - -[[package]] -name = "windows" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" -dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows" version = "0.62.2" @@ -8278,7 +6327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ "windows-collections", - "windows-core 0.62.2", + "windows-core", "windows-future", "windows-numerics", ] @@ -8289,20 +6338,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core 0.62.2", -] - -[[package]] -name = "windows-core" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" -dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", + "windows-core", ] [[package]] @@ -8311,11 +6347,11 @@ version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement 0.60.2", - "windows-interface 0.59.3", + "windows-implement", + "windows-interface", "windows-link", - "windows-result 0.4.1", - "windows-strings 0.5.1", + "windows-result", + "windows-strings", ] [[package]] @@ -8324,22 +6360,11 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.62.2", + "windows-core", "windows-link", "windows-threading", ] -[[package]] -name = "windows-implement" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "windows-implement" version = "0.60.2" @@ -8348,18 +6373,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.58.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -8370,7 +6384,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -8385,7 +6399,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core 0.62.2", + "windows-core", "windows-link", ] @@ -8396,17 +6410,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ "windows-link", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-result" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", + "windows-result", + "windows-strings", ] [[package]] @@ -8418,16 +6423,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-strings" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" -dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows-strings" version = "0.5.1" @@ -8437,40 +6432,13 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -8482,52 +6450,20 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -8541,369 +6477,222 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "windows_x86_64_gnullvm" +name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" +name = "windows_i686_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "windows_x86_64_msvc" +name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" +name = "windows_x86_64_gnu" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "winit" -version = "0.30.13" +name = "windows_x86_64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" -dependencies = [ - "ahash", - "android-activity", - "atomic-waker", - "bitflags 2.11.0", - "block2 0.5.1", - "bytemuck", - "calloop 0.13.0", - "cfg_aliases", - "concurrent-queue", - "core-foundation 0.9.4", - "core-graphics", - "cursor-icon", - "dpi", - "js-sys", - "libc", - "memmap2", - "ndk", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", - "objc2-ui-kit", - "orbclient", - "percent-encoding", - "pin-project", - "raw-window-handle", - "redox_syscall 0.4.1", - "rustix 0.38.44", - "sctk-adwaita", - "smithay-client-toolkit 0.19.2", - "smol_str", - "tracing", - "unicode-segmentation", - "wasm-bindgen", - "wasm-bindgen-futures", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-protocols-plasma", - "web-sys", - "web-time", - "windows-sys 0.52.0", - "x11-dl", - "x11rb", - "xkbcommon-dl", -] +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "winnow" -version = "0.7.15" +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" -dependencies = [ - "memchr", -] +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" -version = "0.51.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] -name = "wit-bindgen-core" -version = "0.51.0" +name = "write16" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] -name = "wit-bindgen-rust" -version = "0.51.0" +name = "writeable" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" +name = "xet-client" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +checksum = "15500e5fc9e17be15224f9e34cb07d41fb2805d9dbbf7bd3507c982fd1eb3efd" dependencies = [ "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", + "async-trait", + "base64 0.22.1", + "bytes", + "chrono", + "crc32fast", + "futures", + "http 1.5.0", + "hyper", + "more-asserts", + "rand 0.10.3", + "redb", + "reqwest", + "reqwest-middleware", + "serde", + "serde_json", + "serde_repr", + "statrs", + "tempfile", + "thiserror", + "tokio", + "tokio-retry", + "tokio_with_wasm", + "tracing", + "url", + "urlencoding", + "uuid", + "web-time", + "xet-core-structures", + "xet-runtime", ] [[package]] -name = "wit-component" -version = "0.244.0" +name = "xet-core-structures" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +checksum = "5080d5803c8bf15fa56ef934050a46b225801bad3ea4cb8bb3c46c5c37976516" dependencies = [ - "anyhow", - "bitflags 2.11.0", - "indexmap", - "log", + "async-trait", + "base64 0.22.1", + "blake3", + "bytemuck", + "bytes", + "countio", + "futures", + "futures-util", + "getrandom 0.4.3", + "heapify", + "itertools 0.14.0", + "lz4_flex", + "more-asserts", + "rand 0.10.3", + "regex", + "safe-transmute", "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", + "static_assertions", + "thiserror", + "tokio", + "tokio-util", + "tracing", + "uuid", + "web-time", + "xet-runtime", ] [[package]] -name = "wit-parser" -version = "0.244.0" +name = "xet-data" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +checksum = "1287a1657fb67de846030f6605a9960dcdad401c467b490657e6e909d2e0d1c0" dependencies = [ "anyhow", - "id-arena", - "indexmap", - "log", - "semver", + "async-trait", + "bytes", + "chrono", + "gearhash", + "http 1.5.0", + "itertools 0.14.0", + "more-asserts", + "rand 0.10.3", "serde", - "serde_derive", "serde_json", - "unicode-xid", - "wasmparser", -] - -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", + "sha2", + "tempfile", + "thiserror", + "tokio", + "tokio-util", + "tokio_with_wasm", + "tracing", + "url", + "uuid", + "web-time", + "xet-client", + "xet-core-structures", + "xet-runtime", ] [[package]] -name = "x11rb" -version = "0.13.2" +name = "xet-runtime" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +checksum = "921953c885ce83e02b21e8d26444b75c78c9e83076699ced08d5c6fc1cdb48ac" dependencies = [ - "as-raw-xcb-connection", - "gethostname", + "anyhow", + "async-trait", + "bytes", + "chrono", + "colored", + "const-str", + "ctor", + "dirs", + "futures", + "git-version", + "humantime", + "konst", "libc", - "libloading", - "once_cell", - "rustix 1.1.4", - "x11rb-protocol", -] - -[[package]] -name = "x11rb-protocol" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" - -[[package]] -name = "xcursor" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" - -[[package]] -name = "xkbcommon-dl" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" -dependencies = [ - "bitflags 2.11.0", - "dlib", - "log", - "once_cell", - "xkeysym", + "more-asserts", + "oneshot", + "pin-project", + "rand 0.10.3", + "reqwest", + "serde", + "serde_json", + "shellexpand", + "sysinfo", + "thiserror", + "tokio", + "tokio-util", + "tokio_with_wasm", + "tracing", + "tracing-appender", + "tracing-subscriber", + "web-time", + "whoami", + "winapi", ] -[[package]] -name = "xkeysym" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" - -[[package]] -name = "xml-rs" -version = "0.8.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" - [[package]] name = "xmlwriter" version = "0.1.0" @@ -8918,32 +6707,20 @@ checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" [[package]] name = "yaml-rust2" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631a50d867fafb7093e709d75aaee9e0e0d5deb934021fcea25ac2fe09edc51e" +checksum = "57e5b818a27a4cd30884ea380857a5e56f7ec3ba24a3990a3cc0b95af3238e18" dependencies = [ "arraydeque", "encoding_rs", "hashlink", ] -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - -[[package]] -name = "yazi" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" - [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -8952,135 +6729,68 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", "synstructure", ] -[[package]] -name = "zbus" -version = "5.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" -dependencies = [ - "async-broadcast", - "async-executor", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-lite", - "hex", - "libc", - "ordered-stream", - "rustix 1.1.4", - "serde", - "serde_repr", - "tracing", - "uds_windows", - "uuid", - "windows-sys 0.61.2", - "winnow 0.7.15", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "5.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", - "zbus_names", - "zvariant", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "4.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" -dependencies = [ - "serde", - "winnow 0.7.15", - "zvariant", -] - -[[package]] -name = "zeno" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" - [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", @@ -9089,9 +6799,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40" dependencies = [ "yoke", "zerofrom", @@ -9100,44 +6810,50 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zstd" -version = "0.13.3" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +checksum = "db3ae9bf90af6e40197bef767eeefc46ad6397420fe873830bd85e5fbbfb3c70" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.4" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +checksum = "ded520f16b29e1a9b771c722221b2b0a0398c2316d9bb3c42b458a5414d7434b" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" +version = "2.1.2+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +checksum = "481ada9c543b2323cbb7bbdbb5249177fbf0188ed959ed2c15e4b22421c84ed3" dependencies = [ "cc", "pkg-config", @@ -9145,15 +6861,9 @@ dependencies = [ [[package]] name = "zune-core" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" - -[[package]] -name = "zune-core" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" [[package]] name = "zune-inflate" @@ -9164,60 +6874,11 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "zune-jpeg" -version = "0.4.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713" -dependencies = [ - "zune-core 0.4.12", -] - [[package]] name = "zune-jpeg" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" dependencies = [ - "zune-core 0.5.1", -] - -[[package]] -name = "zvariant" -version = "5.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b" -dependencies = [ - "endi", - "enumflags2", - "serde", - "winnow 0.7.15", - "zvariant_derive", - "zvariant_utils", -] - -[[package]] -name = "zvariant_derive" -version = "5.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn", - "winnow 0.7.15", + "zune-core", ] diff --git a/Cargo.toml b/Cargo.toml index 7673c05c5..babd81b76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,28 +1,40 @@ [workspace] members = [ "paddler_agent", + "paddler_agent_decision", + "paddler_agent_embeddings", + "paddler_agent_pointer_head", + "paddler_agent_runtime", "paddler_agent_status", + "paddler_agent_text_generation", "paddler_balancer", + "paddler_blocking_thread_fixture", "paddler_bootstrap", "paddler_cache_dir", "paddler_cli", "paddler_cli_tests", "paddler_client", + "paddler_dashboard_assets", "paddler_download_manager", - "paddler_gui", - "paddler_gui_tests", + "paddler_frame_writer", "paddler_image_decoder", "paddler_inference_parameters", "paddler_local_http_fixture", "paddler_messaging", "paddler_model_source", "paddler_openai_response_format_validator", + "paddler_openai_translation", "paddler_opencode_tests", + "paddler_outbox", "paddler_request_registry", + "paddler_sha256_hex", + "paddler_spawn_resuming_panic", "paddler_state_database", + "paddler_tcp_buffer_limits", "paddler_test_cluster_harness", "paddler_tests", "paddler_tool_call_validator", + "paddler_typesafe_translation", ] resolver = "2" @@ -33,94 +45,105 @@ edition = "2024" homepage = "https://paddler.intentee.com/" license = "Apache-2.0" repository = "https://github.com/intentee/paddler" -version = "4.1.0" +version = "5.0.0" [workspace.dependencies] actix-cors = "0.7" -actix-rt = "2.11" -actix-web = "4.13" -actix-web-lab = "0.26" +actix-web = { version = "4.18", default-features = false, features = ["compat", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "macros", "unicode", "ws"] } +actix-web-lab = "0.29" actix-ws = "0.4" anyhow = { version = "1.0", features = ["backtrace"] } -askama = "0.14" -async-openai = { version = "0.40", features = ["byot", "chat-completion", "responses"] } -async-stream = "0.3" +askama = "0.16" +async-openai = { version = "0.42", features = ["byot", "chat-completion", "responses"] } async-trait = "0.1" -bytes = "1.11" -cadence = "1.6" -clap = { version = "4.5", features = ["derive", "string"] } -command_handler = "0.1" -dashmap = "6.1" +base64 = "0.23" +bytes = "1.12" +cadence = "1.8" +clap = { version = "4.6", features = ["derive", "env", "string"] } +command_handler = "0.2" +dashmap = "6.2" data-url = "0.3" encoding_rs = { version = "0.8", features = ["serde"] } env_logger = "0.11" -esbuild-metafile = "0.5" +esbuild-metafile = "0.11" fslock = "0.2" futures = "0.3" -futures-util = { version = "0.3", features = ["tokio-io"] } +futures-util = "0.3" headers = "0.4" -hf-hub = { version = "0.4", features = ["tokio"] } -http = "1.4" +hf-hub = "1.0" +http = "1.5" httparse = "1.10" +icu_normalizer = "2.4" image = "0.25" indoc = "2.0" -jsonschema = { version = "0.37", default-features = false } -llama-cpp-bindings = "0.16" -llama-cpp-bindings-sys = "0.16" -llama-cpp-bindings-types = "0.16" -base64 = "0.22" +jsonschema = { version = "0.60", default-features = false } +libc = "0.2" +llama-cpp-bindings = "0.25" +llama-cpp-bindings-sys = "0.25" +llama-cpp-bindings-types = "0.25" log = "0.4" mime_guess = "2.0" -minijinja = { version = "2.12", features = ["builtins", "json", "loader"] } -minijinja-contrib = { version = "2.12", features = ["datetime", "pycompat", "wordcount", "wordwrap"] } -nanoid = "0.4" -nix = { version = "0.30", features = ["resource", "signal"] } -open = "5.3" -parking_lot = "0.12" -quick-xml = "0.39" -rand = "0.9" -reqwest = { version = "0.12", features = ["json", "stream"] } -resvg = "0.46" -rust-embed = { version = "8.9", features = ["interpolate-folder-path"] } +minijinja = { version = "3.0", features = ["builtins", "json", "serde"] } +minijinja-contrib = { version = "3.0", features = ["datetime", "pycompat", "wordcount", "wordwrap"] } +nanoid = "0.5" +nix = { version = "0.31", features = ["fs", "net", "process", "resource", "signal", "socket", "user"] } +parking_lot = "0.13" +percent-encoding = "2.3" +quick-xml = "0.42" +rand = "0.10" +reqwest = { version = "0.13", features = ["json", "stream"] } +resvg = "0.49" +rust-embed = { version = "8.13", features = ["debug-embed", "interpolate-folder-path"] } serde = { version = "1.0", features = ["derive"] } -serde_json = { version = "1.0", features = ["preserve_order"] } -sha2 = "0.10" +serde_json = { version = "1.0", features = ["preserve_order", "raw_value"] } +seccompiler = "0.5" +sha2 = "0.11" shellexpand = "3.1" socket2 = "0.6" -iced = { version = "0.14", features = ["image", "svg", "tokio"] } -iced_test = { version = "0.14" } -if-addrs = "0.13" -statum = "0.6" -tempfile = "3.20" -tokio = { version = "1.48", features = ["full"] } +syn = { version = "3.0", features = ["full", "visit"] } +tempfile = "3.27" +tokio = { version = "1.54", features = ["full"] } tokio-stream = { version = "0.1", features = ["sync"] } tokio-test = "0.4" -tokio-tungstenite = "0.28" +tokio-tungstenite = { version = "0.30", features = ["rustls-tls-native-roots"] } tokio-util = { version = "0.7", features = ["rt"] } thiserror = "2.0" -trzcina = "0.3" +trzcina = "0.4" url = { version = "2.5", features = ["serde"] } -yaml-rust2 = "0.11" -paddler_agent = { version = "4.1", path = "paddler_agent" } -paddler_agent_status = { version = "4.1", path = "paddler_agent_status" } -paddler_balancer = { version = "4.1", path = "paddler_balancer" } -paddler_bootstrap = { version = "4.1", path = "paddler_bootstrap" } -paddler_cache_dir = { version = "4.1", path = "paddler_cache_dir" } -paddler_cli = { version = "4.1", path = "paddler_cli" } -paddler_client = { version = "4.1", path = "paddler_client" } -paddler_download_manager = { version = "4.1", path = "paddler_download_manager" } -paddler_gui = { version = "4.1", path = "paddler_gui" } -paddler_image_decoder = { version = "4.1", path = "paddler_image_decoder" } -paddler_inference_parameters = { version = "4.1", path = "paddler_inference_parameters" } -paddler_local_http_fixture = { version = "4.1", path = "paddler_local_http_fixture" } -paddler_messaging = { version = "4.1", path = "paddler_messaging" } -paddler_model_source = { version = "4.1", path = "paddler_model_source" } -paddler_openai_response_format_validator = { version = "4.1", path = "paddler_openai_response_format_validator" } -paddler_request_registry = { version = "4.1", path = "paddler_request_registry" } -paddler_state_database = { version = "4.1", path = "paddler_state_database" } -paddler_test_cluster_harness = { version = "4.1", path = "paddler_test_cluster_harness" } -paddler_tests = { version = "4.1", path = "paddler_tests" } -paddler_tool_call_validator = { version = "4.1", path = "paddler_tool_call_validator" } +yaml-rust2 = "0.13" +paddler_agent = { version = "5.0", path = "paddler_agent" } +paddler_agent_decision = { version = "5.0", path = "paddler_agent_decision" } +paddler_agent_embeddings = { version = "5.0", path = "paddler_agent_embeddings" } +paddler_agent_pointer_head = { version = "5.0", path = "paddler_agent_pointer_head" } +paddler_agent_runtime = { version = "5.0", path = "paddler_agent_runtime" } +paddler_agent_status = { version = "5.0", path = "paddler_agent_status" } +paddler_agent_text_generation = { version = "5.0", path = "paddler_agent_text_generation" } +paddler_balancer = { version = "5.0", path = "paddler_balancer" } +paddler_blocking_thread_fixture = { version = "5.0", path = "paddler_blocking_thread_fixture" } +paddler_bootstrap = { version = "5.0", path = "paddler_bootstrap" } +paddler_cache_dir = { version = "5.0", path = "paddler_cache_dir" } +paddler_cli = { version = "5.0", path = "paddler_cli" } +paddler_client = { version = "5.0", path = "paddler_client" } +paddler_dashboard_assets = { version = "5.0", path = "paddler_dashboard_assets" } +paddler_download_manager = { version = "5.0", path = "paddler_download_manager" } +paddler_frame_writer = { version = "5.0", path = "paddler_frame_writer" } +paddler_image_decoder = { version = "5.0", path = "paddler_image_decoder" } +paddler_inference_parameters = { version = "5.0", path = "paddler_inference_parameters" } +paddler_local_http_fixture = { version = "5.0", path = "paddler_local_http_fixture" } +paddler_messaging = { version = "5.0", path = "paddler_messaging" } +paddler_model_source = { version = "5.0", path = "paddler_model_source" } +paddler_openai_response_format_validator = { version = "5.0", path = "paddler_openai_response_format_validator" } +paddler_openai_translation = { version = "5.0", path = "paddler_openai_translation" } +paddler_outbox = { version = "5.0", path = "paddler_outbox" } +paddler_request_registry = { version = "5.0", path = "paddler_request_registry" } +paddler_sha256_hex = { version = "5.0", path = "paddler_sha256_hex" } +paddler_spawn_resuming_panic = { version = "5.0", path = "paddler_spawn_resuming_panic" } +paddler_state_database = { version = "5.0", path = "paddler_state_database" } +paddler_tcp_buffer_limits = { version = "5.0", path = "paddler_tcp_buffer_limits" } +paddler_test_cluster_harness = { version = "5.0", path = "paddler_test_cluster_harness" } +paddler_tests = { version = "5.0", path = "paddler_tests" } +paddler_tool_call_validator = { version = "5.0", path = "paddler_tool_call_validator" } +paddler_typesafe_translation = { version = "5.0", path = "paddler_typesafe_translation" } [profile.release] lto = true diff --git a/Dockerfile b/Dockerfile index 7c2256c7a..9ebe461be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM node:22-bookworm@sha256:363e1587494626837fa7f9a23bdb453d13b0ff3c67c705c2805cfc69c2d2fad7 AS node -FROM rust:1.95.0-bookworm@sha256:6258907abe69656e41cd992e0b705cdcfabcbbe3db374f92ed2d47121282d4a1 +FROM rust:1.99.1-bookworm@sha256:e12566d5abc17a0da741628782cdb504b68b8961f5bfcf677936bf3e8b32de29 COPY --from=node /usr/local/bin/node /usr/local/bin/node COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules diff --git a/Makefile b/Makefile index cd1a8c7e0..27662bf54 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,40 @@ .DEFAULT_GOAL := target/release/paddler +.PRECIOUS: target/kev_%/backbone/config.json + +.NOTPARALLEL: + RUST_LOG ?= debug -PADDLER_CRATES := paddler_agent paddler_agent_status paddler_balancer paddler_bootstrap paddler_cache_dir paddler_cli paddler_client paddler_download_manager paddler_gui paddler_image_decoder paddler_inference_parameters paddler_messaging paddler_model_source paddler_request_registry paddler_state_database paddler_tool_call_validator -PADDLER_SOURCES := $(shell find $(addsuffix /src,$(PADDLER_CRATES)) -name '*.rs') $(addsuffix /Cargo.toml,$(PADDLER_CRATES)) Cargo.toml Cargo.lock +PADDLER_CRATES := paddler_agent paddler_agent_decision paddler_agent_embeddings paddler_agent_pointer_head paddler_agent_runtime paddler_agent_status paddler_agent_text_generation paddler_balancer paddler_bootstrap paddler_cache_dir paddler_cli paddler_client paddler_dashboard_assets paddler_download_manager paddler_frame_writer paddler_image_decoder paddler_inference_parameters paddler_messaging paddler_model_source paddler_openai_translation paddler_outbox paddler_request_registry paddler_sha256_hex paddler_spawn_resuming_panic paddler_state_database paddler_tool_call_validator paddler_typesafe_translation +PADDLER_SOURCES := $(shell find $(addsuffix /src,$(PADDLER_CRATES)) -name '*.rs') $(wildcard $(addsuffix /build.rs,$(PADDLER_CRATES))) $(addsuffix /Cargo.toml,$(PADDLER_CRATES)) Cargo.toml Cargo.lock FRONTEND_SOURCES := $(shell find resources paddler_client_javascript/src -type f) paddler_client_javascript/package.json $(wildcard jarmuz/*.mjs) JS_CLIENT_SOURCES := $(shell find paddler_client_javascript/src -type f) paddler_client_javascript/package.json paddler_client_javascript/tsconfig.json +KEV_CHECKPOINT_0_8b := jaredpalmer/kev-0.8b@788ddbdd65715bb03a56788c822f6c632c9a551d +KEV_CHECKPOINT_27b := jaredpalmer/kev-27b@af0e6d551bdc2cc724f3e9d7a8bee1cd4fb8f7bf +KEV_GGUF_OUTTYPE_0_8b := bf16 +KEV_GGUF_OUTTYPE_27b := q8_0 +KEV_CONVERTER_SOURCES := $(shell find paddler_kev_converter_python/paddler_kev_converter -name '*.py') +QWEN3_5_BASE := Qwen/Qwen3.5-0.8B-Base +QWEN3_5_BASE_REVISION := dc7cdfe2ee4154fa7e30f5b51ca41bfa40174e68 +QWEN3_8_BASE := Qwen/Qwen3.8-27B +QWEN3_8_BASE_REVISION := 1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0 +LLAMA_CPP_SOURCE_DIRECTORY = $(dir $(shell cargo metadata --format-version 1 | jq -r '.packages[] | select(.name == "llama-cpp-bindings-sys") | .manifest_path'))llama.cpp +RUN_WITH_TEST_CLUSTER_SOURCES := $(shell find paddler_tests/src paddler_test_cluster_harness/src -name '*.rs') paddler_tests/Cargo.toml paddler_test_cluster_harness/Cargo.toml LLVM_COV_THIRD_PARTY_SOURCES := /\.cargo/(registry|git)/|/\.rustup/toolchains/|^/rustc/|^/nix/store/|^$(CURDIR)/target/ TEST_DEVICE ?= cpu ifeq ($(TEST_DEVICE),cpu) +NEXTEST_PROFILE := cpu PADDLER_TEST_BINARY := target/debug/paddler +RUN_WITH_TEST_CLUSTER_BINARY := target/debug/run_with_test_cluster TEST_DEVICE_FEATURE_SUFFIX := TEST_DEVICE_TARGET_DIR := else +NEXTEST_PROFILE := default PADDLER_TEST_BINARY := target/$(TEST_DEVICE)/debug/paddler +RUN_WITH_TEST_CLUSTER_BINARY := target/$(TEST_DEVICE)/debug/run_with_test_cluster TEST_DEVICE_FEATURE_SUFFIX := ,$(TEST_DEVICE) TEST_DEVICE_TARGET_DIR := --target-dir target/$(TEST_DEVICE) endif @@ -25,6 +44,48 @@ PADDLER_BINARY_ENVIRONMENT := PADDLER_BINARY=$(CURDIR)/$(PADDLER_TEST_BINARY) esbuild-meta.json: $(FRONTEND_SOURCES) jarmuz-static.mjs tsconfig.json package.json node_modules ./jarmuz-static.mjs +fixtures/kev_%_parity_reference.json: $(KEV_CONVERTER_SOURCES) paddler_kev_converter_python/.venv + poetry -C paddler_kev_converter_python run paddler-kev-converter parity-reference --checkpoint $(KEV_CHECKPOINT_$*) --output $(CURDIR)/$@ + +fixtures/pointer_head_with_f16_tensors.gguf: $(KEV_CONVERTER_SOURCES) paddler_kev_converter_python/.venv + poetry -C paddler_kev_converter_python run paddler-kev-converter malformed-pointer-head --input-normalization NFC --defect f16-tensors --hidden-size 64 --output $(CURDIR)/$@ + +fixtures/pointer_head_with_mismatched_projections.gguf: $(KEV_CONVERTER_SOURCES) paddler_kev_converter_python/.venv + poetry -C paddler_kev_converter_python run paddler-kev-converter malformed-pointer-head --input-normalization NFC --defect mismatched-projections --hidden-size 64 --output $(CURDIR)/$@ + +fixtures/pointer_head_with_zero_temperature.gguf: $(KEV_CONVERTER_SOURCES) paddler_kev_converter_python/.venv + poetry -C paddler_kev_converter_python run paddler-kev-converter malformed-pointer-head --input-normalization NFC --defect zero-temperature --hidden-size 64 --output $(CURDIR)/$@ + +fixtures/pointer_head_without_delimiters.gguf: $(KEV_CONVERTER_SOURCES) paddler_kev_converter_python/.venv + poetry -C paddler_kev_converter_python run paddler-kev-converter malformed-pointer-head --input-normalization NFC --defect missing-delimiters --hidden-size 64 --output $(CURDIR)/$@ + +fixtures/pointer_head_without_input_normalization.gguf: $(KEV_CONVERTER_SOURCES) paddler_kev_converter_python/.venv + poetry -C paddler_kev_converter_python run paddler-kev-converter malformed-pointer-head --input-normalization NFC --defect missing-input-normalization --hidden-size 64 --output $(CURDIR)/$@ + +fixtures/pointer_head_without_temperature.gguf: $(KEV_CONVERTER_SOURCES) paddler_kev_converter_python/.venv + poetry -C paddler_kev_converter_python run paddler-kev-converter malformed-pointer-head --input-normalization NFC --defect missing-temperature --hidden-size 64 --output $(CURDIR)/$@ + +fixtures/pointer_head_with_oversized_head_dim.gguf: $(KEV_CONVERTER_SOURCES) paddler_kev_converter_python/.venv + poetry -C paddler_kev_converter_python run paddler-kev-converter malformed-pointer-head --input-normalization NFC --defect oversized-head-dim --hidden-size 1 --output $(CURDIR)/$@ + +fixtures/qwen3_5_0_8b_pointer_head_with_plain_text_delimiters.gguf: $(KEV_CONVERTER_SOURCES) paddler_kev_converter_python/.venv + poetry -C paddler_kev_converter_python run paddler-kev-converter malformed-pointer-head --input-normalization NFC --defect plain-text-delimiters --hidden-size 1024 --output $(CURDIR)/$@ + +fixtures/qwen3_5_0_8b_synthetic_pointer_head.gguf: $(KEV_CONVERTER_SOURCES) paddler_kev_converter_python/.venv + poetry -C paddler_kev_converter_python run paddler-kev-converter synthetic-pointer-head --input-normalization NFC --hidden-size 1024 --output $(CURDIR)/$@ + +fixtures/qwen3_5_0_8b_synthetic_pointer_head_with_wrong_hidden_size.gguf: $(KEV_CONVERTER_SOURCES) paddler_kev_converter_python/.venv + poetry -C paddler_kev_converter_python run paddler-kev-converter synthetic-pointer-head --input-normalization NFC --hidden-size 512 --output $(CURDIR)/$@ + +fixtures/qwen3_8_27b_synthetic_pointer_head.gguf: $(KEV_CONVERTER_SOURCES) paddler_kev_converter_python/.venv + poetry -C paddler_kev_converter_python run paddler-kev-converter synthetic-pointer-head --input-normalization NFC --hidden-size 5120 --output $(CURDIR)/$@ + +fixtures/qwen3_%_tokenizer_reference.json: $(KEV_CONVERTER_SOURCES) paddler_kev_converter_python/.venv + poetry -C paddler_kev_converter_python run paddler-kev-converter tokenizer-reference --base $(QWEN3_$*_BASE) --base-revision $(QWEN3_$*_BASE_REVISION) --output $(CURDIR)/$@ + +fixtures/typesafe_reference.json: $(KEV_CONVERTER_SOURCES) paddler_kev_converter_python/.venv + poetry -C paddler_kev_converter_python run paddler-kev-converter typesafe-reference --output $(CURDIR)/$@ + node_modules: package-lock.json npm ci touch node_modules @@ -36,45 +97,49 @@ paddler_client_javascript/dist: $(JS_CLIENT_SOURCES) node_modules npm --workspace @intentee/paddler-client run build touch $@ -paddler_client_python/.venv: paddler_client_python/poetry.lock - POETRY_VIRTUALENVS_IN_PROJECT=true poetry -C paddler_client_python install +paddler_kev_converter_python/.venv: paddler_kev_converter_python/poetry.lock + POETRY_VIRTUALENVS_IN_PROJECT=true poetry -C paddler_kev_converter_python install + touch $@ + +paddler_llama_cpp_converter_python/.venv: paddler_llama_cpp_converter_python/poetry.lock + POETRY_VIRTUALENVS_IN_PROJECT=true poetry -C paddler_llama_cpp_converter_python install touch $@ paddler_openai_client_python_test/.venv: paddler_openai_client_python_test/poetry.lock POETRY_VIRTUALENVS_IN_PROJECT=true poetry -C paddler_openai_client_python_test install touch $@ -paddler_test_cluster_python/.venv: paddler_test_cluster_python/poetry.lock - POETRY_VIRTUALENVS_IN_PROJECT=true poetry -C paddler_test_cluster_python install +paddler_typesafe_client_python_test/.venv: paddler_typesafe_client_python_test/poetry.lock + POETRY_VIRTUALENVS_IN_PROJECT=true poetry -C paddler_typesafe_client_python_test install touch $@ +$(RUN_WITH_TEST_CLUSTER_BINARY): $(PADDLER_SOURCES) $(RUN_WITH_TEST_CLUSTER_SOURCES) + cargo build -p paddler_tests --bin run_with_test_cluster --features tests_that_use_llms$(TEST_DEVICE_FEATURE_SUFFIX) $(TEST_DEVICE_TARGET_DIR) + target/cuda/debug/paddler: $(PADDLER_SOURCES) esbuild-meta.json cargo build -p paddler_cli --features cuda,web_admin_panel --target-dir target/cuda target/cuda/release/paddler: $(PADDLER_SOURCES) esbuild-meta.json cargo build --release -p paddler_cli --features cuda,web_admin_panel --target-dir target/cuda -target/cuda/release/paddler_gui: $(PADDLER_SOURCES) esbuild-meta.json - cargo build --release -p paddler_gui --features cuda,web_admin_panel --target-dir target/cuda - target/debug/paddler: $(PADDLER_SOURCES) cargo build -p paddler_cli +target/kev_%/backbone/config.json target/kev_%/pointer_head.gguf: $(KEV_CONVERTER_SOURCES) paddler_kev_converter_python/.venv + poetry -C paddler_kev_converter_python run paddler-kev-converter convert --checkpoint $(KEV_CHECKPOINT_$*) --backbone-directory $(CURDIR)/target/kev_$*/backbone --pointer-head $(CURDIR)/target/kev_$*/pointer_head.gguf + +target/kev_%/model.gguf: target/kev_%/backbone/config.json paddler_llama_cpp_converter_python/.venv + poetry -C paddler_llama_cpp_converter_python run python $(LLAMA_CPP_SOURCE_DIRECTORY)/convert_hf_to_gguf.py $(CURDIR)/target/kev_$*/backbone --no-mtp --outtype $(KEV_GGUF_OUTTYPE_$*) --outfile $(CURDIR)/$@ + target/metal/debug/paddler: $(PADDLER_SOURCES) esbuild-meta.json cargo build -p paddler_cli --features metal,web_admin_panel --target-dir target/metal target/metal/release/paddler: $(PADDLER_SOURCES) esbuild-meta.json cargo build --release -p paddler_cli --features metal,web_admin_panel --target-dir target/metal -target/metal/release/paddler_gui: $(PADDLER_SOURCES) esbuild-meta.json - cargo build --release -p paddler_gui --features metal,web_admin_panel --target-dir target/metal - target/release/paddler: $(PADDLER_SOURCES) esbuild-meta.json cargo build --release -p paddler_cli --features web_admin_panel -target/release/paddler_gui: $(PADDLER_SOURCES) esbuild-meta.json - cargo build --release -p paddler_gui --features web_admin_panel - target/test-model-cards.stamp: $(wildcard paddler_test_cluster_harness/src/model_card/*.rs) paddler_test_cluster_harness/tests/every_model_card_resolves_from_the_hugging_face_cache.rs cargo test -p paddler_test_cluster_harness --features tests_that_use_llms --test every_model_card_resolves_from_the_hugging_face_cache touch $@ @@ -100,23 +165,20 @@ clippy: esbuild-meta.json fmt: node_modules ./jarmuz-fmt.mjs -.PHONY: lint.client.python -lint.client.python: paddler_client_python/.venv paddler_test_cluster_python/.venv - poetry -C paddler_client_python run ruff check - poetry -C paddler_client_python run ruff format --check - poetry -C paddler_client_python run mypy . - poetry -C paddler_test_cluster_python run ruff check - poetry -C paddler_test_cluster_python run ruff format --check - poetry -C paddler_test_cluster_python run mypy . +.PHONY: lint.kev_converter.python +lint.kev_converter.python: paddler_kev_converter_python/.venv + poetry -C paddler_kev_converter_python run mypy . .PHONY: lint.openai.python lint.openai.python: paddler_openai_client_python_test/.venv - poetry -C paddler_openai_client_python_test run ruff check - poetry -C paddler_openai_client_python_test run ruff format --check poetry -C paddler_openai_client_python_test run mypy . +.PHONY: lint.typesafe.python +lint.typesafe.python: paddler_typesafe_client_python_test/.venv + poetry -C paddler_typesafe_client_python_test run mypy . + .PHONY: test -test: test.client.js test.client.python test.unit test.integration +test: test.client.js test.unit test.integration .PHONY: test.client.js test.client.js: $(PADDLER_TEST_BINARY) node_modules @@ -130,53 +192,50 @@ test.client.js.coverage: $(PADDLER_TEST_BINARY) node_modules test.client.js.llm: $(PADDLER_TEST_BINARY) node_modules target/test-model-cards.stamp $(PADDLER_BINARY_ENVIRONMENT) npm --workspace @intentee/paddler-client run test:llm -.PHONY: test.client.python -test.client.python: $(PADDLER_TEST_BINARY) paddler_client_python/.venv paddler_test_cluster_python/.venv - $(PADDLER_BINARY_ENVIRONMENT) poetry -C paddler_test_cluster_python run pytest tests/unit tests/cluster - $(PADDLER_BINARY_ENVIRONMENT) poetry -C paddler_client_python run pytest tests/unit tests/cluster - -.PHONY: test.client.python.coverage -test.client.python.coverage: $(PADDLER_TEST_BINARY) paddler_client_python/.venv paddler_test_cluster_python/.venv - $(PADDLER_BINARY_ENVIRONMENT) poetry -C paddler_test_cluster_python run pytest --cov - $(PADDLER_BINARY_ENVIRONMENT) poetry -C paddler_client_python run pytest --cov - -.PHONY: test.client.python.llm -test.client.python.llm: $(PADDLER_TEST_BINARY) paddler_client_python/.venv paddler_test_cluster_python/.venv target/test-model-cards.stamp - $(PADDLER_BINARY_ENVIRONMENT) poetry -C paddler_test_cluster_python run pytest tests/llm - $(PADDLER_BINARY_ENVIRONMENT) poetry -C paddler_client_python run pytest tests/llm - .PHONY: test.coverage -test.coverage: esbuild-meta.json node_modules target/test-model-cards.stamp +test.coverage: esbuild-meta.json node_modules target/kev_0_8b/model.gguf target/kev_0_8b/pointer_head.gguf target/kev_27b/model.gguf target/kev_27b/pointer_head.gguf target/test-model-cards.stamp cargo llvm-cov clean --workspace - cargo llvm-cov nextest --features tests_that_use_llms,web_admin_panel$(TEST_DEVICE_FEATURE_SUFFIX) --no-report --workspace - cargo llvm-cov report --no-default-ignore-filename-regex --ignore-filename-regex '$(LLVM_COV_THIRD_PARTY_SOURCES)' --json --output-path target/llvm-cov.json - cargo llvm-cov report --no-default-ignore-filename-regex --ignore-filename-regex '$(LLVM_COV_THIRD_PARTY_SOURCES)' --lcov --output-path target/lcov.info - cargo llvm-cov report --no-default-ignore-filename-regex --ignore-filename-regex '$(LLVM_COV_THIRD_PARTY_SOURCES)' + NEXTEST_PROFILE=$(NEXTEST_PROFILE) cargo llvm-cov nextest --features tests_that_use_llms,tests_that_use_opencode,web_admin_panel$(TEST_DEVICE_FEATURE_SUFFIX) --include-build-script --no-report --workspace + cargo llvm-cov report --include-build-script --no-default-ignore-filename-regex --ignore-filename-regex '$(LLVM_COV_THIRD_PARTY_SOURCES)' --json --output-path target/llvm-cov.json + cargo llvm-cov report --include-build-script --no-default-ignore-filename-regex --ignore-filename-regex '$(LLVM_COV_THIRD_PARTY_SOURCES)' --lcov --output-path target/lcov.info + cargo llvm-cov report --include-build-script --no-default-ignore-filename-regex --ignore-filename-regex '$(LLVM_COV_THIRD_PARTY_SOURCES)' npx rust-coverage-check target/llvm-cov.json \ --workspace-root $(CURDIR) \ - --gated paddler_agent=97 \ + --gated paddler_agent=100 \ + --gated paddler_agent_decision=100 \ + --gated paddler_agent_embeddings=100 \ + --gated paddler_agent_pointer_head=100 \ + --gated paddler_agent_runtime=100 \ --gated paddler_agent_status=100 \ - --gated paddler_balancer=99 \ + --gated paddler_agent_text_generation=100 \ + --gated paddler_balancer=100 \ + --gated paddler_blocking_thread_fixture=100 \ --gated paddler_bootstrap=100 \ --gated paddler_cache_dir=100 \ - --gated paddler_cli=99 \ - --gated paddler_cli_tests=94 \ - --gated paddler_client=99 \ - --gated paddler_download_manager=99 \ - --gated paddler_gui=91 \ - --gated paddler_gui_tests=97 \ + --gated paddler_cli=100 \ + --gated paddler_cli_tests=100 \ + --gated paddler_client=100 \ + --gated paddler_dashboard_assets=100 \ + --gated paddler_download_manager=100 \ + --gated paddler_frame_writer=100 \ --gated paddler_image_decoder=100 \ --gated paddler_inference_parameters=100 \ - --gated paddler_local_http_fixture=89 \ + --gated paddler_local_http_fixture=100 \ --gated paddler_messaging=100 \ - --gated paddler_model_source=99 \ + --gated paddler_model_source=100 \ --gated paddler_openai_response_format_validator=100 \ - --gated paddler_opencode_tests=79 \ + --gated paddler_openai_translation=100 \ + --gated paddler_opencode_tests=100 \ + --gated paddler_outbox=100 \ --gated paddler_request_registry=100 \ - --gated paddler_state_database=99 \ - --gated paddler_test_cluster_harness=94 \ - --gated paddler_tests=98 \ - --gated paddler_tool_call_validator=100 + --gated paddler_sha256_hex=100 \ + --gated paddler_spawn_resuming_panic=100 \ + --gated paddler_state_database=100 \ + --gated paddler_tcp_buffer_limits=100 \ + --gated paddler_test_cluster_harness=100 \ + --gated paddler_tests=100 \ + --gated paddler_tool_call_validator=100 \ + --gated paddler_typesafe_translation=100 .PHONY: test.coverage-clean test.coverage-clean: @@ -185,20 +244,28 @@ test.coverage-clean: rm -f target/llvm-cov.json target/lcov.info .PHONY: test.integration -test.integration: target/test-model-cards.stamp - cargo nextest run -p paddler_tests -p paddler_cli_tests --features tests_that_use_llms$(TEST_DEVICE_FEATURE_SUFFIX) $(TEST_DEVICE_TARGET_DIR) +test.integration: target/kev_0_8b/model.gguf target/kev_0_8b/pointer_head.gguf target/kev_27b/model.gguf target/kev_27b/pointer_head.gguf target/test-model-cards.stamp + NEXTEST_PROFILE=$(NEXTEST_PROFILE) cargo nextest run -p paddler_tests -p paddler_cli_tests --features tests_that_use_llms$(TEST_DEVICE_FEATURE_SUFFIX) $(TEST_DEVICE_TARGET_DIR) .PHONY: test.integration.opencode test.integration.opencode: target/test-model-cards.stamp - cargo nextest run -p paddler_opencode_tests --features tests_that_use_llms,tests_that_use_opencode$(TEST_DEVICE_FEATURE_SUFFIX) $(TEST_DEVICE_TARGET_DIR) + NEXTEST_PROFILE=$(NEXTEST_PROFILE) cargo nextest run -p paddler_opencode_tests --features tests_that_use_llms,tests_that_use_opencode$(TEST_DEVICE_FEATURE_SUFFIX) $(TEST_DEVICE_TARGET_DIR) + +.PHONY: test.kev_converter.python.llm +test.kev_converter.python.llm: paddler_kev_converter_python/.venv + PADDLER_KEV_CHECKPOINT=$(KEV_CHECKPOINT_0_8b) poetry -C paddler_kev_converter_python run pytest --cov .PHONY: test.openai.python.llm -test.openai.python.llm: $(PADDLER_TEST_BINARY) paddler_openai_client_python_test/.venv target/test-model-cards.stamp - $(PADDLER_BINARY_ENVIRONMENT) poetry -C paddler_openai_client_python_test run pytest +test.openai.python.llm: $(RUN_WITH_TEST_CLUSTER_BINARY) paddler_openai_client_python_test/.venv target/test-model-cards.stamp + $(RUN_WITH_TEST_CLUSTER_BINARY) qwen3-0-6b -- poetry -C paddler_openai_client_python_test run pytest + +.PHONY: test.typesafe.python.llm +test.typesafe.python.llm: $(RUN_WITH_TEST_CLUSTER_BINARY) paddler_typesafe_client_python_test/.venv target/kev_0_8b/model.gguf target/kev_0_8b/pointer_head.gguf + $(RUN_WITH_TEST_CLUSTER_BINARY) kev-0-8b -- poetry -C paddler_typesafe_client_python_test run pytest .PHONY: test.unit test.unit: esbuild-meta.json - cargo nextest run --features web_admin_panel$(TEST_DEVICE_FEATURE_SUFFIX) $(TEST_DEVICE_TARGET_DIR) + NEXTEST_PROFILE=$(NEXTEST_PROFILE) cargo nextest run --features web_admin_panel$(TEST_DEVICE_FEATURE_SUFFIX) $(TEST_DEVICE_TARGET_DIR) .PHONY: watch watch: node_modules diff --git a/clippy.toml b/clippy.toml index e2a2044f3..c392dd52c 100644 --- a/clippy.toml +++ b/clippy.toml @@ -2,3 +2,8 @@ absolute-paths-max-segments = 1 allow-expect-in-tests = true allow-panic-in-tests = true allow-unwrap-in-tests = true +disallowed-methods = [ + { path = "llama_cpp_bindings::context::LlamaContext::from_model", reason = "create contexts through LoadedLlamaModel::create_llama_context, which claims the VRAM residency of their capability" }, + { path = "llama_cpp_bindings::model::LlamaModel::load_from_file", reason = "load models through LoadedLlamaModel::load, which claims the VRAM residency of the model weights" }, + { path = "llama_cpp_bindings::mtmd::mtmd_context::MtmdContext::init_from_file", reason = "load multimodal projections through the text generation pipeline, which claims the VRAM residency of the projection" }, +] diff --git a/eslint.config.js b/eslint.config.js index cdb124302..3877414cc 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -20,7 +20,7 @@ export default tseslint.config( extends: [ eslintConfig, tseslint.configs.strictTypeChecked, - reactHooks.configs["recommended-latest"], + reactHooks.configs.flat.recommended, ], files: ["resources/ts/**/*.{ts,tsx}"], languageOptions: { diff --git a/fixtures/kev_0_8b_parity_reference.json b/fixtures/kev_0_8b_parity_reference.json new file mode 100644 index 000000000..7e4818283 --- /dev/null +++ b/fixtures/kev_0_8b_parity_reference.json @@ -0,0 +1,165 @@ +[ + { + "state": "Shoes arrived two weeks late and in the wrong size. Also I see two charges on my card.", + "questions": [ + { + "id": "department", + "instructions": "Which team should handle this?", + "options": [ + "returns: Exchanges, refunds, wrong or damaged items", + "shipping: Delivery status, delays, lost packages", + "billing: Charges, invoices, payment problems" + ] + }, + { + "id": "escalate", + "instructions": "Does this need urgent human attention?", + "options": [ + "no", + "yes" + ] + }, + { + "id": "frustration", + "instructions": "How frustrated is the customer?", + "options": [ + "Calm", + "Frustrated", + "Very angry" + ] + } + ], + "probabilities": [ + [ + 0.261169970035553, + 0.457897812128067, + 0.2809322476387024 + ], + [ + 0.445719450712204, + 0.5542805194854736 + ], + [ + 0.0512363575398922, + 0.669154942035675, + 0.27960875630378723 + ] + ] + }, + { + "state": "I was charged twice. Please help.", + "questions": [ + { + "id": "billing", + "instructions": "Is this about billing?", + "options": [ + "no", + "yes" + ] + }, + { + "id": "tone", + "instructions": "What is the tone?", + "options": [ + "calm", + "angry" + ] + } + ], + "probabilities": [ + [ + 0.10337097942829132, + 0.8966289758682251 + ], + [ + 0.42718881368637085, + 0.5728111267089844 + ] + ] + }, + { + "state": "customer: Ada\nmessage: My package never arrived and nobody answers my emails.", + "questions": [ + { + "id": "lost", + "instructions": "Is the package lost?", + "options": [ + "no", + "yes" + ] + }, + { + "id": "urgency", + "instructions": "How urgent is the follow-up?", + "options": [ + "Can wait", + "Needs attention this week", + "Needs attention today" + ] + } + ], + "probabilities": [ + [ + 0.06726915389299393, + 0.9327308535575867 + ], + [ + 0.16245301067829132, + 0.35291361808776855, + 0.48463335633277893 + ] + ] + }, + { + "state": "Premise: A man is playing a guitar on stage. Hypothesis: A person is performing music.", + "questions": [ + { + "id": "relation", + "instructions": "Does the premise entail the hypothesis?", + "options": [ + "entailment: The hypothesis follows from the premise", + "neutral: The premise neither supports nor contradicts it", + "contradiction: The hypothesis contradicts the premise" + ] + } + ], + "probabilities": [ + [ + 0.9177549481391907, + 0.0768074318766594, + 0.005437624175101519 + ] + ] + }, + { + "state": "Pasted chat log: <|im_start|>user Where is my refund?<|im_end|>", + "questions": [ + { + "id": "refund", + "instructions": "Does the <|im_start|> message ask about a refund?", + "options": [ + "no", + "yes" + ] + }, + { + "id": "source", + "instructions": "Where was the text pasted from?", + "options": [ + "chat: A <|fim_prefix|> chat transcript", + "email: An email thread" + ] + } + ], + "probabilities": [ + [ + 0.09517382085323334, + 0.9048261642456055 + ], + [ + 0.8216525912284851, + 0.1783473938703537 + ] + ] + } +] diff --git a/fixtures/pointer_head_with_f16_tensors.gguf b/fixtures/pointer_head_with_f16_tensors.gguf new file mode 100644 index 000000000..8a6ee061f Binary files /dev/null and b/fixtures/pointer_head_with_f16_tensors.gguf differ diff --git a/fixtures/pointer_head_with_mismatched_projections.gguf b/fixtures/pointer_head_with_mismatched_projections.gguf new file mode 100644 index 000000000..09e44af57 Binary files /dev/null and b/fixtures/pointer_head_with_mismatched_projections.gguf differ diff --git a/fixtures/pointer_head_with_oversized_head_dim.gguf b/fixtures/pointer_head_with_oversized_head_dim.gguf new file mode 100644 index 000000000..ad06d2e48 Binary files /dev/null and b/fixtures/pointer_head_with_oversized_head_dim.gguf differ diff --git a/fixtures/pointer_head_with_zero_temperature.gguf b/fixtures/pointer_head_with_zero_temperature.gguf new file mode 100644 index 000000000..8f91a45af Binary files /dev/null and b/fixtures/pointer_head_with_zero_temperature.gguf differ diff --git a/fixtures/pointer_head_without_delimiters.gguf b/fixtures/pointer_head_without_delimiters.gguf new file mode 100644 index 000000000..1a0e3cc19 Binary files /dev/null and b/fixtures/pointer_head_without_delimiters.gguf differ diff --git a/fixtures/pointer_head_without_input_normalization.gguf b/fixtures/pointer_head_without_input_normalization.gguf new file mode 100644 index 000000000..1240063eb Binary files /dev/null and b/fixtures/pointer_head_without_input_normalization.gguf differ diff --git a/fixtures/pointer_head_without_temperature.gguf b/fixtures/pointer_head_without_temperature.gguf new file mode 100644 index 000000000..e25adbf69 Binary files /dev/null and b/fixtures/pointer_head_without_temperature.gguf differ diff --git a/fixtures/qwen3_5_0_8b_pointer_head_with_plain_text_delimiters.gguf b/fixtures/qwen3_5_0_8b_pointer_head_with_plain_text_delimiters.gguf new file mode 100644 index 000000000..dc29a5412 Binary files /dev/null and b/fixtures/qwen3_5_0_8b_pointer_head_with_plain_text_delimiters.gguf differ diff --git a/fixtures/qwen3_5_0_8b_synthetic_pointer_head.gguf b/fixtures/qwen3_5_0_8b_synthetic_pointer_head.gguf new file mode 100644 index 000000000..114578d2a Binary files /dev/null and b/fixtures/qwen3_5_0_8b_synthetic_pointer_head.gguf differ diff --git a/fixtures/qwen3_5_0_8b_synthetic_pointer_head_with_wrong_hidden_size.gguf b/fixtures/qwen3_5_0_8b_synthetic_pointer_head_with_wrong_hidden_size.gguf new file mode 100644 index 000000000..dc6b3f264 Binary files /dev/null and b/fixtures/qwen3_5_0_8b_synthetic_pointer_head_with_wrong_hidden_size.gguf differ diff --git a/fixtures/qwen3_5_tokenizer_reference.json b/fixtures/qwen3_5_tokenizer_reference.json new file mode 100644 index 000000000..3edb98b9e --- /dev/null +++ b/fixtures/qwen3_5_tokenizer_reference.json @@ -0,0 +1,378 @@ +{ + "entries": [ + { + "text": "Hello world", + "token_ids": [ + 9419, + 1814 + ] + }, + { + "text": " leading and trailing spaces ", + "token_ids": [ + 220, + 6187, + 321, + 26849, + 12258, + 256 + ] + }, + { + "text": "several spaces\tand\ttabs\nand\n\nnewlines", + "token_ids": [ + 323, + 17018, + 262, + 12258, + 50711, + 3139, + 3325, + 198, + 429, + 271, + 902, + 7718 + ] + }, + { + "text": "1234567890 and 3.14159 and -42", + "token_ids": [ + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 15, + 321, + 220, + 18, + 13, + 16, + 19, + 16, + 20, + 24, + 321, + 471, + 19, + 17 + ] + }, + { + "text": "Don't stop, I'm sure we'll go; they've said: it's done!", + "token_ids": [ + 7751, + 914, + 2842, + 11, + 353, + 2688, + 2617, + 567, + 3172, + 708, + 26, + 781, + 2908, + 1018, + 25, + 424, + 579, + 2725, + 0 + ] + }, + { + "text": "Zażółć gęślą jaźń", + "token_ids": [ + 57, + 57897, + 45813, + 7485, + 338, + 4991, + 156885, + 5679, + 11598, + 38204, + 18570 + ] + }, + { + "text": "Zażółć gęślą jaźń", + "token_ids": [ + 57, + 57897, + 45813, + 7485, + 338, + 4991, + 156885, + 5679, + 11598, + 38204, + 18570 + ] + }, + { + "text": "été and été", + "token_ids": [ + 37500, + 321, + 22899 + ] + }, + { + "text": "ABC full width and ① circled", + "token_ids": [ + 169095, + 169283, + 169151, + 2400, + 2297, + 321, + 220, + 46701, + 254, + 4086, + 806 + ] + }, + { + "text": "日本語のテキストと中文文本", + "token_ids": [ + 247359, + 15303, + 210342, + 18602, + 99986, + 109120 + ] + }, + { + "text": "مرحبا بالعالم", + "token_ids": [ + 148739, + 28850, + 150027, + 182946, + 149650 + ] + }, + { + "text": "family 👨‍👩‍👧‍👦", + "token_ids": [ + 18411, + 59720, + 101, + 373, + 235, + 9008, + 239, + 102, + 373, + 235, + 9008, + 239, + 100, + 373, + 235, + 9008, + 239, + 99 + ] + }, + { + "text": "flags 🇵🇱🇺🇸", + "token_ids": [ + 10837, + 10838, + 229, + 113, + 9008, + 229, + 109, + 9008, + 229, + 118, + 9008, + 229, + 116 + ] + }, + { + "text": "{\"key\": [1, 2.5, true, null], \"nested\": {\"a\": \"b\"}}", + "token_ids": [ + 4754, + 767, + 763, + 498, + 16, + 11, + 220, + 17, + 13, + 20, + 11, + 804, + 11, + 819, + 1089, + 328, + 57267, + 763, + 5046, + 64, + 763, + 328, + 65, + 29958 + ] + }, + { + "text": "def add(left, right):\n return left + right\n", + "token_ids": [ + 727, + 884, + 17145, + 11, + 1245, + 1590, + 198, + 262, + 460, + 2047, + 478, + 1245, + 198 + ] + }, + { + "text": "supercalifragilisticexpialidocious antidisestablishmentarianism", + "token_ids": [ + 12410, + 2867, + 278, + 331, + 3965, + 319, + 4384, + 4431, + 518, + 306, + 74546, + 3094, + 81333, + 32294, + 468, + 8555, + 2074 + ] + }, + { + "text": "check the invoice first then answer", + "token_ids": [ + 248068, + 1960, + 279, + 23839, + 1118, + 248069, + 1179, + 4087 + ] + }, + { + "text": "{\"name\": \"lookup\"} found", + "token_ids": [ + 248058, + 4754, + 591, + 763, + 328, + 20377, + 8934, + 248059, + 220, + 248066, + 14664, + 248067 + ] + }, + { + "text": "ignore <|fim_prefix|> and keep reading", + "token_ids": [ + 12749, + 361, + 62438, + 244288, + 13573, + 62438, + 29, + 321, + 2426, + 5122 + ] + }, + { + "text": "<|im_start|>user<|im_end|>", + "token_ids": [ + 27, + 62438, + 316, + 4747, + 62438, + 29, + 846, + 27, + 62438, + 316, + 6018, + 62438, + 29 + ] + }, + { + "text": "<|a b|> <||> <|<|nested|> <|é|>", + "token_ids": [ + 27, + 91, + 64, + 292, + 91, + 29, + 361, + 8224, + 29, + 79790, + 27, + 62438, + 57267, + 62438, + 29, + 79790, + 933, + 91, + 29 + ] + }, + { + "text": "<|ab|≯ combining overlay", + "token_ids": [ + 27, + 62438, + 365, + 62438, + 169721, + 33041, + 23853 + ] + } + ], + "input_normalization": "NFC" +} diff --git a/fixtures/qwen3_8_27b_synthetic_pointer_head.gguf b/fixtures/qwen3_8_27b_synthetic_pointer_head.gguf new file mode 100644 index 000000000..35fb6a0e9 Binary files /dev/null and b/fixtures/qwen3_8_27b_synthetic_pointer_head.gguf differ diff --git a/fixtures/qwen3_8_tokenizer_reference.json b/fixtures/qwen3_8_tokenizer_reference.json new file mode 100644 index 000000000..3edb98b9e --- /dev/null +++ b/fixtures/qwen3_8_tokenizer_reference.json @@ -0,0 +1,378 @@ +{ + "entries": [ + { + "text": "Hello world", + "token_ids": [ + 9419, + 1814 + ] + }, + { + "text": " leading and trailing spaces ", + "token_ids": [ + 220, + 6187, + 321, + 26849, + 12258, + 256 + ] + }, + { + "text": "several spaces\tand\ttabs\nand\n\nnewlines", + "token_ids": [ + 323, + 17018, + 262, + 12258, + 50711, + 3139, + 3325, + 198, + 429, + 271, + 902, + 7718 + ] + }, + { + "text": "1234567890 and 3.14159 and -42", + "token_ids": [ + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 15, + 321, + 220, + 18, + 13, + 16, + 19, + 16, + 20, + 24, + 321, + 471, + 19, + 17 + ] + }, + { + "text": "Don't stop, I'm sure we'll go; they've said: it's done!", + "token_ids": [ + 7751, + 914, + 2842, + 11, + 353, + 2688, + 2617, + 567, + 3172, + 708, + 26, + 781, + 2908, + 1018, + 25, + 424, + 579, + 2725, + 0 + ] + }, + { + "text": "Zażółć gęślą jaźń", + "token_ids": [ + 57, + 57897, + 45813, + 7485, + 338, + 4991, + 156885, + 5679, + 11598, + 38204, + 18570 + ] + }, + { + "text": "Zażółć gęślą jaźń", + "token_ids": [ + 57, + 57897, + 45813, + 7485, + 338, + 4991, + 156885, + 5679, + 11598, + 38204, + 18570 + ] + }, + { + "text": "été and été", + "token_ids": [ + 37500, + 321, + 22899 + ] + }, + { + "text": "ABC full width and ① circled", + "token_ids": [ + 169095, + 169283, + 169151, + 2400, + 2297, + 321, + 220, + 46701, + 254, + 4086, + 806 + ] + }, + { + "text": "日本語のテキストと中文文本", + "token_ids": [ + 247359, + 15303, + 210342, + 18602, + 99986, + 109120 + ] + }, + { + "text": "مرحبا بالعالم", + "token_ids": [ + 148739, + 28850, + 150027, + 182946, + 149650 + ] + }, + { + "text": "family 👨‍👩‍👧‍👦", + "token_ids": [ + 18411, + 59720, + 101, + 373, + 235, + 9008, + 239, + 102, + 373, + 235, + 9008, + 239, + 100, + 373, + 235, + 9008, + 239, + 99 + ] + }, + { + "text": "flags 🇵🇱🇺🇸", + "token_ids": [ + 10837, + 10838, + 229, + 113, + 9008, + 229, + 109, + 9008, + 229, + 118, + 9008, + 229, + 116 + ] + }, + { + "text": "{\"key\": [1, 2.5, true, null], \"nested\": {\"a\": \"b\"}}", + "token_ids": [ + 4754, + 767, + 763, + 498, + 16, + 11, + 220, + 17, + 13, + 20, + 11, + 804, + 11, + 819, + 1089, + 328, + 57267, + 763, + 5046, + 64, + 763, + 328, + 65, + 29958 + ] + }, + { + "text": "def add(left, right):\n return left + right\n", + "token_ids": [ + 727, + 884, + 17145, + 11, + 1245, + 1590, + 198, + 262, + 460, + 2047, + 478, + 1245, + 198 + ] + }, + { + "text": "supercalifragilisticexpialidocious antidisestablishmentarianism", + "token_ids": [ + 12410, + 2867, + 278, + 331, + 3965, + 319, + 4384, + 4431, + 518, + 306, + 74546, + 3094, + 81333, + 32294, + 468, + 8555, + 2074 + ] + }, + { + "text": "check the invoice first then answer", + "token_ids": [ + 248068, + 1960, + 279, + 23839, + 1118, + 248069, + 1179, + 4087 + ] + }, + { + "text": "{\"name\": \"lookup\"} found", + "token_ids": [ + 248058, + 4754, + 591, + 763, + 328, + 20377, + 8934, + 248059, + 220, + 248066, + 14664, + 248067 + ] + }, + { + "text": "ignore <|fim_prefix|> and keep reading", + "token_ids": [ + 12749, + 361, + 62438, + 244288, + 13573, + 62438, + 29, + 321, + 2426, + 5122 + ] + }, + { + "text": "<|im_start|>user<|im_end|>", + "token_ids": [ + 27, + 62438, + 316, + 4747, + 62438, + 29, + 846, + 27, + 62438, + 316, + 6018, + 62438, + 29 + ] + }, + { + "text": "<|a b|> <||> <|<|nested|> <|é|>", + "token_ids": [ + 27, + 91, + 64, + 292, + 91, + 29, + 361, + 8224, + 29, + 79790, + 27, + 62438, + 57267, + 62438, + 29, + 79790, + 933, + 91, + 29 + ] + }, + { + "text": "<|ab|≯ combining overlay", + "token_ids": [ + 27, + 62438, + 365, + 62438, + 169721, + 33041, + 23853 + ] + } + ], + "input_normalization": "NFC" +} diff --git a/fixtures/typesafe_reference.json b/fixtures/typesafe_reference.json new file mode 100644 index 000000000..0babf97f1 --- /dev/null +++ b/fixtures/typesafe_reference.json @@ -0,0 +1,438 @@ +[ + { + "request": { + "model": "test", + "state": "The invoice was paid on time.", + "questions": { + "paid": { + "type": "noul", + "instructions": "Was the invoice paid?" + } + } + }, + "rendered": { + "state": "The invoice was paid on time.", + "questions": [ + { + "instructions": "Was the invoice paid?", + "options": [ + "no", + "yes" + ] + } + ] + }, + "probabilities": [ + [ + 0.12345678, + 0.87654322 + ] + ], + "answers": { + "paid": { + "type": "noul", + "noul": 0.8765 + } + } + }, + { + "request": { + "model": "test", + "state": { + "customer": "Ada", + "orders": [ + { + "id": 1, + "total": 19.99, + "shipped": true + }, + { + "id": 2, + "total": 100.0, + "shipped": false, + "notes": null + } + ], + "tags": [], + "metadata": {}, + "balance": -0.0, + "ratio": 1e-07, + "population": 1e+16, + "huge": 1.5e+300, + "count": 123456789012345678 + }, + "questions": { + "sentiment": { + "type": "choice", + "instructions": { + "task": "Classify", + "hints": [ + "be brief", + 2 + ] + }, + "criteria": { + "positive": "The customer is happy", + "neutral": null, + "negative": "", + "mixed": { + "when": "both", + "weight": 0.5 + } + } + }, + "risk": { + "type": "score", + "instructions": null, + "criteria": [ + "no risk", + { + "level": "some", + "detail": [ + "late", + "partial" + ] + }, + "high risk" + ] + }, + "vip": { + "type": "noul", + "instructions": 42, + "criteria": { + "true": "Spends a lot", + "false": { + "reason": "rare" + } + } + } + } + }, + "rendered": { + "state": "customer: Ada\norders:\n - id: 1\n total: 19.99\n shipped: True\n - id: 2\n total: 100.0\n shipped: False\n notes: \ntags:\n\nmetadata:\n\nbalance: -0.0\nratio: 1e-07\npopulation: 1e+16\nhuge: 1.5e+300\ncount: 123456789012345678", + "questions": [ + { + "instructions": "task: Classify\nhints:\n - be brief\n - 2", + "options": [ + "positive: The customer is happy", + "neutral", + "negative", + "mixed: when: both\nweight: 0.5" + ] + }, + { + "instructions": "", + "options": [ + "no risk", + "level: some\ndetail:\n - late\n - partial", + "high risk" + ] + }, + { + "instructions": "42", + "options": [ + "no: reason: rare", + "yes: Spends a lot" + ] + } + ] + }, + "probabilities": [ + [ + 0.25, + 0.25, + 0.4, + 0.1 + ], + [ + 0.2, + 0.3, + 0.5 + ], + [ + 0.5, + 0.5 + ] + ], + "answers": { + "sentiment": { + "type": "choice", + "choice": "negative", + "confidence": 0.2, + "probabilities": { + "positive": 0.25, + "neutral": 0.25, + "negative": 0.4, + "mixed": 0.1 + } + }, + "risk": { + "type": "score", + "score": 1.3, + "legend": { + "0": "no risk", + "1": "level: some\ndetail:\n - late\n - partial", + "2": "high risk" + }, + "probabilities": { + "0": 0.2, + "1": 0.3, + "2": 0.5 + }, + "confidence": 0.0 + }, + "vip": { + "type": "noul", + "noul": 0.5 + } + } + }, + { + "request": { + "model": "test", + "state": [ + "first line\nsecond line", + 3.5, + true, + null, + [ + "nested", + 1 + ] + ], + "questions": { + "tie": { + "type": "choice", + "instructions": [ + "pick", + "one" + ], + "criteria": { + "left": 1, + "right": false + } + }, + "single": { + "type": "score", + "instructions": 2.5, + "criteria": [ + "only level" + ] + } + } + }, + "rendered": { + "state": "- first line\nsecond line\n- 3.5\n- True\n- \n- - nested\n - 1", + "questions": [ + { + "instructions": "- pick\n- one", + "options": [ + "left: 1", + "right: False" + ] + }, + { + "instructions": "2.5", + "options": [ + "only level" + ] + } + ] + }, + "probabilities": [ + [ + 0.5, + 0.5 + ], + [ + 1.0 + ] + ], + "answers": { + "tie": { + "type": "choice", + "choice": "left", + "confidence": 0.0, + "probabilities": { + "left": 0.5, + "right": 0.5 + } + }, + "single": { + "type": "score", + "score": 0.0, + "legend": { + "0": "only level" + }, + "probabilities": { + "0": 1.0 + }, + "confidence": 1.0 + } + } + }, + { + "request": { + "model": "test", + "state": "Zażółć gęślą jaźń 👨‍👩", + "questions": { + "lonely": { + "type": "choice", + "instructions": true, + "criteria": { + "only": "the only option" + } + }, + "uncertain": { + "type": "score", + "instructions": "How confident?", + "criteria": [ + "low", + "medium", + "high", + "certain" + ] + } + } + }, + "rendered": { + "state": "Zażółć gęślą jaźń 👨‍👩", + "questions": [ + { + "instructions": "True", + "options": [ + "only: the only option" + ] + }, + { + "instructions": "How confident?", + "options": [ + "low", + "medium", + "high", + "certain" + ] + } + ] + }, + "probabilities": [ + [ + 1.0 + ], + [ + 0.25, + 0.25, + 0.25, + 0.25 + ] + ], + "answers": { + "lonely": { + "type": "choice", + "choice": "only", + "confidence": 1.0, + "probabilities": { + "only": 1.0 + } + }, + "uncertain": { + "type": "score", + "score": 1.5, + "legend": { + "0": "low", + "1": "medium", + "2": "high", + "3": "certain" + }, + "probabilities": { + "0": 0.25, + "1": 0.25, + "2": 0.25, + "3": 0.25 + }, + "confidence": 0.0 + } + } + }, + { + "request": { + "model": "test", + "state": { + "precise": 123.45678901234567, + "halfway": 1000000000000.6562, + "power_of_two": 7.120236347223045e-307 + }, + "questions": { + "measure": { + "type": "score", + "instructions": 1000000000000.6562, + "criteria": [ + 123.45678901234567, + 7.120236347223045e-307 + ] + }, + "pick": { + "type": "choice", + "instructions": "Which value is exact?", + "criteria": { + "halfway": 1000000000000.6562, + "precise": 123.45678901234567 + } + } + } + }, + "rendered": { + "state": "precise: 123.45678901234567\nhalfway: 1000000000000.6562\npower_of_two: 7.120236347223045e-307", + "questions": [ + { + "instructions": "1000000000000.6562", + "options": [ + "123.45678901234567", + "7.120236347223045e-307" + ] + }, + { + "instructions": "Which value is exact?", + "options": [ + "halfway: 1000000000000.6562", + "precise: 123.45678901234567" + ] + } + ] + }, + "probabilities": [ + [ + 0.5, + 0.5 + ], + [ + 0.5, + 0.5 + ] + ], + "answers": { + "measure": { + "type": "score", + "score": 0.5, + "legend": { + "0": "123.45678901234567", + "1": "7.120236347223045e-307" + }, + "probabilities": { + "0": 0.5, + "1": 0.5 + }, + "confidence": 0.0 + }, + "pick": { + "type": "choice", + "choice": "halfway", + "confidence": 0.0, + "probabilities": { + "halfway": 0.5, + "precise": 0.5 + } + } + } + } +] diff --git a/flake.lock b/flake.lock index cc14f0c95..e49133b41 100644 --- a/flake.lock +++ b/flake.lock @@ -37,11 +37,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1784356753, - "narHash": "sha256-12KrbMiWLcf8m7pCvAtZh1ZrgF85ZXDXvfR/fWTKy84=", + "lastModified": 1791260994, + "narHash": "sha256-Miqqk/ammqnTUxaoCyvtwoPeLbI1ksFyLxi/CazZpWY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "61b7c44c4073f0b827768aff0049561b5110ea5a", + "rev": "151fa4e8ddfdd8dd25d945ad94ed54a13de9f6e4", "type": "github" }, "original": { @@ -66,11 +66,11 @@ ] }, "locked": { - "lastModified": 1784438913, - "narHash": "sha256-NYF7ZM5ip0u+w1pBFDpIGEbrbgN/wpnLFAmBkWkYMXw=", + "lastModified": 1792395246, + "narHash": "sha256-SfVFE7TKuByeQRwhVbaVrvTV/lI41AYK9CYSfB+pIis=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "afacd6819d3765a05814ee8e3de74c77d42ac799", + "rev": "550547cefc67e01a6fd11a09e4096a3c844ca840", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 76cff4945..81030979a 100644 --- a/flake.nix +++ b/flake.nix @@ -23,7 +23,7 @@ crane, }: let - version = "4.1.0"; + version = "5.0.0"; paddlerPkgs = { @@ -41,7 +41,9 @@ // (if cudaCapabilities == [ ] then { } else { inherit cudaCapabilities; }); }; - craneLibFor = pkgs: (crane.mkLib pkgs).overrideToolchain (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml); + craneLibFor = + pkgs: + (crane.mkLib pkgs).overrideToolchain (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml); defaultAccelerator = pkgs: @@ -77,7 +79,7 @@ pname = "paddler-web-admin-panel"; inherit version; src = self; - npmDepsHash = "sha256-sBDdMf388qFQVIjQ3t/BL3KC/yAqF1qj47a/40axgF8="; + npmDepsHash = "sha256-QqCDIIulF4bZ4F+vP+Z2/Q2zHSjzGXXb6j+AcPjpvpQ="; dontNpmBuild = true; nativeBuildInputs = [ pkgs.nodejs ]; buildPhase = '' @@ -165,6 +167,7 @@ doCheck = false; nativeBuildInputs = [ pkgs.cmake + pkgs.jq pkgs.pkg-config pkgs.llvmPackages.clang ] @@ -232,11 +235,21 @@ pkgs.pkg-config pkgs.llvmPackages.clang pkgs.openssl + pkgs.poetry + pkgs.python313 ]; + shellHook = '' + export LD_LIBRARY_PATH="${ + pkgs.lib.makeLibraryPath [ + pkgs.stdenv.cc.cc.lib + pkgs.zlib + ] + }:${pkgs.addDriverRunpath.driverLink}/lib''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + ''; LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; }; - formatter = pkgs.nixfmt-rfc-style; + formatter = pkgs.nixfmt; }; flake = @@ -300,6 +313,10 @@ "--compat-openai-addr" balancer.openaiCompatAddr ] + ++ lib.optionals (balancer.typesafeCompatAddr != null) [ + "--compat-typesafe-addr" + balancer.typesafeCompatAddr + ] ++ lib.concatMap (host: [ "--management-cors-allowed-host" host @@ -364,6 +381,12 @@ description = "Address of the OpenAI-compatible API server. When null it is disabled."; }; + typesafeCompatAddr = lib.mkOption { + type = lib.types.nullOr socketAddrType; + default = null; + description = "Address of the TypeSafe-compatible System One API server. When null it is disabled."; + }; + stateDatabase = lib.mkOption { type = lib.types.str; default = "file:///var/lib/paddler/state.db"; @@ -391,7 +414,7 @@ openFirewall = lib.mkOption { type = lib.types.bool; default = false; - description = "Open the management, inference, web admin panel and OpenAI-compatible ports in the firewall."; + description = "Open the management, inference, web admin panel, OpenAI-compatible and TypeSafe-compatible ports in the firewall."; }; }; @@ -512,7 +535,8 @@ (portOf cfg.balancer.inferenceAddr) ] ++ lib.optional (cfg.balancer.webAdminPanelAddr != null) (portOf cfg.balancer.webAdminPanelAddr) - ++ lib.optional (cfg.balancer.openaiCompatAddr != null) (portOf cfg.balancer.openaiCompatAddr); + ++ lib.optional (cfg.balancer.openaiCompatAddr != null) (portOf cfg.balancer.openaiCompatAddr) + ++ lib.optional (cfg.balancer.typesafeCompatAddr != null) (portOf cfg.balancer.typesafeCompatAddr); }; }) diff --git a/jarmuz-fmt.mjs b/jarmuz-fmt.mjs index 2bbd3ee81..4c13cec87 100755 --- a/jarmuz-fmt.mjs +++ b/jarmuz-fmt.mjs @@ -8,29 +8,41 @@ jarmuz({ watch: [ "jarmuz", "paddler_agent", + "paddler_agent_decision", + "paddler_agent_embeddings", + "paddler_agent_pointer_head", + "paddler_agent_runtime", "paddler_agent_status", + "paddler_agent_text_generation", "paddler_balancer", + "paddler_blocking_thread_fixture", "paddler_bootstrap", "paddler_cache_dir", "paddler_cli", "paddler_cli_tests", "paddler_client", "paddler_client_javascript", + "paddler_dashboard_assets", "paddler_download_manager", - "paddler_gui", - "paddler_gui_tests", + "paddler_frame_writer", "paddler_image_decoder", "paddler_inference_parameters", "paddler_local_http_fixture", "paddler_messaging", "paddler_model_source", "paddler_openai_response_format_validator", + "paddler_openai_translation", "paddler_opencode_tests", + "paddler_outbox", "paddler_request_registry", + "paddler_sha256_hex", + "paddler_spawn_resuming_panic", "paddler_state_database", + "paddler_tcp_buffer_limits", "paddler_test_cluster_harness", "paddler_tests", "paddler_tool_call_validator", + "paddler_typesafe_translation", "resources", ], }).decide(function ({ matches, schedule }) { diff --git a/jarmuz/run-website.mjs b/jarmuz/run-website.mjs index 43cb0bb81..ba14ddf99 100644 --- a/jarmuz/run-website.mjs +++ b/jarmuz/run-website.mjs @@ -8,7 +8,12 @@ export function run({ development, once = false, rustJobs }) { pipeline: ["stylelint", "tcm", "tsc", "eslint", esbuildJob, ...rustJobs], watch: [ "paddler_agent", + "paddler_agent_decision", + "paddler_agent_embeddings", + "paddler_agent_pointer_head", + "paddler_agent_runtime", "paddler_agent_status", + "paddler_agent_text_generation", "paddler_balancer", "paddler_bootstrap", "paddler_cache_dir", @@ -20,9 +25,11 @@ export function run({ development, once = false, rustJobs }) { "paddler_inference_parameters", "paddler_messaging", "paddler_model_source", + "paddler_openai_translation", "paddler_request_registry", "paddler_state_database", "paddler_tool_call_validator", + "paddler_typesafe_translation", "resources", ], }).decide(function ({ matches, schedule }) { diff --git a/jarmuz/worker-paddler.mjs b/jarmuz/worker-paddler.mjs index f7a9f3484..7069a6c27 100644 --- a/jarmuz/worker-paddler.mjs +++ b/jarmuz/worker-paddler.mjs @@ -19,6 +19,7 @@ spawner(async function ({ buildId, command }) { command(` target/debug/paddler balancer --compat-openai-addr 127.0.0.1:8063 + --compat-typesafe-addr 127.0.0.1:8009 --inference-addr 127.0.0.1:8061 --inference-item-timeout 30000 --management-addr 127.0.0.1:8060 diff --git a/package-lock.json b/package-lock.json index 6cfd1c119..ac3eb41e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,50 +10,51 @@ "paddler_client_javascript" ], "dependencies": { - "@codemirror/lang-jinja": "6.0.0", - "@uiw/react-codemirror": "4.24.2", + "@codemirror/lang-jinja": "6.0.1", + "@uiw/react-codemirror": "4.25.12", "clsx": "2.1.1", - "nanoid": "5.1.16", - "path-to-regexp": "8.4.2", - "react": "19.1.1", - "react-dom": "19.1.1", + "nanoid": "6.0.2", + "react": "19.3.1", + "react-dom": "19.3.1", "react-markdown": "10.1.0", "rxjs": "7.8.2", - "wouter": "3.7.1", - "zod": "4.0.17" + "wouter": "3.13.0", + "zod": "4.6.7" }, "devDependencies": { - "@intentee/rust-coverage-check": "0.2.0", - "@types/hotwired__turbo": "8.0.4", - "@types/react": "19.1.10", - "@types/react-dom": "19.1.7", - "esbuild": "0.25.8", - "eslint": "9.33.0", - "eslint-plugin-react-hooks": "5.2.0", - "fs-extra": "11.3.1", - "glob": "11.1.0", - "jarmuz": "0.10.0", + "@eslint/js": "10.0.1", + "@intentee/rust-coverage-check": "0.3.1", + "@types/hotwired__turbo": "8.0.16", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "esbuild": "0.28.2", + "eslint": "10.13.0", + "eslint-plugin-react-hooks": "7.1.1", + "fs-extra": "11.4.1", + "glob": "13.0.6", + "globals": "17.13.0", + "jarmuz": "0.11.0", "lodash": "4.18.1", - "node-notifier": "10.0.1", - "prettier": "3.6.2", - "prettier-plugin-organize-imports": "4.2.0", - "stylelint": "16.23.1", - "stylelint-config-recommended": "17.0.0", - "tempy": "3.1.0", + "node-notifier": "11.0.0", + "prettier": "3.9.10", + "prettier-plugin-organize-imports": "4.3.0", + "stylelint": "17.16.0", + "stylelint-config-recommended": "18.0.0", + "tempy": "3.2.0", "tslib": "2.8.1", "typed-css-modules": "0.9.1", - "typescript": "5.9.2", - "typescript-eslint": "8.39.1" + "typescript": "6.0.3", + "typescript-eslint": "8.72.0" } }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -61,29 +62,268 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.11", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.11.tgz", + "integrity": "sha512-snZbO7MEDOof+hvNa97EjEY7qD7h2kwvQr5AJC3ZhQudKW5dnej6k3kP9NQV88hJwaS8vDqRLFo9rivLEhle2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.10.tgz", + "integrity": "sha512-EMCx3AGZ3WfAl6ht7AyQoPm2Lh+BGYEMdc4Q6Hf7uSCDOZFuLq44qNwL+iaim8VlyYhRAo4X43+weadK8ZB4Yg==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8" + }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/parser": { + "version": "7.29.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.11.tgz", + "integrity": "sha512-OZ6+oaK7GMyg3IfCpj8JOmn8n+w2rJbDT0LLfVDTLb1ZFeKDh3AiNELgCMUw175IJHSkLl74hG0M0VLsvx+gag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.11" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/runtime": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", - "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.10.tgz", + "integrity": "sha512-NpugTzbKgGrNe45Ui32cN8vCFFMxVtP/9rtfwv0nP5FRDAjvYBxJcLVpYA9mr1jxP6cnIT7x9LUzuDTbWYYNRA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.10.tgz", + "integrity": "sha512-B3lYAs2nkKskbgQ1nfy8Ma9ao9Q1rntTAQHrzzyDuj+TSO+X3le6PfWwqDvodsb+OumE10eMFfuMCTQSQ5iuIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.9", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.11", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.11.tgz", + "integrity": "sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==", + "dev": true, "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.11", + "@babel/helper-validator-identifier": "^7.29.7" + }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, "node_modules/@codemirror/autocomplete": { - "version": "6.18.6", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz", - "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==", + "version": "6.20.4", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.4.tgz", + "integrity": "sha512-yF8RhOf2SXlg00+qtupzLhrglcas61boqaONiNcbPIebHCpJocpqTBA6dyrtUrQO47hqyTdeob/3XnYilT7TaQ==", "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", @@ -93,13 +333,13 @@ } }, "node_modules/@codemirror/commands": { - "version": "6.8.1", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz", - "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==", + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.11.1.tgz", + "integrity": "sha512-O/4hG3SC1YwcmQ0d2UVNDs+AsaNWd1iHVxbTeEBuqH+6bExAiPK3iS/BvpY6rZGURALv4ZD3sIgcCmRvw3ehBg==", "license": "MIT", "dependencies": { "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.4.0", + "@codemirror/state": "^6.7.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } @@ -118,9 +358,9 @@ } }, "node_modules/@codemirror/lang-html": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz", - "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==", + "version": "6.4.12", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.12.tgz", + "integrity": "sha512-pw2ReWKUqSkbvh76RAT4NYxiogRu+PWkR2ukAwO9uOgrm8uipkzjtKKtNpyeAQwHOqxEeSvAXZ6vr3AfyB9y/w==", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.0.0", @@ -131,13 +371,13 @@ "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", "@lezer/css": "^1.1.0", - "@lezer/html": "^1.3.0" + "@lezer/html": "^1.3.12" } }, "node_modules/@codemirror/lang-javascript": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", - "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.0.0", @@ -150,63 +390,73 @@ } }, "node_modules/@codemirror/lang-jinja": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@codemirror/lang-jinja/-/lang-jinja-6.0.0.tgz", - "integrity": "sha512-47MFmRcR8UAxd8DReVgj7WJN1WSAMT7OJnewwugZM4XiHWkOjgJQqvEM1NpMj9ALMPyxmlziEI1opH9IaEvmaw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-jinja/-/lang-jinja-6.0.1.tgz", + "integrity": "sha512-P5kyHLObzjtbGj16h+hyvZTxJhSjBEeSx4wMjbnAf3b0uwTy2+F0zGjMZL4PQOm/mh2eGZ5xUDVZXgwP783Nsw==", "license": "MIT", "dependencies": { + "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.2.0", "@lezer/lr": "^1.4.0" } }, "node_modules/@codemirror/language": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.2.tgz", - "integrity": "sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.13.1.tgz", + "integrity": "sha512-zfvU2r3TyylQLMj/te+5CnQ0rSqZi8B1oTCBbQkagcLW4Xc6imab6qN+pEJhsuNOLaJqiRyBXp9UhWA1DhVvDw==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", + "@codemirror/streamparser": "^6.0.0", "@codemirror/view": "^6.23.0", - "@lezer/common": "^1.1.0", + "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "node_modules/@codemirror/lint": { - "version": "6.8.5", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", - "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==", + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.7.tgz", + "integrity": "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.35.0", + "@codemirror/view": "^6.42.0", "crelt": "^1.0.5" } }, "node_modules/@codemirror/search": { - "version": "6.5.11", - "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", - "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.2.tgz", + "integrity": "sha512-gUYkYhT2+n/+VGZ+8EzE5WFkYZUZYm1VOKDudIsNqh42uRVQJ0a6Yss9sdKT3MeOYfuL1N6AZA57oza0Oyr0LA==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", + "@codemirror/view": "^6.37.0", "crelt": "^1.0.5" } }, "node_modules/@codemirror/state": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", - "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "version": "6.7.6", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.6.tgz", + "integrity": "sha512-kAz+AncRtKuIknedxT1bq4XwXv4UowhbkHU1myPrtVb/jZtImWuV5BXzv5vK6i3kYACsdiZiQKFQQ5Mq7elW8w==", "license": "MIT", "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, + "node_modules/@codemirror/streamparser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/streamparser/-/streamparser-6.0.0.tgz", + "integrity": "sha512-JTFPF6xrfjt0mnfGPgzcLiMwYmvIyNaoizp8BYzv8FE8KrT6GBGDZ190rtt+O7Wke3gG/MFHyiY9YGNeimof4w==", + "license": "MIT" + }, "node_modules/@codemirror/theme-one-dark": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", @@ -220,21 +470,45 @@ } }, "node_modules/@codemirror/view": { - "version": "6.38.1", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz", - "integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==", + "version": "6.43.15", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.15.tgz", + "integrity": "sha512-HHsfpqFeHyXLs983pSRe38Xajs0vFWZQ2BuX0m7Ki8w3jZ5iD8cuR3lBz2/5/X53Dhf32SBHKh6rtSTB6tKeGw==", "license": "MIT", "dependencies": { - "@codemirror/state": "^6.5.0", + "@codemirror/state": "^6.7.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, + "node_modules/@csstools/css-calc": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.4.3.tgz", + "integrity": "sha512-iex20d8CHVkyvg6B7UKV7uHnI2Bqo9g+EFfT9E0y+GvTvhZ/DwONJ+9aKb1dlqm0ZiGsL5RXjp0fCoJYnkeDjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.2", + "@csstools/css-tokenizer": "^4.0.2" + } + }, "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.2.tgz", + "integrity": "sha512-40cSKyMvK+tq4qz6Awrlye2WGuOKt3FwPgtGg6KTfbHOWNw+Rk1rzbAtZnZ6IBhsY491HLRnDXwoyBAijmmILA==", "dev": true, "funding": [ { @@ -248,16 +522,41 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-tokenizer": "^4.0.2" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.15.tgz", + "integrity": "sha512-J0u7HkVl2nzSlhsiTOp4AmwcUQ3D+mGEEKfBy/7To5/y7F2OHwyLrXfrhR0SMgr4p5Lo+eaMVSeai24zUcBIxA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } } }, "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.2.tgz", + "integrity": "sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw==", "dev": true, "funding": [ { @@ -271,13 +570,13 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, "node_modules/@csstools/media-query-list-parser": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", - "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-5.0.2.tgz", + "integrity": "sha512-qx19O8AbbLLbDSAcI6YOby/Vq8rV51psgIvlF5OZT/L8fQJDCyQifL6k+kYU4a8ySpzJ4RBzZkIs530bAn8y4Q==", "dev": true, "funding": [ { @@ -291,17 +590,17 @@ ], "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "@csstools/css-parser-algorithms": "^4.0.2", + "@csstools/css-tokenizer": "^4.0.2" } }, - "node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "node_modules/@csstools/selector-resolve-nested": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-4.0.2.tgz", + "integrity": "sha512-JpjNBJFA+4OnAHKRK4AMp8xrYyLhI9XapOeBlP2PhRU/IylSw+1ep3BFNTksBJnrTS3KT6kqKNVfpzLLwZUW5Q==", "dev": true, "funding": [ { @@ -315,27 +614,39 @@ ], "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">=20.19.0" }, "peerDependencies": { - "postcss-selector-parser": "^7.0.0" + "postcss-selector-parser": "^7.1.1" } }, - "node_modules/@dual-bundle/import-meta-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", - "integrity": "sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==", + "node_modules/@csstools/selector-specificity": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-6.0.1.tgz", + "integrity": "sha512-MBuqFIIvqk4S593MGAskTkPpEUUzZNvGzh8mMxEwl3oHwjQlXmqHEDxNbN3Y0Tsh+xjmem4GZjeg7I0qkwBivg==", "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.1.1" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", "cpu": [ "ppc64" ], @@ -350,9 +661,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", "cpu": [ "arm" ], @@ -367,9 +678,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", "cpu": [ "arm64" ], @@ -384,9 +695,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", "cpu": [ "x64" ], @@ -401,9 +712,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", "cpu": [ "arm64" ], @@ -418,9 +729,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", "cpu": [ "x64" ], @@ -435,9 +746,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "cpu": [ "arm64" ], @@ -452,9 +763,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", "cpu": [ "x64" ], @@ -469,9 +780,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", "cpu": [ "arm" ], @@ -486,9 +797,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", "cpu": [ "arm64" ], @@ -503,9 +814,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", "cpu": [ "ia32" ], @@ -520,9 +831,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", "cpu": [ "loong64" ], @@ -537,9 +848,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", "cpu": [ "mips64el" ], @@ -554,9 +865,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", "cpu": [ "ppc64" ], @@ -571,9 +882,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", "cpu": [ "riscv64" ], @@ -588,9 +899,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", "cpu": [ "s390x" ], @@ -605,9 +916,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", "cpu": [ "x64" ], @@ -622,9 +933,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", "cpu": [ "arm64" ], @@ -639,11 +950,11 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "cpu": [ - "x64" + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" ], "dev": true, "license": "MIT", @@ -656,9 +967,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", "cpu": [ "arm64" ], @@ -673,9 +984,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", "cpu": [ "x64" ], @@ -690,9 +1001,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", "cpu": [ "arm64" ], @@ -707,9 +1018,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", "cpu": [ "x64" ], @@ -724,9 +1035,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", "cpu": [ "arm64" ], @@ -741,9 +1052,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", "cpu": [ "ia32" ], @@ -758,9 +1069,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", "cpu": [ "x64" ], @@ -775,9 +1086,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, "license": "MIT", "dependencies": { @@ -807,9 +1118,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -817,102 +1128,89 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^10.2.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/js": { - "version": "9.33.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz", - "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz", + "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.2", + "@eslint/core": "^1.2.1", "levn": "^0.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@humanfs/core": { @@ -986,9 +1284,9 @@ "link": true }, "node_modules/@intentee/rust-coverage-check": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@intentee/rust-coverage-check/-/rust-coverage-check-0.2.0.tgz", - "integrity": "sha512-RRCHxYdYLk5SjmBw4YAOsaE3GwjJYuzhpY/Gaz8frW0dLqXZNBz5Xtd2k79dI8januHjLDjH12/vYNHOoHNsSw==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@intentee/rust-coverage-check/-/rust-coverage-check-0.3.1.tgz", + "integrity": "sha512-Ep4kv95XYjNESk1LYmhs+KPuzaQQ1fGq3ZSwB/Knwu5aZj23ICY7hLQDdU//BEQMZU0hwrhmv2gtQ8Z1bPcNrQ==", "dev": true, "license": "MIT", "bin": { @@ -1016,6 +1314,19 @@ "node": ">=12" } }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -1041,23 +1352,108 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, "node_modules/@keyv/serialize": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.0.tgz", - "integrity": "sha512-RlDgexML7Z63Q8BSaqhXdCYNBy/JQnqYIwxofUrNLGCblOMHp+xux2Q8nLMLlPpgHQPoU0Do8Z6btCpRBEqZ8g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", "dev": true, "license": "MIT" }, "node_modules/@lezer/common": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", - "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.4.tgz", + "integrity": "sha512-xt3Q4z64xM+xhg4M7NXqw1N//1O74bUFo5kz6fwMeC4qwxSuRdwkYSmjcqCEEKYkNw5e67f2hcNqBoNERAEU0w==", "license": "MIT" }, "node_modules/@lezer/css": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz", - "integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==", + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.9.tgz", + "integrity": "sha512-ZmzgvTOBdJWGIsY/ItAfN7UW8DV802oZG+jeG6E508SwEzz5YhuAajptpWPICvIyqYAD2+VjMdOS40GgGkyEOQ==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -1066,18 +1462,18 @@ } }, "node_modules/@lezer/highlight": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", - "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.5.tgz", + "integrity": "sha512-O1GMVKgtf5YspFaRzpmqkVgMtIk2HG9uMAbH6wdAIdNh3TVzCsch/+cRi+6l2//UGDKe6NB9BW3cvuR5ezMQ0w==", "license": "MIT", "dependencies": { - "@lezer/common": "^1.0.0" + "@lezer/common": "^1.3.0" } }, "node_modules/@lezer/html": { - "version": "1.3.10", - "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz", - "integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==", + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -1086,9 +1482,9 @@ } }, "node_modules/@lezer/javascript": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz", - "integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==", + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.6.tgz", + "integrity": "sha512-sEvOqpl4vJQca0NugSQK02vQPbc5KrvoojnYGoqqFgFO0MtcYhwGiHKVEB+LuXYJnfyXo4QPA8ztbrEa4Fw5NA==", "license": "MIT", "dependencies": { "@lezer/common": "^1.2.0", @@ -1097,18 +1493,18 @@ } }, "node_modules/@lezer/lr": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", - "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.11.tgz", + "integrity": "sha512-UwgZcl1R66H2hDzBrA7kWyYmX6s8p5TQ+e/t7CuvgCtBEPxbEPaHYHu+pJBvTvDotEJpuQkJjbXuHiw5Cixxgw==", "license": "MIT", "dependencies": { "@lezer/common": "^1.0.0" } }, "node_modules/@marijn/find-cluster-break": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", - "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.4.tgz", + "integrity": "sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==", "license": "MIT" }, "node_modules/@nodelib/fs.scandir": { @@ -1160,19 +1556,39 @@ "node": ">=14" } }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", "dependencies": { "@types/ms": "*" } }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "license": "MIT" }, "node_modules/@types/estree-jsx": { @@ -1185,18 +1601,18 @@ } }, "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", "license": "MIT", "dependencies": { "@types/unist": "*" } }, "node_modules/@types/hotwired__turbo": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@types/hotwired__turbo/-/hotwired__turbo-8.0.4.tgz", - "integrity": "sha512-kgEKOms0taywPxPQvXUh90+G7M+phBHTiUw95Dosw0FU/we86iFerMfgBGcf3eQcAGs5pTNYnLMhiIlfRw/AfA==", + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/@types/hotwired__turbo/-/hotwired__turbo-8.0.16.tgz", + "integrity": "sha512-FOYSD+lq8reFMIiZAp8O5ooKf5Lv9hdrEi0OPKfAgBp4Y7KpNfCbPzyalWbnyR05FepztJatz4PHK7nbsGi+vQ==", "dev": true, "license": "MIT" }, @@ -1223,32 +1639,32 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", - "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", + "version": "26.6.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.5.tgz", + "integrity": "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~8.9.0" } }, "node_modules/@types/react": { - "version": "19.1.10", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.10.tgz", - "integrity": "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", "license": "MIT", "dependencies": { - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "19.1.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.7.tgz", - "integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", "dev": true, "license": "MIT", "peerDependencies": { - "@types/react": "^19.0.0" + "@types/react": "^19.3.0" } }, "node_modules/@types/unist": { @@ -1258,21 +1674,20 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.39.1.tgz", - "integrity": "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.72.0.tgz", + "integrity": "sha512-aysKgysPX9XOv/dk95UkvUDe++Q3n3dJPG9CEWT3RUccxfkPJ+Qn6nhKPDhZXU6Ofo9kgwGWwKyQCnZIXTgngg==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.39.1", - "@typescript-eslint/type-utils": "8.39.1", - "@typescript-eslint/utils": "8.39.1", - "@typescript-eslint/visitor-keys": "8.39.1", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.72.0", + "@typescript-eslint/type-utils": "8.72.0", + "@typescript-eslint/utils": "8.72.0", + "@typescript-eslint/visitor-keys": "8.72.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1282,15 +1697,15 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.39.1", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "@typescript-eslint/parser": "^8.72.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.14.tgz", + "integrity": "sha512-wCBiZ77u1tuWde6G/nfOtLaCTYcFDU5yCzARzK10y+rLhzYoL7a7yEemcopcc2Mzow7gZOIjnV2ym1NYmdiJOQ==", "dev": true, "license": "MIT", "engines": { @@ -1298,17 +1713,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.39.1.tgz", - "integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.72.0.tgz", + "integrity": "sha512-0O+x3ynciD6zuXb1CKSu165haQV6ey53DKhHHUnujP7U+XlltJG+/keT0Xhd1HXCze+IHf7Tvt39ovyepX/LNw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.39.1", - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/typescript-estree": "8.39.1", - "@typescript-eslint/visitor-keys": "8.39.1", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.72.0", + "@typescript-eslint/types": "8.72.0", + "@typescript-eslint/typescript-estree": "8.72.0", + "@typescript-eslint/visitor-keys": "8.72.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1318,20 +1733,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.1.tgz", - "integrity": "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.72.0.tgz", + "integrity": "sha512-bzHngBbmIgzj6TsTjVUCntGx6FFLgE7mPjpO0PzHycOaZHXqS43XnrkFIHRS16LLr/xO6FHSXG3GJFo/F4bSBQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.39.1", - "@typescript-eslint/types": "^8.39.1", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.72.0", + "@typescript-eslint/types": "^8.72.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1341,18 +1756,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.1.tgz", - "integrity": "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.72.0.tgz", + "integrity": "sha512-JewPyJOYcXBSAS7PIgufscHOwZqhTGyBRiQJhhuDln66tMchxvvt7nJ90JKKIoOelmdHA3kAQbm9cEUHc4JSEA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/visitor-keys": "8.39.1" + "@typescript-eslint/types": "8.72.0", + "@typescript-eslint/visitor-keys": "8.72.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1363,9 +1778,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.1.tgz", - "integrity": "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.0.tgz", + "integrity": "sha512-0vbxCf7xLVPSjcOdm6eQbgViH8EDJ8xnl1laLT9yoqSz2YmcSfSg5Vdl0JUvZf6rcuJ0ZivJIuMD4edTg8obaA==", "dev": true, "license": "MIT", "engines": { @@ -1376,21 +1791,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.39.1.tgz", - "integrity": "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.72.0.tgz", + "integrity": "sha512-gEXxmmbSTSOVTC37IYYvremOM31v+UN2W772bJxmVYPhAOPSUxtacyb7wkm1EhkYKU6KzFJsdU44DCV4to0LWw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/typescript-estree": "8.39.1", - "@typescript-eslint/utils": "8.39.1", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/types": "8.72.0", + "@typescript-eslint/typescript-estree": "8.72.0", + "@typescript-eslint/utils": "8.72.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1400,14 +1815,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.1.tgz", - "integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.72.0.tgz", + "integrity": "sha512-Adfoy59kxNEjT5E5fnyrp7GpHYuWs8ONbdohu4ez6IU46qDq8LltG90tLmkucZB2uhYL8mqBjtXqGd8yhfjXqQ==", "dev": true, "license": "MIT", "engines": { @@ -1419,22 +1834,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.1.tgz", - "integrity": "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.0.tgz", + "integrity": "sha512-gwLfFgWcw15tN2KXDCJvxEOQjPn+P3j/n3eSqkZ5sX4VpW9Cosodr+tWwBZRvJT3TcKx6HnPRGZMCNAiP1Xhug==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.39.1", - "@typescript-eslint/tsconfig-utils": "8.39.1", - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/visitor-keys": "8.39.1", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/project-service": "8.72.0", + "@typescript-eslint/tsconfig-utils": "8.72.0", + "@typescript-eslint/types": "8.72.0", + "@typescript-eslint/visitor-keys": "8.72.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1444,46 +1858,33 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", - "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=10" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.1.tgz", - "integrity": "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.72.0.tgz", + "integrity": "sha512-0up3oArJZBYnzOaEXKo8DmI43QvMZyPoFEt9LWzRJcyl/RcJjfKKPebknN1VbTgXBf3M/KggUh5uUMdURLnseg==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.39.1", - "@typescript-eslint/types": "8.39.1", - "@typescript-eslint/typescript-estree": "8.39.1" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.72.0", + "@typescript-eslint/types": "8.72.0", + "@typescript-eslint/typescript-estree": "8.72.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1493,19 +1894,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.1.tgz", - "integrity": "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.0.tgz", + "integrity": "sha512-E2mroviasaWecDzTGiGMtuEguuLVOeRggnrIn93CJuxYJzQBHbyymlT08aVQydbBal5538o+JkBKXh2sRuCyIA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.39.1", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.72.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1516,9 +1917,9 @@ } }, "node_modules/@uiw/codemirror-extensions-basic-setup": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.24.2.tgz", - "integrity": "sha512-wW/gjLRvVUeYyhdh2TApn25cvdcR+Rhg6R/j3eTOvXQzU1HNzNYCVH4YKVIfgtfdM/Xs+N8fkk+rbr1YvBppCg==", + "version": "4.25.12", + "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.25.12.tgz", + "integrity": "sha512-wR5lz7jbVcBGyiiLEObAmDikcc39KNqrrd4bdUhav85WZu48ttgnSf1YV+grHuMsj5l/WCw6u+r9ZuSfURJgiA==", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.0.0", @@ -1543,16 +1944,16 @@ } }, "node_modules/@uiw/react-codemirror": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.24.2.tgz", - "integrity": "sha512-kp7DhTq4RR+M2zJBQBrHn1dIkBrtOskcwJX4vVsKGByReOvfMrhqRkGTxYMRDTX6x75EG2mvBJPDKYcUQcHWBw==", + "version": "4.25.12", + "resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.25.12.tgz", + "integrity": "sha512-RE0Gj/QesD7/JWPnJREG8Kq8brGv2NAH17iUECcBrLkqXzzEAgsj/4Qqvz/OlK8XWKmzlsenLRkObT9H+gYQ0w==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.6", "@codemirror/commands": "^6.1.0", "@codemirror/state": "^6.1.1", "@codemirror/theme-one-dark": "^6.0.0", - "@uiw/codemirror-extensions-basic-setup": "4.24.2", + "@uiw/codemirror-extensions-basic-setup": "4.25.12", "codemirror": "^6.0.0" }, "funding": { @@ -1569,15 +1970,15 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", "license": "ISC" }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", "dev": true, "license": "MIT", "bin": { @@ -1598,9 +1999,9 @@ } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -1615,9 +2016,9 @@ } }, "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", "dev": true, "license": "MIT", "engines": { @@ -1628,13 +2029,16 @@ } }, "node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -1654,19 +2058,6 @@ "node": ">= 8" } }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1674,16 +2065,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -1705,11 +2086,27 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.32.tgz", + "integrity": "sha512-UvuxFU9P3hM29LrQdwu0cD6w6/+SFr+UlB9s/knI7+O2rK9xFxKUwSDWxGdbbqQ9/V7BJ5s0NI5nQAE4uJp3gQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -1725,14 +2122,16 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", - "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/braces": { @@ -1748,25 +2147,62 @@ "node": ">=8" } }, - "node_modules/cacheable": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-1.10.3.tgz", - "integrity": "sha512-M6p10iJ/VT0wT7TLIGUnm958oVrU2cUK8pQAVU21Zu7h8rbk/PeRtRWrvHJBql97Bhzk3g1N6+2VKC+Rjxna9Q==", + "node_modules/browserslist": { + "version": "4.29.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.4.tgz", + "integrity": "sha512-2TcbbiwXEq+N0kD36tykpCfyyQigUXmuwNQbnPp4Xk4PX9JOqVIKjJlIDjpH7uY6tmRGoAd+apeT1p60T8wsdA==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "hookified": "^1.10.0", - "keyv": "^5.4.0" + "baseline-browser-mapping": "^2.11.29", + "caniuse-lite": "^1.0.30001816", + "electron-to-chromium": "^1.5.459", + "node-releases": "^2.0.59", + "update-browserslist-db": "^1.3.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/cacheable/node_modules/keyv": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.4.0.tgz", - "integrity": "sha512-TMckyVjEoacG5IteUpUrOBsFORtheqziVyyY2dLUwg1jwTb8u48LX4TgmtogkNl9Y9unaEJ1luj10fGyjMGFOQ==", + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", "dev": true, "license": "MIT", "dependencies": { - "@keyv/serialize": "^1.1.0" + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" } }, "node_modules/camelcase": { @@ -1782,16 +2218,77 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "license": "MIT", + "node_modules/caniuse-lite": { + "version": "1.0.30001819", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001819.tgz", + "integrity": "sha512-LFUqIIAwpzsIuevnKnsR9qnStBcgzPShTX61lBwN9uFkkf8o8zLiebEjbW8AYtZiF+1YKDWfdt/pWDtMuG8Xwg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -1873,39 +2370,6 @@ "node": ">=8" } }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -1934,24 +2398,6 @@ "node": ">=8" } }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2013,17 +2459,17 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", "dev": true, "license": "MIT", "dependencies": { @@ -2048,9 +2494,9 @@ } }, "node_modules/crelt": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", - "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", "license": "MIT" }, "node_modules/cross-spawn": { @@ -2098,24 +2544,24 @@ } }, "node_modules/css-functions-list": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.2.3.tgz", - "integrity": "sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.3.3.tgz", + "integrity": "sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==", "dev": true, "license": "MIT", "engines": { - "node": ">=12 || >=16" + "node": ">=12" } }, "node_modules/css-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", - "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "license": "MIT", "dependencies": { - "mdn-data": "2.12.2", - "source-map-js": "^1.0.1" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" @@ -2135,15 +2581,15 @@ } }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2158,9 +2604,9 @@ } }, "node_modules/decode-named-character-reference": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", - "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "license": "MIT", "dependencies": { "character-entities": "^2.0.0" @@ -2199,29 +2645,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dir-glob/node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2229,6 +2652,20 @@ "dev": true, "license": "MIT" }, + "node_modules/electron-to-chromium": { + "version": "1.5.464", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.464.tgz", + "integrity": "sha512-nRa65aFge40viPAKxMKULIY8tcXKwKboJWNn4RoKwyWXJ6SP7BnrDGKUTzc64818jIciH0PK7LSF8QzZes19GA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -2240,9 +2677,9 @@ } }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2250,9 +2687,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2263,32 +2700,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, "node_modules/escalade": { @@ -2315,45 +2752,43 @@ } }, "node_modules/eslint": { - "version": "9.33.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz", - "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", + "version": "10.13.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.13.0.tgz", + "integrity": "sha512-UohDjD4zSzEwhf58M3O7vc/ycc8O1I7dNlhtD0WxE8Px8EHNJKtUwzBqrzeGhisRuv2ag4sAYswszCJRgKXYUg==", "dev": true, "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.33.0", - "@eslint/plugin-kit": "^0.3.5", + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", + "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", + "file-entry-cache": "11.1.5 || >11.1.6 <12", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -2361,7 +2796,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" @@ -2376,103 +2811,79 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.15.0", + "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2583,9 +2994,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", - "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", "dev": true, "funding": [ { @@ -2610,9 +3021,9 @@ } }, "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", "dev": true, "license": "ISC", "dependencies": { @@ -2620,16 +3031,13 @@ } }, "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" + "flat-cache": "^6.1.23" } }, "node_modules/fill-range": { @@ -2663,23 +3071,21 @@ } }, "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", "dev": true, "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -2701,9 +3107,9 @@ } }, "node_modules/fs-extra": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", - "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", "dev": true, "license": "MIT", "dependencies": { @@ -2730,6 +3136,16 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -2740,25 +3156,32 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", - "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -2777,56 +3200,17 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", "dev": true, "license": "MIT", + "dependencies": { + "global-prefix": "^3.0.0" + }, "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", - "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" + "node": ">=6" } }, "node_modules/global-prefix": { @@ -2858,9 +3242,9 @@ } }, "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "version": "17.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.13.0.tgz", + "integrity": "sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==", "dev": true, "license": "MIT", "engines": { @@ -2870,6 +3254,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globby": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.4.tgz", + "integrity": "sha512-c8B/VNLmxRcmqqenRA9t+9IyOjf9+V6lTxPaUJLqOCONdQkWZ0ETYgX0qbtJqPsgCNusT9MZ5Jeidw8Eb9tn2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.5", + "is-path-inside": "^4.0.0", + "micromatch": "^4.0.8", + "slash": "^5.1.0", + "unicorn-magic": "^0.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.14.tgz", + "integrity": "sha512-wCBiZ77u1tuWde6G/nfOtLaCTYcFDU5yCzARzK10y+rLhzYoL7a7yEemcopcc2Mzow7gZOIjnV2ym1NYmdiJOQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/globjoin": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", @@ -2884,13 +3300,6 @@ "dev": true, "license": "ISC" }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, "node_modules/growly": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", @@ -2899,13 +3308,29 @@ "license": "MIT" }, "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-5.0.1.tgz", + "integrity": "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" } }, "node_modules/hast-util-to-jsx-runtime": { @@ -2948,21 +3373,38 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, "node_modules/hookified": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.11.0.tgz", - "integrity": "sha512-aDdIN3GyU5I6wextPplYdfmWCo+aLmjjVbntmX6HLD5RCi/xKsivYEBhnRD+d9224zFf008ZpLMPlWF0ZodYZw==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", "dev": true, "license": "MIT" }, "node_modules/html-tags": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", - "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-5.1.0.tgz", + "integrity": "sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=20.10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3025,14 +3467,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=4" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, "node_modules/imurmurhash": { @@ -3053,9 +3496,9 @@ "license": "ISC" }, "node_modules/inline-style-parser": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", - "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", "license": "MIT" }, "node_modules/is-alphabetical": { @@ -3113,16 +3556,16 @@ } }, "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "dev": true, "license": "MIT", "bin": { "is-docker": "cli.js" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3138,6 +3581,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3161,6 +3614,25 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3171,10 +3643,11 @@ "node": ">=0.12.0" } }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -3183,14 +3656,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-stream": { @@ -3214,16 +3689,19 @@ "license": "MIT" }, "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "dev": true, "license": "MIT", "dependencies": { - "is-docker": "^2.0.0" + "is-inside-container": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/isexe": { @@ -3234,66 +3712,46 @@ "license": "ISC" }, "node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": "20 || >=22" - }, "funding": { "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/jarmuz": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/jarmuz/-/jarmuz-0.10.0.tgz", - "integrity": "sha512-vWaE3NMjDTn5NF6guzaUlHvhwfMp1l81WaUvYiCN0ye0bzVjFrObJKGer5f6cYWWUq9bE/9AVM/M3+oI3d9VTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^4.0.3", - "cross-spawn": "^7.0.6", - "minimatch": "^10.0.3", - "nanoid": "^5.1.5", - "string-argv": "^0.3.2" - } - }, - "node_modules/jarmuz/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/jarmuz/node_modules/brace-expansion": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", - "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/jarmuz/-/jarmuz-0.11.0.tgz", + "integrity": "sha512-qBGM3pTj/gxyK8OsOx8gbSR3USOTqthg69t/qxJxCFYp8Bsa7y0KAuZy4WnagYBi4pcx+FT+APBm4oSpv29oPA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" + "chokidar": "4.0.3", + "cross-spawn": "7.0.6", + "minimatch": "10.2.5", + "nanoid": "5.1.5", + "string-argv": "0.3.2" }, "engines": { - "node": "20 || >=22" + "node": ">=24.0.0" } }, "node_modules/jarmuz/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -3332,12 +3790,18 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, - "license": "MIT" + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -3360,10 +3824,23 @@ "dev": true, "license": "MIT" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3374,13 +3851,13 @@ } }, "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "dev": true, "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" + "@keyv/serialize": "^1.1.1" } }, "node_modules/kind-of": { @@ -3393,13 +3870,6 @@ "node": ">=0.10.0" } }, - "node_modules/known-css-properties": { - "version": "0.37.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", - "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", - "dev": true, - "license": "MIT" - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3444,13 +3914,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -3469,19 +3932,19 @@ } }, "node_modules/lru-cache": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz", - "integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", - "engines": { - "node": "20 || >=22" + "dependencies": { + "yallist": "^3.0.2" } }, "node_modules/mathml-tag-names": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", - "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-4.0.0.tgz", + "integrity": "sha512-aa6AU2Pcx0VP/XWnh8IGL0SYSgQHDT6Ucror2j2mXeFAlN3ahaNs8EZtG1YiticMkSLj3Gt6VPFfZogt7G5iFQ==", "dev": true, "license": "MIT", "funding": { @@ -3490,9 +3953,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.1.0.tgz", + "integrity": "sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -3609,9 +4072,9 @@ } }, "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.2.0.tgz", + "integrity": "sha512-2Jn/ADrHFFyyniBMi0bncTbJqU78K3/TEYvzvwrWWzD9MYPUa/Gm4r3nOeKFQg/PDM5BsbDYdc8ackW4NTY6NA==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -3619,8 +4082,10 @@ "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", + "micromark-util-character": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" }, @@ -3643,20 +4108,20 @@ } }, "node_modules/mdn-data": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", - "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "dev": true, "license": "CC0-1.0" }, "node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-14.1.0.tgz", + "integrity": "sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3673,9 +4138,9 @@ } }, "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.3.tgz", + "integrity": "sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==", "funding": [ { "type": "GitHub Sponsors", @@ -3698,6 +4163,7 @@ "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-edit-map": "^1.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", @@ -3708,9 +4174,9 @@ } }, "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.4.tgz", + "integrity": "sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w==", "funding": [ { "type": "GitHub Sponsors", @@ -3727,18 +4193,19 @@ "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", + "micromark-factory-space": "^2.1.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", + "micromark-util-edit-map": "^1.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "micromark-util-types": "^2.0.3" } }, "node_modules/micromark-factory-destination": { @@ -3785,9 +4252,9 @@ } }, "node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.1.0.tgz", + "integrity": "sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==", "funding": [ { "type": "GitHub Sponsors", @@ -3969,6 +4436,25 @@ "micromark-util-symbol": "^2.0.0" } }, + "node_modules/micromark-util-edit-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-edit-map/-/micromark-util-edit-map-1.0.0.tgz", + "integrity": "sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, "node_modules/micromark-util-encode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", @@ -4099,9 +4585,9 @@ "license": "MIT" }, "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.3.tgz", + "integrity": "sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==", "funding": [ { "type": "GitHub Sponsors", @@ -4128,48 +4614,32 @@ "node": ">=8.6" } }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.8" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "license": "MIT" - }, "node_modules/mkdirp": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", @@ -4193,9 +4663,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "5.1.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz", - "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-6.0.2.tgz", + "integrity": "sha512-hIvVOb9Qr1p/9CNNLOe0OlWvgx/ziTsVwKN/GDSsuArN2K0wDiafP3s0SWmsm7o2BdnyS8Z9Z64NFBUbn6ziBg==", "funding": [ { "type": "github", @@ -4207,7 +4677,7 @@ "nanoid": "bin/nanoid.js" }, "engines": { - "node": "^18 || >=20" + "node": "^22 || ^24 || >=26" } }, "node_modules/natural-compare": { @@ -4218,18 +4688,27 @@ "license": "MIT" }, "node_modules/node-notifier": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-10.0.1.tgz", - "integrity": "sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-11.0.0.tgz", + "integrity": "sha512-C4ePuKXvH+Xd4NpZ4iQyTCEAQDMdVrhH1ntJ4v3pLaIKNuODtM88XyvEcwVceqK54mBzMYlGjkiUHlvtqCdgYg==", "dev": true, "license": "MIT", "dependencies": { "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.5", - "shellwords": "^0.1.1", - "uuid": "^8.3.2", - "which": "^2.0.2" + "is-wsl": "^3.1.1" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.60", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.60.tgz", + "integrity": "sha512-OmO8+GM1JUZqXET720VrJCwkjUCXmCMQm0Z3UDDRbfNLHS4NEAvY60j5jiFv0tkKw2PNNlqOLhahVmwJIOLDIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" } }, "node_modules/normalize-path": { @@ -4312,16 +4791,6 @@ "node": ">=6" } }, - "node_modules/parent-module/node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/parse-entities": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", @@ -4387,9 +4856,9 @@ } }, "node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -4397,20 +4866,20 @@ "minipass": "^7.1.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" } }, "node_modules/picocolors": { @@ -4420,10 +4889,23 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/postcss": { - "version": "8.5.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", - "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "version": "8.5.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz", + "integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==", "dev": true, "funding": [ { @@ -4441,9 +4923,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.18", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -4512,17 +4994,10 @@ "postcss": "^8.1.0" } }, - "node_modules/postcss-resolve-nested-selector": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz", - "integrity": "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==", - "dev": true, - "license": "MIT" - }, "node_modules/postcss-safe-parser": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", - "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.1.0.tgz", + "integrity": "sha512-1WzZxRLaAFwEh6Do+zyGpjWV3nGJNxxhuh7Ubu/q1ICImMgZJnLwhoaEpRbG4pJppp2Y1ncL9ffA2f+LhrefQg==", "dev": true, "funding": [ { @@ -4547,9 +5022,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.6.tgz", - "integrity": "sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==", + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.7.tgz", + "integrity": "sha512-wWBro/7hJ40ye6TPiXkUQoXKCw23xSsjriUvqd7Uls9Sso3oLiON1flc18cCdnjgCRXw4fBVqUJJeOdDTh3P3g==", "dev": true, "license": "MIT", "dependencies": { @@ -4568,9 +5043,9 @@ "license": "MIT" }, "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.19", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", - "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { @@ -4597,9 +5072,9 @@ } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz", + "integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==", "dev": true, "license": "MIT", "bin": { @@ -4613,9 +5088,9 @@ } }, "node_modules/prettier-plugin-organize-imports": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.2.0.tgz", - "integrity": "sha512-Zdy27UhlmyvATZi67BTnLcKTo8fm6Oik59Sz6H64PgZJVs6NJpPD1mT240mmJn62c98/QaL+r3kx9Q3gRpDajg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.3.0.tgz", + "integrity": "sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -4630,9 +5105,9 @@ } }, "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", "license": "MIT", "funding": { "type": "github", @@ -4649,6 +5124,26 @@ "node": ">=6" } }, + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4671,24 +5166,24 @@ "license": "MIT" }, "node_modules/react": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", - "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.1.tgz", + "integrity": "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", - "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.1.tgz", + "integrity": "sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==", "license": "MIT", "dependencies": { - "scheduler": "^0.26.0" + "scheduler": "^0.28.0" }, "peerDependencies": { - "react": "^19.1.1" + "react": "^19.3.1" } }, "node_modules/react-markdown": { @@ -4795,13 +5290,13 @@ } }, "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4" } }, "node_modules/reusify": { @@ -4849,22 +5344,19 @@ } }, "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", "license": "MIT" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, "node_modules/shebang-command": { @@ -4890,13 +5382,6 @@ "node": ">=8" } }, - "node_modules/shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true, - "license": "MIT" - }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -4910,10 +5395,41 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -4940,6 +5456,23 @@ "node": ">=0.6.19" } }, + "node_modules/string-width": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", @@ -4966,23 +5499,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -5011,13 +5527,13 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -5050,47 +5566,34 @@ "node": ">=8" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/style-mod": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", - "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.4.tgz", + "integrity": "sha512-XXWIQt633/EpAFx8aZDOTjBzrCaGmhvEQlQo6MVPfa2OzO2cWo+4hV9h+6UkHYlXGfy+ODXKUdP7Pthmcu5ATw==", "license": "MIT" }, "node_modules/style-to-js": { - "version": "1.1.17", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz", - "integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", "license": "MIT", "dependencies": { - "style-to-object": "1.0.9" + "style-to-object": "1.0.14" } }, "node_modules/style-to-object": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", - "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==", + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", "license": "MIT", "dependencies": { - "inline-style-parser": "0.2.4" + "inline-style-parser": "0.2.7" } }, "node_modules/stylelint": { - "version": "16.23.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.23.1.tgz", - "integrity": "sha512-dNvDTsKV1U2YtiUDfe9d2gp902veFeo3ecCWdGlmLm2WFrAV0+L5LoOj/qHSBABQwMsZPJwfC4bf39mQm1S5zw==", + "version": "17.16.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-17.16.0.tgz", + "integrity": "sha512-ejlWnbJ13x1GU32+zZXa6sYQJsKKkwSdv2B4rAdO3OWdEmvhSJKcXi5pOphQYSzyaj1kXpuGJr7pcMBNfDIuPw==", "dev": true, "funding": [ { @@ -5104,56 +5607,53 @@ ], "license": "MIT", "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3", - "@csstools/selector-specificity": "^5.0.0", - "@dual-bundle/import-meta-resolve": "^4.1.0", - "balanced-match": "^2.0.0", - "colord": "^2.9.3", - "cosmiconfig": "^9.0.0", - "css-functions-list": "^3.2.3", - "css-tree": "^3.1.0", - "debug": "^4.4.1", + "@csstools/css-calc": "^3.3.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-syntax-patches-for-csstree": "^1.1.9", + "@csstools/css-tokenizer": "^4.0.0", + "@csstools/media-query-list-parser": "^5.0.0", + "@csstools/selector-resolve-nested": "^4.0.1", + "@csstools/selector-specificity": "^6.0.0", + "colord": "^2.10.0", + "cosmiconfig": "^9.0.2", + "css-functions-list": "^3.3.3", + "css-tree": "^3.2.1", + "debug": "^4.4.3", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^10.1.3", + "file-entry-cache": "^11.1.5", "global-modules": "^2.0.0", - "globby": "^11.1.0", + "globby": "^16.2.4", "globjoin": "^0.1.4", - "html-tags": "^3.3.1", - "ignore": "^7.0.5", - "imurmurhash": "^0.1.4", - "is-plain-object": "^5.0.0", - "known-css-properties": "^0.37.0", - "mathml-tag-names": "^2.1.3", - "meow": "^13.2.0", + "html-tags": "^5.1.0", + "ignore": "^7.0.6", + "import-meta-resolve": "^4.2.0", + "mathml-tag-names": "^4.0.0", + "meow": "^14.1.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", - "postcss": "^8.5.6", - "postcss-resolve-nested-selector": "^0.1.6", + "postcss": "^8.5.26", "postcss-safe-parser": "^7.0.1", - "postcss-selector-parser": "^7.1.0", + "postcss-selector-parser": "^7.1.5", "postcss-value-parser": "^4.2.0", - "resolve-from": "^5.0.0", - "string-width": "^4.2.3", - "supports-hyperlinks": "^3.2.0", + "string-width": "^8.2.2", + "supports-hyperlinks": "^4.5.0", "svg-tags": "^1.0.0", "table": "^6.9.0", - "write-file-atomic": "^5.0.1" + "write-file-atomic": "^7.0.1" }, "bin": { "stylelint": "bin/stylelint.mjs" }, "engines": { - "node": ">=18.12.0" + "node": ">=20.19.0" } }, "node_modules/stylelint-config-recommended": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-17.0.0.tgz", - "integrity": "sha512-WaMSdEiPfZTSFVoYmJbxorJfA610O0tlYuU2aEwY33UQhSPgFbClrVJYWvy3jGJx+XW37O+LyNLiZOEXhKhJmA==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-18.0.0.tgz", + "integrity": "sha512-mxgT2XY6YZ3HWWe3Di8umG6aBmWmHTblTgu/f10rqFXnyWxjKWwNdjSWkgkwCtxIKnqjSJzvFmPT5yabVIRxZg==", "dev": true, "funding": [ { @@ -5167,120 +5667,110 @@ ], "license": "MIT", "engines": { - "node": ">=18.12.0" + "node": ">=20.19.0" }, "peerDependencies": { - "stylelint": "^16.23.0" + "stylelint": "^17.0.0" } }, - "node_modules/stylelint/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/stylelint/node_modules/ignore": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.14.tgz", + "integrity": "sha512-wCBiZ77u1tuWde6G/nfOtLaCTYcFDU5yCzARzK10y+rLhzYoL7a7yEemcopcc2Mzow7gZOIjnV2ym1NYmdiJOQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" - } - }, - "node_modules/stylelint/node_modules/balanced-match": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", - "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", - "dev": true, - "license": "MIT" - }, - "node_modules/stylelint/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/stylelint/node_modules/file-entry-cache": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.3.tgz", - "integrity": "sha512-D+w75Ub8T55yor7fPgN06rkCAUbAYw2vpxJmmjv/GDAcvCnv9g7IvHhIZoxzRZThrXPFI2maeY24pPbtyYU7Lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^6.1.12" + "node": ">= 4" } }, - "node_modules/stylelint/node_modules/flat-cache": { - "version": "6.1.12", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.12.tgz", - "integrity": "sha512-U+HqqpZPPXP5d24bWuRzjGqVqUcw64k4nZAbruniDwdRg0H10tvN7H6ku1tjhA4rg5B9GS3siEvwO2qjJJ6f8Q==", + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", "dev": true, "license": "MIT", - "dependencies": { - "cacheable": "^1.10.3", - "flatted": "^3.3.3", - "hookified": "^1.10.0" + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/stylelint/node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "node_modules/supports-hyperlinks": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-4.6.0.tgz", + "integrity": "sha512-FL/q/YePS4tFFTNiBOR+zPk2ZrKheqNtmTJ/9OUZ4e1wKHJofDbGQSmSsxS0XNnabSkqexryCaXGvEKz1vf9UQ==", "dev": true, "license": "MIT", "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" + "has-flag": "^5.0.1", + "supports-color": "^10.2.2" }, "engines": { - "node": ">=10" + "node": ">=20" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, - "node_modules/stylelint/node_modules/globby/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, "engines": { - "node": ">= 4" + "node": ">=10.0.0" } }, - "node_modules/stylelint/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "node_modules/table/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 4" + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/stylelint/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/table/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/stylelint/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/stylelint/node_modules/string-width": { + "node_modules/table/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", @@ -5295,7 +5785,7 @@ "node": ">=8" } }, - "node_modules/stylelint/node_modules/strip-ansi": { + "node_modules/table/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -5308,226 +5798,81 @@ "node": ">=8" } }, - "node_modules/stylelint/node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "node_modules/temp-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", + "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, + "license": "MIT", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=14.16" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks": { + "node_modules/tempy": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", - "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.2.0.tgz", + "integrity": "sha512-d79HhZya5Djd7am0q+W4RTsSU+D/aJzM+4Y4AGJGuGlgM2L6sx5ZvOYTmZjqPhrDrV6xJTtRSm1JCLj6V6LHLQ==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=14.18" - }, - "funding": { - "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" - } - }, - "node_modules/svg-tags": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", - "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", - "dev": true - }, - "node_modules/table": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", - "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" + "is-stream": "^3.0.0", + "temp-dir": "^3.0.0", + "type-fest": "^2.12.2", + "unique-string": "^3.0.0" }, "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "node": ">=14.16" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/table/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/table/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">=8" + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/table/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/table/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/table/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/table/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "node": ">=12.0.0" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/table/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" + "peerDependencies": { + "picomatch": "^3 || ^4" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/temp-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", - "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/tempy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", - "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", - "dependencies": { - "is-stream": "^3.0.0", - "temp-dir": "^3.0.0", - "type-fest": "^2.12.2", - "unique-string": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=12.20" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/to-regex-range": { @@ -5564,9 +5909,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -5584,505 +5929,21 @@ }, "node_modules/tsx": { "version": "4.23.15", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz", - "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.28.0" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", - "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", - "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", - "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", - "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", - "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", - "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", - "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", - "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", - "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", - "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", - "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-loong64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", - "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", - "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", - "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", - "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-s390x": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", - "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", - "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", - "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", - "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", - "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", - "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", - "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/sunos-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", - "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-arm64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", - "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-ia32": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", - "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-x64": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", - "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/esbuild": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", - "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz", + "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==", "dev": true, - "hasInstallScript": true, "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, "bin": { - "esbuild": "bin/esbuild" + "tsx": "dist/cli.mjs" }, "engines": { - "node": ">=18" + "node": ">=18.0.0" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.2", - "@esbuild/android-arm": "0.28.2", - "@esbuild/android-arm64": "0.28.2", - "@esbuild/android-x64": "0.28.2", - "@esbuild/darwin-arm64": "0.28.2", - "@esbuild/darwin-x64": "0.28.2", - "@esbuild/freebsd-arm64": "0.28.2", - "@esbuild/freebsd-x64": "0.28.2", - "@esbuild/linux-arm": "0.28.2", - "@esbuild/linux-arm64": "0.28.2", - "@esbuild/linux-ia32": "0.28.2", - "@esbuild/linux-loong64": "0.28.2", - "@esbuild/linux-mips64el": "0.28.2", - "@esbuild/linux-ppc64": "0.28.2", - "@esbuild/linux-riscv64": "0.28.2", - "@esbuild/linux-s390x": "0.28.2", - "@esbuild/linux-x64": "0.28.2", - "@esbuild/netbsd-arm64": "0.28.2", - "@esbuild/netbsd-x64": "0.28.2", - "@esbuild/openbsd-arm64": "0.28.2", - "@esbuild/openbsd-x64": "0.28.2", - "@esbuild/openharmony-arm64": "0.28.2", - "@esbuild/sunos-x64": "0.28.2", - "@esbuild/win32-arm64": "0.28.2", - "@esbuild/win32-ia32": "0.28.2", - "@esbuild/win32-x64": "0.28.2" + "fsevents": "~2.3.3" } }, "node_modules/type-check": { @@ -6098,6 +5959,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typed-css-modules": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/typed-css-modules/-/typed-css-modules-0.9.1.tgz", @@ -6126,21 +6000,12 @@ "node": ">=18.0.0" } }, - "node_modules/typed-css-modules/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/typed-css-modules/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "license": "MIT" }, "node_modules/typed-css-modules/node_modules/brace-expansion": { "version": "2.1.7", @@ -6152,23 +6017,6 @@ "balanced-match": "^1.0.0" } }, - "node_modules/typed-css-modules/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/typed-css-modules/node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -6198,6 +6046,7 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -6228,22 +6077,6 @@ "node": ">= 6" } }, - "node_modules/typed-css-modules/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/typed-css-modules/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -6284,19 +6117,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/typed-css-modules/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/typed-css-modules/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -6311,9 +6131,9 @@ } }, "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -6325,16 +6145,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.39.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.39.1.tgz", - "integrity": "sha512-GDUv6/NDYngUlNvwaHM1RamYftxf782IyEDbdj3SeaIHHv8fNQVRC++fITT7kUJV/5rIA/tkoRSSskt6osEfqg==", + "version": "8.72.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.72.0.tgz", + "integrity": "sha512-r26Fkc8As+mZvqp7SLsMmNFyYUup/6CjK7/eHBHmhOw6GH6n6YFxHVFrG0jN1c5CMpDzelLPxvgeCOzx7BjN+A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.39.1", - "@typescript-eslint/parser": "8.39.1", - "@typescript-eslint/typescript-estree": "8.39.1", - "@typescript-eslint/utils": "8.39.1" + "@typescript-eslint/eslint-plugin": "8.72.0", + "@typescript-eslint/parser": "8.72.0", + "@typescript-eslint/typescript-estree": "8.72.0", + "@typescript-eslint/utils": "8.72.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6344,17 +6164,30 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", "dev": true, "license": "MIT" }, + "node_modules/unicorn-magic": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.1.tgz", + "integrity": "sha512-lzlXPoVB0Uy/FvTaUgX39RbixYiSKoc3JSoSf01+0c2B6FR3qdYIPN7Qjo/AV7n6sqLCk0509cT3LRj1oSZ1+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -6391,9 +6224,9 @@ } }, "node_modules/unist-util-is": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -6430,9 +6263,9 @@ } }, "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -6445,9 +6278,9 @@ } }, "node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -6468,6 +6301,37 @@ "node": ">= 10.0.0" } }, + "node_modules/update-browserslist-db": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -6479,9 +6343,9 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", - "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz", + "integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -6494,20 +6358,6 @@ "dev": true, "license": "MIT" }, - "node_modules/uuid": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", - "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -6569,12 +6419,11 @@ } }, "node_modules/wouter": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/wouter/-/wouter-3.7.1.tgz", - "integrity": "sha512-od5LGmndSUzntZkE2R5CHhoiJ7YMuTIbiXsa0Anytc2RATekgv4sfWRAxLEULBrp7ADzinWQw8g470lkT8+fOw==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/wouter/-/wouter-3.13.0.tgz", + "integrity": "sha512-p1RoCuVyxwb3X5jLjDdkwDzlXj+/UGZqZb+806uaJOVEHvNfGkjET0KiQ/CGiDKC90rLsALMgMj4onNVKxgNdQ==", "license": "Unlicense", "dependencies": { - "mitt": "^3.0.1", "regexparam": "^3.0.0", "use-sync-external-store": "^1.0.0" }, @@ -6583,18 +6432,18 @@ } }, "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/wrap-ansi?sponsor=1" @@ -6629,40 +6478,45 @@ "node": ">=8" } }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "node_modules/wrap-ansi/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", @@ -6677,7 +6531,7 @@ "node": ">=8" } }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "node_modules/wrap-ansi/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -6690,29 +6544,17 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "node_modules/write-file-atomic": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.1.tgz", + "integrity": "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "signal-exit": "^4.0.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/y18n": { @@ -6725,10 +6567,17 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", "dependencies": { @@ -6764,23 +6613,6 @@ "node": ">=8" } }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -6823,14 +6655,27 @@ } }, "node_modules/zod": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.0.17.tgz", - "integrity": "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ==", + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", @@ -6843,15 +6688,15 @@ }, "paddler_client_javascript": { "name": "@intentee/paddler-client", - "version": "4.0.1", + "version": "5.0.0", "license": "Apache-2.0", "dependencies": { - "nanoid": "5.1.16" + "nanoid": "6.0.2" }, "devDependencies": { - "@types/node": "22.19.17", + "@types/node": "26.6.5", "tsx": "4.23.15", - "typescript": "5.9.2" + "typescript": "6.0.3" }, "engines": { "node": ">=22" diff --git a/package.json b/package.json index 69ebce29d..2688392ef 100644 --- a/package.json +++ b/package.json @@ -1,48 +1,52 @@ { "devDependencies": { - "@intentee/rust-coverage-check": "0.2.0", - "@types/hotwired__turbo": "8.0.4", - "@types/react": "19.1.10", - "@types/react-dom": "19.1.7", - "esbuild": "0.25.8", - "eslint": "9.33.0", - "eslint-plugin-react-hooks": "5.2.0", - "fs-extra": "11.3.1", - "glob": "11.1.0", - "jarmuz": "0.10.0", + "@eslint/js": "10.0.1", + "@intentee/rust-coverage-check": "0.3.1", + "@types/hotwired__turbo": "8.0.16", + "@types/react": "19.3.0", + "@types/react-dom": "19.3.0", + "esbuild": "0.28.2", + "eslint": "10.13.0", + "eslint-plugin-react-hooks": "7.1.1", + "fs-extra": "11.4.1", + "glob": "13.0.6", + "globals": "17.13.0", + "jarmuz": "0.11.0", "lodash": "4.18.1", - "node-notifier": "10.0.1", - "prettier": "3.6.2", - "prettier-plugin-organize-imports": "4.2.0", - "stylelint": "16.23.1", - "stylelint-config-recommended": "17.0.0", - "tempy": "3.1.0", + "node-notifier": "11.0.0", + "prettier": "3.9.10", + "prettier-plugin-organize-imports": "4.3.0", + "stylelint": "17.16.0", + "stylelint-config-recommended": "18.0.0", + "tempy": "3.2.0", "tslib": "2.8.1", "typed-css-modules": "0.9.1", - "typescript": "5.9.2", - "typescript-eslint": "8.39.1" + "typescript": "6.0.3", + "typescript-eslint": "8.72.0" }, "dependencies": { - "@codemirror/lang-jinja": "6.0.0", - "@uiw/react-codemirror": "4.24.2", + "@codemirror/lang-jinja": "6.0.1", + "@uiw/react-codemirror": "4.25.12", "clsx": "2.1.1", - "nanoid": "5.1.16", - "path-to-regexp": "8.4.2", - "react": "19.1.1", - "react-dom": "19.1.1", + "nanoid": "6.0.2", + "react": "19.3.1", + "react-dom": "19.3.1", "react-markdown": "10.1.0", "rxjs": "7.8.2", - "wouter": "3.7.1", - "zod": "4.0.17" + "wouter": "3.13.0", + "zod": "4.6.7" }, "license": "Apache-2.0", "name": "paddler", "private": true, "overrides": { - "node-notifier": { - "uuid": "14.0.0" + "jarmuz": { + "nanoid": "6.0.2" } }, "type": "module", - "workspaces": ["paddler_client_javascript"] + "workspaces": ["paddler_client_javascript"], + "allowScripts": { + "esbuild@0.28.2": true + } } diff --git a/paddler_agent/Cargo.toml b/paddler_agent/Cargo.toml index d9c84ab8f..2c686b75a 100644 --- a/paddler_agent/Cargo.toml +++ b/paddler_agent/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "paddler_agent" +autotests = false authors.workspace = true description.workspace = true edition.workspace = true @@ -8,57 +9,51 @@ license.workspace = true repository.workspace = true version.workspace = true +[[test]] +name = "integration" +path = "tests/main.rs" + [dependencies] anyhow = { workspace = true } -async-stream = { workspace = true } async-trait = { workspace = true } -bytes = { workspace = true } -dashmap = { workspace = true } -encoding_rs = { workspace = true } -futures = { workspace = true } futures-util = { workspace = true } -hf-hub = { workspace = true } -llama-cpp-bindings = { workspace = true } -llama-cpp-bindings-sys = { workspace = true } -llama-cpp-bindings-types = { workspace = true } log = { workspace = true } -minijinja = { workspace = true } -minijinja-contrib = { workspace = true } -nanoid = { workspace = true } -paddler_cache_dir = { workspace = true } -paddler_download_manager = { workspace = true } -paddler_image_decoder = { workspace = true } -paddler_inference_parameters = { workspace = true } -paddler_agent_status = { workspace = true } -paddler_messaging = { workspace = true } -paddler_model_source = { workspace = true } -paddler_request_registry = { workspace = true } -paddler_tool_call_validator = { workspace = true } -parking_lot = { workspace = true } -rand = { workspace = true } -reqwest = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -shellexpand = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } -tokio-stream = { workspace = true } tokio-tungstenite = { workspace = true } tokio-util = { workspace = true } trzcina = { workspace = true } -url = { workspace = true } +paddler_agent_decision = { workspace = true } +paddler_agent_embeddings = { workspace = true } +paddler_agent_runtime = { workspace = true } +paddler_agent_status = { workspace = true } +paddler_agent_text_generation = { workspace = true } +paddler_frame_writer = { workspace = true } +paddler_inference_parameters = { workspace = true } +paddler_messaging = { workspace = true } +paddler_model_source = { workspace = true } +paddler_outbox = { workspace = true } +paddler_request_registry = { workspace = true } +paddler_spawn_resuming_panic = { workspace = true } [features] default = [] -cuda = ["llama-cpp-bindings/cuda"] -metal = ["llama-cpp-bindings/metal"] -vulkan = ["llama-cpp-bindings/vulkan"] +cuda = ["paddler_agent_decision/cuda", "paddler_agent_embeddings/cuda", "paddler_agent_runtime/cuda", "paddler_agent_text_generation/cuda"] +metal = ["paddler_agent_decision/metal", "paddler_agent_embeddings/metal", "paddler_agent_runtime/metal", "paddler_agent_text_generation/metal"] +vulkan = ["paddler_agent_decision/vulkan", "paddler_agent_embeddings/vulkan", "paddler_agent_runtime/vulkan", "paddler_agent_text_generation/vulkan"] [dev-dependencies] -indoc = { workspace = true } -paddler_local_http_fixture = { workspace = true } +http = { workspace = true } +nix = { workspace = true } tempfile = { workspace = true } -tokio-test = { workspace = true } +tokio = { workspace = true, features = ["test-util"] } + +paddler_blocking_thread_fixture = { workspace = true } +paddler_cache_dir = { workspace = true } +paddler_download_manager = { workspace = true } +paddler_local_http_fixture = { workspace = true } [lints] workspace = true diff --git a/paddler_agent/src/agent_applicable_model.rs b/paddler_agent/src/agent_applicable_model.rs deleted file mode 100644 index 80f0fbd5e..000000000 --- a/paddler_agent/src/agent_applicable_model.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::path::PathBuf; - -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub enum AgentApplicableModel { - #[default] - NotConfigured, - Resolved { - model_path: PathBuf, - multimodal_projection_path: Option, - }, -} diff --git a/paddler_agent/src/agent_applicable_state.rs b/paddler_agent/src/agent_applicable_state.rs index 43a41548d..7b5c36b82 100644 --- a/paddler_agent/src/agent_applicable_state.rs +++ b/paddler_agent/src/agent_applicable_state.rs @@ -1,11 +1,63 @@ -use paddler_inference_parameters::inference_parameters::InferenceParameters; +use paddler_agent_text_generation::text_generation_settings::TextGenerationSettings; use paddler_messaging::chat_template::ChatTemplate; +use paddler_messaging::chat_template_source::ChatTemplateSource; -use crate::agent_applicable_model::AgentApplicableModel; +use crate::inference_capabilities::InferenceCapabilities; +use crate::model_serving_settings::ModelServingSettings; #[derive(Clone, Debug, Default, PartialEq)] -pub struct AgentApplicableState { - pub chat_template_override: Option, - pub inference_parameters: InferenceParameters, - pub model: AgentApplicableModel, +pub enum AgentApplicableState { + #[default] + NotConfigured, + WithModel(ModelServingSettings), + WithoutModel { + chat_template_source: ChatTemplateSource, + }, +} + +impl AgentApplicableState { + #[must_use] + pub fn chat_template_override(&self) -> Option { + match self { + Self::WithModel(ModelServingSettings { + capabilities: + InferenceCapabilities { + text_generation: + TextGenerationSettings { + chat_template_source: ChatTemplateSource::Override(chat_template), + .. + }, + .. + }, + .. + }) + | Self::WithoutModel { + chat_template_source: ChatTemplateSource::Override(chat_template), + } => Some(chat_template.clone()), + Self::NotConfigured | Self::WithModel(_) | Self::WithoutModel { .. } => None, + } + } +} + +#[cfg(test)] +mod tests { + use paddler_messaging::chat_template::ChatTemplate; + use paddler_messaging::chat_template_source::ChatTemplateSource; + + use super::AgentApplicableState; + + #[test] + fn a_state_without_a_model_overrides_its_chat_template() { + let chat_template = ChatTemplate { + content: "{{ messages }}".to_owned(), + }; + + assert_eq!( + AgentApplicableState::WithoutModel { + chat_template_source: ChatTemplateSource::Override(chat_template.clone()), + } + .chat_template_override(), + Some(chat_template) + ); + } } diff --git a/paddler_agent/src/agent_applicable_state_holder.rs b/paddler_agent/src/agent_applicable_state_holder.rs index 8f8f96f6b..a8a192a5f 100644 --- a/paddler_agent/src/agent_applicable_state_holder.rs +++ b/paddler_agent/src/agent_applicable_state_holder.rs @@ -15,7 +15,15 @@ impl AgentApplicableStateHolder { pub fn set_agent_applicable_state(&self, agent_applicable_state: AgentApplicableState) { self.agent_applicable_state_tx - .send_replace(agent_applicable_state); + .send_if_modified(|current_agent_applicable_state| { + if *current_agent_applicable_state == agent_applicable_state { + return false; + } + + *current_agent_applicable_state = agent_applicable_state; + + true + }); } #[must_use] @@ -27,35 +35,3 @@ impl AgentApplicableStateHolder { agent_applicable_state_rx } } - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - - use paddler_inference_parameters::inference_parameters::InferenceParameters; - - use super::AgentApplicableStateHolder; - use crate::agent_applicable_model::AgentApplicableModel; - use crate::agent_applicable_state::AgentApplicableState; - - #[test] - fn a_new_subscriber_sees_the_state_applied_before_it_subscribed() { - let holder = AgentApplicableStateHolder::default(); - - let applied_state = AgentApplicableState { - chat_template_override: None, - inference_parameters: InferenceParameters::default(), - model: AgentApplicableModel::Resolved { - model_path: PathBuf::from("model.gguf"), - multimodal_projection_path: None, - }, - }; - - holder.set_agent_applicable_state(applied_state.clone()); - - let agent_applicable_state_rx = holder.subscribe(); - - assert!(agent_applicable_state_rx.has_changed().unwrap()); - assert_eq!(*agent_applicable_state_rx.borrow(), applied_state); - } -} diff --git a/paddler_agent/src/agent_desired_state_conversion.rs b/paddler_agent/src/agent_desired_state_conversion.rs index 15e5fe8e4..6b97532af 100644 --- a/paddler_agent/src/agent_desired_state_conversion.rs +++ b/paddler_agent/src/agent_desired_state_conversion.rs @@ -1,5 +1,6 @@ use crate::agent_applicable_state::AgentApplicableState; +#[derive(Debug, PartialEq)] pub enum AgentDesiredStateConversion { Cancelled, Converted(AgentApplicableState), diff --git a/paddler_agent/src/agent_desired_state_converter.rs b/paddler_agent/src/agent_desired_state_converter.rs index 9908bb5a4..fa80c712d 100644 --- a/paddler_agent/src/agent_desired_state_converter.rs +++ b/paddler_agent/src/agent_desired_state_converter.rs @@ -5,19 +5,27 @@ use std::sync::Arc; use tokio_util::sync::CancellationToken; use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; +use paddler_agent_text_generation::multimodal_projection::MultimodalProjection; +use paddler_agent_text_generation::text_generation_settings::TextGenerationSettings; +use paddler_messaging::agent_decisions_settings::AgentDecisionsSettings; use paddler_messaging::agent_desired_model::AgentDesiredModel; use paddler_messaging::agent_desired_state::AgentDesiredState; -use paddler_messaging::agent_issue::AgentIssue; -use paddler_messaging::agent_issue_params::model_path::ModelPath; +use paddler_messaging::balancer_text_generation_settings::BalancerTextGenerationSettings; +use paddler_messaging::multimodal_settings::MultimodalSettings; +use paddler_messaging::served_decision_settings::ServedDecisionSettings; use paddler_model_source::desired_model_resolution::DesiredModelResolution; +use paddler_model_source::desired_model_resolver::DesiredModelResolver; use paddler_model_source::model_source_error::ModelSourceError; -use paddler_model_source::resolve_desired_model::resolve_desired_model; -use crate::agent_applicable_model::AgentApplicableModel; use crate::agent_applicable_state::AgentApplicableState; use crate::agent_desired_state_conversion::AgentDesiredStateConversion; +use crate::applicable_decisions::ApplicableDecisions; +use crate::desired_model_file::DesiredModelFile; +use crate::inference_capabilities::InferenceCapabilities; +use crate::model_serving_settings::ModelServingSettings; pub struct AgentDesiredStateConverter { + pub desired_model_resolver: DesiredModelResolver, pub cancellation_token: CancellationToken, pub slot_aggregated_status: Arc, } @@ -26,60 +34,127 @@ impl AgentDesiredStateConverter { pub async fn convert( &self, AgentDesiredState { - chat_template_override, - inference_parameters, + decisions, model, - multimodal_projection, + model_runtime_parameters, + pooling_type, + text_generation: + BalancerTextGenerationSettings { + chat_template_source, + multimodal: + MultimodalSettings { + image_resize_to_fit, + projection, + }, + sampling_parameters, + }, }: &AgentDesiredState, ) -> Result { let ControlFlow::Continue(model_path) = self - .resolve_model_file(model, AgentIssue::ModelFileDoesNotExist) + .resolve_model_file(model, DesiredModelFile::Model) .await? else { return Ok(AgentDesiredStateConversion::Cancelled); }; + let Some(model_path) = model_path else { + return Ok(AgentDesiredStateConversion::Converted( + AgentApplicableState::WithoutModel { + chat_template_source: chat_template_source.clone(), + }, + )); + }; let ControlFlow::Continue(multimodal_projection_path) = self - .resolve_model_file( - multimodal_projection, - AgentIssue::MultimodalProjectionCannotBeLoaded, - ) + .resolve_model_file(projection, DesiredModelFile::MultimodalProjection) .await? else { return Ok(AgentDesiredStateConversion::Cancelled); }; + let ControlFlow::Continue(applicable_decisions) = self.resolve_decisions(decisions).await? + else { + return Ok(AgentDesiredStateConversion::Cancelled); + }; Ok(AgentDesiredStateConversion::Converted( - AgentApplicableState { - chat_template_override: chat_template_override.clone(), - inference_parameters: inference_parameters.clone(), - model: model_path.map_or(AgentApplicableModel::NotConfigured, |model_path| { - AgentApplicableModel::Resolved { - model_path, - multimodal_projection_path, - } - }), - }, + AgentApplicableState::WithModel(ModelServingSettings { + capabilities: InferenceCapabilities { + decisions: applicable_decisions, + embeddings: pooling_type.clone(), + text_generation: TextGenerationSettings { + chat_template_source: chat_template_source.clone(), + image_resize_to_fit: *image_resize_to_fit, + multimodal_projection: multimodal_projection_path.map_or( + MultimodalProjection::NotConfigured, + MultimodalProjection::File, + ), + sampling_parameters: sampling_parameters.clone(), + }, + }, + model_path, + model_runtime_parameters: model_runtime_parameters.clone(), + }), )) } + async fn resolve_decisions( + &self, + decisions: &AgentDecisionsSettings, + ) -> Result, ModelSourceError> { + match decisions { + AgentDecisionsSettings::NotServed => { + Ok(ControlFlow::Continue(ApplicableDecisions::Disabled)) + } + AgentDecisionsSettings::Served(ServedDecisionSettings { pointer_head_uri }) => Ok( + match self + .resolve_model_uri(pointer_head_uri, DesiredModelFile::PointerHead) + .await? + { + ControlFlow::Break(()) => ControlFlow::Break(()), + ControlFlow::Continue(pointer_head_path) => { + ControlFlow::Continue(ApplicableDecisions::Enabled { pointer_head_path }) + } + }, + ), + } + } + async fn resolve_model_file( &self, desired_model: &AgentDesiredModel, - local_file_missing_issue: fn(ModelPath) -> AgentIssue, + desired_model_file: DesiredModelFile, ) -> Result>, ModelSourceError> { - match resolve_desired_model( - &self.cancellation_token, - desired_model, - self.slot_aggregated_status.clone(), - ) - .await? + match desired_model { + AgentDesiredModel::None => Ok(ControlFlow::Continue(None)), + AgentDesiredModel::Uri(uri) => Ok( + match self.resolve_model_uri(uri, desired_model_file).await? { + ControlFlow::Break(()) => ControlFlow::Break(()), + ControlFlow::Continue(path) => ControlFlow::Continue(Some(path)), + }, + ), + } + } + + async fn resolve_model_uri( + &self, + uri: &str, + desired_model_file: DesiredModelFile, + ) -> Result, ModelSourceError> { + match self + .desired_model_resolver + .resolve_uri( + &self.cancellation_token, + uri, + self.slot_aggregated_status.clone(), + ) + .await? { DesiredModelResolution::Cancelled => Ok(ControlFlow::Break(())), - DesiredModelResolution::NotConfigured => Ok(ControlFlow::Continue(None)), - DesiredModelResolution::Resolved(path) => Ok(ControlFlow::Continue(Some(path))), + DesiredModelResolution::Resolved(path) => { + desired_model_file.report_found(&path, &self.slot_aggregated_status); + + Ok(ControlFlow::Continue(path)) + } DesiredModelResolution::LocalFileMissing(path) => { - self.slot_aggregated_status - .register_issue(local_file_missing_issue(ModelPath::from(path.as_path()))); + desired_model_file.report_missing(&path, &self.slot_aggregated_status); Err(ModelSourceError::LocalFileMissing { path }) } @@ -89,31 +164,55 @@ impl AgentDesiredStateConverter { #[cfg(test)] mod tests { - use std::mem::discriminant; + use std::fs::write; + use std::path::Path; use std::path::PathBuf; use std::sync::Arc; + use tempfile::NamedTempFile; use tempfile::TempDir; use tempfile::tempdir; use tokio_util::sync::CancellationToken; use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; - use paddler_inference_parameters::inference_parameters::InferenceParameters; + use paddler_agent_text_generation::multimodal_projection::MultimodalProjection; + use paddler_agent_text_generation::text_generation_settings::TextGenerationSettings; + use paddler_inference_parameters::model_runtime_parameters::ModelRuntimeParameters; + use paddler_inference_parameters::pooling_type::PoolingType; + use paddler_inference_parameters::sampling_parameters::SamplingParameters; + use paddler_messaging::agent_decisions_settings::AgentDecisionsSettings; use paddler_messaging::agent_desired_model::AgentDesiredModel; use paddler_messaging::agent_desired_state::AgentDesiredState; use paddler_messaging::agent_issue::AgentIssue; use paddler_messaging::agent_issue_params::model_path::ModelPath; - use paddler_messaging::huggingface_model_reference::HuggingFaceModelReference; + use paddler_messaging::balancer_text_generation_settings::BalancerTextGenerationSettings; + use paddler_messaging::chat_template::ChatTemplate; + use paddler_messaging::chat_template_source::ChatTemplateSource; + use paddler_messaging::multimodal_settings::MultimodalSettings; + use paddler_messaging::served_decision_settings::ServedDecisionSettings; + use paddler_model_source::agent_local_model_path::AgentLocalModelPath; + use paddler_model_source::desired_model_resolver::DesiredModelResolver; + use paddler_model_source::huggingface_default_endpoint::HUGGINGFACE_DEFAULT_ENDPOINT; + use paddler_model_source::huggingface_model_reference::HuggingFaceModelReference; + use paddler_model_source::model_source::ModelSource; use paddler_model_source::model_source_error::ModelSourceError; + use crate::agent_applicable_state::AgentApplicableState; use crate::agent_desired_state_conversion::AgentDesiredStateConversion; use crate::agent_desired_state_converter::AgentDesiredStateConverter; + use crate::applicable_decisions::ApplicableDecisions; + use crate::inference_capabilities::InferenceCapabilities; + use crate::model_serving_settings::ModelServingSettings; struct MissingLocalModel { _dir_guard: TempDir, path: PathBuf, } + fn process_env_desired_model_resolver() -> DesiredModelResolver { + DesiredModelResolver::from_process_env(HUGGINGFACE_DEFAULT_ENDPOINT.to_owned()).unwrap() + } + fn fresh_status() -> Arc { Arc::new(SlotAggregatedStatus::new(1)) } @@ -133,11 +232,98 @@ mod tests { multimodal_projection: AgentDesiredModel, ) -> AgentDesiredState { AgentDesiredState { - chat_template_override: None, - inference_parameters: InferenceParameters::default(), + decisions: AgentDecisionsSettings::NotServed, model, - multimodal_projection, + model_runtime_parameters: ModelRuntimeParameters::default(), + pooling_type: PoolingType::Last, + text_generation: BalancerTextGenerationSettings { + multimodal: MultimodalSettings { + projection: multimodal_projection, + ..MultimodalSettings::default() + }, + ..BalancerTextGenerationSettings::default() + }, + } + } + + fn desired_state_serving_decisions( + model: AgentDesiredModel, + pointer_head: &ModelSource, + ) -> AgentDesiredState { + AgentDesiredState { + decisions: AgentDecisionsSettings::Served(ServedDecisionSettings { + pointer_head_uri: pointer_head.uri(), + }), + ..desired_state(model, AgentDesiredModel::None) + } + } + + fn served_settings( + model_file: &NamedTempFile, + decisions: ApplicableDecisions, + multimodal_projection: MultimodalProjection, + ) -> AgentApplicableState { + AgentApplicableState::WithModel(ModelServingSettings { + capabilities: InferenceCapabilities { + decisions, + embeddings: PoolingType::Last, + text_generation: TextGenerationSettings { + chat_template_source: ChatTemplateSource::default(), + image_resize_to_fit: MultimodalSettings::default().image_resize_to_fit, + multimodal_projection, + sampling_parameters: SamplingParameters::default(), + }, + }, + model_path: model_file.path().to_path_buf(), + model_runtime_parameters: ModelRuntimeParameters::default(), + }) + } + + fn local_source_at(path: &Path) -> ModelSource { + ModelSource::LocalToAgent(AgentLocalModelPath::from_absolute_path(path).unwrap()) + } + + fn local_model_at(path: &Path) -> AgentDesiredModel { + local_source_at(path).into_agent_desired_model() + } + + fn cancelled_token() -> CancellationToken { + let cancellation_token = CancellationToken::new(); + + cancellation_token.cancel(); + + cancellation_token + } + + fn uncached_hugging_face_source() -> ModelSource { + ModelSource::HuggingFace(HuggingFaceModelReference { + filename: "uncached.gguf".to_owned(), + repository_name: "repo".to_owned(), + repository_owner: "owner".to_owned(), + revision: "main".to_owned(), + }) + } + + fn uncached_hugging_face_model() -> AgentDesiredModel { + uncached_hugging_face_source().into_agent_desired_model() + } + + async fn conversion( + cancellation_token: CancellationToken, + desired: &AgentDesiredState, + ) -> AgentDesiredStateConversion { + AgentDesiredStateConverter { + desired_model_resolver: process_env_desired_model_resolver(), + cancellation_token, + slot_aggregated_status: fresh_status(), } + .convert(desired) + .await + .expect("the desired state must convert") + } + + async fn converted(desired: &AgentDesiredState) -> AgentDesiredStateConversion { + conversion(CancellationToken::new(), desired).await } #[tokio::test] @@ -147,11 +333,9 @@ mod tests { _dir_guard, path: missing_path, } = nonexistent_path_in_temp_dir("model"); - let desired = desired_state( - AgentDesiredModel::LocalToAgent(missing_path.display().to_string()), - AgentDesiredModel::None, - ); + let desired = desired_state(local_model_at(&missing_path), AgentDesiredModel::None); let converter = AgentDesiredStateConverter { + desired_model_resolver: process_env_desired_model_resolver(), cancellation_token: CancellationToken::new(), slot_aggregated_status: status.clone(), }; @@ -176,19 +360,77 @@ mod tests { ); } + #[tokio::test] + async fn a_model_file_that_appears_on_a_retry_clears_its_missing_file_issue() { + let status = fresh_status(); + let MissingLocalModel { + _dir_guard, + path: appearing_path, + } = nonexistent_path_in_temp_dir("model"); + let desired = desired_state(local_model_at(&appearing_path), AgentDesiredModel::None); + let converter = AgentDesiredStateConverter { + desired_model_resolver: process_env_desired_model_resolver(), + cancellation_token: CancellationToken::new(), + slot_aggregated_status: status.clone(), + }; + + assert!(converter.convert(&desired).await.is_err()); + + write(&appearing_path, b"").unwrap(); + converter.convert(&desired).await.unwrap(); + + assert!( + !status.has_issue(&AgentIssue::ModelFileDoesNotExist(ModelPath { + model_path: appearing_path.display().to_string(), + })) + ); + } + + #[tokio::test] + async fn a_pointer_head_that_appears_on_a_retry_clears_its_missing_file_issue() { + let status = fresh_status(); + let model_file = NamedTempFile::new().unwrap(); + let MissingLocalModel { + _dir_guard, + path: appearing_path, + } = nonexistent_path_in_temp_dir("pointer-head"); + let desired = desired_state_serving_decisions( + local_model_at(model_file.path()), + &local_source_at(&appearing_path), + ); + let converter = AgentDesiredStateConverter { + desired_model_resolver: process_env_desired_model_resolver(), + cancellation_token: CancellationToken::new(), + slot_aggregated_status: status.clone(), + }; + + assert!(converter.convert(&desired).await.is_err()); + + write(&appearing_path, b"").unwrap(); + converter.convert(&desired).await.unwrap(); + + assert!( + !status.has_issue(&AgentIssue::PointerHeadCannotBeLoaded(ModelPath { + model_path: appearing_path.display().to_string(), + })) + ); + } + #[tokio::test] async fn local_missing_multimodal_projection_registers_multimodal_projection_cannot_be_loaded_and_errs() { let status = fresh_status(); + let model_file = NamedTempFile::new().unwrap(); let MissingLocalModel { _dir_guard, path: missing_path, } = nonexistent_path_in_temp_dir("projection"); let desired = desired_state( - AgentDesiredModel::None, - AgentDesiredModel::LocalToAgent(missing_path.display().to_string()), + local_model_at(model_file.path()), + local_model_at(&missing_path), ); let converter = AgentDesiredStateConverter { + desired_model_resolver: process_env_desired_model_resolver(), cancellation_token: CancellationToken::new(), slot_aggregated_status: status.clone(), }; @@ -213,29 +455,155 @@ mod tests { ); } + #[tokio::test] + async fn local_missing_pointer_head_registers_pointer_head_cannot_be_loaded_and_errs() { + let status = fresh_status(); + let model_file = NamedTempFile::new().unwrap(); + let MissingLocalModel { + _dir_guard, + path: missing_path, + } = nonexistent_path_in_temp_dir("pointer-head"); + let converter = AgentDesiredStateConverter { + desired_model_resolver: process_env_desired_model_resolver(), + cancellation_token: CancellationToken::new(), + slot_aggregated_status: status.clone(), + }; + + let outcome = converter + .convert(&desired_state_serving_decisions( + local_model_at(model_file.path()), + &local_source_at(&missing_path), + )) + .await; + + assert_eq!( + outcome.err().as_ref().map(ToString::to_string), + Some( + ModelSourceError::LocalFileMissing { + path: missing_path.clone(), + } + .to_string() + ) + ); + assert!( + status.has_issue(&AgentIssue::PointerHeadCannotBeLoaded(ModelPath { + model_path: missing_path.display().to_string(), + })), + "PointerHeadCannotBeLoaded must be registered for a missing local pointer head" + ); + } + #[tokio::test] async fn cancelling_while_the_multimodal_projection_resolves_cancels_the_conversion() { - let cancellation_token = CancellationToken::new(); - let desired = desired_state( - AgentDesiredModel::None, - AgentDesiredModel::HuggingFace(HuggingFaceModelReference { - filename: "projection.gguf".to_owned(), - repo_id: "owner/repo".to_owned(), - revision: "main".to_owned(), - }), + let model_file = NamedTempFile::new().unwrap(); + + assert_eq!( + conversion( + cancelled_token(), + &desired_state( + local_model_at(model_file.path()), + uncached_hugging_face_model() + ), + ) + .await, + AgentDesiredStateConversion::Cancelled ); + } - cancellation_token.cancel(); + #[tokio::test] + async fn cancelling_while_the_pointer_head_resolves_cancels_the_conversion() { + let model_file = NamedTempFile::new().unwrap(); - let outcome = AgentDesiredStateConverter { - cancellation_token, - slot_aggregated_status: fresh_status(), - } - .convert(&desired) - .await; + assert_eq!( + conversion( + cancelled_token(), + &desired_state_serving_decisions( + local_model_at(model_file.path()), + &uncached_hugging_face_source(), + ), + ) + .await, + AgentDesiredStateConversion::Cancelled + ); + } + + #[tokio::test] + async fn a_state_with_a_model_serves_text_generation_and_embeddings_with_its_resolved_files() { + let model_file = NamedTempFile::new().unwrap(); + let projection_file = NamedTempFile::new().unwrap(); + + assert_eq!( + converted(&desired_state( + local_model_at(model_file.path()), + local_model_at(projection_file.path()), + )) + .await, + AgentDesiredStateConversion::Converted(served_settings( + &model_file, + ApplicableDecisions::Disabled, + MultimodalProjection::File(projection_file.path().to_path_buf()), + )) + ); + } - assert!(outcome.is_ok_and(|conversion| { - discriminant(&conversion) == discriminant(&AgentDesiredStateConversion::Cancelled) - })); + #[tokio::test] + async fn a_state_without_a_model_keeps_its_chat_template() { + let chat_template = ChatTemplate { + content: "{{ messages }}".to_owned(), + }; + + assert_eq!( + converted(&AgentDesiredState { + text_generation: BalancerTextGenerationSettings { + chat_template_source: ChatTemplateSource::Override(chat_template.clone()), + ..BalancerTextGenerationSettings::default() + }, + ..desired_state(AgentDesiredModel::None, AgentDesiredModel::None) + }) + .await, + AgentDesiredStateConversion::Converted(AgentApplicableState::WithoutModel { + chat_template_source: ChatTemplateSource::Override(chat_template), + }) + ); + } + + #[tokio::test] + async fn a_state_without_a_model_does_not_resolve_its_multimodal_projection() { + let MissingLocalModel { + _dir_guard, + path: missing_path, + } = nonexistent_path_in_temp_dir("projection"); + + assert_eq!( + converted(&desired_state( + AgentDesiredModel::None, + local_model_at(&missing_path) + )) + .await, + AgentDesiredStateConversion::Converted(AgentApplicableState::WithoutModel { + chat_template_source: ChatTemplateSource::EmbeddedInModel, + }) + ); + } + + #[tokio::test] + async fn a_state_serving_decisions_carries_its_pointer_head() { + let model_file = NamedTempFile::new().unwrap(); + let pointer_head_file = NamedTempFile::new().unwrap(); + + assert_eq!( + converted(&desired_state_serving_decisions( + local_model_at(model_file.path()), + &local_source_at(pointer_head_file.path()), + )) + .await, + AgentDesiredStateConversion::Converted(served_settings( + &model_file, + ApplicableDecisions::Enabled { + pointer_head_path: pointer_head_file.path().to_path_buf(), + }, + MultimodalProjection::NotConfigured, + )) + ); } } diff --git a/paddler_agent/src/agent_error.rs b/paddler_agent/src/agent_error.rs new file mode 100644 index 000000000..bdc49f2db --- /dev/null +++ b/paddler_agent/src/agent_error.rs @@ -0,0 +1,19 @@ +use paddler_agent_decision::decision_error::DecisionError; +use paddler_agent_embeddings::embedding_error::EmbeddingError; +use paddler_agent_runtime::agent_runtime_error::AgentRuntimeError; +use paddler_agent_text_generation::text_generation_error::TextGenerationError; + +#[derive(Debug, thiserror::Error)] +pub enum AgentError { + #[error("the decision pipeline failed: {0}")] + DecisionPipeline(#[from] DecisionError), + + #[error("the embedding pipeline failed: {0}")] + EmbeddingPipeline(#[from] EmbeddingError), + + #[error("the model failed to load: {0}")] + ModelLoad(#[from] AgentRuntimeError), + + #[error("the text generation pipeline failed: {0}")] + TextGenerationPipeline(#[from] TextGenerationError), +} diff --git a/paddler_agent/src/agent_response_message.rs b/paddler_agent/src/agent_response_message.rs new file mode 100644 index 000000000..2879632a4 --- /dev/null +++ b/paddler_agent/src/agent_response_message.rs @@ -0,0 +1,15 @@ +use paddler_messaging::jsonrpc::response_envelope::ResponseEnvelope; +use paddler_messaging::management_socket::agent::response::Response as JsonRpcResponse; +use paddler_messaging::management_socket::balancer::message::Message as ManagementJsonRpcMessage; + +#[must_use] +pub const fn agent_response_message( + request_id: String, + response: JsonRpcResponse, +) -> ManagementJsonRpcMessage { + ManagementJsonRpcMessage::Response(ResponseEnvelope { + generated_by: None, + request_id, + response, + }) +} diff --git a/paddler_agent/src/applicable_decisions.rs b/paddler_agent/src/applicable_decisions.rs new file mode 100644 index 000000000..8a4b73d0f --- /dev/null +++ b/paddler_agent/src/applicable_decisions.rs @@ -0,0 +1,19 @@ +use std::path::PathBuf; + +use paddler_messaging::agent_decisions_serving::AgentDecisionsServing; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum ApplicableDecisions { + Disabled, + Enabled { pointer_head_path: PathBuf }, +} + +impl ApplicableDecisions { + #[must_use] + pub const fn serving(&self) -> AgentDecisionsServing { + match self { + Self::Disabled => AgentDecisionsServing::NotServed, + Self::Enabled { .. } => AgentDecisionsServing::Served, + } + } +} diff --git a/paddler_agent/src/balancer_message_context.rs b/paddler_agent/src/balancer_message_context.rs index aa5aefa9b..09983ae46 100644 --- a/paddler_agent/src/balancer_message_context.rs +++ b/paddler_agent/src/balancer_message_context.rs @@ -1,22 +1,207 @@ use std::sync::Arc; +use futures_util::StreamExt as _; +use log::debug; +use log::error; +use log::warn; +use serde_json::from_str; use tokio::sync::mpsc; +use tokio::sync::watch; +use tokio_tungstenite::tungstenite::protocol::Message; +use paddler_agent_runtime::agent_request::AgentRequest; +use paddler_agent_runtime::model_metadata_holder::ModelMetadataHolder; use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; +use paddler_agent_status::slot_guard::SlotGuard; use paddler_messaging::agent_desired_state::AgentDesiredState; +use paddler_messaging::decision_result::DecisionResult; +use paddler_messaging::embedding_result::EmbeddingResult; +use paddler_messaging::generated_token_result::GeneratedTokenResult; +use paddler_messaging::jsonrpc::request_envelope::RequestEnvelope; +use paddler_messaging::management_socket::agent::message::Message as JsonRpcMessage; +use paddler_messaging::management_socket::agent::notification::Notification as JsonRpcNotification; +use paddler_messaging::management_socket::agent::notification_params::version_params::VersionParams; +use paddler_messaging::management_socket::agent::request::Request as JsonRpcRequest; +use paddler_messaging::management_socket::agent::response::Response as JsonRpcResponse; +use paddler_messaging::reserved_request::ReservedRequest; +use paddler_outbox::outbox::Outbox; +use paddler_request_registry::request_delivery::RequestDelivery; use paddler_request_registry::request_registry::RequestRegistry; +use paddler_request_registry::request_registry_guard::RequestRegistryGuard; use crate::agent_applicable_state_holder::AgentApplicableStateHolder; -use crate::continuous_batch_preparation_request::ContinuousBatchPreparationRequest; -use crate::model_metadata_holder::ModelMetadataHolder; +use crate::agent_response_message::agent_response_message; +use crate::management_connection_end::ManagementConnectionEnd; +use crate::management_connection_step::ManagementConnectionStep; +use crate::pipeline_request::PipelineRequest; +use crate::pipeline_response_stream::PipelineResponseStream; -#[derive(Clone)] pub struct BalancerMessageContext { - pub agent_applicable_state_holder: Arc, - pub agent_desired_state_tx: mpsc::UnboundedSender, - pub continuous_batch_preparation_request_tx: - mpsc::UnboundedSender, + pub agent_desired_state_tx: watch::Sender, pub model_metadata_holder: Arc, + pub pipeline_requests: Arc>, pub request_stoppers: Arc>>, + pub served_applicable_state_holder: Arc, pub slot_aggregated_status: Arc, } + +impl BalancerMessageContext { + #[must_use] + pub fn handle_message(&self, message: Message) -> ManagementConnectionStep { + match message { + Message::Text(text) => match from_str::(&text) { + Ok(deserialized_message) => self.handle_deserialized_message(deserialized_message), + Err(deserialization_error) => { + error!( + "Closing the management connection after an undeserializable message {text}: {deserialization_error}" + ); + + ManagementConnectionStep::End( + ManagementConnectionEnd::BalancerMessageUndeserializable, + ) + } + }, + Message::Binary(_) | Message::Frame(_) => { + error!("Closing the management connection after a non-text message"); + + ManagementConnectionStep::End(ManagementConnectionEnd::BalancerSentUnsupportedFrame) + } + Message::Close(_) | Message::Ping(_) | Message::Pong(_) => { + ManagementConnectionStep::Continue + } + } + } + + fn handle_deserialized_message( + &self, + deserialized_message: JsonRpcMessage, + ) -> ManagementConnectionStep { + match deserialized_message { + JsonRpcMessage::Notification(JsonRpcNotification::SetState(set_state_params)) => { + let desired_state = set_state_params.desired_state; + + self.agent_desired_state_tx + .send_if_modified(|current_desired_state| { + if *current_desired_state == desired_state { + return false; + } + + *current_desired_state = desired_state; + + true + }); + + ManagementConnectionStep::Continue + } + JsonRpcMessage::Notification(JsonRpcNotification::StopRespondingTo(request_id)) => { + match self.request_stoppers.send_to(&request_id, ()) { + RequestDelivery::Delivered => {} + RequestDelivery::ReceiverDropped | RequestDelivery::RequestNotRegistered => { + debug!("Request {request_id:?} finished before its stop arrived"); + } + } + + ManagementConnectionStep::Continue + } + JsonRpcMessage::Notification(JsonRpcNotification::Version(VersionParams { + version, + })) => { + if version != env!("CARGO_PKG_VERSION") { + warn!( + "Version mismatch: server version is {version}, client version is {}", + env!("CARGO_PKG_VERSION") + ); + } + + ManagementConnectionStep::Continue + } + JsonRpcMessage::Request(RequestEnvelope { + id, + request: + JsonRpcRequest::ContinueFromConversationHistory( + continue_from_conversation_history_params, + ), + }) => self.stream_responses::<_, GeneratedTokenResult>( + id, + continue_from_conversation_history_params, + ), + JsonRpcMessage::Request(RequestEnvelope { + id, + request: JsonRpcRequest::ContinueFromRawPrompt(continue_from_raw_prompt_params), + }) => self + .stream_responses::<_, GeneratedTokenResult>(id, continue_from_raw_prompt_params), + JsonRpcMessage::Request(RequestEnvelope { + id, + request: JsonRpcRequest::Decide(decide_params), + }) => self.stream_responses::<_, DecisionResult>(id, decide_params), + JsonRpcMessage::Request(RequestEnvelope { + id, + request: JsonRpcRequest::GenerateEmbeddingBatch(generate_embedding_batch_params), + }) => self.stream_responses::<_, EmbeddingResult>(id, generate_embedding_batch_params), + JsonRpcMessage::Request(RequestEnvelope { + id, + request: JsonRpcRequest::GetChatTemplateOverride, + }) => ManagementConnectionStep::Write(agent_response_message( + id, + JsonRpcResponse::ChatTemplateOverride( + self.served_applicable_state_holder + .get_agent_applicable_state() + .chat_template_override(), + ), + )), + JsonRpcMessage::Request(RequestEnvelope { + id, + request: JsonRpcRequest::GetModelMetadata, + }) => ManagementConnectionStep::Write(agent_response_message( + id, + JsonRpcResponse::ModelMetadata(self.model_metadata_holder.get_model_metadata()), + )), + } + } + + fn stream_responses( + &self, + request_id: String, + ReservedRequest { + params, + reserved_slots, + }: ReservedRequest, + ) -> ManagementConnectionStep + where + AgentRequest: Into, + TResponse: Into + Send + 'static, + { + let (response_tx, response_rx) = mpsc::unbounded_channel::(); + let (stop_tx, stop_rx) = mpsc::unbounded_channel::<()>(); + + let Some(stopper_guard) = + RequestRegistryGuard::register(&self.request_stoppers, request_id.clone(), stop_tx) + else { + error!( + "The balancer reused the id of request {request_id:?}, which is still in flight" + ); + + return ManagementConnectionStep::End( + ManagementConnectionEnd::BalancerReusedInFlightRequestId, + ); + }; + + self.pipeline_requests.push( + AgentRequest { + params, + response_tx, + slot_guard: SlotGuard::new(self.slot_aggregated_status.clone(), reserved_slots), + stop_rx, + } + .into(), + ); + + ManagementConnectionStep::StreamResponses( + PipelineResponseStream { + response_rx, + stopper_guard, + } + .boxed(), + ) + } +} diff --git a/paddler_agent/src/capability_plan.rs b/paddler_agent/src/capability_plan.rs new file mode 100644 index 000000000..6534bd298 --- /dev/null +++ b/paddler_agent/src/capability_plan.rs @@ -0,0 +1,61 @@ +use paddler_agent_runtime::shuts_down::ShutsDown; + +pub enum CapabilityPlan { + Keep(TServed), + Start, +} + +impl CapabilityPlan +where + TServed: ShutsDown + Send, +{ + pub async fn retaining( + served: TServed, + served_settings: &TSettings, + desired_settings: &TSettings, + ) -> Self { + if served_settings == desired_settings { + Self::Keep(served) + } else { + served.shut_down().await; + + Self::Start + } + } +} + +impl ShutsDown for CapabilityPlan +where + TServed: ShutsDown + Send, +{ + async fn shut_down(self) { + match self { + Self::Keep(served) => served.shut_down().await, + Self::Start => {} + } + } +} + +#[cfg(test)] +mod tests { + use std::mem::discriminant; + + use super::CapabilityPlan; + use crate::served_decisions::ServedDecisions; + + #[tokio::test] + async fn keeps_a_capability_whose_settings_did_not_change() { + assert_eq!( + discriminant(&CapabilityPlan::retaining(ServedDecisions::Disabled, &1, &1).await), + discriminant(&CapabilityPlan::Keep(ServedDecisions::Disabled)) + ); + } + + #[tokio::test] + async fn restarts_a_capability_whose_settings_changed() { + assert_eq!( + discriminant(&CapabilityPlan::retaining(ServedDecisions::Disabled, &1, &2).await), + discriminant(&CapabilityPlan::::Start) + ); + } +} diff --git a/paddler_agent/src/capability_starter.rs b/paddler_agent/src/capability_starter.rs new file mode 100644 index 000000000..70e96d064 --- /dev/null +++ b/paddler_agent/src/capability_starter.rs @@ -0,0 +1,253 @@ +use tokio_util::sync::CancellationToken; + +use paddler_agent_decision::decision_pipeline::DecisionPipeline; +use paddler_agent_decision::decision_prerequisites::DecisionPrerequisites; +use paddler_agent_embeddings::embedding_pipeline::EmbeddingPipeline; +use paddler_agent_runtime::inference_runtime_context::InferenceRuntimeContext; +use paddler_agent_runtime::llama_model_settings::LlamaModelSettings; +use paddler_agent_runtime::loaded_llama_model::LoadedLlamaModel; +use paddler_agent_runtime::shuts_down::ShutsDown as _; +use paddler_agent_runtime::startup_outcome::StartupOutcome; +use paddler_agent_text_generation::text_generation_pipeline::TextGenerationPipeline; +use paddler_inference_parameters::model_runtime_parameters::ModelRuntimeParameters; +use paddler_spawn_resuming_panic::spawn_blocking_resuming_panic::spawn_blocking_resuming_panic; + +use crate::agent_error::AgentError; +use crate::applicable_decisions::ApplicableDecisions; +use crate::capability_plan::CapabilityPlan; +use crate::capability_startup_interruption::CapabilityStartupInterruption; +use crate::inference_capabilities::InferenceCapabilities; +use crate::loaded_model_serving_plan::LoadedModelServingPlan; +use crate::model_serving_settings::ModelServingSettings; +use crate::prepared_decisions::PreparedDecisions; +use crate::served_capabilities::ServedCapabilities; +use crate::served_decisions::ServedDecisions; +use crate::served_embeddings::ServedEmbeddings; +use crate::served_model::ServedModel; +use crate::served_text_generation::ServedTextGeneration; +use crate::serving_interruption::ServingInterruption; + +trait IntoEstablishedCapability { + fn into_established_capability(self) -> Result; +} + +impl IntoEstablishedCapability + for Result, TError> +where + AgentError: From, +{ + fn into_established_capability(self) -> Result { + match self { + Ok(StartupOutcome::Ready(started)) => Ok(started), + Ok(StartupOutcome::Cancelled) => Err(CapabilityStartupInterruption::Cancelled), + Err(startup_error) => Err(CapabilityStartupInterruption::Failed(AgentError::from( + startup_error, + ))), + } + } +} + +pub struct CapabilityStarter<'startup> { + pub cancellation_token: &'startup CancellationToken, + pub inference_runtime_context: &'startup InferenceRuntimeContext, +} + +impl CapabilityStarter<'_> { + pub async fn prepare_decisions( + &self, + applicable_decisions: &ApplicableDecisions, + ) -> Result { + match applicable_decisions { + ApplicableDecisions::Disabled => Ok(PreparedDecisions::Disabled), + ApplicableDecisions::Enabled { pointer_head_path } => { + let decision_prerequisites = DecisionPrerequisites { + inference_runtime_context: self.inference_runtime_context.clone(), + pointer_head_path: pointer_head_path.clone(), + }; + let cancellation_token = self.cancellation_token.clone(); + + spawn_blocking_resuming_panic(move || { + decision_prerequisites + .load_pointer_head() + .map(|loaded_pointer_head| { + if cancellation_token.is_cancelled() { + StartupOutcome::Cancelled + } else { + StartupOutcome::Ready(loaded_pointer_head) + } + }) + }) + .await + .into_established_capability() + .map(|loaded_pointer_head| { + PreparedDecisions::Enabled(Box::new(loaded_pointer_head)) + }) + } + } + } + + pub async fn load_model( + &self, + llama_model_settings: LlamaModelSettings, + ) -> Result { + LoadedLlamaModel::load_unless_shutting_down( + self.cancellation_token, + self.inference_runtime_context.clone(), + llama_model_settings, + ) + .await + .into_established_capability() + } + + pub async fn serve( + &self, + LoadedModelServingPlan { + capability_plans: + InferenceCapabilities { + decisions: decisions_plan, + embeddings: embeddings_plan, + text_generation: text_generation_plan, + }, + loaded_llama_model, + model_serving_settings, + }: LoadedModelServingPlan, + prepared_decisions: PreparedDecisions, + ) -> Result> { + let decisions = match self + .establish_decisions( + &loaded_llama_model, + &model_serving_settings.model_runtime_parameters, + decisions_plan, + prepared_decisions, + ) + .await + { + Ok(decisions) => decisions, + Err(interruption) => { + embeddings_plan.shut_down().await; + text_generation_plan.shut_down().await; + + return Err(Box::new(ServingInterruption { + interruption, + loaded_llama_model, + })); + } + }; + let embeddings = match self + .establish_embeddings( + &loaded_llama_model, + &model_serving_settings, + embeddings_plan, + ) + .await + { + Ok(embeddings) => embeddings, + Err(interruption) => { + decisions.shut_down().await; + text_generation_plan.shut_down().await; + + return Err(Box::new(ServingInterruption { + interruption, + loaded_llama_model, + })); + } + }; + let text_generation = match self + .establish_text_generation( + &loaded_llama_model, + &model_serving_settings, + text_generation_plan, + ) + .await + { + Ok(text_generation) => text_generation, + Err(interruption) => { + decisions.shut_down().await; + embeddings.shut_down().await; + + return Err(Box::new(ServingInterruption { + interruption, + loaded_llama_model, + })); + } + }; + + Ok(ServedModel { + capabilities: ServedCapabilities { + decisions, + embeddings, + text_generation, + }, + loaded_llama_model, + model_serving_settings, + }) + } + + async fn establish_decisions( + &self, + loaded_llama_model: &LoadedLlamaModel, + model_runtime_parameters: &ModelRuntimeParameters, + decisions_plan: CapabilityPlan, + prepared_decisions: PreparedDecisions, + ) -> Result { + match (decisions_plan, prepared_decisions) { + (CapabilityPlan::Keep(served_decisions), _) => Ok(served_decisions), + (CapabilityPlan::Start, PreparedDecisions::Disabled) => Ok(ServedDecisions::Disabled), + (CapabilityPlan::Start, PreparedDecisions::Enabled(loaded_pointer_head)) => { + DecisionPipeline { + inference_runtime_context: self.inference_runtime_context.clone(), + loaded_llama_model: loaded_llama_model.clone(), + loaded_pointer_head: *loaded_pointer_head, + model_runtime_parameters: model_runtime_parameters.clone(), + } + .spawn(self.cancellation_token) + .await + .into_established_capability() + .map(ServedDecisions::Enabled) + } + } + } + + async fn establish_embeddings( + &self, + loaded_llama_model: &LoadedLlamaModel, + model_serving_settings: &ModelServingSettings, + embeddings_plan: CapabilityPlan, + ) -> Result { + match embeddings_plan { + CapabilityPlan::Keep(embeddings) => Ok(embeddings), + CapabilityPlan::Start => EmbeddingPipeline { + inference_runtime_context: self.inference_runtime_context.clone(), + loaded_llama_model: loaded_llama_model.clone(), + model_runtime_parameters: model_serving_settings.model_runtime_parameters.clone(), + pooling_type: model_serving_settings.capabilities.embeddings.clone(), + } + .spawn(self.cancellation_token) + .await + .into_established_capability(), + } + } + + async fn establish_text_generation( + &self, + loaded_llama_model: &LoadedLlamaModel, + model_serving_settings: &ModelServingSettings, + text_generation_plan: CapabilityPlan, + ) -> Result { + match text_generation_plan { + CapabilityPlan::Keep(text_generation) => Ok(text_generation), + CapabilityPlan::Start => TextGenerationPipeline { + inference_runtime_context: self.inference_runtime_context.clone(), + loaded_llama_model: loaded_llama_model.clone(), + model_runtime_parameters: model_serving_settings.model_runtime_parameters.clone(), + text_generation_settings: model_serving_settings + .capabilities + .text_generation + .clone(), + } + .spawn(self.cancellation_token) + .await + .into_established_capability(), + } + } +} diff --git a/paddler_agent/src/capability_startup_interruption.rs b/paddler_agent/src/capability_startup_interruption.rs new file mode 100644 index 000000000..086f7a9b6 --- /dev/null +++ b/paddler_agent/src/capability_startup_interruption.rs @@ -0,0 +1,7 @@ +use crate::agent_error::AgentError; + +#[derive(Debug)] +pub enum CapabilityStartupInterruption { + Cancelled, + Failed(AgentError), +} diff --git a/paddler_agent/src/chat_prompt_render_request.rs b/paddler_agent/src/chat_prompt_render_request.rs deleted file mode 100644 index df6981c4d..000000000 --- a/paddler_agent/src/chat_prompt_render_request.rs +++ /dev/null @@ -1,10 +0,0 @@ -use paddler_messaging::chat_template_message::ChatTemplateMessage; -use paddler_messaging::request_params::continue_from_conversation_history_params::tool::Tool; -use paddler_messaging::request_params::continue_from_conversation_history_params::tool::tool_params::function_call::parameters_schema::validated_parameters_schema::ValidatedParametersSchema; - -pub struct ChatPromptRenderRequest<'request> { - pub add_generation_prompt: bool, - pub enable_thinking: bool, - pub messages: &'request [ChatTemplateMessage], - pub tools: &'request [Tool], -} diff --git a/paddler_agent/src/chat_template_load_status.rs b/paddler_agent/src/chat_template_load_status.rs deleted file mode 100644 index 1852f5004..000000000 --- a/paddler_agent/src/chat_template_load_status.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub enum ChatTemplateLoadStatus { - Loaded, - SkippedForEmbeddings, -} diff --git a/paddler_agent/src/chat_template_renderer/mod.rs b/paddler_agent/src/chat_template_renderer/mod.rs deleted file mode 100644 index 4023ec51b..000000000 --- a/paddler_agent/src/chat_template_renderer/mod.rs +++ /dev/null @@ -1,176 +0,0 @@ -pub mod pyjinja_tojson; -pub mod raise_exception; - -use minijinja::Environment; -use minijinja::Error; -use minijinja_contrib::add_to_environment; -use minijinja_contrib::pycompat::unknown_method_callback; -use serde::ser::Serialize; - -use paddler_messaging::chat_template::ChatTemplate; - -use self::pyjinja_tojson::pyjinja_tojson; -use self::raise_exception::raise_exception; - -const CHAT_TEMPLATE_NAME: &str = "chat_template"; - -pub struct ChatTemplateRenderer { - minijinja_env: Environment<'static>, -} - -impl ChatTemplateRenderer { - pub fn new(ChatTemplate { content }: ChatTemplate) -> Result { - let mut minijinja_env = Environment::new(); - - minijinja_env.add_function("raise_exception", raise_exception); - minijinja_env.add_template_owned(CHAT_TEMPLATE_NAME, content)?; - minijinja_env.set_unknown_method_callback(unknown_method_callback); - - add_to_environment(&mut minijinja_env); - minijinja_env.add_filter("tojson", pyjinja_tojson); - - Ok(Self { minijinja_env }) - } - - pub fn render(&self, context: TContext) -> Result { - self.minijinja_env - .get_template(CHAT_TEMPLATE_NAME) - .and_then(|template| template.render(context)) - } -} - -#[cfg(test)] -mod tests { - use std::collections::HashMap; - - use minijinja::ErrorKind; - use minijinja::context; - - use paddler_messaging::chat_template::ChatTemplate; - use paddler_messaging::chat_template_message::ChatTemplateMessage; - use paddler_messaging::chat_template_message_content::ChatTemplateMessageContent; - - use crate::chat_template_renderer::ChatTemplateRenderer; - - #[test] - fn new_with_valid_template_succeeds() { - let template = ChatTemplate { - content: "Hello {{ name }}!".to_owned(), - }; - - assert!(ChatTemplateRenderer::new(template).is_ok()); - } - - #[test] - fn new_with_invalid_template_fails() { - let template = ChatTemplate { - content: "{% if unclosed %}".to_owned(), - }; - - assert!( - ChatTemplateRenderer::new(template) - .is_err_and(|compile_error| compile_error.kind() == ErrorKind::SyntaxError) - ); - } - - #[test] - fn renders_the_context_values_into_the_template() { - let template = ChatTemplate { - content: "Hello {{ name }}!".to_owned(), - }; - let renderer = ChatTemplateRenderer::new(template).unwrap(); - let mut context = HashMap::new(); - context.insert("name", "world"); - - let result = renderer.render(context).unwrap(); - - assert_eq!(result, "Hello world!"); - } - - #[test] - fn renders_messages_loop_with_roles() { - let template = ChatTemplate { - content: "{% for message in messages %}{{ message.role }}:{{ message.content }}\n{% endfor %}".to_owned(), - }; - let renderer = ChatTemplateRenderer::new(template).unwrap(); - let messages = vec![ - ChatTemplateMessage { - content: ChatTemplateMessageContent::Text("hi".to_owned()), - role: "user".to_owned(), - }, - ChatTemplateMessage { - content: ChatTemplateMessageContent::Text("hello".to_owned()), - role: "assistant".to_owned(), - }, - ]; - - let result = renderer.render(context! { messages => messages }).unwrap(); - - assert_eq!(result, "user:hi\nassistant:hello\n"); - } - - #[test] - fn add_generation_prompt_branch_changes_output() { - let template = ChatTemplate { - content: "A{% if add_generation_prompt %}B{% endif %}".to_owned(), - }; - let renderer = ChatTemplateRenderer::new(template).unwrap(); - - let with_prompt = renderer - .render(context! { add_generation_prompt => true }) - .unwrap(); - let without_prompt = renderer - .render(context! { add_generation_prompt => false }) - .unwrap(); - - assert_eq!(with_prompt, "AB"); - assert_eq!(without_prompt, "A"); - } - - #[test] - fn registers_pyjinja_tojson_filter() { - let template = ChatTemplate { - content: "{{ value | tojson(ensure_ascii=False) }}".to_owned(), - }; - let renderer = ChatTemplateRenderer::new(template).unwrap(); - - let result = renderer.render(context! { value => "café" }).unwrap(); - - assert_eq!(result, "\"café\""); - } - - #[test] - fn registers_raise_exception_function() { - let template = ChatTemplate { - content: "{{ raise_exception('boom') }}".to_owned(), - }; - let template_renderer = ChatTemplateRenderer::new(template).unwrap(); - - let render_error = template_renderer - .render(context! {}) - .expect_err("raise_exception must turn rendering into an error"); - - assert_eq!( - render_error.detail(), - Some("Model's chat template raised an exception: 'boom'") - ); - } - - #[test] - fn render_fails_when_template_is_not_registered() { - let template = ChatTemplate { - content: "Hello {{ name }}!".to_owned(), - }; - let mut renderer = ChatTemplateRenderer::new(template).unwrap(); - - renderer - .minijinja_env - .remove_template(super::CHAT_TEMPLATE_NAME); - - let render_error = renderer - .render(context! {}) - .expect_err("rendering must fail when the template is missing"); - - assert_eq!(render_error.kind(), ErrorKind::TemplateNotFound); - } -} diff --git a/paddler_agent/src/continuous_batch_arbiter.rs b/paddler_agent/src/continuous_batch_arbiter.rs deleted file mode 100644 index 32b0656b4..000000000 --- a/paddler_agent/src/continuous_batch_arbiter.rs +++ /dev/null @@ -1,525 +0,0 @@ -use std::cmp::max; -use std::path::PathBuf; -use std::sync::Arc; -use std::sync::mpsc::channel; -use std::thread; -use std::thread::available_parallelism; - -use anyhow::Context as _; -use anyhow::Result; -use encoding_rs::Decoder; -use encoding_rs::UTF_8; -use llama_cpp_bindings::SampledToken; -use llama_cpp_bindings::context::LlamaContext; -use llama_cpp_bindings::context::params::LlamaContextParams; -use llama_cpp_bindings::error::TokenToStringError; -use llama_cpp_bindings::llama_backend::LlamaBackend; -use llama_cpp_bindings::llama_batch::LlamaBatch; -use llama_cpp_bindings::model::LlamaModel; -use llama_cpp_bindings::model::params::LlamaModelParams; -use llama_cpp_bindings::mtmd::MtmdContext; -use llama_cpp_bindings::mtmd::MtmdContextParams; -use llama_cpp_bindings::mtmd::micro_batch_tokens; -use llama_cpp_bindings::mtmd::mtmd_default_marker; -use llama_cpp_bindings::token::LlamaToken; -use llama_cpp_bindings_sys::LLAMA_FLASH_ATTN_TYPE_AUTO; -use log::debug; -use log::error; -use log::info; -use log::warn; -use tokio::sync::oneshot; -use tokio::task::spawn_blocking; -use tokio_util::sync::CancellationToken; -use tokio_util::task::TaskTracker; - -use paddler_agent_status::agent_issue_fix::AgentIssueFix; -use paddler_inference_parameters::inference_parameters::InferenceParameters; -use paddler_messaging::agent_issue::AgentIssue; -use paddler_messaging::agent_issue_params::chat_template_does_not_compile_params::ChatTemplateDoesNotCompileParams; -use paddler_messaging::agent_issue_params::model_path::ModelPath; -use paddler_messaging::agent_issue_params::slot_cannot_start_params::SlotCannotStartParams; -use paddler_messaging::chat_template::ChatTemplate; -use paddler_messaging::media_marker::MediaMarker; -use paddler_messaging::model_metadata::ModelMetadata; - -use crate::agent_kv_cache_dtype::AgentKvCacheDtype; -use crate::agent_pooling_type::AgentPoolingType; -use crate::chat_prompt_renderer::ChatPromptRenderer; -use crate::chat_template_load_status::ChatTemplateLoadStatus; -use crate::chat_template_renderer::ChatTemplateRenderer; -use crate::continuous_batch_arbiter_context::ContinuousBatchArbiterContext; -use crate::continuous_batch_arbiter_handle::ContinuousBatchArbiterHandle; -use crate::continuous_batch_arbiter_spawn_outcome::ContinuousBatchArbiterSpawnOutcome; -use crate::continuous_batch_request_preparer::ContinuousBatchRequestPreparer; -use crate::continuous_batch_scheduler::ContinuousBatchScheduler; -use crate::continuous_batch_scheduler_context::ContinuousBatchSchedulerContext; -use crate::continuous_batch_scheduler_params::ContinuousBatchSchedulerParams; -use crate::converts_to_llama_kv_cache_dtype::ConvertsToLlamaKvCacheDtype; -use crate::converts_to_llama_pooling_type::ConvertsToLlamaPoolingType; -use crate::embedding_batch_preparer::EmbeddingBatchPreparer; -use crate::generation_request_preparer::GenerationRequestPreparer; -use crate::image_input::ImageInput; -use crate::join_scheduler_thread::join_scheduler_thread; -use crate::multimodal_prompt_support::MultimodalPromptSupport; -use crate::prompt_tokenizer::PromptTokenizer; -use crate::sampler_chain_factory::SamplerChainFactory; -use crate::send_startup_signal::send_startup_signal; -use crate::startup_signal_delivery::StartupSignalDelivery; -use crate::token_generation::TokenGeneration; -use crate::token_generation_support::TokenGenerationSupport; - -const LOGICAL_CORES_PER_PHYSICAL_CORE: i32 = 2; -const MINIMUM_THREAD_COUNT: i32 = 2; - -fn special_token_text( - model: &LlamaModel, - token: LlamaToken, - decoder: &mut Decoder, -) -> Result { - model.token_to_piece(&SampledToken::Content(token), decoder, true, None) -} - -pub struct ContinuousBatchArbiter { - pub chat_template_override: Option, - pub context: ContinuousBatchArbiterContext, - pub inference_parameters: InferenceParameters, - pub model_path: PathBuf, - pub multimodal_projection_path: Option, -} - -impl ContinuousBatchArbiter { - pub async fn spawn( - &self, - cancellation_token: &CancellationToken, - ) -> Result { - if cancellation_token.is_cancelled() { - return Ok(ContinuousBatchArbiterSpawnOutcome::Cancelled); - } - - let desired_slots_total = self.context.slot_aggregated_status.desired_slots_total; - let n_seq_max = u32::from(desired_slots_total); - let (chat_template_loaded_tx, chat_template_loaded_rx) = - oneshot::channel::(); - let (model_loaded_tx, model_loaded_rx) = oneshot::channel::<()>(); - let (agent_warm_and_scheduler_running_tx, agent_warm_and_scheduler_running_rx) = - oneshot::channel::>(); - - let available_parallelism_value: i32 = available_parallelism()?.get().try_into()?; - let thread_count = max( - MINIMUM_THREAD_COUNT, - available_parallelism_value / LOGICAL_CORES_PER_PHYSICAL_CORE, - ); - - info!("Using {thread_count} threads for generation and for batch processing"); - - let agent_name_clone = self.context.agent_name.clone(); - let inference_parameters = self.inference_parameters.clone(); - let model_metadata_holder = self.context.model_metadata_holder.clone(); - let multimodal_projection_path = self.multimodal_projection_path.clone(); - let model_path = self.model_path.clone(); - let model_issue_path = ModelPath::from(self.model_path.as_path()); - let chat_template_override = self.chat_template_override.clone(); - let slot_aggregated_status = self.context.slot_aggregated_status.clone(); - let agent_shutdown = cancellation_token.clone(); - - let scheduler_thread_handle = thread::spawn(move || -> Result<()> { - let llama_backend = - Arc::new(LlamaBackend::init().context("Unable to initialize llama.cpp backend")?); - - let n_batch_tokens = inference_parameters.n_batch.tokens().get(); - - let default_context_params = LlamaContextParams::default(); - let n_ubatch = if inference_parameters.enable_embeddings { - n_batch_tokens - } else { - default_context_params.n_ubatch() - }; - - let context_params = default_context_params - .with_embeddings(inference_parameters.enable_embeddings) - .with_n_ctx(Some(inference_parameters.context_size)) - .with_n_batch(n_batch_tokens) - .with_n_ubatch(n_ubatch) - .with_flash_attention_policy(LLAMA_FLASH_ATTN_TYPE_AUTO) - .with_n_seq_max(n_seq_max) - .with_n_threads(thread_count) - .with_n_threads_batch(thread_count) - .with_pooling_type( - AgentPoolingType(inference_parameters.pooling_type.clone()) - .to_llama_pooling_type(), - ) - .with_type_k( - AgentKvCacheDtype(inference_parameters.k_cache_dtype.clone()) - .to_llama_kv_cache_dtype(), - ) - .with_type_v( - AgentKvCacheDtype(inference_parameters.v_cache_dtype.clone()) - .to_llama_kv_cache_dtype(), - ); - - let model = Arc::new( - LlamaModel::load_from_file( - &llama_backend, - model_path.clone(), - &LlamaModelParams::default() - .with_n_gpu_layers(inference_parameters.n_gpu_layers), - ) - .context("Unable to load model from file")?, - ); - - if matches!( - send_startup_signal(model_loaded_tx, ()), - StartupSignalDelivery::Abandoned - ) { - return Ok(()); - } - - let mut model_metadata = ModelMetadata::default(); - - for metadata_index in 0..model.meta_count() { - model_metadata.set_meta_field( - model.meta_key_by_index(metadata_index)?, - model.meta_val_str_by_index(metadata_index)?, - ); - } - - model_metadata_holder.set_model_metadata(model_metadata); - - let token_generation = if inference_parameters.enable_embeddings - && chat_template_override.is_none() - { - if matches!( - send_startup_signal( - chat_template_loaded_tx, - ChatTemplateLoadStatus::SkippedForEmbeddings, - ), - StartupSignalDelivery::Abandoned - ) { - return Ok(()); - } - - TokenGeneration::DisabledForEmbeddings - } else { - let llama_chat_template_string = match chat_template_override { - Some(chat_template) => chat_template.content, - None => model - .chat_template(None) - .context(format!( - "Failed to load chat template for model at path: {}", - model_path.display() - ))? - .to_string()?, - }; - - if matches!( - send_startup_signal(chat_template_loaded_tx, ChatTemplateLoadStatus::Loaded), - StartupSignalDelivery::Abandoned - ) { - return Ok(()); - } - - let chat_template_renderer = match ChatTemplateRenderer::new(ChatTemplate { - content: llama_chat_template_string.clone(), - }) - .context("Failed to create chat template renderer") - { - Ok(renderer) => { - slot_aggregated_status.register_fix( - &AgentIssueFix::ChatTemplateIsCompiled(model_issue_path.clone()), - ); - - renderer - } - Err(err) => { - slot_aggregated_status.register_issue( - AgentIssue::ChatTemplateDoesNotCompile( - ChatTemplateDoesNotCompileParams { - error: format!("{err}"), - model_path: model_issue_path.clone(), - template_content: llama_chat_template_string, - }, - ), - ); - - return Err(err); - } - }; - - let mut special_token_decoder = UTF_8.new_decoder(); - - TokenGeneration::Enabled(Box::new(TokenGenerationSupport { - chat_prompt_renderer: ChatPromptRenderer { - chat_template_renderer, - media_marker: MediaMarker::new(mtmd_default_marker()?.to_owned()), - token_bos_str: special_token_text( - &model, - model.token_bos(), - &mut special_token_decoder, - )?, - token_eos_str: special_token_text( - &model, - model.token_eos(), - &mut special_token_decoder, - )?, - token_nl_str: special_token_text( - &model, - model.token_nl(), - &mut special_token_decoder, - )?, - }, - streaming_markers: model.streaming_markers()?, - })) - }; - - slot_aggregated_status.set_model_path(Some(model_issue_path.model_path.clone())); - - let mut llama_context = - match LlamaContext::from_model(&model, &llama_backend, context_params) - .context("Unable to create llama.cpp context") - { - Ok(context) => context, - Err(err) => { - for slot_index in 0..n_seq_max { - slot_aggregated_status.register_issue(AgentIssue::SlotCannotStart( - SlotCannotStartParams { - error: format!("{err:#}"), - slot_index, - }, - )); - } - - return Err(err); - } - }; - - let sequence_context_size = llama_context.n_ctx_seq(); - - let image_input = match multimodal_projection_path { - Some(multimodal_projection_path) => { - let multimodal_projection_path_str = - multimodal_projection_path.to_string_lossy(); - - match MtmdContext::init_from_file( - &multimodal_projection_path_str, - &model, - &MtmdContextParams::default(), - ) { - Ok(mtmd_context) => { - slot_aggregated_status.register_fix( - &AgentIssueFix::MultimodalProjectionIsLoaded(ModelPath::from( - multimodal_projection_path.as_path(), - )), - ); - - info!( - "Multimodal context initialized from: {}", - multimodal_projection_path.display() - ); - - ImageInput::Supported(MultimodalPromptSupport { - micro_batch_tokens: micro_batch_tokens( - &llama_context, - inference_parameters.n_batch.tokens(), - ), - multimodal_context: Arc::new(mtmd_context), - n_batch: inference_parameters.n_batch.tokens_i32(), - sequence_context_size, - }) - } - Err(err) => { - slot_aggregated_status.register_issue( - AgentIssue::MultimodalProjectionCannotBeLoaded(ModelPath::from( - multimodal_projection_path.as_path(), - )), - ); - - return Err(err.into()); - } - } - } - None => ImageInput::Unsupported, - }; - - let (scheduler_command_tx, scheduler_command_rx) = channel(); - - let request_preparer = Arc::new(ContinuousBatchRequestPreparer { - agent_name: agent_name_clone.clone(), - embedding_batch_preparer: EmbeddingBatchPreparer { - enable_embeddings: inference_parameters.enable_embeddings, - model: model.clone(), - n_batch: inference_parameters.n_batch.tokens_usize(), - }, - generation_request_preparer: GenerationRequestPreparer { - image_input, - image_resize_to_fit: inference_parameters.image_resize_to_fit, - model: model.clone(), - prompt_tokenizer: PromptTokenizer { - model: model.clone(), - sequence_context_size, - }, - sampler_chain_factory: SamplerChainFactory { - inference_parameters: inference_parameters.clone(), - n_vocab: model.n_vocab(), - }, - token_generation, - }, - llama_backend: llama_backend.clone(), - preparation_tasks: TaskTracker::new(), - scheduler_command_tx, - }); - - let mut batch = LlamaBatch::new( - inference_parameters.n_batch.tokens_usize(), - i32::from(desired_slots_total), - )?; - - Self::run_warmup_decode(&model, &mut llama_context, &mut batch, desired_slots_total); - - let mut scheduler = ContinuousBatchScheduler::new(ContinuousBatchSchedulerParams { - agent_shutdown, - batch, - command_rx: scheduler_command_rx, - llama_context, - scheduler_context: ContinuousBatchSchedulerContext { - agent_name: agent_name_clone, - desired_slots_total, - inference_parameters, - model: model.clone(), - }, - }); - - if matches!( - send_startup_signal(agent_warm_and_scheduler_running_tx, request_preparer), - StartupSignalDelivery::Abandoned - ) { - return Ok(()); - } - - scheduler.run(); - - Ok(()) - }); - - let Some(startup_result) = cancellation_token - .run_until_cancelled(self.await_startup_signals( - model_loaded_rx, - chat_template_loaded_rx, - agent_warm_and_scheduler_running_rx, - )) - .await - else { - if let Err(err) = spawn_blocking(move || join_scheduler_thread(scheduler_thread_handle)) - .await - .context("Failed to join the scheduler shutdown task")? - { - debug!("Scheduler thread ended while its spawn was being cancelled: {err}"); - } - - return Ok(ContinuousBatchArbiterSpawnOutcome::Cancelled); - }; - - let request_preparer = match startup_result { - Ok(request_preparer) => request_preparer, - Err(startup_error) => { - return spawn_blocking(move || join_scheduler_thread(scheduler_thread_handle)) - .await - .context("Failed to join the scheduler thread after its startup failed")? - .and(Err(startup_error)); - } - }; - - for slot_index in 0..n_seq_max { - self.context.slot_aggregated_status.increment_total_slots(); - - self.context - .slot_aggregated_status - .register_fix(&AgentIssueFix::SlotStarted(slot_index)); - } - - Ok(ContinuousBatchArbiterSpawnOutcome::Ready( - ContinuousBatchArbiterHandle { - request_preparer, - scheduler_thread_handle, - }, - )) - } - - async fn await_startup_signals( - &self, - model_loaded_rx: oneshot::Receiver<()>, - chat_template_loaded_rx: oneshot::Receiver, - agent_warm_and_scheduler_running_rx: oneshot::Receiver>, - ) -> Result> { - let model_issue_path = ModelPath::from(self.model_path.as_path()); - - match model_loaded_rx - .await - .context("Failed to receive model loaded signal") - { - Ok(()) => { - self.context - .slot_aggregated_status - .register_fix(&AgentIssueFix::ModelIsLoaded(model_issue_path.clone())); - } - Err(err) => { - error!("Failed to load model: {err}"); - - self.context - .slot_aggregated_status - .register_issue(AgentIssue::ModelCannotBeLoaded(model_issue_path.clone())); - } - } - - match chat_template_loaded_rx - .await - .context("Failed to receive chat template loaded signal") - { - Ok(ChatTemplateLoadStatus::Loaded) => { - self.context.slot_aggregated_status.register_fix( - &AgentIssueFix::ModelChatTemplateIsLoaded(model_issue_path.clone()), - ); - } - Ok(ChatTemplateLoadStatus::SkippedForEmbeddings) => {} - Err(err) => { - error!("Failed to load chat template: {err}"); - - if !self - .context - .slot_aggregated_status - .has_issue(&AgentIssue::ModelCannotBeLoaded(model_issue_path.clone())) - { - self.context - .slot_aggregated_status - .register_issue(AgentIssue::UnableToFindChatTemplate(model_issue_path)); - } - } - } - - agent_warm_and_scheduler_running_rx.await.context( - "Scheduler thread did not signal agent-warm-and-scheduler-running before exiting", - ) - } - - fn run_warmup_decode( - model: &LlamaModel, - llama_context: &mut LlamaContext<'_>, - warmup_batch: &mut LlamaBatch<'static>, - desired_slots_total: u16, - ) { - let warmup_tokens = [model.token_bos(), model.token_eos()]; - - for sequence_index in 0..i32::from(desired_slots_total) { - if let Err(err) = warmup_batch.add_sequence(&warmup_tokens, sequence_index, true) { - warn!("Warmup batch add_sequence failed: {err:#}"); - return; - } - } - - llama_context.clear_kv_cache(); - if let Err(err) = llama_context.decode(warmup_batch) { - warn!("Warmup decode failed: {err:#}"); - } - llama_context.synchronize(); - llama_context.clear_kv_cache(); - } -} diff --git a/paddler_agent/src/continuous_batch_arbiter_context.rs b/paddler_agent/src/continuous_batch_arbiter_context.rs deleted file mode 100644 index f84d239ef..000000000 --- a/paddler_agent/src/continuous_batch_arbiter_context.rs +++ /dev/null @@ -1,12 +0,0 @@ -use std::sync::Arc; - -use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; - -use crate::model_metadata_holder::ModelMetadataHolder; - -#[derive(Clone)] -pub struct ContinuousBatchArbiterContext { - pub agent_name: Option, - pub model_metadata_holder: Arc, - pub slot_aggregated_status: Arc, -} diff --git a/paddler_agent/src/continuous_batch_arbiter_handle.rs b/paddler_agent/src/continuous_batch_arbiter_handle.rs deleted file mode 100644 index e4e60f4e7..000000000 --- a/paddler_agent/src/continuous_batch_arbiter_handle.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::sync::Arc; -use std::thread; - -use anyhow::Context as _; -use anyhow::Result; -use tokio::task::spawn_blocking; - -use crate::continuous_batch_request_preparer::ContinuousBatchRequestPreparer; -use crate::join_scheduler_thread::join_scheduler_thread; - -pub struct ContinuousBatchArbiterHandle { - pub request_preparer: Arc, - pub scheduler_thread_handle: thread::JoinHandle>, -} - -impl ContinuousBatchArbiterHandle { - pub async fn shutdown(self) -> Result<()> { - let Self { - request_preparer, - scheduler_thread_handle, - } = self; - - request_preparer - .shut_down_scheduler_after_pending_preparations() - .await; - - spawn_blocking(move || { - drop(request_preparer); - - join_scheduler_thread(scheduler_thread_handle) - }) - .await - .context("Failed to join the scheduler shutdown task")? - } -} diff --git a/paddler_agent/src/continuous_batch_arbiter_spawn_outcome.rs b/paddler_agent/src/continuous_batch_arbiter_spawn_outcome.rs deleted file mode 100644 index 24de55e8f..000000000 --- a/paddler_agent/src/continuous_batch_arbiter_spawn_outcome.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::continuous_batch_arbiter_handle::ContinuousBatchArbiterHandle; - -pub enum ContinuousBatchArbiterSpawnOutcome { - Cancelled, - Ready(ContinuousBatchArbiterHandle), -} diff --git a/paddler_agent/src/continuous_batch_arbiter_state.rs b/paddler_agent/src/continuous_batch_arbiter_state.rs deleted file mode 100644 index f347f6c2d..000000000 --- a/paddler_agent/src/continuous_batch_arbiter_state.rs +++ /dev/null @@ -1,218 +0,0 @@ -use anyhow::Context as _; -use anyhow::Result; -use log::error; -use log::info; -use log::warn; -use tokio_util::sync::CancellationToken; - -use paddler_messaging::agent_state_application_status::AgentStateApplicationStatus; - -use crate::agent_applicable_model::AgentApplicableModel; -use crate::agent_applicable_state::AgentApplicableState; -use crate::continuous_batch_arbiter::ContinuousBatchArbiter; -use crate::continuous_batch_arbiter_context::ContinuousBatchArbiterContext; -use crate::continuous_batch_arbiter_handle::ContinuousBatchArbiterHandle; -use crate::continuous_batch_arbiter_spawn_outcome::ContinuousBatchArbiterSpawnOutcome; -use crate::continuous_batch_preparation_request::ContinuousBatchPreparationRequest; - -pub enum ContinuousBatchArbiterState { - Idle, - Running(ContinuousBatchArbiterHandle), -} - -impl ContinuousBatchArbiterState { - pub async fn apply( - self, - shutdown: &CancellationToken, - agent_applicable_state: AgentApplicableState, - arbiter_context: &ContinuousBatchArbiterContext, - ) -> Self { - match self - .try_to_apply(shutdown, agent_applicable_state, arbiter_context) - .await - { - Ok(next_state) => next_state, - Err(application_error) => { - error!("Failed to apply reconciled state change: {application_error}"); - - Self::Idle - } - } - } - - pub fn forward(&self, agent_name: Option<&str>, request: ContinuousBatchPreparationRequest) { - match self { - Self::Idle => request.reject_because_no_model_is_loaded(agent_name), - Self::Running(arbiter_handle) => arbiter_handle.request_preparer.prepare(request), - } - } - - pub async fn shut_down(self) -> Result<()> { - match self { - Self::Idle => Ok(()), - Self::Running(arbiter_handle) => arbiter_handle - .shutdown() - .await - .context("Arbiter shutdown returned an error"), - } - } - - async fn try_to_apply( - self, - shutdown: &CancellationToken, - agent_applicable_state: AgentApplicableState, - arbiter_context: &ContinuousBatchArbiterContext, - ) -> Result { - arbiter_context.slot_aggregated_status.reset(); - self.shut_down().await?; - - let next_state = match agent_applicable_state { - AgentApplicableState { - chat_template_override, - inference_parameters, - model: - AgentApplicableModel::Resolved { - model_path, - multimodal_projection_path, - }, - } => { - Self::spawn( - shutdown, - ContinuousBatchArbiter { - chat_template_override, - context: arbiter_context.clone(), - inference_parameters, - model_path, - multimodal_projection_path, - }, - ) - .await? - } - AgentApplicableState { - model: AgentApplicableModel::NotConfigured, - .. - } => { - warn!("No model configured in applicable state; skipping llama.cpp initialization"); - - Self::Idle - } - }; - - arbiter_context - .slot_aggregated_status - .set_state_application_status(AgentStateApplicationStatus::Applied); - - Ok(next_state) - } - - async fn spawn(shutdown: &CancellationToken, arbiter: ContinuousBatchArbiter) -> Result { - match arbiter.spawn(shutdown).await? { - ContinuousBatchArbiterSpawnOutcome::Ready(arbiter_handle) => { - info!("Reconciled state change applied successfully"); - - Ok(Self::Running(arbiter_handle)) - } - ContinuousBatchArbiterSpawnOutcome::Cancelled => { - info!("Model load was cancelled by shutdown before it finished"); - - Ok(Self::Idle) - } - } - } -} - -#[cfg(test)] -mod tests { - use std::mem::discriminant; - use std::num::NonZeroU32; - use std::sync::Arc; - - use tokio::sync::mpsc; - use tokio_util::sync::CancellationToken; - - use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; - use paddler_messaging::agent_state_application_status::AgentStateApplicationStatus; - use paddler_messaging::generated_token_result::GeneratedTokenResult; - use paddler_messaging::request_params::continue_from_raw_prompt_params::ContinueFromRawPromptParams; - - use super::ContinuousBatchArbiterState; - use crate::agent_applicable_state::AgentApplicableState; - use crate::agent_request::AgentRequest; - use crate::continuous_batch_arbiter_context::ContinuousBatchArbiterContext; - use crate::continuous_batch_preparation_request::ContinuousBatchPreparationRequest; - use crate::model_metadata_holder::ModelMetadataHolder; - use crate::slot_guard::SlotGuard; - - fn arbiter_context() -> ContinuousBatchArbiterContext { - ContinuousBatchArbiterContext { - agent_name: Some("agent".to_owned()), - model_metadata_holder: Arc::new(ModelMetadataHolder::default()), - slot_aggregated_status: Arc::new(SlotAggregatedStatus::new(1)), - } - } - - async fn applied( - agent_applicable_state: AgentApplicableState, - arbiter_context: &ContinuousBatchArbiterContext, - ) -> ContinuousBatchArbiterState { - ContinuousBatchArbiterState::Idle - .apply( - &CancellationToken::new(), - agent_applicable_state, - arbiter_context, - ) - .await - } - - #[tokio::test] - async fn an_idle_arbiter_rejects_a_request_because_no_model_is_loaded() { - let arbiter_context = arbiter_context(); - let (generated_tokens_tx, mut generated_tokens_rx) = mpsc::unbounded_channel(); - let (_generate_tokens_stop_tx, generate_tokens_stop_rx) = mpsc::unbounded_channel(); - - ContinuousBatchArbiterState::Idle.forward( - arbiter_context.agent_name.as_deref(), - ContinuousBatchPreparationRequest::ContinueFromRawPrompt(AgentRequest { - params: ContinueFromRawPromptParams { - grammar: None, - max_tokens: NonZeroU32::MIN, - raw_prompt: "Hello".to_owned(), - }, - response_tx: generated_tokens_tx, - slot_guard: SlotGuard::new(arbiter_context.slot_aggregated_status.clone()), - stop_rx: generate_tokens_stop_rx, - }), - ); - - assert_eq!( - generated_tokens_rx.recv().await, - Some(GeneratedTokenResult::ModelNotLoaded( - "agent: no model is loaded".to_owned() - )) - ); - assert_eq!( - arbiter_context - .slot_aggregated_status - .slots_processing_count(), - 0 - ); - } - - #[tokio::test] - async fn applying_a_state_without_a_model_stays_idle_and_reports_applied() { - let arbiter_context = arbiter_context(); - - let arbiter_state = applied(AgentApplicableState::default(), &arbiter_context).await; - - assert_eq!( - discriminant(&arbiter_state), - discriminant(&ContinuousBatchArbiterState::Idle) - ); - assert_eq!( - arbiter_context - .slot_aggregated_status - .get_state_application_status(), - AgentStateApplicationStatus::Applied - ); - } -} diff --git a/paddler_agent/src/continuous_batch_embedding_processor.rs b/paddler_agent/src/continuous_batch_embedding_processor.rs deleted file mode 100644 index ff34ca78e..000000000 --- a/paddler_agent/src/continuous_batch_embedding_processor.rs +++ /dev/null @@ -1,155 +0,0 @@ -use llama_cpp_bindings::context::LlamaContext; -use llama_cpp_bindings::llama_batch::LlamaBatch; -use log::warn; -use tokio::sync::mpsc; - -use paddler_messaging::embedding::Embedding; -use paddler_messaging::embedding_normalization_method::EmbeddingNormalizationMethod; -use paddler_messaging::embedding_result::EmbeddingResult; - -use crate::continuous_batch_scheduler_context::ContinuousBatchSchedulerContext; -use crate::embedding_batch_rejection::EmbeddingBatchRejection; -use crate::embedding_input_tokenized::EmbeddingInputTokenized; -use crate::normalization::normalize_embedding::normalize_embedding; -use crate::plan_embedding_batches::plan_embedding_batches; -use crate::prepared_embedding_batch_request::PreparedEmbeddingBatchRequest; -use crate::receives_stop_request::ReceivesStopRequest as _; - -pub struct ContinuousBatchEmbeddingProcessor<'context> { - batch: &'context mut LlamaBatch<'static>, - llama_context: &'context mut LlamaContext<'static>, - scheduler_context: &'context ContinuousBatchSchedulerContext, -} - -impl<'context> ContinuousBatchEmbeddingProcessor<'context> { - pub const fn new( - batch: &'context mut LlamaBatch<'static>, - llama_context: &'context mut LlamaContext<'static>, - scheduler_context: &'context ContinuousBatchSchedulerContext, - ) -> Self { - Self { - batch, - llama_context, - scheduler_context, - } - } - - pub fn process_embedding_batch( - &mut self, - PreparedEmbeddingBatchRequest { - mut generate_embedding_stop_rx, - generated_embedding_tx, - inputs, - normalization_method, - oversized_documents, - slot_guard, - }: PreparedEmbeddingBatchRequest, - ) -> Result<(), EmbeddingBatchRejection> { - let _slot_guard = slot_guard; - - for oversized_document in oversized_documents { - warn!( - "{:?}: skipped embedding document {:?}: {} tokens exceeds n_batch {}", - self.scheduler_context.agent_name, - oversized_document.source_document_id, - oversized_document.document_tokens, - oversized_document.n_batch, - ); - - generated_embedding_tx - .send(EmbeddingResult::DocumentExceedsBatchSize( - oversized_document, - )) - .map_err(EmbeddingBatchRejection::ClientDisconnected)?; - } - - let n_batch = self - .scheduler_context - .inference_parameters - .n_batch - .tokens_usize(); - let max_sequences_per_batch = self.scheduler_context.desired_slots_total; - - let token_counts: Vec = inputs.iter().map(|input| input.tokens.len()).collect(); - let planned_batches = - plan_embedding_batches(&token_counts, n_batch, max_sequences_per_batch); - self.batch.clear(); - - let mut embeddings_emitted: usize = 0; - - for planned_batch in planned_batches { - if generate_embedding_stop_rx.is_stop_requested() { - break; - } - - let batch_inputs: Vec<&EmbeddingInputTokenized> = - inputs[planned_batch].iter().collect(); - - for (sequence_index, input) in batch_inputs.iter().enumerate() { - self.batch - .add_sequence( - &input.tokens, - i32::try_from(sequence_index) - .map_err(EmbeddingBatchRejection::SequenceIndexOutOfRange)?, - true, - ) - .map_err(EmbeddingBatchRejection::BatchAssemblyFailed)?; - } - - self.embedding_batch_decode( - &batch_inputs, - &generated_embedding_tx, - &normalization_method, - )?; - - embeddings_emitted += batch_inputs.len(); - } - - generated_embedding_tx - .send(if embeddings_emitted == 0 { - EmbeddingResult::NoEmbeddingsProduced - } else { - EmbeddingResult::Done - }) - .map_err(EmbeddingBatchRejection::ClientDisconnected) - } - - fn embedding_batch_decode( - &mut self, - current_batch_embeddings: &[&EmbeddingInputTokenized], - generated_embedding_tx: &mpsc::UnboundedSender, - normalization_method: &EmbeddingNormalizationMethod, - ) -> Result<(), EmbeddingBatchRejection> { - self.llama_context.clear_kv_cache(); - self.llama_context - .decode(self.batch) - .map_err(EmbeddingBatchRejection::DecodeFailed)?; - - for (index, embedding_input_tokenized) in current_batch_embeddings.iter().enumerate() { - let embedding = self - .llama_context - .embeddings_seq_ith( - i32::try_from(index) - .map_err(EmbeddingBatchRejection::SequenceIndexOutOfRange)?, - ) - .map_err(EmbeddingBatchRejection::EmbeddingsUnavailable)?; - - generated_embedding_tx - .send(EmbeddingResult::Embedding(Embedding { - embedding: normalize_embedding(embedding.to_vec(), normalization_method)?, - normalization_method: normalization_method.clone(), - pooling_type: self - .scheduler_context - .inference_parameters - .pooling_type - .clone(), - source_document_id: embedding_input_tokenized.id.clone(), - })) - .map_err(EmbeddingBatchRejection::ClientDisconnected)?; - } - - self.batch.clear(); - - Ok(()) - } -} diff --git a/paddler_agent/src/continuous_batch_preparation_request.rs b/paddler_agent/src/continuous_batch_preparation_request.rs deleted file mode 100644 index 7dcdb4853..000000000 --- a/paddler_agent/src/continuous_batch_preparation_request.rs +++ /dev/null @@ -1,142 +0,0 @@ -use paddler_messaging::embedding_result::EmbeddingResult; -use paddler_messaging::generated_token_result::GeneratedTokenResult; -use paddler_messaging::request_params::continue_from_conversation_history_params::ContinueFromConversationHistoryParams; -use paddler_messaging::request_params::continue_from_conversation_history_params::tool::tool_params::function_call::parameters_schema::validated_parameters_schema::ValidatedParametersSchema; -use paddler_messaging::request_params::continue_from_raw_prompt_params::ContinueFromRawPromptParams; -use paddler_messaging::request_params::generate_embedding_batch_params::GenerateEmbeddingBatchParams; - -use crate::agent_request::AgentRequest; -use crate::embedding_batch_rejection::EmbeddingBatchRejection; -use crate::generation_request_rejection::GenerationRequestRejection; - -pub enum ContinuousBatchPreparationRequest { - ContinueFromConversationHistory( - AgentRequest< - ContinueFromConversationHistoryParams, - GeneratedTokenResult, - >, - ), - ContinueFromRawPrompt(AgentRequest), - GenerateEmbeddingBatch(AgentRequest), -} - -impl ContinuousBatchPreparationRequest { - pub fn reject_because_no_model_is_loaded(self, agent_name: Option<&str>) { - match self { - Self::ContinueFromConversationHistory(AgentRequest { response_tx, .. }) - | Self::ContinueFromRawPrompt(AgentRequest { response_tx, .. }) => { - GenerationRequestRejection::ModelNotLoaded.report(agent_name, &response_tx); - } - Self::GenerateEmbeddingBatch(AgentRequest { response_tx, .. }) => { - EmbeddingBatchRejection::ModelNotLoaded.report(agent_name, &response_tx); - } - } - } -} - -impl - From< - AgentRequest< - ContinueFromConversationHistoryParams, - GeneratedTokenResult, - >, - > for ContinuousBatchPreparationRequest -{ - fn from( - request: AgentRequest< - ContinueFromConversationHistoryParams, - GeneratedTokenResult, - >, - ) -> Self { - Self::ContinueFromConversationHistory(request) - } -} - -impl From> - for ContinuousBatchPreparationRequest -{ - fn from(request: AgentRequest) -> Self { - Self::ContinueFromRawPrompt(request) - } -} - -impl From> - for ContinuousBatchPreparationRequest -{ - fn from(request: AgentRequest) -> Self { - Self::GenerateEmbeddingBatch(request) - } -} - -#[cfg(test)] -mod tests { - use std::num::NonZeroU32; - use std::sync::Arc; - - use tokio::sync::mpsc; - - use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; - use paddler_messaging::conversation_history::ConversationHistory; - use paddler_messaging::embedding_normalization_method::EmbeddingNormalizationMethod; - use paddler_messaging::embedding_result::EmbeddingResult; - use paddler_messaging::generated_token_result::GeneratedTokenResult; - use paddler_messaging::request_params::continue_from_conversation_history_params::ContinueFromConversationHistoryParams; - use paddler_messaging::request_params::generate_embedding_batch_params::GenerateEmbeddingBatchParams; - - use super::ContinuousBatchPreparationRequest; - use crate::agent_request::AgentRequest; - use crate::slot_guard::SlotGuard; - - #[test] - fn rejects_a_conversation_because_no_model_is_loaded() { - let (generated_tokens_tx, mut generated_tokens_rx) = mpsc::unbounded_channel(); - let (_generate_tokens_stop_tx, generate_tokens_stop_rx) = mpsc::unbounded_channel(); - - ContinuousBatchPreparationRequest::ContinueFromConversationHistory(AgentRequest { - params: ContinueFromConversationHistoryParams { - add_generation_prompt: true, - conversation_history: ConversationHistory::new(Vec::new()), - enable_thinking: false, - grammar: None, - max_tokens: NonZeroU32::MIN, - parse_tool_calls: false, - tools: Vec::new(), - }, - response_tx: generated_tokens_tx, - slot_guard: SlotGuard::new(Arc::new(SlotAggregatedStatus::new(1))), - stop_rx: generate_tokens_stop_rx, - }) - .reject_because_no_model_is_loaded(Some("agent")); - - assert_eq!( - generated_tokens_rx.try_recv(), - Ok(GeneratedTokenResult::ModelNotLoaded( - "agent: no model is loaded".to_owned() - )) - ); - } - - #[test] - fn rejects_an_embedding_batch_because_no_model_is_loaded() { - let (generated_embedding_tx, mut generated_embedding_rx) = mpsc::unbounded_channel(); - let (_generate_embedding_stop_tx, generate_embedding_stop_rx) = mpsc::unbounded_channel(); - - ContinuousBatchPreparationRequest::GenerateEmbeddingBatch(AgentRequest { - params: GenerateEmbeddingBatchParams { - input_batch: Vec::new(), - normalization_method: EmbeddingNormalizationMethod::None, - }, - response_tx: generated_embedding_tx, - slot_guard: SlotGuard::new(Arc::new(SlotAggregatedStatus::new(1))), - stop_rx: generate_embedding_stop_rx, - }) - .reject_because_no_model_is_loaded(Some("agent")); - - assert_eq!( - generated_embedding_rx.try_recv(), - Ok(EmbeddingResult::ModelNotLoaded( - "agent: no model is loaded".to_owned() - )) - ); - } -} diff --git a/paddler_agent/src/continuous_batch_request_phase.rs b/paddler_agent/src/continuous_batch_request_phase.rs deleted file mode 100644 index 5dbcf3766..000000000 --- a/paddler_agent/src/continuous_batch_request_phase.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::continuous_batch_generation_step::ContinuousBatchGenerationStep; -use crate::continuous_batch_terminal_outcome::ContinuousBatchTerminalOutcome; -use crate::multimodal_prompt_ingestion::MultimodalPromptIngestion; - -#[derive(Debug)] -pub enum ContinuousBatchRequestPhase { - IngestingText, - IngestingMultimodal(MultimodalPromptIngestion), - Generating(ContinuousBatchGenerationStep), - Completed(ContinuousBatchTerminalOutcome), -} diff --git a/paddler_agent/src/continuous_batch_request_preparer.rs b/paddler_agent/src/continuous_batch_request_preparer.rs deleted file mode 100644 index 6d23c7adb..000000000 --- a/paddler_agent/src/continuous_batch_request_preparer.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::sync::Arc; -use std::sync::mpsc::Sender; - -use llama_cpp_bindings::llama_backend::LlamaBackend; -use tokio_util::task::TaskTracker; - -use paddler_messaging::embedding_result::EmbeddingResult; -use paddler_messaging::generated_token_result::GeneratedTokenResult; -use paddler_messaging::request_params::generate_embedding_batch_params::GenerateEmbeddingBatchParams; - -use crate::agent_request::AgentRequest; -use crate::continuous_batch_preparation_request::ContinuousBatchPreparationRequest; -use crate::continuous_batch_scheduler_command::ContinuousBatchSchedulerCommand; -use crate::embedding_batch_preparer::EmbeddingBatchPreparer; -use crate::forward_scheduler_command::forward_scheduler_command; -use crate::generation_request_preparer::GenerationRequestPreparer; -use crate::generation_request_rejection::GenerationRequestRejection; -use crate::prepared_generation_request::PreparedGenerationRequest; - -pub struct ContinuousBatchRequestPreparer { - pub agent_name: Option, - pub embedding_batch_preparer: EmbeddingBatchPreparer, - pub generation_request_preparer: GenerationRequestPreparer, - pub llama_backend: Arc, - pub preparation_tasks: TaskTracker, - pub scheduler_command_tx: Sender, -} - -impl ContinuousBatchRequestPreparer { - pub fn prepare(self: &Arc, request: ContinuousBatchPreparationRequest) { - let preparer = self.clone(); - - self.preparation_tasks - .spawn_blocking(move || match request { - ContinuousBatchPreparationRequest::ContinueFromConversationHistory(request) => { - preparer.accept_generation( - request, - GenerationRequestPreparer::prepare_conversation_history, - ); - } - ContinuousBatchPreparationRequest::ContinueFromRawPrompt(request) => { - preparer - .accept_generation(request, GenerationRequestPreparer::prepare_raw_prompt); - } - ContinuousBatchPreparationRequest::GenerateEmbeddingBatch(request) => { - preparer.accept_embedding_batch(request); - } - }); - } - - pub async fn shut_down_scheduler_after_pending_preparations(&self) { - self.preparation_tasks.close(); - self.preparation_tasks.wait().await; - self.forward(ContinuousBatchSchedulerCommand::Shutdown); - } - - fn forward(&self, command: ContinuousBatchSchedulerCommand) { - forward_scheduler_command( - &self.scheduler_command_tx, - self.agent_name.as_deref(), - command, - ); - } - - fn accept_generation( - &self, - request: AgentRequest, - prepare: fn( - &GenerationRequestPreparer, - AgentRequest, - ) -> Result, - ) { - let generated_tokens_tx = request.response_tx.clone(); - - match prepare(&self.generation_request_preparer, request) { - Ok(prepared) => self.forward(ContinuousBatchSchedulerCommand::Generate(Box::new( - prepared, - ))), - Err(rejection) => rejection.report(self.agent_name.as_deref(), &generated_tokens_tx), - } - } - - fn accept_embedding_batch( - &self, - request: AgentRequest, - ) { - let generated_embedding_tx = request.response_tx.clone(); - - match self.embedding_batch_preparer.prepare(request) { - Ok(prepared) => { - self.forward(ContinuousBatchSchedulerCommand::GenerateEmbeddingBatch( - prepared, - )); - } - Err(rejection) => { - rejection.report(self.agent_name.as_deref(), &generated_embedding_tx); - } - } - } -} diff --git a/paddler_agent/src/continuous_batch_scheduler/mod.rs b/paddler_agent/src/continuous_batch_scheduler/mod.rs deleted file mode 100644 index c16e21fc6..000000000 --- a/paddler_agent/src/continuous_batch_scheduler/mod.rs +++ /dev/null @@ -1,529 +0,0 @@ -pub mod advance_generating_phase; -pub mod advance_outcome; -pub mod assemble_batch_phase; -pub mod batch_pass; -pub mod classified_token; -pub mod commit_phase; -pub mod completion_check_outcome; -pub mod completion_check_phase; -pub mod contributions; -pub mod decode_failure_phase; -pub mod emit_token_outcome; -pub mod emit_token_phase; -pub mod generating_contribution; -pub mod ingesting_contribution; -pub mod sequence_ordered_insertion_index; - -use std::collections::VecDeque; -use std::mem::transmute; -use std::slice; -use std::sync::mpsc::Receiver; -use std::sync::mpsc::TryRecvError; - -use llama_cpp_bindings::SampledTokenClassifier; -use llama_cpp_bindings::batch_add_error::BatchAddError; -use llama_cpp_bindings::context::LlamaContext; -use llama_cpp_bindings::error::DecodeError; -use llama_cpp_bindings::ingest_outcome::IngestOutcome; -use llama_cpp_bindings::llama_batch::LlamaBatch; -use llama_cpp_bindings::token::data_array::LlamaTokenDataArray; -use log::debug; -use log::error; -use log::info; -use tokio_util::sync::CancellationToken; - -use paddler_messaging::embedding_result::EmbeddingResult; -use paddler_messaging::generated_token_result::GeneratedTokenResult; -use paddler_messaging::generation_finish::GenerationFinish; -use paddler_messaging::generation_summary::GenerationSummary; - -use self::advance_generating_phase::AdvanceGeneratingPhase; -use self::assemble_batch_phase::AssembleBatchPhase; -use self::batch_pass::BatchPass; -use self::commit_phase::CommitPhase; -use self::decode_failure_phase::DecodeFailurePhase; -use self::sequence_ordered_insertion_index::sequence_ordered_insertion_index; -use crate::continuous_batch_active_request::ContinuousBatchActiveRequest; -use crate::continuous_batch_embedding_processor::ContinuousBatchEmbeddingProcessor; -use crate::continuous_batch_request_phase::ContinuousBatchRequestPhase; -use crate::continuous_batch_request_state::ContinuousBatchRequestState; -use crate::continuous_batch_scheduler_command::ContinuousBatchSchedulerCommand; -use crate::continuous_batch_scheduler_context::ContinuousBatchSchedulerContext; -use crate::continuous_batch_scheduler_params::ContinuousBatchSchedulerParams; -use crate::generation_request_rejection::GenerationRequestRejection; -use crate::multimodal_ingestion_progress::MultimodalIngestionProgress; -use crate::prepared_embedding_batch_request::PreparedEmbeddingBatchRequest; -use crate::prepared_generation_request::PreparedGenerationRequest; -use crate::prepared_prompt::PreparedPrompt; -use crate::receives_stop_request::ReceivesStopRequest as _; -use crate::send_result_or_warn::send_result_or_warn; -use crate::sequence_id_guard::SequenceIdGuard; -use crate::sequence_id_pool::SequenceIdPool; -use crate::token_classification::TokenClassification; - -pub struct ContinuousBatchScheduler { - active_requests: Vec, - agent_shutdown: CancellationToken, - batch: LlamaBatch<'static>, - candidates: LlamaTokenDataArray, - command_rx: Receiver, - ingest_outcomes: Vec, - llama_context: LlamaContext<'static>, - pending_embedding_requests: VecDeque, - scheduler_context: ContinuousBatchSchedulerContext, - sequence_id_pool: SequenceIdPool, - shutdown_requested: bool, -} - -impl ContinuousBatchScheduler { - #[must_use] - #[expect( - unsafe_code, - reason = "required for FFI lifetime extension with llama.cpp" - )] - pub fn new( - ContinuousBatchSchedulerParams { - agent_shutdown, - batch, - command_rx, - llama_context, - scheduler_context, - }: ContinuousBatchSchedulerParams, - ) -> Self { - let llama_context = - unsafe { transmute::, LlamaContext<'static>>(llama_context) }; - let sequence_id_pool = SequenceIdPool::new(scheduler_context.desired_slots_total); - - Self { - active_requests: Vec::new(), - agent_shutdown, - batch, - candidates: LlamaTokenDataArray::new(Vec::new(), false), - command_rx, - ingest_outcomes: Vec::new(), - llama_context, - pending_embedding_requests: VecDeque::new(), - scheduler_context, - sequence_id_pool, - shutdown_requested: false, - } - } - - pub fn run(&mut self) { - info!( - "{:?}: continuous batch scheduler started", - self.scheduler_context.agent_name - ); - - while !self.agent_shutdown.is_cancelled() { - self.check_stop_signals(); - self.remove_completed_requests(); - self.accept_new_commands(); - - let has_active_requests = self.has_active_requests(); - - self.try_process_embedding_request(has_active_requests); - - if has_active_requests { - if let Err(batch_add_error) = self.execute_one_iteration() { - self.fail_unfinished_requests(GenerationRequestRejection::BatchAssemblyFailed( - batch_add_error, - )); - } - } else if self.pending_embedding_requests.is_empty() { - if self.shutdown_requested { - break; - } - - self.wait_for_next_command(); - } - } - - while !self.active_requests.is_empty() { - self.cleanup_completed_request(0); - } - - self.llama_context.synchronize(); - self.llama_context.detach_threadpool(); - - info!( - "{:?}: continuous batch scheduler stopped", - self.scheduler_context.agent_name - ); - } - - fn wait_for_next_command(&mut self) { - if let Ok(command) = self.command_rx.recv() { - self.process_command(command); - } else { - info!( - "{:?}: command channel closed, shutting down scheduler", - self.scheduler_context.agent_name - ); - self.shutdown_requested = true; - } - } - - fn accept_new_commands(&mut self) { - loop { - match self.command_rx.try_recv() { - Ok(command) => self.process_command(command), - Err(TryRecvError::Empty) => break, - Err(TryRecvError::Disconnected) => { - self.shutdown_requested = true; - - break; - } - } - } - } - - fn process_command(&mut self, command: ContinuousBatchSchedulerCommand) { - match command { - ContinuousBatchSchedulerCommand::Generate(request) => { - self.accept_generation_request(*request); - } - ContinuousBatchSchedulerCommand::GenerateEmbeddingBatch(request) => { - self.pending_embedding_requests.push_back(request); - } - ContinuousBatchSchedulerCommand::Shutdown => { - self.shutdown_requested = true; - } - } - } - - #[expect( - unsafe_code, - reason = "the SchedulerContext owns the LlamaModel for the lifetime of the active_requests vec — same pattern as LlamaContext<'static> above" - )] - fn build_token_classifier_for_active_request( - &self, - TokenClassification { - bare_json_tool_calls, - streaming_markers, - }: TokenClassification, - ) -> SampledTokenClassifier<'static> { - let classifier = SampledTokenClassifier::new( - &self.scheduler_context.model, - streaming_markers, - bare_json_tool_calls, - ); - - unsafe { - transmute::, SampledTokenClassifier<'static>>(classifier) - } - } - - fn accept_generation_request(&mut self, request: PreparedGenerationRequest) { - let generated_tokens_tx = request.generated_tokens_tx.clone(); - - match self.admit_generation_request(request) { - Ok(active_request) => { - debug!( - "{:?}: accepted generation request on sequence {}", - self.scheduler_context.agent_name, - active_request.sequence_id_guard.sequence_id() - ); - - let insertion_index = sequence_ordered_insertion_index( - self.active_requests - .iter() - .map(|request| request.sequence_id_guard.sequence_id()), - active_request.sequence_id_guard.sequence_id(), - ); - - self.active_requests.insert(insertion_index, active_request); - } - Err(rejection) => { - rejection.report( - self.scheduler_context.agent_name.as_deref(), - &generated_tokens_tx, - ); - } - } - } - - fn admit_generation_request( - &mut self, - PreparedGenerationRequest { - generate_tokens_stop_rx, - generated_tokens_tx, - max_tokens, - prompt, - slot_guard, - token_classification, - token_sampling, - tool_call_handling, - }: PreparedGenerationRequest, - ) -> Result { - let sequence_id_guard = SequenceIdGuard::acquire(&self.sequence_id_pool) - .ok_or(GenerationRequestRejection::NoSequenceSlotAvailable)?; - let mut token_classifier = - self.build_token_classifier_for_active_request(token_classification); - - self.clear_kv_cache_for_sequence(sequence_id_guard.sequence_id())?; - - let mut state = ContinuousBatchRequestState { - current_token_position: 0, - last_outcome_section: token_classifier.current_section(), - max_tokens, - phase: ContinuousBatchRequestPhase::IngestingText, - prompt_tokens: Vec::new(), - prompt_tokens_ingested: 0, - sampled_tokens: 0, - }; - - match prompt { - PreparedPrompt::TextTokens(prompt_tokens) => { - token_classifier.record_prompt_tokens(prompt_tokens.len() as u64); - token_classifier.ingest_prompt_tokens(&prompt_tokens); - state.prompt_tokens = prompt_tokens; - } - PreparedPrompt::Multimodal(ingestion) => { - state.phase = ContinuousBatchRequestPhase::IngestingMultimodal(ingestion); - } - } - - state.last_outcome_section = token_classifier.current_section(); - - Ok(ContinuousBatchActiveRequest { - state, - token_classifier, - token_sampling, - generated_tokens_tx, - generate_tokens_stop_rx, - sequence_id_guard, - slot_guard, - tool_call_handling, - }) - } - - fn ingest_next_multimodal_chunks(&mut self) { - for active_request in &mut self.active_requests { - let ContinuousBatchRequestPhase::IngestingMultimodal(ingestion) = - &mut active_request.state.phase - else { - continue; - }; - - match ingestion.ingest_next_chunk( - &mut active_request.token_classifier, - &self.llama_context, - active_request.sequence_id_guard.sequence_id(), - active_request.state.current_token_position, - ) { - Ok(MultimodalIngestionProgress::ChunksRemain { next_position }) => { - active_request.state.current_token_position = next_position; - } - Ok(MultimodalIngestionProgress::PromptIngested { next_position }) => { - active_request - .state - .begin_generating_after_multimodal_prompt( - next_position, - active_request.token_classifier.current_section(), - ); - self.llama_context.mark_logits_initialized(-1); - - AdvanceGeneratingPhase { - candidates: &mut self.candidates, - ingest_outcomes: &mut self.ingest_outcomes, - llama_context: &self.llama_context, - scheduler_context: &self.scheduler_context, - } - .run(slice::from_mut(active_request)); - } - Err(ingestion_error) => { - active_request.complete_with_outcome( - GenerationRequestRejection::MultimodalIngestionFailed(ingestion_error) - .into_generated_token_result( - self.scheduler_context.agent_name.as_deref(), - ), - ); - } - } - } - } - - fn check_stop_signals(&mut self) { - for active_request in &mut self.active_requests { - if matches!( - active_request.state.phase, - ContinuousBatchRequestPhase::Completed(_) - ) { - continue; - } - - if active_request.generate_tokens_stop_rx.is_stop_requested() { - let summary = GenerationSummary { - finish: GenerationFinish::StopRequested, - usage: *active_request.token_classifier.usage(), - }; - - active_request.complete_with_outcome(GeneratedTokenResult::Done(summary)); - } - } - } - - fn try_process_embedding_request(&mut self, has_active_requests: bool) { - let Some(request) = self.pending_embedding_requests.pop_front() else { - return; - }; - - if has_active_requests { - send_result_or_warn( - self.scheduler_context.agent_name.as_deref(), - &request.generated_embedding_tx, - EmbeddingResult::EmbeddingRejectedDueToActiveTokenGeneration, - ); - - return; - } - - let generated_embedding_tx = request.generated_embedding_tx.clone(); - let mut processor = ContinuousBatchEmbeddingProcessor::new( - &mut self.batch, - &mut self.llama_context, - &self.scheduler_context, - ); - - if let Err(rejection) = processor.process_embedding_batch(request) { - rejection.report( - self.scheduler_context.agent_name.as_deref(), - &generated_embedding_tx, - ); - } - } - - fn has_active_requests(&self) -> bool { - self.active_requests.iter().any(|request| { - !matches!( - request.state.phase, - ContinuousBatchRequestPhase::Completed(_) - ) - }) - } - - fn fail_unfinished_requests(&mut self, rejection: GenerationRequestRejection) { - let outcome = - rejection.into_generated_token_result(self.scheduler_context.agent_name.as_deref()); - - for active_request in &mut self.active_requests { - active_request.complete_with_outcome(outcome.clone()); - } - } - - fn execute_one_iteration(&mut self) -> Result<(), BatchAddError> { - self.advance_generating_requests(); - self.ingest_next_multimodal_chunks(); - - let assemble_phase = AssembleBatchPhase { - n_batch: self - .scheduler_context - .inference_parameters - .n_batch - .tokens_usize(), - }; - - let mut pass = BatchPass::new(&mut self.batch); - - assemble_phase.run(&mut pass, &mut self.active_requests)?; - - if pass.is_empty() { - return Ok(()); - } - - debug!( - "{:?}: decoding batch with {} tokens for {} active requests", - self.scheduler_context.agent_name, - pass.batch.n_tokens(), - self.active_requests.len() - ); - - match self.llama_context.decode(pass.batch) { - Ok(()) => { - CommitPhase { - requests: &mut self.active_requests, - } - .run(pass); - } - Err(DecodeError::Aborted) => {} - Err(decode_error) => { - let description = format!( - "{:?}: llama.cpp rejected the batch: {decode_error}", - self.scheduler_context.agent_name - ); - - error!("{description}"); - DecodeFailurePhase { - requests: &mut self.active_requests, - } - .run(pass, &description); - } - } - - Ok(()) - } - - fn advance_generating_requests(&mut self) { - AdvanceGeneratingPhase { - candidates: &mut self.candidates, - ingest_outcomes: &mut self.ingest_outcomes, - llama_context: &self.llama_context, - scheduler_context: &self.scheduler_context, - } - .run(&mut self.active_requests); - } - - fn remove_completed_requests(&mut self) { - let mut removal_index = 0; - - while removal_index < self.active_requests.len() { - if matches!( - self.active_requests[removal_index].state.phase, - ContinuousBatchRequestPhase::Completed(_) - ) { - self.cleanup_completed_request(removal_index); - } else { - removal_index += 1; - } - } - } - - fn clear_kv_cache_for_sequence( - &mut self, - sequence_id: i32, - ) -> Result<(), GenerationRequestRejection> { - let kv_cache_sequence_id = u32::try_from(sequence_id).map_err(|source| { - GenerationRequestRejection::SequenceIdOutOfRange { - sequence_id, - source, - } - })?; - - self.llama_context - .clear_kv_cache_seq(Some(kv_cache_sequence_id), None, None) - .map_err(GenerationRequestRejection::KvCacheClearFailed) - } - - fn cleanup_completed_request(&mut self, index: usize) { - let removed_request = self.active_requests.remove(index); - let sequence_id = removed_request.sequence_id_guard.sequence_id(); - let usage = *removed_request.token_classifier.usage(); - let terminal_delivery = removed_request.into_terminal_delivery(); - - if let Err(rejection) = self.clear_kv_cache_for_sequence(sequence_id) { - error!( - "{:?}: {rejection}; the next request admitted to sequence {sequence_id} clears it again", - self.scheduler_context.agent_name - ); - } - - debug!( - "{:?}: cleaned up sequence {sequence_id} ({} completion tokens generated)", - self.scheduler_context.agent_name, - usage.completion_tokens(), - ); - - terminal_delivery.deliver(self.scheduler_context.agent_name.as_deref()); - } -} diff --git a/paddler_agent/src/continuous_batch_scheduler/sequence_ordered_insertion_index.rs b/paddler_agent/src/continuous_batch_scheduler/sequence_ordered_insertion_index.rs deleted file mode 100644 index 3fb360d52..000000000 --- a/paddler_agent/src/continuous_batch_scheduler/sequence_ordered_insertion_index.rs +++ /dev/null @@ -1,29 +0,0 @@ -pub fn sequence_ordered_insertion_index( - active_sequence_ids: impl IntoIterator, - sequence_id: i32, -) -> usize { - active_sequence_ids - .into_iter() - .take_while(|active_sequence_id| *active_sequence_id < sequence_id) - .count() -} - -#[cfg(test)] -mod tests { - use super::sequence_ordered_insertion_index; - - #[test] - fn places_a_sequence_before_every_higher_sequence_id() { - assert_eq!(sequence_ordered_insertion_index([1, 4, 6], 5), 2); - } - - #[test] - fn places_the_lowest_sequence_id_first() { - assert_eq!(sequence_ordered_insertion_index([1, 4, 6], 0), 0); - } - - #[test] - fn places_the_highest_sequence_id_last() { - assert_eq!(sequence_ordered_insertion_index([1, 4, 6], 7), 3); - } -} diff --git a/paddler_agent/src/continuous_batch_scheduler_command.rs b/paddler_agent/src/continuous_batch_scheduler_command.rs deleted file mode 100644 index b806ffa0c..000000000 --- a/paddler_agent/src/continuous_batch_scheduler_command.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::prepared_embedding_batch_request::PreparedEmbeddingBatchRequest; -use crate::prepared_generation_request::PreparedGenerationRequest; - -pub enum ContinuousBatchSchedulerCommand { - Generate(Box), - GenerateEmbeddingBatch(PreparedEmbeddingBatchRequest), - Shutdown, -} diff --git a/paddler_agent/src/continuous_batch_scheduler_context.rs b/paddler_agent/src/continuous_batch_scheduler_context.rs deleted file mode 100644 index 008d9f55a..000000000 --- a/paddler_agent/src/continuous_batch_scheduler_context.rs +++ /dev/null @@ -1,12 +0,0 @@ -use std::sync::Arc; - -use llama_cpp_bindings::model::LlamaModel; - -use paddler_inference_parameters::inference_parameters::InferenceParameters; - -pub struct ContinuousBatchSchedulerContext { - pub agent_name: Option, - pub desired_slots_total: u16, - pub inference_parameters: InferenceParameters, - pub model: Arc, -} diff --git a/paddler_agent/src/continuous_batch_scheduler_params.rs b/paddler_agent/src/continuous_batch_scheduler_params.rs deleted file mode 100644 index 1e5d296e1..000000000 --- a/paddler_agent/src/continuous_batch_scheduler_params.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::sync::mpsc::Receiver; - -use llama_cpp_bindings::context::LlamaContext; -use llama_cpp_bindings::llama_batch::LlamaBatch; -use tokio_util::sync::CancellationToken; - -use crate::continuous_batch_scheduler_command::ContinuousBatchSchedulerCommand; -use crate::continuous_batch_scheduler_context::ContinuousBatchSchedulerContext; - -pub struct ContinuousBatchSchedulerParams<'model> { - pub agent_shutdown: CancellationToken, - pub batch: LlamaBatch<'static>, - pub command_rx: Receiver, - pub llama_context: LlamaContext<'model>, - pub scheduler_context: ContinuousBatchSchedulerContext, -} diff --git a/paddler_agent/src/desired_model_file.rs b/paddler_agent/src/desired_model_file.rs new file mode 100644 index 000000000..70d23abd5 --- /dev/null +++ b/paddler_agent/src/desired_model_file.rs @@ -0,0 +1,41 @@ +use std::path::Path; + +use paddler_agent_status::agent_issue_fix::AgentIssueFix; +use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; +use paddler_messaging::agent_issue::AgentIssue; +use paddler_messaging::agent_issue_params::model_path::ModelPath; + +#[derive(Clone, Copy)] +pub enum DesiredModelFile { + Model, + MultimodalProjection, + PointerHead, +} + +impl DesiredModelFile { + pub fn report_found(self, path: &Path, slot_aggregated_status: &SlotAggregatedStatus) { + match self { + Self::Model => { + slot_aggregated_status + .register_fix(&AgentIssueFix::ModelFileExists(ModelPath::from(path))); + } + Self::MultimodalProjection => {} + Self::PointerHead => { + slot_aggregated_status + .register_fix(&AgentIssueFix::PointerHeadFileExists(ModelPath::from(path))); + } + } + } + + pub fn report_missing(self, path: &Path, slot_aggregated_status: &SlotAggregatedStatus) { + let model_path = ModelPath::from(path); + + slot_aggregated_status.register_desired_state_issue(match self { + Self::Model => AgentIssue::ModelFileDoesNotExist(model_path), + Self::MultimodalProjection => { + AgentIssue::MultimodalProjectionCannotBeLoaded(model_path) + } + Self::PointerHead => AgentIssue::PointerHeadCannotBeLoaded(model_path), + }); + } +} diff --git a/paddler_agent/src/desired_state_reconciler.rs b/paddler_agent/src/desired_state_reconciler.rs index 3952aa2ee..9b734b58e 100644 --- a/paddler_agent/src/desired_state_reconciler.rs +++ b/paddler_agent/src/desired_state_reconciler.rs @@ -3,17 +3,17 @@ use std::sync::Arc; use log::error; use tokio_util::sync::CancellationToken; -use paddler_agent_status::agent_issue_fix::AgentIssueFix; use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; use paddler_messaging::agent_desired_state::AgentDesiredState; +use paddler_model_source::desired_model_resolver::DesiredModelResolver; -use crate::agent_applicable_state_holder::AgentApplicableStateHolder; use crate::agent_desired_state_conversion::AgentDesiredStateConversion; use crate::agent_desired_state_converter::AgentDesiredStateConverter; use crate::desired_state_reconciliation::DesiredStateReconciliation; +#[derive(Clone)] pub struct DesiredStateReconciler { - pub agent_applicable_state_holder: Arc, + pub desired_model_resolver: DesiredModelResolver, pub slot_aggregated_status: Arc, } @@ -24,6 +24,7 @@ impl DesiredStateReconciler { agent_desired_state: AgentDesiredState, ) -> DesiredStateReconciliation { let conversion = AgentDesiredStateConverter { + desired_model_resolver: self.desired_model_resolver.clone(), cancellation_token: cancellation_token.clone(), slot_aggregated_status: self.slot_aggregated_status.clone(), } @@ -32,16 +33,7 @@ impl DesiredStateReconciler { match conversion { Ok(AgentDesiredStateConversion::Converted(applicable_state)) => { - let uses_chat_template_override = applicable_state.chat_template_override.is_some(); - - self.agent_applicable_state_holder - .set_agent_applicable_state(applicable_state); - self.slot_aggregated_status - .set_uses_chat_template_override(uses_chat_template_override); - self.slot_aggregated_status - .register_fix(&AgentIssueFix::ModelStateIsReconciled); - - DesiredStateReconciliation::Reconciled + DesiredStateReconciliation::Reconciled(Box::new(applicable_state)) } Ok(AgentDesiredStateConversion::Cancelled) => { DesiredStateReconciliation::Pending(Box::new(agent_desired_state)) @@ -54,3 +46,90 @@ impl DesiredStateReconciler { } } } + +#[cfg(test)] +mod tests { + use std::path::Path; + use std::sync::Arc; + + use tokio_util::sync::CancellationToken; + + use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; + use paddler_messaging::agent_desired_model::AgentDesiredModel; + use paddler_messaging::agent_desired_state::AgentDesiredState; + use paddler_model_source::agent_local_model_path::AgentLocalModelPath; + use paddler_model_source::desired_model_resolver::DesiredModelResolver; + use paddler_model_source::huggingface_default_endpoint::HUGGINGFACE_DEFAULT_ENDPOINT; + use paddler_model_source::huggingface_model_reference::HuggingFaceModelReference; + use paddler_model_source::model_source::ModelSource; + + use super::DesiredStateReconciler; + use crate::desired_state_reconciliation::DesiredStateReconciliation; + + fn desired_state_with_model(model: AgentDesiredModel) -> AgentDesiredState { + AgentDesiredState { + model, + ..AgentDesiredState::default() + } + } + + fn desired_state_reconciler() -> DesiredStateReconciler { + DesiredStateReconciler { + desired_model_resolver: DesiredModelResolver::from_process_env( + HUGGINGFACE_DEFAULT_ENDPOINT.to_owned(), + ) + .unwrap(), + slot_aggregated_status: Arc::new(SlotAggregatedStatus::new(1)), + } + } + + #[tokio::test] + async fn a_missing_local_model_leaves_the_state_unconverted() { + let reconciler = desired_state_reconciler(); + let desired_state = desired_state_with_model( + ModelSource::LocalToAgent( + AgentLocalModelPath::from_absolute_path(Path::new( + "/paddler-nonexistent-model-for-reconciliation.gguf", + )) + .unwrap(), + ) + .into_agent_desired_model(), + ); + + let reconciliation = reconciler + .reconcile(&CancellationToken::new(), desired_state.clone()) + .await; + + assert_eq!( + reconciliation, + DesiredStateReconciliation::Pending(Box::new(desired_state)) + ); + } + + #[tokio::test] + async fn a_model_download_cancelled_mid_conversion_leaves_the_state_unconverted() { + let cancellation_token = CancellationToken::new(); + + cancellation_token.cancel(); + + let reconciler = desired_state_reconciler(); + let desired_state = desired_state_with_model( + ModelSource::HuggingFace(HuggingFaceModelReference { + filename: "model.gguf".to_owned(), + repository_name: "never-downloaded".to_owned(), + repository_owner: "paddler-tests".to_owned(), + revision: "main".to_owned(), + }) + .into_agent_desired_model(), + ); + + let reconciliation = reconciler + .reconcile(&cancellation_token, desired_state.clone()) + .await; + + assert_eq!( + reconciliation, + DesiredStateReconciliation::Pending(Box::new(desired_state)) + ); + } +} diff --git a/paddler_agent/src/desired_state_reconciliation.rs b/paddler_agent/src/desired_state_reconciliation.rs index ee4a9ba6e..7f9056048 100644 --- a/paddler_agent/src/desired_state_reconciliation.rs +++ b/paddler_agent/src/desired_state_reconciliation.rs @@ -1,7 +1,9 @@ use paddler_messaging::agent_desired_state::AgentDesiredState; +use crate::agent_applicable_state::AgentApplicableState; + #[derive(Debug, PartialEq)] pub enum DesiredStateReconciliation { Pending(Box), - Reconciled, + Reconciled(Box), } diff --git a/paddler_agent/src/desired_state_reconciliation_attempt.rs b/paddler_agent/src/desired_state_reconciliation_attempt.rs new file mode 100644 index 000000000..30bf4d67b --- /dev/null +++ b/paddler_agent/src/desired_state_reconciliation_attempt.rs @@ -0,0 +1,209 @@ +use std::future::Future; +use std::future::pending; +use std::pin::Pin; + +use log::error; +use tokio::runtime::Builder; +use tokio_util::sync::CancellationToken; + +use paddler_messaging::agent_desired_state::AgentDesiredState; +use paddler_spawn_resuming_panic::spawn_blocking_resuming_panic::spawn_blocking_resuming_panic; + +use crate::desired_state_reconciler::DesiredStateReconciler; +use crate::desired_state_reconciliation::DesiredStateReconciliation; +use crate::retry_delay::RetryDelay; +use crate::retry_delay_outcome::RetryDelayOutcome; + +pub enum DesiredStateReconciliationAttempt { + Idle, + Reconciling { + cancellation_token: CancellationToken, + reconciliation: Pin + Send>>, + }, +} + +impl DesiredStateReconciliationAttempt { + #[must_use] + pub fn start( + desired_state_reconciler: DesiredStateReconciler, + agent_desired_state: AgentDesiredState, + agent_shutdown: &CancellationToken, + retry_delay: RetryDelay, + ) -> Self { + let cancellation_token = agent_shutdown.child_token(); + let reconciliation_cancellation_token = cancellation_token.clone(); + + Self::Reconciling { + cancellation_token, + reconciliation: Box::pin(async move { + if retry_delay.elapse(&reconciliation_cancellation_token).await + == RetryDelayOutcome::Cancelled + { + return DesiredStateReconciliation::Pending(Box::new(agent_desired_state)); + } + + match Builder::new_current_thread().enable_all().build() { + Ok(reconciliation_runtime) => { + spawn_blocking_resuming_panic(move || { + reconciliation_runtime.block_on( + desired_state_reconciler.reconcile( + &reconciliation_cancellation_token, + agent_desired_state, + ), + ) + }) + .await + } + Err(runtime_creation_error) => { + error!( + "Unable to create the runtime that reconciles the desired state: {runtime_creation_error}" + ); + + DesiredStateReconciliation::Pending(Box::new(agent_desired_state)) + } + } + }), + } + } + + pub async fn abandon(&mut self) { + if let Self::Reconciling { + cancellation_token, + reconciliation, + } = self + { + cancellation_token.cancel(); + reconciliation.as_mut().await; + } + + *self = Self::Idle; + } + + pub async fn finish(&mut self) -> DesiredStateReconciliation { + match self { + Self::Idle => pending().await, + Self::Reconciling { reconciliation, .. } => { + let desired_state_reconciliation = reconciliation.as_mut().await; + + *self = Self::Idle; + + desired_state_reconciliation + } + } + } +} + +#[cfg(test)] +mod tests { + use std::fs::File; + use std::fs::read_dir; + use std::os::fd::AsRawFd as _; + use std::sync::Arc; + + use nix::sys::resource::Resource; + use nix::sys::resource::getrlimit; + use nix::sys::resource::setrlimit; + use tempfile::TempDir; + use tokio_util::sync::CancellationToken; + + use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; + use paddler_cache_dir::cache_dir::CacheDir; + use paddler_download_manager::download_manager::DownloadManager; + use paddler_local_http_fixture::fixture_response::FixtureResponse; + use paddler_local_http_fixture::local_http_fixture::LocalHttpFixture; + use paddler_messaging::agent_desired_model::AgentDesiredModel; + use paddler_messaging::agent_desired_state::AgentDesiredState; + use paddler_model_source::desired_model_resolver::DesiredModelResolver; + use paddler_model_source::huggingface_cache_location::HuggingFaceCacheLocation; + use paddler_model_source::huggingface_default_endpoint::HUGGINGFACE_DEFAULT_ENDPOINT; + use paddler_model_source::huggingface_hub::HuggingFaceHub; + + use super::DesiredStateReconciliationAttempt; + use crate::desired_state_reconciler::DesiredStateReconciler; + use crate::desired_state_reconciliation::DesiredStateReconciliation; + use crate::retry_delay::RetryDelay; + + #[tokio::test] + async fn a_reconciliation_that_cannot_create_its_runtime_stays_pending() { + let (soft_descriptor_limit, hard_descriptor_limit) = + getrlimit(Resource::RLIMIT_NOFILE).unwrap(); + let lowest_free_descriptor = File::open("/dev/null").unwrap().as_raw_fd(); + let mut reconciliation_attempt = DesiredStateReconciliationAttempt::start( + DesiredStateReconciler { + desired_model_resolver: DesiredModelResolver::from_process_env( + HUGGINGFACE_DEFAULT_ENDPOINT.to_owned(), + ) + .unwrap(), + slot_aggregated_status: Arc::new(SlotAggregatedStatus::new(1)), + }, + AgentDesiredState::default(), + &CancellationToken::new(), + RetryDelay::Immediately, + ); + + setrlimit( + Resource::RLIMIT_NOFILE, + u64::try_from(lowest_free_descriptor).unwrap(), + hard_descriptor_limit, + ) + .unwrap(); + + let desired_state_reconciliation = reconciliation_attempt.finish().await; + + setrlimit( + Resource::RLIMIT_NOFILE, + soft_descriptor_limit, + hard_descriptor_limit, + ) + .unwrap(); + + assert_eq!( + desired_state_reconciliation, + DesiredStateReconciliation::Pending(Box::default()) + ); + } + + #[tokio::test(start_paused = true)] + async fn a_reconciliation_abandoned_during_its_retry_delay_never_resolves_its_model() { + let cache_dir = TempDir::new().unwrap(); + let model_server = LocalHttpFixture::start(FixtureResponse::Ok(b"model".to_vec())) + .await + .unwrap(); + let mut reconciliation_attempt = DesiredStateReconciliationAttempt::start( + DesiredStateReconciler { + desired_model_resolver: DesiredModelResolver { + cache_dir: CacheDir { + explicit: Some(cache_dir.path().to_path_buf()), + home: None, + xdg: None, + }, + download_manager: DownloadManager::new().unwrap(), + huggingface_client: HuggingFaceHub { + cache_location: HuggingFaceCacheLocation::HubDefault, + endpoint: HUGGINGFACE_DEFAULT_ENDPOINT.to_owned(), + } + .client() + .unwrap(), + }, + slot_aggregated_status: Arc::new(SlotAggregatedStatus::new(1)), + }, + AgentDesiredState { + model: AgentDesiredModel::Uri(model_server.url("/model.gguf")), + ..AgentDesiredState::default() + }, + &CancellationToken::new(), + RetryDelay::AfterRetryInterval, + ); + + reconciliation_attempt.abandon().await; + + assert_eq!(read_dir(cache_dir.path()).unwrap().count(), 0); + assert_eq!(model_server.request_count(), 0); + + model_server + .stop() + .await + .expect("the fixture task must not panic") + .expect("the fixture must serve every request"); + } +} diff --git a/paddler_agent/src/embedding_batch_preparer.rs b/paddler_agent/src/embedding_batch_preparer.rs deleted file mode 100644 index 4ef98112a..000000000 --- a/paddler_agent/src/embedding_batch_preparer.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::sync::Arc; - -use llama_cpp_bindings::model::AddBos; -use llama_cpp_bindings::model::LlamaModel; - -use paddler_messaging::embedding_result::EmbeddingResult; -use paddler_messaging::oversized_embedding_document_details::OversizedEmbeddingDocumentDetails; -use paddler_messaging::request_params::generate_embedding_batch_params::GenerateEmbeddingBatchParams; - -use crate::agent_request::AgentRequest; -use crate::embedding_batch_rejection::EmbeddingBatchRejection; -use crate::embedding_input_tokenized::EmbeddingInputTokenized; -use crate::prepared_embedding_batch_request::PreparedEmbeddingBatchRequest; -use crate::require_embeddings_enabled::require_embeddings_enabled; - -pub struct EmbeddingBatchPreparer { - pub enable_embeddings: bool, - pub model: Arc, - pub n_batch: usize, -} - -impl EmbeddingBatchPreparer { - pub fn prepare( - &self, - AgentRequest { - params: - GenerateEmbeddingBatchParams { - input_batch, - normalization_method, - }, - response_tx: generated_embedding_tx, - slot_guard, - stop_rx: generate_embedding_stop_rx, - }: AgentRequest, - ) -> Result { - require_embeddings_enabled(self.enable_embeddings)?; - - let mut inputs = Vec::with_capacity(input_batch.len()); - let mut oversized_documents = Vec::new(); - - for input in input_batch { - let tokens = self - .model - .str_to_token(&input.content, AddBos::Always) - .map_err(|source| EmbeddingBatchRejection::InputTokenizationFailed { - source_document_id: input.id.clone(), - source, - })?; - - if tokens.len() > self.n_batch { - oversized_documents.push(OversizedEmbeddingDocumentDetails { - document_tokens: tokens.len(), - n_batch: self.n_batch, - source_document_id: input.id, - }); - } else { - inputs.push(EmbeddingInputTokenized { - id: input.id, - tokens, - }); - } - } - - Ok(PreparedEmbeddingBatchRequest { - generate_embedding_stop_rx, - generated_embedding_tx, - inputs, - normalization_method, - oversized_documents, - slot_guard, - }) - } -} diff --git a/paddler_agent/src/embedding_batch_rejection.rs b/paddler_agent/src/embedding_batch_rejection.rs deleted file mode 100644 index 1e25cb031..000000000 --- a/paddler_agent/src/embedding_batch_rejection.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::num::TryFromIntError; - -use llama_cpp_bindings::batch_add_error::BatchAddError; -use llama_cpp_bindings::error::DecodeError; -use llama_cpp_bindings::error::EmbeddingsError; -use llama_cpp_bindings::error::StringToTokenError; -use log::error; -use tokio::sync::mpsc; -use tokio::sync::mpsc::error::SendError; - -use paddler_messaging::embedding_result::EmbeddingResult; - -use crate::rejection_description::rejection_description; -use crate::send_result_or_warn::send_result_or_warn; - -#[derive(Debug, thiserror::Error)] -pub enum EmbeddingBatchRejection { - #[error("embeddings are not enabled")] - EmbeddingsDisabled, - - #[error("failed to tokenize embedding input {source_document_id:?}: {source}")] - InputTokenizationFailed { - source_document_id: String, - #[source] - source: StringToTokenError, - }, - - #[error("no model is loaded")] - ModelNotLoaded, - - #[error("the scheduler is no longer accepting requests")] - SchedulerUnavailable, - - #[error("failed to add an embedding input to the batch: {0}")] - BatchAssemblyFailed(#[source] BatchAddError), - - #[error("the client stopped listening for embeddings: {0}")] - ClientDisconnected(#[source] SendError), - - #[error("llama.cpp rejected the embedding batch: {0}")] - DecodeFailed(#[source] DecodeError), - - #[error("{dimensions}-dimensional embedding is too long for RMS normalization: {source}")] - EmbeddingTooLongForRmsNormalization { - dimensions: usize, - #[source] - source: TryFromIntError, - }, - - #[error("failed to read the embedding of a batch sequence: {0}")] - EmbeddingsUnavailable(#[source] EmbeddingsError), - - #[error("embedding batch sequence index does not fit in i32: {0}")] - SequenceIndexOutOfRange(#[source] TryFromIntError), -} - -impl EmbeddingBatchRejection { - pub fn report( - self, - agent_name: Option<&str>, - generated_embedding_tx: &mpsc::UnboundedSender, - ) { - let message = rejection_description(agent_name, &self); - - error!("{message}"); - - let result = match self { - Self::EmbeddingsDisabled => EmbeddingResult::EmbeddingsDisabled, - Self::ModelNotLoaded => EmbeddingResult::ModelNotLoaded(message), - Self::InputTokenizationFailed { .. } - | Self::SchedulerUnavailable - | Self::BatchAssemblyFailed(_) - | Self::ClientDisconnected(_) - | Self::DecodeFailed(_) - | Self::EmbeddingTooLongForRmsNormalization { .. } - | Self::EmbeddingsUnavailable(_) - | Self::SequenceIndexOutOfRange(_) => EmbeddingResult::Error(message), - }; - - send_result_or_warn(agent_name, generated_embedding_tx, result); - } -} - -#[cfg(test)] -mod tests { - use tokio::sync::mpsc; - - use paddler_messaging::embedding_result::EmbeddingResult; - - use super::EmbeddingBatchRejection; - - #[test] - fn reports_disabled_embeddings() { - let (generated_embedding_tx, mut generated_embedding_rx) = mpsc::unbounded_channel(); - - EmbeddingBatchRejection::EmbeddingsDisabled.report(Some("agent"), &generated_embedding_tx); - - assert_eq!( - generated_embedding_rx.try_recv(), - Ok(EmbeddingResult::EmbeddingsDisabled) - ); - } - - #[test] - fn reports_other_rejections_as_errors_naming_the_cause() { - let (generated_embedding_tx, mut generated_embedding_rx) = mpsc::unbounded_channel(); - - EmbeddingBatchRejection::SchedulerUnavailable - .report(Some("agent"), &generated_embedding_tx); - - assert_eq!( - generated_embedding_rx.try_recv(), - Ok(EmbeddingResult::Error( - "agent: the scheduler is no longer accepting requests".to_owned() - )) - ); - } - - #[test] - fn tolerates_a_disconnected_client() { - let (generated_embedding_tx, generated_embedding_rx) = mpsc::unbounded_channel(); - - drop(generated_embedding_rx); - - EmbeddingBatchRejection::EmbeddingsDisabled.report(None, &generated_embedding_tx); - - assert!(generated_embedding_tx.is_closed()); - } -} diff --git a/paddler_agent/src/enqueue_management_message.rs b/paddler_agent/src/enqueue_management_message.rs new file mode 100644 index 000000000..a6a4f839e --- /dev/null +++ b/paddler_agent/src/enqueue_management_message.rs @@ -0,0 +1,58 @@ +use log::error; +use serde::Serialize; +use serde_json::to_string; +use tokio::sync::mpsc; +use tokio_tungstenite::tungstenite::protocol::Message; + +use crate::enqueue_outbound_frame::enqueue_outbound_frame; +use crate::management_connection_end::ManagementConnectionEnd; +use crate::management_connection_step::ManagementConnectionStep; + +pub fn enqueue_management_message( + outbound_tx: &mpsc::UnboundedSender, + message: &TMessage, + step_after_enqueue: ManagementConnectionStep, +) -> ManagementConnectionStep +where + TMessage: Serialize, +{ + match to_string(message) { + Ok(serialized_message) => enqueue_outbound_frame( + outbound_tx, + Message::Text(serialized_message.into()), + step_after_enqueue, + ), + Err(serialization_error) => { + error!("Failed to serialize a management socket message: {serialization_error}"); + + ManagementConnectionStep::End(ManagementConnectionEnd::OutgoingMessageUnserializable) + } + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use tokio::sync::mpsc; + + use super::enqueue_management_message; + use crate::management_connection_end::ManagementConnectionEnd; + use crate::management_connection_step::ManagementConnectionStep; + + #[test] + fn ends_the_connection_when_the_message_cannot_be_serialized() { + let (outbound_tx, _outbound_rx) = mpsc::unbounded_channel(); + let map_with_a_non_string_key = BTreeMap::from([(vec![1_u8], 1_u8)]); + + assert!(matches!( + enqueue_management_message( + &outbound_tx, + &map_with_a_non_string_key, + ManagementConnectionStep::Continue, + ), + ManagementConnectionStep::End(management_connection_end) + if management_connection_end == ManagementConnectionEnd::OutgoingMessageUnserializable + )); + } +} diff --git a/paddler_agent/src/enqueue_outbound_frame.rs b/paddler_agent/src/enqueue_outbound_frame.rs new file mode 100644 index 000000000..3c49bb464 --- /dev/null +++ b/paddler_agent/src/enqueue_outbound_frame.rs @@ -0,0 +1,50 @@ +use log::error; +use tokio::sync::mpsc; +use tokio::sync::mpsc::error::SendError; +use tokio_tungstenite::tungstenite::protocol::Message; + +use crate::management_connection_end::ManagementConnectionEnd; +use crate::management_connection_step::ManagementConnectionStep; + +pub fn enqueue_outbound_frame( + outbound_tx: &mpsc::UnboundedSender, + frame: Message, + step_after_enqueue: ManagementConnectionStep, +) -> ManagementConnectionStep { + match outbound_tx.send(frame) { + Ok(()) => step_after_enqueue, + Err(SendError(undelivered_frame)) => { + error!("The management socket writer stopped before it could send {undelivered_frame}"); + + ManagementConnectionStep::End(ManagementConnectionEnd::OutgoingStreamFailed) + } + } +} + +#[cfg(test)] +mod tests { + use tokio::sync::mpsc; + use tokio_tungstenite::tungstenite::Bytes; + use tokio_tungstenite::tungstenite::protocol::Message; + + use super::enqueue_outbound_frame; + use crate::management_connection_end::ManagementConnectionEnd; + use crate::management_connection_step::ManagementConnectionStep; + + #[test] + fn ends_the_connection_when_the_writer_stopped() { + let (outbound_tx, outbound_rx) = mpsc::unbounded_channel(); + + drop(outbound_rx); + + assert!(matches!( + enqueue_outbound_frame( + &outbound_tx, + Message::Ping(Bytes::new()), + ManagementConnectionStep::Continue, + ), + ManagementConnectionStep::End(management_connection_end) + if management_connection_end == ManagementConnectionEnd::OutgoingStreamFailed + )); + } +} diff --git a/paddler_agent/src/finished_serving_startup.rs b/paddler_agent/src/finished_serving_startup.rs new file mode 100644 index 000000000..1df50fb7f --- /dev/null +++ b/paddler_agent/src/finished_serving_startup.rs @@ -0,0 +1,7 @@ +use crate::agent_applicable_state::AgentApplicableState; +use crate::replacement_outcome::ReplacementOutcome; + +pub struct FinishedServingStartup { + pub agent_applicable_state: AgentApplicableState, + pub outcome: ReplacementOutcome, +} diff --git a/paddler_agent/src/forward_management_socket_messages.rs b/paddler_agent/src/forward_management_socket_messages.rs deleted file mode 100644 index 7cb64adaa..000000000 --- a/paddler_agent/src/forward_management_socket_messages.rs +++ /dev/null @@ -1,155 +0,0 @@ -use std::fmt::Display; - -use futures_util::Sink; -use futures_util::SinkExt as _; -use log::error; -use serde_json::to_string; -use tokio::select; -use tokio::sync::mpsc; -use tokio_tungstenite::tungstenite::protocol::Message; -use tokio_util::sync::CancellationToken; - -use paddler_messaging::management_socket::balancer::message::Message as ManagementJsonRpcMessage; - -async fn write_message(write: &mut TSink, message: ManagementJsonRpcMessage) -> bool -where - TSink: Sink + Unpin, - TSink::Error: Display, -{ - match to_string(&message) { - Ok(serialized_message) => { - match write.send(Message::Text(serialized_message.into())).await { - Ok(()) => true, - Err(err) => { - error!("Failed to send a management socket message: {err}"); - - false - } - } - } - Err(err) => { - error!("Failed to serialize a management socket message: {err}"); - - false - } - } -} - -pub async fn forward_management_socket_messages( - connection_close: CancellationToken, - mut message_rx: mpsc::UnboundedReceiver, - mut write: TSink, -) -> TSink -where - TSink: Sink + Unpin, - TSink::Error: Display, -{ - loop { - select! { - () = connection_close.cancelled() => { - while let Ok(message) = message_rx.try_recv() - && write_message(&mut write, message).await - {} - - break; - } - message = message_rx.recv() => { - let Some(message) = message else { - break; - }; - - if !write_message(&mut write, message).await { - break; - } - } - } - } - - write -} - -#[cfg(test)] -mod tests { - use futures::channel::mpsc as sink_channel; - use futures_util::StreamExt as _; - use serde_json::to_string; - use tokio::sync::mpsc; - use tokio_tungstenite::tungstenite::protocol::Message; - use tokio_util::sync::CancellationToken; - - use paddler_messaging::management_socket::balancer::message::Message as ManagementJsonRpcMessage; - use paddler_messaging::management_socket::balancer::notification::Notification; - - use super::forward_management_socket_messages; - - fn deregister_agent() -> ManagementJsonRpcMessage { - ManagementJsonRpcMessage::Notification(Notification::DeregisterAgent) - } - - fn deregister_agent_text() -> Message { - Message::Text( - to_string(&deregister_agent()) - .expect("a deregistration must serialize") - .into(), - ) - } - - #[tokio::test] - async fn forwards_queued_messages_until_every_sender_is_gone() { - let (sink_tx, sink_rx) = sink_channel::unbounded::(); - let (message_tx, message_rx) = mpsc::unbounded_channel(); - - message_tx - .send(deregister_agent()) - .expect("the forwarder must still receive messages"); - drop(message_tx); - - drop( - forward_management_socket_messages(CancellationToken::new(), message_rx, sink_tx).await, - ); - - assert_eq!( - sink_rx.collect::>().await, - vec![deregister_agent_text()] - ); - } - - #[tokio::test] - async fn drains_queued_messages_when_the_connection_closes() { - let (sink_tx, sink_rx) = sink_channel::unbounded::(); - let (message_tx, message_rx) = mpsc::unbounded_channel(); - let connection_close = CancellationToken::new(); - - for _ in 0..2 { - message_tx - .send(deregister_agent()) - .expect("the forwarder must still receive messages"); - } - connection_close.cancel(); - - drop(forward_management_socket_messages(connection_close, message_rx, sink_tx).await); - - assert_eq!( - sink_rx.collect::>().await, - vec![deregister_agent_text(), deregister_agent_text()] - ); - drop(message_tx); - } - - #[tokio::test] - async fn stops_forwarding_when_the_socket_rejects_a_message() { - let (sink_tx, sink_rx) = sink_channel::unbounded::(); - let (message_tx, message_rx) = mpsc::unbounded_channel(); - - drop(sink_rx); - message_tx - .send(deregister_agent()) - .expect("the forwarder must still receive messages"); - - let returned_sink = - forward_management_socket_messages(CancellationToken::new(), message_rx, sink_tx).await; - - assert!(returned_sink.is_closed()); - drop(message_tx); - } -} diff --git a/paddler_agent/src/forward_scheduler_command.rs b/paddler_agent/src/forward_scheduler_command.rs deleted file mode 100644 index 4dfaacac3..000000000 --- a/paddler_agent/src/forward_scheduler_command.rs +++ /dev/null @@ -1,174 +0,0 @@ -use std::sync::mpsc::SendError; -use std::sync::mpsc::Sender; - -use log::info; - -use crate::continuous_batch_scheduler_command::ContinuousBatchSchedulerCommand; -use crate::embedding_batch_rejection::EmbeddingBatchRejection; -use crate::generation_request_rejection::GenerationRequestRejection; - -pub fn forward_scheduler_command( - scheduler_command_tx: &Sender, - agent_name: Option<&str>, - command: ContinuousBatchSchedulerCommand, -) { - if let Err(SendError(unsent_command)) = scheduler_command_tx.send(command) { - match unsent_command { - ContinuousBatchSchedulerCommand::Generate(request) => { - GenerationRequestRejection::SchedulerUnavailable - .report(agent_name, &request.generated_tokens_tx); - } - ContinuousBatchSchedulerCommand::GenerateEmbeddingBatch(request) => { - EmbeddingBatchRejection::SchedulerUnavailable - .report(agent_name, &request.generated_embedding_tx); - } - ContinuousBatchSchedulerCommand::Shutdown => { - info!("{agent_name:?}: scheduler stopped before the shutdown command arrived"); - } - } - } -} - -#[cfg(test)] -mod tests { - use std::mem::discriminant; - use std::num::NonZeroU32; - use std::sync::Arc; - use std::sync::mpsc::channel; - - use llama_cpp_bindings::BareJsonToolCalls; - use llama_cpp_bindings::StreamingMarkers; - use tokio::sync::mpsc; - - use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; - use paddler_inference_parameters::inference_parameters::InferenceParameters; - use paddler_messaging::embedding_normalization_method::EmbeddingNormalizationMethod; - use paddler_messaging::embedding_result::EmbeddingResult; - use paddler_messaging::generated_token_result::GeneratedTokenResult; - - use super::forward_scheduler_command; - use crate::continuous_batch_scheduler_command::ContinuousBatchSchedulerCommand; - use crate::grammar_sampling::GrammarSampling; - use crate::prepared_embedding_batch_request::PreparedEmbeddingBatchRequest; - use crate::prepared_generation_request::PreparedGenerationRequest; - use crate::prepared_prompt::PreparedPrompt; - use crate::sampler_chain_factory::SamplerChainFactory; - use crate::slot_guard::SlotGuard; - use crate::token_classification::TokenClassification; - use crate::token_sampling::TokenSampling; - use crate::tool_call_handling::ToolCallHandling; - - fn slot_guard() -> SlotGuard { - SlotGuard::new(Arc::new(SlotAggregatedStatus::new(1))) - } - - #[test] - fn delivers_command_to_a_running_scheduler() { - let (scheduler_command_tx, scheduler_command_rx) = channel(); - - forward_scheduler_command( - &scheduler_command_tx, - None, - ContinuousBatchSchedulerCommand::Shutdown, - ); - - assert_eq!( - scheduler_command_rx - .try_recv() - .map(|command| discriminant(&command)), - Ok(discriminant(&ContinuousBatchSchedulerCommand::Shutdown)) - ); - } - - #[test] - fn rejects_generation_when_the_scheduler_stopped() { - let (scheduler_command_tx, scheduler_command_rx) = channel(); - let (generated_tokens_tx, mut generated_tokens_rx) = mpsc::unbounded_channel(); - let (_generate_tokens_stop_tx, generate_tokens_stop_rx) = mpsc::unbounded_channel(); - - drop(scheduler_command_rx); - - forward_scheduler_command( - &scheduler_command_tx, - Some("agent"), - ContinuousBatchSchedulerCommand::Generate(Box::new(PreparedGenerationRequest { - generate_tokens_stop_rx, - generated_tokens_tx, - max_tokens: NonZeroU32::new(1).unwrap(), - prompt: PreparedPrompt::TextTokens(Vec::new()), - slot_guard: slot_guard(), - token_classification: TokenClassification { - bare_json_tool_calls: BareJsonToolCalls::Ignore, - streaming_markers: Arc::new(StreamingMarkers::default()), - }, - token_sampling: TokenSampling { - chain: SamplerChainFactory { - inference_parameters: InferenceParameters::default(), - n_vocab: 1, - } - .create(0) - .unwrap(), - grammar: GrammarSampling::Unconstrained, - }, - tool_call_handling: ToolCallHandling::Streamed, - })), - ); - - assert_eq!( - generated_tokens_rx.try_recv(), - Ok(GeneratedTokenResult::SchedulerUnavailable( - "agent: the scheduler is no longer accepting requests".to_owned() - )) - ); - } - - #[test] - fn rejects_embedding_batch_when_the_scheduler_stopped() { - let (scheduler_command_tx, scheduler_command_rx) = channel(); - let (generated_embedding_tx, mut generated_embedding_rx) = mpsc::unbounded_channel(); - let (_generate_embedding_stop_tx, generate_embedding_stop_rx) = mpsc::unbounded_channel(); - - drop(scheduler_command_rx); - - forward_scheduler_command( - &scheduler_command_tx, - Some("agent"), - ContinuousBatchSchedulerCommand::GenerateEmbeddingBatch( - PreparedEmbeddingBatchRequest { - generate_embedding_stop_rx, - generated_embedding_tx, - inputs: Vec::new(), - normalization_method: EmbeddingNormalizationMethod::None, - oversized_documents: Vec::new(), - slot_guard: slot_guard(), - }, - ), - ); - - assert_eq!( - generated_embedding_rx.try_recv(), - Ok(EmbeddingResult::Error( - "agent: the scheduler is no longer accepting requests".to_owned() - )) - ); - } - - #[test] - fn accepts_shutdown_after_the_scheduler_stopped() { - let (scheduler_command_tx, scheduler_command_rx) = channel(); - - drop(scheduler_command_rx); - - forward_scheduler_command( - &scheduler_command_tx, - None, - ContinuousBatchSchedulerCommand::Shutdown, - ); - - assert!( - scheduler_command_tx - .send(ContinuousBatchSchedulerCommand::Shutdown) - .is_err() - ); - } -} diff --git a/paddler_agent/src/generation_request_preparer.rs b/paddler_agent/src/generation_request_preparer.rs deleted file mode 100644 index a69b5b351..000000000 --- a/paddler_agent/src/generation_request_preparer.rs +++ /dev/null @@ -1,214 +0,0 @@ -use std::num::NonZeroU32; -use std::sync::Arc; - -use llama_cpp_bindings::BareJsonToolCalls; -use llama_cpp_bindings::ChatMessageParser; -use llama_cpp_bindings::model::LlamaModel; -use llama_cpp_bindings::mtmd::MtmdBitmap; -use rand::Rng as _; -use rand::rng; -use serde_json::to_string; - -use paddler_image_decoder::decoded_image::DecodedImage; -use paddler_messaging::chat_template_conversation::ChatTemplateConversation; -use paddler_messaging::generated_token_result::GeneratedTokenResult; -use paddler_messaging::grammar_constraint::GrammarConstraint; -use paddler_messaging::image_url::ImageUrl; -use paddler_messaging::request_params::continue_from_conversation_history_params::ContinueFromConversationHistoryParams; -use paddler_messaging::request_params::continue_from_conversation_history_params::tool::Tool; -use paddler_messaging::request_params::continue_from_conversation_history_params::tool::tool_params::function_call::parameters_schema::validated_parameters_schema::ValidatedParametersSchema; -use paddler_messaging::request_params::continue_from_raw_prompt_params::ContinueFromRawPromptParams; -use paddler_tool_call_validator::tool_call_validator::ToolCallValidator; - -use crate::agent_request::AgentRequest; -use crate::chat_prompt_render_request::ChatPromptRenderRequest; -use crate::converts_to_mtmd_bitmap::ConvertsToMtmdBitmap; -use crate::generation_request_rejection::GenerationRequestRejection; -use crate::image_input::ImageInput; -use crate::prepared_generation_request::PreparedGenerationRequest; -use crate::prepared_prompt::PreparedPrompt; -use crate::prompt_modality::PromptModality; -use crate::prompt_tokenizer::PromptTokenizer; -use crate::require_grammar_compatible_with_thinking::require_grammar_compatible_with_thinking; -use crate::resolve_grammar::resolve_grammar; -use crate::sampler_chain_factory::SamplerChainFactory; -use crate::token_classification::TokenClassification; -use crate::token_generation::TokenGeneration; -use crate::token_generation_support::TokenGenerationSupport; -use crate::token_sampling::TokenSampling; -use crate::tool_call_handling::ToolCallHandling; -use crate::tool_call_pipeline::ToolCallPipeline; - -pub struct GenerationRequestPreparer { - pub image_input: ImageInput, - pub image_resize_to_fit: NonZeroU32, - pub model: Arc, - pub prompt_tokenizer: PromptTokenizer, - pub sampler_chain_factory: SamplerChainFactory, - pub token_generation: TokenGeneration, -} - -impl GenerationRequestPreparer { - pub fn prepare_raw_prompt( - &self, - AgentRequest { - params: - ContinueFromRawPromptParams { - grammar, - max_tokens, - raw_prompt, - }, - response_tx: generated_tokens_tx, - slot_guard, - stop_rx: generate_tokens_stop_rx, - }: AgentRequest, - ) -> Result { - let TokenGenerationSupport { - streaming_markers, .. - } = self.token_generation.require_enabled()?; - let prompt = PreparedPrompt::TextTokens(self.prompt_tokenizer.tokenize(&raw_prompt)?); - let token_sampling = self.build_token_sampling(grammar)?; - - Ok(PreparedGenerationRequest { - generate_tokens_stop_rx, - generated_tokens_tx, - max_tokens, - prompt, - slot_guard, - token_classification: TokenClassification { - bare_json_tool_calls: BareJsonToolCalls::Ignore, - streaming_markers: streaming_markers.clone(), - }, - token_sampling, - tool_call_handling: ToolCallHandling::Streamed, - }) - } - - pub fn prepare_conversation_history( - &self, - AgentRequest { - response_tx: generated_tokens_tx, - stop_rx: generate_tokens_stop_rx, - params: - ContinueFromConversationHistoryParams { - add_generation_prompt, - conversation_history, - enable_thinking, - grammar, - max_tokens, - parse_tool_calls, - tools, - }, - slot_guard, - }: AgentRequest< - ContinueFromConversationHistoryParams, - GeneratedTokenResult, - >, - ) -> Result { - let TokenGenerationSupport { - chat_prompt_renderer, - streaming_markers, - } = self.token_generation.require_enabled()?; - - require_grammar_compatible_with_thinking(grammar.as_ref(), enable_thinking)?; - - let ChatTemplateConversation { - image_urls, - messages, - } = conversation_history - .into_chat_template_conversation(&chat_prompt_renderer.media_marker); - let prompt_modality = self.image_input.prompt_modality_for(&image_urls)?; - - let raw_prompt = chat_prompt_renderer.render(ChatPromptRenderRequest { - add_generation_prompt, - enable_thinking, - messages: &messages, - tools: &tools, - })?; - - let prompt = match prompt_modality { - PromptModality::Multimodal(multimodal_prompt_support) => { - let add_special_tokens = !self - .prompt_tokenizer - .renders_its_own_bos_token(&raw_prompt)?; - - PreparedPrompt::Multimodal( - multimodal_prompt_support.prepare_prompt( - &image_urls - .iter() - .map(|image_url| self.decode_image_bitmap(image_url)) - .collect::, GenerationRequestRejection>>()?, - raw_prompt, - add_special_tokens, - )?, - ) - } - PromptModality::TextOnly => { - PreparedPrompt::TextTokens(self.prompt_tokenizer.tokenize(&raw_prompt)?) - } - }; - - let token_sampling = self.build_token_sampling(grammar)?; - let tool_call_handling = if parse_tool_calls { - ToolCallHandling::Parsed(self.build_tool_call_pipeline(&tools)?) - } else { - ToolCallHandling::Streamed - }; - let bare_json_tool_calls = if tools.is_empty() { - BareJsonToolCalls::Ignore - } else { - BareJsonToolCalls::Detect - }; - - Ok(PreparedGenerationRequest { - generate_tokens_stop_rx, - generated_tokens_tx, - max_tokens, - prompt, - slot_guard, - token_classification: TokenClassification { - bare_json_tool_calls, - streaming_markers: streaming_markers.clone(), - }, - token_sampling, - tool_call_handling, - }) - } - - fn decode_image_bitmap( - &self, - image_url: &ImageUrl, - ) -> Result { - DecodedImage::from_data_uri(image_url, self.image_resize_to_fit) - .map_err(GenerationRequestRejection::ImageDecodingFailed)? - .to_mtmd_bitmap() - .map_err(GenerationRequestRejection::ImageBitmapCreationFailed) - } - - fn build_token_sampling( - &self, - grammar: Option, - ) -> Result { - let grammar = resolve_grammar(grammar, &self.model)?; - let chain = self - .sampler_chain_factory - .create(rng().random::()) - .map_err(GenerationRequestRejection::SamplerChainCreationFailed)?; - - Ok(TokenSampling { chain, grammar }) - } - - fn build_tool_call_pipeline( - &self, - tools: &[Tool], - ) -> Result { - let validator = ToolCallValidator::from_tools(tools) - .map_err(GenerationRequestRejection::ToolSchemaInvalid)?; - let tools_json = - to_string(tools).map_err(GenerationRequestRejection::ToolsSerializationFailed)?; - let chat_message_parser = ChatMessageParser::new(&self.model, &tools_json) - .map_err(GenerationRequestRejection::ToolCallParserCreationFailed)?; - - Ok(ToolCallPipeline::new(chat_message_parser, validator)) - } -} diff --git a/paddler_agent/src/generation_request_rejection.rs b/paddler_agent/src/generation_request_rejection.rs deleted file mode 100644 index 03bcb002c..000000000 --- a/paddler_agent/src/generation_request_rejection.rs +++ /dev/null @@ -1,559 +0,0 @@ -use std::num::TryFromIntError; - -use llama_cpp_bindings::batch_add_error::BatchAddError; -use llama_cpp_bindings::error::ClearKvCacheSeqError; -use llama_cpp_bindings::error::EvalMultimodalChunksError; -use llama_cpp_bindings::error::GrammarError; -use llama_cpp_bindings::error::JsonSchemaToGrammarError; -use llama_cpp_bindings::error::ParseChatMessageError; -use llama_cpp_bindings::error::SamplingError; -use llama_cpp_bindings::error::StringToTokenError; -use llama_cpp_bindings::mtmd::MtmdBitmapError; -use llama_cpp_bindings::mtmd::MtmdEvalError; -use llama_cpp_bindings::mtmd::MtmdTokenizeError; -use log::error; -use minijinja::Error as MinijinjaError; -use serde_json::Error; -use tokio::sync::mpsc; - -use paddler_image_decoder::decoded_image_error::DecodedImageError; -use paddler_messaging::generated_token_result::GeneratedTokenResult; -use paddler_messaging::oversized_media_details::OversizedMediaDetails; -use paddler_messaging::oversized_prompt_details::OversizedPromptDetails; -use paddler_tool_call_validator::tool_call_validation_error::ToolCallValidationError; - -use crate::rejection_description::rejection_description; -use crate::send_result_or_warn::send_result_or_warn; - -#[derive(Debug, thiserror::Error)] -pub enum GenerationRequestRejection { - #[error("Grammar constraints are incompatible with thinking mode")] - GrammarIncompatibleWithThinking, - - #[error("Failed to convert JSON schema to grammar: {0}")] - GrammarConversionFailed(#[source] JsonSchemaToGrammarError), - - #[error("token generation is disabled because this agent is running in embeddings-only mode")] - TokenGenerationDisabled, - - #[error("failed to decode images: {0}")] - ImageDecodingFailed(#[source] DecodedImageError), - - #[error("failed to create image bitmap: {0}")] - ImageBitmapCreationFailed(#[source] MtmdBitmapError), - - #[error("received images but model does not support multimodal input")] - MultimodalNotSupported, - - #[error("no model is loaded")] - ModelNotLoaded, - - #[error("failed to render chat template: {0}")] - ChatTemplateRenderingFailed(#[source] MinijinjaError), - - #[error("{0}")] - ToolSchemaInvalid(#[source] ToolCallValidationError), - - #[error("failed to serialize tools: {0}")] - ToolsSerializationFailed(#[source] Error), - - #[error("failed to build this model's tool call parser: {0}")] - ToolCallParserCreationFailed(#[source] ParseChatMessageError), - - #[error("failed to tokenize prompt: {0}")] - PromptTokenizationFailed(#[source] StringToTokenError), - - #[error( - "prompt has {} tokens but each sequence holds {} tokens", - details.prompt_tokens, - details.sequence_context_size - )] - PromptExceedsContextSize { details: OversizedPromptDetails }, - - #[error("the scheduler is no longer accepting requests")] - SchedulerUnavailable, - - #[error("no available sequence slots, all slots are busy")] - NoSequenceSlotAvailable, - - #[error("invalid grammar: {0}")] - GrammarInvalid(#[source] GrammarError), - #[error("failed to initialize grammar sampler: {0}")] - GrammarSamplerInitializationFailed(#[source] GrammarError), - - #[error("failed to create sampler chain: {0}")] - SamplerChainCreationFailed(#[source] SamplingError), - - #[error("failed to tokenize multimodal input: {0}")] - MultimodalTokenizationFailed(#[source] MtmdTokenizeError), - - #[error( - "media chunk has {} tokens but one micro batch holds {} tokens", - details.media_tokens, - details.micro_batch_tokens - )] - MediaExceedsMicroBatch { details: OversizedMediaDetails }, - #[error("failed to check media chunks against the micro batch: {0}")] - MediaMicroBatchCheckFailed(#[source] MtmdEvalError), - - #[error("failed to ingest multimodal prompt: {0}")] - MultimodalIngestionFailed(#[source] EvalMultimodalChunksError), - - #[error("failed to add a token to the batch: {0}")] - BatchAssemblyFailed(#[source] BatchAddError), - - #[error("failed to clear the KV cache of the sequence: {0}")] - KvCacheClearFailed(#[source] ClearKvCacheSeqError), - - #[error("sequence id {sequence_id} cannot address a KV cache sequence: {source}")] - SequenceIdOutOfRange { - sequence_id: i32, - #[source] - source: TryFromIntError, - }, -} - -impl GenerationRequestRejection { - #[must_use] - pub const fn for_gbnf_grammar_error(grammar_error: GrammarError) -> Self { - match grammar_error { - GrammarError::GrammarContainsNul(_) - | GrammarError::GrammarRejected(_) - | GrammarError::RootContainsNul(_) - | GrammarError::RootNotFound => Self::GrammarInvalid(grammar_error), - GrammarError::FfiContract(_) - | GrammarError::FfiStatus(_) - | GrammarError::GrammarMalformed - | GrammarError::InvalidTriggerPattern { .. } - | GrammarError::LazyGrammarMalformed - | GrammarError::LlamaCppOutOfMemory - | GrammarError::LlguidanceFactoryUnavailable { .. } - | GrammarError::LlguidanceGrammarInvalid { .. } - | GrammarError::LlguidanceParserUnavailable { .. } - | GrammarError::NotEnoughMemory - | GrammarError::Reported { .. } - | GrammarError::SamplerInitialization(_) - | GrammarError::SequenceBreakerContainsNul(_) - | GrammarError::TokEnvUnavailable(_) - | GrammarError::TriggerPatternContainsNul(_) => { - Self::GrammarSamplerInitializationFailed(grammar_error) - } - } - } - - #[must_use] - pub fn for_micro_batch_fit_error(fit_error: MtmdEvalError) -> Self { - match fit_error { - MtmdEvalError::NonCausalChunkExceedsMicroBatch(mismatch) => { - Self::MediaExceedsMicroBatch { - details: OversizedMediaDetails { - media_tokens: mismatch.chunk_tokens, - micro_batch_tokens: mismatch.micro_batch_tokens, - }, - } - } - other_fit_error => Self::MediaMicroBatchCheckFailed(other_fit_error), - } - } - - pub fn report( - self, - agent_name: Option<&str>, - generated_tokens_tx: &mpsc::UnboundedSender, - ) { - send_result_or_warn( - agent_name, - generated_tokens_tx, - self.into_generated_token_result(agent_name), - ); - } - - #[must_use] - pub fn into_generated_token_result(self, agent_name: Option<&str>) -> GeneratedTokenResult { - let message = rejection_description(agent_name, &self); - - error!("{message}"); - - match self { - Self::GrammarIncompatibleWithThinking => { - GeneratedTokenResult::GrammarIncompatibleWithThinking(message) - } - Self::GrammarConversionFailed(_) | Self::GrammarInvalid(_) => { - GeneratedTokenResult::GrammarSyntaxError(message) - } - Self::TokenGenerationDisabled => GeneratedTokenResult::TokenGenerationDisabled(message), - Self::ImageDecodingFailed(_) | Self::ImageBitmapCreationFailed(_) => { - GeneratedTokenResult::ImageDecodingFailed(message) - } - Self::ModelNotLoaded => GeneratedTokenResult::ModelNotLoaded(message), - Self::MultimodalNotSupported => GeneratedTokenResult::MultimodalNotSupported(message), - Self::ChatTemplateRenderingFailed(_) | Self::ToolCallParserCreationFailed(_) => { - GeneratedTokenResult::ChatTemplateError(message) - } - Self::ToolSchemaInvalid(_) => GeneratedTokenResult::ToolSchemaInvalid(message), - Self::GrammarSamplerInitializationFailed(_) => { - GeneratedTokenResult::GrammarInitializationFailed(message) - } - Self::MediaExceedsMicroBatch { details } => { - GeneratedTokenResult::MediaExceedsMicroBatch(details) - } - Self::PromptExceedsContextSize { details } => { - GeneratedTokenResult::PromptExceedsContextSize(details) - } - Self::ToolsSerializationFailed(_) => { - GeneratedTokenResult::ToolsSerializationFailed(message) - } - Self::MediaMicroBatchCheckFailed(_) => { - GeneratedTokenResult::MediaMicroBatchCheckFailed(message) - } - Self::PromptTokenizationFailed(_) => { - GeneratedTokenResult::PromptTokenizationFailed(message) - } - Self::SchedulerUnavailable => GeneratedTokenResult::SchedulerUnavailable(message), - Self::NoSequenceSlotAvailable => GeneratedTokenResult::NoSequenceSlotAvailable(message), - Self::SamplerChainCreationFailed(_) => { - GeneratedTokenResult::SamplerChainCreationFailed(message) - } - Self::MultimodalTokenizationFailed(_) => { - GeneratedTokenResult::MultimodalTokenizationFailed(message) - } - Self::MultimodalIngestionFailed(_) => { - GeneratedTokenResult::MultimodalIngestionFailed(message) - } - Self::BatchAssemblyFailed(_) => GeneratedTokenResult::BatchAssemblyFailed(message), - Self::KvCacheClearFailed(_) => GeneratedTokenResult::KvCacheClearFailed(message), - Self::SequenceIdOutOfRange { .. } => { - GeneratedTokenResult::SequenceIdOutOfRange(message) - } - } - } -} - -#[cfg(test)] -mod tests { - use llama_cpp_bindings::batch_add_error::BatchAddError; - use llama_cpp_bindings::error::ClearKvCacheSeqError; - use llama_cpp_bindings::error::EvalMultimodalChunksError; - use llama_cpp_bindings::error::FfiContractError; - use llama_cpp_bindings::error::FfiStatusError; - use llama_cpp_bindings::error::GrammarError; - use llama_cpp_bindings::error::JsonSchemaToGrammarError; - use llama_cpp_bindings::error::ParseChatMessageError; - use llama_cpp_bindings::error::SamplingError; - use llama_cpp_bindings::mtmd::MtmdEvalError; - use llama_cpp_bindings::mtmd::NonCausalChunkMicroBatchMismatch; - use minijinja::Error as MinijinjaError; - use minijinja::ErrorKind as MinijinjaErrorKind; - use serde_json::Map; - use serde_json::Value; - use serde_json::from_str; - use serde_json::json; - use tokio::sync::mpsc; - - use paddler_image_decoder::decoded_image_error::DecodedImageError; - use paddler_messaging::generated_token_result::GeneratedTokenResult; - use paddler_messaging::oversized_media_details::OversizedMediaDetails; - use paddler_messaging::oversized_prompt_details::OversizedPromptDetails; - use paddler_messaging::request_params::continue_from_conversation_history_params::tool::Tool; - use paddler_messaging::request_params::continue_from_conversation_history_params::tool::tool_params::function_call::FunctionCall; - use paddler_messaging::request_params::continue_from_conversation_history_params::tool::tool_params::function_call::function::Function; - use paddler_messaging::request_params::continue_from_conversation_history_params::tool::tool_params::function_call::parameters::Parameters; - use paddler_messaging::request_params::continue_from_conversation_history_params::tool::tool_params::function_call::parameters_schema::validated_parameters_schema::ValidatedParametersSchema; - use paddler_tool_call_validator::tool_call_validator::ToolCallValidator; - - use super::GenerationRequestRejection; - - struct ExpectedReport { - rejection: GenerationRequestRejection, - into_result: fn(String) -> GeneratedTokenResult, - } - - fn reported(rejection: GenerationRequestRejection) -> GeneratedTokenResult { - let (generated_tokens_tx, mut generated_tokens_rx) = mpsc::unbounded_channel(); - - rejection.report(Some("agent"), &generated_tokens_tx); - - generated_tokens_rx.try_recv().unwrap() - } - - fn agent_message(description: &str) -> String { - format!("agent: {description}") - } - - #[test] - fn reports_thinking_incompatibility_with_the_agent_name() { - assert_eq!( - reported(GenerationRequestRejection::GrammarIncompatibleWithThinking), - GeneratedTokenResult::GrammarIncompatibleWithThinking(agent_message( - "Grammar constraints are incompatible with thinking mode" - )) - ); - } - - #[test] - fn reports_grammar_conversion_failure_as_grammar_syntax_error() { - let conversion_error = JsonSchemaToGrammarError::NotEnoughMemory; - let expected_message = agent_message(&format!( - "Failed to convert JSON schema to grammar: {conversion_error}" - )); - - assert_eq!( - reported(GenerationRequestRejection::GrammarConversionFailed( - conversion_error - )), - GeneratedTokenResult::GrammarSyntaxError(expected_message) - ); - } - - #[test] - fn reports_disabled_token_generation() { - assert_eq!( - reported(GenerationRequestRejection::TokenGenerationDisabled), - GeneratedTokenResult::TokenGenerationDisabled(agent_message( - "token generation is disabled because this agent is running in embeddings-only mode" - )) - ); - } - - #[test] - fn reports_image_failures_as_image_decoding_failure() { - assert_eq!( - reported(GenerationRequestRejection::ImageDecodingFailed( - DecodedImageError::MissingCommaSeparator, - )), - GeneratedTokenResult::ImageDecodingFailed(agent_message( - "failed to decode images: Invalid data URI: missing comma separator" - )) - ); - } - - #[test] - fn reports_missing_multimodal_support() { - assert_eq!( - reported(GenerationRequestRejection::MultimodalNotSupported), - GeneratedTokenResult::MultimodalNotSupported(agent_message( - "received images but model does not support multimodal input" - )) - ); - } - - #[test] - fn reports_prompt_rendering_failures_as_chat_template_error() { - let rendering_error = - MinijinjaError::new(MinijinjaErrorKind::UndefinedError, "missing variable"); - let expected_message = agent_message(&format!( - "failed to render chat template: {rendering_error}" - )); - - assert_eq!( - reported(GenerationRequestRejection::ChatTemplateRenderingFailed( - rendering_error - )), - GeneratedTokenResult::ChatTemplateError(expected_message) - ); - } - - #[test] - fn reports_invalid_tool_schema() { - let mut properties = Map::new(); - - properties.insert("location".to_owned(), json!({"type": 123})); - - let schema_error = ToolCallValidator::from_tools(&[Tool::Function(FunctionCall { - function: Function { - name: "get_weather".to_owned(), - description: "fetch weather".to_owned(), - parameters: Parameters::Schema(ValidatedParametersSchema { - schema_type: "object".to_owned(), - properties: Some(properties), - ..ValidatedParametersSchema::default() - }), - }, - })]) - .err() - .expect("a property typed as a number is not a valid JSON Schema"); - let expected_message = agent_message(&schema_error.to_string()); - - assert_eq!( - reported(GenerationRequestRejection::ToolSchemaInvalid(schema_error)), - GeneratedTokenResult::ToolSchemaInvalid(expected_message) - ); - } - - #[test] - fn reports_grammar_sampler_initialization_failure() { - let grammar_error = GrammarError::FfiStatus(FfiStatusError { - operation: "llama_sampler_init_grammar", - code: 1, - }); - let expected_message = agent_message(&format!( - "failed to initialize grammar sampler: {grammar_error}" - )); - - assert_eq!( - reported(GenerationRequestRejection::GrammarSamplerInitializationFailed(grammar_error)), - GeneratedTokenResult::GrammarInitializationFailed(expected_message) - ); - } - - #[test] - fn reports_a_grammar_the_client_malformed_as_grammar_syntax_error() { - let expected_message = - agent_message(&format!("invalid grammar: {}", GrammarError::RootNotFound)); - - assert_eq!( - reported(GenerationRequestRejection::for_gbnf_grammar_error( - GrammarError::RootNotFound - )), - GeneratedTokenResult::GrammarSyntaxError(expected_message) - ); - } - - #[test] - fn reports_a_grammar_the_agent_failed_to_initialize_as_grammar_initialization_failure() { - let expected_message = agent_message(&format!( - "failed to initialize grammar sampler: {}", - GrammarError::NotEnoughMemory - )); - - assert_eq!( - reported(GenerationRequestRejection::for_gbnf_grammar_error( - GrammarError::NotEnoughMemory - )), - GeneratedTokenResult::GrammarInitializationFailed(expected_message) - ); - } - - #[test] - fn reports_tool_call_parser_creation_failure_as_chat_template_error() { - let parser_error = ParseChatMessageError::ToolsNotAnArray; - let expected_message = agent_message(&format!( - "failed to build this model's tool call parser: {parser_error}" - )); - - assert_eq!( - reported(GenerationRequestRejection::ToolCallParserCreationFailed( - parser_error - )), - GeneratedTokenResult::ChatTemplateError(expected_message) - ); - } - - #[test] - fn reports_media_exceeding_the_micro_batch_with_its_token_counts() { - let rejection = GenerationRequestRejection::for_micro_batch_fit_error( - MtmdEvalError::NonCausalChunkExceedsMicroBatch(NonCausalChunkMicroBatchMismatch { - chunk_tokens: 256, - micro_batch_tokens: 128, - }), - ); - - assert_eq!( - reported(rejection), - GeneratedTokenResult::MediaExceedsMicroBatch(OversizedMediaDetails { - media_tokens: 256, - micro_batch_tokens: 128, - }) - ); - } - - #[test] - fn reports_other_micro_batch_check_failures_as_micro_batch_check_failures() { - let fit_error = MtmdEvalError::FfiContract(FfiContractError { - operation: "mtmd_input_chunks_get", - detail: "returned a null chunk within the chunk count", - }); - let expected_message = agent_message(&format!( - "failed to check media chunks against the micro batch: {fit_error}" - )); - - assert_eq!( - reported(GenerationRequestRejection::for_micro_batch_fit_error( - fit_error - )), - GeneratedTokenResult::MediaMicroBatchCheckFailed(expected_message) - ); - } - - #[test] - fn reports_oversized_prompt_with_its_token_counts() { - assert_eq!( - reported(GenerationRequestRejection::PromptExceedsContextSize { - details: OversizedPromptDetails { - prompt_tokens: 9895, - sequence_context_size: 8192, - }, - }), - GeneratedTokenResult::PromptExceedsContextSize(OversizedPromptDetails { - prompt_tokens: 9895, - sequence_context_size: 8192, - }) - ); - } - - #[test] - fn reports_multimodal_ingestion_failures_as_ingestion_failures() { - let rejection = GenerationRequestRejection::MultimodalIngestionFailed( - EvalMultimodalChunksError::ChunkOutOfBounds(3), - ); - - assert_eq!( - reported(rejection), - GeneratedTokenResult::MultimodalIngestionFailed(agent_message( - "failed to ingest multimodal prompt: chunk index 3 out of bounds during post-eval walk" - )) - ); - } - - #[test] - fn reports_internal_scheduler_failures_under_their_own_results() { - for ExpectedReport { - rejection, - into_result, - } in [ - ExpectedReport { - rejection: GenerationRequestRejection::BatchAssemblyFailed( - BatchAddError::EmptyBuffer, - ), - into_result: GeneratedTokenResult::BatchAssemblyFailed, - }, - ExpectedReport { - rejection: GenerationRequestRejection::KvCacheClearFailed( - ClearKvCacheSeqError::PartialSequenceNotRemoved { - seq_id: 1, - p0: 0, - p1: 4, - }, - ), - into_result: GeneratedTokenResult::KvCacheClearFailed, - }, - ExpectedReport { - rejection: GenerationRequestRejection::NoSequenceSlotAvailable, - into_result: GeneratedTokenResult::NoSequenceSlotAvailable, - }, - ExpectedReport { - rejection: GenerationRequestRejection::SamplerChainCreationFailed( - SamplingError::SamplerUnavailable { sampler: "grammar" }, - ), - into_result: GeneratedTokenResult::SamplerChainCreationFailed, - }, - ExpectedReport { - rejection: GenerationRequestRejection::SequenceIdOutOfRange { - sequence_id: -1, - source: u32::try_from(-1_i32).unwrap_err(), - }, - into_result: GeneratedTokenResult::SequenceIdOutOfRange, - }, - ExpectedReport { - rejection: GenerationRequestRejection::ToolsSerializationFailed( - from_str::("{").unwrap_err(), - ), - into_result: GeneratedTokenResult::ToolsSerializationFailed, - }, - ] { - let expected_message = agent_message(&rejection.to_string()); - - assert_eq!(reported(rejection), into_result(expected_message)); - } - } -} diff --git a/paddler_agent/src/image_input.rs b/paddler_agent/src/image_input.rs deleted file mode 100644 index 3cc28a6cd..000000000 --- a/paddler_agent/src/image_input.rs +++ /dev/null @@ -1,53 +0,0 @@ -use paddler_messaging::image_url::ImageUrl; - -use crate::generation_request_rejection::GenerationRequestRejection; -use crate::multimodal_prompt_support::MultimodalPromptSupport; -use crate::prompt_modality::PromptModality; - -pub enum ImageInput { - Supported(MultimodalPromptSupport), - Unsupported, -} - -impl ImageInput { - pub const fn prompt_modality_for( - &self, - image_urls: &[ImageUrl], - ) -> Result, GenerationRequestRejection> { - if image_urls.is_empty() { - return Ok(PromptModality::TextOnly); - } - - match self { - Self::Supported(multimodal_prompt_support) => { - Ok(PromptModality::Multimodal(multimodal_prompt_support)) - } - Self::Unsupported => Err(GenerationRequestRejection::MultimodalNotSupported), - } - } -} - -#[cfg(test)] -mod tests { - use std::mem::discriminant; - - use paddler_messaging::image_url::ImageUrl; - - use super::ImageInput; - use crate::generation_request_rejection::GenerationRequestRejection; - - #[test] - fn rejects_images_when_no_multimodal_projection_is_loaded() { - assert_eq!( - ImageInput::Unsupported - .prompt_modality_for(&[ImageUrl { - url: "data:image/png;base64,AAAA".to_owned(), - }]) - .err() - .map(|rejection| discriminant(&rejection)), - Some(discriminant( - &GenerationRequestRejection::MultimodalNotSupported - )) - ); - } -} diff --git a/paddler_agent/src/inference_capabilities.rs b/paddler_agent/src/inference_capabilities.rs new file mode 100644 index 000000000..90bcd5bd1 --- /dev/null +++ b/paddler_agent/src/inference_capabilities.rs @@ -0,0 +1,32 @@ +use tokio::join; + +use paddler_agent_runtime::shuts_down::ShutsDown; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct InferenceCapabilities { + pub decisions: TDecisions, + pub embeddings: TEmbeddings, + pub text_generation: TTextGeneration, +} + +impl ShutsDown + for InferenceCapabilities +where + TDecisions: ShutsDown + Send, + TEmbeddings: ShutsDown + Send, + TTextGeneration: ShutsDown + Send, +{ + async fn shut_down(self) { + let Self { + decisions, + embeddings, + text_generation, + } = self; + + join!( + decisions.shut_down(), + embeddings.shut_down(), + text_generation.shut_down() + ); + } +} diff --git a/paddler_agent/src/last_announced_agent_status.rs b/paddler_agent/src/last_announced_agent_status.rs new file mode 100644 index 000000000..b4e7876e6 --- /dev/null +++ b/paddler_agent/src/last_announced_agent_status.rs @@ -0,0 +1,75 @@ +use paddler_messaging::agent_status::AgentStatus; +use paddler_messaging::management_socket::balancer::message::Message as ManagementJsonRpcMessage; +use paddler_messaging::management_socket::balancer::notification::Notification as ManagementJsonRpcNotification; +use paddler_messaging::management_socket::balancer::notification_params::update_agent_status_params::UpdateAgentStatusParams; +use paddler_messaging::slot_aggregated_status_snapshot::SlotAggregatedStatusSnapshot; + +use crate::management_connection_step::ManagementConnectionStep; + +pub struct LastAnnouncedAgentStatus { + agent_status: AgentStatus, +} + +impl LastAnnouncedAgentStatus { + #[must_use] + pub const fn new(agent_status: AgentStatus) -> Self { + Self { agent_status } + } + + pub fn announce_if_changed( + &mut self, + slot_aggregated_status_snapshot: SlotAggregatedStatusSnapshot, + ) -> ManagementConnectionStep { + if self.agent_status == slot_aggregated_status_snapshot.status { + return ManagementConnectionStep::Continue; + } + + self.agent_status = slot_aggregated_status_snapshot.status.clone(); + + ManagementConnectionStep::Write(ManagementJsonRpcMessage::Notification( + ManagementJsonRpcNotification::UpdateAgentStatus(UpdateAgentStatusParams { + slot_aggregated_status_snapshot, + }), + )) + } +} + +#[cfg(test)] +mod tests { + use std::mem::discriminant; + + use paddler_messaging::agent_status::AgentStatus; + use paddler_messaging::management_socket::balancer::message::Message as ManagementJsonRpcMessage; + use paddler_messaging::management_socket::balancer::notification::Notification as ManagementJsonRpcNotification; + use paddler_messaging::management_socket::balancer::notification_params::update_agent_status_params::UpdateAgentStatusParams; + use paddler_messaging::slot_aggregated_status_snapshot::SlotAggregatedStatusSnapshot; + + use super::LastAnnouncedAgentStatus; + use crate::management_connection_step::ManagementConnectionStep; + + #[test] + fn a_changed_status_is_announced_once() { + let mut last_announced_agent_status = LastAnnouncedAgentStatus::new(AgentStatus::default()); + let changed_snapshot = SlotAggregatedStatusSnapshot { + status: AgentStatus { + uses_chat_template_override: true, + ..AgentStatus::default() + }, + version: 1, + }; + + assert!(matches!( + last_announced_agent_status.announce_if_changed(changed_snapshot.clone()), + ManagementConnectionStep::Write(announced_message) + if announced_message == ManagementJsonRpcMessage::Notification( + ManagementJsonRpcNotification::UpdateAgentStatus(UpdateAgentStatusParams { + slot_aggregated_status_snapshot: changed_snapshot.clone(), + }) + ) + )); + assert_eq!( + discriminant(&last_announced_agent_status.announce_if_changed(changed_snapshot)), + discriminant(&ManagementConnectionStep::Continue) + ); + } +} diff --git a/paddler_agent/src/lib.rs b/paddler_agent/src/lib.rs index b447c0d35..8c42e31dc 100644 --- a/paddler_agent/src/lib.rs +++ b/paddler_agent/src/lib.rs @@ -1,83 +1,51 @@ -pub mod agent_applicable_model; pub mod agent_applicable_state; pub mod agent_applicable_state_holder; pub mod agent_desired_state_conversion; pub mod agent_desired_state_converter; -pub mod agent_kv_cache_dtype; -pub mod agent_pooling_type; -pub mod agent_request; +pub mod agent_error; +pub mod agent_response_message; +pub mod applicable_decisions; pub mod balancer_message_context; -pub mod chat_prompt_render_request; -pub mod chat_prompt_renderer; -pub mod chat_template_load_status; -pub mod chat_template_renderer; -pub mod continuous_batch_active_request; -pub mod continuous_batch_arbiter; -pub mod continuous_batch_arbiter_context; -pub mod continuous_batch_arbiter_handle; -pub mod continuous_batch_arbiter_spawn_outcome; -pub mod continuous_batch_arbiter_state; -pub mod continuous_batch_embedding_processor; -pub mod continuous_batch_generation_step; -pub mod continuous_batch_preparation_request; -pub mod continuous_batch_request_phase; -pub mod continuous_batch_request_preparer; -pub mod continuous_batch_request_state; -pub mod continuous_batch_scheduler; -pub mod continuous_batch_scheduler_command; -pub mod continuous_batch_scheduler_context; -pub mod continuous_batch_scheduler_params; -pub mod continuous_batch_terminal_delivery; -pub mod continuous_batch_terminal_outcome; -pub mod converts_to_llama_kv_cache_dtype; -pub mod converts_to_llama_pooling_type; -pub mod converts_to_mtmd_bitmap; +pub mod capability_plan; +pub mod capability_starter; +pub mod capability_startup_interruption; +pub mod desired_model_file; pub mod desired_state_reconciler; pub mod desired_state_reconciliation; -pub mod embedding_batch_preparer; -pub mod embedding_batch_rejection; -pub mod embedding_input_tokenized; -pub mod forward_management_socket_messages; -pub mod forward_scheduler_command; -pub mod generation_request_preparer; -pub mod generation_request_rejection; -pub mod grammar_sampling; -pub mod image_input; -pub mod join_scheduler_thread; +pub mod desired_state_reconciliation_attempt; +pub mod enqueue_management_message; +pub mod enqueue_outbound_frame; +pub mod finished_serving_startup; +pub mod inference_capabilities; +pub mod last_announced_agent_status; pub mod llamacpp_arbiter_service; +pub mod loaded_model_serving_plan; +pub mod management_connection_end; +pub mod management_connection_step; pub mod management_socket_client_service; -pub mod model_bos_token; -pub mod model_metadata_holder; -pub mod multimodal_ingestion_progress; -pub mod multimodal_prompt_ingestion; -pub mod multimodal_prompt_support; -pub mod normalization; -pub mod plan_embedding_batches; -pub mod prepared_embedding_batch_request; -pub mod prepared_generation_request; -pub mod prepared_prompt; -pub mod prompt_modality; -pub mod prompt_tokenizer; -pub mod receives_stop_request; +pub mod model_serving; +pub mod model_serving_settings; +pub mod pipeline_arbiter; +pub mod pipeline_arbiter_params; +pub mod pipeline_request; +pub mod pipeline_response_stream; +pub mod preparation_outcome; +pub mod prepared_decisions; +pub mod prepared_serving; pub mod reconciliation_service; -pub mod rejection_description; -pub mod require_embeddings_enabled; -pub mod require_grammar_compatible_with_thinking; -pub mod require_prompt_fits_sequence_context; -pub mod resolve_grammar; -pub mod sampler_chain_factory; -pub mod sampling_outcome; -pub mod send_result_or_warn; -pub mod send_startup_signal; -pub mod sequence_id_guard; -pub mod sequence_id_pool; -pub mod slot_guard; -pub mod startup_signal_delivery; -pub mod token_classification; -pub mod token_generation; -pub mod token_generation_support; -pub mod token_sampling; -pub mod tool_call_buffer; -pub mod tool_call_event; -pub mod tool_call_handling; -pub mod tool_call_pipeline; +pub mod replacement_outcome; +pub mod retained_serving; +pub mod retry_delay; +pub mod retry_delay_outcome; +pub mod served_capabilities; +pub mod served_decisions; +pub mod served_embeddings; +pub mod served_model; +pub mod served_text_generation; +pub mod serving_interruption; +pub mod serving_preparation; +pub mod serving_replacement; +pub mod serving_startup; +pub mod serving_startup_progress; +pub mod startup_phase; +pub mod superseding_state; diff --git a/paddler_agent/src/llamacpp_arbiter_service.rs b/paddler_agent/src/llamacpp_arbiter_service.rs index a03c47843..13fffd018 100644 --- a/paddler_agent/src/llamacpp_arbiter_service.rs +++ b/paddler_agent/src/llamacpp_arbiter_service.rs @@ -2,101 +2,71 @@ use std::sync::Arc; use anyhow::Result; use async_trait::async_trait; -use log::error; use tokio::select; -use tokio::sync::mpsc; -use tokio::time::Duration; -use tokio::time::Instant; -use tokio::time::MissedTickBehavior; -use tokio::time::interval_at; use tokio_util::sync::CancellationToken; use trzcina::Service; -use paddler_messaging::agent_state_application_status::AgentStateApplicationStatus; +use paddler_agent_runtime::inference_runtime_context::InferenceRuntimeContext; +use paddler_outbox::outbox::Outbox; use crate::agent_applicable_state_holder::AgentApplicableStateHolder; -use crate::continuous_batch_arbiter_context::ContinuousBatchArbiterContext; -use crate::continuous_batch_arbiter_state::ContinuousBatchArbiterState; -use crate::continuous_batch_preparation_request::ContinuousBatchPreparationRequest; - -const STATE_APPLICATION_RETRY_INTERVAL: Duration = Duration::from_secs(1); +use crate::pipeline_arbiter::PipelineArbiter; +use crate::pipeline_arbiter_params::PipelineArbiterParams; +use crate::pipeline_request::PipelineRequest; pub struct LlamaCppArbiterService { pub agent_applicable_state_holder: Arc, - pub arbiter_context: ContinuousBatchArbiterContext, - pub continuous_batch_preparation_request_rx: - mpsc::UnboundedReceiver, + pub inference_runtime_context: InferenceRuntimeContext, + pub pipeline_requests: Arc>, + pub served_applicable_state_holder: Arc, } -#[async_trait] -impl Service for LlamaCppArbiterService { - fn name(&self) -> &'static str { - "agent::llamacpp_arbiter_service" - } - - async fn run(self: Box, shutdown: CancellationToken) -> Result<()> { +impl LlamaCppArbiterService { + async fn arbitrate_pipelines_until_shutdown( + self: Box, + shutdown: CancellationToken, + ) -> Result<()> { let Self { agent_applicable_state_holder, - arbiter_context, - mut continuous_batch_preparation_request_rx, + inference_runtime_context, + pipeline_requests, + served_applicable_state_holder, } = *self; - - let mut arbiter_state = ContinuousBatchArbiterState::Idle; - let mut reconciled_state = agent_applicable_state_holder.subscribe(); - let mut ticker = interval_at( - Instant::now() + STATE_APPLICATION_RETRY_INTERVAL, - STATE_APPLICATION_RETRY_INTERVAL, - ); - - ticker.set_missed_tick_behavior(MissedTickBehavior::Delay); - - let shutdown_outcome = loop { + let mut agent_applicable_state_rx = agent_applicable_state_holder.subscribe(); + let mut pipeline_arbiter = PipelineArbiter::new(PipelineArbiterParams { + agent_shutdown: shutdown.clone(), + inference_runtime_context, + served_applicable_state_holder, + }); + + let service_outcome = loop { select! { biased; () = shutdown.cancelled() => break Ok(()), - _ = ticker.tick() => { - let slot_aggregated_status = &arbiter_context.slot_aggregated_status; - let current_status = slot_aggregated_status.get_state_application_status(); - - if current_status.should_try_to_apply() { - slot_aggregated_status.set_state_application_status( - if matches!(current_status, AgentStateApplicationStatus::AttemptedAndRetrying) { - AgentStateApplicationStatus::Stuck - } else { - AgentStateApplicationStatus::AttemptedAndRetrying - } - ); - - let agent_applicable_state = reconciled_state.borrow().clone(); - - arbiter_state = arbiter_state - .apply(&shutdown, agent_applicable_state, &arbiter_context) - .await; - } + Ok(()) = agent_applicable_state_rx.changed() => { + pipeline_arbiter.apply(agent_applicable_state_rx.borrow_and_update().clone()); } - Ok(()) = reconciled_state.changed() => { - let agent_applicable_state = reconciled_state.borrow_and_update().clone(); - - arbiter_context - .slot_aggregated_status - .set_state_application_status(AgentStateApplicationStatus::Fresh); - - arbiter_state = arbiter_state - .apply(&shutdown, agent_applicable_state, &arbiter_context) - .await; + finished_pipeline_startup = pipeline_arbiter.next_finished_startup() => { + pipeline_arbiter.conclude(finished_pipeline_startup); } - request = continuous_batch_preparation_request_rx.recv() => match request { - Some(request) => arbiter_state.forward(arbiter_context.agent_name.as_deref(), request), - None => break Ok(()), - }, + pipeline_request = pipeline_requests.next() => pipeline_arbiter.forward(pipeline_request), } }; - if let Err(shutdown_error) = arbiter_state.shut_down().await { - error!("Failed to shut down arbiter cleanly: {shutdown_error:#}"); - } + pipeline_arbiter.shut_down().await; + + service_outcome + } +} + +#[async_trait] +impl Service for LlamaCppArbiterService { + fn name(&self) -> &'static str { + "agent::llamacpp_arbiter_service" + } - shutdown_outcome + async fn run(self: Box, shutdown: CancellationToken) -> Result<()> { + self.arbitrate_pipelines_until_shutdown(shutdown).await } } @@ -104,36 +74,36 @@ impl Service for LlamaCppArbiterService { mod tests { use std::sync::Arc; - use tokio::sync::mpsc; use tokio_util::sync::CancellationToken; use trzcina::Service; - use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; + use paddler_agent_runtime::inference_runtime_context::InferenceRuntimeContext; + use paddler_agent_runtime::inference_runtime_context_params::InferenceRuntimeContextParams; + use paddler_outbox::outbox::Outbox; use super::LlamaCppArbiterService; use crate::agent_applicable_state_holder::AgentApplicableStateHolder; - use crate::continuous_batch_arbiter_context::ContinuousBatchArbiterContext; - use crate::model_metadata_holder::ModelMetadataHolder; #[tokio::test(flavor = "multi_thread")] - async fn exits_when_its_request_channel_closes() { - let (continuous_batch_preparation_request_tx, continuous_batch_preparation_request_rx) = - mpsc::unbounded_channel(); + async fn exits_when_the_agent_shuts_down() { + let agent_shutdown = CancellationToken::new(); let service = LlamaCppArbiterService { agent_applicable_state_holder: Arc::new(AgentApplicableStateHolder::default()), - arbiter_context: ContinuousBatchArbiterContext { - agent_name: None, - model_metadata_holder: Arc::new(ModelMetadataHolder::default()), - slot_aggregated_status: Arc::new(SlotAggregatedStatus::new(1)), - }, - continuous_batch_preparation_request_rx, + inference_runtime_context: InferenceRuntimeContext::new( + InferenceRuntimeContextParams { + agent_name: None, + slots: 1, + }, + ), + pipeline_requests: Arc::new(Outbox::default()), + served_applicable_state_holder: Arc::new(AgentApplicableStateHolder::default()), }; - drop(continuous_batch_preparation_request_tx); + agent_shutdown.cancel(); Box::new(service) - .run(CancellationToken::new()) + .run(agent_shutdown) .await - .expect("the service must finish cleanly once no request can arrive"); + .expect("the service must finish cleanly once the agent shuts down"); } } diff --git a/paddler_agent/src/loaded_model_serving_plan.rs b/paddler_agent/src/loaded_model_serving_plan.rs new file mode 100644 index 000000000..e05381336 --- /dev/null +++ b/paddler_agent/src/loaded_model_serving_plan.rs @@ -0,0 +1,36 @@ +use paddler_agent_runtime::loaded_llama_model::LoadedLlamaModel; + +use crate::capability_plan::CapabilityPlan; +use crate::inference_capabilities::InferenceCapabilities; +use crate::model_serving_settings::ModelServingSettings; +use crate::served_decisions::ServedDecisions; +use crate::served_embeddings::ServedEmbeddings; +use crate::served_text_generation::ServedTextGeneration; + +pub struct LoadedModelServingPlan { + pub capability_plans: InferenceCapabilities< + CapabilityPlan, + CapabilityPlan, + CapabilityPlan, + >, + pub loaded_llama_model: LoadedLlamaModel, + pub model_serving_settings: ModelServingSettings, +} + +impl LoadedModelServingPlan { + #[must_use] + pub const fn starting_every_capability( + loaded_llama_model: LoadedLlamaModel, + model_serving_settings: ModelServingSettings, + ) -> Self { + Self { + capability_plans: InferenceCapabilities { + decisions: CapabilityPlan::Start, + embeddings: CapabilityPlan::Start, + text_generation: CapabilityPlan::Start, + }, + loaded_llama_model, + model_serving_settings, + } + } +} diff --git a/paddler_agent/src/management_connection_end.rs b/paddler_agent/src/management_connection_end.rs new file mode 100644 index 000000000..141439d19 --- /dev/null +++ b/paddler_agent/src/management_connection_end.rs @@ -0,0 +1,89 @@ +use tokio_tungstenite::tungstenite::Utf8Bytes; +use tokio_tungstenite::tungstenite::protocol::CloseFrame; +use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode; + +const fn described_close_frame(code: CloseCode, reason: &'static str) -> CloseFrame { + CloseFrame { + code, + reason: Utf8Bytes::from_static(reason), + } +} + +#[derive(Debug, Eq, PartialEq)] +pub enum ManagementConnectionEnd { + AgentShuttingDown, + BalancerClosedConnection, + BalancerMessageUndeserializable, + BalancerReusedInFlightRequestId, + BalancerSentUnsupportedFrame, + BalancerStoppedAnsweringPings, + IncomingStreamFailed, + OutgoingMessageUnserializable, + OutgoingStreamFailed, +} + +impl ManagementConnectionEnd { + #[must_use] + pub const fn close_frame(&self) -> Option { + match self { + Self::AgentShuttingDown => Some(described_close_frame( + CloseCode::Away, + "Agent shutting down", + )), + Self::BalancerClosedConnection + | Self::BalancerStoppedAnsweringPings + | Self::IncomingStreamFailed + | Self::OutgoingStreamFailed => None, + Self::BalancerMessageUndeserializable => Some(described_close_frame( + CloseCode::Invalid, + "Balancer message could not be deserialized", + )), + Self::BalancerReusedInFlightRequestId => Some(described_close_frame( + CloseCode::Policy, + "Balancer reused the id of a request in flight", + )), + Self::BalancerSentUnsupportedFrame => Some(described_close_frame( + CloseCode::Unsupported, + "Balancer sent a frame that is not text", + )), + Self::OutgoingMessageUnserializable => Some(described_close_frame( + CloseCode::Error, + "Agent message could not be serialized", + )), + } + } +} + +#[cfg(test)] +mod tests { + use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode; + + use super::ManagementConnectionEnd; + + #[test] + fn an_abandoned_connection_tells_the_balancer_why_it_closes() { + let close_codes: Vec> = [ + ManagementConnectionEnd::BalancerMessageUndeserializable, + ManagementConnectionEnd::BalancerReusedInFlightRequestId, + ManagementConnectionEnd::BalancerSentUnsupportedFrame, + ManagementConnectionEnd::OutgoingMessageUnserializable, + ] + .iter() + .map(|connection_end| { + connection_end + .close_frame() + .map(|close_frame| close_frame.code) + }) + .collect(); + + assert_eq!( + close_codes, + vec![ + Some(CloseCode::Invalid), + Some(CloseCode::Policy), + Some(CloseCode::Unsupported), + Some(CloseCode::Error), + ] + ); + } +} diff --git a/paddler_agent/src/management_connection_step.rs b/paddler_agent/src/management_connection_step.rs new file mode 100644 index 000000000..bd30b14b4 --- /dev/null +++ b/paddler_agent/src/management_connection_step.rs @@ -0,0 +1,13 @@ +use futures_util::stream::BoxStream; + +use paddler_messaging::management_socket::balancer::message::Message as ManagementJsonRpcMessage; + +use crate::management_connection_end::ManagementConnectionEnd; + +pub enum ManagementConnectionStep { + Continue, + Deregister, + End(ManagementConnectionEnd), + StreamResponses(BoxStream<'static, ManagementJsonRpcMessage>), + Write(ManagementJsonRpcMessage), +} diff --git a/paddler_agent/src/management_socket_client_service.rs b/paddler_agent/src/management_socket_client_service.rs index f72775ec0..3996bdb28 100644 --- a/paddler_agent/src/management_socket_client_service.rs +++ b/paddler_agent/src/management_socket_client_service.rs @@ -1,415 +1,210 @@ -use anyhow::Context; use anyhow::Result; use async_trait::async_trait; -use futures_util::SinkExt as _; -use futures_util::StreamExt; -use log::debug; +use futures_util::StreamExt as _; +use futures_util::stream::BoxStream; +use futures_util::stream::SelectAll; +use futures_util::stream::SplitStream; use log::error; use log::info; -use log::warn; -use serde_json::from_str; +use tokio::join; use tokio::net::TcpStream; use tokio::select; -use tokio::spawn; use tokio::sync::mpsc; -use tokio::sync::mpsc::error::SendError; -use tokio::sync::watch; use tokio::time::Duration; +use tokio::time::Instant; use tokio::time::MissedTickBehavior; use tokio::time::interval; +use tokio::time::interval_at; use tokio_tungstenite::MaybeTlsStream; use tokio_tungstenite::WebSocketStream; use tokio_tungstenite::connect_async_with_config; +use tokio_tungstenite::tungstenite::Bytes; use tokio_tungstenite::tungstenite::protocol::Message; use tokio_util::sync::CancellationToken; use trzcina::Service; -use paddler_messaging::balancer_connection::BalancerConnection; -use paddler_messaging::embedding_result::EmbeddingResult; -use paddler_messaging::generated_token_result::GeneratedTokenResult; -use paddler_messaging::jsonrpc::request_envelope::RequestEnvelope; -use paddler_messaging::jsonrpc::response_envelope::ResponseEnvelope; -use paddler_messaging::management_socket::agent::message::Message as JsonRpcMessage; -use paddler_messaging::management_socket::agent::notification::Notification as JsonRpcNotification; -use paddler_messaging::management_socket::agent::notification_params::version_params::VersionParams; -use paddler_messaging::management_socket::agent::request::Request as JsonRpcRequest; -use paddler_messaging::management_socket::agent::response::Response as JsonRpcResponse; +use paddler_frame_writer::frame_writing_end::FrameWritingEnd; +use paddler_frame_writer::write_frames_until_connection_closes::write_frames_until_connection_closes; use paddler_messaging::management_socket::balancer::message::Message as ManagementJsonRpcMessage; use paddler_messaging::management_socket::balancer::notification::Notification as ManagementJsonRpcNotification; use paddler_messaging::management_socket::balancer::notification_params::register_agent_params::RegisterAgentParams; -use paddler_messaging::management_socket::balancer::notification_params::update_agent_status_params::UpdateAgentStatusParams; -use paddler_messaging::produces_snapshot::ProducesSnapshot; +use paddler_messaging::ping_interval::PING_INTERVAL; +use paddler_messaging::ping_liveness::PingLiveness; +use paddler_messaging::ping_turn::PingTurn; +use paddler_messaging::produces_snapshot::ProducesSnapshot as _; use paddler_messaging::subscribes_to_updates::SubscribesToUpdates as _; -use paddler_request_registry::request_delivery::RequestDelivery; -use paddler_request_registry::request_registry_guard::RequestRegistryGuard; -use crate::agent_request::AgentRequest; use crate::balancer_message_context::BalancerMessageContext; -use crate::continuous_batch_preparation_request::ContinuousBatchPreparationRequest; -use crate::forward_management_socket_messages::forward_management_socket_messages; -use crate::slot_guard::SlotGuard; +use crate::enqueue_management_message::enqueue_management_message; +use crate::enqueue_outbound_frame::enqueue_outbound_frame; +use crate::last_announced_agent_status::LastAnnouncedAgentStatus; +use crate::management_connection_end::ManagementConnectionEnd; +use crate::management_connection_step::ManagementConnectionStep; const BALANCER_RECONNECT_INTERVAL: Duration = Duration::from_secs(1); -struct IncomingMessageContext { - balancer_message_context: BalancerMessageContext, - connection_close: CancellationToken, - message_tx: mpsc::UnboundedSender, -} - -impl IncomingMessageContext { - fn generate_responses(self, id: String, params: TParams) -> Result<()> - where - AgentRequest: Into, - TResponse: Into + Send + 'static, - { - let Self { - balancer_message_context: - BalancerMessageContext { - continuous_batch_preparation_request_tx, - request_stoppers, - slot_aggregated_status, - .. - }, - connection_close, - message_tx, - } = self; - let (response_tx, mut response_rx) = mpsc::unbounded_channel::(); - let (stop_tx, stop_rx) = mpsc::unbounded_channel::<()>(); - - let stopper_guard = RequestRegistryGuard::register(&request_stoppers, id.clone(), stop_tx) - .context(format!("Failed to register stopper for request: {id}"))?; - - continuous_batch_preparation_request_tx.send( - AgentRequest { - params, - response_tx, - slot_guard: SlotGuard::new(slot_aggregated_status), - stop_rx, - } - .into(), - )?; - - spawn(async move { - let _stopper_guard = stopper_guard; - - loop { - select! { - () = connection_close.cancelled() => break, - response = response_rx.recv() => { - match response { - Some(response) => { - if let Err(err) = message_tx.send( - ManagementJsonRpcMessage::Response( - ResponseEnvelope { - generated_by: None, - request_id: id.clone(), - response: response.into(), - } - ), - ) { - error!("Failed to forward response for request {id:?}: {err}"); - - break; - } - } - None => break, - } - } - } - } - }); - - Ok(()) - } - - fn handle_deserialized_message(self, deserialized_message: JsonRpcMessage) -> Result<()> { - match deserialized_message { - JsonRpcMessage::Notification(JsonRpcNotification::SetState(set_state_params)) => { - self.balancer_message_context - .agent_desired_state_tx - .send(set_state_params.desired_state)?; - - Ok(()) - } - JsonRpcMessage::Notification(JsonRpcNotification::StopRespondingTo(request_id)) => { - debug!("Received StopGeneratingTokens notification for request ID: {request_id:?}"); - match self - .balancer_message_context - .request_stoppers - .send_to(&request_id, ()) - { - RequestDelivery::Delivered => Ok(()), - RequestDelivery::ReceiverDropped | RequestDelivery::RequestNotRegistered => { - debug!("Request {request_id:?} finished before its stop arrived"); - - Ok(()) - } - } - } - JsonRpcMessage::Notification(JsonRpcNotification::Version(VersionParams { - version, - })) => { - if version != env!("CARGO_PKG_VERSION") { - warn!( - "Version mismatch: server version is {version}, client version is {}", - env!("CARGO_PKG_VERSION") - ); - } - - Ok(()) - } - JsonRpcMessage::Request(RequestEnvelope { - id, - request: - JsonRpcRequest::ContinueFromConversationHistory( - continue_from_conversation_history_params, - ), - }) => self.generate_responses::<_, GeneratedTokenResult>( - id, - continue_from_conversation_history_params, - ), - JsonRpcMessage::Request(RequestEnvelope { - id, - request: JsonRpcRequest::ContinueFromRawPrompt(generate_tokens_params), - }) => self.generate_responses::<_, GeneratedTokenResult>(id, generate_tokens_params), - JsonRpcMessage::Request(RequestEnvelope { - id, - request: JsonRpcRequest::GenerateEmbeddingBatch(generate_embedding_batch_params), - }) => { - self.generate_responses::<_, EmbeddingResult>(id, generate_embedding_batch_params) - } - JsonRpcMessage::Request(RequestEnvelope { - id, - request: JsonRpcRequest::GetChatTemplateOverride, - }) => { - Ok(self - .message_tx - .send(ManagementJsonRpcMessage::Response(ResponseEnvelope { - generated_by: None, - request_id: id, - response: JsonRpcResponse::ChatTemplateOverride( - self.balancer_message_context - .agent_applicable_state_holder - .get_agent_applicable_state() - .chat_template_override, - ), - }))?) - } - JsonRpcMessage::Request(RequestEnvelope { - id, - request: JsonRpcRequest::GetModelMetadata, - }) => { - Ok(self - .message_tx - .send(ManagementJsonRpcMessage::Response(ResponseEnvelope { - generated_by: None, - request_id: id, - response: JsonRpcResponse::ModelMetadata( - self.balancer_message_context - .model_metadata_holder - .get_model_metadata(), - ), - }))?) - } - } - } - - fn handle_incoming_message(self, msg: Message) { - match msg { - Message::Text(text) => match from_str::(&text) { - Ok(deserialized_message) => { - if let Err(err) = self.handle_deserialized_message(deserialized_message) { - error!("Error handling incoming message: {err}"); - } - } - Err(deserialization_error) => { - error!( - "Closing the management connection after an undeserializable message {text}: {deserialization_error}" - ); - - self.connection_close.cancel(); - } - }, - Message::Binary(_) | Message::Frame(_) => { - error!("Closing the management connection after a non-text message"); - - self.connection_close.cancel(); - } - Message::Close(_) => { - info!("Connection closed by server"); - } - Message::Ping(_) | Message::Pong(_) => {} - } - } -} - pub struct ManagementSocketClientService { - pub balancer_connection_tx: watch::Sender, pub balancer_message_context: BalancerMessageContext, pub name: Option, pub socket_url: String, } impl ManagementSocketClientService { - async fn keep_connection_alive(&self, shutdown: CancellationToken) -> Result<()> { - match shutdown - .run_until_cancelled( - self.balancer_message_context - .slot_aggregated_status - .wait_until_no_slots_are_processing(), - ) - .await - { - Some(idle_outcome) => idle_outcome - .context("Slot status stopped announcing updates while waiting for idle slots")?, - None => return Ok(()), - } + async fn connect_once_slots_are_idle( + &self, + ) -> Result>> { + self.balancer_message_context + .slot_aggregated_status + .wait_until_no_slots_are_processing() + .await; info!("Connecting to management server at {}", self.socket_url); - let (ws_stream, _response) = match shutdown - .run_until_cancelled(connect_async_with_config( - self.socket_url.clone(), - None, - true, - )) - .await - { - Some(connect_outcome) => connect_outcome?, - None => return Ok(()), - }; + let (ws_stream, _response) = + connect_async_with_config(self.socket_url.clone(), None, true).await?; info!("Connected to management server"); - self.balancer_connection_tx - .send_replace(BalancerConnection::Connected); - - let connection_outcome = self.serve_connection(ws_stream, shutdown).await; + Ok(ws_stream) + } - self.balancer_connection_tx - .send_replace(BalancerConnection::Connecting); + async fn keep_connection_alive(&self, shutdown: CancellationToken) -> Result<()> { + if let Some(connect_outcome) = shutdown + .run_until_cancelled(self.connect_once_slots_are_idle()) + .await + { + self.serve_connection(connect_outcome?, shutdown).await; + } - connection_outcome + Ok(()) } async fn serve_connection( &self, ws_stream: WebSocketStream>, shutdown: CancellationToken, - ) -> Result<()> { + ) { + let (ws_sink, ws_source) = ws_stream.split(); + let (outbound_tx, outbound_rx) = mpsc::unbounded_channel(); let connection_close = CancellationToken::new(); - let (message_tx, message_rx) = mpsc::unbounded_channel::(); - let (write, mut read) = ws_stream.split(); - - let message_forward_handle = spawn(forward_management_socket_messages( - connection_close.clone(), - message_rx, - write, - )); - let mut update_rx = self - .balancer_message_context - .slot_aggregated_status - .subscribe_to_updates(); - - message_tx - .send(ManagementJsonRpcMessage::Notification( - ManagementJsonRpcNotification::RegisterAgent(RegisterAgentParams { - name: self.name.clone(), - slot_aggregated_status_snapshot: self - .balancer_message_context - .slot_aggregated_status - .make_snapshot(), - }), - )) - .context("The management socket writer stopped before the agent registered")?; + let ((), frame_writing_end) = join!( + self.read_until_the_connection_ends( + connection_close.clone(), + ws_source, + outbound_tx, + shutdown + ), + write_frames_until_connection_closes(connection_close, outbound_rx, ws_sink), + ); - let agent_ends_connection = loop { - select! { - () = connection_close.cancelled() => { - info!("Connection close signal received, shutting down"); + match frame_writing_end { + FrameWritingEnd::ConnectionClosed => { + info!("Abandoned the management socket frames the balancer did not read"); + } + FrameWritingEnd::EveryFrameSent => {} + FrameWritingEnd::SendFailed(send_error) => { + error!("Failed to send a management socket frame: {send_error}"); + } + } + } - break false; - } - () = shutdown.cancelled() => break true, - Ok(()) = update_rx.changed() => { - let status_update = ManagementJsonRpcMessage::Notification( - ManagementJsonRpcNotification::UpdateAgentStatus(UpdateAgentStatusParams { - slot_aggregated_status_snapshot: self - .balancer_message_context - .slot_aggregated_status - .make_snapshot(), - }), - ); + async fn read_until_the_connection_ends( + &self, + connection_close: CancellationToken, + mut ws_source: SplitStream>>, + outbound_tx: mpsc::UnboundedSender, + shutdown: CancellationToken, + ) { + let slot_aggregated_status = &self.balancer_message_context.slot_aggregated_status; + let mut update_rx = slot_aggregated_status.subscribe_to_updates(); + let registration_snapshot = slot_aggregated_status.make_snapshot(); + let mut last_announced_agent_status = + LastAnnouncedAgentStatus::new(registration_snapshot.status.clone()); + let mut pipeline_responses = + SelectAll::>::new(); + let mut ping_ticker = interval_at(Instant::now() + PING_INTERVAL, PING_INTERVAL); + let mut ping_liveness = PingLiveness::default(); + let mut step = ManagementConnectionStep::Write(ManagementJsonRpcMessage::Notification( + ManagementJsonRpcNotification::RegisterAgent(RegisterAgentParams { + name: self.name.clone(), + slot_aggregated_status_snapshot: registration_snapshot, + }), + )); - if let Err(SendError(_undelivered_status_update)) = message_tx.send(status_update) { - info!("The management socket writer stopped, reconnecting"); + ping_ticker.set_missed_tick_behavior(MissedTickBehavior::Delay); - break false; - } - } - msg = read.next() => { - let should_close = match msg { - Some(Ok(msg)) => { - IncomingMessageContext { - balancer_message_context: self.balancer_message_context.clone(), - connection_close: connection_close.clone(), - message_tx: message_tx.clone(), - } - .handle_incoming_message(msg); + let connection_end = loop { + step = match step { + ManagementConnectionStep::Continue => select! { + biased; + () = shutdown.cancelled() => ManagementConnectionStep::Deregister, + incoming_message = ws_source.next() => match incoming_message { + Some(Ok(Message::Pong(_))) => { + ping_liveness.record_pong(); - false + ManagementConnectionStep::Continue } - Some(Err(err)) => { - error!("Error reading message: {err}"); + Some(Ok(message)) => self.balancer_message_context.handle_message(message), + Some(Err(read_error)) => { + error!("Failed to read from the management socket: {read_error}"); - true + ManagementConnectionStep::End(ManagementConnectionEnd::IncomingStreamFailed) } - None => true, - }; - - if should_close { - connection_close.cancel(); - - break false; + None => ManagementConnectionStep::End(ManagementConnectionEnd::BalancerClosedConnection), + }, + _ = ping_ticker.tick() => match ping_liveness.next_ping() { + PingTurn::PeerStoppedAnswering => { + ManagementConnectionStep::End(ManagementConnectionEnd::BalancerStoppedAnsweringPings) + } + PingTurn::SendPing => enqueue_outbound_frame( + &outbound_tx, + Message::Ping(Bytes::new()), + ManagementConnectionStep::Continue, + ), + }, + Ok(()) = update_rx.changed() => last_announced_agent_status + .announce_if_changed(slot_aggregated_status.make_snapshot()), + Some(pipeline_response) = pipeline_responses.next() => { + ManagementConnectionStep::Write(pipeline_response) } + }, + ManagementConnectionStep::Deregister => enqueue_management_message( + &outbound_tx, + &ManagementJsonRpcMessage::Notification( + ManagementJsonRpcNotification::DeregisterAgent, + ), + ManagementConnectionStep::End(ManagementConnectionEnd::AgentShuttingDown), + ), + ManagementConnectionStep::End(connection_end) => break connection_end, + ManagementConnectionStep::StreamResponses(pipeline_response_stream) => { + pipeline_responses.push(pipeline_response_stream); + + ManagementConnectionStep::Continue } - } + ManagementConnectionStep::Write(message) => enqueue_management_message( + &outbound_tx, + &message, + ManagementConnectionStep::Continue, + ), + }; }; - if let Err(SendError(_undelivered_deregistration)) = message_tx.send( - ManagementJsonRpcMessage::Notification(ManagementJsonRpcNotification::DeregisterAgent), - ) { - info!( - "The management socket writer already stopped; the balancer deregisters the agent once the connection drops" - ); - } + let close_frame_delivery = outbound_tx.send(Message::Close(connection_end.close_frame())); connection_close.cancel(); - let mut write = message_forward_handle - .await - .context("Failed to join message forwarding task")?; - - if agent_ends_connection { - write - .send(Message::Close(None)) - .await - .context("Failed to close the management socket")?; - } - - Ok(()) + info!( + "Closing the management connection after {connection_end:?}: {close_frame_delivery:?}" + ); } } -#[async_trait] -impl Service for ManagementSocketClientService { - fn name(&self) -> &'static str { - "agent::management_socket_client_service" - } - - async fn run(self: Box, shutdown: CancellationToken) -> Result<()> { +impl ManagementSocketClientService { + async fn reconnect_to_the_balancer_until_shutdown( + self: Box, + shutdown: CancellationToken, + ) -> Result<()> { let mut ticker = interval(BALANCER_RECONNECT_INTERVAL); ticker.set_missed_tick_behavior(MissedTickBehavior::Delay); @@ -432,605 +227,14 @@ impl Service for ManagementSocketClientService { } } -#[cfg(test)] -mod tests { - use std::collections::BTreeMap; - use std::num::NonZeroU32; - use std::sync::Arc; - - use futures_util::StreamExt as _; - use serde_json::from_str; - use serde_json::to_string; - use tokio::net::TcpListener; - use tokio::spawn; - use tokio::sync::mpsc; - use tokio::sync::oneshot; - use tokio::sync::watch; - use tokio::task::JoinHandle; - use tokio_tungstenite::accept_async; - use tokio_tungstenite::tungstenite::Bytes; - use tokio_tungstenite::tungstenite::protocol::Message; - use tokio_util::sync::CancellationToken; - use trzcina::Service; - - use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; - use paddler_messaging::agent_desired_state::AgentDesiredState; - use paddler_messaging::agent_status::AgentStatus; - use paddler_messaging::api_path::ApiPath; - use paddler_messaging::balancer_connection::BalancerConnection; - use paddler_messaging::generated_token_result::GeneratedTokenResult; - use paddler_messaging::jsonrpc::request_envelope::RequestEnvelope; - use paddler_messaging::jsonrpc::response_envelope::ResponseEnvelope; - use paddler_messaging::management_socket::agent::message::Message as JsonRpcMessage; - use paddler_messaging::management_socket::agent::notification::Notification as JsonRpcNotification; - use paddler_messaging::management_socket::agent::notification_params::set_state_params::SetStateParams; - use paddler_messaging::management_socket::agent::notification_params::version_params::VersionParams; - use paddler_messaging::management_socket::agent::request::Request as JsonRpcRequest; - use paddler_messaging::management_socket::agent::response::Response as JsonRpcResponse; - use paddler_messaging::management_socket::balancer::message::Message as ManagementJsonRpcMessage; - use paddler_messaging::management_socket::balancer::notification::Notification as ManagementJsonRpcNotification; - use paddler_messaging::management_socket::balancer::notification_params::update_agent_status_params::UpdateAgentStatusParams; - use paddler_messaging::model_metadata::ModelMetadata; - use paddler_messaging::request_params::continue_from_raw_prompt_params::ContinueFromRawPromptParams; - use paddler_messaging::slot_aggregated_status_snapshot::SlotAggregatedStatusSnapshot; - use paddler_request_registry::request_delivery::RequestDelivery; - use paddler_request_registry::request_registry_guard::RequestRegistryGuard; - - use super::IncomingMessageContext; - use super::ManagementSocketClientService; - use crate::agent_applicable_state_holder::AgentApplicableStateHolder; - use crate::balancer_message_context::BalancerMessageContext; - use crate::continuous_batch_preparation_request::ContinuousBatchPreparationRequest; - use crate::model_metadata_holder::ModelMetadataHolder; - - struct IncomingMessageFixture { - agent_desired_state_rx: mpsc::UnboundedReceiver, - context: IncomingMessageContext, - continuous_batch_preparation_request_rx: - mpsc::UnboundedReceiver, - message_rx: mpsc::UnboundedReceiver, - } - - fn incoming_message_fixture() -> IncomingMessageFixture { - let (agent_desired_state_tx, agent_desired_state_rx) = mpsc::unbounded_channel(); - let (continuous_batch_preparation_request_tx, continuous_batch_preparation_request_rx) = - mpsc::unbounded_channel(); - let (message_tx, message_rx) = mpsc::unbounded_channel(); - - IncomingMessageFixture { - agent_desired_state_rx, - context: IncomingMessageContext { - balancer_message_context: BalancerMessageContext { - agent_applicable_state_holder: Arc::new(AgentApplicableStateHolder::default()), - agent_desired_state_tx, - continuous_batch_preparation_request_tx, - model_metadata_holder: Arc::new(ModelMetadataHolder::new()), - request_stoppers: Arc::default(), - slot_aggregated_status: Arc::new(SlotAggregatedStatus::new(2)), - }, - connection_close: CancellationToken::new(), - message_tx, - }, - continuous_batch_preparation_request_rx, - message_rx, - } - } - - fn service_with_socket_url(socket_url: String) -> ManagementSocketClientService { - let (balancer_connection_tx, _initial_balancer_connection_rx) = - watch::channel(BalancerConnection::Connecting); - - ManagementSocketClientService { - balancer_connection_tx, - balancer_message_context: incoming_message_fixture().context.balancer_message_context, - name: None, - socket_url, - } - } - - fn raw_prompt_params() -> ContinueFromRawPromptParams { - ContinueFromRawPromptParams { - grammar: None, - max_tokens: NonZeroU32::new(8).unwrap(), - raw_prompt: "hello".to_owned(), - } - } - - fn set_state_message() -> JsonRpcMessage { - JsonRpcMessage::Notification(JsonRpcNotification::SetState(Box::new(SetStateParams { - desired_state: AgentDesiredState::default(), - }))) - } - - struct StalledHandshakeFixture { - accepted_rx: oneshot::Receiver<()>, - server: JoinHandle<()>, - shutdown: CancellationToken, - } - - fn spawn_stalled_handshake_fixture(listener: TcpListener) -> StalledHandshakeFixture { - let (accepted_tx, accepted_rx) = oneshot::channel::<()>(); - let shutdown = CancellationToken::new(); - let server_shutdown = shutdown.clone(); - - let server = spawn(async move { - let (_stream, _peer_addr) = listener - .accept() - .await - .expect("the fixture balancer must accept the agent connection"); - - accepted_tx - .send(()) - .expect("the test must still be waiting for the accept signal"); - - server_shutdown.cancelled().await; - }); - - StalledHandshakeFixture { - accepted_rx, - server, - shutdown, - } - } - - #[tokio::test] - async fn keep_connection_alive_returns_when_shutdown_arrives_during_a_stalled_handshake() { - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - let StalledHandshakeFixture { - accepted_rx, - server, - shutdown: fixture_shutdown, - } = spawn_stalled_handshake_fixture(listener); - - let service = service_with_socket_url(format!( - "ws://{addr}{}", - ApiPath::agent_socket("test-agent") - )); - let shutdown = CancellationToken::new(); - let keep_alive_shutdown = shutdown.clone(); - let keep_alive_handle = - spawn(async move { service.keep_connection_alive(keep_alive_shutdown).await }); - - accepted_rx - .await - .expect("the agent's connect must reach the fixture balancer before shutdown"); - - shutdown.cancel(); - - let keep_alive_result = keep_alive_handle - .await - .expect("the keep_connection_alive task must not panic"); - - assert!(keep_alive_result.is_ok()); - - fixture_shutdown.cancel(); - server - .await - .expect("the fixture balancer task must not panic"); - } - - #[tokio::test] - async fn keep_connection_alive_errors_when_the_balancer_refuses_the_connection() { - let service = service_with_socket_url(format!( - "ws://127.0.0.1:1{}", - ApiPath::agent_socket("test-agent") - )); - let shutdown = CancellationToken::new(); - - let keep_alive_result = service.keep_connection_alive(shutdown).await; - - assert!(keep_alive_result.is_err()); - } - - #[tokio::test] - async fn run_returns_when_shutdown_arrives_during_a_stalled_handshake() { - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - let StalledHandshakeFixture { - accepted_rx, - server, - shutdown: fixture_shutdown, - } = spawn_stalled_handshake_fixture(listener); - - let service = service_with_socket_url(format!( - "ws://{addr}{}", - ApiPath::agent_socket("test-agent") - )); - let shutdown = CancellationToken::new(); - let run_shutdown = shutdown.clone(); - let run_handle = spawn(async move { Box::new(service).run(run_shutdown).await }); - - accepted_rx - .await - .expect("the agent's connect must reach the fixture balancer before shutdown"); - - shutdown.cancel(); - - let run_result = run_handle.await.expect("the run task must not panic"); - - assert!(run_result.is_ok()); - - fixture_shutdown.cancel(); - server - .await - .expect("the fixture balancer task must not panic"); - } - - #[tokio::test] - async fn announces_every_status_change_to_the_balancer() { - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - let service = service_with_socket_url(format!( - "ws://{addr}{}", - ApiPath::agent_socket("test-agent") - )); - let slot_aggregated_status = service - .balancer_message_context - .slot_aggregated_status - .clone(); - let shutdown = CancellationToken::new(); - let keep_alive_shutdown = shutdown.clone(); - let keep_alive_handle = - spawn(async move { service.keep_connection_alive(keep_alive_shutdown).await }); - - let (stream, _peer_addr) = listener.accept().await.unwrap(); - let mut balancer_socket = accept_async(stream).await.unwrap(); - let registration = balancer_socket.next().await.unwrap().unwrap(); - - assert!(matches!( - from_str::(registration.to_text().unwrap()).unwrap(), - ManagementJsonRpcMessage::Notification(ManagementJsonRpcNotification::RegisterAgent( - register_agent_params - )) if register_agent_params.name.is_none() - )); - - slot_aggregated_status.set_uses_chat_template_override(true); - - let status_update = balancer_socket.next().await.unwrap().unwrap(); - - assert!(matches!( - from_str::(status_update.to_text().unwrap()).unwrap(), - ManagementJsonRpcMessage::Notification( - ManagementJsonRpcNotification::UpdateAgentStatus(UpdateAgentStatusParams { - slot_aggregated_status_snapshot: SlotAggregatedStatusSnapshot { - status: AgentStatus { - uses_chat_template_override: true, - .. - }, - .. - }, - }) - ) - )); - - shutdown.cancel(); - - assert!(keep_alive_handle.await.unwrap().is_ok()); +#[async_trait] +impl Service for ManagementSocketClientService { + fn name(&self) -> &'static str { + "agent::management_socket_client_service" } - #[tokio::test] - async fn reports_the_balancer_connected_only_while_its_socket_is_open() { - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - let service = service_with_socket_url(format!( - "ws://{addr}{}", - ApiPath::agent_socket("test-agent") - )); - let mut balancer_connection_rx = service.balancer_connection_tx.subscribe(); - let keep_alive_handle = spawn(async move { - service - .keep_connection_alive(CancellationToken::new()) - .await - }); - - let (stream, _peer_addr) = listener.accept().await.unwrap(); - let balancer_socket = accept_async(stream).await.unwrap(); - - balancer_connection_rx - .wait_for(|balancer_connection| *balancer_connection == BalancerConnection::Connected) - .await - .unwrap(); - - drop(balancer_socket); - - balancer_connection_rx - .wait_for(|balancer_connection| *balancer_connection == BalancerConnection::Connecting) + async fn run(self: Box, shutdown: CancellationToken) -> Result<()> { + self.reconnect_to_the_balancer_until_shutdown(shutdown) .await - .unwrap(); - - assert!(keep_alive_handle.await.unwrap().is_ok()); - } - - #[tokio::test] - async fn set_state_notification_forwards_desired_state() { - let mut fixture = incoming_message_fixture(); - - fixture - .context - .handle_deserialized_message(set_state_message()) - .unwrap(); - - assert_eq!( - fixture.agent_desired_state_rx.try_recv().unwrap(), - AgentDesiredState::default() - ); - } - - #[tokio::test] - async fn set_state_notification_errors_when_receiver_dropped() { - let fixture = incoming_message_fixture(); - - drop(fixture.agent_desired_state_rx); - - assert!( - fixture - .context - .handle_deserialized_message(set_state_message()) - .is_err() - ); - } - - #[tokio::test] - async fn stop_responding_to_a_finished_request_is_not_an_error_and_retains_nothing() { - let fixture = incoming_message_fixture(); - let request_stoppers = fixture - .context - .balancer_message_context - .request_stoppers - .clone(); - - fixture - .context - .handle_deserialized_message(JsonRpcMessage::Notification( - JsonRpcNotification::StopRespondingTo("already_finished".to_owned()), - )) - .expect("a stop for a request that already finished must not be an error"); - - assert!( - request_stoppers - .with_registered("already_finished", |_registered_stopper| ()) - .is_none(), - "the stop must leave nothing behind; retaining it would leak one entry per cancelled \ - request" - ); - } - - #[tokio::test] - async fn stop_responding_to_registered_request_signals_stopper() { - let fixture = incoming_message_fixture(); - let (stop_tx, mut stop_rx) = mpsc::unbounded_channel::<()>(); - - let _stopper_guard = RequestRegistryGuard::register( - &fixture.context.balancer_message_context.request_stoppers, - "active_request".to_owned(), - stop_tx, - ) - .expect("a fresh request id must register"); - - fixture - .context - .handle_deserialized_message(JsonRpcMessage::Notification( - JsonRpcNotification::StopRespondingTo("active_request".to_owned()), - )) - .unwrap(); - - assert_eq!(stop_rx.try_recv(), Ok(())); - } - - #[tokio::test] - async fn mismatched_version_notification_is_acknowledged() { - let mut fixture = incoming_message_fixture(); - - fixture - .context - .handle_deserialized_message(JsonRpcMessage::Notification( - JsonRpcNotification::Version(VersionParams { - version: "0.0.0-mismatch".to_owned(), - }), - )) - .unwrap(); - - assert!(fixture.message_rx.try_recv().is_err()); - } - - #[tokio::test] - async fn get_chat_template_override_without_applicable_state_responds_with_none() { - let mut fixture = incoming_message_fixture(); - - fixture - .context - .handle_deserialized_message(JsonRpcMessage::Request(RequestEnvelope { - id: "req_template".to_owned(), - request: JsonRpcRequest::GetChatTemplateOverride, - })) - .unwrap(); - - assert!(matches!( - fixture.message_rx.try_recv().unwrap(), - ManagementJsonRpcMessage::Response(ResponseEnvelope { - request_id, - response: JsonRpcResponse::ChatTemplateOverride(None), - .. - }) if request_id == "req_template" - )); - } - - #[tokio::test] - async fn get_chat_template_override_errors_when_message_receiver_dropped() { - let fixture = incoming_message_fixture(); - - drop(fixture.message_rx); - - assert!( - fixture - .context - .handle_deserialized_message(JsonRpcMessage::Request(RequestEnvelope { - id: "req_template".to_owned(), - request: JsonRpcRequest::GetChatTemplateOverride, - })) - .is_err() - ); - } - - #[tokio::test] - async fn get_model_metadata_responds_with_stored_metadata() { - let mut fixture = incoming_message_fixture(); - let mut metadata = BTreeMap::new(); - - metadata.insert("architecture".to_owned(), "llama".to_owned()); - fixture - .context - .balancer_message_context - .model_metadata_holder - .set_model_metadata(ModelMetadata { - metadata: metadata.clone(), - }); - - fixture - .context - .handle_deserialized_message(JsonRpcMessage::Request(RequestEnvelope { - id: "req_metadata".to_owned(), - request: JsonRpcRequest::GetModelMetadata, - })) - .unwrap(); - - assert!(matches!( - fixture.message_rx.try_recv().unwrap(), - ManagementJsonRpcMessage::Response(ResponseEnvelope { - response: JsonRpcResponse::ModelMetadata(Some(returned_metadata)), - .. - }) if returned_metadata.metadata == metadata - )); - } - - #[tokio::test] - async fn get_model_metadata_errors_when_message_receiver_dropped() { - let fixture = incoming_message_fixture(); - - drop(fixture.message_rx); - - assert!( - fixture - .context - .handle_deserialized_message(JsonRpcMessage::Request(RequestEnvelope { - id: "req_metadata".to_owned(), - request: JsonRpcRequest::GetModelMetadata, - })) - .is_err() - ); - } - - #[tokio::test] - async fn text_message_dispatches_deserialized_set_state() { - let mut fixture = incoming_message_fixture(); - - fixture.context.handle_incoming_message(Message::Text( - to_string(&set_state_message()).unwrap().into(), - )); - - assert_eq!( - fixture.agent_desired_state_rx.recv().await.unwrap(), - AgentDesiredState::default() - ); - } - - #[tokio::test] - async fn undecodable_text_message_closes_the_connection() { - let fixture = incoming_message_fixture(); - let connection_close = fixture.context.connection_close.clone(); - - fixture - .context - .handle_incoming_message(Message::Text("not json".into())); - - assert!(connection_close.is_cancelled()); - } - - #[tokio::test] - async fn binary_message_closes_the_connection() { - let fixture = incoming_message_fixture(); - let connection_close = fixture.context.connection_close.clone(); - - fixture - .context - .handle_incoming_message(Message::Binary(Bytes::from_static(b"\x00\x01"))); - - assert!(connection_close.is_cancelled()); - } - - #[tokio::test] - async fn generate_responses_registers_the_stopper_before_it_returns() { - let mut fixture = incoming_message_fixture(); - let request_stoppers = fixture - .context - .balancer_message_context - .request_stoppers - .clone(); - - fixture - .context - .generate_responses::<_, GeneratedTokenResult>( - "req_generate".to_owned(), - raw_prompt_params(), - ) - .expect("the request must be accepted"); - - let dispatched_request = fixture - .continuous_batch_preparation_request_rx - .try_recv() - .expect("the request must be dispatched to the arbiter"); - - assert!(matches!( - &dispatched_request, - ContinuousBatchPreparationRequest::ContinueFromRawPrompt(request) - if request.params.raw_prompt == "hello" - )); - assert_eq!( - request_stoppers.send_to("req_generate", ()), - RequestDelivery::Delivered, - "the stopper must be registered before generate_responses returns, so a stop arriving \ - on the next frame cannot be lost" - ); - } - - #[tokio::test] - async fn generate_responses_errors_when_request_receiver_dropped() { - let fixture = incoming_message_fixture(); - - drop(fixture.continuous_batch_preparation_request_rx); - - assert!( - fixture - .context - .generate_responses::<_, GeneratedTokenResult>( - "req_generate".to_owned(), - raw_prompt_params(), - ) - .is_err() - ); - } - - #[tokio::test] - async fn generate_responses_errors_when_stopper_already_registered() { - let fixture = incoming_message_fixture(); - let (existing_stop_tx, _existing_stop_rx) = mpsc::unbounded_channel::<()>(); - - let _existing_stopper_guard = RequestRegistryGuard::register( - &fixture.context.balancer_message_context.request_stoppers, - "req_generate".to_owned(), - existing_stop_tx, - ) - .expect("a fresh request id must register"); - - assert!( - fixture - .context - .generate_responses::<_, GeneratedTokenResult>( - "req_generate".to_owned(), - raw_prompt_params(), - ) - .is_err() - ); } } diff --git a/paddler_agent/src/model_bos_token.rs b/paddler_agent/src/model_bos_token.rs deleted file mode 100644 index 10b86efa2..000000000 --- a/paddler_agent/src/model_bos_token.rs +++ /dev/null @@ -1,54 +0,0 @@ -use llama_cpp_bindings::model::LlamaModel; -use llama_cpp_bindings::token::LlamaToken; - -pub struct ModelBosToken { - pub added_by_tokenizer: bool, - pub token: LlamaToken, -} - -impl ModelBosToken { - #[must_use] - pub fn of(model: &LlamaModel) -> Self { - Self { - added_by_tokenizer: model.adds_bos_token(), - token: model.token_bos(), - } - } - - #[must_use] - pub fn is_duplicated_at_start_of(&self, prompt_tokens: &[LlamaToken]) -> bool { - self.added_by_tokenizer && prompt_tokens.starts_with(&[self.token, self.token]) - } -} - -#[cfg(test)] -mod tests { - use llama_cpp_bindings::token::LlamaToken; - - use super::ModelBosToken; - - const BOS: LlamaToken = LlamaToken(1); - const TEXT: LlamaToken = LlamaToken(42); - - fn bos_added_by_tokenizer(added_by_tokenizer: bool) -> ModelBosToken { - ModelBosToken { - added_by_tokenizer, - token: BOS, - } - } - - #[test] - fn detects_a_rendered_bos_after_the_one_the_tokenizer_added() { - assert!(bos_added_by_tokenizer(true).is_duplicated_at_start_of(&[BOS, BOS, TEXT])); - } - - #[test] - fn accepts_a_single_leading_bos() { - assert!(!bos_added_by_tokenizer(true).is_duplicated_at_start_of(&[BOS, TEXT])); - } - - #[test] - fn keeps_repeated_bos_tokens_the_tokenizer_did_not_add() { - assert!(!bos_added_by_tokenizer(false).is_duplicated_at_start_of(&[BOS, BOS, TEXT])); - } -} diff --git a/paddler_agent/src/model_metadata_holder.rs b/paddler_agent/src/model_metadata_holder.rs deleted file mode 100644 index e00a7d597..000000000 --- a/paddler_agent/src/model_metadata_holder.rs +++ /dev/null @@ -1,63 +0,0 @@ -use parking_lot::RwLock; - -use paddler_messaging::model_metadata::ModelMetadata; - -pub struct ModelMetadataHolder { - model_metadata: RwLock>, -} - -impl ModelMetadataHolder { - #[must_use] - pub fn new() -> Self { - Self::default() - } - - pub fn set_model_metadata(&self, metadata: ModelMetadata) { - let mut lock = self.model_metadata.write(); - - *lock = Some(metadata); - } - - pub fn get_model_metadata(&self) -> Option { - let lock = self.model_metadata.read(); - - lock.clone() - } -} - -impl Default for ModelMetadataHolder { - fn default() -> Self { - Self { - model_metadata: RwLock::new(None), - } - } -} - -#[cfg(test)] -mod tests { - use std::collections::BTreeMap; - - use paddler_messaging::model_metadata::ModelMetadata; - - use super::ModelMetadataHolder; - - #[test] - fn new_holder_starts_empty() { - let holder = ModelMetadataHolder::new(); - - assert!(holder.get_model_metadata().is_none()); - } - - #[test] - fn stored_metadata_is_returned() { - let holder = ModelMetadataHolder::new(); - let mut metadata = BTreeMap::new(); - metadata.insert("architecture".to_owned(), "llama".to_owned()); - - holder.set_model_metadata(ModelMetadata { metadata }); - - let stored = holder.get_model_metadata().unwrap(); - - assert_eq!(stored.metadata.get("architecture").unwrap(), "llama"); - } -} diff --git a/paddler_agent/src/model_serving.rs b/paddler_agent/src/model_serving.rs new file mode 100644 index 000000000..9ab691742 --- /dev/null +++ b/paddler_agent/src/model_serving.rs @@ -0,0 +1,77 @@ +use paddler_agent_runtime::loaded_llama_model::LoadedLlamaModel; +use paddler_agent_runtime::shuts_down::ShutsDown; + +use crate::agent_applicable_state::AgentApplicableState; +use crate::applicable_decisions::ApplicableDecisions; +use crate::loaded_model_serving_plan::LoadedModelServingPlan; +use crate::model_serving_settings::ModelServingSettings; +use crate::pipeline_request::PipelineRequest; +use crate::retained_serving::RetainedServing; +use crate::served_model::ServedModel; + +pub enum ModelServing { + Holding(LoadedLlamaModel), + Idle, + Serving(Box), +} + +impl ModelServing { + #[must_use] + pub fn decisions_to_prepare( + &self, + agent_applicable_state: &AgentApplicableState, + ) -> ApplicableDecisions { + match (self, agent_applicable_state) { + (Self::Serving(served_model), AgentApplicableState::WithModel(desired_settings)) + if served_model.keeps_decisions_of(desired_settings) => + { + ApplicableDecisions::Disabled + } + (_, AgentApplicableState::WithModel(desired_settings)) => { + desired_settings.capabilities.decisions.clone() + } + ( + _, + AgentApplicableState::NotConfigured | AgentApplicableState::WithoutModel { .. }, + ) => ApplicableDecisions::Disabled, + } + } + + pub fn forward(&self, agent_name: Option<&str>, request: PipelineRequest) { + match self { + Self::Holding(_) | Self::Idle => request.reject_because_no_model_is_loaded(agent_name), + Self::Serving(served_model) => served_model.forward(agent_name, request), + } + } + + pub async fn retain_for(self, desired_settings: ModelServingSettings) -> RetainedServing { + match self { + Self::Holding(loaded_llama_model) + if loaded_llama_model.llama_model_settings + == desired_settings.llama_model_settings() => + { + RetainedServing::LoadedModel(LoadedModelServingPlan::starting_every_capability( + loaded_llama_model, + desired_settings, + )) + } + Self::Holding(loaded_llama_model) => { + loaded_llama_model.release().await; + + RetainedServing::ModelToLoad(desired_settings) + } + Self::Idle => RetainedServing::ModelToLoad(desired_settings), + Self::Serving(served_model) => served_model.retain_for(desired_settings).await, + } + } +} + +impl ShutsDown for ModelServing { + async fn shut_down(self) { + match self { + Self::Holding(loaded_llama_model) => loaded_llama_model.release().await, + Self::Idle => {} + Self::Serving(served_model) => served_model.shut_down().await, + } + } +} diff --git a/paddler_agent/src/model_serving_settings.rs b/paddler_agent/src/model_serving_settings.rs new file mode 100644 index 000000000..2776df1a1 --- /dev/null +++ b/paddler_agent/src/model_serving_settings.rs @@ -0,0 +1,27 @@ +use std::path::PathBuf; + +use paddler_agent_runtime::llama_model_settings::LlamaModelSettings; +use paddler_agent_text_generation::text_generation_settings::TextGenerationSettings; +use paddler_inference_parameters::model_runtime_parameters::ModelRuntimeParameters; +use paddler_inference_parameters::pooling_type::PoolingType; + +use crate::applicable_decisions::ApplicableDecisions; +use crate::inference_capabilities::InferenceCapabilities; + +#[derive(Clone, Debug, PartialEq)] +pub struct ModelServingSettings { + pub capabilities: + InferenceCapabilities, + pub model_path: PathBuf, + pub model_runtime_parameters: ModelRuntimeParameters, +} + +impl ModelServingSettings { + #[must_use] + pub fn llama_model_settings(&self) -> LlamaModelSettings { + LlamaModelSettings { + model_path: self.model_path.clone(), + n_gpu_layers: self.model_runtime_parameters.n_gpu_layers, + } + } +} diff --git a/paddler_agent/src/multimodal_prompt_ingestion.rs b/paddler_agent/src/multimodal_prompt_ingestion.rs deleted file mode 100644 index 95070a741..000000000 --- a/paddler_agent/src/multimodal_prompt_ingestion.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::sync::Arc; - -use llama_cpp_bindings::SampledTokenClassifier; -use llama_cpp_bindings::context::LlamaContext; -use llama_cpp_bindings::error::EvalMultimodalChunksError; -use llama_cpp_bindings::ingest_prompt_chunk; -use llama_cpp_bindings::mtmd::MtmdContext; -use llama_cpp_bindings::mtmd::MtmdInputChunks; - -use crate::multimodal_ingestion_progress::MultimodalIngestionProgress; - -#[derive(Debug)] -pub struct MultimodalPromptIngestion { - pub chunks: MtmdInputChunks, - pub multimodal_context: Arc, - pub n_batch: i32, - pub next_chunk_index: usize, -} - -impl MultimodalPromptIngestion { - pub fn ingest_next_chunk( - &mut self, - token_classifier: &mut SampledTokenClassifier<'_>, - llama_context: &LlamaContext, - sequence_id: i32, - start_position: i32, - ) -> Result { - let chunk_index = self.next_chunk_index; - let chunk = self - .chunks - .get(chunk_index) - .ok_or(EvalMultimodalChunksError::ChunkOutOfBounds(chunk_index))?; - let is_last_chunk = chunk_index + 1 == self.chunks.len(); - let next_position = chunk.eval_single( - &self.multimodal_context, - llama_context, - start_position, - sequence_id, - self.n_batch, - is_last_chunk, - )?; - - ingest_prompt_chunk(token_classifier, &chunk)?; - - self.next_chunk_index += 1; - - Ok(if is_last_chunk { - MultimodalIngestionProgress::PromptIngested { next_position } - } else { - MultimodalIngestionProgress::ChunksRemain { next_position } - }) - } -} diff --git a/paddler_agent/src/multimodal_prompt_support.rs b/paddler_agent/src/multimodal_prompt_support.rs deleted file mode 100644 index f39ce6caa..000000000 --- a/paddler_agent/src/multimodal_prompt_support.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::sync::Arc; - -use llama_cpp_bindings::mtmd::MtmdBitmap; -use llama_cpp_bindings::mtmd::MtmdContext; -use llama_cpp_bindings::mtmd::MtmdInputText; - -use crate::generation_request_rejection::GenerationRequestRejection; -use crate::multimodal_prompt_ingestion::MultimodalPromptIngestion; -use crate::require_prompt_fits_sequence_context::require_prompt_fits_sequence_context; - -pub struct MultimodalPromptSupport { - pub micro_batch_tokens: u32, - pub multimodal_context: Arc, - pub n_batch: i32, - pub sequence_context_size: u32, -} - -impl MultimodalPromptSupport { - pub fn prepare_prompt( - &self, - bitmaps: &[MtmdBitmap], - text: String, - add_special_tokens: bool, - ) -> Result { - let bitmap_refs: Vec<&MtmdBitmap> = bitmaps.iter().collect(); - let chunks = self - .multimodal_context - .tokenize( - MtmdInputText { - text, - add_special: add_special_tokens, - parse_special: true, - }, - &bitmap_refs, - ) - .map_err(GenerationRequestRejection::MultimodalTokenizationFailed)?; - - require_prompt_fits_sequence_context(chunks.total_tokens(), self.sequence_context_size)?; - - chunks - .fit_to_micro_batch(&self.multimodal_context, self.micro_batch_tokens) - .map_err(GenerationRequestRejection::for_micro_batch_fit_error)?; - - Ok(MultimodalPromptIngestion { - chunks, - multimodal_context: self.multimodal_context.clone(), - n_batch: self.n_batch, - next_chunk_index: 0, - }) - } -} diff --git a/paddler_agent/src/pipeline_arbiter.rs b/paddler_agent/src/pipeline_arbiter.rs new file mode 100644 index 000000000..e91abbcf1 --- /dev/null +++ b/paddler_agent/src/pipeline_arbiter.rs @@ -0,0 +1,262 @@ +use std::mem; +use std::sync::Arc; + +use log::error; +use tokio_util::sync::CancellationToken; + +use paddler_agent_runtime::inference_runtime_context::InferenceRuntimeContext; +use paddler_agent_runtime::shuts_down::ShutsDown as _; + +use crate::agent_applicable_state::AgentApplicableState; +use crate::agent_applicable_state_holder::AgentApplicableStateHolder; +use crate::finished_serving_startup::FinishedServingStartup; +use crate::model_serving::ModelServing; +use crate::pipeline_arbiter_params::PipelineArbiterParams; +use crate::pipeline_request::PipelineRequest; +use crate::preparation_outcome::PreparationOutcome; +use crate::prepared_serving::PreparedServing; +use crate::replacement_outcome::ReplacementOutcome; +use crate::retry_delay::RetryDelay; +use crate::served_model::ServedModel; +use crate::serving_preparation::ServingPreparation; +use crate::serving_replacement::ServingReplacement; +use crate::serving_startup::ServingStartup; +use crate::serving_startup_progress::ServingStartupProgress; +use crate::startup_phase::StartupPhase; +use crate::superseding_state::SupersedingState; + +pub struct PipelineArbiter { + agent_shutdown: CancellationToken, + inference_runtime_context: InferenceRuntimeContext, + model_serving: ModelServing, + served_applicable_state_holder: Arc, + serving_startup: ServingStartup, + superseding_state: SupersedingState, +} + +impl PipelineArbiter { + #[must_use] + pub fn new( + PipelineArbiterParams { + agent_shutdown, + inference_runtime_context, + served_applicable_state_holder, + }: PipelineArbiterParams, + ) -> Self { + Self { + agent_shutdown, + inference_runtime_context, + model_serving: ModelServing::Idle, + served_applicable_state_holder, + serving_startup: ServingStartup::Idle, + superseding_state: SupersedingState::NotSuperseded, + } + } + + pub fn apply(&mut self, agent_applicable_state: AgentApplicableState) { + match self.serving_startup { + ServingStartup::Idle => self.start_pipeline(agent_applicable_state), + ServingStartup::Preparing(_) | ServingStartup::Replacing(_) => { + self.inference_runtime_context + .slot_aggregated_status + .begin_pipeline(); + self.serving_startup.cancel(); + self.superseding_state = SupersedingState::SupersededBy(agent_applicable_state); + } + } + } + + pub async fn next_finished_startup(&mut self) -> ServingStartupProgress { + self.serving_startup.next_progress().await + } + + pub fn conclude(&mut self, serving_startup_progress: ServingStartupProgress) { + match serving_startup_progress { + ServingStartupProgress::Prepared(preparation_outcome) => { + self.conclude_preparation(preparation_outcome); + } + ServingStartupProgress::Replaced(finished_serving_startup) => { + self.conclude_replacement(finished_serving_startup); + } + } + } + + pub fn forward(&self, request: PipelineRequest) { + self.model_serving.forward( + self.inference_runtime_context.agent_name.as_deref(), + request, + ); + } + + pub async fn shut_down(self) { + let Self { + model_serving, + serving_startup, + .. + } = self; + + serving_startup.shut_down().await; + model_serving.shut_down().await; + } + + fn conclude_preparation(&mut self, preparation_outcome: PreparationOutcome) { + if let PreparationOutcome::Failed { error, .. } = &preparation_outcome { + error!("Failed to prepare the reconciled state: {error}"); + } + + if let SupersedingState::SupersededBy(superseding_applicable_state) = + mem::replace(&mut self.superseding_state, SupersedingState::NotSuperseded) + { + self.start_pipeline(superseding_applicable_state); + + return; + } + + match preparation_outcome { + PreparationOutcome::Cancelled => {} + PreparationOutcome::Failed { + agent_applicable_state, + .. + } => { + self.inference_runtime_context + .slot_aggregated_status + .retry_pipeline(); + self.prepare(agent_applicable_state, RetryDelay::AfterRetryInterval); + } + PreparationOutcome::Prepared(prepared_serving) => self.replace(prepared_serving), + } + } + + fn conclude_replacement( + &mut self, + FinishedServingStartup { + agent_applicable_state, + outcome, + }: FinishedServingStartup, + ) { + if let ReplacementOutcome::Failed { error, .. } = &outcome { + error!("Failed to serve the reconciled state: {error}"); + } + + if let SupersedingState::SupersededBy(superseding_applicable_state) = + mem::replace(&mut self.superseding_state, SupersedingState::NotSuperseded) + { + self.model_serving = outcome.into_model_serving(); + self.start_pipeline(superseding_applicable_state); + + return; + } + + self.settle_replacement(agent_applicable_state, &outcome); + self.model_serving = outcome.into_model_serving(); + } + + fn prepare(&mut self, agent_applicable_state: AgentApplicableState, retry_delay: RetryDelay) { + let cancellation_token = self.agent_shutdown.child_token(); + let decisions_to_prepare = self + .model_serving + .decisions_to_prepare(&agent_applicable_state); + + self.serving_startup = ServingStartup::Preparing(StartupPhase { + cancellation_token: cancellation_token.clone(), + outcome: Box::pin( + ServingPreparation { + agent_applicable_state, + decisions_to_prepare, + retry_delay, + } + .prepare(cancellation_token, self.inference_runtime_context.clone()), + ), + }); + } + + fn replace(&mut self, prepared_serving: PreparedServing) { + if let ModelServing::Serving(served_model) = &self.model_serving + && let AgentApplicableState::WithModel(desired_settings) = + &prepared_serving.agent_applicable_state + && served_model.model_serving_settings == *desired_settings + { + self.resume_serving(served_model, prepared_serving.agent_applicable_state); + + return; + } + + let held = self.withdraw_serving(); + let cancellation_token = self.agent_shutdown.child_token(); + + self.serving_startup = ServingStartup::Replacing(StartupPhase { + cancellation_token: cancellation_token.clone(), + outcome: Box::pin( + ServingReplacement { + held, + prepared_serving, + } + .replace(cancellation_token, self.inference_runtime_context.clone()), + ), + }); + } + + fn resume_serving( + &self, + served_model: &ServedModel, + agent_applicable_state: AgentApplicableState, + ) { + self.inference_runtime_context + .slot_aggregated_status + .start_serving( + served_model + .serving_report(agent_applicable_state.chat_template_override().is_some()), + ); + self.inference_runtime_context + .model_metadata_holder + .set_model_metadata(served_model.loaded_llama_model.model_metadata.clone()); + self.served_applicable_state_holder + .set_agent_applicable_state(agent_applicable_state); + } + + fn settle_replacement( + &mut self, + agent_applicable_state: AgentApplicableState, + outcome: &ReplacementOutcome, + ) { + match outcome { + ReplacementOutcome::Cancelled { .. } => {} + ReplacementOutcome::Failed { .. } => { + self.inference_runtime_context + .slot_aggregated_status + .retry_pipeline(); + self.prepare(agent_applicable_state, RetryDelay::AfterRetryInterval); + } + ReplacementOutcome::NothingToServe => { + self.inference_runtime_context + .slot_aggregated_status + .serve_nothing(agent_applicable_state.chat_template_override().is_some()); + self.served_applicable_state_holder + .set_agent_applicable_state(agent_applicable_state); + } + ReplacementOutcome::Serving(served_model) => { + self.resume_serving(served_model, agent_applicable_state); + } + } + } + + fn start_pipeline(&mut self, agent_applicable_state: AgentApplicableState) { + self.inference_runtime_context + .slot_aggregated_status + .begin_pipeline(); + self.prepare(agent_applicable_state, RetryDelay::Immediately); + } + + fn withdraw_serving(&mut self) -> ModelServing { + self.inference_runtime_context + .slot_aggregated_status + .withdraw_serving(); + self.inference_runtime_context + .model_metadata_holder + .forget_model_metadata(); + self.served_applicable_state_holder + .set_agent_applicable_state(AgentApplicableState::NotConfigured); + + mem::replace(&mut self.model_serving, ModelServing::Idle) + } +} diff --git a/paddler_agent/src/pipeline_arbiter_params.rs b/paddler_agent/src/pipeline_arbiter_params.rs new file mode 100644 index 000000000..d72d45bb0 --- /dev/null +++ b/paddler_agent/src/pipeline_arbiter_params.rs @@ -0,0 +1,13 @@ +use std::sync::Arc; + +use tokio_util::sync::CancellationToken; + +use paddler_agent_runtime::inference_runtime_context::InferenceRuntimeContext; + +use crate::agent_applicable_state_holder::AgentApplicableStateHolder; + +pub struct PipelineArbiterParams { + pub agent_shutdown: CancellationToken, + pub inference_runtime_context: InferenceRuntimeContext, + pub served_applicable_state_holder: Arc, +} diff --git a/paddler_agent/src/pipeline_request.rs b/paddler_agent/src/pipeline_request.rs new file mode 100644 index 000000000..ff6e83f88 --- /dev/null +++ b/paddler_agent/src/pipeline_request.rs @@ -0,0 +1,78 @@ +use paddler_agent_decision::decision_error::DecisionError; +use paddler_agent_decision::reports_decision_outcome::ReportsDecisionOutcome as _; +use paddler_agent_embeddings::embedding_error::EmbeddingError; +use paddler_agent_runtime::agent_request::AgentRequest; +use paddler_agent_text_generation::text_generation_error::TextGenerationError; +use paddler_messaging::decision_result::DecisionResult; +use paddler_messaging::embedding_result::EmbeddingResult; +use paddler_messaging::generated_token_result::GeneratedTokenResult; +use paddler_messaging::request_params::continue_from_conversation_history_params::ContinueFromConversationHistoryParams; +use paddler_messaging::request_params::continue_from_conversation_history_params::tool::tool_params::function_call::parameters_schema::validated_parameters_schema::ValidatedParametersSchema; +use paddler_messaging::request_params::continue_from_raw_prompt_params::ContinueFromRawPromptParams; +use paddler_messaging::request_params::decide_params::DecideParams; +use paddler_messaging::request_params::generate_embedding_batch_params::GenerateEmbeddingBatchParams; + +pub enum PipelineRequest { + ContinueFromConversationHistory( + AgentRequest< + ContinueFromConversationHistoryParams, + GeneratedTokenResult, + >, + ), + ContinueFromRawPrompt(AgentRequest), + Decide(AgentRequest), + GenerateEmbeddingBatch(AgentRequest), +} + +impl PipelineRequest { + pub fn reject_because_no_model_is_loaded(self, agent_name: Option<&str>) { + match self { + Self::ContinueFromConversationHistory(AgentRequest { response_tx, .. }) + | Self::ContinueFromRawPrompt(AgentRequest { response_tx, .. }) => { + TextGenerationError::ModelNotLoaded.report(agent_name, &response_tx); + } + Self::Decide(AgentRequest { response_tx, .. }) => { + DecisionError::ModelNotLoaded.report(agent_name, &response_tx); + } + Self::GenerateEmbeddingBatch(AgentRequest { response_tx, .. }) => { + EmbeddingError::ModelNotLoaded.report(agent_name, &response_tx); + } + } + } +} + +impl + From< + AgentRequest< + ContinueFromConversationHistoryParams, + GeneratedTokenResult, + >, + > for PipelineRequest +{ + fn from( + request: AgentRequest< + ContinueFromConversationHistoryParams, + GeneratedTokenResult, + >, + ) -> Self { + Self::ContinueFromConversationHistory(request) + } +} + +impl From> for PipelineRequest { + fn from(request: AgentRequest) -> Self { + Self::ContinueFromRawPrompt(request) + } +} + +impl From> for PipelineRequest { + fn from(request: AgentRequest) -> Self { + Self::Decide(request) + } +} + +impl From> for PipelineRequest { + fn from(request: AgentRequest) -> Self { + Self::GenerateEmbeddingBatch(request) + } +} diff --git a/paddler_agent/src/pipeline_response_stream.rs b/paddler_agent/src/pipeline_response_stream.rs new file mode 100644 index 000000000..d128fd8af --- /dev/null +++ b/paddler_agent/src/pipeline_response_stream.rs @@ -0,0 +1,37 @@ +use std::pin::Pin; +use std::task::Context; +use std::task::Poll; + +use futures_util::Stream; +use tokio::sync::mpsc; + +use paddler_messaging::management_socket::agent::response::Response as JsonRpcResponse; +use paddler_messaging::management_socket::balancer::message::Message as ManagementJsonRpcMessage; +use paddler_request_registry::request_registry_guard::RequestRegistryGuard; + +use crate::agent_response_message::agent_response_message; + +pub struct PipelineResponseStream { + pub response_rx: mpsc::UnboundedReceiver, + pub stopper_guard: RequestRegistryGuard>, +} + +impl Stream for PipelineResponseStream +where + TResponse: Into, +{ + type Item = ManagementJsonRpcMessage; + + fn poll_next(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll> { + let Self { + response_rx, + stopper_guard, + } = self.get_mut(); + + response_rx.poll_recv(context).map(|received_response| { + received_response.map(|response| { + agent_response_message(stopper_guard.request_id.clone(), response.into()) + }) + }) + } +} diff --git a/paddler_agent/src/plan_embedding_batches.rs b/paddler_agent/src/plan_embedding_batches.rs deleted file mode 100644 index a8f0b2dd9..000000000 --- a/paddler_agent/src/plan_embedding_batches.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::ops::Range; - -#[must_use] -pub fn plan_embedding_batches( - token_counts: &[usize], - n_batch: usize, - max_sequences_per_batch: u16, -) -> Vec> { - let mut batches = Vec::new(); - let mut start = 0usize; - let mut current_tokens: usize = 0; - let mut current_sequences: u16 = 0; - - for (index, &token_count) in token_counts.iter().enumerate() { - let would_exceed_tokens = current_tokens + token_count > n_batch; - let would_exceed_sequences = current_sequences >= max_sequences_per_batch; - - if (would_exceed_tokens || would_exceed_sequences) && current_sequences > 0 { - batches.push(start..index); - start = index; - current_tokens = 0; - current_sequences = 0; - } - - current_tokens += token_count; - current_sequences += 1; - } - - if current_sequences > 0 { - batches.push(start..token_counts.len()); - } - - batches -} - -#[cfg(test)] -mod tests { - use super::plan_embedding_batches; - - #[test] - fn empty_input_yields_no_batches() { - let batches = plan_embedding_batches(&[], 512, 4); - - assert!(batches.is_empty()); - } - - #[test] - fn inputs_under_both_caps_fit_in_single_batch() { - let batches = plan_embedding_batches(&[10, 20, 30], 512, 4); - - assert_eq!(batches, vec![0..3]); - } - - #[test] - fn batch_flushes_when_sequence_cap_reached() { - let batches = plan_embedding_batches(&[10, 10, 10, 10, 10], 512, 2); - - assert_eq!(batches, vec![0..2, 2..4, 4..5]); - } - - #[test] - fn batch_flushes_when_token_cap_would_be_exceeded() { - let batches = plan_embedding_batches(&[300, 300, 300], 512, 16); - - assert_eq!(batches, vec![0..1, 1..2, 2..3]); - } - - #[test] - fn oversized_single_input_gets_its_own_batch() { - let batches = plan_embedding_batches(&[50, 1000, 50], 512, 16); - - assert_eq!(batches, vec![0..1, 1..2, 2..3]); - } - - #[test] - fn batch_exactly_at_sequence_cap_starts_new_batch_on_next_input() { - let batches = plan_embedding_batches(&[10, 10, 10, 10], 512, 4); - - assert_eq!(batches, vec![0..4]); - } - - #[test] - fn input_count_three_times_slot_cap_splits_into_three_batches() { - let batches = plan_embedding_batches(&[10; 12], 512, 4); - - assert_eq!(batches, vec![0..4, 4..8, 8..12]); - } -} diff --git a/paddler_agent/src/preparation_outcome.rs b/paddler_agent/src/preparation_outcome.rs new file mode 100644 index 000000000..800d0f223 --- /dev/null +++ b/paddler_agent/src/preparation_outcome.rs @@ -0,0 +1,12 @@ +use crate::agent_applicable_state::AgentApplicableState; +use crate::agent_error::AgentError; +use crate::prepared_serving::PreparedServing; + +pub enum PreparationOutcome { + Cancelled, + Failed { + agent_applicable_state: AgentApplicableState, + error: AgentError, + }, + Prepared(PreparedServing), +} diff --git a/paddler_agent/src/prepared_decisions.rs b/paddler_agent/src/prepared_decisions.rs new file mode 100644 index 000000000..b6689bfba --- /dev/null +++ b/paddler_agent/src/prepared_decisions.rs @@ -0,0 +1,6 @@ +use paddler_agent_decision::loaded_pointer_head::LoadedPointerHead; + +pub enum PreparedDecisions { + Disabled, + Enabled(Box), +} diff --git a/paddler_agent/src/prepared_embedding_batch_request.rs b/paddler_agent/src/prepared_embedding_batch_request.rs deleted file mode 100644 index eb3c42727..000000000 --- a/paddler_agent/src/prepared_embedding_batch_request.rs +++ /dev/null @@ -1,17 +0,0 @@ -use tokio::sync::mpsc; - -use paddler_messaging::embedding_normalization_method::EmbeddingNormalizationMethod; -use paddler_messaging::embedding_result::EmbeddingResult; -use paddler_messaging::oversized_embedding_document_details::OversizedEmbeddingDocumentDetails; - -use crate::embedding_input_tokenized::EmbeddingInputTokenized; -use crate::slot_guard::SlotGuard; - -pub struct PreparedEmbeddingBatchRequest { - pub generate_embedding_stop_rx: mpsc::UnboundedReceiver<()>, - pub generated_embedding_tx: mpsc::UnboundedSender, - pub inputs: Vec, - pub normalization_method: EmbeddingNormalizationMethod, - pub oversized_documents: Vec, - pub slot_guard: SlotGuard, -} diff --git a/paddler_agent/src/prepared_generation_request.rs b/paddler_agent/src/prepared_generation_request.rs deleted file mode 100644 index bec20fedc..000000000 --- a/paddler_agent/src/prepared_generation_request.rs +++ /dev/null @@ -1,22 +0,0 @@ -use std::num::NonZeroU32; - -use tokio::sync::mpsc; - -use paddler_messaging::generated_token_result::GeneratedTokenResult; - -use crate::prepared_prompt::PreparedPrompt; -use crate::slot_guard::SlotGuard; -use crate::token_classification::TokenClassification; -use crate::token_sampling::TokenSampling; -use crate::tool_call_handling::ToolCallHandling; - -pub struct PreparedGenerationRequest { - pub generate_tokens_stop_rx: mpsc::UnboundedReceiver<()>, - pub generated_tokens_tx: mpsc::UnboundedSender, - pub max_tokens: NonZeroU32, - pub prompt: PreparedPrompt, - pub slot_guard: SlotGuard, - pub token_classification: TokenClassification, - pub token_sampling: TokenSampling, - pub tool_call_handling: ToolCallHandling, -} diff --git a/paddler_agent/src/prepared_serving.rs b/paddler_agent/src/prepared_serving.rs new file mode 100644 index 000000000..ba8ca9655 --- /dev/null +++ b/paddler_agent/src/prepared_serving.rs @@ -0,0 +1,7 @@ +use crate::agent_applicable_state::AgentApplicableState; +use crate::prepared_decisions::PreparedDecisions; + +pub struct PreparedServing { + pub agent_applicable_state: AgentApplicableState, + pub prepared_decisions: PreparedDecisions, +} diff --git a/paddler_agent/src/prompt_tokenizer.rs b/paddler_agent/src/prompt_tokenizer.rs deleted file mode 100644 index 5dff1e64f..000000000 --- a/paddler_agent/src/prompt_tokenizer.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::sync::Arc; - -use llama_cpp_bindings::model::AddBos; -use llama_cpp_bindings::model::LlamaModel; -use llama_cpp_bindings::token::LlamaToken; - -use crate::generation_request_rejection::GenerationRequestRejection; -use crate::model_bos_token::ModelBosToken; -use crate::require_prompt_fits_sequence_context::require_prompt_fits_sequence_context; - -pub struct PromptTokenizer { - pub model: Arc, - pub sequence_context_size: u32, -} - -impl PromptTokenizer { - pub fn renders_its_own_bos_token( - &self, - prompt: &str, - ) -> Result { - self.tokens_with_special_tokens(prompt) - .map(|prompt_tokens| { - ModelBosToken::of(&self.model).is_duplicated_at_start_of(&prompt_tokens) - }) - } - - pub fn tokenize(&self, prompt: &str) -> Result, GenerationRequestRejection> { - let mut prompt_tokens = self.tokens_with_special_tokens(prompt)?; - - if ModelBosToken::of(&self.model).is_duplicated_at_start_of(&prompt_tokens) { - prompt_tokens.remove(0); - } - - require_prompt_fits_sequence_context(prompt_tokens.len(), self.sequence_context_size)?; - - Ok(prompt_tokens) - } - - fn tokens_with_special_tokens( - &self, - prompt: &str, - ) -> Result, GenerationRequestRejection> { - self.model - .str_to_token(prompt, AddBos::Always) - .map_err(GenerationRequestRejection::PromptTokenizationFailed) - } -} diff --git a/paddler_agent/src/reconciliation_service.rs b/paddler_agent/src/reconciliation_service.rs index a37bc7b9e..5f5dc8ab2 100644 --- a/paddler_agent/src/reconciliation_service.rs +++ b/paddler_agent/src/reconciliation_service.rs @@ -1,67 +1,94 @@ +use std::sync::Arc; + use anyhow::Result; use async_trait::async_trait; use log::error; use tokio::select; -use tokio::sync::mpsc; -use tokio::time::Duration; -use tokio::time::MissedTickBehavior; -use tokio::time::interval; +use tokio::sync::watch; use tokio_util::sync::CancellationToken; use trzcina::Service; use paddler_messaging::agent_desired_state::AgentDesiredState; +use crate::agent_applicable_state_holder::AgentApplicableStateHolder; use crate::desired_state_reconciler::DesiredStateReconciler; use crate::desired_state_reconciliation::DesiredStateReconciliation; - -const DESIRED_STATE_CONVERSION_RETRY_INTERVAL: Duration = Duration::from_secs(1); +use crate::desired_state_reconciliation_attempt::DesiredStateReconciliationAttempt; +use crate::retry_delay::RetryDelay; pub struct ReconciliationService { - pub agent_desired_state_rx: mpsc::UnboundedReceiver, + pub agent_applicable_state_holder: Arc, + pub agent_desired_state_rx: watch::Receiver, pub desired_state_reconciler: DesiredStateReconciler, } -#[async_trait] -impl Service for ReconciliationService { - fn name(&self) -> &'static str { - "agent::reconciliation_service" - } - - async fn run(self: Box, shutdown: CancellationToken) -> Result<()> { +impl ReconciliationService { + async fn reconcile_desired_states_until_shutdown( + self: Box, + shutdown: CancellationToken, + ) -> Result<()> { let Self { + agent_applicable_state_holder, mut agent_desired_state_rx, desired_state_reconciler, } = *self; + let mut reconciliation_attempt = DesiredStateReconciliationAttempt::Idle; - let mut reconciliation = DesiredStateReconciliation::Reconciled; - let mut ticker = interval(DESIRED_STATE_CONVERSION_RETRY_INTERVAL); - - ticker.set_missed_tick_behavior(MissedTickBehavior::Delay); - - loop { + let service_outcome = loop { select! { biased; () = shutdown.cancelled() => break Ok(()), - _ = ticker.tick() => { - if let DesiredStateReconciliation::Pending(agent_desired_state) = reconciliation { - reconciliation = desired_state_reconciler - .reconcile(&shutdown, *agent_desired_state) - .await; - } - }, - next_agent_desired_state = agent_desired_state_rx.recv() => { - let Some(agent_desired_state) = next_agent_desired_state else { + desired_state_change = agent_desired_state_rx.changed() => { + if desired_state_change.is_err() { error!("Agent desired state channel closed, stopping reconciliation service."); break Ok(()); - }; + } - reconciliation = desired_state_reconciler - .reconcile(&shutdown, agent_desired_state) - .await; + reconciliation_attempt.abandon().await; + desired_state_reconciler + .slot_aggregated_status + .begin_desired_state(); + reconciliation_attempt = DesiredStateReconciliationAttempt::start( + desired_state_reconciler.clone(), + agent_desired_state_rx.borrow_and_update().clone(), + &shutdown, + RetryDelay::Immediately, + ); + } + desired_state_reconciliation = reconciliation_attempt.finish() => { + match desired_state_reconciliation { + DesiredStateReconciliation::Pending(agent_desired_state) => { + reconciliation_attempt = DesiredStateReconciliationAttempt::start( + desired_state_reconciler.clone(), + *agent_desired_state, + &shutdown, + RetryDelay::AfterRetryInterval, + ); + } + DesiredStateReconciliation::Reconciled(agent_applicable_state) => { + agent_applicable_state_holder + .set_agent_applicable_state(*agent_applicable_state); + } + } } } - } + }; + + reconciliation_attempt.abandon().await; + + service_outcome + } +} + +#[async_trait] +impl Service for ReconciliationService { + fn name(&self) -> &'static str { + "agent::reconciliation_service" + } + + async fn run(self: Box, shutdown: CancellationToken) -> Result<()> { + self.reconcile_desired_states_until_shutdown(shutdown).await } } @@ -69,85 +96,93 @@ impl Service for ReconciliationService { mod tests { use std::sync::Arc; + use http::StatusCode; + use tempfile::TempDir; + use tokio::spawn; + use tokio::sync::watch; + use tokio::time::Instant; use tokio_util::sync::CancellationToken; + use trzcina::Service as _; use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; - use paddler_inference_parameters::inference_parameters::InferenceParameters; + use paddler_cache_dir::cache_dir::CacheDir; + use paddler_download_manager::download_manager::DownloadManager; + use paddler_local_http_fixture::fixture_response::FixtureResponse; + use paddler_local_http_fixture::local_http_fixture::LocalHttpFixture; use paddler_messaging::agent_desired_model::AgentDesiredModel; use paddler_messaging::agent_desired_state::AgentDesiredState; - use paddler_messaging::huggingface_model_reference::HuggingFaceModelReference; + use paddler_model_source::desired_model_resolver::DesiredModelResolver; + use paddler_model_source::huggingface_cache_location::HuggingFaceCacheLocation; + use paddler_model_source::huggingface_default_endpoint::HUGGINGFACE_DEFAULT_ENDPOINT; + use paddler_model_source::huggingface_hub::HuggingFaceHub; - use crate::agent_applicable_state::AgentApplicableState; + use super::ReconciliationService; use crate::agent_applicable_state_holder::AgentApplicableStateHolder; use crate::desired_state_reconciler::DesiredStateReconciler; - use crate::desired_state_reconciliation::DesiredStateReconciliation; fn desired_state_with_model(model: AgentDesiredModel) -> AgentDesiredState { AgentDesiredState { - chat_template_override: None, - inference_parameters: InferenceParameters::default(), model, - multimodal_projection: AgentDesiredModel::None, + ..AgentDesiredState::default() } } - fn desired_state_reconciler() -> DesiredStateReconciler { - DesiredStateReconciler { - agent_applicable_state_holder: Arc::new(AgentApplicableStateHolder::default()), - slot_aggregated_status: Arc::new(SlotAggregatedStatus::new(1)), - } - } - - #[tokio::test] - async fn a_missing_local_model_leaves_the_state_unconverted() { - let reconciler = desired_state_reconciler(); - let desired_state = desired_state_with_model(AgentDesiredModel::LocalToAgent( - "/paddler-nonexistent-model-for-reconciliation.gguf".to_owned(), - )); - - let reconciliation = reconciler - .reconcile(&CancellationToken::new(), desired_state.clone()) - .await; - - assert_eq!( - reconciliation, - DesiredStateReconciliation::Pending(Box::new(desired_state)) - ); - assert_eq!( - reconciler - .agent_applicable_state_holder - .get_agent_applicable_state(), - AgentApplicableState::default() + #[tokio::test(start_paused = true)] + async fn a_desired_state_arriving_during_retries_is_reconciled_without_waiting() { + let cache_dir = TempDir::new().unwrap(); + let failing_model_server = + LocalHttpFixture::start(FixtureResponse::Status(StatusCode::INTERNAL_SERVER_ERROR)) + .await + .unwrap(); + let agent_applicable_state_holder = Arc::new(AgentApplicableStateHolder::default()); + let mut agent_applicable_state_rx = agent_applicable_state_holder.subscribe(); + let (agent_desired_state_tx, agent_desired_state_rx) = + watch::channel(AgentDesiredState::default()); + let shutdown = CancellationToken::new(); + let reconciliation = spawn( + Box::new(ReconciliationService { + agent_applicable_state_holder: agent_applicable_state_holder.clone(), + agent_desired_state_rx, + desired_state_reconciler: DesiredStateReconciler { + desired_model_resolver: DesiredModelResolver { + cache_dir: CacheDir { + explicit: Some(cache_dir.path().to_path_buf()), + home: None, + xdg: None, + }, + download_manager: DownloadManager::new().unwrap(), + huggingface_client: HuggingFaceHub { + cache_location: HuggingFaceCacheLocation::HubDefault, + endpoint: HUGGINGFACE_DEFAULT_ENDPOINT.to_owned(), + } + .client() + .unwrap(), + }, + slot_aggregated_status: Arc::new(SlotAggregatedStatus::new(1)), + }, + }) + .run(shutdown.clone()), ); - } - #[tokio::test] - async fn a_model_download_cancelled_mid_conversion_leaves_the_state_unconverted() { - let cancellation_token = CancellationToken::new(); + agent_desired_state_tx.send_replace(desired_state_with_model(AgentDesiredModel::Uri( + failing_model_server.url("/model.gguf"), + ))); + failing_model_server.wait_for_requests(2).await.unwrap(); - cancellation_token.cancel(); + let replaced_at = Instant::now(); - let reconciler = desired_state_reconciler(); - let desired_state = - desired_state_with_model(AgentDesiredModel::HuggingFace(HuggingFaceModelReference { - filename: "model.gguf".to_owned(), - repo_id: "paddler-tests/never-downloaded".to_owned(), - revision: "main".to_owned(), - })); + agent_desired_state_tx.send_replace(AgentDesiredState::default()); + agent_applicable_state_rx.changed().await.unwrap(); - let reconciliation = reconciler - .reconcile(&cancellation_token, desired_state.clone()) - .await; + assert_eq!(Instant::now(), replaced_at); + assert_eq!(failing_model_server.request_count(), 2); - assert_eq!( - reconciliation, - DesiredStateReconciliation::Pending(Box::new(desired_state)) - ); - assert_eq!( - reconciler - .agent_applicable_state_holder - .get_agent_applicable_state(), - AgentApplicableState::default() - ); + shutdown.cancel(); + reconciliation.await.unwrap().unwrap(); + failing_model_server + .stop() + .await + .expect("the fixture task must not panic") + .expect("the fixture must serve every request"); } } diff --git a/paddler_agent/src/replacement_outcome.rs b/paddler_agent/src/replacement_outcome.rs new file mode 100644 index 000000000..85c581731 --- /dev/null +++ b/paddler_agent/src/replacement_outcome.rs @@ -0,0 +1,34 @@ +use paddler_agent_runtime::shuts_down::ShutsDown; + +use crate::agent_error::AgentError; +use crate::model_serving::ModelServing; +use crate::served_model::ServedModel; + +pub enum ReplacementOutcome { + Cancelled { + held: ModelServing, + }, + Failed { + error: AgentError, + held: ModelServing, + }, + NothingToServe, + Serving(Box), +} + +impl ReplacementOutcome { + #[must_use] + pub fn into_model_serving(self) -> ModelServing { + match self { + Self::Cancelled { held } | Self::Failed { held, .. } => held, + Self::NothingToServe => ModelServing::Idle, + Self::Serving(served_model) => ModelServing::Serving(served_model), + } + } +} + +impl ShutsDown for ReplacementOutcome { + async fn shut_down(self) { + self.into_model_serving().shut_down().await; + } +} diff --git a/paddler_agent/src/require_embeddings_enabled.rs b/paddler_agent/src/require_embeddings_enabled.rs deleted file mode 100644 index 2328b08f8..000000000 --- a/paddler_agent/src/require_embeddings_enabled.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::embedding_batch_rejection::EmbeddingBatchRejection; - -pub const fn require_embeddings_enabled( - enable_embeddings: bool, -) -> Result<(), EmbeddingBatchRejection> { - if enable_embeddings { - Ok(()) - } else { - Err(EmbeddingBatchRejection::EmbeddingsDisabled) - } -} - -#[cfg(test)] -mod tests { - use std::mem::discriminant; - - use super::require_embeddings_enabled; - use crate::embedding_batch_rejection::EmbeddingBatchRejection; - - #[test] - fn accepts_batches_when_embeddings_are_enabled() { - assert!(require_embeddings_enabled(true).is_ok()); - } - - #[test] - fn rejects_batches_when_embeddings_are_disabled() { - assert!( - require_embeddings_enabled(false).is_err_and(|rejection| discriminant(&rejection) - == discriminant(&EmbeddingBatchRejection::EmbeddingsDisabled)) - ); - } -} diff --git a/paddler_agent/src/resolve_grammar.rs b/paddler_agent/src/resolve_grammar.rs deleted file mode 100644 index a997a7c05..000000000 --- a/paddler_agent/src/resolve_grammar.rs +++ /dev/null @@ -1,32 +0,0 @@ -use llama_cpp_bindings::json_schema_to_grammar; -use llama_cpp_bindings::model::LlamaModel; -use llama_cpp_bindings::sampling::LlamaSampler; - -use paddler_messaging::grammar_constraint::GrammarConstraint; - -use crate::generation_request_rejection::GenerationRequestRejection; -use crate::grammar_sampling::GrammarSampling; - -const JSON_SCHEMA_GRAMMAR_ROOT: &str = "root"; - -pub fn resolve_grammar( - grammar: Option, - model: &LlamaModel, -) -> Result { - match grammar { - None => Ok(GrammarSampling::Unconstrained), - Some(GrammarConstraint::Gbnf { grammar, root }) => { - LlamaSampler::grammar(model, &grammar, &root) - .map(GrammarSampling::Constrained) - .map_err(GenerationRequestRejection::for_gbnf_grammar_error) - } - Some(GrammarConstraint::JsonSchema { schema }) => { - let grammar = json_schema_to_grammar(&schema) - .map_err(GenerationRequestRejection::GrammarConversionFailed)?; - - LlamaSampler::grammar(model, &grammar, JSON_SCHEMA_GRAMMAR_ROOT) - .map(GrammarSampling::Constrained) - .map_err(GenerationRequestRejection::GrammarSamplerInitializationFailed) - } - } -} diff --git a/paddler_agent/src/retained_serving.rs b/paddler_agent/src/retained_serving.rs new file mode 100644 index 000000000..769f8b01e --- /dev/null +++ b/paddler_agent/src/retained_serving.rs @@ -0,0 +1,7 @@ +use crate::loaded_model_serving_plan::LoadedModelServingPlan; +use crate::model_serving_settings::ModelServingSettings; + +pub enum RetainedServing { + LoadedModel(LoadedModelServingPlan), + ModelToLoad(ModelServingSettings), +} diff --git a/paddler_agent/src/retry_delay.rs b/paddler_agent/src/retry_delay.rs new file mode 100644 index 000000000..420e71bfe --- /dev/null +++ b/paddler_agent/src/retry_delay.rs @@ -0,0 +1,32 @@ +use tokio::time::Duration; +use tokio::time::sleep; +use tokio_util::sync::CancellationToken; + +use crate::retry_delay_outcome::RetryDelayOutcome; + +const RETRY_INTERVAL: Duration = Duration::from_secs(1); + +#[derive(Clone, Copy)] +pub enum RetryDelay { + AfterRetryInterval, + Immediately, +} + +impl RetryDelay { + pub async fn elapse(self, cancellation_token: &CancellationToken) -> RetryDelayOutcome { + match self { + Self::AfterRetryInterval => { + cancellation_token + .run_until_cancelled(sleep(RETRY_INTERVAL)) + .await; + } + Self::Immediately => {} + } + + if cancellation_token.is_cancelled() { + RetryDelayOutcome::Cancelled + } else { + RetryDelayOutcome::Elapsed + } + } +} diff --git a/paddler_agent/src/retry_delay_outcome.rs b/paddler_agent/src/retry_delay_outcome.rs new file mode 100644 index 000000000..04b6553a3 --- /dev/null +++ b/paddler_agent/src/retry_delay_outcome.rs @@ -0,0 +1,5 @@ +#[derive(Debug, Eq, PartialEq)] +pub enum RetryDelayOutcome { + Cancelled, + Elapsed, +} diff --git a/paddler_agent/src/sampler_chain_factory.rs b/paddler_agent/src/sampler_chain_factory.rs deleted file mode 100644 index f43c1d598..000000000 --- a/paddler_agent/src/sampler_chain_factory.rs +++ /dev/null @@ -1,68 +0,0 @@ -use llama_cpp_bindings::error::SamplingError; -use llama_cpp_bindings::sampling::LlamaSampler; - -use paddler_inference_parameters::inference_parameters::InferenceParameters; - -pub struct SamplerChainFactory { - pub inference_parameters: InferenceParameters, - pub n_vocab: i32, -} - -impl SamplerChainFactory { - pub fn create(&self, seed: u32) -> Result { - let inference_parameters = &self.inference_parameters; - let samplers = [ - LlamaSampler::penalties( - self.n_vocab, - inference_parameters.penalty_last_n, - inference_parameters.penalty_repeat, - inference_parameters.penalty_frequency, - inference_parameters.penalty_presence, - ), - LlamaSampler::top_k(inference_parameters.top_k), - LlamaSampler::top_p(inference_parameters.top_p, 0), - LlamaSampler::min_p(inference_parameters.min_p, 0), - LlamaSampler::temp(inference_parameters.temperature), - LlamaSampler::dist(seed), - ]; - - LlamaSampler::chain_simple(samplers.into_iter().collect::, _>>()?) - } -} - -#[cfg(test)] -mod tests { - use llama_cpp_bindings::token::LlamaToken; - use llama_cpp_bindings::token::data::LlamaTokenData; - use llama_cpp_bindings::token::data_array::LlamaTokenDataArray; - - use paddler_inference_parameters::inference_parameters::InferenceParameters; - - use super::SamplerChainFactory; - - #[test] - fn chain_honors_configured_top_k() { - let inference_parameters = InferenceParameters { - top_k: 1, - ..InferenceParameters::default() - }; - let sampler_chain = SamplerChainFactory { - inference_parameters, - n_vocab: 3, - } - .create(7) - .unwrap(); - let mut candidates = LlamaTokenDataArray::from_iter( - [ - LlamaTokenData::new(LlamaToken(0), 0.5, 0.0), - LlamaTokenData::new(LlamaToken(1), 3.0, 0.0), - LlamaTokenData::new(LlamaToken(2), 1.0, 0.0), - ], - false, - ); - - candidates.apply_sampler(&sampler_chain).unwrap(); - - assert_eq!(candidates.selected_token(), Some(LlamaToken(1))); - } -} diff --git a/paddler_agent/src/send_startup_signal.rs b/paddler_agent/src/send_startup_signal.rs deleted file mode 100644 index 83693b108..000000000 --- a/paddler_agent/src/send_startup_signal.rs +++ /dev/null @@ -1,51 +0,0 @@ -use log::debug; -use tokio::sync::oneshot; - -use crate::startup_signal_delivery::StartupSignalDelivery; - -pub fn send_startup_signal( - startup_signal_tx: oneshot::Sender, - signal: TSignal, -) -> StartupSignalDelivery { - match startup_signal_tx.send(signal) { - Ok(()) => StartupSignalDelivery::Delivered, - Err(_undelivered_signal) => { - debug!("The arbiter abandoned the scheduler startup; stopping the scheduler thread"); - - StartupSignalDelivery::Abandoned - } - } -} - -#[cfg(test)] -mod tests { - use std::mem::discriminant; - - use tokio::sync::oneshot; - - use super::send_startup_signal; - use crate::startup_signal_delivery::StartupSignalDelivery; - - #[test] - fn delivers_the_signal_to_a_live_receiver() { - let (startup_signal_tx, startup_signal_rx) = oneshot::channel::<()>(); - - assert_eq!( - discriminant(&send_startup_signal(startup_signal_tx, ())), - discriminant(&StartupSignalDelivery::Delivered) - ); - assert_eq!(startup_signal_rx.blocking_recv(), Ok(())); - } - - #[test] - fn reports_an_abandoned_startup_when_the_receiver_was_dropped() { - let (startup_signal_tx, startup_signal_rx) = oneshot::channel::<()>(); - - drop(startup_signal_rx); - - assert_eq!( - discriminant(&send_startup_signal(startup_signal_tx, ())), - discriminant(&StartupSignalDelivery::Abandoned) - ); - } -} diff --git a/paddler_agent/src/served_capabilities.rs b/paddler_agent/src/served_capabilities.rs new file mode 100644 index 000000000..ae57dc4cd --- /dev/null +++ b/paddler_agent/src/served_capabilities.rs @@ -0,0 +1,7 @@ +use crate::inference_capabilities::InferenceCapabilities; +use crate::served_decisions::ServedDecisions; +use crate::served_embeddings::ServedEmbeddings; +use crate::served_text_generation::ServedTextGeneration; + +pub type ServedCapabilities = + InferenceCapabilities; diff --git a/paddler_agent/src/served_decisions.rs b/paddler_agent/src/served_decisions.rs new file mode 100644 index 000000000..e026d1199 --- /dev/null +++ b/paddler_agent/src/served_decisions.rs @@ -0,0 +1,37 @@ +use paddler_agent_decision::decision_error::DecisionError; +use paddler_agent_decision::decision_request_preparer::DecisionRequestPreparer; +use paddler_agent_decision::reports_decision_outcome::ReportsDecisionOutcome as _; +use paddler_agent_runtime::agent_request::AgentRequest; +use paddler_agent_runtime::scheduler_handle::SchedulerHandle; +use paddler_agent_runtime::shuts_down::ShutsDown; +use paddler_messaging::decision_result::DecisionResult; +use paddler_messaging::request_params::decide_params::DecideParams; + +pub enum ServedDecisions { + Disabled, + Enabled(SchedulerHandle), +} + +impl ServedDecisions { + pub fn forward( + &self, + agent_name: Option<&str>, + request: AgentRequest, + ) { + match self { + Self::Disabled => { + DecisionError::DecisionsNotServed.report(agent_name, &request.response_tx); + } + Self::Enabled(scheduler_handle) => scheduler_handle.request_preparer.prepare(request), + } + } +} + +impl ShutsDown for ServedDecisions { + async fn shut_down(self) { + match self { + Self::Disabled => {} + Self::Enabled(scheduler_handle) => scheduler_handle.shut_down().await, + } + } +} diff --git a/paddler_agent/src/served_embeddings.rs b/paddler_agent/src/served_embeddings.rs new file mode 100644 index 000000000..58a400bdc --- /dev/null +++ b/paddler_agent/src/served_embeddings.rs @@ -0,0 +1,4 @@ +use paddler_agent_embeddings::embedding_batch_preparer::EmbeddingBatchPreparer; +use paddler_agent_runtime::scheduler_handle::SchedulerHandle; + +pub type ServedEmbeddings = SchedulerHandle; diff --git a/paddler_agent/src/served_model.rs b/paddler_agent/src/served_model.rs new file mode 100644 index 000000000..f59a79ffb --- /dev/null +++ b/paddler_agent/src/served_model.rs @@ -0,0 +1,160 @@ +use tokio::join; + +use paddler_agent_runtime::loaded_llama_model::LoadedLlamaModel; +use paddler_agent_runtime::shuts_down::ShutsDown; +use paddler_agent_status::serving_report::ServingReport; +use paddler_agent_text_generation::text_generation_request::TextGenerationRequest; + +use crate::capability_plan::CapabilityPlan; +use crate::inference_capabilities::InferenceCapabilities; +use crate::loaded_model_serving_plan::LoadedModelServingPlan; +use crate::model_serving_settings::ModelServingSettings; +use crate::pipeline_request::PipelineRequest; +use crate::retained_serving::RetainedServing; +use crate::served_capabilities::ServedCapabilities; + +pub struct ServedModel { + pub capabilities: ServedCapabilities, + pub loaded_llama_model: LoadedLlamaModel, + pub model_serving_settings: ModelServingSettings, +} + +impl ServedModel { + pub fn forward(&self, agent_name: Option<&str>, request: PipelineRequest) { + match request { + PipelineRequest::ContinueFromConversationHistory(request) => { + self.capabilities.text_generation.request_preparer.prepare( + TextGenerationRequest::ContinueFromConversationHistory(request), + ); + } + PipelineRequest::ContinueFromRawPrompt(request) => { + self.capabilities + .text_generation + .request_preparer + .prepare(TextGenerationRequest::ContinueFromRawPrompt(request)); + } + PipelineRequest::Decide(request) => { + self.capabilities.decisions.forward(agent_name, request); + } + PipelineRequest::GenerateEmbeddingBatch(request) => { + self.capabilities + .embeddings + .request_preparer + .prepare(request); + } + } + } + + #[must_use] + pub fn keeps_decisions_of(&self, desired_settings: &ModelServingSettings) -> bool { + self.serves_the_model_of(desired_settings) + && self.runs_the_runtime_parameters_of(desired_settings) + && self.model_serving_settings.capabilities.decisions + == desired_settings.capabilities.decisions + } + + pub async fn retain_for(self, desired_settings: ModelServingSettings) -> RetainedServing { + let serves_the_desired_model = self.serves_the_model_of(&desired_settings); + let runs_the_desired_runtime_parameters = + self.runs_the_runtime_parameters_of(&desired_settings); + let Self { + capabilities, + loaded_llama_model, + model_serving_settings: served_settings, + } = self; + + if !serves_the_desired_model { + capabilities.shut_down().await; + loaded_llama_model.release().await; + + return RetainedServing::ModelToLoad(desired_settings); + } + + if !runs_the_desired_runtime_parameters { + capabilities.shut_down().await; + + return RetainedServing::LoadedModel( + LoadedModelServingPlan::starting_every_capability( + loaded_llama_model, + desired_settings, + ), + ); + } + + let InferenceCapabilities { + decisions, + embeddings, + text_generation, + } = capabilities; + + let (decisions_plan, embeddings_plan, text_generation_plan) = join!( + CapabilityPlan::retaining( + decisions, + &served_settings.capabilities.decisions, + &desired_settings.capabilities.decisions, + ), + CapabilityPlan::retaining( + embeddings, + &served_settings.capabilities.embeddings, + &desired_settings.capabilities.embeddings, + ), + CapabilityPlan::retaining( + text_generation, + &served_settings.capabilities.text_generation, + &desired_settings.capabilities.text_generation, + ), + ); + + RetainedServing::LoadedModel(LoadedModelServingPlan { + capability_plans: InferenceCapabilities { + decisions: decisions_plan, + embeddings: embeddings_plan, + text_generation: text_generation_plan, + }, + loaded_llama_model, + model_serving_settings: desired_settings, + }) + } + + fn serves_the_model_of(&self, desired_settings: &ModelServingSettings) -> bool { + self.loaded_llama_model.llama_model_settings == desired_settings.llama_model_settings() + } + + fn runs_the_runtime_parameters_of(&self, desired_settings: &ModelServingSettings) -> bool { + self.model_serving_settings.model_runtime_parameters + == desired_settings.model_runtime_parameters + } + + #[must_use] + pub fn serving_report(&self, uses_chat_template_override: bool) -> ServingReport { + ServingReport { + decisions: self.model_serving_settings.capabilities.decisions.serving(), + model_path: self + .loaded_llama_model + .llama_model_settings + .reported_model_path() + .model_path, + serving_issues: self + .capabilities + .text_generation + .request_preparer + .preparation + .conversation_support + .serving_issues(), + uses_chat_template_override, + } + } +} + +impl ShutsDown for ServedModel { + async fn shut_down(self) { + let Self { + capabilities, + loaded_llama_model, + .. + } = self; + + capabilities.shut_down().await; + loaded_llama_model.release().await; + } +} diff --git a/paddler_agent/src/served_text_generation.rs b/paddler_agent/src/served_text_generation.rs new file mode 100644 index 000000000..a2d7c439d --- /dev/null +++ b/paddler_agent/src/served_text_generation.rs @@ -0,0 +1,4 @@ +use paddler_agent_runtime::scheduler_handle::SchedulerHandle; +use paddler_agent_text_generation::generation_request_preparer::GenerationRequestPreparer; + +pub type ServedTextGeneration = SchedulerHandle; diff --git a/paddler_agent/src/serving_interruption.rs b/paddler_agent/src/serving_interruption.rs new file mode 100644 index 000000000..e2083cc66 --- /dev/null +++ b/paddler_agent/src/serving_interruption.rs @@ -0,0 +1,8 @@ +use paddler_agent_runtime::loaded_llama_model::LoadedLlamaModel; + +use crate::capability_startup_interruption::CapabilityStartupInterruption; + +pub struct ServingInterruption { + pub interruption: CapabilityStartupInterruption, + pub loaded_llama_model: LoadedLlamaModel, +} diff --git a/paddler_agent/src/serving_preparation.rs b/paddler_agent/src/serving_preparation.rs new file mode 100644 index 000000000..fbee2cc43 --- /dev/null +++ b/paddler_agent/src/serving_preparation.rs @@ -0,0 +1,54 @@ +use tokio_util::sync::CancellationToken; + +use paddler_agent_runtime::inference_runtime_context::InferenceRuntimeContext; + +use crate::agent_applicable_state::AgentApplicableState; +use crate::applicable_decisions::ApplicableDecisions; +use crate::capability_starter::CapabilityStarter; +use crate::capability_startup_interruption::CapabilityStartupInterruption; +use crate::preparation_outcome::PreparationOutcome; +use crate::prepared_serving::PreparedServing; +use crate::retry_delay::RetryDelay; +use crate::retry_delay_outcome::RetryDelayOutcome; + +pub struct ServingPreparation { + pub agent_applicable_state: AgentApplicableState, + pub decisions_to_prepare: ApplicableDecisions, + pub retry_delay: RetryDelay, +} + +impl ServingPreparation { + pub async fn prepare( + self, + cancellation_token: CancellationToken, + inference_runtime_context: InferenceRuntimeContext, + ) -> PreparationOutcome { + let Self { + agent_applicable_state, + decisions_to_prepare, + retry_delay, + } = self; + + if retry_delay.elapse(&cancellation_token).await == RetryDelayOutcome::Cancelled { + return PreparationOutcome::Cancelled; + } + + match (CapabilityStarter { + cancellation_token: &cancellation_token, + inference_runtime_context: &inference_runtime_context, + }) + .prepare_decisions(&decisions_to_prepare) + .await + { + Ok(prepared_decisions) => PreparationOutcome::Prepared(PreparedServing { + agent_applicable_state, + prepared_decisions, + }), + Err(CapabilityStartupInterruption::Cancelled) => PreparationOutcome::Cancelled, + Err(CapabilityStartupInterruption::Failed(error)) => PreparationOutcome::Failed { + agent_applicable_state, + error, + }, + } + } +} diff --git a/paddler_agent/src/serving_replacement.rs b/paddler_agent/src/serving_replacement.rs new file mode 100644 index 000000000..19a9ed69f --- /dev/null +++ b/paddler_agent/src/serving_replacement.rs @@ -0,0 +1,117 @@ +use tokio_util::sync::CancellationToken; + +use paddler_agent_runtime::inference_runtime_context::InferenceRuntimeContext; +use paddler_agent_runtime::shuts_down::ShutsDown as _; + +use crate::agent_applicable_state::AgentApplicableState; +use crate::capability_starter::CapabilityStarter; +use crate::capability_startup_interruption::CapabilityStartupInterruption; +use crate::finished_serving_startup::FinishedServingStartup; +use crate::loaded_model_serving_plan::LoadedModelServingPlan; +use crate::model_serving::ModelServing; +use crate::model_serving_settings::ModelServingSettings; +use crate::prepared_decisions::PreparedDecisions; +use crate::prepared_serving::PreparedServing; +use crate::replacement_outcome::ReplacementOutcome; +use crate::retained_serving::RetainedServing; +use crate::serving_interruption::ServingInterruption; + +pub struct ServingReplacement { + pub held: ModelServing, + pub prepared_serving: PreparedServing, +} + +impl ServingReplacement { + pub async fn replace( + self, + cancellation_token: CancellationToken, + inference_runtime_context: InferenceRuntimeContext, + ) -> FinishedServingStartup { + let Self { + held, + prepared_serving: + PreparedServing { + agent_applicable_state, + prepared_decisions, + }, + } = self; + let capability_starter = CapabilityStarter { + cancellation_token: &cancellation_token, + inference_runtime_context: &inference_runtime_context, + }; + let outcome = match &agent_applicable_state { + AgentApplicableState::NotConfigured | AgentApplicableState::WithoutModel { .. } => { + held.shut_down().await; + + ReplacementOutcome::NothingToServe + } + AgentApplicableState::WithModel(desired_settings) => { + Self::serve( + &capability_starter, + held, + desired_settings.clone(), + prepared_decisions, + ) + .await + } + }; + + FinishedServingStartup { + agent_applicable_state, + outcome, + } + } + + async fn serve( + capability_starter: &CapabilityStarter<'_>, + held: ModelServing, + desired_settings: ModelServingSettings, + prepared_decisions: PreparedDecisions, + ) -> ReplacementOutcome { + let loaded_model_serving_plan = match held.retain_for(desired_settings).await { + RetainedServing::LoadedModel(loaded_model_serving_plan) => loaded_model_serving_plan, + RetainedServing::ModelToLoad(desired_settings) => match capability_starter + .load_model(desired_settings.llama_model_settings()) + .await + { + Ok(loaded_llama_model) => LoadedModelServingPlan::starting_every_capability( + loaded_llama_model, + desired_settings, + ), + Err(CapabilityStartupInterruption::Cancelled) => { + return ReplacementOutcome::Cancelled { + held: ModelServing::Idle, + }; + } + Err(CapabilityStartupInterruption::Failed(error)) => { + return ReplacementOutcome::Failed { + error, + held: ModelServing::Idle, + }; + } + }, + }; + + match capability_starter + .serve(loaded_model_serving_plan, prepared_decisions) + .await + { + Ok(served_model) => ReplacementOutcome::Serving(Box::new(served_model)), + Err(serving_interruption) => match *serving_interruption { + ServingInterruption { + interruption: CapabilityStartupInterruption::Cancelled, + loaded_llama_model, + } => ReplacementOutcome::Cancelled { + held: ModelServing::Holding(loaded_llama_model), + }, + ServingInterruption { + interruption: CapabilityStartupInterruption::Failed(error), + loaded_llama_model, + } => ReplacementOutcome::Failed { + error, + held: ModelServing::Holding(loaded_llama_model), + }, + }, + } + } +} diff --git a/paddler_agent/src/serving_startup.rs b/paddler_agent/src/serving_startup.rs new file mode 100644 index 000000000..a17eb7575 --- /dev/null +++ b/paddler_agent/src/serving_startup.rs @@ -0,0 +1,66 @@ +use std::future::pending; +use std::mem; + +use log::error; + +use paddler_agent_runtime::shuts_down::ShutsDown; + +use crate::finished_serving_startup::FinishedServingStartup; +use crate::preparation_outcome::PreparationOutcome; +use crate::serving_startup_progress::ServingStartupProgress; +use crate::startup_phase::StartupPhase; + +pub enum ServingStartup { + Idle, + Preparing(StartupPhase), + Replacing(StartupPhase), +} + +impl ServingStartup { + pub fn cancel(&self) { + match self { + Self::Idle => {} + Self::Preparing(StartupPhase { + cancellation_token, .. + }) + | Self::Replacing(StartupPhase { + cancellation_token, .. + }) => cancellation_token.cancel(), + } + } + + pub async fn next_progress(&mut self) -> ServingStartupProgress { + let serving_startup_progress = match self { + Self::Idle => pending().await, + Self::Preparing(StartupPhase { outcome, .. }) => { + ServingStartupProgress::Prepared(outcome.as_mut().await) + } + Self::Replacing(StartupPhase { outcome, .. }) => { + ServingStartupProgress::Replaced(outcome.as_mut().await) + } + }; + + drop(mem::replace(self, Self::Idle)); + + serving_startup_progress + } +} + +impl ShutsDown for ServingStartup { + async fn shut_down(self) { + self.cancel(); + + match self { + Self::Idle => {} + Self::Preparing(StartupPhase { outcome, .. }) => match outcome.await { + PreparationOutcome::Cancelled | PreparationOutcome::Prepared(_) => {} + PreparationOutcome::Failed { error, .. } => { + error!("Failed to prepare the serving that the shutdown stopped: {error}"); + } + }, + Self::Replacing(StartupPhase { outcome, .. }) => { + outcome.await.outcome.shut_down().await; + } + } + } +} diff --git a/paddler_agent/src/serving_startup_progress.rs b/paddler_agent/src/serving_startup_progress.rs new file mode 100644 index 000000000..d1a3593f4 --- /dev/null +++ b/paddler_agent/src/serving_startup_progress.rs @@ -0,0 +1,7 @@ +use crate::finished_serving_startup::FinishedServingStartup; +use crate::preparation_outcome::PreparationOutcome; + +pub enum ServingStartupProgress { + Prepared(PreparationOutcome), + Replaced(FinishedServingStartup), +} diff --git a/paddler_agent/src/slot_guard.rs b/paddler_agent/src/slot_guard.rs deleted file mode 100644 index fb03ab3db..000000000 --- a/paddler_agent/src/slot_guard.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::sync::Arc; - -use paddler_agent_status::dispenses_slots::DispensesSlots as _; -use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; - -pub struct SlotGuard { - slot_aggregated_status: Arc, -} - -impl SlotGuard { - #[must_use] - pub fn new(slot_aggregated_status: Arc) -> Self { - slot_aggregated_status.take_slot(); - - Self { - slot_aggregated_status, - } - } -} - -impl Drop for SlotGuard { - fn drop(&mut self) { - self.slot_aggregated_status.release_slot(); - } -} - -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; - - use crate::slot_guard::SlotGuard; - - #[tokio::test] - async fn increments_slot_on_construct_and_releases_on_drop() { - let slot_aggregated_status = Arc::new(SlotAggregatedStatus::new(4)); - - assert_eq!(slot_aggregated_status.slots_processing_count(), 0); - - { - let _guard = SlotGuard::new(slot_aggregated_status.clone()); - - assert_eq!(slot_aggregated_status.slots_processing_count(), 1); - } - - assert_eq!(slot_aggregated_status.slots_processing_count(), 0); - } -} diff --git a/paddler_agent/src/startup_phase.rs b/paddler_agent/src/startup_phase.rs new file mode 100644 index 000000000..81cf4f27b --- /dev/null +++ b/paddler_agent/src/startup_phase.rs @@ -0,0 +1,9 @@ +use std::future::Future; +use std::pin::Pin; + +use tokio_util::sync::CancellationToken; + +pub struct StartupPhase { + pub cancellation_token: CancellationToken, + pub outcome: Pin + Send>>, +} diff --git a/paddler_agent/src/superseding_state.rs b/paddler_agent/src/superseding_state.rs new file mode 100644 index 000000000..876205142 --- /dev/null +++ b/paddler_agent/src/superseding_state.rs @@ -0,0 +1,6 @@ +use crate::agent_applicable_state::AgentApplicableState; + +pub enum SupersedingState { + NotSuperseded, + SupersededBy(AgentApplicableState), +} diff --git a/paddler_agent/src/token_generation.rs b/paddler_agent/src/token_generation.rs deleted file mode 100644 index d196525f3..000000000 --- a/paddler_agent/src/token_generation.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::generation_request_rejection::GenerationRequestRejection; -use crate::token_generation_support::TokenGenerationSupport; - -pub enum TokenGeneration { - DisabledForEmbeddings, - Enabled(Box), -} - -impl TokenGeneration { - pub const fn require_enabled( - &self, - ) -> Result<&TokenGenerationSupport, GenerationRequestRejection> { - match self { - Self::DisabledForEmbeddings => Err(GenerationRequestRejection::TokenGenerationDisabled), - Self::Enabled(token_generation_support) => Ok(token_generation_support), - } - } -} - -#[cfg(test)] -mod tests { - use std::mem::discriminant; - - use super::TokenGeneration; - use crate::generation_request_rejection::GenerationRequestRejection; - - #[test] - fn rejects_generation_in_embeddings_mode() { - assert_eq!( - TokenGeneration::DisabledForEmbeddings - .require_enabled() - .err() - .map(|rejection| discriminant(&rejection)), - Some(discriminant( - &GenerationRequestRejection::TokenGenerationDisabled - )) - ); - } -} diff --git a/paddler_agent/src/token_generation_support.rs b/paddler_agent/src/token_generation_support.rs deleted file mode 100644 index 75f691929..000000000 --- a/paddler_agent/src/token_generation_support.rs +++ /dev/null @@ -1,10 +0,0 @@ -use std::sync::Arc; - -use llama_cpp_bindings::StreamingMarkers; - -use crate::chat_prompt_renderer::ChatPromptRenderer; - -pub struct TokenGenerationSupport { - pub chat_prompt_renderer: ChatPromptRenderer, - pub streaming_markers: Arc, -} diff --git a/paddler_agent/src/token_sampling.rs b/paddler_agent/src/token_sampling.rs deleted file mode 100644 index 90d87a0f8..000000000 --- a/paddler_agent/src/token_sampling.rs +++ /dev/null @@ -1,48 +0,0 @@ -use anyhow::Context as _; -use anyhow::Result; -use llama_cpp_bindings::context::LlamaContext; -use llama_cpp_bindings::sampling::LlamaSampler; -use llama_cpp_bindings::token::data_array::LlamaTokenDataArray; - -use crate::grammar_sampling::GrammarSampling; -use crate::sampling_outcome::SamplingOutcome; - -pub struct TokenSampling { - pub chain: LlamaSampler, - pub grammar: GrammarSampling, -} - -impl TokenSampling { - pub fn sample( - &mut self, - llama_context: &LlamaContext, - batch_index: i32, - candidates: &mut LlamaTokenDataArray, - ) -> Result { - llama_context - .fill_token_data_array_ith(batch_index, candidates) - .context("failed to read token data array for sampling")?; - self.grammar - .constrain(candidates) - .context("failed to apply grammar sampler to token data array")?; - candidates - .apply_sampler(&self.chain) - .context("failed to apply sampler chain to token data array")?; - - let Some(token) = candidates.selected_token() else { - return Ok(SamplingOutcome::AllCandidatesEliminated); - }; - - self.chain - .accept(token) - .context("sampler chain failed to accept the selected token")?; - - if let Err(grammar_rejection) = self.grammar.accept(token) { - return Ok(SamplingOutcome::GrammarRejectedModelOutput( - grammar_rejection.to_string(), - )); - } - - Ok(SamplingOutcome::Token(token)) - } -} diff --git a/paddler_agent/src/tool_call_buffer.rs b/paddler_agent/src/tool_call_buffer.rs deleted file mode 100644 index e8ef7e13d..000000000 --- a/paddler_agent/src/tool_call_buffer.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::mem::take; - -#[derive(Debug, Default)] -pub struct ToolCallBuffer { - accumulated: String, -} - -impl ToolCallBuffer { - pub fn append(&mut self, fragment: &str) { - self.accumulated.push_str(fragment); - } - - #[must_use] - pub fn take(&mut self) -> String { - take(&mut self.accumulated) - } - - #[must_use] - pub const fn is_empty(&self) -> bool { - self.accumulated.is_empty() - } -} - -#[cfg(test)] -mod tests { - use super::ToolCallBuffer; - - #[test] - fn take_returns_every_appended_fragment_and_empties_the_buffer() { - let mut buffer = ToolCallBuffer::default(); - - buffer.append("\n"); - buffer.append("{\"name\":\"x\"}"); - - assert_eq!(buffer.take(), "\n{\"name\":\"x\"}"); - assert!(buffer.is_empty()); - } -} diff --git a/paddler_agent/src/tool_call_pipeline.rs b/paddler_agent/src/tool_call_pipeline.rs deleted file mode 100644 index 3f9e72fcf..000000000 --- a/paddler_agent/src/tool_call_pipeline.rs +++ /dev/null @@ -1,72 +0,0 @@ -use llama_cpp_bindings::ChatMessageParseOutcome; -use llama_cpp_bindings::ChatMessageParser; -use llama_cpp_bindings::ParsedToolCall; -use llama_cpp_bindings::RawChatMessage; - -use paddler_messaging::raw_tool_call_tokens::RawToolCallTokens; -use paddler_tool_call_validator::tool_call_validator::ToolCallValidator; - -use crate::tool_call_buffer::ToolCallBuffer; -use crate::tool_call_event::ToolCallEvent; - -pub struct ToolCallPipeline { - buffer: ToolCallBuffer, - chat_message_parser: ChatMessageParser, - validator: ToolCallValidator, -} - -impl ToolCallPipeline { - #[must_use] - pub fn new(chat_message_parser: ChatMessageParser, validator: ToolCallValidator) -> Self { - Self { - buffer: ToolCallBuffer::default(), - chat_message_parser, - validator, - } - } - - pub fn feed(&mut self, fragment: &str) { - self.buffer.append(fragment); - } - - #[must_use] - pub const fn buffer_is_empty(&self) -> bool { - self.buffer.is_empty() - } - - pub fn finalize(&mut self) -> ToolCallEvent { - let input = self.buffer.take(); - - if input.is_empty() { - return ToolCallEvent::Resolved(Vec::new()); - } - - match self.chat_message_parser.parse(&input, false) { - Ok(ChatMessageParseOutcome::Recognized(parsed)) => { - self.validate_resolved(parsed.tool_calls) - } - Ok(ChatMessageParseOutcome::Unrecognized(RawChatMessage { - text, - ffi_error_message, - .. - })) => ToolCallEvent::UnrecognizedFormat(RawToolCallTokens { - text, - ffi_error_message, - }), - Err(parse_error) => ToolCallEvent::ParseFailed(parse_error), - } - } - - fn validate_resolved(&self, tool_calls: Vec) -> ToolCallEvent { - let validation_errors: Vec<_> = tool_calls - .iter() - .filter_map(|tool_call| self.validator.validate(tool_call).err()) - .collect(); - - if validation_errors.is_empty() { - ToolCallEvent::Resolved(tool_calls) - } else { - ToolCallEvent::ValidationFailed(validation_errors) - } - } -} diff --git a/paddler_agent/tests/a_new_subscriber_sees_the_state_applied_before_it_subscribed.rs b/paddler_agent/tests/a_new_subscriber_sees_the_state_applied_before_it_subscribed.rs new file mode 100644 index 000000000..2f6aa5c50 --- /dev/null +++ b/paddler_agent/tests/a_new_subscriber_sees_the_state_applied_before_it_subscribed.rs @@ -0,0 +1,16 @@ +use paddler_agent::agent_applicable_state_holder::AgentApplicableStateHolder; + +use crate::state_overriding_the_chat_template::state_overriding_the_chat_template; + +#[test] +fn a_new_subscriber_sees_the_state_applied_before_it_subscribed() { + let holder = AgentApplicableStateHolder::default(); + let applied_state = state_overriding_the_chat_template(); + + holder.set_agent_applicable_state(applied_state.clone()); + + let agent_applicable_state_rx = holder.subscribe(); + + assert!(agent_applicable_state_rx.has_changed().unwrap()); + assert_eq!(*agent_applicable_state_rx.borrow(), applied_state); +} diff --git a/paddler_agent/tests/a_pipeline_that_fails_to_start_is_retried_until_it_is_stuck.rs b/paddler_agent/tests/a_pipeline_that_fails_to_start_is_retried_until_it_is_stuck.rs new file mode 100644 index 000000000..5a3ca8a89 --- /dev/null +++ b/paddler_agent/tests/a_pipeline_that_fails_to_start_is_retried_until_it_is_stuck.rs @@ -0,0 +1,32 @@ +use tokio_util::sync::CancellationToken; + +use paddler_messaging::agent_state_application_status::AgentStateApplicationStatus; + +use crate::arbiter_under_test::ArbiterUnderTest; +use crate::state_whose_model_cannot_load::state_whose_model_cannot_load; + +#[tokio::test] +async fn a_pipeline_that_fails_to_start_is_retried_until_it_is_stuck() { + let mut arbiter_under_test = ArbiterUnderTest::new(CancellationToken::new()); + + arbiter_under_test + .pipeline_arbiter + .apply(state_whose_model_cannot_load()); + + assert_eq!( + [ + arbiter_under_test.conclude_next_progress().await, + arbiter_under_test.conclude_next_progress().await, + arbiter_under_test.conclude_next_progress().await, + arbiter_under_test.conclude_next_progress().await, + ], + [ + AgentStateApplicationStatus::Fresh, + AgentStateApplicationStatus::AttemptedAndRetrying, + AgentStateApplicationStatus::AttemptedAndRetrying, + AgentStateApplicationStatus::Stuck, + ] + ); + + arbiter_under_test.pipeline_arbiter.shut_down().await; +} diff --git a/paddler_agent/tests/a_reconciliation_replaced_before_its_worker_runs_leaves_no_issue_behind.rs b/paddler_agent/tests/a_reconciliation_replaced_before_its_worker_runs_leaves_no_issue_behind.rs new file mode 100644 index 000000000..cb669c628 --- /dev/null +++ b/paddler_agent/tests/a_reconciliation_replaced_before_its_worker_runs_leaves_no_issue_behind.rs @@ -0,0 +1,36 @@ +use std::path::Path; + +use paddler_messaging::agent_desired_state::AgentDesiredState; +use paddler_messaging::agent_issue::AgentIssue; +use paddler_messaging::agent_issue_params::model_path::ModelPath; +use paddler_model_source::agent_local_model_path::AgentLocalModelPath; +use paddler_model_source::model_source::ModelSource; + +use crate::reconciliation_after_replacement::ReconciliationAfterReplacement; +use crate::reconciliation_replaced_before_its_worker_runs::ReconciliationReplacedBeforeItsWorkerRuns; + +const MISSING_MODEL_PATH: &str = "/paddler-missing-model-of-a-replaced-desired-state.gguf"; + +#[test] +fn a_reconciliation_replaced_before_its_worker_runs_leaves_no_issue_behind() { + let ReconciliationAfterReplacement { + slot_aggregated_status, + .. + } = ReconciliationReplacedBeforeItsWorkerRuns { + replaced_desired_state: AgentDesiredState { + model: ModelSource::LocalToAgent( + AgentLocalModelPath::from_absolute_path(Path::new(MISSING_MODEL_PATH)) + .expect("the missing model path must be absolute"), + ) + .into_agent_desired_model(), + ..AgentDesiredState::default() + }, + } + .run(); + + assert!( + !slot_aggregated_status.has_issue(&AgentIssue::ModelFileDoesNotExist(ModelPath { + model_path: MISSING_MODEL_PATH.to_owned(), + })) + ); +} diff --git a/paddler_agent/tests/a_reconciliation_replaced_before_its_worker_runs_never_publishes_its_state.rs b/paddler_agent/tests/a_reconciliation_replaced_before_its_worker_runs_never_publishes_its_state.rs new file mode 100644 index 000000000..42e61992e --- /dev/null +++ b/paddler_agent/tests/a_reconciliation_replaced_before_its_worker_runs_never_publishes_its_state.rs @@ -0,0 +1,21 @@ +use paddler_agent::agent_applicable_state::AgentApplicableState; +use paddler_messaging::agent_desired_state::AgentDesiredState; + +use crate::reconciliation_after_replacement::ReconciliationAfterReplacement; +use crate::reconciliation_replaced_before_its_worker_runs::ReconciliationReplacedBeforeItsWorkerRuns; + +#[test] +fn a_reconciliation_replaced_before_its_worker_runs_never_publishes_its_state() { + let ReconciliationAfterReplacement { + agent_applicable_state_holder, + .. + } = ReconciliationReplacedBeforeItsWorkerRuns { + replaced_desired_state: AgentDesiredState::default(), + } + .run(); + + assert_eq!( + agent_applicable_state_holder.get_agent_applicable_state(), + AgentApplicableState::NotConfigured + ); +} diff --git a/paddler_agent/tests/a_replacement_cancelled_by_the_agent_shutting_down_changes_nothing.rs b/paddler_agent/tests/a_replacement_cancelled_by_the_agent_shutting_down_changes_nothing.rs new file mode 100644 index 000000000..adc18cefa --- /dev/null +++ b/paddler_agent/tests/a_replacement_cancelled_by_the_agent_shutting_down_changes_nothing.rs @@ -0,0 +1,35 @@ +use tokio_util::sync::CancellationToken; + +use paddler_messaging::produces_snapshot::ProducesSnapshot as _; + +use crate::arbiter_under_test::ArbiterUnderTest; +use crate::state_whose_model_cannot_load::state_whose_model_cannot_load; + +#[tokio::test] +async fn a_replacement_cancelled_by_the_agent_shutting_down_changes_nothing() { + let agent_shutdown = CancellationToken::new(); + let mut arbiter_under_test = ArbiterUnderTest::new(agent_shutdown.clone()); + + arbiter_under_test + .pipeline_arbiter + .apply(state_whose_model_cannot_load()); + arbiter_under_test.conclude_next_progress().await; + agent_shutdown.cancel(); + + let status_before_the_replacement_concluded = arbiter_under_test + .inference_runtime_context + .slot_aggregated_status + .make_snapshot(); + + arbiter_under_test.conclude_next_progress().await; + + assert_eq!( + arbiter_under_test + .inference_runtime_context + .slot_aggregated_status + .make_snapshot(), + status_before_the_replacement_concluded + ); + + arbiter_under_test.pipeline_arbiter.shut_down().await; +} diff --git a/paddler_agent/tests/a_replacement_whose_model_cannot_load_reports_nothing_as_served.rs b/paddler_agent/tests/a_replacement_whose_model_cannot_load_reports_nothing_as_served.rs new file mode 100644 index 000000000..5bc894090 --- /dev/null +++ b/paddler_agent/tests/a_replacement_whose_model_cannot_load_reports_nothing_as_served.rs @@ -0,0 +1,41 @@ +use tokio_util::sync::CancellationToken; + +use paddler_agent::agent_applicable_state::AgentApplicableState; +use paddler_messaging::agent_state_application_status::AgentStateApplicationStatus; + +use crate::arbiter_under_test::ArbiterUnderTest; +use crate::state_overriding_the_chat_template::state_overriding_the_chat_template; +use crate::state_whose_model_cannot_load::state_whose_model_cannot_load; + +#[tokio::test] +async fn a_replacement_whose_model_cannot_load_reports_nothing_as_served() { + let mut arbiter_under_test = ArbiterUnderTest::new(CancellationToken::new()); + + arbiter_under_test + .pipeline_arbiter + .apply(state_overriding_the_chat_template()); + arbiter_under_test.conclude_next_progress().await; + + assert_eq!( + arbiter_under_test.conclude_next_progress().await, + AgentStateApplicationStatus::Applied + ); + + arbiter_under_test + .pipeline_arbiter + .apply(state_whose_model_cannot_load()); + arbiter_under_test.conclude_next_progress().await; + + assert_eq!( + arbiter_under_test.conclude_next_progress().await, + AgentStateApplicationStatus::AttemptedAndRetrying + ); + assert_eq!( + arbiter_under_test + .served_applicable_state_holder + .get_agent_applicable_state(), + AgentApplicableState::NotConfigured + ); + + arbiter_under_test.pipeline_arbiter.shut_down().await; +} diff --git a/paddler_agent/tests/a_startup_cancelled_by_the_agent_shutting_down_changes_nothing.rs b/paddler_agent/tests/a_startup_cancelled_by_the_agent_shutting_down_changes_nothing.rs new file mode 100644 index 000000000..487cd6390 --- /dev/null +++ b/paddler_agent/tests/a_startup_cancelled_by_the_agent_shutting_down_changes_nothing.rs @@ -0,0 +1,34 @@ +use tokio_util::sync::CancellationToken; + +use paddler_messaging::produces_snapshot::ProducesSnapshot as _; + +use crate::arbiter_under_test::ArbiterUnderTest; +use crate::state_whose_model_cannot_load::state_whose_model_cannot_load; + +#[tokio::test] +async fn a_startup_cancelled_by_the_agent_shutting_down_changes_nothing() { + let agent_shutdown = CancellationToken::new(); + let mut arbiter_under_test = ArbiterUnderTest::new(agent_shutdown.clone()); + + agent_shutdown.cancel(); + arbiter_under_test + .pipeline_arbiter + .apply(state_whose_model_cannot_load()); + + let status_before_the_startup_concluded = arbiter_under_test + .inference_runtime_context + .slot_aggregated_status + .make_snapshot(); + + arbiter_under_test.conclude_next_progress().await; + + assert_eq!( + arbiter_under_test + .inference_runtime_context + .slot_aggregated_status + .make_snapshot(), + status_before_the_startup_concluded + ); + + arbiter_under_test.pipeline_arbiter.shut_down().await; +} diff --git a/paddler_agent/tests/a_state_superseded_while_it_is_prepared_yields_to_its_replacement.rs b/paddler_agent/tests/a_state_superseded_while_it_is_prepared_yields_to_its_replacement.rs new file mode 100644 index 000000000..844961f75 --- /dev/null +++ b/paddler_agent/tests/a_state_superseded_while_it_is_prepared_yields_to_its_replacement.rs @@ -0,0 +1,30 @@ +use tokio_util::sync::CancellationToken; + +use paddler_agent::agent_applicable_state::AgentApplicableState; + +use crate::arbiter_under_test::ArbiterUnderTest; +use crate::state_overriding_the_chat_template::state_overriding_the_chat_template; + +#[tokio::test] +async fn a_state_superseded_while_it_is_prepared_yields_to_its_replacement() { + let mut arbiter_under_test = ArbiterUnderTest::new(CancellationToken::new()); + + arbiter_under_test + .pipeline_arbiter + .apply(AgentApplicableState::NotConfigured); + arbiter_under_test + .pipeline_arbiter + .apply(state_overriding_the_chat_template()); + arbiter_under_test.conclude_next_progress().await; + arbiter_under_test.conclude_next_progress().await; + arbiter_under_test.conclude_next_progress().await; + + assert_eq!( + arbiter_under_test + .served_applicable_state_holder + .get_agent_applicable_state(), + state_overriding_the_chat_template() + ); + + arbiter_under_test.pipeline_arbiter.shut_down().await; +} diff --git a/paddler_agent/tests/a_state_superseded_while_its_model_loads_yields_to_its_replacement.rs b/paddler_agent/tests/a_state_superseded_while_its_model_loads_yields_to_its_replacement.rs new file mode 100644 index 000000000..d952f17c6 --- /dev/null +++ b/paddler_agent/tests/a_state_superseded_while_its_model_loads_yields_to_its_replacement.rs @@ -0,0 +1,30 @@ +use tokio_util::sync::CancellationToken; + +use crate::arbiter_under_test::ArbiterUnderTest; +use crate::state_overriding_the_chat_template::state_overriding_the_chat_template; +use crate::state_whose_model_cannot_load::state_whose_model_cannot_load; + +#[tokio::test] +async fn a_state_superseded_while_its_model_loads_yields_to_its_replacement() { + let mut arbiter_under_test = ArbiterUnderTest::new(CancellationToken::new()); + + arbiter_under_test + .pipeline_arbiter + .apply(state_whose_model_cannot_load()); + arbiter_under_test.conclude_next_progress().await; + arbiter_under_test + .pipeline_arbiter + .apply(state_overriding_the_chat_template()); + arbiter_under_test.conclude_next_progress().await; + arbiter_under_test.conclude_next_progress().await; + arbiter_under_test.conclude_next_progress().await; + + assert_eq!( + arbiter_under_test + .served_applicable_state_holder + .get_agent_applicable_state(), + state_overriding_the_chat_template() + ); + + arbiter_under_test.pipeline_arbiter.shut_down().await; +} diff --git a/paddler_agent/tests/a_state_superseding_a_decisions_preparation_that_fails_leaves_no_issue_behind.rs b/paddler_agent/tests/a_state_superseding_a_decisions_preparation_that_fails_leaves_no_issue_behind.rs new file mode 100644 index 000000000..379118f07 --- /dev/null +++ b/paddler_agent/tests/a_state_superseding_a_decisions_preparation_that_fails_leaves_no_issue_behind.rs @@ -0,0 +1,17 @@ +use crate::slots_insufficient_for_decisions::slots_insufficient_for_decisions; +use crate::startup_superseded_while_its_blocking_work_waits::StartupSupersededWhileItsBlockingWorkWaits; +use crate::state_enabling_decisions::state_enabling_decisions; + +#[test] +fn a_state_superseding_a_decisions_preparation_that_fails_leaves_no_issue_behind() { + let slot_aggregated_status = StartupSupersededWhileItsBlockingWorkWaits { + superseded_state: state_enabling_decisions(), + } + .run(); + + assert!( + !slot_aggregated_status.has_issue(&slots_insufficient_for_decisions( + slot_aggregated_status.desired_slots_total + )) + ); +} diff --git a/paddler_agent/tests/a_state_superseding_a_model_load_that_fails_leaves_no_issue_behind.rs b/paddler_agent/tests/a_state_superseding_a_model_load_that_fails_leaves_no_issue_behind.rs new file mode 100644 index 000000000..98428d4ee --- /dev/null +++ b/paddler_agent/tests/a_state_superseding_a_model_load_that_fails_leaves_no_issue_behind.rs @@ -0,0 +1,19 @@ +use paddler_messaging::agent_issue::AgentIssue; +use paddler_messaging::agent_issue_params::model_path::ModelPath; + +use crate::settings_whose_model_cannot_load::settings_whose_model_cannot_load; +use crate::startup_superseded_while_its_blocking_work_waits::StartupSupersededWhileItsBlockingWorkWaits; +use crate::state_whose_model_cannot_load::state_whose_model_cannot_load; + +#[test] +fn a_state_superseding_a_model_load_that_fails_leaves_no_issue_behind() { + assert!( + !StartupSupersededWhileItsBlockingWorkWaits { + superseded_state: state_whose_model_cannot_load(), + } + .run() + .has_issue(&AgentIssue::ModelCannotBeLoaded(ModelPath::from( + settings_whose_model_cannot_load().model_path.as_path() + ))) + ); +} diff --git a/paddler_agent/tests/a_state_without_a_model_serves_nothing_and_forgets_the_previous_model.rs b/paddler_agent/tests/a_state_without_a_model_serves_nothing_and_forgets_the_previous_model.rs new file mode 100644 index 000000000..2bf90a434 --- /dev/null +++ b/paddler_agent/tests/a_state_without_a_model_serves_nothing_and_forgets_the_previous_model.rs @@ -0,0 +1,54 @@ +use std::collections::BTreeMap; +use std::sync::Arc; + +use tokio_util::sync::CancellationToken; + +use paddler_agent::agent_applicable_state::AgentApplicableState; +use paddler_messaging::agent_runtime_status::AgentRuntimeStatus; +use paddler_messaging::agent_state_application_status::AgentStateApplicationStatus; +use paddler_messaging::model_metadata::ModelMetadata; +use paddler_messaging::produces_snapshot::ProducesSnapshot as _; + +use crate::arbiter_under_test::ArbiterUnderTest; + +#[tokio::test] +async fn a_state_without_a_model_serves_nothing_and_forgets_the_previous_model() { + let mut arbiter_under_test = ArbiterUnderTest::new(CancellationToken::new()); + + arbiter_under_test + .inference_runtime_context + .model_metadata_holder + .set_model_metadata(Arc::new(ModelMetadata { + metadata: BTreeMap::from([("general.name".to_owned(), "previous".to_owned())]), + })); + arbiter_under_test + .pipeline_arbiter + .apply(AgentApplicableState::NotConfigured); + + assert_eq!( + arbiter_under_test.conclude_next_progress().await, + AgentStateApplicationStatus::Fresh + ); + assert_eq!( + arbiter_under_test.conclude_next_progress().await, + AgentStateApplicationStatus::Applied + ); + assert_eq!( + arbiter_under_test + .inference_runtime_context + .slot_aggregated_status + .make_snapshot() + .status + .runtime, + AgentRuntimeStatus::Idle + ); + assert_eq!( + arbiter_under_test + .inference_runtime_context + .model_metadata_holder + .get_model_metadata(), + None + ); + + arbiter_under_test.pipeline_arbiter.shut_down().await; +} diff --git a/paddler_agent/tests/an_identical_state_is_not_applied_again.rs b/paddler_agent/tests/an_identical_state_is_not_applied_again.rs new file mode 100644 index 000000000..759845abf --- /dev/null +++ b/paddler_agent/tests/an_identical_state_is_not_applied_again.rs @@ -0,0 +1,18 @@ +use paddler_agent::agent_applicable_state_holder::AgentApplicableStateHolder; + +use crate::state_overriding_the_chat_template::state_overriding_the_chat_template; + +#[test] +fn an_identical_state_is_not_applied_again() { + let holder = AgentApplicableStateHolder::default(); + let applied_state = state_overriding_the_chat_template(); + + holder.set_agent_applicable_state(applied_state.clone()); + + let mut agent_applicable_state_rx = holder.subscribe(); + + agent_applicable_state_rx.borrow_and_update(); + holder.set_agent_applicable_state(applied_state); + + assert!(!agent_applicable_state_rx.has_changed().unwrap()); +} diff --git a/paddler_agent/tests/arbiter_under_test.rs b/paddler_agent/tests/arbiter_under_test.rs new file mode 100644 index 000000000..4b1229b38 --- /dev/null +++ b/paddler_agent/tests/arbiter_under_test.rs @@ -0,0 +1,47 @@ +use std::sync::Arc; + +use tokio_util::sync::CancellationToken; + +use paddler_agent::agent_applicable_state_holder::AgentApplicableStateHolder; +use paddler_agent::pipeline_arbiter::PipelineArbiter; +use paddler_agent::pipeline_arbiter_params::PipelineArbiterParams; +use paddler_agent_runtime::inference_runtime_context::InferenceRuntimeContext; +use paddler_agent_runtime::inference_runtime_context_params::InferenceRuntimeContextParams; +use paddler_messaging::agent_state_application_status::AgentStateApplicationStatus; + +pub struct ArbiterUnderTest { + pub inference_runtime_context: InferenceRuntimeContext, + pub pipeline_arbiter: PipelineArbiter, + pub served_applicable_state_holder: Arc, +} + +impl ArbiterUnderTest { + pub fn new(agent_shutdown: CancellationToken) -> Self { + let inference_runtime_context = + InferenceRuntimeContext::new(InferenceRuntimeContextParams { + agent_name: Some("agent".to_owned()), + slots: 1, + }); + let served_applicable_state_holder = Arc::new(AgentApplicableStateHolder::default()); + + Self { + pipeline_arbiter: PipelineArbiter::new(PipelineArbiterParams { + agent_shutdown, + inference_runtime_context: inference_runtime_context.clone(), + served_applicable_state_holder: served_applicable_state_holder.clone(), + }), + inference_runtime_context, + served_applicable_state_holder, + } + } + + pub async fn conclude_next_progress(&mut self) -> AgentStateApplicationStatus { + let serving_startup_progress = self.pipeline_arbiter.next_finished_startup().await; + + self.pipeline_arbiter.conclude(serving_startup_progress); + + self.inference_runtime_context + .slot_aggregated_status + .get_state_application_status() + } +} diff --git a/paddler_agent/tests/main.rs b/paddler_agent/tests/main.rs new file mode 100644 index 000000000..032ca201c --- /dev/null +++ b/paddler_agent/tests/main.rs @@ -0,0 +1,33 @@ +mod a_new_subscriber_sees_the_state_applied_before_it_subscribed; +mod a_pipeline_that_fails_to_start_is_retried_until_it_is_stuck; +mod a_reconciliation_replaced_before_its_worker_runs_leaves_no_issue_behind; +mod a_reconciliation_replaced_before_its_worker_runs_never_publishes_its_state; +mod a_replacement_cancelled_by_the_agent_shutting_down_changes_nothing; +mod a_replacement_whose_model_cannot_load_reports_nothing_as_served; +mod a_startup_cancelled_by_the_agent_shutting_down_changes_nothing; +mod a_state_superseded_while_it_is_prepared_yields_to_its_replacement; +mod a_state_superseded_while_its_model_loads_yields_to_its_replacement; +mod a_state_superseding_a_decisions_preparation_that_fails_leaves_no_issue_behind; +mod a_state_superseding_a_model_load_that_fails_leaves_no_issue_behind; +mod a_state_without_a_model_serves_nothing_and_forgets_the_previous_model; +mod an_identical_state_is_not_applied_again; +#[cfg(test)] +mod arbiter_under_test; +#[cfg(test)] +mod reconciliation_after_replacement; +#[cfg(test)] +mod reconciliation_replaced_before_its_worker_runs; +#[cfg(test)] +mod settings_whose_model_cannot_load; +mod shutting_down_waits_for_a_decisions_preparation_that_fails; +#[cfg(test)] +mod slots_insufficient_for_decisions; +#[cfg(test)] +mod startup_superseded_while_its_blocking_work_waits; +#[cfg(test)] +mod state_enabling_decisions; +#[cfg(test)] +mod state_overriding_the_chat_template; +#[cfg(test)] +mod state_whose_model_cannot_load; +mod the_reconciliation_service_stops_once_its_desired_state_channel_closes; diff --git a/paddler_agent/tests/reconciliation_after_replacement.rs b/paddler_agent/tests/reconciliation_after_replacement.rs new file mode 100644 index 000000000..0e067713c --- /dev/null +++ b/paddler_agent/tests/reconciliation_after_replacement.rs @@ -0,0 +1,9 @@ +use std::sync::Arc; + +use paddler_agent::agent_applicable_state_holder::AgentApplicableStateHolder; +use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; + +pub struct ReconciliationAfterReplacement { + pub agent_applicable_state_holder: Arc, + pub slot_aggregated_status: Arc, +} diff --git a/paddler_agent/tests/reconciliation_replaced_before_its_worker_runs.rs b/paddler_agent/tests/reconciliation_replaced_before_its_worker_runs.rs new file mode 100644 index 000000000..9998a535c --- /dev/null +++ b/paddler_agent/tests/reconciliation_replaced_before_its_worker_runs.rs @@ -0,0 +1,112 @@ +use std::sync::Arc; + +use tempfile::TempDir; +use tokio::spawn; +use tokio::sync::watch; +use tokio_util::sync::CancellationToken; +use trzcina::Service as _; + +use paddler_agent::agent_applicable_state_holder::AgentApplicableStateHolder; +use paddler_agent::desired_state_reconciler::DesiredStateReconciler; +use paddler_agent::reconciliation_service::ReconciliationService; +use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; +use paddler_blocking_thread_fixture::occupied_blocking_thread::OccupiedBlockingThread; +use paddler_blocking_thread_fixture::single_blocking_thread_runtime::single_blocking_thread_runtime; +use paddler_cache_dir::cache_dir::CacheDir; +use paddler_download_manager::download_manager::DownloadManager; +use paddler_local_http_fixture::fixture_response::FixtureResponse; +use paddler_local_http_fixture::local_http_fixture::LocalHttpFixture; +use paddler_messaging::agent_desired_model::AgentDesiredModel; +use paddler_messaging::agent_desired_state::AgentDesiredState; +use paddler_messaging::subscribes_to_updates::SubscribesToUpdates as _; +use paddler_model_source::desired_model_resolver::DesiredModelResolver; +use paddler_model_source::huggingface_cache_location::HuggingFaceCacheLocation; +use paddler_model_source::huggingface_default_endpoint::HUGGINGFACE_DEFAULT_ENDPOINT; +use paddler_model_source::huggingface_hub::HuggingFaceHub; + +use crate::reconciliation_after_replacement::ReconciliationAfterReplacement; + +pub struct ReconciliationReplacedBeforeItsWorkerRuns { + pub replaced_desired_state: AgentDesiredState, +} + +impl ReconciliationReplacedBeforeItsWorkerRuns { + pub fn run(self) -> ReconciliationAfterReplacement { + single_blocking_thread_runtime() + .expect("the test runtime must build") + .block_on(self.run_on_one_blocking_thread()) + } + + async fn run_on_one_blocking_thread(self) -> ReconciliationAfterReplacement { + let cache_dir = TempDir::new().expect("the download cache must be creatable"); + let stalled_model_server = LocalHttpFixture::start(FixtureResponse::StallBeforeHeaders) + .await + .expect("the stalled model server must start"); + let occupied_blocking_thread = OccupiedBlockingThread::occupy(); + let agent_applicable_state_holder = Arc::new(AgentApplicableStateHolder::default()); + let slot_aggregated_status = Arc::new(SlotAggregatedStatus::new(1)); + let mut status_updates = slot_aggregated_status.subscribe_to_updates(); + let (agent_desired_state_tx, agent_desired_state_rx) = + watch::channel(AgentDesiredState::default()); + let shutdown = CancellationToken::new(); + let reconciliation = spawn( + Box::new(ReconciliationService { + agent_applicable_state_holder: agent_applicable_state_holder.clone(), + agent_desired_state_rx, + desired_state_reconciler: DesiredStateReconciler { + desired_model_resolver: DesiredModelResolver { + cache_dir: CacheDir { + explicit: Some(cache_dir.path().to_path_buf()), + home: None, + xdg: None, + }, + download_manager: DownloadManager::new() + .expect("the model downloader must start"), + huggingface_client: HuggingFaceHub { + cache_location: HuggingFaceCacheLocation::HubDefault, + endpoint: HUGGINGFACE_DEFAULT_ENDPOINT.to_owned(), + } + .client() + .expect("the Hugging Face client must start"), + }, + slot_aggregated_status: slot_aggregated_status.clone(), + }, + }) + .run(shutdown.clone()), + ); + + status_updates.borrow_and_update(); + agent_desired_state_tx.send_replace(self.replaced_desired_state); + status_updates + .changed() + .await + .expect("the agent must begin the replaced desired state"); + agent_desired_state_tx.send_replace(AgentDesiredState { + model: AgentDesiredModel::Uri(stalled_model_server.url("/model.gguf")), + ..AgentDesiredState::default() + }); + occupied_blocking_thread + .release() + .await + .expect("the blocking thread occupant must be released"); + stalled_model_server + .wait_for_requests(1) + .await + .expect("the replacement must start downloading after the replaced worker ran"); + shutdown.cancel(); + reconciliation + .await + .expect("the reconciliation service must not panic") + .expect("the reconciliation service must stop cleanly"); + stalled_model_server + .stop() + .await + .expect("the stalled model server must not panic") + .expect("the stalled model server must serve every request"); + + ReconciliationAfterReplacement { + agent_applicable_state_holder, + slot_aggregated_status, + } + } +} diff --git a/paddler_agent/tests/settings_whose_model_cannot_load.rs b/paddler_agent/tests/settings_whose_model_cannot_load.rs new file mode 100644 index 000000000..14961a359 --- /dev/null +++ b/paddler_agent/tests/settings_whose_model_cannot_load.rs @@ -0,0 +1,29 @@ +use std::num::NonZeroU32; +use std::path::PathBuf; + +use paddler_agent::applicable_decisions::ApplicableDecisions; +use paddler_agent::inference_capabilities::InferenceCapabilities; +use paddler_agent::model_serving_settings::ModelServingSettings; +use paddler_agent_text_generation::multimodal_projection::MultimodalProjection; +use paddler_agent_text_generation::text_generation_settings::TextGenerationSettings; +use paddler_inference_parameters::model_runtime_parameters::ModelRuntimeParameters; +use paddler_inference_parameters::pooling_type::PoolingType; +use paddler_inference_parameters::sampling_parameters::SamplingParameters; +use paddler_messaging::chat_template_source::ChatTemplateSource; + +pub fn settings_whose_model_cannot_load() -> ModelServingSettings { + ModelServingSettings { + capabilities: InferenceCapabilities { + decisions: ApplicableDecisions::Disabled, + embeddings: PoolingType::Last, + text_generation: TextGenerationSettings { + chat_template_source: ChatTemplateSource::EmbeddedInModel, + image_resize_to_fit: NonZeroU32::MIN, + multimodal_projection: MultimodalProjection::NotConfigured, + sampling_parameters: SamplingParameters::default(), + }, + }, + model_path: PathBuf::from("/nonexistent/model.gguf"), + model_runtime_parameters: ModelRuntimeParameters::default(), + } +} diff --git a/paddler_agent/tests/shutting_down_waits_for_a_decisions_preparation_that_fails.rs b/paddler_agent/tests/shutting_down_waits_for_a_decisions_preparation_that_fails.rs new file mode 100644 index 000000000..0f00d3f1a --- /dev/null +++ b/paddler_agent/tests/shutting_down_waits_for_a_decisions_preparation_that_fails.rs @@ -0,0 +1,55 @@ +use futures_util::FutureExt as _; +use tokio::join; +use tokio_util::sync::CancellationToken; + +use paddler_blocking_thread_fixture::occupied_blocking_thread::OccupiedBlockingThread; +use paddler_blocking_thread_fixture::single_blocking_thread_runtime::single_blocking_thread_runtime; + +use crate::arbiter_under_test::ArbiterUnderTest; +use crate::slots_insufficient_for_decisions::slots_insufficient_for_decisions; +use crate::state_enabling_decisions::state_enabling_decisions; + +#[test] +fn shutting_down_waits_for_a_decisions_preparation_that_fails() { + single_blocking_thread_runtime() + .expect("the test runtime must build") + .block_on(async { + let occupied_blocking_thread = OccupiedBlockingThread::occupy(); + let mut arbiter_under_test = ArbiterUnderTest::new(CancellationToken::new()); + + arbiter_under_test + .pipeline_arbiter + .apply(state_enabling_decisions()); + + assert!( + arbiter_under_test + .pipeline_arbiter + .next_finished_startup() + .now_or_never() + .is_none() + ); + + let ArbiterUnderTest { + inference_runtime_context, + pipeline_arbiter, + .. + } = arbiter_under_test; + + join!(pipeline_arbiter.shut_down(), async { + occupied_blocking_thread + .release() + .await + .expect("the blocking thread occupant must be released"); + }); + + assert!( + inference_runtime_context.slot_aggregated_status.has_issue( + &slots_insufficient_for_decisions( + inference_runtime_context + .slot_aggregated_status + .desired_slots_total + ) + ) + ); + }); +} diff --git a/paddler_agent/tests/slots_insufficient_for_decisions.rs b/paddler_agent/tests/slots_insufficient_for_decisions.rs new file mode 100644 index 000000000..089a9688b --- /dev/null +++ b/paddler_agent/tests/slots_insufficient_for_decisions.rs @@ -0,0 +1,10 @@ +use paddler_messaging::agent_issue::AgentIssue; +use paddler_messaging::agent_issue_params::slots_insufficient_for_decisions_params::SlotsInsufficientForDecisionsParams; +use paddler_messaging::decision_slots_minimum::DECISION_SLOTS_MINIMUM; + +pub fn slots_insufficient_for_decisions(desired_slots: u16) -> AgentIssue { + AgentIssue::SlotsInsufficientForDecisions(SlotsInsufficientForDecisionsParams { + desired_slots, + required_slots: DECISION_SLOTS_MINIMUM.get(), + }) +} diff --git a/paddler_agent/tests/startup_superseded_while_its_blocking_work_waits.rs b/paddler_agent/tests/startup_superseded_while_its_blocking_work_waits.rs new file mode 100644 index 000000000..9167a8d24 --- /dev/null +++ b/paddler_agent/tests/startup_superseded_while_its_blocking_work_waits.rs @@ -0,0 +1,62 @@ +use std::sync::Arc; + +use futures_util::FutureExt as _; +use tokio_util::sync::CancellationToken; + +use paddler_agent::agent_applicable_state::AgentApplicableState; +use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; +use paddler_blocking_thread_fixture::occupied_blocking_thread::OccupiedBlockingThread; +use paddler_blocking_thread_fixture::single_blocking_thread_runtime::single_blocking_thread_runtime; + +use crate::arbiter_under_test::ArbiterUnderTest; + +pub struct StartupSupersededWhileItsBlockingWorkWaits { + pub superseded_state: AgentApplicableState, +} + +impl StartupSupersededWhileItsBlockingWorkWaits { + pub fn run(self) -> Arc { + single_blocking_thread_runtime() + .expect("the test runtime must build") + .block_on(self.run_on_one_blocking_thread()) + } + + async fn run_on_one_blocking_thread(self) -> Arc { + let occupied_blocking_thread = OccupiedBlockingThread::occupy(); + let mut arbiter_under_test = ArbiterUnderTest::new(CancellationToken::new()); + + arbiter_under_test + .pipeline_arbiter + .apply(self.superseded_state); + + while let Some(serving_startup_progress) = arbiter_under_test + .pipeline_arbiter + .next_finished_startup() + .now_or_never() + { + arbiter_under_test + .pipeline_arbiter + .conclude(serving_startup_progress); + } + + arbiter_under_test + .pipeline_arbiter + .apply(AgentApplicableState::NotConfigured); + occupied_blocking_thread + .release() + .await + .expect("the blocking thread occupant must be released"); + arbiter_under_test.conclude_next_progress().await; + arbiter_under_test.conclude_next_progress().await; + arbiter_under_test.conclude_next_progress().await; + + let slot_aggregated_status = arbiter_under_test + .inference_runtime_context + .slot_aggregated_status + .clone(); + + arbiter_under_test.pipeline_arbiter.shut_down().await; + + slot_aggregated_status + } +} diff --git a/paddler_agent/tests/state_enabling_decisions.rs b/paddler_agent/tests/state_enabling_decisions.rs new file mode 100644 index 000000000..7294961de --- /dev/null +++ b/paddler_agent/tests/state_enabling_decisions.rs @@ -0,0 +1,22 @@ +use std::path::PathBuf; + +use paddler_agent::agent_applicable_state::AgentApplicableState; +use paddler_agent::applicable_decisions::ApplicableDecisions; +use paddler_agent::inference_capabilities::InferenceCapabilities; +use paddler_agent::model_serving_settings::ModelServingSettings; + +use crate::settings_whose_model_cannot_load::settings_whose_model_cannot_load; + +pub fn state_enabling_decisions() -> AgentApplicableState { + let settings_whose_model_cannot_load = settings_whose_model_cannot_load(); + + AgentApplicableState::WithModel(ModelServingSettings { + capabilities: InferenceCapabilities { + decisions: ApplicableDecisions::Enabled { + pointer_head_path: PathBuf::from("/nonexistent/pointer_head.gguf"), + }, + ..settings_whose_model_cannot_load.capabilities + }, + ..settings_whose_model_cannot_load + }) +} diff --git a/paddler_agent/tests/state_overriding_the_chat_template.rs b/paddler_agent/tests/state_overriding_the_chat_template.rs new file mode 100644 index 000000000..a7f7aae85 --- /dev/null +++ b/paddler_agent/tests/state_overriding_the_chat_template.rs @@ -0,0 +1,11 @@ +use paddler_agent::agent_applicable_state::AgentApplicableState; +use paddler_messaging::chat_template::ChatTemplate; +use paddler_messaging::chat_template_source::ChatTemplateSource; + +pub fn state_overriding_the_chat_template() -> AgentApplicableState { + AgentApplicableState::WithoutModel { + chat_template_source: ChatTemplateSource::Override(ChatTemplate { + content: "{{ messages }}".to_owned(), + }), + } +} diff --git a/paddler_agent/tests/state_whose_model_cannot_load.rs b/paddler_agent/tests/state_whose_model_cannot_load.rs new file mode 100644 index 000000000..38cfc97ec --- /dev/null +++ b/paddler_agent/tests/state_whose_model_cannot_load.rs @@ -0,0 +1,7 @@ +use paddler_agent::agent_applicable_state::AgentApplicableState; + +use crate::settings_whose_model_cannot_load::settings_whose_model_cannot_load; + +pub fn state_whose_model_cannot_load() -> AgentApplicableState { + AgentApplicableState::WithModel(settings_whose_model_cannot_load()) +} diff --git a/paddler_agent/tests/the_reconciliation_service_stops_once_its_desired_state_channel_closes.rs b/paddler_agent/tests/the_reconciliation_service_stops_once_its_desired_state_channel_closes.rs new file mode 100644 index 000000000..ee027532f --- /dev/null +++ b/paddler_agent/tests/the_reconciliation_service_stops_once_its_desired_state_channel_closes.rs @@ -0,0 +1,50 @@ +use std::sync::Arc; + +use tokio::sync::watch; +use tokio_util::sync::CancellationToken; +use trzcina::Service as _; + +use paddler_agent::agent_applicable_state_holder::AgentApplicableStateHolder; +use paddler_agent::desired_state_reconciler::DesiredStateReconciler; +use paddler_agent::reconciliation_service::ReconciliationService; +use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; +use paddler_cache_dir::cache_dir::CacheDir; +use paddler_download_manager::download_manager::DownloadManager; +use paddler_messaging::agent_desired_state::AgentDesiredState; +use paddler_model_source::desired_model_resolver::DesiredModelResolver; +use paddler_model_source::huggingface_cache_location::HuggingFaceCacheLocation; +use paddler_model_source::huggingface_default_endpoint::HUGGINGFACE_DEFAULT_ENDPOINT; +use paddler_model_source::huggingface_hub::HuggingFaceHub; + +#[tokio::test] +async fn the_reconciliation_service_stops_once_its_desired_state_channel_closes() { + let (agent_desired_state_tx, agent_desired_state_rx) = + watch::channel(AgentDesiredState::default()); + + drop(agent_desired_state_tx); + + Box::new(ReconciliationService { + agent_applicable_state_holder: Arc::new(AgentApplicableStateHolder::default()), + agent_desired_state_rx, + desired_state_reconciler: DesiredStateReconciler { + desired_model_resolver: DesiredModelResolver { + cache_dir: CacheDir { + explicit: None, + home: None, + xdg: None, + }, + download_manager: DownloadManager::new().expect("the model downloader must start"), + huggingface_client: HuggingFaceHub { + cache_location: HuggingFaceCacheLocation::HubDefault, + endpoint: HUGGINGFACE_DEFAULT_ENDPOINT.to_owned(), + } + .client() + .expect("the Hugging Face client must start"), + }, + slot_aggregated_status: Arc::new(SlotAggregatedStatus::new(1)), + }, + }) + .run(CancellationToken::new()) + .await + .expect("the reconciliation service must stop cleanly once nothing can send it desired states"); +} diff --git a/paddler_agent_decision/Cargo.toml b/paddler_agent_decision/Cargo.toml new file mode 100644 index 000000000..93e305b8c --- /dev/null +++ b/paddler_agent_decision/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "paddler_agent_decision" +authors.workspace = true +description.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[dependencies] +llama-cpp-bindings = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +tokio-util = { workspace = true } +paddler_agent_pointer_head = { workspace = true } +paddler_agent_runtime = { workspace = true } +paddler_agent_status = { workspace = true } +paddler_inference_parameters = { workspace = true } +paddler_messaging = { workspace = true } + +[features] +default = [] +cuda = ["llama-cpp-bindings/cuda", "paddler_agent_pointer_head/cuda", "paddler_agent_runtime/cuda"] +metal = ["llama-cpp-bindings/metal", "paddler_agent_pointer_head/metal", "paddler_agent_runtime/metal"] +vulkan = ["llama-cpp-bindings/vulkan", "paddler_agent_pointer_head/vulkan", "paddler_agent_runtime/vulkan"] + +[lints] +workspace = true diff --git a/paddler_agent_decision/src/active_decision_request.rs b/paddler_agent_decision/src/active_decision_request.rs new file mode 100644 index 000000000..ace8525ec --- /dev/null +++ b/paddler_agent_decision/src/active_decision_request.rs @@ -0,0 +1,249 @@ +use std::collections::VecDeque; +use std::mem; +use std::time::Instant; + +use llama_cpp_bindings::token::LlamaToken; +use tokio::sync::mpsc; + +use paddler_agent_pointer_head::pointer_head::PointerHead; +use paddler_agent_runtime::receives_stop_request::ReceivesStopRequest as _; +use paddler_agent_runtime::sequence_id::SequenceId; +use paddler_agent_runtime::sequence_id_guard::SequenceIdGuard; +use paddler_agent_status::slot_guard::SlotGuard; +use paddler_messaging::decision_answer::DecisionAnswer; +use paddler_messaging::decision_result::DecisionResult; +use paddler_messaging::decision_summary::DecisionSummary; + +use crate::decision_admission::DecisionAdmission; +use crate::decision_advance::DecisionAdvance; +use crate::decision_decoder::DecisionDecoder; +use crate::decision_error::DecisionError; +use crate::decision_question_row::DecisionQuestionRow; +use crate::decision_request_phase::DecisionRequestPhase; +use crate::decision_token_layout::DecisionTokenLayout; +use crate::prepared_decision_request::PreparedDecisionRequest; +use crate::question_lane::QuestionLane; +use crate::reports_decision_outcome::ReportsDecisionOutcome; + +pub struct ActiveDecisionRequest { + pub current_question: DecisionQuestionRow, + pub decision_result_tx: mpsc::UnboundedSender, + pub decision_stop_rx: mpsc::UnboundedReceiver<()>, + pub hidden_states: Vec>, + pub input_tokens: usize, + pub phase: DecisionRequestPhase, + pub question_lane: QuestionLane, + pub remaining_questions: VecDeque, + pub slot_guard: SlotGuard, + pub started_at: Instant, + pub state: Vec, + pub state_sequence: SequenceIdGuard, +} + +impl ActiveDecisionRequest { + #[must_use] + pub fn new( + PreparedDecisionRequest { + decision_result_tx, + decision_stop_rx, + layout, + slot_guard, + started_at, + }: PreparedDecisionRequest, + DecisionAdmission { + question_lane, + state_sequence, + }: DecisionAdmission, + ) -> Self { + let input_tokens = layout.total_tokens(); + let DecisionTokenLayout { + first_question, + remaining_questions, + state, + } = layout; + + Self { + current_question: first_question, + decision_result_tx, + decision_stop_rx, + hidden_states: Vec::new(), + input_tokens, + phase: DecisionRequestPhase::IngestingState { next_offset: 0 }, + question_lane, + remaining_questions, + slot_guard, + started_at, + state, + state_sequence, + } + } + + pub fn advance( + &mut self, + decoder: &mut DecisionDecoder, + pointer_head: &PointerHead, + agent_name: Option<&str>, + ) -> Result { + match self.phase { + DecisionRequestPhase::IngestingState { next_offset } => { + let next_offset = decoder.ingest_state_chunk( + self.state_sequence.sequence_id(), + &self.state, + next_offset, + )?; + + self.phase = if next_offset < self.state.len() { + DecisionRequestPhase::IngestingState { next_offset } + } else { + DecisionRequestPhase::AnsweringQuestion { next_offset: 0 } + }; + + Ok(DecisionAdvance::Continuing) + } + DecisionRequestPhase::AnsweringQuestion { next_offset } => match &self.question_lane { + QuestionLane::Reserved(lane) => { + let lane_sequence_id = lane.sequence_id(); + + self.answer_leading_question( + decoder, + pointer_head, + agent_name, + lane_sequence_id, + next_offset, + ) + } + QuestionLane::Released => { + self.answer_last_question(decoder, pointer_head, agent_name, next_offset) + } + }, + } + } + + pub fn finish_with( + mut self, + decoder: &mut DecisionDecoder, + agent_name: Option<&str>, + outcome: TOutcome, + ) { + self.state_sequence + .sequence_id() + .release_kv_cache_in(decoder.llama_context, agent_name); + self.release_question_lane(decoder, agent_name); + + outcome.report(agent_name, &self.decision_result_tx); + } + + pub fn is_stop_requested(&mut self) -> bool { + self.decision_stop_rx.is_stop_requested() + } + + #[must_use] + pub fn summary(&self) -> DecisionResult { + DecisionResult::Done(DecisionSummary { + input_tokens: self.input_tokens, + processing_milliseconds: self.started_at.elapsed().as_millis() as u64, + }) + } + + fn answer_last_question( + &mut self, + decoder: &mut DecisionDecoder, + pointer_head: &PointerHead, + agent_name: Option<&str>, + next_offset: usize, + ) -> Result { + let next_offset = + self.decode_question_chunk(decoder, self.state_sequence.sequence_id(), next_offset)?; + + if next_offset < self.current_question.tokens.len() { + self.phase = DecisionRequestPhase::AnsweringQuestion { next_offset }; + + return Ok(DecisionAdvance::Continuing); + } + + self.send_answer(pointer_head)?; + + Ok(self.finish_question(decoder, agent_name)) + } + + fn answer_leading_question( + &mut self, + decoder: &mut DecisionDecoder, + pointer_head: &PointerHead, + agent_name: Option<&str>, + lane_sequence_id: SequenceId, + next_offset: usize, + ) -> Result { + if next_offset == 0 { + decoder.copy_sequence(self.state_sequence.sequence_id(), lane_sequence_id)?; + } + + let next_offset = self.decode_question_chunk(decoder, lane_sequence_id, next_offset)?; + + if next_offset < self.current_question.tokens.len() { + self.phase = DecisionRequestPhase::AnsweringQuestion { next_offset }; + + return Ok(DecisionAdvance::Continuing); + } + + self.send_answer(pointer_head)?; + + Ok(self.finish_question(decoder, agent_name)) + } + + fn decode_question_chunk( + &mut self, + decoder: &mut DecisionDecoder, + sequence_id: SequenceId, + next_offset: usize, + ) -> Result { + decoder.decode_row_chunk( + sequence_id, + &self.current_question, + next_offset, + self.state.len(), + &mut self.hidden_states, + ) + } + + fn finish_question( + &mut self, + decoder: &mut DecisionDecoder, + agent_name: Option<&str>, + ) -> DecisionAdvance { + let Some(next_question) = self.remaining_questions.pop_front() else { + return DecisionAdvance::Completed; + }; + + self.current_question = next_question; + + if self.remaining_questions.is_empty() { + self.release_question_lane(decoder, agent_name); + } + + self.phase = DecisionRequestPhase::AnsweringQuestion { next_offset: 0 }; + + DecisionAdvance::Continuing + } + + fn release_question_lane(&mut self, decoder: &mut DecisionDecoder, agent_name: Option<&str>) { + if let QuestionLane::Reserved(lane) = + mem::replace(&mut self.question_lane, QuestionLane::Released) + { + lane.sequence_id() + .release_kv_cache_in(decoder.llama_context, agent_name); + } + } + + fn send_answer(&mut self, pointer_head: &PointerHead) -> Result<(), DecisionError> { + let probabilities = pointer_head.option_probabilities(&self.hidden_states); + + self.hidden_states.clear(); + self.decision_result_tx + .send(DecisionResult::QuestionAnswered(DecisionAnswer { + id: self.current_question.id.clone(), + probabilities, + })) + .map_err(DecisionError::ClientDisconnected) + } +} diff --git a/paddler_agent_decision/src/decision_admission.rs b/paddler_agent_decision/src/decision_admission.rs new file mode 100644 index 000000000..7385e94ba --- /dev/null +++ b/paddler_agent_decision/src/decision_admission.rs @@ -0,0 +1,8 @@ +use paddler_agent_runtime::sequence_id_guard::SequenceIdGuard; + +use crate::question_lane::QuestionLane; + +pub struct DecisionAdmission { + pub question_lane: QuestionLane, + pub state_sequence: SequenceIdGuard, +} diff --git a/paddler_agent_decision/src/decision_advance.rs b/paddler_agent_decision/src/decision_advance.rs new file mode 100644 index 000000000..a64b25873 --- /dev/null +++ b/paddler_agent_decision/src/decision_advance.rs @@ -0,0 +1,4 @@ +pub enum DecisionAdvance { + Completed, + Continuing, +} diff --git a/paddler_agent_decision/src/decision_capacity_ledger.rs b/paddler_agent_decision/src/decision_capacity_ledger.rs new file mode 100644 index 000000000..93ee0d5e8 --- /dev/null +++ b/paddler_agent_decision/src/decision_capacity_ledger.rs @@ -0,0 +1,70 @@ +use paddler_agent_runtime::sequence_id_guard::SequenceIdGuard; +use paddler_agent_runtime::sequence_id_pool::SequenceIdPool; + +use crate::decision_admission::DecisionAdmission; +use crate::question_lane::QuestionLane; +use crate::question_lane_demand::QuestionLaneDemand; + +pub struct DecisionCapacityLedger { + sequence_id_pool: SequenceIdPool, +} + +impl DecisionCapacityLedger { + #[must_use] + pub fn new(sequences: u16) -> Self { + Self { + sequence_id_pool: SequenceIdPool::new(sequences), + } + } + + #[must_use] + pub fn admit(&self, question_lane_demand: QuestionLaneDemand) -> Option { + let state_sequence = SequenceIdGuard::acquire(&self.sequence_id_pool)?; + let question_lane = match question_lane_demand { + QuestionLaneDemand::Needed => { + QuestionLane::Reserved(SequenceIdGuard::acquire(&self.sequence_id_pool)?) + } + QuestionLaneDemand::NotNeeded => QuestionLane::Released, + }; + + Some(DecisionAdmission { + question_lane, + state_sequence, + }) + } +} + +#[cfg(test)] +mod tests { + use super::DecisionCapacityLedger; + use crate::question_lane_demand::QuestionLaneDemand; + + #[test] + fn a_single_question_decision_takes_only_its_state_sequence() { + let ledger = DecisionCapacityLedger::new(2); + let _first_admission = ledger + .admit(QuestionLaneDemand::NotNeeded) + .expect("one free sequence fits a single question"); + + assert!(ledger.admit(QuestionLaneDemand::NotNeeded).is_some()); + } + + #[test] + fn a_multi_question_decision_waits_for_a_free_question_lane() { + let ledger = DecisionCapacityLedger::new(1); + + assert!(ledger.admit(QuestionLaneDemand::Needed).is_none()); + } + + #[test] + fn a_decision_runs_once_the_sequences_of_a_finished_one_are_returned() { + let ledger = DecisionCapacityLedger::new(2); + let first_admission = ledger.admit(QuestionLaneDemand::Needed); + + assert!(ledger.admit(QuestionLaneDemand::NotNeeded).is_none()); + + drop(first_admission); + + assert!(ledger.admit(QuestionLaneDemand::NotNeeded).is_some()); + } +} diff --git a/paddler_agent_decision/src/decision_decoder.rs b/paddler_agent_decision/src/decision_decoder.rs new file mode 100644 index 000000000..2fc80368e --- /dev/null +++ b/paddler_agent_decision/src/decision_decoder.rs @@ -0,0 +1,134 @@ +use llama_cpp_bindings::SampledToken; +use llama_cpp_bindings::context::LlamaContext; +use llama_cpp_bindings::llama_batch::LlamaBatch; +use llama_cpp_bindings::token::LlamaToken; + +use paddler_agent_runtime::sequence_id::SequenceId; + +use crate::decision_error::DecisionError; +use crate::decision_question_row::DecisionQuestionRow; + +pub struct DecisionDecoder<'scheduler, 'model> { + pub batch: &'scheduler mut LlamaBatch<'static>, + pub llama_context: &'scheduler mut LlamaContext<'model>, + pub n_batch: usize, +} + +impl DecisionDecoder<'_, '_> { + pub fn copy_sequence( + &mut self, + source_sequence_id: SequenceId, + destination_sequence_id: SequenceId, + ) -> Result<(), DecisionError> { + self.remove_sequence(destination_sequence_id)?; + self.llama_context + .copy_kv_cache_seq( + source_sequence_id.llama_sequence_id(), + destination_sequence_id.llama_sequence_id(), + None, + None, + ) + .map_err(DecisionError::KvCacheCopyFailed) + } + + pub fn ingest_state_chunk( + &mut self, + sequence_id: SequenceId, + state: &[LlamaToken], + next_offset: usize, + ) -> Result { + self.batch.clear(); + + for (position, token) in (0_i32..).zip(state).skip(next_offset).take(self.n_batch) { + self.batch + .add( + &SampledToken::Content(*token), + position, + &[sequence_id.llama_sequence_id()], + false, + ) + .map_err(DecisionError::BatchAssemblyFailed)?; + } + + self.llama_context + .decode(self.batch) + .map_err(DecisionError::DecodeFailed)?; + + Ok(next_offset + self.batch.n_tokens() as usize) + } + + pub fn decode_row_chunk( + &mut self, + sequence_id: SequenceId, + row: &DecisionQuestionRow, + next_offset: usize, + state_length: usize, + hidden_states: &mut Vec>, + ) -> Result { + self.batch.clear(); + + let mut output_batch_indices = Vec::new(); + + let row_chunk = row + .tokens + .iter() + .enumerate() + .skip(next_offset) + .take(self.n_batch); + let row_chunk_positions = (0_i32..).skip(state_length + next_offset); + + for ((batch_index, position), (row_offset, token)) in + (0_i32..).zip(row_chunk_positions).zip(row_chunk) + { + let is_output = row.is_output(row_offset); + + self.batch + .add( + &SampledToken::Content(*token), + position, + &[sequence_id.llama_sequence_id()], + is_output, + ) + .map_err(DecisionError::BatchAssemblyFailed)?; + + if is_output { + output_batch_indices.push(batch_index); + } + } + + self.llama_context.set_embeddings(true); + + let row_hidden_states = self.decode_row_hidden_states(&output_batch_indices); + + self.llama_context.set_embeddings(false); + + hidden_states.extend(row_hidden_states?); + + Ok(next_offset + self.batch.n_tokens() as usize) + } + + fn decode_row_hidden_states( + &mut self, + output_batch_indices: &[i32], + ) -> Result>, DecisionError> { + self.llama_context + .decode(self.batch) + .map_err(DecisionError::DecodeFailed)?; + + output_batch_indices + .iter() + .map(|batch_index| { + self.llama_context + .embeddings_ith(*batch_index) + .map(<[f32]>::to_vec) + .map_err(DecisionError::HiddenStateUnavailable) + }) + .collect() + } + + pub fn remove_sequence(&mut self, sequence_id: SequenceId) -> Result<(), DecisionError> { + sequence_id + .clear_kv_cache_in(self.llama_context) + .map_err(DecisionError::KvCacheRemovalFailed) + } +} diff --git a/paddler_agent_decision/src/decision_delimiter_tokens.rs b/paddler_agent_decision/src/decision_delimiter_tokens.rs new file mode 100644 index 000000000..c623c0409 --- /dev/null +++ b/paddler_agent_decision/src/decision_delimiter_tokens.rs @@ -0,0 +1,142 @@ +use llama_cpp_bindings::llama_token_attr::LlamaTokenAttr; +use llama_cpp_bindings::token::LlamaToken; + +use paddler_agent_pointer_head::pointer_head_delimiters::PointerHeadDelimiters; +use paddler_messaging::agent_issue_params::pointer_head_incompatibility::PointerHeadIncompatibility; + +use crate::decision_error::DecisionError; +use crate::delimiter_vocabulary::DelimiterVocabulary; + +fn resolve_control_token( + vocabulary: &TVocabulary, + delimiter: &str, +) -> Result { + let not_a_control_token = || DecisionError::PointerHeadIncompatibleWithModel { + incompatibility: PointerHeadIncompatibility::DelimiterIsNotAControlToken { + delimiter: delimiter.to_owned(), + }, + }; + let tokens = vocabulary.tokenize_delimiter(delimiter).map_err(|source| { + DecisionError::DelimiterTokenizationFailed { + delimiter: delimiter.to_owned(), + source, + } + })?; + let control_token = match tokens.as_slice() { + [token] => vocabulary + .token_attributes(*token) + .map_err(|source| DecisionError::DelimiterAttributesUnreadable { + delimiter: delimiter.to_owned(), + source, + })? + .contains(LlamaTokenAttr::Control) + .then_some(*token), + _ => None, + }; + + control_token.ok_or_else(not_a_control_token) +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct DecisionDelimiterTokens { + pub decide: LlamaToken, + pub option_end: LlamaToken, + pub option_start: LlamaToken, + pub question: LlamaToken, + pub state: LlamaToken, +} + +impl DecisionDelimiterTokens { + pub fn resolve( + vocabulary: &TVocabulary, + PointerHeadDelimiters { + decide, + option_end, + option_start, + question, + state, + }: &PointerHeadDelimiters, + ) -> Result { + let mut control_tokens = [LlamaToken::new(0); 5]; + + for (control_token, delimiter) in + control_tokens + .iter_mut() + .zip([decide, option_end, option_start, question, state]) + { + *control_token = resolve_control_token(vocabulary, delimiter)?; + } + + let [decide, option_end, option_start, question, state] = control_tokens; + + Ok(Self { + decide, + option_end, + option_start, + question, + state, + }) + } +} + +#[cfg(test)] +mod tests { + use llama_cpp_bindings::error::StringToTokenError; + use llama_cpp_bindings::error::TokenAttrError; + use llama_cpp_bindings::llama_token_attrs::LlamaTokenAttrs; + use llama_cpp_bindings::llama_token_attrs_from_int_error::LlamaTokenAttrsFromIntError; + use llama_cpp_bindings::token::LlamaToken; + + use paddler_agent_pointer_head::pointer_head_delimiters::PointerHeadDelimiters; + + use super::DecisionDelimiterTokens; + use crate::decision_error::DecisionError; + use crate::delimiter_vocabulary::DelimiterVocabulary; + + const UNKNOWN_ATTRIBUTE_BITS: u32 = 1 << 30; + + struct VocabularyWithUnreadableAttributes; + + impl DelimiterVocabulary for VocabularyWithUnreadableAttributes { + fn tokenize_delimiter( + &self, + _delimiter: &str, + ) -> Result, StringToTokenError> { + Ok(vec![LlamaToken::new(0)]) + } + + fn token_attributes(&self, _token: LlamaToken) -> Result { + Err(TokenAttrError::UnknownAttributes( + LlamaTokenAttrsFromIntError::UnknownValue(UNKNOWN_ATTRIBUTE_BITS), + )) + } + } + + #[test] + fn a_delimiter_whose_token_attributes_are_unreadable_is_refused() { + assert_eq!( + DecisionDelimiterTokens::resolve( + &VocabularyWithUnreadableAttributes, + &PointerHeadDelimiters { + decide: "".to_owned(), + option_end: "".to_owned(), + option_start: "".to_owned(), + question: "".to_owned(), + state: "".to_owned(), + }, + ) + .err() + .as_ref() + .map(ToString::to_string), + Some( + DecisionError::DelimiterAttributesUnreadable { + delimiter: "".to_owned(), + source: TokenAttrError::UnknownAttributes( + LlamaTokenAttrsFromIntError::UnknownValue(UNKNOWN_ATTRIBUTE_BITS), + ), + } + .to_string() + ) + ); + } +} diff --git a/paddler_agent_decision/src/decision_error.rs b/paddler_agent_decision/src/decision_error.rs new file mode 100644 index 000000000..0f3ed1a7e --- /dev/null +++ b/paddler_agent_decision/src/decision_error.rs @@ -0,0 +1,234 @@ +use llama_cpp_bindings::batch_add_error::BatchAddError; +use llama_cpp_bindings::error::ClearKvCacheSeqError; +use llama_cpp_bindings::error::CopyKvCacheSeqError; +use llama_cpp_bindings::error::DecodeError; +use llama_cpp_bindings::error::EmbeddingsError; +use llama_cpp_bindings::error::StringToTokenError; +use llama_cpp_bindings::error::TokenAttrError; +use log::error; +use tokio::sync::mpsc; +use tokio::sync::mpsc::error::SendError; + +use paddler_agent_pointer_head::pointer_head_error::PointerHeadError; +use paddler_agent_runtime::agent_runtime_error::AgentRuntimeError; +use paddler_agent_runtime::rejection_description::rejection_description; +use paddler_agent_runtime::send_result_or_warn::send_result_or_warn; +use paddler_messaging::agent_issue_params::pointer_head_incompatibility::PointerHeadIncompatibility; +use paddler_messaging::decision_result::DecisionResult; +use paddler_messaging::oversized_decision_details::OversizedDecisionDetails; + +use crate::reports_decision_outcome::ReportsDecisionOutcome; + +#[derive(Debug, thiserror::Error)] +pub enum DecisionError { + #[error(transparent)] + AgentRuntime(#[from] AgentRuntimeError), + + #[error("failed to add a decision token to the batch: {0}")] + BatchAssemblyFailed(#[source] BatchAddError), + + #[error("the client stopped listening for decisions: {0}")] + ClientDisconnected(#[source] SendError), + + #[error("no pointer head is configured, so the agent serves no decisions")] + DecisionsNotServed, + + #[error("llama.cpp rejected the decision batch: {0}")] + DecodeFailed(#[source] DecodeError), + + #[error("unable to read the attributes of the pointer head delimiter {delimiter}: {source}")] + DelimiterAttributesUnreadable { + delimiter: String, + #[source] + source: TokenAttrError, + }, + + #[error("unable to tokenize the pointer head delimiter {delimiter}: {source}")] + DelimiterTokenizationFailed { + delimiter: String, + #[source] + source: StringToTokenError, + }, + + #[error("unable to read the hidden state of a decision output: {0}")] + HiddenStateUnavailable(#[source] EmbeddingsError), + + #[error("failed to tokenize the decision input: {0}")] + InputTokenizationFailed(#[source] StringToTokenError), + + #[error("failed to copy the decision state into its question lane: {0}")] + KvCacheCopyFailed(#[source] CopyKvCacheSeqError), + + #[error("failed to remove a decision sequence from the KV cache: {0}")] + KvCacheRemovalFailed(#[source] ClearKvCacheSeqError), + + #[error("no model is loaded")] + ModelNotLoaded, + + #[error("unable to load the pointer head: {0}")] + PointerHeadCannotBeLoaded(#[source] PointerHeadError), + + #[error("the pointer head does not fit the model: {incompatibility:?}")] + PointerHeadIncompatibleWithModel { + incompatibility: PointerHeadIncompatibility, + }, + + #[error( + "the decision needs {} tokens in the cache, more than the {} the context holds", + details.required_tokens, + details.context_size + )] + RequestExceedsContext { details: OversizedDecisionDetails }, + + #[error("the scheduler is no longer accepting requests")] + SchedulerUnavailable, + + #[error("decisions need {required_slots} slots, the agent runs {desired_slots}")] + SlotsInsufficient { + desired_slots: u16, + required_slots: u16, + }, +} + +impl ReportsDecisionOutcome for DecisionError { + fn report( + self, + agent_name: Option<&str>, + decision_result_tx: &mpsc::UnboundedSender, + ) { + let message = rejection_description(agent_name, &self); + + error!("{message}"); + + let result = match self { + Self::AgentRuntime(_) + | Self::DelimiterAttributesUnreadable { .. } + | Self::DelimiterTokenizationFailed { .. } + | Self::ModelNotLoaded + | Self::PointerHeadCannotBeLoaded(_) + | Self::PointerHeadIncompatibleWithModel { .. } + | Self::SlotsInsufficient { .. } => DecisionResult::ModelNotLoaded(message), + Self::BatchAssemblyFailed(_) => DecisionResult::BatchAssemblyFailed(message), + Self::ClientDisconnected(_) => return, + Self::DecisionsNotServed => DecisionResult::DecisionsNotServed(message), + Self::DecodeFailed(_) => DecisionResult::DecodeFailed(message), + Self::HiddenStateUnavailable(_) => DecisionResult::HiddenStateUnavailable(message), + Self::InputTokenizationFailed(_) => DecisionResult::InputTokenizationFailed(message), + Self::KvCacheCopyFailed(_) => DecisionResult::KvCacheCopyFailed(message), + Self::KvCacheRemovalFailed(_) => DecisionResult::KvCacheRemovalFailed(message), + Self::RequestExceedsContext { details } => { + DecisionResult::RequestExceedsContext(details) + } + Self::SchedulerUnavailable => DecisionResult::SchedulerUnavailable(message), + }; + + send_result_or_warn(agent_name, decision_result_tx, result); + } +} + +#[cfg(test)] +mod tests { + use std::ffi::CString; + use std::mem::discriminant; + + use llama_cpp_bindings::batch_add_error::BatchAddError; + use llama_cpp_bindings::error::ClearKvCacheSeqError; + use llama_cpp_bindings::error::CopyKvCacheSeqError; + use llama_cpp_bindings::error::DecodeError; + use llama_cpp_bindings::error::EmbeddingsError; + use llama_cpp_bindings::error::StringToTokenError; + use tokio::sync::mpsc; + use tokio::sync::mpsc::error::SendError; + + use paddler_messaging::decision_result::DecisionResult; + use paddler_messaging::oversized_decision_details::OversizedDecisionDetails; + + use super::DecisionError; + use crate::reports_decision_outcome::ReportsDecisionOutcome as _; + + fn reported(decision_error: DecisionError) -> Option { + let (decision_result_tx, mut decision_result_rx) = mpsc::unbounded_channel(); + + decision_error.report(None, &decision_result_tx); + + decision_result_rx.try_recv().ok() + } + + #[test] + fn every_request_failure_reports_its_own_decision_result() { + let description = String::new; + let oversized = OversizedDecisionDetails { + context_size: 256, + required_tokens: 900, + }; + let failures = [ + ( + DecisionError::BatchAssemblyFailed(BatchAddError::InsufficientSpace(1)), + DecisionResult::BatchAssemblyFailed(description()), + ), + ( + DecisionError::DecisionsNotServed, + DecisionResult::DecisionsNotServed(description()), + ), + ( + DecisionError::DecodeFailed(DecodeError::NoKvCacheSlot), + DecisionResult::DecodeFailed(description()), + ), + ( + DecisionError::HiddenStateUnavailable(EmbeddingsError::LogitsNotEnabled), + DecisionResult::HiddenStateUnavailable(description()), + ), + ( + DecisionError::InputTokenizationFailed(StringToTokenError::NulError( + CString::new("a \0 byte").unwrap_err(), + )), + DecisionResult::InputTokenizationFailed(description()), + ), + ( + DecisionError::KvCacheCopyFailed(CopyKvCacheSeqError::MemoryHandleUnavailable), + DecisionResult::KvCacheCopyFailed(description()), + ), + ( + DecisionError::KvCacheRemovalFailed( + ClearKvCacheSeqError::PartialSequenceNotRemoved { + seq_id: 0, + p0: -1, + p1: -1, + }, + ), + DecisionResult::KvCacheRemovalFailed(description()), + ), + ( + DecisionError::ModelNotLoaded, + DecisionResult::ModelNotLoaded(description()), + ), + ( + DecisionError::RequestExceedsContext { + details: oversized.clone(), + }, + DecisionResult::RequestExceedsContext(oversized), + ), + ( + DecisionError::SchedulerUnavailable, + DecisionResult::SchedulerUnavailable(description()), + ), + ]; + + for (decision_error, expected_result) in failures { + assert_eq!( + reported(decision_error).as_ref().map(discriminant), + Some(discriminant(&expected_result)) + ); + } + } + + #[test] + fn a_disconnected_client_is_not_sent_a_result() { + assert_eq!( + reported(DecisionError::ClientDisconnected(SendError( + DecisionResult::SchedulerUnavailable(String::new()) + ))), + None + ); + } +} diff --git a/paddler_agent_decision/src/decision_llama_context_settings.rs b/paddler_agent_decision/src/decision_llama_context_settings.rs new file mode 100644 index 000000000..4be105de7 --- /dev/null +++ b/paddler_agent_decision/src/decision_llama_context_settings.rs @@ -0,0 +1,30 @@ +use llama_cpp_bindings::context::params::LlamaPoolingType; + +use paddler_agent_runtime::inference_runtime_context::InferenceRuntimeContext; +use paddler_agent_runtime::llama_context_settings::LlamaContextSettings; +use paddler_agent_runtime::loaded_llama_model::LoadedLlamaModel; +use paddler_agent_runtime::resident_llama_context::ResidentLlamaContext; +use paddler_messaging::inference_capability::InferenceCapability; + +use crate::decision_error::DecisionError; + +pub struct DecisionLlamaContextSettings<'parameters> { + pub llama_context_settings: LlamaContextSettings<'parameters>, +} + +impl DecisionLlamaContextSettings<'_> { + pub fn create_llama_context<'model>( + self, + loaded_llama_model: &'model LoadedLlamaModel, + inference_runtime_context: &InferenceRuntimeContext, + ) -> Result, DecisionError> { + Ok(loaded_llama_model.create_llama_context( + inference_runtime_context, + InferenceCapability::Decisions, + self.llama_context_settings + .into_llama_context_params() + .with_kv_unified(true) + .with_pooling_type(LlamaPoolingType::None), + )?) + } +} diff --git a/paddler_agent_decision/src/decision_pipeline.rs b/paddler_agent_decision/src/decision_pipeline.rs new file mode 100644 index 000000000..f644721c6 --- /dev/null +++ b/paddler_agent_decision/src/decision_pipeline.rs @@ -0,0 +1,191 @@ +use std::collections::VecDeque; +use std::sync::Arc; + +use llama_cpp_bindings::llama_batch::LlamaBatch; +use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::mpsc::unbounded_channel; +use tokio_util::sync::CancellationToken; +use tokio_util::task::TaskTracker; + +use paddler_agent_runtime::continue_startup_unless_shutting_down::continue_startup_unless_shutting_down; +use paddler_agent_runtime::inference_runtime_context::InferenceRuntimeContext; +use paddler_agent_runtime::loaded_llama_model::LoadedLlamaModel; +use paddler_agent_runtime::run_scheduler_unless_startup_abandoned::run_scheduler_unless_startup_abandoned; +use paddler_agent_runtime::scheduler_handle::SchedulerHandle; +use paddler_agent_runtime::scheduler_inbox::SchedulerInbox; +use paddler_agent_runtime::scheduler_ready::SchedulerReady; +use paddler_agent_runtime::scheduler_request_preparer::SchedulerRequestPreparer; +use paddler_agent_runtime::start_scheduler_thread::start_scheduler_thread; +use paddler_agent_runtime::startup_outcome::StartupOutcome; +use paddler_agent_status::agent_issue_fix::AgentIssueFix; +use paddler_inference_parameters::model_runtime_parameters::ModelRuntimeParameters; +use paddler_messaging::agent_issue::AgentIssue; +use paddler_messaging::agent_issue_params::model_path::ModelPath; +use paddler_messaging::agent_issue_params::pointer_head_incompatibility::PointerHeadIncompatibility; +use paddler_messaging::agent_issue_params::pointer_head_incompatible_with_model_params::PointerHeadIncompatibleWithModelParams; + +use crate::decision_capacity_ledger::DecisionCapacityLedger; +use crate::decision_delimiter_tokens::DecisionDelimiterTokens; +use crate::decision_error::DecisionError; +use crate::decision_llama_context_settings::DecisionLlamaContextSettings; +use crate::decision_request_preparer::DecisionRequestPreparer; +use crate::decision_scheduler::DecisionScheduler; +use crate::decision_scheduler_context::DecisionSchedulerContext; +use crate::decision_text_tokenizer::DecisionTextTokenizer; +use crate::loaded_pointer_head::LoadedPointerHead; + +pub struct DecisionPipeline { + pub inference_runtime_context: InferenceRuntimeContext, + pub loaded_llama_model: LoadedLlamaModel, + pub loaded_pointer_head: LoadedPointerHead, + pub model_runtime_parameters: ModelRuntimeParameters, +} + +impl DecisionPipeline { + pub async fn spawn( + self, + cancellation_token: &CancellationToken, + ) -> Result>, DecisionError> { + start_scheduler_thread( + cancellation_token, + move |agent_shutdown, scheduler_startup_tx| { + self.run_scheduler(agent_shutdown, scheduler_startup_tx) + }, + ) + .await + } + + fn register_compatibility(&self) { + self.inference_runtime_context + .slot_aggregated_status + .register_fix(&AgentIssueFix::PointerHeadIsCompatibleWithModel( + ModelPath::from(self.loaded_pointer_head.pointer_head_path.as_path()), + )); + } + + fn register_incompatibility( + &self, + incompatibility: PointerHeadIncompatibility, + ) -> DecisionError { + self.inference_runtime_context + .slot_aggregated_status + .register_pipeline_issue(AgentIssue::PointerHeadIncompatibleWithModel( + PointerHeadIncompatibleWithModelParams { + incompatibility: incompatibility.clone(), + pointer_head_path: ModelPath::from( + self.loaded_pointer_head.pointer_head_path.as_path(), + ), + }, + )); + + DecisionError::PointerHeadIncompatibleWithModel { incompatibility } + } + + fn resolve_delimiter_tokens(&self) -> Result { + let model_hidden_size = self.loaded_llama_model.model.n_embd_out() as usize; + let pointer_head = &self.loaded_pointer_head.pointer_head; + + if pointer_head.hidden_size() != model_hidden_size { + return Err(self.register_incompatibility( + PointerHeadIncompatibility::HiddenSizeMismatch { + model_hidden_size, + pointer_head_hidden_size: pointer_head.hidden_size(), + }, + )); + } + + match DecisionDelimiterTokens::resolve( + &self.loaded_llama_model.model.llama_model, + &pointer_head.delimiters, + ) { + Err(DecisionError::PointerHeadIncompatibleWithModel { incompatibility }) => { + Err(self.register_incompatibility(incompatibility)) + } + resolution => resolution.inspect(|_delimiter_tokens| self.register_compatibility()), + } + } + + fn run_scheduler( + self, + agent_shutdown: &CancellationToken, + scheduler_startup_tx: &UnboundedSender< + Result, DecisionError>, + >, + ) -> Result<(), DecisionError> { + continue_startup_unless_shutting_down(agent_shutdown, || { + let delimiter_tokens = self.resolve_delimiter_tokens()?; + let desired_slots_total = self + .inference_runtime_context + .slot_aggregated_status + .desired_slots_total; + let n_batch = self.model_runtime_parameters.n_batch.tokens_usize(); + let llama_context = DecisionLlamaContextSettings { + llama_context_settings: self.loaded_llama_model.llama_context_settings( + &self.model_runtime_parameters, + u32::from(desired_slots_total), + ), + } + .create_llama_context(&self.loaded_llama_model, &self.inference_runtime_context)?; + let batch = LlamaBatch::new(self.model_runtime_parameters.n_batch.capacity(), 1); + + continue_startup_unless_shutting_down(agent_shutdown, || { + self.loaded_llama_model + .warm_up_and_serve( + llama_context, + batch, + desired_slots_total, + |llama_context, batch| { + let cells_per_slot = + llama_context.n_ctx() / u32::from(desired_slots_total); + let (scheduler_message_tx, scheduler_message_rx) = unbounded_channel(); + + run_scheduler_unless_startup_abandoned( + scheduler_startup_tx, + Ok(SchedulerReady { + request_preparer: Arc::new(SchedulerRequestPreparer { + agent_name: self + .inference_runtime_context + .agent_name + .clone(), + preparation: DecisionRequestPreparer { + cells_per_slot, + delimiter_tokens, + text_tokenizer: DecisionTextTokenizer { + input_normalization: self + .loaded_pointer_head + .pointer_head + .input_normalization, + model: self.loaded_llama_model.model.clone(), + }, + }, + preparation_tasks: TaskTracker::new(), + scheduler_message_tx, + }), + }), + || { + DecisionScheduler { + active_requests: VecDeque::new(), + agent_shutdown: agent_shutdown.clone(), + batch, + capacity_ledger: DecisionCapacityLedger::new( + desired_slots_total, + ), + llama_context, + pending_requests: VecDeque::new(), + scheduler_context: DecisionSchedulerContext { + agent_name: self.inference_runtime_context.agent_name, + n_batch, + pointer_head: self.loaded_pointer_head.pointer_head, + }, + scheduler_inbox: SchedulerInbox::new(scheduler_message_rx), + } + .run(); + }, + ); + }, + ) + .map_err(DecisionError::from) + }) + }) + } +} diff --git a/paddler_agent_decision/src/decision_prerequisites.rs b/paddler_agent_decision/src/decision_prerequisites.rs new file mode 100644 index 000000000..0f662f296 --- /dev/null +++ b/paddler_agent_decision/src/decision_prerequisites.rs @@ -0,0 +1,72 @@ +use std::path::PathBuf; + +use paddler_agent_pointer_head::pointer_head::PointerHead; +use paddler_agent_runtime::inference_runtime_context::InferenceRuntimeContext; +use paddler_agent_status::agent_issue_fix::AgentIssueFix; +use paddler_messaging::agent_issue::AgentIssue; +use paddler_messaging::agent_issue_params::model_path::ModelPath; +use paddler_messaging::agent_issue_params::slots_insufficient_for_decisions_params::SlotsInsufficientForDecisionsParams; +use paddler_messaging::decision_slots_minimum::DECISION_SLOTS_MINIMUM; + +use crate::decision_error::DecisionError; +use crate::loaded_pointer_head::LoadedPointerHead; + +pub struct DecisionPrerequisites { + pub inference_runtime_context: InferenceRuntimeContext, + pub pointer_head_path: PathBuf, +} + +impl DecisionPrerequisites { + pub fn load_pointer_head(self) -> Result { + self.require_enough_slots()?; + + match PointerHead::load(&self.pointer_head_path) { + Ok(pointer_head) => { + self.inference_runtime_context + .slot_aggregated_status + .register_fix(&AgentIssueFix::PointerHeadIsLoaded(ModelPath::from( + self.pointer_head_path.as_path(), + ))); + + Ok(LoadedPointerHead { + pointer_head, + pointer_head_path: self.pointer_head_path, + }) + } + Err(source) => { + self.inference_runtime_context + .slot_aggregated_status + .register_pipeline_issue(AgentIssue::PointerHeadCannotBeLoaded( + ModelPath::from(self.pointer_head_path.as_path()), + )); + + Err(DecisionError::PointerHeadCannotBeLoaded(source)) + } + } + } + + fn require_enough_slots(&self) -> Result<(), DecisionError> { + let desired_slots = self + .inference_runtime_context + .slot_aggregated_status + .desired_slots_total; + + if desired_slots >= DECISION_SLOTS_MINIMUM.get() { + return Ok(()); + } + + self.inference_runtime_context + .slot_aggregated_status + .register_pipeline_issue(AgentIssue::SlotsInsufficientForDecisions( + SlotsInsufficientForDecisionsParams { + desired_slots, + required_slots: DECISION_SLOTS_MINIMUM.get(), + }, + )); + + Err(DecisionError::SlotsInsufficient { + desired_slots, + required_slots: DECISION_SLOTS_MINIMUM.get(), + }) + } +} diff --git a/paddler_agent_decision/src/decision_question_row.rs b/paddler_agent_decision/src/decision_question_row.rs new file mode 100644 index 000000000..764f753bc --- /dev/null +++ b/paddler_agent_decision/src/decision_question_row.rs @@ -0,0 +1,38 @@ +use llama_cpp_bindings::token::LlamaToken; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DecisionQuestionRow { + pub id: String, + pub option_end_offsets: Vec, + pub tokens: Vec, +} + +impl DecisionQuestionRow { + #[must_use] + pub fn is_output(&self, row_offset: usize) -> bool { + row_offset + 1 == self.tokens.len() || self.option_end_offsets.contains(&row_offset) + } +} + +#[cfg(test)] +mod tests { + use llama_cpp_bindings::token::LlamaToken; + + use super::DecisionQuestionRow; + + #[test] + fn option_ends_and_the_closing_decide_token_are_outputs() { + let row = DecisionQuestionRow { + id: "question".to_owned(), + option_end_offsets: vec![2], + tokens: (0..4).map(LlamaToken::new).collect(), + }; + + assert_eq!( + (0..4) + .map(|row_offset| row.is_output(row_offset)) + .collect::>(), + vec![false, false, true, true] + ); + } +} diff --git a/paddler_agent_decision/src/decision_request_phase.rs b/paddler_agent_decision/src/decision_request_phase.rs new file mode 100644 index 000000000..334a725c3 --- /dev/null +++ b/paddler_agent_decision/src/decision_request_phase.rs @@ -0,0 +1,5 @@ +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum DecisionRequestPhase { + AnsweringQuestion { next_offset: usize }, + IngestingState { next_offset: usize }, +} diff --git a/paddler_agent_decision/src/decision_request_preparer.rs b/paddler_agent_decision/src/decision_request_preparer.rs new file mode 100644 index 000000000..936db4b8d --- /dev/null +++ b/paddler_agent_decision/src/decision_request_preparer.rs @@ -0,0 +1,130 @@ +use std::collections::HashMap; +use std::collections::hash_map::Entry; +use std::time::Instant; + +use llama_cpp_bindings::token::LlamaToken; + +use paddler_agent_runtime::agent_request::AgentRequest; +use paddler_agent_runtime::prepares_scheduler_command::PreparesSchedulerCommand; +use paddler_messaging::decision_question::DecisionQuestion; +use paddler_messaging::decision_result::DecisionResult; +use paddler_messaging::oversized_decision_details::OversizedDecisionDetails; +use paddler_messaging::request_params::decide_params::DecideParams; + +use crate::decision_delimiter_tokens::DecisionDelimiterTokens; +use crate::decision_error::DecisionError; +use crate::decision_text_tokenizer::DecisionTextTokenizer; +use crate::decision_token_layout::DecisionTokenLayout; +use crate::prepared_decision_request::PreparedDecisionRequest; +use crate::reports_decision_outcome::ReportsDecisionOutcome as _; +use crate::tokenized_decision_question::TokenizedDecisionQuestion; + +pub struct DecisionRequestPreparer { + pub cells_per_slot: u32, + pub delimiter_tokens: DecisionDelimiterTokens, + pub text_tokenizer: DecisionTextTokenizer, +} + +impl DecisionRequestPreparer { + fn tokenize_once( + &self, + tokens_by_text: &mut HashMap>, + text: String, + ) -> Result, DecisionError> { + match tokens_by_text.entry(text) { + Entry::Occupied(tokenized_text) => Ok(tokenized_text.get().clone()), + Entry::Vacant(untokenized_text) => { + let tokens = self.text_tokenizer.tokenize(untokenized_text.key())?; + + Ok(untokenized_text.insert(tokens).clone()) + } + } + } + + fn tokenize_question( + &self, + tokens_by_text: &mut HashMap>, + DecisionQuestion { + id, + instructions, + options, + }: DecisionQuestion, + ) -> Result { + Ok(TokenizedDecisionQuestion { + id, + instructions: self.tokenize_once(tokens_by_text, instructions)?, + options: options + .into_iter() + .map(|option| self.tokenize_once(tokens_by_text, option)) + .collect::, _>>()?, + }) + } + + fn prepare( + &self, + AgentRequest { + params, + response_tx: decision_result_tx, + slot_guard, + stop_rx: decision_stop_rx, + }: AgentRequest, + ) -> Result { + let started_at = Instant::now(); + let context_size = u32::from(slot_guard.slots().get()) * self.cells_per_slot; + let DecideParams { + last_question, + leading_questions, + state, + } = params; + let mut tokens_by_text = HashMap::new(); + let layout = DecisionTokenLayout::new( + &self.delimiter_tokens, + self.text_tokenizer.tokenize(&state)?, + leading_questions + .into_iter() + .map(|question| self.tokenize_question(&mut tokens_by_text, question)) + .collect::, _>>()?, + self.tokenize_question(&mut tokens_by_text, last_question)?, + ); + let required_tokens = layout.required_cells(); + + if required_tokens > context_size as usize { + return Err(DecisionError::RequestExceedsContext { + details: OversizedDecisionDetails { + context_size, + required_tokens, + }, + }); + } + + Ok(PreparedDecisionRequest { + decision_result_tx, + decision_stop_rx, + layout, + slot_guard, + started_at, + }) + } +} + +impl PreparesSchedulerCommand for DecisionRequestPreparer { + type Command = PreparedDecisionRequest; + type Request = AgentRequest; + + fn prepare_scheduler_command( + &self, + agent_name: Option<&str>, + request: Self::Request, + ) -> Option { + let decision_result_tx = request.response_tx.clone(); + + match self.prepare(request) { + Ok(prepared) => Some(prepared), + Err(rejection) => { + rejection.report(agent_name, &decision_result_tx); + + None + } + } + } +} diff --git a/paddler_agent_decision/src/decision_scheduler.rs b/paddler_agent_decision/src/decision_scheduler.rs new file mode 100644 index 000000000..cd2d7be07 --- /dev/null +++ b/paddler_agent_decision/src/decision_scheduler.rs @@ -0,0 +1,158 @@ +use std::collections::VecDeque; + +use llama_cpp_bindings::llama_batch::LlamaBatch; +use log::info; +use tokio_util::sync::CancellationToken; + +use paddler_agent_runtime::receives_stop_request::ReceivesStopRequest as _; +use paddler_agent_runtime::resident_llama_context::ResidentLlamaContext; +use paddler_agent_runtime::scheduler_inbox::SchedulerInbox; +use paddler_messaging::decision_result::DecisionResult; + +use crate::active_decision_request::ActiveDecisionRequest; +use crate::decision_advance::DecisionAdvance; +use crate::decision_capacity_ledger::DecisionCapacityLedger; +use crate::decision_decoder::DecisionDecoder; +use crate::decision_error::DecisionError; +use crate::decision_scheduler_context::DecisionSchedulerContext; +use crate::prepared_decision_request::PreparedDecisionRequest; +use crate::reports_decision_outcome::ReportsDecisionOutcome as _; + +pub struct DecisionScheduler<'model> { + pub active_requests: VecDeque, + pub agent_shutdown: CancellationToken, + pub batch: LlamaBatch<'static>, + pub capacity_ledger: DecisionCapacityLedger, + pub llama_context: ResidentLlamaContext<'model>, + pub pending_requests: VecDeque, + pub scheduler_context: DecisionSchedulerContext, + pub scheduler_inbox: SchedulerInbox, +} + +impl DecisionScheduler<'_> { + pub fn run(&mut self) { + info!( + "{:?}: decision scheduler started", + self.scheduler_context.agent_name + ); + + while !self.agent_shutdown.is_cancelled() { + if self.active_requests.is_empty() && self.pending_requests.is_empty() { + if self.scheduler_inbox.shutdown_requested { + break; + } + + self.wait_for_next_scheduler_message(); + } else { + self.accept_new_scheduler_messages(); + } + + self.admit_pending_requests(); + self.advance_next_active_request(); + } + + self.reject_unfinished_requests(); + self.llama_context.synchronize(); + + info!( + "{:?}: decision scheduler stopped", + self.scheduler_context.agent_name + ); + } + + fn accept_new_scheduler_messages(&mut self) { + while let Some(request) = self.scheduler_inbox.take_next_waiting_command() { + self.pending_requests.push_back(request); + } + } + + fn admit_pending_requests(&mut self) { + while let Some(mut request) = self.pending_requests.pop_front() { + if request.decision_stop_rx.is_stop_requested() { + DecisionResult::StopRequested.report( + self.scheduler_context.agent_name.as_deref(), + &request.decision_result_tx, + ); + + continue; + } + + let Some(admission) = self + .capacity_ledger + .admit(request.layout.question_lane_demand()) + else { + self.pending_requests.push_front(request); + + break; + }; + + if let Err(clear_error) = admission + .state_sequence + .sequence_id() + .clear_kv_cache_in(&mut self.llama_context) + { + DecisionError::KvCacheRemovalFailed(clear_error).report( + self.scheduler_context.agent_name.as_deref(), + &request.decision_result_tx, + ); + + continue; + } + + self.active_requests + .push_back(ActiveDecisionRequest::new(request, admission)); + } + } + + fn advance_next_active_request(&mut self) { + let Some(mut request) = self.active_requests.pop_front() else { + return; + }; + let mut decoder = DecisionDecoder { + batch: &mut self.batch, + llama_context: &mut self.llama_context, + n_batch: self.scheduler_context.n_batch, + }; + let agent_name = self.scheduler_context.agent_name.as_deref(); + + if request.is_stop_requested() { + request.finish_with(&mut decoder, agent_name, DecisionResult::StopRequested); + + return; + } + + match request.advance( + &mut decoder, + &self.scheduler_context.pointer_head, + agent_name, + ) { + Ok(DecisionAdvance::Continuing) => self.active_requests.push_back(request), + Ok(DecisionAdvance::Completed) => { + let summary = request.summary(); + + request.finish_with(&mut decoder, agent_name, summary); + } + Err(advance_error) => request.finish_with(&mut decoder, agent_name, advance_error), + } + } + + fn reject_unfinished_requests(&mut self) { + let agent_name = self.scheduler_context.agent_name.as_deref(); + + for request in self.active_requests.drain(..) { + DecisionError::SchedulerUnavailable.report(agent_name, &request.decision_result_tx); + } + + for request in self.pending_requests.drain(..) { + DecisionError::SchedulerUnavailable.report(agent_name, &request.decision_result_tx); + } + + self.scheduler_inbox.reject_waiting_commands(agent_name); + } + + fn wait_for_next_scheduler_message(&mut self) { + if let Some(request) = self.scheduler_inbox.wait_for_next_command() { + self.pending_requests.push_back(request); + } + } +} diff --git a/paddler_agent_decision/src/decision_scheduler_context.rs b/paddler_agent_decision/src/decision_scheduler_context.rs new file mode 100644 index 000000000..d7bae17e1 --- /dev/null +++ b/paddler_agent_decision/src/decision_scheduler_context.rs @@ -0,0 +1,7 @@ +use paddler_agent_pointer_head::pointer_head::PointerHead; + +pub struct DecisionSchedulerContext { + pub agent_name: Option, + pub n_batch: usize, + pub pointer_head: PointerHead, +} diff --git a/paddler_agent_decision/src/decision_text_tokenizer.rs b/paddler_agent_decision/src/decision_text_tokenizer.rs new file mode 100644 index 000000000..3743f2077 --- /dev/null +++ b/paddler_agent_decision/src/decision_text_tokenizer.rs @@ -0,0 +1,31 @@ +use std::ops::Deref; +use std::sync::Arc; + +use llama_cpp_bindings::model::AddBos; +use llama_cpp_bindings::model::LlamaModel; +use llama_cpp_bindings::model::ParseSpecialTokens; +use llama_cpp_bindings::token::LlamaToken; + +use paddler_agent_pointer_head::pointer_head_input_normalization::PointerHeadInputNormalization; + +use crate::decision_error::DecisionError; +use crate::escape_special_token_lookalikes::escape_special_token_lookalikes; + +pub struct DecisionTextTokenizer { + pub input_normalization: PointerHeadInputNormalization, + pub model: Arc + Send + Sync>, +} + +impl DecisionTextTokenizer { + pub fn tokenize(&self, text: &str) -> Result, DecisionError> { + self.model + .str_to_token( + &self + .input_normalization + .normalize(&escape_special_token_lookalikes(text)), + AddBos::Never, + ParseSpecialTokens::Never, + ) + .map_err(DecisionError::InputTokenizationFailed) + } +} diff --git a/paddler_agent_decision/src/decision_token_layout.rs b/paddler_agent_decision/src/decision_token_layout.rs new file mode 100644 index 000000000..570a08a09 --- /dev/null +++ b/paddler_agent_decision/src/decision_token_layout.rs @@ -0,0 +1,205 @@ +use std::collections::VecDeque; + +use llama_cpp_bindings::token::LlamaToken; + +use crate::decision_delimiter_tokens::DecisionDelimiterTokens; +use crate::decision_question_row::DecisionQuestionRow; +use crate::question_lane_demand::QuestionLaneDemand; +use crate::tokenized_decision_question::TokenizedDecisionQuestion; + +fn question_row( + delimiter_tokens: &DecisionDelimiterTokens, + TokenizedDecisionQuestion { + id, + instructions, + options, + }: TokenizedDecisionQuestion, +) -> DecisionQuestionRow { + let mut tokens = Vec::with_capacity( + instructions.len() + options.iter().map(|option| option.len() + 2).sum::() + 2, + ); + let mut option_end_offsets = Vec::with_capacity(options.len()); + + tokens.push(delimiter_tokens.question); + tokens.extend(instructions); + + for option in options { + tokens.push(delimiter_tokens.option_start); + tokens.extend(option); + option_end_offsets.push(tokens.len()); + tokens.push(delimiter_tokens.option_end); + } + + tokens.push(delimiter_tokens.decide); + + DecisionQuestionRow { + id, + option_end_offsets, + tokens, + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct DecisionTokenLayout { + pub first_question: DecisionQuestionRow, + pub remaining_questions: VecDeque, + pub state: Vec, +} + +impl DecisionTokenLayout { + #[must_use] + pub fn new( + delimiter_tokens: &DecisionDelimiterTokens, + state: Vec, + leading_questions: Vec, + last_question: TokenizedDecisionQuestion, + ) -> Self { + let mut state_tokens = Vec::with_capacity(state.len() + 1); + + state_tokens.push(delimiter_tokens.state); + state_tokens.extend(state); + + let mut remaining_questions: VecDeque = leading_questions + .into_iter() + .map(|question| question_row(delimiter_tokens, question)) + .collect(); + let last_question = question_row(delimiter_tokens, last_question); + let first_question = match remaining_questions.pop_front() { + Some(first_question) => { + remaining_questions.push_back(last_question); + + first_question + } + None => last_question, + }; + + Self { + first_question, + remaining_questions, + state: state_tokens, + } + } + + #[must_use] + pub fn question_lane_demand(&self) -> QuestionLaneDemand { + if self.remaining_questions.is_empty() { + QuestionLaneDemand::NotNeeded + } else { + QuestionLaneDemand::Needed + } + } + + #[must_use] + pub fn required_cells(&self) -> usize { + self.state.len() + + self + .remaining_questions + .iter() + .map(|question| question.tokens.len()) + .fold(self.first_question.tokens.len(), usize::max) + } + + #[must_use] + pub fn total_tokens(&self) -> usize { + self.state.len() + + self.first_question.tokens.len() + + self + .remaining_questions + .iter() + .map(|question| question.tokens.len()) + .sum::() + } +} + +#[cfg(test)] +mod tests { + use llama_cpp_bindings::token::LlamaToken; + + use super::DecisionTokenLayout; + use crate::decision_delimiter_tokens::DecisionDelimiterTokens; + use crate::decision_question_row::DecisionQuestionRow; + use crate::tokenized_decision_question::TokenizedDecisionQuestion; + + fn tokens(ids: &[i32]) -> Vec { + ids.iter().copied().map(LlamaToken::new).collect() + } + + fn delimiter_tokens() -> DecisionDelimiterTokens { + DecisionDelimiterTokens { + decide: LlamaToken::new(-5), + option_end: LlamaToken::new(-4), + option_start: LlamaToken::new(-3), + question: LlamaToken::new(-2), + state: LlamaToken::new(-1), + } + } + + fn first_question() -> TokenizedDecisionQuestion { + TokenizedDecisionQuestion { + id: "first".to_owned(), + instructions: tokens(&[20]), + options: vec![tokens(&[30, 31]), tokens(&[32])], + } + } + + fn last_question() -> TokenizedDecisionQuestion { + TokenizedDecisionQuestion { + id: "last".to_owned(), + instructions: Vec::new(), + options: vec![tokens(&[40])], + } + } + + fn two_question_layout() -> DecisionTokenLayout { + DecisionTokenLayout::new( + &delimiter_tokens(), + tokens(&[10, 11]), + vec![first_question()], + last_question(), + ) + } + + #[test] + fn lays_out_the_state_and_one_row_per_question_with_kev_delimiters() { + assert_eq!( + two_question_layout(), + DecisionTokenLayout { + first_question: DecisionQuestionRow { + id: "first".to_owned(), + option_end_offsets: vec![5, 8], + tokens: tokens(&[-2, 20, -3, 30, 31, -4, -3, 32, -4, -5]), + }, + remaining_questions: [DecisionQuestionRow { + id: "last".to_owned(), + option_end_offsets: vec![3], + tokens: tokens(&[-2, -3, 40, -4, -5]), + }] + .into(), + state: tokens(&[-1, 10, 11]), + } + ); + } + + #[test] + fn a_single_question_is_the_first_question() { + let layout = DecisionTokenLayout::new( + &delimiter_tokens(), + tokens(&[10]), + Vec::new(), + last_question(), + ); + + assert_eq!(layout.first_question.id, "last"); + assert!(layout.remaining_questions.is_empty()); + } + + #[test] + fn a_decision_needs_its_state_and_its_longest_question_in_the_cache() { + assert_eq!(two_question_layout().required_cells(), 13); + } + + #[test] + fn a_decision_decodes_its_state_once_and_every_question_row() { + assert_eq!(two_question_layout().total_tokens(), 18); + } +} diff --git a/paddler_agent_decision/src/delimiter_vocabulary.rs b/paddler_agent_decision/src/delimiter_vocabulary.rs new file mode 100644 index 000000000..0faf1933c --- /dev/null +++ b/paddler_agent_decision/src/delimiter_vocabulary.rs @@ -0,0 +1,23 @@ +use llama_cpp_bindings::error::StringToTokenError; +use llama_cpp_bindings::error::TokenAttrError; +use llama_cpp_bindings::llama_token_attrs::LlamaTokenAttrs; +use llama_cpp_bindings::model::AddBos; +use llama_cpp_bindings::model::LlamaModel; +use llama_cpp_bindings::model::ParseSpecialTokens; +use llama_cpp_bindings::token::LlamaToken; + +pub trait DelimiterVocabulary { + fn tokenize_delimiter(&self, delimiter: &str) -> Result, StringToTokenError>; + + fn token_attributes(&self, token: LlamaToken) -> Result; +} + +impl DelimiterVocabulary for LlamaModel { + fn tokenize_delimiter(&self, delimiter: &str) -> Result, StringToTokenError> { + self.str_to_token(delimiter, AddBos::Never, ParseSpecialTokens::Always) + } + + fn token_attributes(&self, token: LlamaToken) -> Result { + self.token_attr(token) + } +} diff --git a/paddler_agent_decision/src/escape_special_token_lookalikes.rs b/paddler_agent_decision/src/escape_special_token_lookalikes.rs new file mode 100644 index 000000000..8d776b1af --- /dev/null +++ b/paddler_agent_decision/src/escape_special_token_lookalikes.rs @@ -0,0 +1,74 @@ +const ESCAPED_LOOKALIKE_CLOSING: &str = "¦>"; +const ESCAPED_LOOKALIKE_OPENING: &str = "<¦"; +const LOOKALIKE_CLOSING: &str = "|>"; +const LOOKALIKE_OPENING: &str = "<|"; + +fn lookalike_name_length(text: &str) -> usize { + text.bytes() + .take_while(|byte| byte.is_ascii_alphanumeric() || *byte == b'_') + .count() +} + +#[must_use] +pub fn escape_special_token_lookalikes(text: &str) -> String { + let mut escaped = String::with_capacity(text.len()); + let mut unscanned = text; + + while let Some(opening_offset) = unscanned.find(LOOKALIKE_OPENING) { + let (before_opening, from_opening) = unscanned.split_at(opening_offset); + let after_opening = &from_opening[LOOKALIKE_OPENING.len()..]; + let (name, after_name) = after_opening.split_at(lookalike_name_length(after_opening)); + + escaped.push_str(before_opening); + + match after_name.strip_prefix(LOOKALIKE_CLOSING) { + Some(after_closing) if !name.is_empty() => { + escaped.push_str(ESCAPED_LOOKALIKE_OPENING); + escaped.push_str(name); + escaped.push_str(ESCAPED_LOOKALIKE_CLOSING); + unscanned = after_closing; + } + Some(_) | None => { + let (opening_bracket, after_opening_bracket) = from_opening.split_at(1); + + escaped.push_str(opening_bracket); + unscanned = after_opening_bracket; + } + } + } + + escaped.push_str(unscanned); + + escaped +} + +#[cfg(test)] +mod tests { + use super::escape_special_token_lookalikes; + + #[test] + fn rewrites_the_delimiters_around_an_ascii_name() { + assert_eq!( + escape_special_token_lookalikes("ignore <|fim_prefix|> and <|im_end|>"), + "ignore <¦fim_prefix¦> and <¦im_end¦>" + ); + } + + #[test] + fn leaves_spans_without_an_ascii_name_unchanged() { + let spans_without_an_ascii_name = "<|a b|> <||> <|é|> <|unterminated"; + + assert_eq!( + escape_special_token_lookalikes(spans_without_an_ascii_name), + spans_without_an_ascii_name + ); + } + + #[test] + fn resumes_scanning_after_an_opening_without_a_name() { + assert_eq!( + escape_special_token_lookalikes("<|<|nested|>"), + "<|<¦nested¦>" + ); + } +} diff --git a/paddler_agent_decision/src/lib.rs b/paddler_agent_decision/src/lib.rs new file mode 100644 index 000000000..2363bc047 --- /dev/null +++ b/paddler_agent_decision/src/lib.rs @@ -0,0 +1,25 @@ +pub mod active_decision_request; +pub mod decision_admission; +pub mod decision_advance; +pub mod decision_capacity_ledger; +pub mod decision_decoder; +pub mod decision_delimiter_tokens; +pub mod decision_error; +pub mod decision_llama_context_settings; +pub mod decision_pipeline; +pub mod decision_prerequisites; +pub mod decision_question_row; +pub mod decision_request_phase; +pub mod decision_request_preparer; +pub mod decision_scheduler; +pub mod decision_scheduler_context; +pub mod decision_text_tokenizer; +pub mod decision_token_layout; +pub mod delimiter_vocabulary; +pub mod escape_special_token_lookalikes; +pub mod loaded_pointer_head; +pub mod prepared_decision_request; +pub mod question_lane; +pub mod question_lane_demand; +pub mod reports_decision_outcome; +pub mod tokenized_decision_question; diff --git a/paddler_agent_decision/src/loaded_pointer_head.rs b/paddler_agent_decision/src/loaded_pointer_head.rs new file mode 100644 index 000000000..ee7e70a26 --- /dev/null +++ b/paddler_agent_decision/src/loaded_pointer_head.rs @@ -0,0 +1,8 @@ +use std::path::PathBuf; + +use paddler_agent_pointer_head::pointer_head::PointerHead; + +pub struct LoadedPointerHead { + pub pointer_head: PointerHead, + pub pointer_head_path: PathBuf, +} diff --git a/paddler_agent_decision/src/prepared_decision_request.rs b/paddler_agent_decision/src/prepared_decision_request.rs new file mode 100644 index 000000000..63c0d448e --- /dev/null +++ b/paddler_agent_decision/src/prepared_decision_request.rs @@ -0,0 +1,75 @@ +use std::time::Instant; + +use tokio::sync::mpsc; + +use paddler_agent_runtime::scheduler_command::SchedulerCommand; +use paddler_agent_status::slot_guard::SlotGuard; +use paddler_messaging::decision_result::DecisionResult; + +use crate::decision_error::DecisionError; +use crate::decision_token_layout::DecisionTokenLayout; +use crate::reports_decision_outcome::ReportsDecisionOutcome as _; + +pub struct PreparedDecisionRequest { + pub decision_result_tx: mpsc::UnboundedSender, + pub decision_stop_rx: mpsc::UnboundedReceiver<()>, + pub layout: DecisionTokenLayout, + pub slot_guard: SlotGuard, + pub started_at: Instant, +} + +impl SchedulerCommand for PreparedDecisionRequest { + fn reject_because_the_scheduler_stopped(self, agent_name: Option<&str>) { + DecisionError::SchedulerUnavailable.report(agent_name, &self.decision_result_tx); + } +} + +#[cfg(test)] +mod tests { + use std::collections::VecDeque; + use std::num::NonZeroU16; + use std::sync::Arc; + use std::time::Instant; + + use llama_cpp_bindings::token::LlamaToken; + use tokio::sync::mpsc; + + use paddler_agent_runtime::scheduler_command::SchedulerCommand as _; + use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; + use paddler_agent_status::slot_guard::SlotGuard; + use paddler_messaging::decision_result::DecisionResult; + + use super::PreparedDecisionRequest; + use crate::decision_question_row::DecisionQuestionRow; + use crate::decision_token_layout::DecisionTokenLayout; + + #[test] + fn rejects_a_decision_when_the_scheduler_stopped() { + let (decision_result_tx, mut decision_result_rx) = mpsc::unbounded_channel(); + let (_decision_stop_tx, decision_stop_rx) = mpsc::unbounded_channel(); + + PreparedDecisionRequest { + decision_result_tx, + decision_stop_rx, + layout: DecisionTokenLayout { + first_question: DecisionQuestionRow { + id: "paid".to_owned(), + option_end_offsets: Vec::new(), + tokens: vec![LlamaToken::new(0)], + }, + remaining_questions: VecDeque::new(), + state: Vec::new(), + }, + slot_guard: SlotGuard::new(Arc::new(SlotAggregatedStatus::new(1)), NonZeroU16::MIN), + started_at: Instant::now(), + } + .reject_because_the_scheduler_stopped(Some("agent")); + + assert_eq!( + decision_result_rx.try_recv(), + Ok(DecisionResult::SchedulerUnavailable( + "agent: the scheduler is no longer accepting requests".to_owned() + )) + ); + } +} diff --git a/paddler_agent_decision/src/question_lane.rs b/paddler_agent_decision/src/question_lane.rs new file mode 100644 index 000000000..bfe995807 --- /dev/null +++ b/paddler_agent_decision/src/question_lane.rs @@ -0,0 +1,6 @@ +use paddler_agent_runtime::sequence_id_guard::SequenceIdGuard; + +pub enum QuestionLane { + Released, + Reserved(SequenceIdGuard), +} diff --git a/paddler_agent_decision/src/question_lane_demand.rs b/paddler_agent_decision/src/question_lane_demand.rs new file mode 100644 index 000000000..b05a6eec9 --- /dev/null +++ b/paddler_agent_decision/src/question_lane_demand.rs @@ -0,0 +1,5 @@ +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum QuestionLaneDemand { + NotNeeded, + Needed, +} diff --git a/paddler_agent_decision/src/reports_decision_outcome.rs b/paddler_agent_decision/src/reports_decision_outcome.rs new file mode 100644 index 000000000..fba32e58f --- /dev/null +++ b/paddler_agent_decision/src/reports_decision_outcome.rs @@ -0,0 +1,22 @@ +use tokio::sync::mpsc; + +use paddler_agent_runtime::send_result_or_warn::send_result_or_warn; +use paddler_messaging::decision_result::DecisionResult; + +pub trait ReportsDecisionOutcome { + fn report( + self, + agent_name: Option<&str>, + decision_result_tx: &mpsc::UnboundedSender, + ); +} + +impl ReportsDecisionOutcome for DecisionResult { + fn report( + self, + agent_name: Option<&str>, + decision_result_tx: &mpsc::UnboundedSender, + ) { + send_result_or_warn(agent_name, decision_result_tx, self); + } +} diff --git a/paddler_agent_decision/src/tokenized_decision_question.rs b/paddler_agent_decision/src/tokenized_decision_question.rs new file mode 100644 index 000000000..05fcbbe24 --- /dev/null +++ b/paddler_agent_decision/src/tokenized_decision_question.rs @@ -0,0 +1,7 @@ +use llama_cpp_bindings::token::LlamaToken; + +pub struct TokenizedDecisionQuestion { + pub id: String, + pub instructions: Vec, + pub options: Vec>, +} diff --git a/paddler_agent_embeddings/Cargo.toml b/paddler_agent_embeddings/Cargo.toml new file mode 100644 index 000000000..3e6b1965f --- /dev/null +++ b/paddler_agent_embeddings/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "paddler_agent_embeddings" +authors.workspace = true +description.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[dependencies] +llama-cpp-bindings = { workspace = true } +log = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +tokio-util = { workspace = true } +paddler_agent_runtime = { workspace = true } +paddler_agent_status = { workspace = true } +paddler_inference_parameters = { workspace = true } +paddler_messaging = { workspace = true } + +[features] +default = [] +cuda = ["llama-cpp-bindings/cuda", "paddler_agent_runtime/cuda"] +metal = ["llama-cpp-bindings/metal", "paddler_agent_runtime/metal"] +vulkan = ["llama-cpp-bindings/vulkan", "paddler_agent_runtime/vulkan"] + +[lints] +workspace = true diff --git a/paddler_agent_embeddings/src/active_embedding_request.rs b/paddler_agent_embeddings/src/active_embedding_request.rs new file mode 100644 index 000000000..4a107dd9d --- /dev/null +++ b/paddler_agent_embeddings/src/active_embedding_request.rs @@ -0,0 +1,68 @@ +use std::collections::VecDeque; + +use tokio::sync::mpsc; + +use paddler_agent_runtime::receives_stop_request::ReceivesStopRequest as _; +use paddler_agent_runtime::send_result_or_warn::send_result_or_warn; +use paddler_agent_status::slot_guard::SlotGuard; +use paddler_messaging::embedding_normalization_method::EmbeddingNormalizationMethod; +use paddler_messaging::embedding_result::EmbeddingResult; + +use crate::embedding_input_tokenized::EmbeddingInputTokenized; +use crate::embedding_request_continuation::EmbeddingRequestContinuation; + +pub struct ActiveEmbeddingRequest { + pub embeddings_emitted: usize, + pub generate_embedding_stop_rx: mpsc::UnboundedReceiver<()>, + pub generated_embedding_tx: mpsc::UnboundedSender, + pub normalization_method: EmbeddingNormalizationMethod, + pub pending_inputs: VecDeque, + pub slot_guard: SlotGuard, +} + +impl ActiveEmbeddingRequest { + #[must_use] + pub const fn sequences(&self) -> u16 { + self.slot_guard.slots().get() + } + + #[must_use] + pub fn continue_unless_stop_requested( + mut self, + agent_name: Option<&str>, + ) -> EmbeddingRequestContinuation { + if self.generate_embedding_stop_rx.is_stop_requested() { + self.finish(agent_name); + + return EmbeddingRequestContinuation::Concluded; + } + + EmbeddingRequestContinuation::Continues(self) + } + + #[must_use] + pub fn continue_unless_every_input_is_embedded( + self, + agent_name: Option<&str>, + ) -> EmbeddingRequestContinuation { + if self.pending_inputs.is_empty() { + self.finish(agent_name); + + return EmbeddingRequestContinuation::Concluded; + } + + EmbeddingRequestContinuation::Continues(self) + } + + fn finish(self, agent_name: Option<&str>) { + send_result_or_warn( + agent_name, + &self.generated_embedding_tx, + if self.embeddings_emitted == 0 { + EmbeddingResult::NoEmbeddingsProduced + } else { + EmbeddingResult::Done + }, + ); + } +} diff --git a/paddler_agent/src/agent_pooling_type.rs b/paddler_agent_embeddings/src/agent_pooling_type.rs similarity index 100% rename from paddler_agent/src/agent_pooling_type.rs rename to paddler_agent_embeddings/src/agent_pooling_type.rs diff --git a/paddler_agent_embeddings/src/batched_embedding_input.rs b/paddler_agent_embeddings/src/batched_embedding_input.rs new file mode 100644 index 000000000..0da661101 --- /dev/null +++ b/paddler_agent_embeddings/src/batched_embedding_input.rs @@ -0,0 +1,8 @@ +use crate::embedding_input_tokenized::EmbeddingInputTokenized; + +#[derive(Debug, Eq, PartialEq)] +pub struct BatchedEmbeddingInput { + pub input: EmbeddingInputTokenized, + pub request_index: usize, + pub sequence_id: i32, +} diff --git a/paddler_agent/src/converts_to_llama_pooling_type.rs b/paddler_agent_embeddings/src/converts_to_llama_pooling_type.rs similarity index 100% rename from paddler_agent/src/converts_to_llama_pooling_type.rs rename to paddler_agent_embeddings/src/converts_to_llama_pooling_type.rs diff --git a/paddler_agent_embeddings/src/embedding_batch_preparer.rs b/paddler_agent_embeddings/src/embedding_batch_preparer.rs new file mode 100644 index 000000000..3d234695c --- /dev/null +++ b/paddler_agent_embeddings/src/embedding_batch_preparer.rs @@ -0,0 +1,118 @@ +use llama_cpp_bindings::model::AddBos; +use llama_cpp_bindings::model::ParseSpecialTokens; + +use paddler_agent_runtime::agent_request::AgentRequest; +use paddler_agent_runtime::loaded_llama_model::LoadedLlamaModel; +use paddler_agent_runtime::prepares_scheduler_command::PreparesSchedulerCommand; +use paddler_messaging::embedding_document_exceeding_sequence_context_details::EmbeddingDocumentExceedingSequenceContextDetails; +use paddler_messaging::embedding_document_tokenized_to_nothing_details::EmbeddingDocumentTokenizedToNothingDetails; +use paddler_messaging::embedding_result::EmbeddingResult; +use paddler_messaging::oversized_embedding_document_details::OversizedEmbeddingDocumentDetails; +use paddler_messaging::request_params::generate_embedding_batch_params::GenerateEmbeddingBatchParams; + +use crate::embedding_error::EmbeddingError; +use crate::embedding_input_tokenized::EmbeddingInputTokenized; +use crate::prepared_embedding_batch_request::PreparedEmbeddingBatchRequest; +use crate::sequence_context_limit::SequenceContextLimit; +use crate::unembeddable_embedding_document::UnembeddableEmbeddingDocument; + +pub struct EmbeddingBatchPreparer { + pub loaded_llama_model: LoadedLlamaModel, + pub n_batch: usize, + pub sequence_context_limit: SequenceContextLimit, +} + +impl EmbeddingBatchPreparer { + fn prepare( + &self, + AgentRequest { + params: + GenerateEmbeddingBatchParams { + input_batch, + normalization_method, + }, + response_tx: generated_embedding_tx, + slot_guard, + stop_rx: generate_embedding_stop_rx, + }: AgentRequest, + ) -> Result { + let mut inputs = Vec::with_capacity(input_batch.len()); + let mut unembeddable_documents = Vec::new(); + + for input in input_batch { + let tokens = self + .loaded_llama_model + .model + .str_to_token(&input.content, AddBos::Always, ParseSpecialTokens::Always) + .map_err(|source| EmbeddingError::InputTokenizationFailed { + source_document_id: input.id.clone(), + source, + })?; + + let document_tokens = tokens.len(); + + if tokens.is_empty() { + unembeddable_documents.push(UnembeddableEmbeddingDocument::TokenizedToNothing( + EmbeddingDocumentTokenizedToNothingDetails { + source_document_id: input.id, + }, + )); + } else if document_tokens > self.n_batch { + unembeddable_documents.push(UnembeddableEmbeddingDocument::ExceedsBatchSize( + OversizedEmbeddingDocumentDetails { + document_tokens, + n_batch: self.n_batch, + source_document_id: input.id, + }, + )); + } else if let SequenceContextLimit::Tokens(sequence_context_size) = + self.sequence_context_limit + && document_tokens > sequence_context_size + { + unembeddable_documents.push(UnembeddableEmbeddingDocument::ExceedsSequenceContext( + EmbeddingDocumentExceedingSequenceContextDetails { + document_tokens, + sequence_context_size, + source_document_id: input.id, + }, + )); + } else { + inputs.push(EmbeddingInputTokenized { + id: input.id, + tokens, + }); + } + } + + Ok(PreparedEmbeddingBatchRequest { + generate_embedding_stop_rx, + generated_embedding_tx, + inputs, + normalization_method, + unembeddable_documents, + slot_guard, + }) + } +} + +impl PreparesSchedulerCommand for EmbeddingBatchPreparer { + type Command = PreparedEmbeddingBatchRequest; + type Request = AgentRequest; + + fn prepare_scheduler_command( + &self, + agent_name: Option<&str>, + request: Self::Request, + ) -> Option { + let generated_embedding_tx = request.response_tx.clone(); + + match self.prepare(request) { + Ok(prepared) => Some(prepared), + Err(rejection) => { + rejection.report(agent_name, &generated_embedding_tx); + + None + } + } + } +} diff --git a/paddler_agent_embeddings/src/embedding_batch_processor.rs b/paddler_agent_embeddings/src/embedding_batch_processor.rs new file mode 100644 index 000000000..25d05b61d --- /dev/null +++ b/paddler_agent_embeddings/src/embedding_batch_processor.rs @@ -0,0 +1,167 @@ +use std::collections::BTreeSet; + +use llama_cpp_bindings::context::LlamaContext; +use llama_cpp_bindings::llama_batch::LlamaBatch; + +use paddler_messaging::embedding::Embedding; +use paddler_messaging::embedding_normalization_method::EmbeddingNormalizationMethod; +use paddler_messaging::embedding_result::EmbeddingResult; +use paddler_messaging::embedding_vector::EmbeddingVector; + +use crate::active_embedding_request::ActiveEmbeddingRequest; +use crate::batched_embedding_input::BatchedEmbeddingInput; +use crate::embedding_error::EmbeddingError; +use crate::embedding_input_tokenized::EmbeddingInputTokenized; +use crate::embedding_normalization::EmbeddingNormalization; +use crate::embedding_request_step::EmbeddingRequestStep; +use crate::embedding_scheduler_context::EmbeddingSchedulerContext; +use crate::next_embedding_batch::next_embedding_batch; +use crate::normalizes_embedding::NormalizesEmbedding; + +pub struct EmbeddingBatchProcessor< + 'scheduler, + 'model, + TNormalization: NormalizesEmbedding = EmbeddingNormalization, +> { + pub batch: &'scheduler mut LlamaBatch<'static>, + pub llama_context: &'scheduler mut LlamaContext<'model>, + pub normalization: TNormalization, + pub scheduler_context: &'scheduler EmbeddingSchedulerContext, +} + +impl EmbeddingBatchProcessor<'_, '_, TNormalization> { + #[must_use] + pub fn embed_next_batch( + &mut self, + active_requests: Vec, + ) -> Vec { + let agent_name = self.scheduler_context.agent_name.as_deref(); + let mut active_requests: Vec = active_requests + .into_iter() + .flat_map(|active_request| active_request.continue_unless_stop_requested(agent_name)) + .collect(); + let batched_inputs = next_embedding_batch( + &mut active_requests, + self.scheduler_context.n_batch, + self.scheduler_context.desired_slots_total, + ); + + if batched_inputs.is_empty() { + return active_requests; + } + + let mut request_steps = vec![EmbeddingRequestStep::Continuing; active_requests.len()]; + + match self.decode(&batched_inputs) { + Ok(()) => { + self.deliver_embeddings(batched_inputs, &mut active_requests, &mut request_steps); + } + Err(batch_error) => { + let failed_request_indexes: BTreeSet = batched_inputs + .iter() + .map(|batched_input| batched_input.request_index) + .collect(); + + batch_error.report_to_each( + agent_name, + failed_request_indexes.iter().map(|request_index| { + &active_requests[*request_index].generated_embedding_tx + }), + ); + + for request_index in failed_request_indexes { + request_steps[request_index] = EmbeddingRequestStep::Ended; + } + } + } + + active_requests + .into_iter() + .zip(request_steps) + .filter(|(_active_request, request_step)| { + *request_step == EmbeddingRequestStep::Continuing + }) + .flat_map(|(active_request, _request_step)| { + active_request.continue_unless_every_input_is_embedded(agent_name) + }) + .collect() + } + + fn decode(&mut self, batched_inputs: &[BatchedEmbeddingInput]) -> Result<(), EmbeddingError> { + self.batch.clear(); + + for BatchedEmbeddingInput { + input, sequence_id, .. + } in batched_inputs + { + self.batch + .add_sequence(&input.tokens, *sequence_id, true) + .map_err(EmbeddingError::BatchAssemblyFailed)?; + } + + self.llama_context.clear_kv_cache(); + self.llama_context + .decode(self.batch) + .map_err(EmbeddingError::DecodeFailed) + } + + fn deliver_embeddings( + &self, + batched_inputs: Vec, + active_requests: &mut [ActiveEmbeddingRequest], + request_steps: &mut [EmbeddingRequestStep], + ) { + let agent_name = self.scheduler_context.agent_name.as_deref(); + + for BatchedEmbeddingInput { + input, + request_index, + sequence_id, + } in batched_inputs + { + if request_steps[request_index] == EmbeddingRequestStep::Ended { + continue; + } + + let active_request = &mut active_requests[request_index]; + let delivery = self + .embedding_of(&input, sequence_id, &active_request.normalization_method) + .and_then(|embedding| { + active_request + .generated_embedding_tx + .send(EmbeddingResult::Embedding(embedding)) + .map_err(EmbeddingError::ClientDisconnected) + }); + + match delivery { + Ok(()) => active_request.embeddings_emitted += 1, + Err(delivery_error) => { + delivery_error.report(agent_name, &active_request.generated_embedding_tx); + request_steps[request_index] = EmbeddingRequestStep::Ended; + } + } + } + } + + fn embedding_of( + &self, + input: &EmbeddingInputTokenized, + sequence_id: i32, + normalization_method: &EmbeddingNormalizationMethod, + ) -> Result { + let embedding = self + .llama_context + .embeddings_seq_ith(sequence_id) + .map_err(EmbeddingError::EmbeddingsUnavailable)?; + + Ok(Embedding { + embedding: EmbeddingVector::from( + self.normalization + .normalize(embedding.to_vec(), normalization_method)?, + ), + normalization_method: normalization_method.clone(), + pooling_type: self.scheduler_context.pooling_type.clone(), + source_document_id: input.id.clone(), + }) + } +} diff --git a/paddler_agent_embeddings/src/embedding_error.rs b/paddler_agent_embeddings/src/embedding_error.rs new file mode 100644 index 000000000..39e456bf8 --- /dev/null +++ b/paddler_agent_embeddings/src/embedding_error.rs @@ -0,0 +1,209 @@ +use std::num::TryFromIntError; + +use llama_cpp_bindings::batch_add_error::BatchAddError; +use llama_cpp_bindings::error::DecodeError; +use llama_cpp_bindings::error::EmbeddingsError; +use llama_cpp_bindings::error::StringToTokenError; +use log::error; +use tokio::sync::mpsc; +use tokio::sync::mpsc::error::SendError; + +use paddler_agent_runtime::agent_runtime_error::AgentRuntimeError; +use paddler_agent_runtime::rejection_description::rejection_description; +use paddler_agent_runtime::send_result_or_warn::send_result_or_warn; +use paddler_messaging::embedding_result::EmbeddingResult; + +#[derive(Debug, thiserror::Error)] +pub enum EmbeddingError { + #[error(transparent)] + AgentRuntime(#[from] AgentRuntimeError), + + #[error("failed to tokenize embedding input {source_document_id:?}: {source}")] + InputTokenizationFailed { + source_document_id: String, + #[source] + source: StringToTokenError, + }, + + #[error("no model is loaded")] + ModelNotLoaded, + + #[error("the scheduler is no longer accepting requests")] + SchedulerUnavailable, + + #[error("failed to add an embedding input to the batch: {0}")] + BatchAssemblyFailed(#[source] BatchAddError), + + #[error("the client stopped listening for embeddings: {0}")] + ClientDisconnected(#[source] SendError), + + #[error("llama.cpp rejected the embedding batch: {0}")] + DecodeFailed(#[source] DecodeError), + + #[error("{dimensions}-dimensional embedding is too long for RMS normalization: {source}")] + EmbeddingTooLongForRmsNormalization { + dimensions: usize, + #[source] + source: TryFromIntError, + }, + + #[error("failed to read the embedding of a batch sequence: {0}")] + EmbeddingsUnavailable(#[source] EmbeddingsError), +} + +impl EmbeddingError { + pub fn report( + self, + agent_name: Option<&str>, + generated_embedding_tx: &mpsc::UnboundedSender, + ) { + self.report_to_each(agent_name, [generated_embedding_tx]); + } + + pub fn report_to_each<'sender>( + self, + agent_name: Option<&str>, + generated_embedding_txs: impl IntoIterator< + Item = &'sender mpsc::UnboundedSender, + >, + ) { + let message = rejection_description(agent_name, &self); + + error!("{message}"); + + let result = match self { + Self::AgentRuntime(_) => EmbeddingResult::AgentRuntimeFailed(message), + Self::BatchAssemblyFailed(_) => EmbeddingResult::BatchAssemblyFailed(message), + Self::ClientDisconnected(_) => return, + Self::DecodeFailed(_) => EmbeddingResult::DecodeFailed(message), + Self::EmbeddingTooLongForRmsNormalization { .. } => { + EmbeddingResult::EmbeddingTooLongForRmsNormalization(message) + } + Self::EmbeddingsUnavailable(_) => EmbeddingResult::EmbeddingsUnavailable(message), + Self::InputTokenizationFailed { .. } => { + EmbeddingResult::InputTokenizationFailed(message) + } + Self::ModelNotLoaded => EmbeddingResult::ModelNotLoaded(message), + Self::SchedulerUnavailable => EmbeddingResult::SchedulerUnavailable(message), + }; + + for generated_embedding_tx in generated_embedding_txs { + send_result_or_warn(agent_name, generated_embedding_tx, result.clone()); + } + } +} + +#[cfg(test)] +mod tests { + use std::ffi::CString; + use std::io; + use std::mem::discriminant; + + use llama_cpp_bindings::batch_add_error::BatchAddError; + use llama_cpp_bindings::error::DecodeError; + use llama_cpp_bindings::error::EmbeddingsError; + use llama_cpp_bindings::error::StringToTokenError; + use tokio::sync::mpsc; + use tokio::sync::mpsc::error::SendError; + use tokio::sync::mpsc::error::TryRecvError; + + use paddler_agent_runtime::agent_runtime_error::AgentRuntimeError; + use paddler_messaging::embedding_result::EmbeddingResult; + + use super::EmbeddingError; + + #[test] + fn every_embedding_failure_reports_its_own_embedding_result() { + let description = String::new; + let failures = [ + ( + EmbeddingError::AgentRuntime(AgentRuntimeError::AvailableParallelismUnknown( + io::Error::other("parallelism unknown"), + )), + EmbeddingResult::AgentRuntimeFailed(description()), + ), + ( + EmbeddingError::BatchAssemblyFailed(BatchAddError::EmptyBuffer), + EmbeddingResult::BatchAssemblyFailed(description()), + ), + ( + EmbeddingError::DecodeFailed(DecodeError::NoKvCacheSlot), + EmbeddingResult::DecodeFailed(description()), + ), + ( + EmbeddingError::EmbeddingTooLongForRmsNormalization { + dimensions: usize::MAX, + source: u16::try_from(usize::MAX).unwrap_err(), + }, + EmbeddingResult::EmbeddingTooLongForRmsNormalization(description()), + ), + ( + EmbeddingError::EmbeddingsUnavailable(EmbeddingsError::NextnEmbeddingsNotEnabled), + EmbeddingResult::EmbeddingsUnavailable(description()), + ), + ( + EmbeddingError::InputTokenizationFailed { + source_document_id: "document".to_owned(), + source: StringToTokenError::NulError(CString::new("a \0 byte").unwrap_err()), + }, + EmbeddingResult::InputTokenizationFailed(description()), + ), + ( + EmbeddingError::ModelNotLoaded, + EmbeddingResult::ModelNotLoaded(description()), + ), + ( + EmbeddingError::SchedulerUnavailable, + EmbeddingResult::SchedulerUnavailable(description()), + ), + ]; + + for (embedding_error, expected_result) in failures { + let (generated_embedding_tx, mut generated_embedding_rx) = mpsc::unbounded_channel(); + + embedding_error.report(None, &generated_embedding_tx); + + assert_eq!( + generated_embedding_rx.try_recv().as_ref().map(discriminant), + Ok(discriminant(&expected_result)) + ); + } + } + + #[test] + fn sends_nothing_to_a_client_that_already_stopped_listening() { + let (generated_embedding_tx, mut generated_embedding_rx) = mpsc::unbounded_channel(); + + EmbeddingError::ClientDisconnected(SendError(EmbeddingResult::Done)) + .report(Some("agent"), &generated_embedding_tx); + + assert_eq!(generated_embedding_rx.try_recv(), Err(TryRecvError::Empty)); + } + + #[test] + fn tolerates_a_disconnected_client() { + let (generated_embedding_tx, generated_embedding_rx) = mpsc::unbounded_channel(); + + drop(generated_embedding_rx); + + EmbeddingError::SchedulerUnavailable.report(None, &generated_embedding_tx); + + assert!(generated_embedding_tx.is_closed()); + } + + #[test] + fn reports_one_failure_to_every_request_it_affects() { + let (first_embedding_tx, mut first_embedding_rx) = mpsc::unbounded_channel(); + let (second_embedding_tx, mut second_embedding_rx) = mpsc::unbounded_channel(); + + EmbeddingError::DecodeFailed(DecodeError::NoKvCacheSlot) + .report_to_each(None, [&first_embedding_tx, &second_embedding_tx]); + + let expected_result = Ok(EmbeddingResult::DecodeFailed( + EmbeddingError::DecodeFailed(DecodeError::NoKvCacheSlot).to_string(), + )); + + assert_eq!(first_embedding_rx.try_recv(), expected_result); + assert_eq!(second_embedding_rx.try_recv(), expected_result); + } +} diff --git a/paddler_agent/src/embedding_input_tokenized.rs b/paddler_agent_embeddings/src/embedding_input_tokenized.rs similarity index 80% rename from paddler_agent/src/embedding_input_tokenized.rs rename to paddler_agent_embeddings/src/embedding_input_tokenized.rs index 28653f8d6..daf6310e7 100644 --- a/paddler_agent/src/embedding_input_tokenized.rs +++ b/paddler_agent_embeddings/src/embedding_input_tokenized.rs @@ -1,5 +1,6 @@ use llama_cpp_bindings::token::LlamaToken; +#[derive(Debug, Eq, PartialEq)] pub struct EmbeddingInputTokenized { pub id: String, pub tokens: Vec, diff --git a/paddler_agent_embeddings/src/embedding_llama_context_settings.rs b/paddler_agent_embeddings/src/embedding_llama_context_settings.rs new file mode 100644 index 000000000..87e19fce2 --- /dev/null +++ b/paddler_agent_embeddings/src/embedding_llama_context_settings.rs @@ -0,0 +1,43 @@ +use paddler_agent_runtime::agent_runtime_error::AgentRuntimeError; +use paddler_agent_runtime::inference_runtime_context::InferenceRuntimeContext; +use paddler_agent_runtime::llama_context_settings::LlamaContextSettings; +use paddler_agent_runtime::loaded_llama_model::LoadedLlamaModel; +use paddler_agent_runtime::resident_llama_context::ResidentLlamaContext; +use paddler_inference_parameters::pooling_type::PoolingType; +use paddler_messaging::inference_capability::InferenceCapability; + +use crate::agent_pooling_type::AgentPoolingType; +use crate::converts_to_llama_pooling_type::ConvertsToLlamaPoolingType as _; + +pub struct EmbeddingLlamaContextSettings<'parameters> { + pub llama_context_settings: LlamaContextSettings<'parameters>, + pub pooling_type: PoolingType, +} + +impl EmbeddingLlamaContextSettings<'_> { + pub fn create_llama_context<'model>( + self, + loaded_llama_model: &'model LoadedLlamaModel, + inference_runtime_context: &InferenceRuntimeContext, + ) -> Result, AgentRuntimeError> { + let Self { + llama_context_settings, + pooling_type, + } = self; + let n_ubatch = llama_context_settings + .model_runtime_parameters + .n_batch + .tokens() + .get(); + + loaded_llama_model.create_llama_context( + inference_runtime_context, + InferenceCapability::Embeddings, + llama_context_settings + .into_llama_context_params() + .with_embeddings(true) + .with_n_ubatch(n_ubatch) + .with_pooling_type(AgentPoolingType(pooling_type).to_llama_pooling_type()), + ) + } +} diff --git a/paddler_agent_embeddings/src/embedding_normalization.rs b/paddler_agent_embeddings/src/embedding_normalization.rs new file mode 100644 index 000000000..6b690b2ba --- /dev/null +++ b/paddler_agent_embeddings/src/embedding_normalization.rs @@ -0,0 +1,17 @@ +use paddler_messaging::embedding_normalization_method::EmbeddingNormalizationMethod; + +use crate::embedding_error::EmbeddingError; +use crate::normalization::normalize_embedding::normalize_embedding; +use crate::normalizes_embedding::NormalizesEmbedding; + +pub struct EmbeddingNormalization; + +impl NormalizesEmbedding for EmbeddingNormalization { + fn normalize( + &self, + embedding: Vec, + normalization_method: &EmbeddingNormalizationMethod, + ) -> Result, EmbeddingError> { + normalize_embedding(embedding, normalization_method) + } +} diff --git a/paddler_agent_embeddings/src/embedding_pipeline.rs b/paddler_agent_embeddings/src/embedding_pipeline.rs new file mode 100644 index 000000000..3e53c822a --- /dev/null +++ b/paddler_agent_embeddings/src/embedding_pipeline.rs @@ -0,0 +1,130 @@ +use std::sync::Arc; + +use llama_cpp_bindings::llama_batch::LlamaBatch; +use tokio::sync::mpsc::UnboundedSender; +use tokio::sync::mpsc::unbounded_channel; +use tokio_util::sync::CancellationToken; +use tokio_util::task::TaskTracker; + +use paddler_agent_runtime::continue_startup_unless_shutting_down::continue_startup_unless_shutting_down; +use paddler_agent_runtime::inference_runtime_context::InferenceRuntimeContext; +use paddler_agent_runtime::loaded_llama_model::LoadedLlamaModel; +use paddler_agent_runtime::run_scheduler_unless_startup_abandoned::run_scheduler_unless_startup_abandoned; +use paddler_agent_runtime::scheduler_handle::SchedulerHandle; +use paddler_agent_runtime::scheduler_inbox::SchedulerInbox; +use paddler_agent_runtime::scheduler_ready::SchedulerReady; +use paddler_agent_runtime::scheduler_request_preparer::SchedulerRequestPreparer; +use paddler_agent_runtime::start_scheduler_thread::start_scheduler_thread; +use paddler_agent_runtime::startup_outcome::StartupOutcome; +use paddler_inference_parameters::model_runtime_parameters::ModelRuntimeParameters; +use paddler_inference_parameters::pooling_type::PoolingType; + +use crate::embedding_batch_preparer::EmbeddingBatchPreparer; +use crate::embedding_error::EmbeddingError; +use crate::embedding_llama_context_settings::EmbeddingLlamaContextSettings; +use crate::embedding_scheduler::EmbeddingScheduler; +use crate::embedding_scheduler_context::EmbeddingSchedulerContext; +use crate::sequence_context_limit::SequenceContextLimit; + +pub struct EmbeddingPipeline { + pub inference_runtime_context: InferenceRuntimeContext, + pub loaded_llama_model: LoadedLlamaModel, + pub model_runtime_parameters: ModelRuntimeParameters, + pub pooling_type: PoolingType, +} + +impl EmbeddingPipeline { + pub async fn spawn( + self, + cancellation_token: &CancellationToken, + ) -> Result>, EmbeddingError> { + start_scheduler_thread( + cancellation_token, + move |agent_shutdown, scheduler_startup_tx| { + self.run_scheduler(agent_shutdown, scheduler_startup_tx) + }, + ) + .await + } + + fn run_scheduler( + self, + agent_shutdown: &CancellationToken, + scheduler_startup_tx: &UnboundedSender< + Result, EmbeddingError>, + >, + ) -> Result<(), EmbeddingError> { + let Self { + inference_runtime_context, + loaded_llama_model, + model_runtime_parameters, + pooling_type, + } = self; + let desired_slots_total = inference_runtime_context + .slot_aggregated_status + .desired_slots_total; + let n_batch = model_runtime_parameters.n_batch.tokens_usize(); + + continue_startup_unless_shutting_down(agent_shutdown, || { + let llama_context = EmbeddingLlamaContextSettings { + llama_context_settings: loaded_llama_model.llama_context_settings( + &model_runtime_parameters, + u32::from(desired_slots_total), + ), + pooling_type: pooling_type.clone(), + } + .create_llama_context(&loaded_llama_model, &inference_runtime_context)?; + let batch = LlamaBatch::new( + model_runtime_parameters.n_batch.capacity(), + i32::from(desired_slots_total), + ); + + continue_startup_unless_shutting_down(agent_shutdown, || { + loaded_llama_model + .warm_up_and_serve( + llama_context, + batch, + desired_slots_total, + |llama_context, batch| { + let (scheduler_message_tx, scheduler_message_rx) = unbounded_channel(); + + run_scheduler_unless_startup_abandoned( + scheduler_startup_tx, + Ok(SchedulerReady { + request_preparer: Arc::new(SchedulerRequestPreparer { + agent_name: inference_runtime_context.agent_name.clone(), + preparation: EmbeddingBatchPreparer { + loaded_llama_model: loaded_llama_model.clone(), + n_batch, + sequence_context_limit: SequenceContextLimit::of( + &llama_context, + ), + }, + preparation_tasks: TaskTracker::new(), + scheduler_message_tx, + }), + }), + || { + EmbeddingScheduler { + active_requests: Vec::new(), + agent_shutdown: agent_shutdown.clone(), + batch, + llama_context, + scheduler_context: EmbeddingSchedulerContext { + agent_name: inference_runtime_context.agent_name, + desired_slots_total, + n_batch, + pooling_type, + }, + scheduler_inbox: SchedulerInbox::new(scheduler_message_rx), + } + .run(); + }, + ); + }, + ) + .map_err(EmbeddingError::from) + }) + }) + } +} diff --git a/paddler_agent_embeddings/src/embedding_request_continuation.rs b/paddler_agent_embeddings/src/embedding_request_continuation.rs new file mode 100644 index 000000000..b73fd63c0 --- /dev/null +++ b/paddler_agent_embeddings/src/embedding_request_continuation.rs @@ -0,0 +1,20 @@ +use std::option::IntoIter; + +use crate::active_embedding_request::ActiveEmbeddingRequest; + +pub enum EmbeddingRequestContinuation { + Concluded, + Continues(ActiveEmbeddingRequest), +} + +impl IntoIterator for EmbeddingRequestContinuation { + type IntoIter = IntoIter; + type Item = ActiveEmbeddingRequest; + + fn into_iter(self) -> Self::IntoIter { + match self { + Self::Concluded => None.into_iter(), + Self::Continues(active_request) => Some(active_request).into_iter(), + } + } +} diff --git a/paddler_agent_embeddings/src/embedding_request_step.rs b/paddler_agent_embeddings/src/embedding_request_step.rs new file mode 100644 index 000000000..3e242db16 --- /dev/null +++ b/paddler_agent_embeddings/src/embedding_request_step.rs @@ -0,0 +1,5 @@ +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum EmbeddingRequestStep { + Continuing, + Ended, +} diff --git a/paddler_agent_embeddings/src/embedding_scheduler.rs b/paddler_agent_embeddings/src/embedding_scheduler.rs new file mode 100644 index 000000000..faa9a9c2f --- /dev/null +++ b/paddler_agent_embeddings/src/embedding_scheduler.rs @@ -0,0 +1,92 @@ +use std::mem::take; + +use llama_cpp_bindings::llama_batch::LlamaBatch; +use log::info; +use tokio_util::sync::CancellationToken; + +use paddler_agent_runtime::resident_llama_context::ResidentLlamaContext; +use paddler_agent_runtime::scheduler_inbox::SchedulerInbox; + +use crate::active_embedding_request::ActiveEmbeddingRequest; +use crate::embedding_batch_processor::EmbeddingBatchProcessor; +use crate::embedding_error::EmbeddingError; +use crate::embedding_normalization::EmbeddingNormalization; +use crate::embedding_scheduler_context::EmbeddingSchedulerContext; +use crate::prepared_embedding_batch_request::PreparedEmbeddingBatchRequest; + +pub struct EmbeddingScheduler<'model> { + pub active_requests: Vec, + pub agent_shutdown: CancellationToken, + pub batch: LlamaBatch<'static>, + pub llama_context: ResidentLlamaContext<'model>, + pub scheduler_context: EmbeddingSchedulerContext, + pub scheduler_inbox: SchedulerInbox, +} + +impl EmbeddingScheduler<'_> { + pub fn run(&mut self) { + info!( + "{:?}: embedding scheduler started", + self.scheduler_context.agent_name + ); + + while !self.agent_shutdown.is_cancelled() { + if self.active_requests.is_empty() { + if self.scheduler_inbox.shutdown_requested { + break; + } + + self.wait_for_next_scheduler_message(); + } else { + self.accept_new_scheduler_messages(); + } + + let active_requests = take(&mut self.active_requests); + + self.active_requests = EmbeddingBatchProcessor { + batch: &mut self.batch, + llama_context: &mut self.llama_context, + normalization: EmbeddingNormalization, + scheduler_context: &self.scheduler_context, + } + .embed_next_batch(active_requests); + } + + self.reject_unfinished_requests(); + self.llama_context.synchronize(); + self.llama_context.detach_threadpool(); + + info!( + "{:?}: embedding scheduler stopped", + self.scheduler_context.agent_name + ); + } + + fn accept_new_scheduler_messages(&mut self) { + while let Some(request) = self.scheduler_inbox.take_next_waiting_command() { + self.admit(request); + } + } + + fn admit(&mut self, request: PreparedEmbeddingBatchRequest) { + self.active_requests + .extend(request.admit(self.scheduler_context.agent_name.as_deref())); + } + + fn reject_unfinished_requests(&mut self) { + let agent_name = self.scheduler_context.agent_name.as_deref(); + + for active_request in self.active_requests.drain(..) { + EmbeddingError::SchedulerUnavailable + .report(agent_name, &active_request.generated_embedding_tx); + } + + self.scheduler_inbox.reject_waiting_commands(agent_name); + } + + fn wait_for_next_scheduler_message(&mut self) { + if let Some(request) = self.scheduler_inbox.wait_for_next_command() { + self.admit(request); + } + } +} diff --git a/paddler_agent_embeddings/src/embedding_scheduler_context.rs b/paddler_agent_embeddings/src/embedding_scheduler_context.rs new file mode 100644 index 000000000..f4dcf9069 --- /dev/null +++ b/paddler_agent_embeddings/src/embedding_scheduler_context.rs @@ -0,0 +1,8 @@ +use paddler_inference_parameters::pooling_type::PoolingType; + +pub struct EmbeddingSchedulerContext { + pub agent_name: Option, + pub desired_slots_total: u16, + pub n_batch: usize, + pub pooling_type: PoolingType, +} diff --git a/paddler_agent_embeddings/src/lib.rs b/paddler_agent_embeddings/src/lib.rs new file mode 100644 index 000000000..b424b9236 --- /dev/null +++ b/paddler_agent_embeddings/src/lib.rs @@ -0,0 +1,21 @@ +pub mod active_embedding_request; +pub mod agent_pooling_type; +pub mod batched_embedding_input; +pub mod converts_to_llama_pooling_type; +pub mod embedding_batch_preparer; +pub mod embedding_batch_processor; +pub mod embedding_error; +pub mod embedding_input_tokenized; +pub mod embedding_llama_context_settings; +pub mod embedding_normalization; +pub mod embedding_pipeline; +pub mod embedding_request_continuation; +pub mod embedding_request_step; +pub mod embedding_scheduler; +pub mod embedding_scheduler_context; +pub mod next_embedding_batch; +pub mod normalization; +pub mod normalizes_embedding; +pub mod prepared_embedding_batch_request; +pub mod sequence_context_limit; +pub mod unembeddable_embedding_document; diff --git a/paddler_agent_embeddings/src/next_embedding_batch.rs b/paddler_agent_embeddings/src/next_embedding_batch.rs new file mode 100644 index 000000000..b7f74254e --- /dev/null +++ b/paddler_agent_embeddings/src/next_embedding_batch.rs @@ -0,0 +1,179 @@ +use crate::active_embedding_request::ActiveEmbeddingRequest; +use crate::batched_embedding_input::BatchedEmbeddingInput; + +#[must_use] +pub fn next_embedding_batch( + active_requests: &mut [ActiveEmbeddingRequest], + n_batch: usize, + sequences_in_batch: u16, +) -> Vec { + let mut batched_inputs = Vec::new(); + let mut batched_tokens = 0; + let mut next_sequence_id: u16 = 0; + + for (request_index, active_request) in active_requests.iter_mut().enumerate() { + let mut request_sequences = 0; + + while request_sequences < active_request.sequences() + && next_sequence_id < sequences_in_batch + { + let Some(input) = active_request.pending_inputs.pop_front() else { + break; + }; + + if batched_tokens + input.tokens.len() > n_batch { + active_request.pending_inputs.push_front(input); + + break; + } + + batched_tokens += input.tokens.len(); + batched_inputs.push(BatchedEmbeddingInput { + input, + request_index, + sequence_id: i32::from(next_sequence_id), + }); + request_sequences += 1; + next_sequence_id += 1; + } + } + + batched_inputs +} + +#[cfg(test)] +mod tests { + use std::collections::VecDeque; + use std::num::NonZeroU16; + use std::sync::Arc; + + use llama_cpp_bindings::token::LlamaToken; + use tokio::sync::mpsc; + + use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; + use paddler_agent_status::slot_guard::SlotGuard; + use paddler_messaging::embedding_normalization_method::EmbeddingNormalizationMethod; + + use super::next_embedding_batch; + use crate::active_embedding_request::ActiveEmbeddingRequest; + use crate::batched_embedding_input::BatchedEmbeddingInput; + use crate::embedding_input_tokenized::EmbeddingInputTokenized; + use crate::prepared_embedding_batch_request::PreparedEmbeddingBatchRequest; + + fn document(id: &str, tokens: usize) -> EmbeddingInputTokenized { + EmbeddingInputTokenized { + id: id.to_owned(), + tokens: vec![LlamaToken::new(0); tokens], + } + } + + fn active_request( + sequences: u16, + inputs: Vec, + ) -> ActiveEmbeddingRequest { + let (generated_embedding_tx, _generated_embedding_rx) = mpsc::unbounded_channel(); + let (_generate_embedding_stop_tx, generate_embedding_stop_rx) = mpsc::unbounded_channel(); + + PreparedEmbeddingBatchRequest { + generate_embedding_stop_rx, + generated_embedding_tx, + inputs, + normalization_method: EmbeddingNormalizationMethod::None, + unembeddable_documents: Vec::new(), + slot_guard: SlotGuard::new( + Arc::new(SlotAggregatedStatus::new(8)), + NonZeroU16::new(sequences).unwrap(), + ), + } + .admit(None) + .into_iter() + .next() + .expect("a request with inputs to embed must stay active") + } + + fn batched( + request_index: usize, + input: EmbeddingInputTokenized, + sequence_id: i32, + ) -> BatchedEmbeddingInput { + BatchedEmbeddingInput { + input, + request_index, + sequence_id, + } + } + + #[test] + fn every_request_contributes_up_to_its_reserved_sequences() { + let mut active_requests = [ + active_request(1, vec![document("a1", 1), document("a2", 1)]), + active_request( + 2, + vec![document("b1", 1), document("b2", 1), document("b3", 1)], + ), + ]; + + assert_eq!( + next_embedding_batch(&mut active_requests, 100, 8), + [ + batched(0, document("a1", 1), 0), + batched(1, document("b1", 1), 1), + batched(1, document("b2", 1), 2), + ] + ); + assert_eq!( + active_requests[1].pending_inputs, + VecDeque::from([document("b3", 1)]) + ); + } + + #[test] + fn a_request_without_enough_documents_leaves_its_reserved_sequences_unused() { + let mut active_requests = [ + active_request(3, vec![document("a1", 1)]), + active_request(1, vec![document("b1", 1)]), + ]; + + assert_eq!( + next_embedding_batch(&mut active_requests, 100, 8), + [ + batched(0, document("a1", 1), 0), + batched(1, document("b1", 1), 1) + ] + ); + } + + #[test] + fn a_document_that_does_not_fit_the_remaining_tokens_waits_for_the_next_batch() { + let mut active_requests = [ + active_request(1, vec![document("a1", 6)]), + active_request(2, vec![document("b1", 5), document("b2", 3)]), + ]; + + assert_eq!( + next_embedding_batch(&mut active_requests, 10, 8), + [batched(0, document("a1", 6), 0)] + ); + assert_eq!( + active_requests[1].pending_inputs, + VecDeque::from([document("b1", 5), document("b2", 3)]) + ); + } + + #[test] + fn a_batch_holds_no_more_sequences_than_the_context_serves() { + let mut active_requests = [ + active_request(2, vec![document("a1", 1), document("a2", 1)]), + active_request(2, vec![document("b1", 1), document("b2", 1)]), + ]; + + assert_eq!( + next_embedding_batch(&mut active_requests, 100, 3), + [ + batched(0, document("a1", 1), 0), + batched(0, document("a2", 1), 1), + batched(1, document("b1", 1), 2), + ] + ); + } +} diff --git a/paddler_agent/src/normalization/l2.rs b/paddler_agent_embeddings/src/normalization/l2.rs similarity index 100% rename from paddler_agent/src/normalization/l2.rs rename to paddler_agent_embeddings/src/normalization/l2.rs diff --git a/paddler_agent/src/normalization/mod.rs b/paddler_agent_embeddings/src/normalization/mod.rs similarity index 100% rename from paddler_agent/src/normalization/mod.rs rename to paddler_agent_embeddings/src/normalization/mod.rs diff --git a/paddler_agent/src/normalization/normalize_embedding.rs b/paddler_agent_embeddings/src/normalization/normalize_embedding.rs similarity index 86% rename from paddler_agent/src/normalization/normalize_embedding.rs rename to paddler_agent_embeddings/src/normalization/normalize_embedding.rs index 67a8c774f..3d031ce7c 100644 --- a/paddler_agent/src/normalization/normalize_embedding.rs +++ b/paddler_agent_embeddings/src/normalization/normalize_embedding.rs @@ -1,19 +1,19 @@ use paddler_messaging::embedding_normalization_method::EmbeddingNormalizationMethod; -use crate::embedding_batch_rejection::EmbeddingBatchRejection; +use crate::embedding_error::EmbeddingError; use crate::normalization::l2::l2; use crate::normalization::rms_norm::rms_norm; pub fn normalize_embedding( mut embedding: Vec, normalization_method: &EmbeddingNormalizationMethod, -) -> Result, EmbeddingBatchRejection> { +) -> Result, EmbeddingError> { match normalization_method { EmbeddingNormalizationMethod::None => {} EmbeddingNormalizationMethod::L2 => l2(&mut embedding), EmbeddingNormalizationMethod::RmsNorm { epsilon } => { rms_norm(&mut embedding, *epsilon).map_err(|source| { - EmbeddingBatchRejection::EmbeddingTooLongForRmsNormalization { + EmbeddingError::EmbeddingTooLongForRmsNormalization { dimensions: embedding.len(), source, } @@ -29,7 +29,7 @@ mod tests { use paddler_messaging::embedding_normalization_method::EmbeddingNormalizationMethod; use super::normalize_embedding; - use crate::embedding_batch_rejection::EmbeddingBatchRejection; + use crate::embedding_error::EmbeddingError; #[test] fn scales_the_embedding_to_unit_length_with_l2() { @@ -68,7 +68,7 @@ mod tests { assert!(matches!( result, - Err(EmbeddingBatchRejection::EmbeddingTooLongForRmsNormalization { + Err(EmbeddingError::EmbeddingTooLongForRmsNormalization { dimensions, .. }) if dimensions == oversized_length diff --git a/paddler_agent/src/normalization/rms_norm.rs b/paddler_agent_embeddings/src/normalization/rms_norm.rs similarity index 98% rename from paddler_agent/src/normalization/rms_norm.rs rename to paddler_agent_embeddings/src/normalization/rms_norm.rs index a742923e8..184432b7c 100644 --- a/paddler_agent/src/normalization/rms_norm.rs +++ b/paddler_agent_embeddings/src/normalization/rms_norm.rs @@ -89,7 +89,7 @@ mod tests { let mut embedding: Vec = Vec::new(); rms_norm(&mut embedding, 0.0).unwrap(); - assert!(embedding.is_empty()); + assert_eq!(embedding, Vec::::new()); } #[test] diff --git a/paddler_agent_embeddings/src/normalizes_embedding.rs b/paddler_agent_embeddings/src/normalizes_embedding.rs new file mode 100644 index 000000000..eb9296525 --- /dev/null +++ b/paddler_agent_embeddings/src/normalizes_embedding.rs @@ -0,0 +1,11 @@ +use paddler_messaging::embedding_normalization_method::EmbeddingNormalizationMethod; + +use crate::embedding_error::EmbeddingError; + +pub trait NormalizesEmbedding { + fn normalize( + &self, + embedding: Vec, + normalization_method: &EmbeddingNormalizationMethod, + ) -> Result, EmbeddingError>; +} diff --git a/paddler_agent_embeddings/src/prepared_embedding_batch_request.rs b/paddler_agent_embeddings/src/prepared_embedding_batch_request.rs new file mode 100644 index 000000000..74dc90735 --- /dev/null +++ b/paddler_agent_embeddings/src/prepared_embedding_batch_request.rs @@ -0,0 +1,195 @@ +use log::warn; +use tokio::sync::mpsc; + +use paddler_agent_runtime::scheduler_command::SchedulerCommand; +use paddler_agent_status::slot_guard::SlotGuard; +use paddler_messaging::embedding_normalization_method::EmbeddingNormalizationMethod; +use paddler_messaging::embedding_result::EmbeddingResult; + +use crate::active_embedding_request::ActiveEmbeddingRequest; +use crate::embedding_error::EmbeddingError; +use crate::embedding_input_tokenized::EmbeddingInputTokenized; +use crate::embedding_request_continuation::EmbeddingRequestContinuation; +use crate::unembeddable_embedding_document::UnembeddableEmbeddingDocument; + +pub struct PreparedEmbeddingBatchRequest { + pub generate_embedding_stop_rx: mpsc::UnboundedReceiver<()>, + pub generated_embedding_tx: mpsc::UnboundedSender, + pub inputs: Vec, + pub normalization_method: EmbeddingNormalizationMethod, + pub unembeddable_documents: Vec, + pub slot_guard: SlotGuard, +} + +impl PreparedEmbeddingBatchRequest { + #[must_use] + pub fn admit(self, agent_name: Option<&str>) -> EmbeddingRequestContinuation { + let Self { + generate_embedding_stop_rx, + generated_embedding_tx, + inputs, + normalization_method, + unembeddable_documents, + slot_guard, + } = self; + + for unembeddable_document in unembeddable_documents { + warn!("{agent_name:?}: skipped an unembeddable document: {unembeddable_document:?}"); + + if let Err(send_error) = + generated_embedding_tx.send(unembeddable_document.into_embedding_result()) + { + EmbeddingError::ClientDisconnected(send_error) + .report(agent_name, &generated_embedding_tx); + + return EmbeddingRequestContinuation::Concluded; + } + } + + ActiveEmbeddingRequest { + embeddings_emitted: 0, + generate_embedding_stop_rx, + generated_embedding_tx, + normalization_method, + pending_inputs: inputs.into(), + slot_guard, + } + .continue_unless_every_input_is_embedded(agent_name) + } +} + +impl SchedulerCommand for PreparedEmbeddingBatchRequest { + fn reject_because_the_scheduler_stopped(self, agent_name: Option<&str>) { + EmbeddingError::SchedulerUnavailable.report(agent_name, &self.generated_embedding_tx); + } +} + +#[cfg(test)] +mod tests { + use std::mem::discriminant; + use std::num::NonZeroU16; + use std::sync::Arc; + + use llama_cpp_bindings::token::LlamaToken; + use tokio::sync::mpsc; + use tokio::sync::mpsc::error::TryRecvError; + + use paddler_agent_runtime::scheduler_command::SchedulerCommand as _; + use paddler_agent_status::slot_aggregated_status::SlotAggregatedStatus; + use paddler_agent_status::slot_guard::SlotGuard; + use paddler_messaging::embedding_normalization_method::EmbeddingNormalizationMethod; + use paddler_messaging::embedding_result::EmbeddingResult; + use paddler_messaging::oversized_embedding_document_details::OversizedEmbeddingDocumentDetails; + + use super::PreparedEmbeddingBatchRequest; + use crate::embedding_input_tokenized::EmbeddingInputTokenized; + use crate::embedding_request_continuation::EmbeddingRequestContinuation; + use crate::unembeddable_embedding_document::UnembeddableEmbeddingDocument; + + fn oversized_document_details() -> OversizedEmbeddingDocumentDetails { + OversizedEmbeddingDocumentDetails { + document_tokens: 4096, + n_batch: 2048, + source_document_id: "oversized".to_owned(), + } + } + + fn prepared_request( + generated_embedding_tx: mpsc::UnboundedSender, + inputs: Vec, + ) -> PreparedEmbeddingBatchRequest { + let (_generate_embedding_stop_tx, generate_embedding_stop_rx) = mpsc::unbounded_channel(); + + PreparedEmbeddingBatchRequest { + generate_embedding_stop_rx, + generated_embedding_tx, + inputs, + normalization_method: EmbeddingNormalizationMethod::None, + unembeddable_documents: vec![UnembeddableEmbeddingDocument::ExceedsBatchSize( + oversized_document_details(), + )], + slot_guard: SlotGuard::new(Arc::new(SlotAggregatedStatus::new(1)), NonZeroU16::MIN), + } + } + + #[test] + fn rejects_an_embedding_batch_when_the_scheduler_stopped() { + let (generated_embedding_tx, mut generated_embedding_rx) = mpsc::unbounded_channel(); + + prepared_request(generated_embedding_tx, Vec::new()) + .reject_because_the_scheduler_stopped(Some("agent")); + + assert_eq!( + generated_embedding_rx.try_recv(), + Ok(EmbeddingResult::SchedulerUnavailable( + "agent: the scheduler is no longer accepting requests".to_owned() + )) + ); + } + + #[test] + fn a_batch_of_only_oversized_documents_reports_them_and_produces_no_embeddings() { + let (generated_embedding_tx, mut generated_embedding_rx) = mpsc::unbounded_channel(); + + assert_eq!( + discriminant(&prepared_request(generated_embedding_tx, Vec::new()).admit(None)), + discriminant(&EmbeddingRequestContinuation::Concluded) + ); + assert_eq!( + generated_embedding_rx.try_recv(), + Ok(EmbeddingResult::DocumentExceedsBatchSize( + oversized_document_details() + )) + ); + assert_eq!( + generated_embedding_rx.try_recv(), + Ok(EmbeddingResult::NoEmbeddingsProduced) + ); + assert_eq!( + generated_embedding_rx.try_recv(), + Err(TryRecvError::Disconnected) + ); + } + + #[test] + fn a_client_that_stopped_listening_concludes_before_its_documents_are_embedded() { + let (generated_embedding_tx, generated_embedding_rx) = mpsc::unbounded_channel(); + + drop(generated_embedding_rx); + + assert_eq!( + discriminant( + &prepared_request( + generated_embedding_tx, + vec![EmbeddingInputTokenized { + id: "document".to_owned(), + tokens: vec![LlamaToken::new(0)], + }], + ) + .admit(None) + ), + discriminant(&EmbeddingRequestContinuation::Concluded) + ); + } + + #[test] + fn a_batch_with_documents_to_embed_stays_open_after_reporting_its_oversized_documents() { + let (generated_embedding_tx, mut generated_embedding_rx) = mpsc::unbounded_channel(); + let _continuation = prepared_request( + generated_embedding_tx, + vec![EmbeddingInputTokenized { + id: "document".to_owned(), + tokens: vec![LlamaToken::new(0)], + }], + ) + .admit(None); + + assert_eq!( + generated_embedding_rx.try_recv(), + Ok(EmbeddingResult::DocumentExceedsBatchSize( + oversized_document_details() + )) + ); + assert_eq!(generated_embedding_rx.try_recv(), Err(TryRecvError::Empty)); + } +} diff --git a/paddler_agent_embeddings/src/sequence_context_limit.rs b/paddler_agent_embeddings/src/sequence_context_limit.rs new file mode 100644 index 000000000..e1ae8076a --- /dev/null +++ b/paddler_agent_embeddings/src/sequence_context_limit.rs @@ -0,0 +1,18 @@ +use llama_cpp_bindings::context::LlamaContext; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum SequenceContextLimit { + Tokens(usize), + Unlimited, +} + +impl SequenceContextLimit { + #[must_use] + pub fn of(llama_context: &LlamaContext<'_>) -> Self { + if llama_context.has_memory() { + Self::Tokens(llama_context.n_ctx_seq() as usize) + } else { + Self::Unlimited + } + } +} diff --git a/paddler_agent_embeddings/src/unembeddable_embedding_document.rs b/paddler_agent_embeddings/src/unembeddable_embedding_document.rs new file mode 100644 index 000000000..1267b06fd --- /dev/null +++ b/paddler_agent_embeddings/src/unembeddable_embedding_document.rs @@ -0,0 +1,26 @@ +use paddler_messaging::embedding_document_exceeding_sequence_context_details::EmbeddingDocumentExceedingSequenceContextDetails; +use paddler_messaging::embedding_document_tokenized_to_nothing_details::EmbeddingDocumentTokenizedToNothingDetails; +use paddler_messaging::embedding_result::EmbeddingResult; +use paddler_messaging::oversized_embedding_document_details::OversizedEmbeddingDocumentDetails; + +#[derive(Debug)] +pub enum UnembeddableEmbeddingDocument { + ExceedsBatchSize(OversizedEmbeddingDocumentDetails), + ExceedsSequenceContext(EmbeddingDocumentExceedingSequenceContextDetails), + TokenizedToNothing(EmbeddingDocumentTokenizedToNothingDetails), +} + +impl UnembeddableEmbeddingDocument { + #[must_use] + pub fn into_embedding_result(self) -> EmbeddingResult { + match self { + Self::ExceedsBatchSize(details) => EmbeddingResult::DocumentExceedsBatchSize(details), + Self::ExceedsSequenceContext(details) => { + EmbeddingResult::DocumentExceedsSequenceContext(details) + } + Self::TokenizedToNothing(details) => { + EmbeddingResult::DocumentTokenizedToNothing(details) + } + } + } +} diff --git a/paddler_agent_pointer_head/Cargo.toml b/paddler_agent_pointer_head/Cargo.toml new file mode 100644 index 000000000..f9f32f878 --- /dev/null +++ b/paddler_agent_pointer_head/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "paddler_agent_pointer_head" +autotests = false +authors.workspace = true +description.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[[test]] +name = "integration" +path = "tests/main.rs" + +[dependencies] +icu_normalizer = { workspace = true } +llama-cpp-bindings = { workspace = true } +thiserror = { workspace = true } + +[features] +default = [] +cuda = ["llama-cpp-bindings/cuda"] +metal = ["llama-cpp-bindings/metal"] +vulkan = ["llama-cpp-bindings/vulkan"] + +[lints] +workspace = true diff --git a/paddler_agent_pointer_head/src/lib.rs b/paddler_agent_pointer_head/src/lib.rs new file mode 100644 index 000000000..c89686829 --- /dev/null +++ b/paddler_agent_pointer_head/src/lib.rs @@ -0,0 +1,5 @@ +pub mod pointer_head; +pub mod pointer_head_delimiters; +pub mod pointer_head_error; +pub mod pointer_head_input_normalization; +pub mod pointer_head_projection; diff --git a/paddler_agent_pointer_head/src/pointer_head.rs b/paddler_agent_pointer_head/src/pointer_head.rs new file mode 100644 index 000000000..602f20ffb --- /dev/null +++ b/paddler_agent_pointer_head/src/pointer_head.rs @@ -0,0 +1,213 @@ +use std::path::Path; + +use llama_cpp_bindings::gguf_context::GgufContext; +use llama_cpp_bindings::gguf_tensor_f32::GgufTensorF32; + +use crate::pointer_head_delimiters::PointerHeadDelimiters; +use crate::pointer_head_error::PointerHeadError; +use crate::pointer_head_input_normalization::PointerHeadInputNormalization; +use crate::pointer_head_projection::PointerHeadProjection; + +const TEMPERATURE_KEY: &str = "pointer_head.temperature"; +const TENSORS: [&str; 4] = [ + "pointer_head.key.bias", + "pointer_head.key.weight", + "pointer_head.query.bias", + "pointer_head.query.weight", +]; + +fn read_temperature(gguf_context: &GgufContext) -> Result { + let temperature = gguf_context + .find_key(TEMPERATURE_KEY) + .and_then(|key_id| gguf_context.val_f32(key_id)) + .map_err(|source| PointerHeadError::KeyUnreadable { + key: TEMPERATURE_KEY.to_owned(), + source, + })?; + + if temperature.is_finite() && temperature > 0.0 { + Ok(temperature) + } else { + Err(PointerHeadError::TemperatureOutOfRange { temperature }) + } +} + +fn softmax(logits: &[f32]) -> Vec { + let largest_logit = logits.iter().copied().fold(f32::NEG_INFINITY, f32::max); + let exponentials: Vec = logits + .iter() + .map(|logit| (logit - largest_logit).exp()) + .collect(); + let exponential_sum: f32 = exponentials.iter().sum(); + + exponentials + .iter() + .map(|exponential| exponential / exponential_sum) + .collect() +} + +pub struct PointerHead { + pub delimiters: PointerHeadDelimiters, + pub input_normalization: PointerHeadInputNormalization, + pub key_projection: PointerHeadProjection, + pub logit_scale: f32, + pub query_projection: PointerHeadProjection, + pub temperature: f32, +} + +impl PointerHead { + pub fn load(path: &Path) -> Result { + let gguf_context = + GgufContext::from_file(path).map_err(|source| PointerHeadError::FileUnreadable { + path: path.to_path_buf(), + source, + })?; + let temperature = read_temperature(&gguf_context)?; + let delimiters = PointerHeadDelimiters::load(&gguf_context)?; + let input_normalization = PointerHeadInputNormalization::load(&gguf_context)?; + let mut tensor_shapes = [[0; 4]; 4]; + let mut tensor_values: [Vec; 4] = Default::default(); + + for ((tensor, tensor_shape), tensor_value) in TENSORS + .iter() + .zip(&mut tensor_shapes) + .zip(&mut tensor_values) + { + let GgufTensorF32 { shape, values } = + gguf_context.read_tensor_f32(tensor).map_err(|source| { + PointerHeadError::TensorUnreadable { + tensor: (*tensor).to_owned(), + source, + } + })?; + + *tensor_shape = shape; + *tensor_value = values; + } + + let [key_bias_shape, key_weight_shape, _, _] = tensor_shapes; + let head_dim = key_bias_shape[0]; + let hidden_size = key_weight_shape[0]; + let bias_shape = [head_dim, 1, 1, 1]; + let weight_shape = [hidden_size, head_dim, 1, 1]; + + for ((tensor, actual), expected) in TENSORS.iter().zip(tensor_shapes).zip([ + bias_shape, + weight_shape, + bias_shape, + weight_shape, + ]) { + if actual != expected { + return Err(PointerHeadError::TensorShapeMismatch { + tensor: (*tensor).to_owned(), + expected, + actual, + }); + } + } + + let [key_bias, key_weight, query_bias, query_weight] = tensor_values; + + let logit_scale = + f32::from(u16::try_from(head_dim).map_err(|source| { + PointerHeadError::HeadDimensionOutOfRange { head_dim, source } + })?) + .sqrt() + .recip(); + + Ok(Self { + delimiters, + input_normalization, + key_projection: PointerHeadProjection { + bias: key_bias, + hidden_size: hidden_size as usize, + weight: key_weight, + }, + logit_scale, + query_projection: PointerHeadProjection { + bias: query_bias, + hidden_size: hidden_size as usize, + weight: query_weight, + }, + temperature, + }) + } + + #[must_use] + pub const fn hidden_size(&self) -> usize { + self.key_projection.hidden_size + } + + #[must_use] + pub fn option_probabilities(&self, output_hidden_states: &[Vec]) -> Vec { + let Some((decide_hidden_state, option_hidden_states)) = output_hidden_states.split_last() + else { + return Vec::new(); + }; + let query = self.query_projection.project(decide_hidden_state); + + softmax( + &option_hidden_states + .iter() + .map(|option_hidden_state| { + let key = self.key_projection.project(option_hidden_state); + let alignment: f32 = key + .iter() + .zip(&query) + .map(|(key_value, query_value)| key_value * query_value) + .sum(); + + alignment * self.logit_scale / self.temperature + }) + .collect::>(), + ) + } +} + +#[cfg(test)] +mod tests { + use super::PointerHead; + use crate::pointer_head_delimiters::PointerHeadDelimiters; + use crate::pointer_head_input_normalization::PointerHeadInputNormalization; + use crate::pointer_head_projection::PointerHeadProjection; + + fn identity_projection() -> PointerHeadProjection { + PointerHeadProjection { + bias: vec![0.0, 0.0], + hidden_size: 2, + weight: vec![1.0, 0.0, 0.0, 1.0], + } + } + + fn pointer_head() -> PointerHead { + PointerHead { + delimiters: PointerHeadDelimiters { + decide: "".to_owned(), + option_end: "".to_owned(), + option_start: "