Skip to content

feat(providers): preserve prompt cache affinity - #622

Merged
SantiagoDePolonia merged 7 commits into
mainfrom
feat/prompt-cache-affinity
Aug 1, 2026
Merged

feat(providers): preserve prompt cache affinity#622
SantiagoDePolonia merged 7 commits into
mainfrom
feat/prompt-cache-affinity

Conversation

@SantiagoDePolonia

@SantiagoDePolonia SantiagoDePolonia commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • preserve Anthropic cache_control through managed /v1/messages translation at request, system/content, tool, tool-use, and tool-result locations
  • forward detected GoModel session IDs to OpenRouter for upstream model/provider affinity
  • select provider API keys with deterministic session-sticky rendezvous hashing by default while retaining round robin for sessionless traffic
  • add a provider-level session_sticky_keys opt-out in YAML, admin persistence, migrations, and the dashboard
  • update prompt-caching and key-rotation documentation

Validation

  • go test ./...
  • repository pre-commit suite (make test-race, lint, performance guard, dashboard tests/dist check, Mintlify validation)
  • npm run check and production dashboard build

Summary by CodeRabbit

  • New Features

    • Identified sessions now remain on a deterministic provider API key by default, improving prompt-cache affinity across standard and realtime requests.
    • Sessionless requests continue rotating keys round robin.
    • Added a provider and dashboard setting to disable session stickiness for per-request rotation.
    • OpenRouter now forwards session identifiers when available.
  • Bug Fixes

    • Anthropic cache_control settings are preserved across supported messages, tools, batches, and content.
    • Unsupported providers safely remove incompatible cache metadata while preserving other request data.
  • Documentation

    • Updated configuration, key rotation, session handling, and Anthropic compatibility guidance.

Copilot AI review requested due to automatic review settings July 31, 2026 13:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Provider API keys now use deterministic session affinity by default, with an opt-out for strict round-robin rotation. Anthropic request translation now preserves supported cache_control metadata across messages, tools, and request-level fields.

Changes

Provider key affinity

