Skip to content

feat(typesafeai): send OpenRouter provider preferences from JevConfig - #3118

Merged
gold-silver-copper merged 2 commits into
mainfrom
feat/typesafeai-provider-preferences
Nov 7, 2026
Merged

gold-silver-copper merged 2 commits into
mainfrom
feat/typesafeai-provider-preferences

Conversation

@gold-silver-copper

@gold-silver-copper gold-silver-copper commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Description

rig-typesafeai's JevConfig can now send OpenRouter's provider routing preferences (zdr, data_collection, only, ...) with a Jev evaluation. It reuses rig-core's OpenRouter ProviderPreferences. The field sits beside model in the /systemone body and is absent unless set, so TypeSafe's own API never sees it unless a caller sets it.

Supersedes #2872 by tmihalicka

Closes #2871

Before: the body was always {model, state, questions}, and nothing could be added to it.

After:

JevConfig::new(openrouter_key)
    .model("typesafe/jev-1.14")
    .with_endpoint("https://openrouter.ai/api/v1/systemone")
    .provider(
        ProviderPreferences::new()
            .zdr(true)
            .data_collection(DataCollection::Deny),
    )

ProviderPreferences and the types inside it now derive Deserialize. JevConfig is reloadable configuration, and without this a reloaded config could not keep a zero-retention requirement. A config stored without provider still loads.

PR Verdict Test
#2872 Rewritten, feature added evaluation_sends_provider_preferences (cassette replay)
#2872 Rewritten, feature added provider_preferences_survive_reloading_the_configuration

This is new API, so on the test commit these tests fail by not compiling (no method named provider). Both pass on the head.

Replay compares request bodies exactly: dropping provider fails evaluation_sends_provider_preferences, and sending it when unset fails the existing evaluation_reads_the_reported_cost. Nothing else catches a reload losing the preferences, hence provider_preferences_survive_reloading_the_configuration.

Changelog

  • (rig-typesafeai) JevConfig::provider sends OpenRouter's routing preferences, such as zero data retention, with a Jev evaluation through OpenRouter's /systemone.
  • (rig-core) OpenRouter's ProviderPreferences and the types inside it implement Deserialize, so configuration holding them reloads intact.

Migration

None.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Testing

  • check-tests.sh (the rig-20 failing-test check, outside the repo): both tests fail on the test commit and pass on the head
  • cargo xtask cassette scan --base origin/main: scanned 2 file(s), 0 hit(s)
  • cargo xtask cassette acceptance --check: passes at this head
  • cargo xtask verify --affected --base origin/main: All 29 checks passed on the earlier head 52dbac2, which had two more tests and a second fixture. Production code is byte-identical since then, and only tests and fixtures changed, so it was not rerun; CI on this head is the check

Cassette fixture review

Reviewed under tests/README.md, "Cassette Safety". Nothing was recorded for this PR. evaluation_sends_provider_preferences.yaml is taken unchanged from #2872. That PR's other fixture, for a refused route, is not included: its test only replayed a recorded 404.

  • Keys and personal data: the scan above found no hits, and reading the file by hand found no authorization header, key, cookie or account id. The request text is synthetic ("I was charged twice for one order."). The gen-dec- id is provider-issued, which the policy allows.
  • Paths, methods, bodies: POST /api/v1/systemone, with model, provider, questions and state. The replay passes, so the recorded request matches what this code sends.
  • Response: a 200 with a noul answer.
  • Churn: no existing fixture changed.
  • Generated index: crates/rig-cassette/fixtures/acceptance.toml gains 2 lines from cargo xtask cassette acceptance, both OpenRouter /api/v1/systemone request facts from this fixture. acceptance --check requires them.

Checklist:

  • I have updated READMEs and Rust docs affected by this change
  • I have added tests that prove my fix is effective or that my feature works
  • I did not edit CHANGELOG.md or MIGRATING.md (they are generated at release)

Notes

Differences from #2872:

  • No #[serde(default)] on the new field: a missing Option field already loads as None, and the reload test checks it.
  • rig-core's tests are unchanged, with no new round-trip test or every_preference helper: the one reload test covers both forms of each untagged setting.
  • Two of feat(rig-typesafeai): send OpenRouter provider preferences from JevConfig #2872's tests are left out: sends_provider_preferences_only_when_set and evaluation_with_an_unservable_route_is_refused. The replays above already catch the first, and the second could not show OpenRouter applying anything.

Line deltas, added/removed:

  • Production: +20/-0 in rig-typesafeai (15 code and docs in wire.rs, 5 README), +12/-12 in rig-core's derives.
  • Generated index: +2/-0.
  • Tests: +76/-7 (tests.rs, jev.rs, support.rs).
  • Fixture: +16/-0.

Not verified: no live OpenRouter call was made for this PR. The evidence is the unit test and the contributor's recording replayed, which shows the request body and how the recorded reply is handled, not that OpenRouter applied the preferences. The recording and the test helper use typesafe/jev-1.13, while the issue reports jev-1.14.

Follow-ups:

  • JevConfig has no method to supply the credential again after a reload. Its token is stored redacted, so it has to be written into the document before loading. The same on main.
  • Unknown keys in a stored provider object are dropped silently, so a misspelled data_colection in a hand-written config loads and sends without that restriction. ProviderPreferences and JevConfig don't use deny_unknown_fields.
  • ProviderPreferences::new() with nothing set sends "provider":{}, as the chat path does. Not tried against TypeSafe's own API.
  • provider_request_id stays None through OpenRouter, as the issue noted.

Choices left to the owner:

  • Positional binary formats. A JevConfig with no provider no longer round trips through bincode or array-form MessagePack, and bytes main wrote there no longer load. JSON, TOML, YAML and named MessagePack are unaffected. Nothing in this repository uses those formats, and on main a config loaded that way can't send, because its token is stored redacted. This PR accepts it. Dropping skip_serializing_if on the stored field should fix round trips on this PR (not tested), at the cost of writing provider: null into stored JSON; bytes written by main would still fail.

gold-silver-copper and others added 2 commits November 7, 2026 03:03
Co-authored-by: Tomas Mihalicka <tomas@mihalicka.com>
JevConfig::provider sets rig-core's ProviderPreferences, sent beside the
model only when set. The preference types gain Deserialize so a reloaded
configuration keeps them.

Co-authored-by: Tomas Mihalicka <tomas@mihalicka.com>
@gold-silver-copper
gold-silver-copper force-pushed the feat/typesafeai-provider-preferences branch from 52dbac2 to ba0fd58 Compare November 7, 2026 11:16
@gold-silver-copper
gold-silver-copper added this pull request to the merge queue Nov 7, 2026
Merged via the queue into main with commit 33cf334 Nov 7, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(rig-typesafeai): let JevConfig send OpenRouter's provider routing preferences

1 participant