Skip to content

feat(agents): support OpenAI Agents SDK#353

Open
SantiagoDePolonia wants to merge 14 commits into
mainfrom
feat/agents-sdk
Open

feat(agents): support OpenAI Agents SDK#353
SantiagoDePolonia wants to merge 14 commits into
mainfrom
feat/agents-sdk

Conversation

@SantiagoDePolonia
Copy link
Copy Markdown
Contributor

@SantiagoDePolonia SantiagoDePolonia commented May 26, 2026

Summary

  • add typed Responses/Chat fields used by OpenAI Agents SDK and preserve unknown Responses input items for native Responses providers
  • honor store: false for local response snapshots and return clear errors for unsupported chat-translated Responses state
  • add OpenAI Agents SDK guide plus runnable examples under docs/examples/openai-agents-sdk
  • add Agents SDK support research notes and regenerate Swagger/OpenAPI

Tests

  • go test ./...
  • jq empty docs/docs.json
  • python3 -m py_compile docs/examples/openai-agents-sdk/python_basic.py docs/examples/openai-agents-sdk/python_streaming_tool.py
  • node --check docs/examples/openai-agents-sdk/javascript_basic.mjs
  • pre-commit hook: make test-race, go mod tidy, perf guard, make lint, mint validate

Summary by CodeRabbit

Release Notes

  • New Features

    • Added conversation management endpoints: Create, retrieve, update, and delete conversations via new REST endpoints.
    • Extended Responses API with new fields including service_tier, user, top_p, store, conversation, previous_response_id, prompt, structured text output (text), and context management controls.
    • Added OpenAI Agents SDK compatibility with support for streaming, tool use, and portable flows across providers.
  • Documentation

    • New guide for integrating OpenAI Agents SDK with GoModel, including Anthropic and other provider compatibility details.
    • Added Python and JavaScript example scripts demonstrating agent setup and execution.

@mintlify
Copy link
Copy Markdown

mintlify Bot commented May 26, 2026

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

Project Status Preview Updated (UTC)
gomodel 🟢 Ready View Preview May 26, 2026, 1:04 AM

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

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
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR extends GoModel's OpenAI-compatible Responses API to support OpenAI Agents SDK portable flows by adding conversation references, expanded request controls, structured-output translation, and validated chat conversion with comprehensive documentation and runnable examples.

Changes

OpenAI Agents SDK Support

Layer / File(s) Summary
API contracts and schema updates
internal/core/types.go, internal/core/conversations.go, internal/core/responses.go, cmd/gomodel/docs/docs.go, docs/openapi.json
ChatRequest gains TopP, User, ServiceTier fields; ConversationCreateRequest.Items switches to object schema and ConversationUpdateRequest.Metadata is marked required. ResponsesRequest, ResponseInputTokensRequest, and ResponseCompactRequest are extended with conversation refs, prompt/context/caching/storage controls, sampling controls, and safety identifiers. New ResponsesConversationRef schema preserves conversation ID shape.
Chat and Responses JSON serialization
internal/core/chat_json.go, internal/core/responses_json.go, internal/core/json_fields.go, internal/core/responses_json_test.go, internal/core/json_fields_test.go
Extended ChatRequest JSON to handle top_p, user, service_tier fields. Responses JSON marshaling preserves conversation string vs object shape and unknown input-item types via raw JSON. Added MergeUnknownJSONFields utility to combine base and overridden JSON fields with comprehensive round-trip and merge tests.
Chat-translated input validation
internal/providers/responses_input.go
Restricts input element types to "" and "message" for chat-translation paths; unknown types (reasoning, hosted tools) now error instead of defaulting to message handling.
Responses-to-Chat translation with field validation
internal/providers/responses_adapter.go, internal/providers/responses_adapter_test.go
Implements upfront validation rejecting stateful Responses fields (conversation, previous_response_id) for chat providers, translates text.format into Chat response_format (including json_schema), normalizes tool-choice aliases, and maps User/ServiceTier. Includes tests validating field mapping, structured-output translation, hosted-tool rejection, and unknown item-type errors.
TopP resolution across Anthropic, Bedrock, and Gemini
internal/providers/anthropic/types.go, internal/providers/anthropic/request_translation.go, internal/providers/anthropic/anthropic_test.go, internal/providers/bedrock/chat.go, internal/providers/bedrock/bedrock_test.go, internal/providers/gemini/native.go, internal/providers/gemini/gemini_test.go, internal/providers/gemini/native_schema_test.go
Standardizes typed TopP field precedence over ExtraFields["top_p"] across providers. Anthropic validates unsupported response_format/verbosity extras; Bedrock and Gemini prevent fallback overwrites. Added tests covering typed field handling, fallback parsing, and tool-validation strictness.
Utility forwarding and store:false handling
internal/providers/openai/compatible_provider.go, internal/providers/openai/openai_test.go, internal/server/translated_inference_service.go, internal/server/handlers_test.go
Refactored OpenAI-compatible /responses/input_tokens and /responses/compact to use shared utility request helper that forwards expanded Responses context. Implemented store: false behavior to skip local response snapshots. Updated tests to verify field forwarding and storage skipping.
Compatibility guides and user documentation
docs/advanced/responses-api.mdx, docs/advanced/responses-compatibility.mdx
Documents Responses routing modes (native vs chat-translated), feature-level compatibility matrices, rejection behavior for hosted tools and unsupported fields, and Agents SDK portable-flow capabilities. Explains structured-output translation and lists current limitations.
Runnable examples, probes, and doc navigation
docs/examples/openai-agents-sdk/, docs/guides/openai-agents-sdk.mdx, docs/docs.json
Added Python/JavaScript basic and streaming examples, Anthropic agents and responses probe scripts, setup README with Docker and environment variable instructions. Created comprehensive Agents SDK guide covering SDK configuration, model ID pass-through, supported flows, and local snapshot behavior. Updated navigation to include guides and compatibility pages.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ENTERPILOT/GoModel#243: Introduces core /v1/responses request models and utility endpoint serialization groundwork that this PR extends with conversation references, routing fields, and agent-SDK-compatible field forwarding.
  • ENTERPILOT/GoModel#299: Both PRs modify Gemini request translation logic (topP handling and tool/function-call strictness in internal/providers/gemini), so changes are code-level related in native API translation behavior.
  • ENTERPILOT/GoModel#163: Both PRs build on internal/core/json_fields.go; the earlier PR introduced UnknownJSONFields infrastructure and this PR adds MergeUnknownJSONFields using that same representation.

