iachat/enterprise/app/models/captain/codex_credential.rb
Rodribm10 928b1ec6b9 feat(captain): Codex OAuth auth module + proxy controller
Implementa Fases 1+2 do plano Captain Codex OAuth.

Fase 1 — Auth módulo:
- Migration captain_codex_credentials (tokens AR-encrypted)
- Model Captain::CodexCredential (singleton-ish com .current)
- Captain::Codex::AuthService com device flow completo:
  start_device_login, poll_once, exchange_for_credential,
  valid_access_token (auto-refresh), refresh!
- Rake task captain:codex:{login,status,refresh}
- Sidekiq job Captain::Codex::RefreshTokensJob rodando a cada 30min

Fase 2 — Proxy Chat Completions → Responses:
- Captain::Codex::Translator (chat ↔ responses, tools, tool_calls)
- Captain::Codex::Client (streaming SSE → agregado)
- Api::Internal::CodexProxyController expondo
  POST /codex/v1/chat/completions
- 10 specs do Translator passando

Próximo: Fase 3 (feature flag + fallback) e reconfiguração dos
clientes RubyLLM/Agents/ruby-openai pra apontarem pro proxy quando
CAPTAIN_LLM_PROVIDER=openai_codex_oauth.

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

33 lines
812 B
Ruby

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