Modernize provider support

This commit is contained in:
rob
2026-07-19 20:58:04 -03:00
parent 15943f251c
commit 6908d8ee6f
25 changed files with 1749 additions and 581 deletions
+15 -1
View File
@@ -17,6 +17,7 @@ class ProviderDialog(QDialog):
self.setMinimumWidth(450)
self._editing = provider is not None
self._original_name = name
self._provider = provider
self._setup_ui()
if provider:
@@ -114,7 +115,20 @@ class ProviderDialog(QDialog):
return
try:
add_provider(Provider(name, command, description, fallback))
existing = self._provider
add_provider(Provider(
name=name,
command=command,
description=description,
fallback=fallback,
type=existing.type if existing else "subprocess",
model=existing.model if existing else None,
tags=existing.tags if existing else [],
install=existing.install if existing else None,
fallback_chain=existing.fallback_chain if existing else None,
api_key_env=existing.api_key_env if existing else None,
pty_config=existing.pty_config if existing else None,
))
self.accept()
except Exception as e:
from PySide6.QtWidgets import QMessageBox
@@ -19,59 +19,68 @@ from ...providers import load_providers, add_provider, Provider
PROVIDER_INSTALL_INFO = {
"claude": {
"group": "Anthropic Claude",
"install_cmd": "npm install -g @anthropic-ai/claude-code",
"requires": "Node.js 18+ and npm",
"setup": "Run 'claude' - opens browser for sign-in",
"cost": "Pay-per-use (billed to your Anthropic account)",
"install_cmd": "curl -fsSL https://claude.ai/install.sh | bash",
"requires": "curl and bash",
"setup": "Run 'claude' to sign in with a subscription or API key.",
"cost": "Paid subscription or API key",
"variants": [
("claude", "claude -p", "Claude (default model)"),
("claude-haiku", "claude -p --model claude-3-haiku-20240307", "Claude Haiku (fast, cheap)"),
("claude-sonnet", "claude -p --model claude-3-5-sonnet-20241022", "Claude Sonnet (balanced)"),
("claude-opus", "claude -p --model claude-3-opus-20240229", "Claude Opus (most capable)"),
("claude-haiku", "claude -p --model haiku", "Claude Haiku (fast)"),
("claude-sonnet", "claude -p --model sonnet", "Claude Sonnet (balanced)"),
("claude-opus", "claude -p --model opus", "Claude Opus (highest quality)"),
],
},
"codex": {
"group": "OpenAI Codex",
"install_cmd": "npm install -g @openai/codex",
"requires": "Node.js 18+ and npm",
"setup": "Run 'codex' - opens browser for sign-in",
"cost": "Pay-per-use (billed to your OpenAI account)",
"setup": "Run 'codex' to sign in.",
"cost": "Free tier or ChatGPT subscription, subject to account limits",
"variants": [
("codex", "codex", "OpenAI Codex"),
("codex", "codex exec -", "OpenAI Codex"),
],
},
"gemini": {
"group": "Google Gemini",
"install_cmd": "npm install -g @google/gemini-cli",
"requires": "Node.js 18+ and npm",
"setup": "Run 'gemini' - opens browser for Google sign-in",
"cost": "Free tier available, pay-per-use for more",
"agy": {
"group": "Google Antigravity",
"install_cmd": None,
"requires": "Google Antigravity desktop application",
"setup": "Install from https://antigravity.google/download, then run 'agy install' to configure your PATH.",
"cost": "Free tier available, subject to account limits",
"variants": [
("gemini", "gemini", "Gemini Pro (default)"),
("gemini-flash", "gemini --model gemini-1.5-flash", "Gemini Flash (faster)"),
("agy", "agy -p", "Google Antigravity (automatic model selection)"),
],
},
"opencode": {
"group": "OpenCode (75+ providers)",
"group": "OpenCode",
"install_cmd": "curl -fsSL https://opencode.ai/install | bash",
"requires": "curl, bash",
"setup": "Run 'opencode' - opens browser to connect providers",
"cost": "4 FREE models included, 75+ more available",
"setup": "Run 'opencode' and use /connect to configure providers.",
"cost": "Free models available; paid providers require credentials",
"variants": [
("opencode-pickle", "$HOME/.opencode/bin/opencode -p big-pickle", "Big Pickle (FREE, high quality)"),
("opencode-deepseek", "$HOME/.opencode/bin/opencode -p deepseek", "DeepSeek (fast, cheap)"),
("opencode-reasoner", "$HOME/.opencode/bin/opencode -p deepseek-reasoner", "DeepSeek Reasoner (complex tasks)"),
("opencode-grok", "$HOME/.opencode/bin/opencode -p grok", "Grok (xAI)"),
("opencode-pickle", "opencode run --model opencode/big-pickle", "Big Pickle (free)"),
("opencode-free", "opencode run --model opencode/deepseek-v4-flash-free", "DeepSeek V4 Flash (free)"),
("opencode-deepseek", "opencode run --model deepseek/deepseek-chat", "DeepSeek Chat (API key required)"),
("opencode-reasoner", "opencode run --model deepseek/deepseek-reasoner", "DeepSeek Reasoner (API key required)"),
],
},
"crush": {
"group": "Crush (Charm)",
"install_cmd": "npm install -g @charmland/crush",
"requires": "Node.js 18+ and npm",
"setup": "Run 'crush' to configure Hyper or your provider API keys.",
"cost": "Limited Hyper credits or your own provider billing",
"variants": [
("crush", "crush run --quiet", "Crush multi-model agent"),
],
},
"ollama": {
"group": "Ollama (Local LLMs)",
"install_cmd": "curl -fsSL https://ollama.ai/install.sh | bash",
"requires": "curl, bash, 8GB+ RAM (GPU recommended)",
"setup": "Run 'ollama pull llama3' to download a model",
"setup": "Run 'ollama pull llama3.2' to download a model.",
"cost": "FREE (runs entirely on your machine)",
"variants": [
("ollama-llama3", "ollama run llama3", "Llama 3 (general purpose)"),
("ollama-llama3", "ollama run llama3.2", "Llama 3.2 (general purpose)"),
("ollama-codellama", "ollama run codellama", "CodeLlama (coding)"),
("ollama-mistral", "ollama run mistral", "Mistral (fast)"),
],
@@ -336,7 +345,7 @@ class ProviderInstallDialog(QDialog):
return opencode_path.exists() or shutil.which("opencode") is not None
elif provider_key == "ollama":
return shutil.which("ollama") is not None
elif provider_key in ("claude", "codex", "gemini"):
elif provider_key in ("claude", "codex", "agy", "crush"):
return shutil.which(provider_key) is not None
return False
@@ -351,10 +360,11 @@ class ProviderInstallDialog(QDialog):
self.btn_install.setEnabled(True)
# Update details
install_method = info['install_cmd'] or "Manual download"
details = f"""<b>{info['group']}</b><br><br>
<b>Cost:</b> {info['cost']}<br>
<b>Requirements:</b> {info['requires']}<br>
<b>Install command:</b> <code>{info['install_cmd']}</code><br>
<b>Install method:</b> <code>{install_method}</code><br>
<b>After install:</b> {info['setup']}<br><br>
<b>Available models:</b> {', '.join(v[0] for v in info['variants'])}"""
@@ -364,7 +374,10 @@ class ProviderInstallDialog(QDialog):
self.details_label.setText(details)
# Change button text based on installed status
self.btn_install.setText("Reinstall" if is_installed else "Install Selected")
if info["install_cmd"]:
self.btn_install.setText("Reinstall" if is_installed else "Install Selected")
else:
self.btn_install.setText("Add Providers" if is_installed else "Setup Instructions")
def _on_provider_double_clicked(self, item: QListWidgetItem):
"""Handle double-click to start install."""
@@ -378,6 +391,14 @@ class ProviderInstallDialog(QDialog):
info = PROVIDER_INSTALL_INFO[self._selected_provider]
if not info["install_cmd"]:
if self._check_installed(self._selected_provider):
self._populate_variants()
self.stack.setCurrentIndex(2)
else:
QMessageBox.information(self, "Manual Setup", info["setup"])
return
# Switch to install page
self.stack.setCurrentIndex(1)
self.install_title.setText(f"Installing {info['group']}...")
@@ -450,10 +471,8 @@ class ProviderInstallDialog(QDialog):
# Update next steps
self.next_steps_label.setText(
f"<b>Important:</b> Before using these providers, you need to authenticate:<br><br>"
f"1. Open a terminal<br>"
f"2. Run: <code>source ~/.bashrc</code> (to update PATH)<br>"
f"3. Run: <code>{info['setup'].split(' - ')[0].replace('Run ', '')}</code><br><br>"
f"This will open your browser to sign in."
f"1. Open a new terminal so PATH changes are loaded.<br>"
f"2. {info['setup']}"
)
def _add_selected_variants(self):