Through the gateway door I hop,
SDK calls find the perfect drop,
Chat or native, streaming true,
Agents flow from me to you. 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.49% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(agents): support OpenAI Agents SDK' accurately summarizes the main change—adding support for the OpenAI Agents SDK to the project.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agents-sdk

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 26, 2026

Greptile Summary

This PR adds OpenAI Agents SDK support on top of GoModel's Responses API. The main changes are:

  • Typed Responses fields for Agents SDK request shapes and native provider forwarding.
  • Chat-translation handling for portable Responses inputs, tools, text settings, and unsupported stateful fields.
  • Local response snapshot behavior that honors store: false.
  • Conversation and Responses utility endpoint schema updates.
  • New OpenAI Agents SDK docs, probes, and runnable examples.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Reviews (7): Last reviewed commit: "fix(agents): tighten conversation refere..." | Re-trigger Greptile

Comment thread internal/providers/responses_adapter.go Outdated
Comment thread internal/core/responses_json.go Outdated
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 26, 2026

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 88.69565% with 65 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/providers/responses_adapter.go 75.21% 16 Missing and 13 partials ⚠️
internal/core/responses_json.go 94.68% 13 Missing and 3 partials ⚠️
internal/core/json_fields.go 77.41% 7 Missing and 7 partials ⚠️
...nternal/providers/anthropic/request_translation.go 86.20% 2 Missing and 2 partials ⚠️
internal/providers/responses_input.go 66.66% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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 `@cmd/gomodel/docs/docs.go`:
- Around line 5088-5106: The generated OpenAPI schema for
core.ConversationCreateRequest currently types "items" as array<array<int>>
(byte arrays) and the update schema does not mark "metadata" as required; fix
the source model used to generate docs so that
core.ConversationCreateRequest.items is defined as an array of opaque JSON
values (e.g., array of string/JSON) rather than array<array<int>>, and ensure
the ConversationUpdateRequest (or the update contract/type) marks the "metadata"
property as required; then re-run the Swagger/Docs generator to regenerate
cmd/gomodel/docs/docs.go so the core.ConversationCreateRequest,
ConversationUpdateRequest, and their "items" and "metadata" properties are
correctly emitted.

