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>
82 lines
2.5 KiB
Ruby
82 lines
2.5 KiB
Ruby
namespace :captain do
|
|
namespace :codex do
|
|
desc 'Autentica o Captain AI com a assinatura ChatGPT Plus via device flow OAuth'
|
|
task login: :environment do
|
|
start = Captain::Codex::AuthService.start_device_login
|
|
|
|
puts
|
|
puts '=== Captain Codex OAuth Login ==='
|
|
puts
|
|
puts '1. Abra a URL abaixo no browser:'
|
|
puts " \e[36m#{start[:verify_url]}\e[0m"
|
|
puts
|
|
puts '2. Faça login com a conta ChatGPT Plus e cole o código:'
|
|
puts " \e[93m#{start[:user_code]}\e[0m"
|
|
puts
|
|
puts 'Aguardando autorização (timeout 15min, Ctrl+C para cancelar)...'
|
|
|
|
deadline = 15.minutes.from_now
|
|
result = nil
|
|
|
|
loop do
|
|
raise 'Timeout de 15min atingido' if Time.current > deadline
|
|
|
|
sleep start[:poll_interval]
|
|
begin
|
|
result = Captain::Codex::AuthService.poll_once(
|
|
device_auth_id: start[:device_auth_id],
|
|
user_code: start[:user_code]
|
|
)
|
|
break
|
|
rescue Captain::Codex::AuthService::PendingAuthorization
|
|
print '.'
|
|
end
|
|
end
|
|
|
|
puts
|
|
puts 'Autorização recebida, trocando por tokens...'
|
|
|
|
cred = Captain::Codex::AuthService.exchange_for_credential(
|
|
authorization_code: result[:authorization_code],
|
|
code_verifier: result[:code_verifier]
|
|
)
|
|
|
|
puts
|
|
puts '✓ Login OK'
|
|
puts " Email: #{cred.email}"
|
|
puts " Plan: #{cred.chatgpt_plan_type}"
|
|
puts " ChatGPT account: #{cred.chatgpt_account_id}"
|
|
puts " Access token expira: #{cred.expires_at}"
|
|
puts ' Refresh automático antes de cada call.'
|
|
end
|
|
|
|
desc 'Mostra status da credencial Codex ativa'
|
|
task status: :environment do
|
|
cred = Captain::CodexCredential.current
|
|
if cred.nil?
|
|
puts 'Nenhuma credencial Codex ativa. Rode: rails captain:codex:login'
|
|
exit 1
|
|
end
|
|
|
|
puts "Status: #{cred.status}"
|
|
puts "Email: #{cred.email}"
|
|
puts "Plan: #{cred.chatgpt_plan_type}"
|
|
puts "Expires at: #{cred.expires_at}"
|
|
puts "Last refresh: #{cred.last_refresh_at}"
|
|
puts "Needs refresh? #{cred.needs_refresh?}"
|
|
end
|
|
|
|
desc 'Força refresh da credencial Codex atual'
|
|
task refresh: :environment do
|
|
cred = Captain::CodexCredential.current
|
|
if cred.nil?
|
|
puts 'Nenhuma credencial ativa.'
|
|
exit 1
|
|
end
|
|
|
|
refreshed = Captain::Codex::AuthService.refresh!(cred)
|
|
puts "Refreshed. Nova expiração: #{refreshed.expires_at}"
|
|
end
|
|
end
|
|
end
|