Skip to content

Handle universal ToolDiscoveryResult type #312

Merged
Ken Jiang (knjiang) merged 23 commits into
mainfrom
06-19-handle_openai_responses_tool_search_input_items
Jun 23, 2026
Merged

Handle universal ToolDiscoveryResult type #312
Ken Jiang (knjiang) merged 23 commits into
mainfrom
06-19-handle_openai_responses_tool_search_input_items

Conversation

@knjiang

@knjiang Ken Jiang (knjiang) commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds cross-provider support for Responses tool_search / deferred tool discovery, including Anthropic tool-search
translation and universal message/tool representations for discovery calls and results.

Also tightens Anthropic validation to continue rejecting OpenAI-shaped payloads now that unknown fields are captured
in extras.

Changes

  • Add universal ToolDiscoveryCall / ToolDiscoveryResult content parts.
  • Add deferred tool availability on UniversalTool.
  • Convert OpenAI Responses tool_search_call and tool_search_output to/from universal messages.
  • Convert Anthropic tool-search server tool use/results to/from universal messages.
  • Preserve discovered deferred tool definitions through follow-up transforms.
  • Move provider-specific discovery logic into openai/tool_discovery.rs and anthropic/tool_discovery.rs to keep
    converters/adapters readable.
  • Add payload coverage for responsesToolSearchInputParam.
  • Require max_tokens in Anthropic validation and reject OpenAI/gateway-only fields like reasoning_enabled,
    suffix_messages, and chat_template_kwargs.

Testing

cargo test -p lingua validation::anthropic
cargo test -p lingua responses_tool_search_input_items_convert_to_universal
cargo test -p lingua test_responses_preserves_deferred_namespace_tool_search_tools
cargo test -p lingua test_transform_request_responses_discovery_tools_to_anthropic
cargo test -p lingua test_dedup_preserves_discovery_results_with_different_tool_definitions
cargo fmt
git diff --check

Copy link
Copy Markdown
Contributor Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@knjiang Ken Jiang (knjiang) marked this pull request as ready for review June 19, 2026 05:04

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c27f502bf5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment on lines +987 to +993
let provider_executed = if matches!(
input.execution.as_ref(),
Some(openai::ToolSearchExecutionType::Server)
) {
Some(true)
} else {
None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve client tool_search items on re-export

When a Responses request contains a client-executed tool_search_call, this branch records it as a normal non-provider-executed tool call. The Responses exporter treats those as function_call items, so importing and then re-emitting the same history changes tool_search_call/tool_search_output into ordinary function-call items and loses the typed execution/tools wire shape that OpenAI expects for tool-search history. Preserve enough typed state here, or teach the Responses exporter to emit tool_search_* for tool_name == "tool_search".

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 964b4b0d87

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

}));
}

let anthropic_tool = Tool::try_from(tool)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize Responses discovery tools before Anthropic serialization

When a Responses request uses the normal dynamic-discovery tool config (tools entries like {"type":"tool_search"} or {"type":"namespace"}), parse_openai_responses_tools_array stores those as BuiltinToolProvider::Responses. This new Anthropic serialization path only special-cases Anthropic builtins before calling Tool::try_from, which rejects Responses builtins, so Responses→Anthropic fails instead of emitting the equivalent tool_search_tool_regex and deferred tools. Normalize or filter Responses discovery tools before this conversion.

Useful? React with 👍 / 👎.

Comment on lines +275 to +276
ToolContentPart::ToolResult(result) => Some(result),
ToolContentPart::ToolDiscoveryResult(_) => None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid emitting empty Bedrock messages for discovery results

For a Responses history containing a tool_search_output, the universal Tool message contains only ToolDiscoveryResult; this filter drops every part, and universal_to_bedrock_messages still pushes a Bedrock user message with content: []. That produces an invalid/meaningless Converse request instead of either dropping discovery-only messages at the message level or returning an explicit unsupported-mapping error.

Useful? React with 👍 / 👎.