In `@docs/openapi.json`:
- Around line 7131-7140: The OpenAPI schema for core.ConversationUpdateRequest
is missing a required declaration for the metadata field; update the
ConversationUpdateRequest schema to include required: ["metadata"] so the API
spec matches internal/core/conversations.go (which expects a *map[string]string
to detect omitted vs empty) and ensures clients know metadata must be present on
update requests.
- Around line 7099-7107: The OpenAPI schema currently documents
ConversationCreateRequest.Items as nested arrays of integers; update the schema
for the JSON field backing ConversationCreateRequest.Items (which is
[]json.RawMessage) so each entry is treated as opaque JSON rather than []int.
Locate the block that currently nests "type":"array" with inner
"type":"array"/"type":"integer" and replace the inner item schema with an opaque
JSON schema (e.g. an unconstrained item or a oneOf covering
object/array/string/number/boolean/null) so codegen clients will emit arbitrary
JSON values instead of byte arrays.

In `@internal/providers/responses_adapter.go`:
- Around line 69-108: The Conversation field on core.ResponsesRequest is
currently typed as any which prevents compile-time checks; change
core.ResponsesRequest.Conversation to a concrete type (e.g., *string or a
dedicated struct representing the conversation shape) and update all call sites
and JSON unmarshalling logic accordingly so validation in
validateResponsesRequestForChatTranslation can remain a nil-check; ensure
references in validateResponsesRequestForChatTranslation, any request
decoding/parsing code, and downstream handlers are updated to use the new
concrete type and that unsupportedResponsesChatTranslationField continues to be
used for rejected fields.
🪄 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

Run ID: 576fd7ec-8f1e-44b7-8554-57af88e2fb7d

📥 Commits

Reviewing files that changed from the base of the PR and between 7c84044 and 986f702.

📒 Files selected for processing (20)
  • cmd/gomodel/docs/docs.go
  • docs/dev/agents-sdk-support.md
  • docs/dev/claude-agent-sdk-support.md
  • docs/docs.json
  • docs/examples/openai-agents-sdk/README.md
  • docs/examples/openai-agents-sdk/javascript_basic.mjs
  • docs/examples/openai-agents-sdk/python_basic.py
  • docs/examples/openai-agents-sdk/python_streaming_tool.py
  • docs/guides/openai-agents-sdk.mdx
  • docs/openapi.json
  • internal/core/chat_json.go
  • internal/core/responses.go
  • internal/core/responses_json.go
  • internal/core/responses_json_test.go
  • internal/core/types.go
  • internal/providers/responses_adapter.go
  • internal/providers/responses_adapter_test.go
  • internal/providers/responses_input.go
  • internal/server/handlers_test.go
  • internal/server/translated_inference_service.go

Comment thread cmd/gomodel/docs/docs.go
Comment thread docs/openapi.json
Comment thread docs/openapi.json
Comment on lines +69 to +108
func validateResponsesRequestForChatTranslation(req *core.ResponsesRequest) error {
if strings.TrimSpace(req.PreviousResponseID) != "" {
return unsupportedResponsesChatTranslationField("previous_response_id")
}
if req.Conversation != nil {
return unsupportedResponsesChatTranslationField("conversation")
}
if len(req.Include) > 0 {
return unsupportedResponsesChatTranslationField("include")
}
if req.Prompt != nil {
return unsupportedResponsesChatTranslationField("prompt")
}
if strings.TrimSpace(req.Truncation) != "" {
return unsupportedResponsesChatTranslationField("truncation")
}
if req.Text != nil {
return unsupportedResponsesChatTranslationField("text")
}
if strings.TrimSpace(req.PromptCacheRetention) != "" {
return unsupportedResponsesChatTranslationField("prompt_cache_retention")
}
if req.ContextManagement != nil {
return unsupportedResponsesChatTranslationField("context_management")
}
if req.TopLogprobs != nil {
return unsupportedResponsesChatTranslationField("top_logprobs")
}
if strings.TrimSpace(req.SafetyIdentifier) != "" {
return unsupportedResponsesChatTranslationField("safety_identifier")
}
return nil
}

func unsupportedResponsesChatTranslationField(field string) error {
return core.NewInvalidRequestError(
fmt.Sprintf("responses field %q is only supported by native Responses providers; use an OpenAI-compatible provider or passthrough for this request", field),
nil,
)
}
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the type definition of ResponsesRequest.Conversation
ast-grep --pattern $'type ResponsesRequest struct {
  $$$
  Conversation $FIELD_TYPE
  $$$
}'

Repository: ENTERPILOT/GoModel

Length of output: 3108


Type ResponsesRequest.Conversation instead of any
The req.Conversation != nil check is correct because internal/core.ResponsesRequest.Conversation is typed as any; the issue is that the API payload field is untyped. Change Conversation to a concrete type (e.g., *string or a dedicated struct) so validation and downstream handling are type-safe.

