Acrescenta valor 'openai_hermes_gateway' ao CAPTAIN_LLM_PROVIDER, sem mexer nas opções existentes (openai_api e openai_codex_oauth continuam intactos). Quando ativado, o Captain chama o Hermes Agent rodando em modo gateway HTTP local (CAPTAIN_HERMES_GATEWAY_URL, default http://host.docker.internal:9877). O Hermes faz o roteamento multi-modelo (Codex/Anthropic/Gemini) usando o OAuth dele em ~/.hermes/auth.json — o Captain não precisa fazer OAuth direto. Configs novas em installation_config.yml: - CAPTAIN_HERMES_GATEWAY_URL — URL do gateway (default host.docker.internal:9877) - CAPTAIN_HERMES_GATEWAY_MODEL — modelo no formato <provider>/<model> - CAPTAIN_HERMES_GATEWAY_API_KEY — opcional, dummy se gateway local não exige Embeddings e Files API continuam apontando pra OpenAI tradicional via legacy_openai_settings — Hermes Gateway não expõe esses endpoints. Specs cobrem: dummy key, custom api_key override, custom model, defaults, trailing slash strip, light_model por provider, hermes_gateway? predicate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
131 lines
4.8 KiB
Ruby
131 lines
4.8 KiB
Ruby
# Single source of truth para a configuração do provider LLM do Captain.
|
|
#
|
|
# Lê CAPTAIN_LLM_PROVIDER e retorna a combinação certa de (api_key, api_base, model):
|
|
#
|
|
# - openai_api (padrão): usa CAPTAIN_OPEN_AI_API_KEY + CAPTAIN_OPEN_AI_ENDPOINT.
|
|
# Mesmo comportamento legado.
|
|
#
|
|
# - openai_codex_oauth: aponta para o proxy interno
|
|
# (CAPTAIN_CODEX_PROXY_URL, default http://localhost:3000/codex) e usa uma
|
|
# api_key dummy — o proxy ignora o Authorization header e usa OAuth interno.
|
|
#
|
|
# - openai_hermes_gateway: aponta para o Hermes Agent rodando em modo gateway
|
|
# (CAPTAIN_HERMES_GATEWAY_URL, default http://host.docker.internal:9877).
|
|
# O Hermes Gateway expõe API HTTP compatível com OpenAI e roteia internamente
|
|
# pra Codex/Anthropic/Gemini conforme sua config local em ~/.hermes/config.yaml.
|
|
# Auth: usa CAPTAIN_HERMES_GATEWAY_API_KEY se setado, senão dummy (gateway local).
|
|
#
|
|
# O "legacy" ruby-openai usado para PDF/Files API NÃO deve usar esse módulo:
|
|
# o endpoint Codex/Hermes não expõe Files API nem /embeddings, então esses
|
|
# serviços continuam apontando sempre para OpenAI tradicional via
|
|
# legacy_openai_settings.
|
|
class Captain::Llm::ProviderConfig
|
|
DEFAULT_MODEL = 'gpt-4.1-mini'.freeze
|
|
DEFAULT_OPENAI_ENDPOINT = 'https://api.openai.com'.freeze
|
|
DEFAULT_CODEX_PROXY_URL = 'http://localhost:3000/codex'.freeze
|
|
DUMMY_API_KEY = 'codex-oauth'.freeze
|
|
|
|
# Modelo padrão pro Codex. gpt-5.2 é o mais recente reconhecido pelo RubyLLM
|
|
# (gpt-5.4 ainda não está no catalog do gem). Ambos são suportados pelo
|
|
# endpoint Codex da OpenAI via ChatGPT Plus.
|
|
DEFAULT_CODEX_MODEL = 'gpt-5.2'.freeze
|
|
|
|
# Hermes Gateway: defaults para o setup standard do Hermes Agent rodando
|
|
# como gateway HTTP local. O gateway escuta em 0.0.0.0:9877 por padrão e
|
|
# aceita o nome do modelo no formato `<provider>/<model>`.
|
|
DEFAULT_HERMES_GATEWAY_URL = 'http://host.docker.internal:9877'.freeze
|
|
DEFAULT_HERMES_GATEWAY_MODEL = 'anthropic/claude-opus-4-5'.freeze
|
|
HERMES_GATEWAY_DUMMY_KEY = 'hermes-gateway'.freeze
|
|
|
|
# Modelo leve pra tasks de background (extração de memória, verificação de
|
|
# contradição, traduções internas). Quando usamos Codex/Hermes, reutilizamos
|
|
# o mesmo modelo do chat — esses endpoints não expõem gpt-4o-mini.
|
|
LIGHT_MODEL_DEFAULTS = {
|
|
'openai_api' => 'gpt-4o-mini',
|
|
'openai_codex_oauth' => DEFAULT_CODEX_MODEL,
|
|
'openai_hermes_gateway' => DEFAULT_HERMES_GATEWAY_MODEL
|
|
}.freeze
|
|
|
|
class << self
|
|
def provider
|
|
cfg('CAPTAIN_LLM_PROVIDER').presence || 'openai_api'
|
|
end
|
|
|
|
def codex_oauth?
|
|
provider == 'openai_codex_oauth'
|
|
end
|
|
|
|
def hermes_gateway?
|
|
provider == 'openai_hermes_gateway'
|
|
end
|
|
|
|
# Retorna { api_key:, api_base:, model: } para RubyLLM/Agents.
|
|
def settings
|
|
case provider
|
|
when 'openai_codex_oauth' then codex_settings
|
|
when 'openai_hermes_gateway' then hermes_gateway_settings
|
|
else openai_api_settings
|
|
end
|
|
end
|
|
|
|
def api_key
|
|
settings[:api_key]
|
|
end
|
|
|
|
# Base URL "crua", sem /v1. O cliente (ai_agents.rb) adiciona /v1.
|
|
def api_base
|
|
settings[:api_base]
|
|
end
|
|
|
|
def model
|
|
settings[:model]
|
|
end
|
|
|
|
# Modelo pra tasks leves (memory extraction, contradiction check, etc).
|
|
# Respeita a flag de provider: em Codex OAuth, usa o mesmo modelo do chat.
|
|
def light_model
|
|
LIGHT_MODEL_DEFAULTS[provider] || LIGHT_MODEL_DEFAULTS['openai_api']
|
|
end
|
|
|
|
# Settings sempre da OpenAI tradicional, independente do provider.
|
|
# Usado por recursos que o endpoint Codex NÃO expõe: /embeddings e Files API.
|
|
# Lança AuthError se não houver CAPTAIN_OPEN_AI_API_KEY configurada.
|
|
def legacy_openai_settings
|
|
{
|
|
api_key: cfg('CAPTAIN_OPEN_AI_API_KEY'),
|
|
api_base: (cfg('CAPTAIN_OPEN_AI_ENDPOINT').presence || DEFAULT_OPENAI_ENDPOINT).chomp('/')
|
|
}
|
|
end
|
|
|
|
private
|
|
|
|
def codex_settings
|
|
{
|
|
api_key: DUMMY_API_KEY,
|
|
api_base: (cfg('CAPTAIN_CODEX_PROXY_URL').presence || DEFAULT_CODEX_PROXY_URL).chomp('/'),
|
|
model: cfg('CAPTAIN_OPEN_AI_MODEL').presence || DEFAULT_CODEX_MODEL
|
|
}
|
|
end
|
|
|
|
def hermes_gateway_settings
|
|
{
|
|
api_key: cfg('CAPTAIN_HERMES_GATEWAY_API_KEY').presence || HERMES_GATEWAY_DUMMY_KEY,
|
|
api_base: (cfg('CAPTAIN_HERMES_GATEWAY_URL').presence || DEFAULT_HERMES_GATEWAY_URL).chomp('/'),
|
|
model: cfg('CAPTAIN_HERMES_GATEWAY_MODEL').presence || DEFAULT_HERMES_GATEWAY_MODEL
|
|
}
|
|
end
|
|
|
|
def openai_api_settings
|
|
{
|
|
api_key: cfg('CAPTAIN_OPEN_AI_API_KEY'),
|
|
api_base: (cfg('CAPTAIN_OPEN_AI_ENDPOINT').presence || DEFAULT_OPENAI_ENDPOINT).chomp('/'),
|
|
model: cfg('CAPTAIN_OPEN_AI_MODEL').presence || DEFAULT_MODEL
|
|
}
|
|
end
|
|
|
|
def cfg(name)
|
|
InstallationConfig.find_by(name: name)&.value
|
|
end
|
|
end
|
|
end
|