feat: a chat request carries plugins, so a caller can turn OpenRouter's context compression off #27
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/request-plugins"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What.
ChatCompletionRequest.Plugins, a list ofRequestPlugin, sent asplugins.Each entry has:
Id;Enabled, where null keeps OpenRouter's default for the plugin;AdditionalProperties).RequestPlugin.ContextCompression(false)builds{"id":"context-compression","enabled":false}.Why. OpenRouter compresses a prompt that does not fit the window of a model with 8,192 tokens or less. It cuts from the prompt's middle and answers as if nothing had happened.
openai/gpt-3.5-turbo-0613(a 4,095-token window). The answer was200 OKwithprompt_tokens: 944.pluginslist, which this library could not send.llmnode (slice 8b) turns compression off on every request. A prompt that does not fit then fails visibly ("This endpoint's maximum context length is …"), rather than losing most of the story context without a word.Validation.
Validate()refuses, each with its own sentence:idorenabled, which would reach the wire twice (the same ruleTraceConfig.AdditionalPropertiesfollows);Every problem of every entry is reported.
Also:
Transforms' summary points to the plugin.Not included. Typed settings for the other plugins (
web,file-parser,response-healing). They go throughAdditionalPropertiesfor now, and a typed member can be added later without breaking anything.Tests.
OpenRouter.Net.Testsgoes from 1294 to 1307 (13 new, inRequestPluginTests):The other two test projects are untouched.
Coverage. Every new line and branch of
RequestPlugin.csandChatCompletionRequest'sCheckPluginsis covered.Mutation probes, all killed. Nine, run on a copy:
enabledallowed as a setting;idallowed as a setting;IsNullOrEmptyfor the blank id;ContextCompressionalways off;Pluginsnever validated;Downstream. Tsumugi's slice 8b bumps the submodule to the merged commit, and its request builder sends
[RequestPlugin.ContextCompression(false)]on everyllmrequest.🤖 Generated with Claude Code
Summary
Summary
Coverage
OpenRouter.Net - 97.7%
OpenRouter.Net.Agents - 84.4%
OpenRouter.Net.Imaging - 81.8%
🔮 fufu~ Jibril reviewed your code!
Oh? A silent middle-cut compressor, eating 4,800 tokens of a story without a word, and the fix is a tiny typed
RequestPluginwith pass-through settings? This is delightful — a small surface with a measured reason to exist (5,700 tokens in,prompt_tokens: 944out — horrifying~ ♡). I read the whole diff, every changed file in full, the siblings it borrows from, and the live OpenRouter plugin docs.Verdict: ✅ Looks good to me~
✅ What I liked~
CheckPluginsreproducesDecisionsRequest.CheckExtensionData's reserved-key sentence word for word ("names a field that is already sent, so it would reach the wire twice"), reusesDecisionWire.Unwritable(so the disposed-JsonDocumenttrap was already solved and you didn't re-solve it), and "Plugins contains a null entry." echoes theModelssibling. Consistency makes my wings flutter~pluginsas an array,id,enabled: falseto disable, and settings as sibling keys ({"id":"web","max_results":3}) — yourAdditionalPropertiesextension-data model matches precisely, andEnabled = nullcorrectly stays off the wire underWhenWritingNull.RequestPlugin.cs100% line and branch; every new branch ofCheckPluginsat 100% (the only partials inChatCompletionRequestare pre-existing lines 181/232/236, untouched by this PR). 1307/1307 locally, matching your table exactly.enabledreservation, freezingContextCompressiontofalse,break-ing on a null entry, deleting the duplicate-id check, and misspelling the wire name. Each went red on precisely its own test(s), nothing else. Fufu~ that's a pinned specification, not incidental coverage~Every_problem_of_every_entry_is_reportedpins the walk's order and completeness exactly — thecontinue-not-returnsemantics can't silently regress.Transformssummary pointing to the plugin, and the README's Context compression section (with the 8,192-token default threshold), close the loop between old knob and new.💡 Little ideas (non-blocking)~
ChatCompletionRequest.cs:259— a plugin with a blank id still gets its id added toseen. Harmless today (a blank id is refused anyway, so the request never sends), purely an observation for whoever refactors here later; no change requested.web/file-parser/response-healing(wisely deferred here), aRequestPlugin.Web(maxResults)builder would mirrorContextCompressionnicely — noted for that future PR, not this one.Automated review by Jibril · 2026-09-26
CI/CD: absent for head SHA at review time (no coverage bot yet) · Local checks: Release build 0w/0e, full solution 1427/1427, coverage independently reproduced, 5/5 mutation probes killed
Thanks, Jibril, and thanks for checking the wire shape against the live docs. No code change for either 💡:
seen. Agreed that it's harmless: a blank id already makes the request invalid, so nothing is sent. Skipping the add would change no outcome and cost one more branch.RequestPlugin.Web(maxResults). Noted for the PR that types the other plugins' settings;ContextCompressionis the pattern it will mirror.Downstream, Tsumugi's slice 8b repoints its OpenRouter.Net submodule at the merge (
15a1f28) and sends[RequestPlugin.ContextCompression(false)]on everyllmrequest.