🤖 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/responses_adapter.go` around lines 69 - 108, The
Conversation field on core.ResponsesRequest is currently typed as any which
prevents compile-time checks; change core.ResponsesRequest.Conversation to a
concrete type (e.g., *string or a dedicated struct representing the conversation
shape) and update all call sites and JSON unmarshalling logic accordingly so
validation in validateResponsesRequestForChatTranslation can remain a nil-check;
ensure references in validateResponsesRequestForChatTranslation, any request
decoding/parsing code, and downstream handlers are updated to use the new
concrete type and that unsupportedResponsesChatTranslationField continues to be
used for rejected fields.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 `@docs/openapi.json`:
- Around line 7655-7662: The "conversation" schema's description says it accepts
either a conversation ID string or an object with id, but the schema currently
only references core.ResponsesConversationRef (object), breaking clients that
send strings; update the "conversation" schema (and the other occurrences noted)
to allow both by using a oneOf/anyOf that includes the string type and the
existing $ref (e.g., oneOf: [{ "type": "string" }, { "$ref":
"`#/components/schemas/core.ResponsesConversationRef`" }]) so generated clients
can send either a plain ID string or the object with id.
🪄 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

Run ID: 4d27f74b-707a-4fc2-8b9c-84abd2f26abb

📥 Commits

Reviewing files that changed from the base of the PR and between 986f702 and a964078.

📒 Files selected for processing (11)
  • cmd/gomodel/docs/docs.go
  • docs/dev/agents-sdk-support.md
  • docs/openapi.json
  • internal/core/conversations.go
  • internal/core/responses.go
  • internal/core/responses_json.go
  • internal/core/responses_json_test.go
  • internal/providers/openai/compatible_provider.go
  • internal/providers/openai/openai_test.go
  • internal/providers/responses_adapter.go
  • internal/providers/responses_adapter_test.go

Comment thread docs/openapi.json
SantiagoDePolonia and others added 2 commits May 30, 2026 19:53
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	cmd/gomodel/docs/docs.go
#	docs/openapi.json
SantiagoDePolonia and others added 2 commits June 1, 2026 00:04
… items

Unknown Responses input item types stored the full payload in Raw and
also extracted every non-type field into ExtraFields. Marshaling then
merged both, emitting duplicate JSON keys (e.g. id, summary) to native
Responses providers. Preserve the verbatim Raw on decode and reserve
ExtraFields for metadata added after decoding, so a round trip emits
each field once.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Chat-translated providers previously rejected any Responses request
carrying structured text output settings. Map text.format onto the Chat
Completions response_format (json_schema fields are nested under a
json_schema member; json_object passes through) and forward
text.verbosity, so the Agents SDK can request structured outputs on
non-native providers. Unknown text formats still return a clear error.

Adds core.MergeUnknownJSONFields to inject the derived passthrough
fields, reusing the same extras path providers already read for
response_format.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
internal/core/responses_json.go (1)

448-505: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset ResponsesInputElement before repopulating it.

UnmarshalJSON only assigns fields that are present in the new payload. Reusing the same ResponsesInputElement leaves stale Status, CallID, Content, Raw, and especially ExtraFields behind; with the new unknown-type fast path, a second marshal can merge unrelated keys into the preserved raw item.

