fix(captain): translate response_format to text.format on Codex proxy

Sem isso o Codex devolvia texto puro e o reaction_emoji do JSON
estruturado nunca chegava ao ResponseBuilderJob — quebrava a
ferramenta de reagir mensagens com emoji.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rodribm10 2026-04-22 22:47:09 -03:00
parent 9e8550dd45
commit 0ecfce5c27

View File

@ -18,9 +18,14 @@ class Captain::Codex::Translator
# chat_body: hash no formato OpenAI Chat Completions. # chat_body: hash no formato OpenAI Chat Completions.
# Retorna hash pro POST /responses. # Retorna hash pro POST /responses.
def self.chat_to_responses(chat_body) def self.chat_to_responses(chat_body)
messages = chat_body['messages'] || chat_body[:messages] || [] instructions, input = split_system(chat_body['messages'] || chat_body[:messages] || [])
instructions, input = split_system(messages) body = base_body(chat_body, instructions, input)
apply_tools!(body, chat_body)
apply_response_format!(body, chat_body['response_format'] || chat_body[:response_format])
body
end
def self.base_body(chat_body, instructions, input)
body = { body = {
model: override_model(chat_body['model'] || chat_body[:model]), model: override_model(chat_body['model'] || chat_body[:model]),
input: input, input: input,
@ -31,14 +36,41 @@ class Captain::Codex::Translator
stream: true # Codex exige streaming sempre — o Client agrega. stream: true # Codex exige streaming sempre — o Client agrega.
} }
body[:max_output_tokens] = chat_body['max_tokens'] if chat_body['max_tokens'] body[:max_output_tokens] = chat_body['max_tokens'] if chat_body['max_tokens']
body
end
def self.apply_tools!(body, chat_body)
tools = chat_body['tools'] || chat_body[:tools] tools = chat_body['tools'] || chat_body[:tools]
if tools.present? return if tools.blank?
body[:tools] = tools.map { |t| translate_tool(t) } body[:tools] = tools.map { |t| translate_tool(t) }
body[:tool_choice] = translate_tool_choice(chat_body['tool_choice'] || chat_body[:tool_choice]) body[:tool_choice] = translate_tool_choice(chat_body['tool_choice'] || chat_body[:tool_choice])
end end
body def self.apply_response_format!(body, response_format)
text_format = translate_response_format(response_format)
body[:text] = { format: text_format } if text_format
end
# Chat Completions: { type: 'json_schema', json_schema: { name, schema, strict } }
# Responses API: { type: 'json_schema', name, schema, strict } (sem wrapper json_schema)
# Também aceita { type: 'json_object' } (sem schema).
def self.translate_response_format(format)
return nil if format.blank?
format = format.stringify_keys
case format['type']
when 'json_schema'
js = (format['json_schema'] || {}).stringify_keys
{
type: 'json_schema',
name: js['name'] || 'response',
schema: js['schema'] || {},
strict: js.fetch('strict', true)
}
when 'json_object'
{ type: 'json_object' }
end
end end
# Separa a(s) mensagem(ns) system do resto. # Separa a(s) mensagem(ns) system do resto.