Skip to content

fix(openai/responses): preserve OpenRouter server tools (openrouter:web_search) in filterUnsupportedTools#4532

Open
abdenasseraroukhsiss wants to merge 10 commits into
maximhq:devfrom
abdenasseraroukhsiss:fix/responses-preserve-openrouter-server-tools
Open

fix(openai/responses): preserve OpenRouter server tools (openrouter:web_search) in filterUnsupportedTools#4532
abdenasseraroukhsiss wants to merge 10 commits into
maximhq:devfrom
abdenasseraroukhsiss:fix/responses-preserve-openrouter-server-tools

Conversation

@abdenasseraroukhsiss

Copy link
Copy Markdown

What

filterUnsupportedTools (core/providers/openai/responses.go) strips any tool whose type is not in the OpenAI-native whitelist. OpenRouter server tools (openrouter:web_search) are not in that whitelist, so for provider=openrouter on /v1/responses the tool is dropped: the upstream OpenRouter call runs with tools: [], no web search executes, and with tool_choice: "required" the upstream returns 400.

Fixes #4530.

Change

Mirror the existing xAI x_search handling: when provider == OpenRouter, allow the OpenRouter-native openrouter:web_search server tool through the filter. Adds the ResponsesToolTypeOpenRouterWebSearch constant.

Repro (before this change)

A direct OpenRouter call runs the tool and returns url_citation annotations; the same request routed through Bifrost drops the tool and returns no citations. Full curl repro in #4530.

Test

TestToOpenAIResponsesRequest_OpenRouterServerToolPreserved asserts openrouter:web_search is preserved for provider=openrouter and stripped for provider=openai.

roroghost17 and others added 9 commits June 18, 2026 17:39
## Summary

Fixes a bug where OTEL plugin headers were being overwritten with redacted placeholder values when saving a plugin configuration. After the multi-profile change, header values stored as plain strings inside the `profiles` array were not being restored from the database before saving, causing real credentials to be replaced with masked values like `****`.

## Changes

- Extracted `restoreRedactedValue` as a standalone recursive helper, replacing the inline logic in `restoreRedactedFromExisting`. This allows the restoration logic to descend into both nested maps and slices.
- Added slice traversal support (index-aligned) so that elements within arrays like the OTEL `profiles` array are individually checked and restored.
- Added plain-string redaction detection so that header values stored as raw strings (rather than `EnvVar` objects) are also restored from the existing DB config when they carry a redaction artifact. Empty strings are intentionally left as-is to allow clearing a value.
- Added `TestRestoreRedacted_OTELProfilesHeaders` to cover both failure modes: slice traversal and plain-string secret restoration. Also asserts that genuinely new (non-redacted) values pass through unchanged.

## Type of change

- [x] Bug fix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

## Affected areas

- [ ] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [ ] Docs

## How to test

```sh
go test ./transports/bifrost-http/handlers/...
```

Verify that saving an OTEL plugin configuration with multiple profiles, after a GET that returns redacted header values, does not overwrite the stored credentials in the database. Confirm that providing a genuinely new header value still persists correctly.

## Screenshots/Recordings

N/A

## Breaking changes

- [ ] Yes
- [x] No

## Related issues

N/A

## Security considerations

This fix ensures that redacted credential placeholders returned to the client are never written back over real secrets stored in the database. The restoration logic only replaces values that are confirmed redaction artifacts; empty strings and non-redacted values are always passed through as-is, preserving the ability to clear a credential intentionally.

## Checklist

- [ ] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [ ] I verified the CI pipeline passes locally if applicable
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5aad0c46-74ad-4468-b2c3-2d57b1dab0f6

📥 Commits

Reviewing files that changed from the base of the PR and between 087bf22 and 0d1397d.

📒 Files selected for processing (3)
  • core/providers/openai/responses.go
  • core/providers/openai/responses_test.go
  • core/schemas/responses.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/providers/openai/responses_test.go

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Fixed an issue where OpenRouter server-side tools in the openrouter: namespace (including web search) could be incorrectly removed during request processing. These tools are now properly recognized and preserved when using the OpenRouter provider.
  • Tests
    • Added coverage to ensure OpenRouter openrouter: tools are retained, while openrouter:-namespaced tools are still stripped when using the OpenAI provider.

Walkthrough

Adds a new ResponsesToolTypeOpenRouterPrefix constant ("openrouter:") to define the OpenRouter tool namespace, then extends filterUnsupportedTools to whitelist any tool type with that prefix when the request provider is schemas.OpenRouter. A new test verifies that OpenRouter-namespaced tools are preserved for OpenRouter and stripped for OpenAI.

Changes

OpenRouter server tools namespace whitelist

Layer / File(s) Summary
OpenRouter namespace constant, filter whitelist, and test validation
core/schemas/responses.go, core/providers/openai/responses.go, core/providers/openai/responses_test.go
ResponsesToolTypeOpenRouterPrefix ("openrouter:") constant is introduced. filterUnsupportedTools is extended to allow any tool whose Type starts with that prefix when provider == schemas.OpenRouter, broadening support to all future OpenRouter server tools instead of hardcoding individual types. A new test asserts that openrouter:web_search and openrouter:web_fetch survive conversion for OpenRouter and are removed for OpenAI.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • maximhq/bifrost#3976: Modifies the same filterUnsupportedTools function in core/providers/openai/responses.go using the same pattern of provider-conditional tool whitelisting (for xAI x_search).

Suggested reviewers

  • akshaydeo

Poem

