Merge branch 'main' into pr-630
This commit is contained in:
commit
df09ba1232
@ -588,7 +588,7 @@ Config file: `~/.nanobot/config.json`
|
|||||||
| `zhipu` | LLM (Zhipu GLM) | [open.bigmodel.cn](https://open.bigmodel.cn) |
|
| `zhipu` | LLM (Zhipu GLM) | [open.bigmodel.cn](https://open.bigmodel.cn) |
|
||||||
| `vllm` | LLM (local, any OpenAI-compatible server) | — |
|
| `vllm` | LLM (local, any OpenAI-compatible server) | — |
|
||||||
| `openai_codex` | LLM (Codex, OAuth) | `nanobot provider login openai-codex` |
|
| `openai_codex` | LLM (Codex, OAuth) | `nanobot provider login openai-codex` |
|
||||||
| `github_copilot` | LLM (GitHub Copilot, OAuth) | Requires [GitHub Copilot](https://github.com/features/copilot) subscription |
|
| `github_copilot` | LLM (GitHub Copilot, OAuth) | `nanobot provider login github-copilot` |
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><b>OpenAI Codex (OAuth)</b></summary>
|
<summary><b>OpenAI Codex (OAuth)</b></summary>
|
||||||
|
|||||||
@ -152,13 +152,15 @@ class SlackChannel(BaseChannel):
|
|||||||
|
|
||||||
text = self._strip_bot_mention(text)
|
text = self._strip_bot_mention(text)
|
||||||
|
|
||||||
thread_ts = event.get("thread_ts") or event.get("ts")
|
thread_ts = event.get("thread_ts")
|
||||||
|
if self.config.reply_in_thread and not thread_ts:
|
||||||
|
thread_ts = event.get("ts")
|
||||||
# Add :eyes: reaction to the triggering message (best-effort)
|
# Add :eyes: reaction to the triggering message (best-effort)
|
||||||
try:
|
try:
|
||||||
if self._web_client and event.get("ts"):
|
if self._web_client and event.get("ts"):
|
||||||
await self._web_client.reactions_add(
|
await self._web_client.reactions_add(
|
||||||
channel=chat_id,
|
channel=chat_id,
|
||||||
name="eyes",
|
name=self.config.react_emoji,
|
||||||
timestamp=event.get("ts"),
|
timestamp=event.get("ts"),
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@ -901,7 +901,9 @@ def status():
|
|||||||
p = getattr(config.providers, spec.name, None)
|
p = getattr(config.providers, spec.name, None)
|
||||||
if p is None:
|
if p is None:
|
||||||
continue
|
continue
|
||||||
if spec.is_local:
|
if spec.is_oauth:
|
||||||
|
console.print(f"{spec.label}: [green]✓ (OAuth)[/green]")
|
||||||
|
elif spec.is_local:
|
||||||
# Local deployments show api_base instead of api_key
|
# Local deployments show api_base instead of api_key
|
||||||
if p.api_base:
|
if p.api_base:
|
||||||
console.print(f"{spec.label}: [green]✓ {p.api_base}[/green]")
|
console.print(f"{spec.label}: [green]✓ {p.api_base}[/green]")
|
||||||
@ -920,42 +922,78 @@ provider_app = typer.Typer(help="Manage providers")
|
|||||||
app.add_typer(provider_app, name="provider")
|
app.add_typer(provider_app, name="provider")
|
||||||
|
|
||||||
|
|
||||||
|
_LOGIN_HANDLERS: dict[str, callable] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def _register_login(name: str):
|
||||||
|
def decorator(fn):
|
||||||
|
_LOGIN_HANDLERS[name] = fn
|
||||||
|
return fn
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
@provider_app.command("login")
|
@provider_app.command("login")
|
||||||
def provider_login(
|
def provider_login(
|
||||||
provider: str = typer.Argument(..., help="OAuth provider to authenticate with (e.g., 'openai-codex')"),
|
provider: str = typer.Argument(..., help="OAuth provider (e.g. 'openai-codex', 'github-copilot')"),
|
||||||
):
|
):
|
||||||
"""Authenticate with an OAuth provider."""
|
"""Authenticate with an OAuth provider."""
|
||||||
console.print(f"{__logo__} OAuth Login - {provider}\n")
|
from nanobot.providers.registry import PROVIDERS
|
||||||
|
|
||||||
if provider == "openai-codex":
|
key = provider.replace("-", "_")
|
||||||
|
spec = next((s for s in PROVIDERS if s.name == key and s.is_oauth), None)
|
||||||
|
if not spec:
|
||||||
|
names = ", ".join(s.name.replace("_", "-") for s in PROVIDERS if s.is_oauth)
|
||||||
|
console.print(f"[red]Unknown OAuth provider: {provider}[/red] Supported: {names}")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
handler = _LOGIN_HANDLERS.get(spec.name)
|
||||||
|
if not handler:
|
||||||
|
console.print(f"[red]Login not implemented for {spec.label}[/red]")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
console.print(f"{__logo__} OAuth Login - {spec.label}\n")
|
||||||
|
handler()
|
||||||
|
|
||||||
|
|
||||||
|
@_register_login("openai_codex")
|
||||||
|
def _login_openai_codex() -> None:
|
||||||
|
try:
|
||||||
|
from oauth_cli_kit import get_token, login_oauth_interactive
|
||||||
|
token = None
|
||||||
try:
|
try:
|
||||||
from oauth_cli_kit import get_token, login_oauth_interactive
|
token = get_token()
|
||||||
token = None
|
except Exception:
|
||||||
try:
|
pass
|
||||||
token = get_token()
|
if not (token and token.access):
|
||||||
except Exception:
|
console.print("[cyan]Starting interactive OAuth login...[/cyan]\n")
|
||||||
token = None
|
token = login_oauth_interactive(
|
||||||
if not (token and token.access):
|
print_fn=lambda s: console.print(s),
|
||||||
console.print("[cyan]No valid token found. Starting interactive OAuth login...[/cyan]")
|
prompt_fn=lambda s: typer.prompt(s),
|
||||||
console.print("A browser window may open for you to authenticate.\n")
|
)
|
||||||
token = login_oauth_interactive(
|
if not (token and token.access):
|
||||||
print_fn=lambda s: console.print(s),
|
console.print("[red]✗ Authentication failed[/red]")
|
||||||
prompt_fn=lambda s: typer.prompt(s),
|
|
||||||
)
|
|
||||||
if not (token and token.access):
|
|
||||||
console.print("[red]✗ Authentication failed[/red]")
|
|
||||||
raise typer.Exit(1)
|
|
||||||
console.print("[green]✓ Successfully authenticated with OpenAI Codex![/green]")
|
|
||||||
console.print(f"[dim]Account ID: {token.account_id}[/dim]")
|
|
||||||
except ImportError:
|
|
||||||
console.print("[red]oauth_cli_kit not installed. Run: pip install oauth-cli-kit[/red]")
|
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
except Exception as e:
|
console.print(f"[green]✓ Authenticated with OpenAI Codex[/green] [dim]{token.account_id}[/dim]")
|
||||||
console.print(f"[red]Authentication error: {e}[/red]")
|
except ImportError:
|
||||||
raise typer.Exit(1)
|
console.print("[red]oauth_cli_kit not installed. Run: pip install oauth-cli-kit[/red]")
|
||||||
else:
|
raise typer.Exit(1)
|
||||||
console.print(f"[red]Unknown OAuth provider: {provider}[/red]")
|
|
||||||
console.print("[yellow]Supported providers: openai-codex[/yellow]")
|
|
||||||
|
@_register_login("github_copilot")
|
||||||
|
def _login_github_copilot() -> None:
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
console.print("[cyan]Starting GitHub Copilot device flow...[/cyan]\n")
|
||||||
|
|
||||||
|
async def _trigger():
|
||||||
|
from litellm import acompletion
|
||||||
|
await acompletion(model="github_copilot/gpt-4o", messages=[{"role": "user", "content": "hi"}], max_tokens=1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
asyncio.run(_trigger())
|
||||||
|
console.print("[green]✓ Authenticated with GitHub Copilot[/green]")
|
||||||
|
except Exception as e:
|
||||||
|
console.print(f"[red]Authentication error: {e}[/red]")
|
||||||
raise typer.Exit(1)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -148,6 +148,8 @@ class SlackConfig(Base):
|
|||||||
bot_token: str = "" # xoxb-...
|
bot_token: str = "" # xoxb-...
|
||||||
app_token: str = "" # xapp-...
|
app_token: str = "" # xapp-...
|
||||||
user_token_read_only: bool = True
|
user_token_read_only: bool = True
|
||||||
|
reply_in_thread: bool = True
|
||||||
|
react_emoji: str = "eyes"
|
||||||
group_policy: str = "mention" # "mention", "open", "allowlist"
|
group_policy: str = "mention" # "mention", "open", "allowlist"
|
||||||
group_allow_from: list[str] = Field(default_factory=list) # Allowed channel IDs if allowlist
|
group_allow_from: list[str] = Field(default_factory=list) # Allowed channel IDs if allowlist
|
||||||
dm: SlackDMConfig = Field(default_factory=SlackDMConfig)
|
dm: SlackDMConfig = Field(default_factory=SlackDMConfig)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user