rig 0.41.0

An opinionated library for building LLM powered applications.
Documentation
# Inspired by https://github.com/joshka/github-workflows/blob/main/.github/workflows/rust-check.yml
name: Lint & Test

on:
  pull_request:
    branches:
      - "**"
  merge_group:
  workflow_call:

env:
  CARGO_TERM_COLOR: always
  RUST_VERSION: 1.94.0

# ensure that the workflow is only triggered once per PR, subsequent pushes to the PR will cancel
# and restart the workflow. See https://docs.github.com/en/actions/using-jobs/using-concurrency
concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

jobs:
  fmt:
    name: stable / fmt
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Rust stable
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          components: rustfmt
          toolchain: ${{ env.RUST_VERSION }}

      - name: Run cargo fmt
        run: cargo fmt -- --check

      # Guard against CWD-relative test fixture paths. `cargo test` runs from the
      # crate root while `cargo nextest` (CI's runner) runs from the workspace
      # root, so a bare `tests/data/...` path passes under one and fails under the
      # other — letting a broken fixture path stay green in CI. Real test code
      # must anchor fixtures to CARGO_MANIFEST_DIR (see
      # crates/rig-core/src/loaders/test_fixtures.rs). Doc-comment example
      # snippets (`///`, `//!`) are exempt.
      - name: Check test fixture paths are CWD-independent
        run: |
          if grep -rnE '"(\./)?tests/data' crates/*/src crates/*/tests --include='*.rs' \
            | grep -vE ':[[:space:]]*//[/!]'; then
            echo "::error::Found a CWD-relative 'tests/data' path in test code. Anchor it to CARGO_MANIFEST_DIR via crate::loaders::test_fixtures (see crates/rig-core/src/loaders/test_fixtures.rs)."
            exit 1
          fi

      - name: Test WASM chat worker runtime
        run: node --test examples/candle_wasm_chat/www/worker-runtime.test.mjs

  # Special check to make sure rig-core is compatible with the wasm target
  check-wasm:
    name: stable / check rig-core wasm target
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Rust stable
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          target: wasm32-unknown-unknown
          toolchain: ${{ env.RUST_VERSION }}

      - name: Run cargo check wasm target
        run: cargo check --package rig-core --target wasm32-unknown-unknown

  # The runtime split adds wasm-sensitive code to rig-agent, feature forwarding
  # in the `rig` facade, and explicit feature wiring in the Candle runtime and
  # browser example, so check each on the wasm target too. These are plain
  # checks with no feature flags: the relaxed async bounds now follow from the
  # target alone, so building for `wasm32-unknown-unknown` is the whole opt-in.
  # None of them download a model.
  #
  # Each package must be checked **on its own**: cargo unifies features across
  # packages built in one invocation, so a workspace-wide `--all-features` build
  # (the doctest job) silently supplies features an individual manifest forgot to
  # declare. That is precisely how `candle_wasm_chat` came to declare
  # `default-features = false` without `agent` while importing `rig::agent` —
  # green in CI, broken standalone. It is also the only consumer that exercises
  # the browser-wasm *facade* path, and compiling it here means a
  # JavaScript-only worker test cannot hide a Rust build regression.
  #
  # `wasm32-unknown-unknown` (browser) is the only supported wasm target. See
  # crates/rig-agent/README.md for the matrix.
  check-wasm-runtimes:
    name: stable / check ${{ matrix.package }} wasm target
    runs-on: ubuntu-latest
    strategy:
      matrix:
        package: [rig-agent, rig, rig-candle, candle_wasm_chat]
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Rust stable
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          target: wasm32-unknown-unknown
          toolchain: ${{ env.RUST_VERSION }}

      - name: Run cargo check wasm target
        run: cargo check --package ${{ matrix.package }} --target wasm32-unknown-unknown

  # `rmcp` is native-only: rmcp's `ClientHandler` requires `Send + Sync`
  # unconditionally, which rig's wasm tool registry cannot satisfy. Asking for it
  # on wasm must fail with exactly one actionable sentence rather than a wall of
  # `dyn ErasedTool` trait errors, so assert both the message *and* the error
  # count — a new ungated `#[cfg(feature = "rmcp")]` would leak follow-on errors
  # and is the regression this guards.
  check-rmcp-native-only:
    name: stable / rmcp rejected on wasm
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Rust stable
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          target: wasm32-unknown-unknown
          toolchain: ${{ env.RUST_VERSION }}

      - name: Assert the native-only diagnostic is the only error
        run: |
          set +e
          out=$(cargo check --package rig-agent --features rmcp \
                  --target wasm32-unknown-unknown 2>&1)
          status=$?
          set -e
          echo "$out"
          # Parse a decolorized copy. The workflow sets CARGO_TERM_COLOR=always,
          # so rustc prefixes every diagnostic with SGR escapes and an anchored
          # `^error` matches nothing — the count came out 0 and the step failed
          # claiming the gate had leaked. The log above keeps its colors.
          plain=$(printf '%s\n' "$out" | perl -pe 's/\e\[[0-9;]*[a-zA-Z]//g')
          if [ $status -eq 0 ]; then
            echo "::error::expected the rmcp native-only compile_error, but the build succeeded — the gate is gone"
            exit 1
          fi
          if ! printf '%s\n' "$plain" | grep -q 'the `rmcp` feature is native-only'; then
            echo "::error::build failed for the wrong reason; the native-only compile_error did not fire"
            exit 1
          fi
          # Exclude cargo's trailing "could not compile ..." summary, which is
          # itself printed as an `error:` line.
          count=$(printf '%s\n' "$plain" | grep -E '^error(\[|:)' | grep -cv 'could not compile' || true)
          if [ "$count" -ne 1 ]; then
            echo "::error::expected exactly 1 error, got $count — an ungated \`#[cfg(feature = \"rmcp\")]\` is leaking follow-on errors"
            exit 1
          fi
          echo "ok: rmcp on wasm fails with exactly one actionable error"

  clippy:
    name: stable / clippy
    runs-on: ubuntu-latest
    permissions:
      checks: write
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Rust stable
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          components: clippy
          toolchain: ${{ env.RUST_VERSION }}

      # Required to compile rig-lancedb
      - name: Install Protoc
        run: sudo apt-get update && sudo apt-get install -y protobuf-compiler

      - name: Run clippy action
        uses: clechasseur/rs-clippy-check@v3
        with:
          args: --all-features --all-targets

  test:
    name: stable / test
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Rust stable
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          toolchain: ${{ env.RUST_VERSION }}

      - name: Install nextest
        uses: taiki-e/install-action@v2
        with:
          tool: nextest

      # Required to compile rig-lancedb
      - name: Install Protoc
        run: sudo apt-get update && sudo apt-get install -y protobuf-compiler

      # `nextest` below runs with `--all-features`, which does not compile the
      # default-feature test graph. Build the root integration tests with the
      # default feature set so feature-gated test modules cannot accidentally
      # depend on APIs that only exist under optional features.
      - name: Compile default-feature root test targets
        run: cargo test -p rig --tests --no-run

      - name: Test rig-agent agent unit tests with all features
        run: cargo nextest run -p rig-agent --all-features -E 'test(agent::)' --retries 2

      # `cargo nextest run` (below) selects the workspace's *default members*,
      # and this workspace's root manifest is itself the `rig` package with no
      # `default-members` — so that run covers `rig` alone and a `crates/*` unit
      # test executes in CI only when its package is named explicitly. The
      # rig-agent half of the completion-parent contract rides along on the
      # `agent::` filter above, but rig-core's `telemetry::` tests match nothing
      # and would otherwise never run. They are the drift tripwires for the
      # marker and the required `gen_ai.*` field set: unrun, the three forms of
      # the contract can silently desync, which is the exact failure the
      # `completion_parent_span!` macro exists to prevent.
      #
      # rig-agent is named explicitly even though `test(agent::)` above already
      # matches `span_safety_net::`, so the cross-crate tripwire keeps running
      # if that filter is ever narrowed. The tests are cheap and the duplicate
      # run is the price of not depending on an unrelated filter.
      #
      # No `--retries`, deliberately, unlike the steps around it: these are
      # static-metadata assertions (set equality against a `const`, field
      # counts, callsite dedup), so a pass on the second attempt would be
      # concealing nondeterminism rather than tolerating a flaky network.
      #
      # This resolves rig-core with its own `--all-features` set, a different
      # configuration from both the `-p rig-agent` step above and the root
      # `-p rig` run below, so rig-core is compiled here rather than reused.
      # That cost is accepted deliberately: folding these tests into the
      # `agent::` step would share one build but hand them its `--retries 2`,
      # and a drift tripwire that passes on the second attempt conceals exactly
      # the nondeterminism it exists to catch.
      - name: Test telemetry completion-parent contract
        run: cargo nextest run -p rig-core -p rig-agent --all-features -E 'test(telemetry::) + test(span_safety_net::)'

      - name: Test with latest nextest release
        uses: actions-rs/cargo@v1
        with:
          command: nextest
          args: run --all-features --retries 2
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
          COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
          PERPLEXITY_API_KEY: ${{ secrets.PERPLEXITY_API_KEY }}

  # `cargo nextest` (used by the `test` job) does NOT run doctests, and the
  # `doc` job only builds documentation — so without this job nothing executes
  # the `///` code examples and a broken doctest can sail through CI.
  doctest:
    name: stable / doctest
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Rust stable
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          toolchain: ${{ env.RUST_VERSION }}

      # Required to compile rig-lancedb
      - name: Install Protoc
        run: sudo apt-get update && sudo apt-get install -y protobuf-compiler

      # No API keys needed: the doctests that actually execute are pure (the
      # provider examples are all `no_run`/compile-only), so this job stays
      # hermetic and non-flaky.
      #
      # Keep `--workspace`: it is also the only thing in CI that compiles
      # `rig-core-macro-hygiene` (a bare root invocation selects just the `rig`
      # package). A dedicated `cargo check -p` step would resolve rig-core with
      # `default-features = false` and rebuild its whole tree; here feature
      # unification makes the tripwire free.
      - name: Run doctests
        run: cargo test --doc --workspace --all-features

  doc:
    name: stable / doc
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Rust stable
        uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          components: rust-docs
          toolchain: ${{ env.RUST_VERSION }}

      # Required to compile rig-lancedb
      - name: Install Protoc
        run: sudo apt-get update && sudo apt-get install -y protobuf-compiler

      - name: Run cargo doc
        run: cargo doc --no-deps --all-features
        env:
          RUSTDOCFLAGS: -D warnings