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>
33 lines
812 B
Ruby
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
|