Skip to content

fix(schemas/responses): forward server-tool parameters on ResponsesTool#4543

Open
abdenasseraroukhsiss wants to merge 10 commits into
maximhq:devfrom
abdenasseraroukhsiss:fix/responses-forward-server-tool-parameters
Open

fix(schemas/responses): forward server-tool parameters on ResponsesTool#4543
abdenasseraroukhsiss wants to merge 10 commits into
maximhq:devfrom
abdenasseraroukhsiss:fix/responses-forward-server-tool-parameters

Conversation

@abdenasseraroukhsiss

Copy link
Copy Markdown

What

ResponsesTool models typed tool variants (function, web_search, etc.) but has no generic parameters passthrough. As a result, configuration for provider/server tools — e.g. OpenRouter's openrouter:web_search with {"engine":"exa","max_results":5} — is silently dropped during (de)serialization on /v1/responses.

Fixes #4541.

Change

Add a Parameters map[string]interface{} field to ResponsesTool, captured in UnmarshalJSON and emitted (deterministically, via MarshalSorted) in MarshalJSON, so server-tool parameters are forwarded verbatim.

Test

TestResponsesTool_ParametersRoundTrip — unmarshal a tool with parameters, assert it is captured, marshal it back, assert the parameters survive on the wire.

Note

Independent of #4530 / #4532 (the filterUnsupportedTools whitelist). Both are needed for OpenRouter server tools to fully work on /v1/responses: the type must pass the whitelist and its parameters must be forwarded.

roroghost17 and others added 10 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
ResponsesTool modeled typed tool variants but had no generic `parameters`
passthrough, so configuration for provider/server tools (e.g. OpenRouter's
"openrouter:web_search" with {"engine":"exa"}) was silently dropped during
(de)serialization. Add a `Parameters` field carried verbatim through
UnmarshalJSON/MarshalJSON.

Fixes maximhq#4541
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Tools now support optional custom parameters for provider-specific configuration, enabling more flexible tool setup and provider customization.

Walkthrough

ResponsesTool gains an optional Parameters map[string]interface{} field. MarshalJSON serializes it under "parameters" when non-empty using deterministic marshaling; UnmarshalJSON reads it back when the JSON value is an object. A new roundtrip test validates that parameters survive unmarshal and re-marshal.

Changes

ResponsesTool parameters passthrough

Layer / File(s) Summary
Parameters field, marshal, and unmarshal
core/schemas/responses.go, core/schemas/responses_test.go
Adds Parameters map[string]interface{} to ResponsesTool; extends MarshalJSON to emit the field when non-empty and UnmarshalJSON to populate it from a JSON object; adds TestResponsesTool_ParametersRoundTrip to verify the field survives a full unmarshal + re-marshal cycle.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

A bunny once lost its config with a hop,
parameters vanished — the engine would stop.
Now MarshalJSON carries the map along,
and UnmarshalJSON sings the same song.
No more dropped fields — the search runs just right! 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 summarizes the main change: adding parameter forwarding to ResponsesTool for server-tool configuration preservation.
Description check ✅ Passed The PR description provides comprehensive context with 'What,' 'Change,' 'Test,' and 'Note' sections, clearly explaining the bug, the solution, and testing approach.
Linked Issues check ✅ Passed The PR fully addresses #4541 by implementing the Parameters field in ResponsesTool to preserve server-tool configuration during serialization and deserialization.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the Parameters field in ResponsesTool and testing its round-trip serialization behavior.

✏️ 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.