Layer / File(s) Summary
Session-aware keyring selection
internal/providers/keyring.go, internal/providers/keyring_test.go, internal/providers/config.go
Identified sessions use deterministic key affinity. Sessionless requests and disabled stickiness use round-robin selection.
Provider request wiring
internal/providers/*, internal/embedding/embedding.go
Provider and realtime authentication paths pass request contexts to key selection. OpenRouter forwards the session ID header.
Credential configuration and persistence
internal/admin/..., internal/providers/credentials*, internal/providers/credential_schema.go, web/dashboard/..., run/providers_test.go
The session_sticky_keys setting flows through APIs, storage, schemas, provider status, and dashboard forms.
Key-affinity documentation
config/*, docs/features/session-keeping.mdx, docs/providers/*, .env.template
Configuration, routing, retry, and supported-route behavior now describe session affinity.

Anthropic cache-control preservation

Layer / File(s) Summary
Request contracts and canonical conversion
internal/anthropicapi/types.go, internal/anthropicapi/request.go, internal/core/json_fields.go
Request structures accept cache-control objects. Canonical conversion preserves metadata-bearing content and validates object values.
Anthropic provider translation
internal/providers/anthropic/*, internal/providers/cache_control.go, internal/providers/router.go
Translation validates and restores cache-control metadata. Routing removes unsupported Anthropic metadata for strict providers without mutating the original request.
Validation and documentation
internal/anthropicapi/request_test.go, internal/providers/anthropic/anthropic_test.go, internal/providers/router_test.go, docs/adr/*, docs/advanced/*, docs/dev/*
Tests cover preservation, routing adaptation, and invalid values. Documentation marks supported cache-control handling as fixed.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Request
  participant Provider
  participant Keyring
  participant Upstream
  Request->>Provider: request with session context
  Provider->>Keyring: NextForContext(ctx)
  Keyring-->>Provider: pinned or round-robin key
  Provider->>Upstream: authenticated request
Loading
sequenceDiagram
  participant CanonicalRequest
  participant Router
  participant AnthropicTranslation
  participant AnthropicAPI
  CanonicalRequest->>Router: chat request with cache-control
  Router->>AnthropicTranslation: provider-specific adaptation
  AnthropicTranslation->>AnthropicAPI: translated request with supported cache-control
Loading

Possibly related PRs

Suggested reviewers: copilot

Poem

A rabbit pins a key in place,
While round-robin keeps its pace.
Cache controls cross the stream,
Tools retain their metadata scheme.
Forms and routes now agree.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 31.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: preserving prompt-cache affinity for providers.
Description check ✅ Passed The description clearly explains the changes and validation, although it uses Summary instead of the template's Description heading.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/prompt-cache-affinity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mintlify

mintlify Bot commented Jul 31, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
gomodel 🟢 Ready View Preview Jul 31, 2026, 1:46 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Comment thread internal/providers/keyring.go Fixed
@codecov-commenter

codecov-commenter commented Jul 31, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/providers/anthropic/request_translation.go (1)

571-604: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share the tail validation between anthropicCacheControlFromExtra and anthropicCacheControlFromValue.

Both functions perform the same "empty/null → nil, else must start with {, else clone" check after producing a json.RawMessage from different sources. Extract that shared tail into one helper to avoid the two implementations drifting apart.

♻️ Proposed refactor
+func validatedCacheControlJSON(raw json.RawMessage) (json.RawMessage, error) {
+	trimmed := bytes.TrimSpace(raw)
+	if len(trimmed) == 0 || core.IsJSONNull(trimmed) {
+		return nil, nil
+	}
+	if trimmed[0] != '{' {
+		return nil, core.NewInvalidRequestError("anthropic cache_control must be an object", nil)
+	}
+	return core.CloneRawJSON(trimmed), nil
+}
+
 func anthropicCacheControlFromExtra(extraFields core.UnknownJSONFields) (json.RawMessage, error) {
 	raw := extraFields.Lookup("cache_control")
 	if len(raw) == 0 {
 		return nil, nil
 	}
-	trimmed := bytes.TrimSpace(raw)
-	if core.IsJSONNull(trimmed) {
-		return nil, nil
-	}
-	if trimmed[0] != '{' {
-		return nil, core.NewInvalidRequestError("anthropic cache_control must be an object", nil)
-	}
-	return core.CloneRawJSON(trimmed), nil
+	return validatedCacheControlJSON(raw)
 }

 func anthropicCacheControlFromValue(value any) (json.RawMessage, error) {
 	if value == nil {
 		return nil, nil
 	}
 	raw, err := json.Marshal(value)
 	if err != nil {
 		return nil, core.NewInvalidRequestError("anthropic cache_control must be an object", err)
 	}
-	trimmed := bytes.TrimSpace(raw)
-	if len(trimmed) == 0 || core.IsJSONNull(trimmed) {
-		return nil, nil
-	}
-	if trimmed[0] != '{' {
-		return nil, core.NewInvalidRequestError("anthropic cache_control must be an object", nil)
-	}
-	return core.CloneRawJSON(trimmed), nil
+	return validatedCacheControlJSON(raw)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/providers/anthropic/request_translation.go` around lines 571 - 604,
Extract the shared post-processing logic from anthropicCacheControlFromExtra and
anthropicCacheControlFromValue into a helper that accepts the produced raw JSON,
returns nil for empty or JSON null values, rejects non-object JSON with the
existing invalid-request error, and clones valid objects. Update both functions
to delegate to this helper while preserving their source-specific handling and
error behavior.
internal/anthropicapi/request.go (1)

534-544: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Drop cache_control before routing to non-Anthropic providers.

buildExtraFields unconditionally stores req.CacheControl in core.ChatRequest.ExtraFields, and OpenAI-style providers emit ExtraFields in the final request body. If a request with cache_control routes to OpenAI, the unknown top-level field can produce a 400 instead of the intended graceful drop. Add an Anthropic-only gate/strip path for this extra field.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/anthropicapi/request.go` around lines 534 - 544, Ensure the
cache_control field from buildExtraFields is retained only for Anthropic
requests. Before routing or serializing requests for non-Anthropic providers,
remove req.CacheControl or its cache_control entry from
core.ChatRequest.ExtraFields, while preserving stop and other provider-neutral
fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/anthropicapi/request.go`:
- Around line 361-372: Extract the shared cache-control validation from
anthropicCacheControlExtra and convertTools into a helper that trims the raw
JSON, accepts empty or null values, and rejects non-object or invalid JSON with
the established error behavior. Update both callers to use this helper while
preserving anthropicCacheControlExtra’s unknown-field construction and
convertTools’ existing flow.

In `@internal/embedding/embedding.go`:
- Line 134: Update NewEmbedder’s providers.NewKeyring construction to pass the
provider’s raw.SessionStickyKeys setting, defaulting it to true when
unspecified, so embedding requests honor session_sticky_keys=false while
preserving sticky behavior by default.

In `@internal/providers/openrouter/openrouter_test.go`:
- Around line 59-61: Synchronize the shared gotSessionID access between the
httptest handler and the test assertions. Replace the unsynchronized write/read
with a channel or mutex, ensuring the test waits for the handler’s captured
X-Session-Id before asserting it and remains race-free under go test -race.

In `@web/dashboard/src/pages/providers-config/providersConfigLogic.js`:
- Around line 460-465: Update the rendered-field loop in
buildProviderCredentialPayload to skip FIELD_SESSION_STICKY_KEYS when schema is
unavailable, ensuring it is not marked rendered or included in the payload;
preserve existing behavior when schema is present, and add a regression test
covering buildProviderCredentialPayload(form, null).

---

Outside diff comments:
In `@internal/anthropicapi/request.go`:
- Around line 534-544: Ensure the cache_control field from buildExtraFields is
retained only for Anthropic requests. Before routing or serializing requests for
non-Anthropic providers, remove req.CacheControl or its cache_control entry from
core.ChatRequest.ExtraFields, while preserving stop and other provider-neutral
fields.

In `@internal/providers/anthropic/request_translation.go`:
- Around line 571-604: Extract the shared post-processing logic from
anthropicCacheControlFromExtra and anthropicCacheControlFromValue into a helper
that accepts the produced raw JSON, returns nil for empty or JSON null values,
rejects non-object JSON with the existing invalid-request error, and clones
valid objects. Update both functions to delegate to this helper while preserving
their source-specific handling and error behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e9dbd72d-2ca6-4d34-9c1c-4248330b0909

📥 Commits

Reviewing files that changed from the base of the PR and between 61493f4 and 04a51c7.

⛔ Files ignored due to path filters (3)
  • internal/admin/dashboard/static/dist/assets/index-ClpBYKZR.js is excluded by !**/dist/**
  • internal/admin/dashboard/static/dist/assets/index-DNWYS-jJ.js is excluded by !**/dist/**
  • internal/admin/dashboard/static/dist/index.html is excluded by !**/dist/**
📒 Files selected for processing (47)
  • CLAUDE.md
  • config/config.example.yaml
  • config/providers.go
  • docs/adr/0007-anthropic-messages-ingress.md
  • docs/advanced/anthropic-messages-api.mdx
  • docs/dev/2026-07-17_anthropic-sdk-compat-findings.md
  • docs/features/session-keeping.mdx
  • docs/providers/key-rotation.mdx
  • docs/providers/overview.mdx
  • internal/admin/handler_provider_credentials.go
  • internal/admin/handler_provider_credentials_test.go
  • internal/anthropicapi/request.go
  • internal/anthropicapi/request_test.go
  • internal/anthropicapi/types.go
  • internal/embedding/embedding.go
  • internal/providers/anthropic/anthropic.go
  • internal/providers/anthropic/anthropic_test.go
  • internal/providers/anthropic/request_translation.go
  • internal/providers/anthropic/types.go
  • internal/providers/azure/realtime.go
  • internal/providers/bailian/realtime.go
  • internal/providers/bedrockmantle/auth.go
  • internal/providers/cohere/cohere.go
  • internal/providers/config.go
  • internal/providers/config_test.go
  • internal/providers/credential_schema.go
  • internal/providers/credential_schema_test.go
  • internal/providers/credentials.go
  • internal/providers/credentials_store_mongodb.go
  • internal/providers/credentials_store_sql.go
  • internal/providers/credentials_store_sql_test.go
  • internal/providers/factory.go
  • internal/providers/gemini/gemini.go
  • internal/providers/keyring.go
  • internal/providers/keyring_test.go
  • internal/providers/ollama/ollama.go
  • internal/providers/openai/compatible_provider.go
  • internal/providers/openai/realtime.go
  • internal/providers/openrouter/openrouter.go
  • internal/providers/openrouter/openrouter_test.go
  • internal/providers/provider_status.go
  • internal/providers/xai/realtime.go
  • internal/providers/zai/realtime.go
  • run/providers_test.go
  • web/dashboard/src/pages/providers-config/ProviderCredentialField.svelte
  • web/dashboard/src/pages/providers-config/providersConfigLogic.js
  • web/dashboard/tests/providers-config.test.js

Comment thread internal/anthropicapi/request.go
Comment thread internal/embedding/embedding.go
Comment thread internal/providers/openrouter/openrouter_test.go Outdated
Comment thread web/dashboard/src/pages/providers-config/providersConfigLogic.js
@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

Safe to merge based on the exercised translation, routing, and credential-selection paths.

The final defect set is empty. The exercised request path preserved cache controls only where supported, and the key-selection path behaved deterministically for identified sessions while preserving configured round-robin behavior elsewhere.

Files Needing Attention: No files require corrective action. The primary exercised areas were internal/anthropicapi/request.go, internal/providers/cache_control.go, internal/providers/router.go, and internal/providers/keyring.go.

T-Rex T-Rex Logs

What T-Rex did

  • Ran TestTrexPromptCacheIngressBeforeRouting and confirmed that the translated canonical request contains top-level, system-content, user-content, and tool cache_control fields.
  • Ran TestTrexPromptCacheRoutingAfterTranslation and verified that Anthropic cache metadata is retained while OpenAI-compatible routing metadata is not present.
  • Ran TestTrexKeyringAffinity and observed the sequences for identified-session selection [key-c key-c key-c], sessionless selection [key-a key-b key-c key-a], and sticky-disabled selection [key-a key-b key-c key-a].
  • These results validate the intended translation, routing isolation, and credential-selection behavior.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (3): Last reviewed commit: "fix(providers): adapt message batch cach..." | Re-trigger Greptile

Comment thread internal/anthropicapi/request.go
Copilot AI review requested due to automatic review settings July 31, 2026 14:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@SantiagoDePolonia

Copy link
Copy Markdown
Contributor Author

Addressed the review pass in 55cf7a9:

  • sanitize Anthropic cache directives after provider resolution while preserving Anthropic/OpenRouter support
  • honor session_sticky_keys=false for embeddings
  • omit session_sticky_keys when the dashboard has no advertised schema
  • use HMAC-SHA256 for rendezvous scoring (CodeQL is now green)
  • consolidate cache-control validation and synchronize the OpenRouter test
  • add focused regressions and rebuild dashboard assets

Validated by the full pre-commit suite: race tests, dashboard tests/dist check, performance guard, modernization check, lint, and docs validation.

@SantiagoDePolonia

Copy link
Copy Markdown
Contributor Author

Added environment-only configuration parity in ce1bbe3: <PROVIDER>[_SUFFIX]_SESSION_STICKY_KEYS=false now controls key affinity for both unsuffixed and suffixed provider instances. .env.template, session/key-rotation docs, overlay tests, and invalid-value coverage are included. Full pre-commit and CI suites pass.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/dashboard/src/pages/providers-config/providersConfigLogic.js (1)

453-471: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Use the same schema-availability check for payload construction.

providerCredentialFormFields treats missing or empty schema.fields as the compatibility fallback. This guard checks only schema == null. If a matched schema has fields: [] or omits fields, the fallback includes session_sticky_keys, and this loop sends session_sticky_keys: true even though the gateway did not advertise it. Base the skip on the explicit schema field list and add a regression test for an empty or missing fields array.

Proposed fix
   const { primary, advanced } = providerCredentialFormFields(schema);
+  const hasAdvertisedFields = Boolean(
+    schema && Array.isArray(schema.fields) && schema.fields.length > 0,
+  );
   const rendered = new Set();
   for (const field of [...primary, ...advanced]) {
-    if (!schema && field.name === FIELD_SESSION_STICKY_KEYS) {
+    if (!hasAdvertisedFields && field.name === FIELD_SESSION_STICKY_KEYS) {
       continue;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/dashboard/src/pages/providers-config/providersConfigLogic.js` around
lines 453 - 471, Update the `session_sticky_keys` skip logic in the
payload-construction loops to use the same explicit schema-field availability
check as `providerCredentialFormFields`, treating missing or empty
`schema.fields` as unsupported rather than checking only `schema`. Ensure
`FIELD_SESSION_STICKY_KEYS` is omitted unless it is advertised in the schema,
and add a regression test covering an empty or missing `fields` array.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/anthropicapi/request.go`:
- Around line 374-383: Extract the duplicated trim/empty-null/object-shape/clone
logic from validatedCacheControlJSON in internal/anthropicapi/request.go lines
374-383 and validatedAnthropicCacheControlJSON in
internal/providers/anthropic/request_translation.go lines 586-594 into a shared
core helper returning cloned JSON and a plain error; update both callers to use
it, with validatedAnthropicCacheControlJSON wrapping failures in
core.NewInvalidRequestError("anthropic cache_control must be an object", nil)
while preserving each function’s existing behavior.

In `@internal/embedding/embedding_test.go`:
- Around line 55-83: Replace TestNewEmbedder_HonorsSessionStickyKeysOptOut with
table-driven HTTP tests that exercise the public embedding request path,
covering default session-sticky behavior and SessionStickyKeys set to false. Use
a test server to record authorization keys from successive embedding requests
made with the same session context, and assert the observed key sequences
without accessing apiEmbedder or its keyring directly.

In `@internal/providers/config_test.go`:
- Around line 473-487: Refactor TestApplyProviderEnvVars_SessionStickyKeys into
a table-driven test covering unsuffixed and suffixed discovery,
environment-over-YAML precedence, valid true/false values, default
configuration, and invalid environment values. Include a case with an existing
YAML SessionStickyKeys value and assert that an invalid environment value
preserves it, while retaining provider-specific mappings for openai and
openai-eu.

In `@internal/providers/router_test.go`:
- Around line 563-645: Expand
TestRouterChatCompletion_AdaptsAnthropicCacheControlAfterRouting into a
table-driven routing test covering provider types and cache-control behavior at
request, content, tool, tool-use, and tool-result locations. Assert forwarded
fields match each provider’s expectation, including removal for unsupported
providers and preservation for supported ones, while verifying every original
nested field remains unchanged; retain the existing x_keep preservation checks
where applicable.

---

Outside diff comments:
In `@web/dashboard/src/pages/providers-config/providersConfigLogic.js`:
- Around line 453-471: Update the `session_sticky_keys` skip logic in the
payload-construction loops to use the same explicit schema-field availability
check as `providerCredentialFormFields`, treating missing or empty
`schema.fields` as unsupported rather than checking only `schema`. Ensure
`FIELD_SESSION_STICKY_KEYS` is omitted unless it is advertised in the schema,
and add a regression test covering an empty or missing `fields` array.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5e4efa91-4d03-402e-89ab-e88a53fdf95f

📥 Commits

Reviewing files that changed from the base of the PR and between 04a51c7 and ce1bbe3.

⛔ Files ignored due to path filters (2)
  • internal/admin/dashboard/static/dist/assets/index-BdnxAytg.js is excluded by !**/dist/**
  • internal/admin/dashboard/static/dist/index.html is excluded by !**/dist/**
📒 Files selected for processing (18)
  • .env.template
  • docs/features/session-keeping.mdx
  • docs/providers/key-rotation.mdx
  • internal/anthropicapi/request.go
  • internal/core/json_fields.go
  • internal/core/json_fields_test.go
  • internal/embedding/embedding.go
  • internal/embedding/embedding_test.go
  • internal/providers/anthropic/request_translation.go
  • internal/providers/cache_control.go
  • internal/providers/config.go
  • internal/providers/config_test.go
  • internal/providers/keyring.go
  • internal/providers/openrouter/openrouter_test.go
  • internal/providers/router.go
  • internal/providers/router_test.go
  • web/dashboard/src/pages/providers-config/providersConfigLogic.js
  • web/dashboard/tests/providers-config.test.js

Comment thread internal/anthropicapi/request.go
Comment thread internal/embedding/embedding_test.go Outdated
Comment thread internal/providers/config_test.go Outdated
Comment thread internal/providers/router_test.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 54 out of 57 changed files in this pull request and generated no new comments.

Suppressed comments (2)

internal/providers/router.go:558

  • forwardChatRequest calls r.GetProviderType(selector.QualifiedModel()), which re-runs model resolution even though selector is already resolved. This adds avoidable work on the hot path (especially for cache_control-heavy traffic) and risks subtle divergence if ResolveModel applies additional rewriting.
    internal/providers/cache_control.go:17
  • adaptAnthropicCacheControl treats the generic field name cache_control as “Anthropic-only” and strips it for most provider types. Elsewhere in the codebase, cache_control is used as an opaque/unknown nested field that is preserved for OpenAI-compatible chat payloads (e.g. OpenAI provider tests), so this routing-time stripping is a backwards-incompatible behavior change for /v1/chat/completions callers relying on that passthrough. Consider scoping Anthropic /v1/messages cache controls under a distinct internal metadata key (or otherwise gating this stripping to the Anthropic-ingress path) so OpenAI-compatible cache_control usage is not unintentionally removed.
const anthropicCacheControlField = "cache_control"

// adaptAnthropicCacheControl removes Anthropic-only cache directives after the
// route is known unless the selected provider accepts Anthropic request shapes.
// The caller's request remains unchanged.
func adaptAnthropicCacheControl(req *core.ChatRequest, providerType string) *core.ChatRequest {
	if req == nil || providerAcceptsAnthropicCacheControl(providerType) || !hasAnthropicCacheControl(req) {
		return req

Copilot AI review requested due to automatic review settings August 1, 2026 12:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 54 out of 57 changed files in this pull request and generated no new comments.

Suppressed comments (1)

internal/providers/anthropic/request_translation.go:290

  • cache_control is only read from toolCall.ExtraFields when building Anthropic tool_use blocks. However, the rest of this PR treats toolCall.function as another possible location for cache_control (e.g. cache stripping and router tests). If a caller sends cache_control under tool_calls[].function, it will currently be dropped during Anthropic translation, defeating the goal of preserving prompt-cache breakpoints at tool-use locations.
		cacheControl, err := anthropicCacheControlFromExtra(toolCall.ExtraFields)
		if err != nil {
			return nil, err
		}

Copilot AI review requested due to automatic review settings August 1, 2026 16:01
@SantiagoDePolonia

Copy link
Copy Markdown
Contributor Author

Completed an exhaustive pass over all review surfaces (issue comments, review summaries, inline threads, and suppressed Copilot comments). The remaining three actionable suppressed findings are fixed in 2b9d150:

  • scope Anthropic cache_control removal to requests translated from /v1/messages, preserving opaque metadata on ordinary /v1/chat/completions requests
  • avoid repeated model resolution in forwardChatRequest by using the already-resolved selector/provider lookup
  • preserve tool-call cache metadata supplied under function.extra_fields during Anthropic translation

The full pre-commit gate passed: race tests, gofmt/imports, module tidiness, hot-path performance thresholds, fix-check, and lint. The other comments are resolved, obsolete after earlier fixes, informational/status-only, or repository/org configuration requests rather than code changes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/providers/router_test.go (1)

624-669: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Verify caller ExtraFields directly after routing.

Lines 624-669 serialize ChatRequest with json.Marshal. ExtraFields uses json:"-", so this comparison cannot detect mutations to request, message, content-part, tool-call, function-call, or tool-result cache metadata.

After routing, assert that each original UnknownJSONFields value still contains cache_control and x_keep. Keep the serialized comparison for fields that are part of the JSON request shape.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/providers/router_test.go` around lines 624 - 669, Update the
caller-mutation assertions in the ChatCompletion routing test to inspect the
original request’s ExtraFields directly after routing, rather than relying on
json.Marshal for metadata tagged json:"-". Apply the existing assertFields
helper to the request, message, content-part, tool-call, function-call, and
tool-result ExtraFields, while retaining the serialized before/after comparison
for JSON-visible fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/providers/router_test.go`:
- Around line 624-669: Update the caller-mutation assertions in the
ChatCompletion routing test to inspect the original request’s ExtraFields
directly after routing, rather than relying on json.Marshal for metadata tagged
json:"-". Apply the existing assertFields helper to the request, message,
content-part, tool-call, function-call, and tool-result ExtraFields, while
retaining the serialized before/after comparison for JSON-visible fields.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4844036c-05e7-4279-a561-821f2e00dfdf

📥 Commits

Reviewing files that changed from the base of the PR and between c53f173 and 2b9d150.

📒 Files selected for processing (7)
  • internal/anthropicapi/request.go
  • internal/anthropicapi/request_test.go
  • internal/core/types.go
  • internal/providers/anthropic/anthropic_test.go
  • internal/providers/anthropic/request_translation.go
  • internal/providers/router.go
  • internal/providers/router_test.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 55 out of 58 changed files in this pull request and generated no new comments.

Suppressed comments (1)

internal/providers/router.go:560

  • AnthropicMessagesRequest is gateway-only routing metadata (like Provider). After using it to decide whether to adapt cache_control, it should be cleared before dispatching to providers to avoid leaking internal flags into downstream logic (even if it’s JSON-ignored today).

Copilot AI review requested due to automatic review settings August 1, 2026 16:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@SantiagoDePolonia

Copy link
Copy Markdown
Contributor Author

Addressed the two findings from the latest review pass in 671e5cc:

  • clear the internal Anthropic Messages ingress marker before provider dispatch
  • verify caller-owned ExtraFields directly at every nested cache-control location, since those fields are intentionally excluded from JSON serialization

The full pre-commit gate passes again. PR #624 has also been rebased onto this commit and its focused provider/cache suites pass.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/providers/router_test.go`:
- Around line 646-648: Update the cache_control assertions in assertFields and
the forwarded.Tools[0] checks to compare the canonical JSON value against the
expected preservation value, rather than only checking key presence. For
stripping cases, explicitly assert that cache_control is absent; preserve the
existing table-driven provider translation coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e86d4c6b-e512-44b4-ab6c-359f36a06f7d

📥 Commits

Reviewing files that changed from the base of the PR and between 2b9d150 and 671e5cc.

📒 Files selected for processing (2)
  • internal/providers/router.go
  • internal/providers/router_test.go

Comment thread internal/providers/router_test.go Outdated
Copilot AI review requested due to automatic review settings August 1, 2026 16:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@SantiagoDePolonia

Copy link
Copy Markdown
Contributor Author

Fixed the batch-path cache-control gap and exact-value assertion in dda4362.

  • ingress provenance is now typed request-context metadata rather than a field on canonical ChatRequest
  • both live /v1/messages and /v1/messages/batches carry the Anthropic dialect through routing
  • post-routing batch adaptation preserves cache controls for Anthropic/OpenRouter and strips them for unsupported providers without mutating the caller
  • ordinary /v1/batches payloads remain opaque
  • preservation tests compare exact canonical cache-control JSON; stripping tests require absence

The full pre-commit suite passes. PR #624 was rebased onto this commit at 89b23f8, and its focused provider/cache/server suites pass.

@SantiagoDePolonia
SantiagoDePolonia merged commit 288c00f into main Aug 1, 2026
21 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.

4 participants