🐇 The rabbit opens the namespace gate wide,
openrouter: tools now pass with pride!
No more hardcoding, the prefix leads the way,
All OpenRouter servers tools here to stay.
Three files, a prefix, the future is bright,
The tool filter's now... just right! 🌐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 and specifically describes the primary change: preserving OpenRouter server tools in the filterUnsupportedTools function, with a concrete example (openrouter:web_search).
Description check ✅ Passed The PR description covers the problem (tools being stripped), the solution (whitelisting OpenRouter-namespaced tools), and includes test validation. However, the description is minimal and lacks detailed explanations of affected areas and security considerations typically expected per the template.
Linked Issues check ✅ Passed The PR directly addresses issue #4530 by implementing the suggested fix: allowing openrouter:-prefixed tools through the filter when provider==OpenRouter, with a test verifying the behavior for both OpenRouter and OpenAI providers.
Out of Scope Changes check ✅ Passed All changes are scoped to the filter fix and related test coverage. The addition of the ResponsesToolTypeOpenRouterPrefix constant and test updates directly support the stated objective of fixing the tool filtering issue.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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 and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

Safe to merge; the change is a minimal, additive guard that only affects OpenRouter requests carrying openrouter:-prefixed tools, leaving all other providers and tool types unmodified.

The fix is surgical: one new constant, one two-line guard in the filter loop, and targeted tests that cover both the preserved and stripped paths. The normalizer leaves openrouter: tool types unchanged, and the guard is correctly scoped to provider == OpenRouter, so there is no risk of accidentally passing through these tools for any other OpenAI-compatible provider.

No files require special attention.

Important Files Changed

Filename Overview
core/providers/openai/responses.go Adds inline OpenRouter prefix check in filterUnsupportedTools; logic is correct and consistent with the existing xAI allowance pattern.
core/schemas/responses.go Adds ResponsesToolTypeOpenRouterPrefix constant; well-placed alongside existing tool-type constants and normalizer is unaffected by openrouter: namespace.
core/providers/openai/responses_test.go Adds table-driven sub-tests covering openrouter:web_search and openrouter:web_fetch preserved for OpenRouter, and stripped for OpenAI — adequate coverage for the change.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[filterUnsupportedTools] --> B{len tools == 0?}
    B -- yes --> Z[return]
    B -- no --> C[build supportedTypes whitelist]
    C --> D{provider == xAI?}
    D -- yes --> E[add x_search to whitelist]
    D -- no --> F[iterate tools]
    E --> F
    F --> G{tool.Type in whitelist?}
    G -- yes --> H[keep tool]
    G -- no --> I{provider == OpenRouter AND HasPrefix openrouter:?}
    I -- yes --> H
    I -- no --> J[drop tool]
    H --> K[append to filteredTools]
    K --> F
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[filterUnsupportedTools] --> B{len tools == 0?}
    B -- yes --> Z[return]
    B -- no --> C[build supportedTypes whitelist]
    C --> D{provider == xAI?}
    D -- yes --> E[add x_search to whitelist]
    D -- no --> F[iterate tools]
    E --> F
    F --> G{tool.Type in whitelist?}
    G -- yes --> H[keep tool]
    G -- no --> I{provider == OpenRouter AND HasPrefix openrouter:?}
    I -- yes --> H
    I -- no --> J[drop tool]
    H --> K[append to filteredTools]
    K --> F
Loading

Reviews (2): Last reviewed commit: "fix(openai/responses): preserve OpenRout..." | Re-trigger Greptile

@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 `@core/providers/openai/responses.go`:
- Around line 341-343: The current implementation only allowlists
ResponsesToolTypeOpenRouterWebSearch for OpenRouter providers (lines 341-343),
but OpenRouter supports 6 server-side tools with the openrouter: prefix. Instead
of checking for individual tool type constants that don't exist for the other 5
tools, replace the explicit tool type check with a namespace-based prefix match
that identifies any tool starting with openrouter: and adds it to
supportedTypes. This approach will capture all current and future OpenRouter
tools (web_search, web_fetch, datetime, image_generation, apply_patch, subagent)
and prevent them from being silently stripped by the whitelist validation logic.
Additionally, add test coverage for at least one non-web_search OpenRouter tool
to ensure the namespace prefix matching works correctly.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 23fd0ac7-527b-4a30-952f-041bcf73be50

📥 Commits

Reviewing files that changed from the base of the PR and between 96bb2bd and 087bf22.

📒 Files selected for processing (3)
  • core/providers/openai/responses.go
  • core/providers/openai/responses_test.go
  • core/schemas/responses.go

Comment thread core/providers/openai/responses.go Outdated
…pportedTools

filterUnsupportedTools stripped tools whose type was not in the OpenAI-native
whitelist, including OpenRouter server tools (the "openrouter:" namespace:
web_search, web_fetch, datetime, image_generation, apply_patch, subagent).
For provider=openrouter on /v1/responses this dropped the tool, so the upstream
call ran with tools:[] and no server tool executed (tool_choice:"required" then
caused a 400 from the upstream).

Allow any "openrouter:"-prefixed tool type through the filter when
provider == OpenRouter, mirroring the existing XAI x_search handling. This
covers all current and future OpenRouter server tools without per-tool additions.

Fixes maximhq#4530
@abdenasseraroukhsiss abdenasseraroukhsiss force-pushed the fix/responses-preserve-openrouter-server-tools branch from 087bf22 to 0d1397d Compare June 18, 2026 14:18
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 18, 2026
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.

[Bug]: /v1/responses strips OpenRouter server tools (openrouter:web_search) via filterUnsupportedTools

6 participants