Suggested fix
 func (e *ResponsesInputElement) UnmarshalJSON(data []byte) error {
 	var raw map[string]json.RawMessage
 	if err := json.Unmarshal(data, &raw); err != nil {
 		return err
 	}
+
+	*e = ResponsesInputElement{}
 
 	if v, ok := raw["type"]; ok {
 		_ = json.Unmarshal(v, &e.Type)
 	}
🤖 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/core/responses_json.go` around lines 448 - 505, UnmarshalJSON for
ResponsesInputElement must clear any existing state before repopulating to avoid
carrying over stale fields (Status, CallID, Content, Raw, ExtraFields); after
successfully decoding into raw (the local variable map[string]json.RawMessage)
reset the receiver with *e = ResponsesInputElement{} and then proceed with the
existing logic that reads raw and assigns to e.Type, e.Name, e.CallID, e.Status,
e.Arguments, e.Output, e.Role, e.Content or sets e.Raw; this ensures ExtraFields
and other fields are not preserved across calls to
ResponsesInputElement.UnmarshalJSON.
♻️ Duplicate comments (1)
docs/openapi.json (1)

7913-7919: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

conversation is still modeled as object-only.

The description says these fields accept either a conversation ID string or an object with id, but the schema still only references core.ResponsesConversationRef, and that ref still does not require id. Generated clients will reject valid string IDs and may allow {} for the object form.

Also applies to: 8086-8092, 8240-8246, 8296-8302

🤖 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 `@docs/openapi.json` around lines 7913 - 7919, The "conversation" schema
currently only references core.ResponsesConversationRef and that ref does not
require id, so clients reject valid string IDs and may accept empty objects;
update the schemas (e.g., the "conversation" property and the referenced
core.ResponsesConversationRef) to allow either a string ID or an object with a
required id by changing the "conversation" schema to a oneOf (or anyOf) that
accepts { type: "string" } or an object reference to
core.ResponsesConversationRef, and modify core.ResponsesConversationRef to
require the id property (add "required": ["id"] or ensure the object schema
enforces id) so both forms (string or { id: "..." }) validate correctly; apply
the same change to the other occurrences mentioned (lines referencing the same
"conversation" schema at 8086-8092, 8240-8246, 8296-8302).
🤖 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 `@docs/guides/openai-agents-sdk.mdx`:
- Around line 133-157: The snippet uses top-level await and an undefined agent;
wrap the Runner.run call inside an async function (e.g., async def main()) and
call it with asyncio.run(main()), and either provide a minimal agent definition
or add a comment that agent is defined earlier; specifically update the block
that constructs AsyncOpenAI, RunConfig/MultiProvider and the Runner.run(...)
invocation so Runner.run is called from within main() (use asyncio.run) and
ensure agent is declared or referenced as continuing from a prior example.

In `@internal/core/json_fields.go`:
- Around line 83-92: The current code unmarshals base.raw into a map which
rewrites/loses the original raw formatting; instead build a merged raw object
without decoding: if base.IsEmpty() start with "{" otherwise scan/copy
base.raw's object members while skipping any keys present in additions (so
additions win), then append the additions as raw fields (encode keys with
json.Marshal to escape) separated by commas and close the object; replace the
json.Unmarshal/merged map logic in the block using base.raw, additions, and
returning UnknownJSONFieldsFromMap/UnknownJSONFields with this raw-object
merging using bytes.Buffer/json.RawMessage to preserve original byte layout for
unchanged fields.

In `@internal/providers/anthropic/anthropic_test.go`:
- Around line 1345-1380: The test
TestConvertToAnthropicRequest_RejectsUnsupportedChatExtras currently only checks
the error text; update it to assert the error shape too: after calling
convertToAnthropicRequest, ensure err is a *core.GatewayError, cast it (e.g. ge
:= err.(*core.GatewayError) or use errors.As), then assert ge.Code ==
"invalid_request_error" and ge.Status == 400 in addition to verifying the error
message contains tt.field; keep the existing message substring check as well so
the test matches the neighboring validation tests.

In `@internal/providers/bedrock/bedrock_test.go`:
- Around line 273-290: Update the test to cover precedence: add a case to
TestBuildConverseParts_TopPFromTypedField (or a new test in
internal/providers/bedrock/bedrock_test.go) where both the typed field req.TopP
and a conflicting value in req.ExtraFields (e.g., map with "top_p": 0.3) are
set, call buildConverseParts(req), and assert parts.infCfg.TopP equals the typed
value (0.8) not the ExtraFields value; reference buildConverseParts and
parts.infCfg.TopP/awssdk.ToFloat32 for locating assertions.

In `@internal/providers/responses_adapter.go`:
- Around line 106-108: validateResponsesToolsForChatTranslation blocks hosted
tool definitions but req.ToolChoice is not validated, allowing hosted
tool_choice objects to slip through; update
validateResponsesRequestForChatTranslation to also call
validateResponsesToolsForChatTranslation (or add a new validation step) on
req.ToolChoice (and ensure normalizeResponsesToolChoiceForChat does not return
hosted definitions unchanged) so any hosted tool_choice like
{"type":"file_search"} is rejected the same way as hosted entries in req.Tools;
apply the same fix for the equivalent logic path handled around
normalizeResponsesToolChoiceForChat (the other occurrence mentioned in the
comment).

---

Outside diff comments:
In `@internal/core/responses_json.go`:
- Around line 448-505: UnmarshalJSON for ResponsesInputElement must clear any
existing state before repopulating to avoid carrying over stale fields (Status,
CallID, Content, Raw, ExtraFields); after successfully decoding into raw (the
local variable map[string]json.RawMessage) reset the receiver with *e =
ResponsesInputElement{} and then proceed with the existing logic that reads raw
and assigns to e.Type, e.Name, e.CallID, e.Status, e.Arguments, e.Output,
e.Role, e.Content or sets e.Raw; this ensures ExtraFields and other fields are
not preserved across calls to ResponsesInputElement.UnmarshalJSON.

---

Duplicate comments:
In `@docs/openapi.json`:
- Around line 7913-7919: The "conversation" schema currently only references
core.ResponsesConversationRef and that ref does not require id, so clients
reject valid string IDs and may accept empty objects; update the schemas (e.g.,
the "conversation" property and the referenced core.ResponsesConversationRef) to
allow either a string ID or an object with a required id by changing the
"conversation" schema to a oneOf (or anyOf) that accepts { type: "string" } or
an object reference to core.ResponsesConversationRef, and modify
core.ResponsesConversationRef to require the id property (add "required": ["id"]
or ensure the object schema enforces id) so both forms (string or { id: "..." })
validate correctly; apply the same change to the other occurrences mentioned
(lines referencing the same "conversation" schema at 8086-8092, 8240-8246,
8296-8302).
🪄 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

Run ID: 98038e4d-ea4b-43f3-90f5-ecd2d8721da6

📥 Commits

Reviewing files that changed from the base of the PR and between a964078 and e2529b9.

📒 Files selected for processing (25)
  • cmd/gomodel/docs/docs.go
  • docs/advanced/responses-api.mdx
  • docs/advanced/responses-compatibility.mdx
  • docs/dev/agents-sdk-support.md
  • docs/docs.json
  • docs/examples/openai-agents-sdk/README.md
  • docs/examples/openai-agents-sdk/anthropic_agents_probe.py
  • docs/examples/openai-agents-sdk/anthropic_responses_probe.py
  • docs/guides/openai-agents-sdk.mdx
  • docs/openapi.json
  • internal/core/json_fields.go
  • internal/core/json_fields_test.go
  • internal/core/responses_json.go
  • internal/core/responses_json_test.go
  • internal/core/types.go
  • internal/providers/anthropic/anthropic_test.go
  • internal/providers/anthropic/request_translation.go
  • internal/providers/bedrock/bedrock_test.go
  • internal/providers/bedrock/chat.go
  • internal/providers/gemini/gemini_test.go
  • internal/providers/gemini/native.go
  • internal/providers/gemini/native_schema_test.go
  • internal/providers/openai/compatible_provider.go
  • internal/providers/responses_adapter.go
  • internal/providers/responses_adapter_test.go

Comment thread docs/guides/openai-agents-sdk.mdx
Comment thread internal/core/json_fields.go Outdated
Comment thread internal/providers/anthropic/anthropic_test.go
Comment thread internal/providers/bedrock/bedrock_test.go
Comment thread internal/providers/responses_adapter.go
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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)

360-367: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t reject noop chat extras just because the key is present.

validateAnthropicUnsupportedChatExtras currently fails any request where response_format or verbosity exists in ExtraFields, including explicit null values. That turns valid OpenAI-compatible no-op inputs like {"response_format":null} into 400s. response_format: {"type":"text"} is another default/no-op case that Anthropic can safely ignore rather than reject.

Minimal fix
 func validateAnthropicUnsupportedChatExtras(extra core.UnknownJSONFields) error {
 	for _, field := range []string{"response_format", "verbosity"} {
-		if extra.Lookup(field) != nil {
+		raw := bytes.TrimSpace(extra.Lookup(field))
+		if len(raw) == 0 || bytes.Equal(raw, []byte("null")) {
+			continue
+		}
+		if field == "response_format" {
+			var cfg struct {
+				Type string `json:"type"`
+			}
+			if err := json.Unmarshal(raw, &cfg); err == nil && (cfg.Type == "" || cfg.Type == "text") {
+				continue
+			}
+		}
+		if len(raw) != 0 {
 			return core.NewInvalidRequestError("chat field "+field+" is not supported by Anthropic translation", nil)
 		}
 	}
 	return nil
 }

As per coding guidelines: Accept requests generously and adapt them to each provider's requirements; preserve the OpenAI-compatible public API when changing provider behavior.

internal/core/responses_json.go (1)

163-169: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't let Raw override a later Conversation.ID update.

Line 164 always re-emits c.Raw when it exists, so decoding conversation, changing req.Conversation.ID, and then marshaling still sends the old ID. That can forward the wrong conversation reference or persist stale state. Preserve only the original shape (string vs object), or rewrite the stored raw value when ID changes, before marshaling.

🤖 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/core/responses_json.go` around lines 163 - 169,
ResponsesConversationRef.MarshalJSON currently always returns
cloneRawMessage(c.Raw) when Raw is non-empty, which can re-emit a stale ID;
change it to preserve the original shape but honor an updated c.ID: if c.Raw is
empty behave as today (json.Marshal(c.ID) when ID != ""), otherwise detect
whether c.Raw is a JSON string or object (e.g., trim and inspect first non-space
char); if it was a string, return json.Marshal(c.ID) so the shape remains a
string but with the new ID; if it was an object, unmarshal c.Raw into a
map[string]any, set or replace the "id" field with c.ID and re-marshal that map;
only fall back to cloneRawMessage(c.Raw) when c.ID is empty or when parsing
fails, and keep using cloneRawMessage to preserve raw bytes when appropriate —
update the function ResponsesConversationRef.MarshalJSON and reuse
cloneRawMessage/json.Marshal accordingly.
🤖 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/core/json_fields_test.go`:
- Around line 113-141: Add table-driven tests for MergeUnknownJSONFields to
cover its error paths: include cases where base.raw is malformed JSON (e.g.,
truncated/invalid bytes) expecting an error, where base.raw is valid JSON but
not an object (e.g., array or string) causing appendUnknownJSONMembers to fail,
and where one of the additions contains invalid JSON so MergeUnknownJSONFields
returns an error; reference the UnknownJSONFields struct for constructing base,
call MergeUnknownJSONFields with those inputs, and assert that an error is
returned (and optionally assert that the error message or type indicates a JSON
unmarshal/non-object failure) rather than a successful merge so these 400-paths
remain tested.

In `@internal/providers/responses_adapter_test.go`:
- Around line 471-488: Add table-driven test cases in the same test table in
responses_adapter_test.go that cover object-form aliases for ToolChoice: add
entries named like "tool_choice none alias", "tool_choice auto alias", and
"tool_choice required alias" each using req: &core.ResponsesRequest{Model:
"test-model", Input: "Hello", ToolChoice: map[string]any{"type":"none"}} (and
similarly for "auto" and "required") and set want to the normalized
chat-compatible string ("none", "auto", "required"). This ensures the code paths
in the ResponsesRequest -> response adapter that read ToolChoice (look for the
test harness and the core.ResponsesRequest usage in this table) continue to
accept object-form inputs and normalize them to the expected string values.

In `@internal/providers/responses_adapter.go`:
- Around line 125-136: The validator
validateResponsesToolChoiceForChatTranslation currently rejects map-shaped
tool_choice unless type == "function"; instead, accept and normalize
OpenAI-style aliases by allowing "none", "auto", "required" (and "function")
when choice is a map and treat them as valid (i.e., return nil) rather than
calling unsupportedResponsesChatTranslationTool; update the logic to read
choiceMap["type"], trim it, switch/compare against
"function","none","auto","required" and only call
unsupportedResponsesChatTranslationTool for truly unsupported values; apply the
same normalization change to the analogous validator referenced around lines
284-306.

---

Outside diff comments:
In `@internal/core/responses_json.go`:
- Around line 163-169: ResponsesConversationRef.MarshalJSON currently always
returns cloneRawMessage(c.Raw) when Raw is non-empty, which can re-emit a stale
ID; change it to preserve the original shape but honor an updated c.ID: if c.Raw
is empty behave as today (json.Marshal(c.ID) when ID != ""), otherwise detect
whether c.Raw is a JSON string or object (e.g., trim and inspect first non-space
char); if it was a string, return json.Marshal(c.ID) so the shape remains a
string but with the new ID; if it was an object, unmarshal c.Raw into a
map[string]any, set or replace the "id" field with c.ID and re-marshal that map;
only fall back to cloneRawMessage(c.Raw) when c.ID is empty or when parsing
fails, and keep using cloneRawMessage to preserve raw bytes when appropriate —
update the function ResponsesConversationRef.MarshalJSON and reuse
cloneRawMessage/json.Marshal accordingly.
🪄 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

Run ID: e45845dd-1351-4f51-85cb-b55b5387a5ac

📥 Commits

Reviewing files that changed from the base of the PR and between e2529b9 and 9332cfb.

📒 Files selected for processing (11)
  • docs/guides/openai-agents-sdk.mdx
  • internal/core/json_fields.go
  • internal/core/json_fields_test.go
  • internal/core/responses_json.go
  • internal/core/responses_json_test.go
  • internal/providers/anthropic/anthropic_test.go
  • internal/providers/anthropic/request_translation.go
  • internal/providers/anthropic/types.go
  • internal/providers/bedrock/bedrock_test.go
  • internal/providers/responses_adapter.go
  • internal/providers/responses_adapter_test.go

Comment thread internal/core/json_fields_test.go
Comment thread internal/providers/responses_adapter_test.go
Comment thread internal/providers/responses_adapter.go
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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/core/json_fields.go`:
- Around line 84-86: Remove the redundant call to validateUnknownJSONObject on
additionFields.raw immediately after UnknownJSONFieldsFromMap builds it: since
UnknownJSONFieldsFromMap guarantees a well-formed JSON object (or returns empty)
and appendUnknownJSONMembers performs necessary validation later, delete the
validateUnknownJSONObject(additionFields.raw) check and its error path; keep
UnknownJSONFieldsFromMap, the UnknownJSONFields construction, and the later
validation in appendUnknownJSONMembers intact to avoid double-validation.

In `@internal/core/responses_json.go`:
- Around line 164-181: The current marshaling returns cloneRawMessage(trimmed)
when c.ID == "" which preserves an old ID in c.Raw and hides JSON corruption by
returning raw on unmarshal errors; update the logic so that you no longer
short-circuit on c.ID == "" — always inspect trimmed and handle each shape: for
'"' return json.Marshal(c.ID) (which will produce an empty string when ID is
cleared); for '{' attempt json.Unmarshal into obj and if c.ID == "" delete
obj["id"] otherwise set obj["id"]=c.ID, then json.Marshal(obj); and on
json.Unmarshal errors return the error (propagate it) instead of silently
returning cloneRawMessage(trimmed); use the existing symbols c.Raw, c.ID,
cloneRawMessage, json.Unmarshal/json.Marshal to locate and modify the code.

In `@internal/providers/responses_adapter.go`:
- Around line 293-299: The switch on choiceType (variable names choiceType and
choice) contains an empty case "function" that intentionally continues to the
normalization logic; make this explicit by adding a clear fallthrough comment
(e.g. "// fallthrough: allow 'function' to be handled by the default
normalization below") inside the case "function": block so future maintainers
understand the intent.
🪄 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

Run ID: 8eea4024-8a25-43b2-88cf-9af11570a73f

📥 Commits

Reviewing files that changed from the base of the PR and between 9332cfb and a7b87b2.

📒 Files selected for processing (8)
  • internal/core/json_fields.go
  • internal/core/json_fields_test.go
  • internal/core/responses_json.go
  • internal/core/responses_json_test.go
  • internal/providers/anthropic/anthropic_test.go
  • internal/providers/anthropic/request_translation.go
  • internal/providers/responses_adapter.go
  • internal/providers/responses_adapter_test.go

Comment on lines +84 to +86
if err := validateUnknownJSONObject(additionFields.raw); err != nil {
return UnknownJSONFields{}, err
}
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.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Remove redundant validation of additions.

Line 84 validates additionFields.raw immediately after UnknownJSONFieldsFromMap constructs it at line 83. Since UnknownJSONFieldsFromMap always builds a well-formed JSON object (or returns empty), this validation will never fail and is redundant with the validation performed later in appendUnknownJSONMembers at line 148.

♻️ Optional simplification
 func MergeUnknownJSONFields(base UnknownJSONFields, additions map[string]json.RawMessage) (UnknownJSONFields, error) {
 	if len(additions) == 0 {
 		return base, nil
 	}
 	additionFields := UnknownJSONFieldsFromMap(additions)
-	if err := validateUnknownJSONObject(additionFields.raw); err != nil {
-		return UnknownJSONFields{}, err
-	}
 	if base.IsEmpty() {
 		return additionFields, nil
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if err := validateUnknownJSONObject(additionFields.raw); err != nil {
return UnknownJSONFields{}, err
}
func MergeUnknownJSONFields(base UnknownJSONFields, additions map[string]json.RawMessage) (UnknownJSONFields, error) {
if len(additions) == 0 {
return base, nil
}
additionFields := UnknownJSONFieldsFromMap(additions)
if base.IsEmpty() {
return additionFields, nil
}
🤖 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/core/json_fields.go` around lines 84 - 86, Remove the redundant call
to validateUnknownJSONObject on additionFields.raw immediately after
UnknownJSONFieldsFromMap builds it: since UnknownJSONFieldsFromMap guarantees a
well-formed JSON object (or returns empty) and appendUnknownJSONMembers performs
necessary validation later, delete the
validateUnknownJSONObject(additionFields.raw) check and its error path; keep
UnknownJSONFieldsFromMap, the UnknownJSONFields construction, and the later
validation in appendUnknownJSONMembers intact to avoid double-validation.

Comment thread internal/core/responses_json.go
Comment thread internal/providers/responses_adapter.go
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.

2 participants