Conversation
0x0079
force-pushed
the
claude/provider-extension-fields-2nmxvh
branch
2 times, most recently
from
August 18, 2026 06:19
2c9c733 to
fc87817
Compare
0x0079
force-pushed
the
claude/provider-extension-fields-2nmxvh
branch
2 times, most recently
from
August 18, 2026 11:46
d528aa8 to
ea2bebf
Compare
Backend half of the provider/model level (frontend follows in a separate PR), completing the three-level Custom Headers control on top of the rule level merged in #1589 and rebased onto the unified rule-flag path (#1592). - ai.Provider gains Flags / ModelFlags, both typed ProviderFlags, so the service reads p.Flags.ExtraHeaders exactly the way it reads rule.Flags.ExtraHeaders. The package documents an admission rule to keep that struct narrow: only fields describing how to reach the upstream belong in ai; gateway product behaviour stays in rule flags. - ProviderFlagRegistry is the same shape as RuleFlagRegistry — no scope or merge axis per spec, because merging is a property of the levels (model overrides provider), not of each flag. - The supply side rides the client, not the request: wrapWithRuleFlags takes the model and resolves typ.SupplyExtraHeaders(provider, model) once per client — clients are already keyed by provider + model — so probe, model-list and vision-proxy paths get upstream headers too and dispatch needs no changes at all. - Precedence provider < model < rule falls out of the transport write order (supply, then rule, then UA), so nothing merges all three levels and the rule-flag context key stays strictly the resolved RuleFlags — diagnostics never report provider config as a rule flag. - Persistence: additive flags / model_flags columns (serializer:json, same pattern as credential and vmodel_detail), no migration script. - api_key scope: Config.AddProvider/UpdateProvider reject on save, covering HTTP, CLI and import; wrapWithRuleFlags loads no supply headers for a non-api_key provider, and vendor chains mount nothing at all. - Provider API exposes typed flags / model_flags with partial-update semantics (nil untouched, non-null replaces wholesale) plus GET /provider/flags/registry. - The extra_headers e2e behavior case gains a three-level route. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KBYfVGevcaVk7PxCJRh69t
Provider flags shipped with exactly one knob (extra_headers), which left
every upstream quirk to be re-configured on each rule that touches that
provider -- even for flags whose own description names the provider or the
model family as the reason (use_max_tokens: "older OpenAI-compatible
providers"; claude_code_compat: "third-party Anthropic-compatible providers
reject that role"; context_1m: "supported models").
Replace the old admission rule ("only fields describing how to reach the
upstream") with a stronger, easier one: ProviderFlags mirrors the consumer's
request-side flag set. A knob means the same thing at every level; only the
scope changes, and no supply-side vocabulary gets invented. Twelve flags
are now configurable at the provider and model levels.
Four rule flags are deliberately not mirrored, with a test pinning it:
session_affinity and vision_proxy_service are consumed before an upstream is
chosen; openai_endpoint_override duplicates Provider.OpenAIEndpointMode;
claude_org_id is Claude OAuth only while provider flags are api_key only.
ProviderFlagRegistry() now derives each spec from the rule spec of the same
key and overrides only the description, so both surfaces render the same
control for the same knob and cannot drift apart.
extra_headers keeps its transport write-order precedence. The rest inject
through the transform chain and the SDK layer, where no write order exists,
so they fold in via typ.ApplyProviderFlags at the single point that already
resolves the effective flags for a request (ResolveRuleFlagsWithScenario,
which gains a model parameter). Precedence is provider < model < rule
throughout: bools OR across levels, scalars take the narrowest non-zero
value.
Per-flag validation stays out on purpose -- a custom upstream is exactly
where an unusual value is legitimate, so only extra_headers keeps the
structural check it needs to be putable on the wire.
The frontend Plugins catalog currently renders the headers control only;
the registry now returns bool/string/enum types too, so exposing this batch
is pure UI work on top of an already-settled contract.
The provider catalog was a second definition: a key -> supply-side wording table that pulled control shape from the rule spec of the same key. Even with the shape derived, that is two places to maintain and two texts to keep aligned -- and "which levels can this flag be set at" is a property of the flag itself, so it belongs on the spec. FlagSpec gains Levels []FlagLevel (provider < model < rule, widest first). RuleFlagRegistry is now the single definition -- 12 flags declare all three levels, 4 declare rule only -- and ProviderFlagRegistry degenerates to the provider-level slice of it with the rule-side scenario axis cleared. The description table is gone. This is not the Scope/MergeMode pair that was removed earlier for being speculative: those had no production reader and no data behind them, while Levels carries 16 real declarations, has one reader, and decides which controls each surface renders. Merge semantics stay off the spec -- that really is a property of the level, not the flag. One flag now means one description, so the wording has to be level-neutral; the surface a user is on already says which level they are editing. Only extra_headers and custom_user_agent actually needed rewording, which is itself a sign the old table was mostly copied text. Also trims the comments this feature added. The rationale lives in .design/provider-flags.md; the code now states what it does and points there, rather than restating the design at each site.
0x0079
force-pushed
the
claude/provider-extension-fields-2nmxvh
branch
from
August 24, 2026 07:27
cc12542 to
2534926
Compare
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.
Summary
Follows #1589 (rule level, merged) and rebases onto #1592's unified rule-flag path: completes the three-level Custom Headers control (provider < model < rule). Backend only — the UI is a separate PR so the capability and its API can land independently. api_key providers only; design in
.design/provider-flags.md.Key Changes
ai.Provider:Flags/ModelFlagscarry the values directly, so the service readsp.Flags.ExtraHeadersexactly the way it readsrule.Flags.ExtraHeaders— no container, no accessors, no well-known keys.aidocuments an admission rule to keep that struct narrow: only "how to reach the upstream" belongs there, gateway product behaviour stays in rule flags.ProviderFlagRegistrymirrorsRuleFlagRegistrywith no per-spec scope or merge axis, because merging is a property of the levels (model overrides provider), not of each flag.wrapWithRuleFlagstakes the model and resolvestyp.SupplyExtraHeaders(provider, model)once per client — clients are already keyed by provider + model — so probe / model-list / vision-proxy paths get upstream headers too and dispatch needs no changes at all.X-Tingly-Applied-Flagsnever reports provider config as a rule flag.flags/model_flagscolumns (serializer:json, same pattern ascredential/vmodel_detail), no migration script.Config.AddProvider/UpdateProviderreject on save (HTTP, CLI, import),wrapWithRuleFlagsloads no supply headers for a non-api_key provider, and vendor chains mount nothing at all.flags/model_flagswith partial-update semantics (nil untouched, non-null replaces wholesale) plusGET /provider/flags/registry.Notes
Extensionscontainer and per-specScope/MergeModeaxes were built first and then removed — the container had exactly one consumer and the axes had no production reader, so both were structure kept for an imagined second flag (~135 lines).