Merge remote-tracking branch 'origin/main' into feature/codex-oauth

This commit is contained in:
pinhua33
2026-02-08 13:49:25 +08:00
24 changed files with 1227 additions and 239 deletions

View File

@@ -172,6 +172,33 @@ This file stores important information that should persist across sessions.
console.print(" [dim]Created memory/MEMORY.md[/dim]")
def _make_provider(config):
"""Create LiteLLMProvider from config. Exits if no API key found."""
from nanobot.providers.litellm_provider import LiteLLMProvider
from nanobot.providers.openai_codex_provider import OpenAICodexProvider
from oauth_cli_kit import get_token as get_codex_token
p = config.get_provider()
model = config.agents.defaults.model
if model.startswith("openai-codex/"):
try:
_ = get_codex_token()
except Exception:
console.print("Please run: [cyan]nanobot login --provider openai-codex[/cyan]")
raise typer.Exit(1)
return OpenAICodexProvider(default_model=model)
if not (p and p.api_key) and not model.startswith("bedrock/"):
console.print("[red]Error: No API key configured.[/red]")
console.print("Set one in ~/.nanobot/config.json under providers section")
raise typer.Exit(1)
return LiteLLMProvider(
api_key=p.api_key if p else None,
api_base=config.get_api_base(),
default_model=model,
extra_headers=p.extra_headers if p else None,
)
# ============================================================================
# Gateway / Server
# ============================================================================
@@ -185,11 +212,9 @@ def gateway(
"""Start the nanobot gateway."""
from nanobot.config.loader import load_config, get_data_dir
from nanobot.bus.queue import MessageBus
from nanobot.providers.litellm_provider import LiteLLMProvider
from nanobot.providers.openai_codex_provider import OpenAICodexProvider
from oauth_cli_kit import get_token as get_codex_token
from nanobot.agent.loop import AgentLoop
from nanobot.channels.manager import ChannelManager
from nanobot.session.manager import SessionManager
from nanobot.cron.service import CronService
from nanobot.cron.types import CronJob
from nanobot.heartbeat.service import HeartbeatService
@@ -201,37 +226,15 @@ def gateway(
console.print(f"{__logo__} Starting nanobot gateway on port {port}...")
config = load_config()
# Create components
bus = MessageBus()
provider = _make_provider(config)
session_manager = SessionManager(config.workspace_path)
# Create provider (supports OpenRouter, Anthropic, OpenAI, Bedrock)
api_key = config.get_api_key()
api_base = config.get_api_base()
model = config.agents.defaults.model
is_bedrock = model.startswith("bedrock/")
is_codex = model.startswith("openai-codex/")
if is_codex:
try:
_ = get_codex_token()
except Exception as e:
console.print(f"[red]Error: {e}[/red]")
console.print("Please run: [cyan]nanobot login --provider openai-codex[/cyan]")
raise typer.Exit(1)
provider = OpenAICodexProvider(default_model=model)
else:
if not api_key and not is_bedrock:
console.print("[red]Error: No API key configured.[/red]")
console.print("Set one in ~/.nanobot/config.json under providers.openrouter.apiKey")
raise typer.Exit(1)
provider = LiteLLMProvider(
api_key=api_key,
api_base=api_base,
default_model=config.agents.defaults.model
)
# Create cron service first (callback set after agent creation)
cron_store_path = get_data_dir() / "cron" / "jobs.json"
cron = CronService(cron_store_path)
# Create agent
# Create agent with cron service
agent = AgentLoop(
bus=bus,
provider=provider,
@@ -240,27 +243,29 @@ def gateway(
max_iterations=config.agents.defaults.max_tool_iterations,
brave_api_key=config.tools.web.search.api_key or None,
exec_config=config.tools.exec,
cron_service=cron,
restrict_to_workspace=config.tools.restrict_to_workspace,
session_manager=session_manager,
)
# Create cron service
# Set cron callback (needs agent)
async def on_cron_job(job: CronJob) -> str | None:
"""Execute a cron job through the agent."""
response = await agent.process_direct(
job.payload.message,
session_key=f"cron:{job.id}"
session_key=f"cron:{job.id}",
channel=job.payload.channel or "cli",
chat_id=job.payload.to or "direct",
)
# Optionally deliver to channel
if job.payload.deliver and job.payload.to:
from nanobot.bus.events import OutboundMessage
await bus.publish_outbound(OutboundMessage(
channel=job.payload.channel or "whatsapp",
channel=job.payload.channel or "cli",
chat_id=job.payload.to,
content=response or ""
))
return response
cron_store_path = get_data_dir() / "cron" / "jobs.json"
cron = CronService(cron_store_path, on_job=on_cron_job)
cron.on_job = on_cron_job
# Create heartbeat service
async def on_heartbeat(prompt: str) -> str:
@@ -275,7 +280,7 @@ def gateway(
)
# Create channel manager
channels = ChannelManager(config, bus)
channels = ChannelManager(config, bus, session_manager=session_manager)
if channels.enabled_channels:
console.print(f"[green]✓[/green] Channels enabled: {', '.join(channels.enabled_channels)}")
@@ -321,40 +326,12 @@ def agent(
"""Interact with the agent directly."""
from nanobot.config.loader import load_config
from nanobot.bus.queue import MessageBus
from nanobot.providers.litellm_provider import LiteLLMProvider
from nanobot.providers.openai_codex_provider import OpenAICodexProvider
from oauth_cli_kit import get_token as get_codex_token
from nanobot.agent.loop import AgentLoop
config = load_config()
api_key = config.get_api_key()
api_base = config.get_api_base()
model = config.agents.defaults.model
is_bedrock = model.startswith("bedrock/")
is_codex = model.startswith("openai-codex/")
if is_codex:
try:
_ = get_codex_token()
except Exception as e:
console.print(f"[red]Error: {e}[/red]")
console.print("Please run: [cyan]nanobot login --provider openai-codex[/cyan]")
raise typer.Exit(1)
else:
if not api_key and not is_bedrock:
console.print("[red]Error: No API key configured.[/red]")
raise typer.Exit(1)
bus = MessageBus()
if is_codex:
provider = OpenAICodexProvider(default_model=config.agents.defaults.model)
else:
provider = LiteLLMProvider(
api_key=api_key,
api_base=api_base,
default_model=config.agents.defaults.model
)
provider = _make_provider(config)
agent_loop = AgentLoop(
bus=bus,
@@ -362,6 +339,7 @@ def agent(
workspace=config.workspace_path,
brave_api_key=config.tools.web.search.api_key or None,
exec_config=config.tools.exec,
restrict_to_workspace=config.tools.restrict_to_workspace,
)
if message:
@@ -420,6 +398,13 @@ def channels_status():
wa.bridge_url
)
dc = config.channels.discord
table.add_row(
"Discord",
"" if dc.enabled else "",
dc.gateway_url
)
# Telegram
tg = config.channels.telegram
tg_config = f"token: {tg.token[:10]}..." if tg.token else "[dim]not configured[/dim]"
@@ -693,13 +678,17 @@ def status():
has_anthropic = bool(config.providers.anthropic.api_key)
has_openai = bool(config.providers.openai.api_key)
has_gemini = bool(config.providers.gemini.api_key)
has_zhipu = bool(config.providers.zhipu.api_key)
has_vllm = bool(config.providers.vllm.api_base)
has_aihubmix = bool(config.providers.aihubmix.api_key)
console.print(f"OpenRouter API: {'[green]✓[/green]' if has_openrouter else '[dim]not set[/dim]'}")
console.print(f"Anthropic API: {'[green]✓[/green]' if has_anthropic else '[dim]not set[/dim]'}")
console.print(f"OpenAI API: {'[green]✓[/green]' if has_openai else '[dim]not set[/dim]'}")
console.print(f"Gemini API: {'[green]✓[/green]' if has_gemini else '[dim]not set[/dim]'}")
vllm_status = f"[green]<5D>?{config.providers.vllm.api_base}[/green]" if has_vllm else "[dim]not set[/dim]"
console.print(f"Zhipu AI API: {'[green]✓[/green]' if has_zhipu else '[dim]not set[/dim]'}")
console.print(f"AiHubMix API: {'[green]✓[/green]' if has_aihubmix else '[dim]not set[/dim]'}")
vllm_status = f"[green]✓ {config.providers.vllm.api_base}[/green]" if has_vllm else "[dim]not set[/dim]"
console.print(f"vLLM/Local: {vllm_status}")
try: