Repository navigation
feat(typesafeai): send OpenRouter provider preferences from JevConfig - #3118
Merged
Merged
Conversation
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
force-pushed
the
feat/typesafeai-provider-preferences
branch
from
November 7, 2026 11:16
52dbac2 to
ba0fd58
Compare
11 of 15 tasks
This was referenced Nov 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
rig-typesafeai'sJevConfigcan now send OpenRouter'sproviderrouting preferences (zdr,data_collection,only, ...) with a Jev evaluation. It reuses rig-core's OpenRouterProviderPreferences. The field sits besidemodelin the/systemonebody 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:
ProviderPreferencesand the types inside it now deriveDeserialize.JevConfigis reloadable configuration, and without this a reloaded config could not keep a zero-retention requirement. A config stored withoutproviderstill loads.evaluation_sends_provider_preferences(cassette replay)provider_preferences_survive_reloading_the_configurationThis 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
providerfailsevaluation_sends_provider_preferences, and sending it when unset fails the existingevaluation_reads_the_reported_cost. Nothing else catches a reload losing the preferences, henceprovider_preferences_survive_reloading_the_configuration.Changelog
JevConfig::providersends OpenRouter's routing preferences, such as zero data retention, with a Jev evaluation through OpenRouter's/systemone.ProviderPreferencesand the types inside it implementDeserialize, so configuration holding them reloads intact.Migration
None.
Type of change
Testing
check-tests.sh(the rig-20 failing-test check, outside the repo): both tests fail on the test commit and pass on the headcargo xtask cassette scan --base origin/main:scanned 2 file(s), 0 hit(s)cargo xtask cassette acceptance --check: passes at this headcargo xtask verify --affected --base origin/main:All 29 checks passedon 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 checkCassette fixture review
Reviewed under
tests/README.md, "Cassette Safety". Nothing was recorded for this PR.evaluation_sends_provider_preferences.yamlis taken unchanged from #2872. That PR's other fixture, for a refused route, is not included: its test only replayed a recorded 404.gen-dec-id is provider-issued, which the policy allows.POST /api/v1/systemone, withmodel,provider,questionsandstate. The replay passes, so the recorded request matches what this code sends.noulanswer.crates/rig-cassette/fixtures/acceptance.tomlgains 2 lines fromcargo xtask cassette acceptance, both OpenRouter/api/v1/systemonerequest facts from this fixture.acceptance --checkrequires them.Checklist:
CHANGELOG.mdorMIGRATING.md(they are generated at release)Notes
Differences from #2872:
#[serde(default)]on the new field: a missingOptionfield already loads asNone, and the reload test checks it.every_preferencehelper: the one reload test covers both forms of each untagged setting.sends_provider_preferences_only_when_setandevaluation_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:
rig-typesafeai(15 code and docs inwire.rs, 5 README), +12/-12 in rig-core's derives.tests.rs,jev.rs,support.rs).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 reportsjev-1.14.Follow-ups:
JevConfighas 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.providerobject are dropped silently, so a misspelleddata_colectionin a hand-written config loads and sends without that restriction.ProviderPreferencesandJevConfigdon't usedeny_unknown_fields.ProviderPreferences::new()with nothing set sends"provider":{}, as the chat path does. Not tried against TypeSafe's own API.provider_request_idstaysNonethrough OpenRouter, as the issue noted.Choices left to the owner:
JevConfigwith noproviderno 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. Droppingskip_serializing_ifon the stored field should fix round trips on this PR (not tested), at the cost of writingprovider: nullinto stored JSON; bytes written by main would still fail.