iachat/enterprise/app/models/captain/codex_credential.rb
Rodribm10 b457e84c2f fix(captain): route embeddings to legacy OpenAI + retry transient errors
Resolve duas camadas de problema identificadas em teste end-to-end:

1. Embeddings falhavam com HTTP 404 (/codex/v1/embeddings não existe).
   Solução: Captain::Llm::EmbeddingService sempre usa OpenAI tradicional
   via Llm::Config.with_api_key(legacy_settings). ProviderConfig expõe
   legacy_openai_settings pra isso.

2. Servidor Codex ocasionalmente responde com response.failed +
   code=server_error (instabilidade transitória). Client agora retenta
   até 2x com backoff exponencial (0.5s, 1.5s) em erros retryable:
   HTTP 5xx, server_error no response.failed, ou stream inacabado.

Outras correções nesta etapa:
- Scenario#agent_model: em modo Codex, ignora CAPTAIN_OPEN_AI_MODEL_SCENARIO
  (que pode ter gpt-4o legado) e usa ProviderConfig.model.
- ExtractionService/ContradictionCheckerService/TranslateQueryService:
  trocam constantes hardcoded gpt-4o-mini/gpt-4.1-nano por
  ProviderConfig.light_model (respeitando o provider ativo).
- ProviderConfig.DEFAULT_CODEX_MODEL agora é gpt-5.2 (reconhecido pelo
  RubyLLM; gpt-5.4 não está no catalog do gem).

Validado ponta-a-ponta: WhatsApp → Chatwoot → Jasmine → handoff Daniela
→ faq_lookup com embedding OK → resposta com preços corretos.

Docs em docs/captain-codex-oauth.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:42:31 -03:00

54 lines
1.5 KiB
Ruby

# == Schema Information
#
# Table name: captain_codex_credentials
#
# id :bigint not null, primary key
# access_token :text not null
# chatgpt_plan_type :string
# email :string
# expires_at :datetime not null
# last_refresh_at :datetime
# refresh_token :text not null
# status :string default("active"), not null
# created_at :datetime not null
# updated_at :datetime not null
# chatgpt_account_id :string
#
# Indexes
#
# index_captain_codex_credentials_on_expires_at (expires_at)
# index_captain_codex_credentials_on_status (status)
#
class Captain::CodexCredential < ApplicationRecord
self.table_name = 'captain_codex_credentials'
STATUSES = %w[active expired revoked].freeze
encrypts :access_token
encrypts :refresh_token
validates :access_token, presence: true
validates :refresh_token, presence: true
validates :expires_at, presence: true
validates :status, inclusion: { in: STATUSES }
scope :active, -> { where(status: 'active') }
scope :expiring_soon, ->(skew = 5.minutes) { where(expires_at: ..(Time.current + skew)) }
def self.current
active.order(updated_at: :desc).first
end
def needs_refresh?(skew_seconds: 120)
expires_at.nil? || expires_at <= Time.current + skew_seconds.seconds
end
def expired?
expires_at <= Time.current
end
def revoke!
update!(status: 'revoked')
end
end