@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/schemas/responses_test.go`:
- Around line 182-183: The current assertion using strings.Contains for
`"engine":"exa"` is fragile and can pass even if the parameter is in the wrong
location in the JSON structure. Replace the strings.Contains check with a proper
roundtrip validation: unmarshal the output bytes into an appropriate response
structure, then assert that both parameters.engine and parameters.max_results
are correctly preserved as nested fields within the parameters object. This
ensures the JSON structure is correct, not just that a substring exists
somewhere in the output.
🪄 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: 9520a1a7-e379-499e-94f7-35c0ca4b709d

📥 Commits

Reviewing files that changed from the base of the PR and between 96bb2bd and 7532a78.

📒 Files selected for processing (2)
  • core/schemas/responses.go
  • core/schemas/responses_test.go

Comment on lines +182 to +183
if !strings.Contains(string(out), `"engine":"exa"`) {
t.Fatalf("expected parameters to be preserved on marshal, got %s", string(out))

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Strengthen the roundtrip assertion to validate the parameters object, not just a substring.

The current strings.Contains check can false-pass if "engine":"exa" appears in the wrong location. Re-unmarshal out and assert both parameters.engine and parameters.max_results are preserved under parameters.

Proposed test hardening
 	out, err := tool.MarshalJSON()
 	if err != nil {
 		t.Fatalf("marshal: %v", err)
 	}
-	if !strings.Contains(string(out), `"engine":"exa"`) {
-		t.Fatalf("expected parameters to be preserved on marshal, got %s", string(out))
-	}
+	var roundTripped ResponsesTool
+	if err := Unmarshal(out, &roundTripped); err != nil {
+		t.Fatalf("re-unmarshal: %v", err)
+	}
+	if roundTripped.Parameters == nil ||
+		roundTripped.Parameters["engine"] != "exa" ||
+		roundTripped.Parameters["max_results"] != float64(5) {
+		t.Fatalf("expected parameters roundtrip preservation, got %+v", roundTripped.Parameters)
+	}
 }
🤖 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 `@core/schemas/responses_test.go` around lines 182 - 183, The current assertion
using strings.Contains for `"engine":"exa"` is fragile and can pass even if the
parameter is in the wrong location in the JSON structure. Replace the
strings.Contains check with a proper roundtrip validation: unmarshal the output
bytes into an appropriate response structure, then assert that both
parameters.engine and parameters.max_results are correctly preserved as nested
fields within the parameters object. This ensures the JSON structure is correct,
not just that a substring exists somewhere in the output.

@greptile-apps

greptile-apps Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 4/5

The change correctly forwards server-tool parameters for unrecognized types; wire format round-trips are sound because the typed merge path always overwrites "parameters" for function tools.

The marshal output is correct for all tested scenarios, including function tools (the type-specific merge overwrites any intermediate write from t.Parameters). The main concern is that t.Parameters is populated with the function JSON schema after unmarshaling any function tool, making the field an unreliable discriminant for "has server-tool config" in code written against this struct. This is a latent correctness issue rather than a present breakage.

The UnmarshalJSON change in core/schemas/responses.go (the unconditional t.Parameters assignment) deserves a closer look; core/schemas/responses_test.go would benefit from a function tool round-trip case.

Important Files Changed

Filename Overview
core/schemas/responses.go Adds Parameters map[string]interface{} to ResponsesTool with custom marshal/unmarshal; the new unmarshal code unconditionally populates this field for all tool types including function, where parameters is the typed JSON schema rather than a server-tool config bag.
core/schemas/responses_test.go Adds TestResponsesTool_ParametersRoundTrip covering only the openrouter:web_search unrecognized-type path; no coverage for the function type interaction or nesting assertion.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Client
    participant UnmarshalJSON
    participant ResponsesTool
    participant TypeSwitch

    Client->>UnmarshalJSON: raw JSON bytes
    UnmarshalJSON->>ResponsesTool: "t.Type = normalizeResponsesToolType(...)"
    UnmarshalJSON->>ResponsesTool: "t.Parameters = raw["parameters"] (ALL types)"
    UnmarshalJSON->>TypeSwitch: switch t.Type
    alt function
        TypeSwitch->>ResponsesTool: "t.ResponsesToolFunction = &funcTool"
        Note over ResponsesTool: t.Parameters contaminated with function schema
    else openrouter:web_search (unrecognized)
        TypeSwitch-->>ResponsesTool: no typed struct set
        Note over ResponsesTool: t.Parameters holds server-tool config
    end
    Client->>ResponsesTool: MarshalJSON()
    ResponsesTool->>ResponsesTool: emit t.Parameters under parameters
    ResponsesTool->>ResponsesTool: mergeJSONFields overwrites parameters for function
    ResponsesTool-->>Client: final JSON bytes
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"}}}%%
sequenceDiagram
    participant Client
    participant UnmarshalJSON
    participant ResponsesTool
    participant TypeSwitch

    Client->>UnmarshalJSON: raw JSON bytes
    UnmarshalJSON->>ResponsesTool: "t.Type = normalizeResponsesToolType(...)"
    UnmarshalJSON->>ResponsesTool: "t.Parameters = raw["parameters"] (ALL types)"
    UnmarshalJSON->>TypeSwitch: switch t.Type
    alt function
        TypeSwitch->>ResponsesTool: "t.ResponsesToolFunction = &funcTool"
        Note over ResponsesTool: t.Parameters contaminated with function schema
    else openrouter:web_search (unrecognized)
        TypeSwitch-->>ResponsesTool: no typed struct set
        Note over ResponsesTool: t.Parameters holds server-tool config
    end
    Client->>ResponsesTool: MarshalJSON()
    ResponsesTool->>ResponsesTool: emit t.Parameters under parameters
    ResponsesTool->>ResponsesTool: mergeJSONFields overwrites parameters for function
    ResponsesTool-->>Client: final JSON bytes
Loading

Reviews (1): Last reviewed commit: "fix(schemas/responses): forward server-t..." | Re-trigger Greptile

Comment thread core/schemas/responses.go
Comment on lines +2030 to +2032
if params, ok := raw["parameters"].(map[string]interface{}); ok {
t.Parameters = params
}

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.

P2 Parameters populated for function type tools

The raw["parameters"].(map[string]interface{}) type assertion succeeds for function tools too — their "parameters" field is a JSON Schema object and is a valid map[string]interface{}. After this code runs, t.Parameters will be non-nil for every function tool, even though the struct comment says this field is "for provider/server tools that are not modeled as a typed variant." Any future code that checks len(tool.Parameters) > 0 to decide whether to forward server-tool config will treat all function tools as having server parameters. The fix is to guard the assignment so it only fires when no typed variant claimed the type — i.e., wrap it in a check like if t.ResponsesToolFunction == nil after the switch block, or move the assignment into the default: branch of the type switch.

Comment on lines +182 to +184
if !strings.Contains(string(out), `"engine":"exa"`) {
t.Fatalf("expected parameters to be preserved on marshal, got %s", string(out))
}

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.

P2 Test assertion doesn't verify correct nesting

strings.Contains(string(out), "engine":"exa") passes even if "engine" appears at the top level of the marshaled tool rather than nested under "parameters". A stronger assertion such as strings.Contains(string(out), "parameters":{"engine":) would confirm that sjson.SetRawBytes placed the map under the "parameters" key rather than inlining its fields. Worth adding a companion test for a function type tool round-trip as well, to confirm that t.Parameters is not contaminated (and that the function schema marshals correctly when both fields are set).

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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 drops server-tool parameters (ResponsesTool has no parameters field)

6 participants