Comment on lines +249 to +250
for item in &result.tools {
item.tool_name.hash(hasher);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include discovered tool definitions in the dedup key

If two imported tool_search_output messages have the same call id and tool names but different returned tool definitions (for example a schema/strictness change), deduplication treats them as identical because only tool_name is hashed. Since ToolDiscoveryResultItem.tool carries the actual definition that is later re-exported to Responses/Anthropic, the second result can be dropped and leave stale tool metadata in the conversation.

Useful? React with 👍 / 👎.

@knjiang

Copy link
Copy Markdown
Contributor Author

Codex (@codex) review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 919f99456c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment on lines +34 to +38
if request.messages.is_none() {
return Err(ValidationError::DeserializationFailed(
"missing field `messages`".to_string(),
));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Enforce required max_tokens in Anthropic validation

This required-field check stops after model and messages, but AnthropicParams made max_tokens optional even though Anthropic Messages requests require it (and try_parse_anthropic still calls required_max_tokens). As a result, validate_anthropic_request now accepts {model, messages} payloads that the old generated CreateMessageParams validation rejected and that Anthropic will not accept.

Useful? React with 👍 / 👎.

Comment on lines +21 to +23
"store",
"stream_options",
"top_logprobs",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject all OpenAI-only fields in Anthropic validation

This validation-only OpenAI field list is shorter than the one used by providers/anthropic/detect.rs; it omits gateway/chat fields such as reasoning_enabled, suffix_messages, and chat_template_kwargs. Because AnthropicParams now flattens unknown fields into extras, an OpenAI-compatible request with model, messages, max_tokens, and one of those extensions will pass validate_anthropic_request, reintroducing the cross-provider false positive this guard is meant to prevent.

Useful? React with 👍 / 👎.

@knjiang Ken Jiang (knjiang) force-pushed the 06-19-handle_openai_responses_tool_search_input_items branch from 919f994 to 65d3afc Compare June 22, 2026 14:46

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 65d3afc0f4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment on lines +2039 to +2040
if name == "tool_search" {
tool_discovery::input_call_from_tool_arguments(call_id, arguments, id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid keying discovery calls on the function name

When a normal function/tool is named tool_search (for example a Chat Completions function call with a tool of that name), this name-only branch converts it to a Responses tool_search_call instead of a function_call. input_call_from_tool_arguments then expects nested arguments/status/execution fields, so a regular argument map like {"query":"..."} is dropped and the target sees a discovery call rather than the original function call. Use the new ToolDiscoveryCall representation or explicit provider metadata to select this path instead of the function name alone.

Useful? React with 👍 / 👎.

if capabilities.requires_thought_signature_for_function_call_history {
add_dummy_thought_signatures_for_transferred_function_call_history(&mut messages);
}
messages.retain(|message| !is_discovery_only_message(message));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Re-check Google contents after dropping discovery messages

For a Responses follow-up that contains only dynamic-discovery history, such as previous_response_id plus a tool_search_output, messages is non-empty when the earlier Google validation runs, but this retain removes every remaining message and the adapter then serializes contents: []. Google requests require at least one content message, so this should re-check after filtering and return an unsupported/validation error instead of producing an invalid request.

Useful? React with 👍 / 👎.

@knjiang Ken Jiang (knjiang) changed the title Handle OpenAI Responses tool_search input items Handle universal ToolDiscoveryResult type Jun 22, 2026
@knjiang Ken Jiang (knjiang) force-pushed the 06-19-handle_openai_responses_tool_search_input_items branch from 65d3afc to 38c4a5f Compare June 22, 2026 15:22
@knjiang

Copy link
Copy Markdown
Contributor Author

Codex (@codex) review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2f648052f8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

pub(super) fn message_from_output_output(
item: openai::OutputItem,
) -> Result<Message, ConvertError> {
let tool_call_id = item.call_id.or_else(|| item.id.clone()).ok_or_else(|| {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve pairing for null tool-search call IDs

When a Responses response contains server-executed tool_search_call/tool_search_output items with call_id: null, this fallback assigns the output's own id as tool_call_id, while part_from_output_call does the same for the call item. Because those item IDs differ, the universal ToolDiscoveryCall and ToolDiscoveryResult no longer match, so exporting to Anthropic emits a server_tool_use and tool_search_tool_result with different IDs. Track the preceding tool-search call or otherwise preserve a shared synthetic ID for this null-call_id response shape.

Useful? React with 👍 / 👎.

file_id: None,
});
}
ToolContentPart::ToolDiscoveryResult(discovery_result) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject unpaired tool-search results for Anthropic

For a valid Responses follow-up that uses an item_reference plus a tool_search_output, there is no adjacent ToolDiscoveryCall in req.messages; this branch still serializes the discovery result as a standalone Anthropic user message containing tool_search_tool_result. The later merge helper only moves it into assistant content when the matching call is present immediately before it, so reference-based follow-ups produce an invalid Anthropic transcript instead of returning an unsupported-mapping error.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ce9398a43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment thread crates/lingua/src/providers/openai/tool_discovery.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a6bd1f6b53

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment thread crates/lingua/src/providers/anthropic/tool_discovery.rs Outdated
Comment thread crates/lingua/src/providers/anthropic/tool_discovery.rs

@CLowbrow Alex Z (CLowbrow) 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.

I think you can merge this once codex stops complaining

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9b495926fa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment on lines +616 to +618
}) if tool_type == "web_search_20250305"
|| tool_type == "google_search"
|| is_anthropic_tool_search_builtin_type(&tool_type) => {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Skip Responses discovery tools for Chat export

When a Responses request uses dynamic discovery, ResponsesAdapter::request_to_universal stores top-level namespace/tool_search entries as BuiltinToolProvider::Responses; transforming that request to Chat Completions then calls tools_to_openai_chat_value, where to_openai_chat_value returns UnsupportedToolType for those builtins and this catch only skips web/search and Anthropic tool-search types. The new responsesToolSearchInputParam case is expected to drop discovery-only history for Chat, but it will fail before message conversion unless the Responses discovery builtins are also skipped or otherwise marked as an intentional unsupported loss.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dbf4a5fc92

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

result.push(openai::InputItem {
role: None,
content: None,
content: Some(openai::InputItemContent::InputContentArray(vec![])),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep reasoning items content-free

When replaying any Responses history that contains a reasoning item (for example a GPT/o-series follow-up), this now serializes the item as {"type":"reasoning","content":[],"summary":...}. The prior conversion and captured Responses payloads omit content for reasoning items; adding an empty message-content array changes the union shape and can make follow-up requests invalid or break round-trip parity. Leave content as None and carry the summary/encrypted fields only.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6b75854e7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment on lines 125 to 129
fn has_anthropic_source_markers(request: &AnthropicParams) -> bool {
request.system.is_some()
|| request.thinking.is_some()
|| request.output_config.is_some()
|| request.cache_control.is_some()
|| request.top_k.is_some()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep cache_control as an Anthropic source marker

When a Claude Code-style Messages payload uses only top-level cache_control as its Anthropic-specific marker and includes a system-role message with a non-Anthropic model, try_parse_anthropic_source now rejects it because AnthropicParams captures cache_control in extras while this marker check no longer looks there. That regresses the source-detection path for those payloads, so they can fall through to another adapter instead of preserving Anthropic message semantics.

Useful? React with 👍 / 👎.

Comment on lines +30 to +31
pub fn validate_anthropic_request(json: &str) -> Result<AnthropicParams, ValidationError> {
let request: AnthropicParams = validate_json(json)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate known Anthropic extras with typed fields

Because request validation now deserializes into the partial AnthropicParams, known Anthropic fields that are not declared there, such as top-level cache_control, container, inference_geo, or the enum-shaped service_tier, are accepted in extras with any JSON shape. For example {..., "cache_control": "bad"} now passes validate_anthropic_request even though the generated CreateMessageParams typed this field and would reject it, weakening the validation API for real Anthropic request syntax.

Useful? React with 👍 / 👎.

This reverts commit e6b7585.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

"max_completion_tokens",
];

P2 Badge Reject legacy OpenAI function fields

When an OpenAI Chat Completions request uses the legacy functions or function_call fields together with max_tokens, AnthropicParams captures those keys in extras and this guard does not reject them. Such a payload can therefore be detected as Anthropic and passed through by transform_request(..., Anthropic) with OpenAI-only fields instead of being converted or rejected; add the legacy function fields to this OpenAI-only list as well.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ddf2fcf89

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment on lines +3999 to +4001
if let ToolContentPart::ToolResult(tool_result) = part {
result.push(tool_result_to_chat_completion_message(tool_result)?);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Filter discovery-only assistant calls for Chat export

Fresh evidence in this version is that this branch drops only the ToolDiscoveryResult half of a dynamic-discovery transcript; the paired Message::Assistant containing only ToolDiscoveryCall still reaches the other arm and extract_content_tool_calls_and_reasoning ignores it, producing an assistant Chat message with no content and no tool_calls. For Responses/Anthropic histories with tool-search calls, Chat export should drop both discovery-only halves or return an unsupported-mapping error instead of emitting an invalid empty assistant message.

Useful? React with 👍 / 👎.

Comment on lines +1504 to +1505
if !input_message_is_pure_tool_search_result(message) {
return None;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Split mixed tool-search results before Anthropic merge

When a parallel tool-output turn has both a normal ToolResult and a ToolDiscoveryResult in the same Message::Tool, this purity guard returns None, so the later merge/validation is skipped and the tool_search_tool_result remains inside a user message next to regular tool_result blocks. The converter only makes valid Anthropic tool-search history by merging discovery results into the preceding assistant server_tool_use, so mixed tool messages need to be split/merged or rejected instead of emitted in that user-message shape.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a6f8116059

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment on lines +593 to +599
!matches!(
msg,
Message::Tool { content }
if content
.iter()
.all(|part| matches!(part, ToolContentPart::ToolDiscoveryResult(_)))
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Drop discovery-only assistant messages from Chat responses

Fresh evidence after the prior request-path fix is that OpenAIAdapter::response_from_universal still drops only discovery result tool messages. When a Responses/Anthropic response contains a discovery-only assistant ToolDiscoveryCall, the filtered resp.messages still sends it through ChatCompletionResponseMessageExt::try_from, which ignores that part and emits a Chat choice with neither content nor tool_calls; this violates the expected ChatCompletions loss of dynamic-discovery history and can produce invalid empty choices. Filter discovery-only assistant messages here too.

Useful? React with 👍 / 👎.

Comment on lines +270 to +271
pub(super) fn message_from_input_output(input: openai::InputItem) -> Result<Message, ConvertError> {
let tool_call_id = input.call_id.or_else(|| input.id.clone()).ok_or_else(|| {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve null call_id pairing for Responses input replay

Fresh evidence is that the null-call_id pairing fix was added only for OutputItem conversion; this InputItem replay path still falls back independently to each item's id. If a follow-up Responses request includes prior tool_search_call/tool_search_output input items with call_id: null (the same shape the response converter now handles), the call gets the call item's id and the result gets the output item's id, so downstream Anthropic export treats them as unpaired or emits mismatched IDs. Track the preceding input call id here as well instead of using the output item id.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b29a0c9a9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

logprobs: Option<Value>,
top_logprobs: Option<Value>,
logit_bias: Option<Value>,
response_format: Option<Value>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Treat null OpenAI-only markers as present

If an OpenAI-compatible payload includes one of these guard fields as JSON null, for example {..., "response_format": null} with model, messages, and max_tokens, serde deserializes this Option<Value> as None, so first_present() does not reject it. Because the generated Anthropic request type ignores unknown fields, such payloads can still pass Anthropic detection/validation; use presence-aware fields or a raw typed map so null OpenAI markers are rejected too.

Useful? React with 👍 / 👎.

Comment on lines +381 to +382
if blocks.is_empty() {
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Drop discovery-only assistant calls for Bedrock

When converting a Responses/Anthropic history with a tool_search_call/tool_search_output pair to Bedrock, this empty-block skip only covers the Tool result half. The preceding assistant message containing only ToolDiscoveryCall falls through to BedrockMessage::try_from, where that part is ignored and the request emits an assistant message with content: [], which Converse rejects. Filter or reject discovery-only assistant messages in the same path.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2dd99e171e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment on lines +386 to +388
if !normalized
.iter()
.any(|existing: &UniversalTool| existing.name == expanded_tool.name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve namespace collisions when flattening deferred tools

When a Responses request has two namespace tools that both contain a function with the same local name, calls remain distinguishable by the namespace field, but this name-only dedup drops the second expanded tool. The Anthropic request then advertises only one deferred definition for both namespaces, so discovery output or later calls for the dropped namespace are mapped to the wrong schema instead of returning an unsupported non-lossy mapping error.

Useful? React with 👍 / 👎.

Comment on lines +57 to +61
let view: ToolDiscoveryQueryView = serde_json::from_value(arguments_to_value(arguments))
.map_err(|e| ConvertError::JsonSerializationFailed {
field: "tool_discovery.arguments".to_string(),
error: e.to_string(),
})?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle string tool-search arguments before reading query

If a Responses tool_search_call uses the string arm that the generated InputItem.arguments type accepts, for example a JSON string like "{\"query\":\"foo\"}", this deserializes the string itself as ToolDiscoveryQueryView and returns an error before the original arguments can be preserved. That makes otherwise deserializable tool-search history fail on import/replay; parse JSON strings first or treat them as opaque arguments with query: None.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 228d870dc0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment on lines +393 to +394
if is_discovery_only_assistant_message(&message) {
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject all-discovery histories before emitting Bedrock

When the source history contains only dynamic-discovery items, this filter drops the ToolDiscoveryCall assistant message and the Message::Tool branch below drops the paired ToolDiscoveryResult because it produces no Bedrock blocks. BedrockAdapter::request_from_universal then serializes the returned vector as messages: [], producing an invalid Converse request instead of a clear unsupported/validation error; add a post-filter empty check like the Google path uses.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e9e55f2e9e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment thread crates/lingua/src/providers/bedrock_anthropic/adapter.rs
Comment thread crates/lingua/src/providers/vertex_anthropic/adapter.rs
@knjiang Ken Jiang (knjiang) force-pushed the 06-19-handle_openai_responses_tool_search_input_items branch from 07e3fcf to ae000c4 Compare June 22, 2026 22:50

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f0ed01ddeb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

Comment on lines +414 to +415
if existing.responses_namespace.is_some()
&& expanded_tool.responses_namespace.is_some()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject namespace/function name collisions

When a Responses tool list has both a top-level function and a namespace tool with the same local function name, Responses can still distinguish calls via the namespace field, but this check only rejects collisions where both definitions came from namespaces. A namespace-vs-function collision falls through and drops whichever definition is encountered second, so Anthropic receives a single name-only tool with the wrong schema for one of the call sites instead of an explicit unsupported-mapping error.

Useful? React with 👍 / 👎.

Comment on lines +151 to +153
.map(|item| generated::RequestToolReferenceBlock {
cache_control: None,
tool_name: item.tool_name,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject unknown tool-search result payloads

If result_from_input_content/result_from_response_content hits an unrecognized tool_search_tool_result.content, unknown_result_items stores the original payload in provider_options, but this export path ignores that field and emits only a tool_reference named from item.tool_name (often unknown). In that scenario an unsupported Anthropic result payload is silently rewritten as a successful empty discovery reference, so the converter should either re-emit the preserved content or return an unsupported-mapping error.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f23f6a56a4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

for part in content {
let ToolContentPart::ToolResult(tool_result) = part;
result.push(tool_result_to_chat_completion_message(tool_result)?);
if let ToolContentPart::ToolResult(tool_result) = part {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject all-discovery Chat requests after filtering

Fresh evidence in this version is that the Chat export now skips discovery-only assistant calls and only emits normal ToolResult parts here; if a Responses follow-up contains only dynamic-discovery history, such as previous_response_id plus a tool_search_output or a tool_search_call/tool_search_output pair, this returns an empty message vector and request_from_universal serializes messages: []. Chat Completions requests require at least one message, so this should return an unsupported/validation error instead of emitting an invalid request.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: af16050c15

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "Codex (@codex) review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".

if capabilities.requires_thought_signature_for_function_call_history {
add_dummy_thought_signatures_for_transferred_function_call_history(&mut messages);
}
messages.retain(|message| !is_discovery_only_message(message));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Move discovery filtering before flattening Google messages

When a Responses history has normal content on both sides of discovery-only items (for example user → tool_search_call/output → user, as in the new fixture), this retain runs after flatten_consecutive_messages, so dropping the discovery messages creates two adjacent user messages that are never flattened. The adapter comment above says Google doesn't allow consecutive same-role messages, so these requests can still serialize invalid contents; filter discovery-only messages before flattening, or flatten again after this step.

Useful? React with 👍 / 👎.

@knjiang Ken Jiang (knjiang) merged commit 40aebc9 into main Jun 23, 2026
10 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.

2 participants