From 095d248a6603007e81335e72f238a69f319478e7 Mon Sep 17 00:00:00 2001 From: Auto Date: Mon, 26 Jan 2026 09:42:01 +0200 Subject: [PATCH 1/2] add ollama support --- .claude/settings.local.json | 8 ++++++++ .env.example | 17 +++++++++++++++++ CLAUDE.md | 33 +++++++++++++++++++++++++++++++++ assets/ollama.png | Bin 0 -> 3706 bytes client.py | 12 ++++++++++-- server/routers/settings.py | 12 +++++++++++- server/schemas.py | 1 + ui/public/ollama.png | Bin 0 -> 3706 bytes ui/src/App.tsx | 11 +++++++++++ ui/src/hooks/useProjects.ts | 1 + ui/src/lib/types.ts | 1 + 11 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 assets/ollama.png create mode 100644 ui/public/ollama.png diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..cf757bf --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(copy \"C:\\\\Projects\\\\autocoder\\\\assets\\\\ollama.png\" \"C:\\\\Projects\\\\autocoder\\\\ui\\\\public\\\\ollama.png\")", + "Bash(npm run build:*)" + ] + } +} diff --git a/.env.example b/.env.example index e29bec3..aa3d9fa 100644 --- a/.env.example +++ b/.env.example @@ -19,3 +19,20 @@ # ANTHROPIC_DEFAULT_SONNET_MODEL=glm-4.7 # ANTHROPIC_DEFAULT_OPUS_MODEL=glm-4.7 # ANTHROPIC_DEFAULT_HAIKU_MODEL=glm-4.5-air + +# Ollama Local Model Configuration (Optional) +# To use local models via Ollama instead of Claude, uncomment and set these variables. +# Requires Ollama v0.14.0+ with Anthropic API compatibility. +# See: https://ollama.com/blog/claude +# +# ANTHROPIC_BASE_URL=http://localhost:11434 +# ANTHROPIC_AUTH_TOKEN=ollama +# API_TIMEOUT_MS=3000000 +# ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3-coder +# ANTHROPIC_DEFAULT_OPUS_MODEL=qwen3-coder +# ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3-coder +# +# Model recommendations: +# - For best results, use a capable coding model like qwen3-coder or deepseek-coder-v2 +# - You can use the same model for all tiers, or different models per tier +# - Larger models (70B+) work best for Opus tier, smaller (7B-20B) for Haiku diff --git a/CLAUDE.md b/CLAUDE.md index 29cc2a5..c7a1b93 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -256,6 +256,39 @@ python test_security_integration.py - `examples/README.md` - Comprehensive guide with use cases, testing, and troubleshooting - `PHASE3_SPEC.md` - Specification for mid-session approval feature (future enhancement) +### Ollama Local Models (Optional) + +Run coding agents using local models via Ollama v0.14.0+: + +1. Install Ollama: https://ollama.com +2. Start Ollama: `ollama serve` +3. Pull a coding model: `ollama pull qwen3-coder` +4. Configure `.env`: + ``` + ANTHROPIC_BASE_URL=http://localhost:11434 + ANTHROPIC_AUTH_TOKEN=ollama + API_TIMEOUT_MS=3000000 + ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3-coder + ANTHROPIC_DEFAULT_OPUS_MODEL=qwen3-coder + ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3-coder + ``` +5. Run autocoder normally - it will use your local Ollama models + +**Recommended coding models:** +- `qwen3-coder` - Good balance of speed and capability +- `deepseek-coder-v2` - Strong coding performance +- `codellama` - Meta's code-focused model + +**Model tier mapping:** +- Use the same model for all tiers, or map different models per capability level +- Larger models (70B+) work best for Opus tier +- Smaller models (7B-20B) work well for Haiku tier + +**Known limitations:** +- Smaller context windows than Claude (model-dependent) +- Extended context beta disabled (not supported by Ollama) +- Performance depends on local hardware (GPU recommended) + ## Claude Code Integration - `.claude/commands/create-spec.md` - `/create-spec` slash command for interactive spec creation diff --git a/assets/ollama.png b/assets/ollama.png new file mode 100644 index 0000000000000000000000000000000000000000..9f559ae7db3cc2be98a19647e5e838420da278ef GIT binary patch literal 3706 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 Y4^M=S;B7=Er~m)}07*qoM6N<$f+*q<_5c6? literal 0 HcmV?d00001 diff --git a/client.py b/client.py index e844aa4..7994f64 100644 --- a/client.py +++ b/client.py @@ -257,9 +257,16 @@ def create_client( if value: sdk_env[var] = value + # Detect alternative API mode (Ollama or GLM) + base_url = sdk_env.get("ANTHROPIC_BASE_URL", "") + is_alternative_api = bool(base_url) + is_ollama = "localhost:11434" in base_url or "127.0.0.1:11434" in base_url + if sdk_env: print(f" - API overrides: {', '.join(sdk_env.keys())}") - if "ANTHROPIC_BASE_URL" in sdk_env: + if is_ollama: + print(" - Ollama Mode: Using local models") + elif "ANTHROPIC_BASE_URL" in sdk_env: print(f" - GLM Mode: Using {sdk_env['ANTHROPIC_BASE_URL']}") # Create a wrapper for bash_security_hook that passes project_dir via context @@ -336,7 +343,8 @@ def create_client( # Enable extended context beta for better handling of long sessions. # This provides up to 1M tokens of context with automatic compaction. # See: https://docs.anthropic.com/en/api/beta-headers - betas=["context-1m-2025-08-07"], + # Disabled for alternative APIs (Ollama, GLM) as they don't support Claude-specific betas. + betas=[] if is_alternative_api else ["context-1m-2025-08-07"], # Note on context management: # The Claude Agent SDK handles context management automatically through the # underlying Claude Code CLI. When context approaches limits, the CLI diff --git a/server/routers/settings.py b/server/routers/settings.py index cf16045..8f3f906 100644 --- a/server/routers/settings.py +++ b/server/routers/settings.py @@ -40,7 +40,15 @@ def _parse_yolo_mode(value: str | None) -> bool: def _is_glm_mode() -> bool: """Check if GLM API is configured via environment variables.""" - return bool(os.getenv("ANTHROPIC_BASE_URL")) + base_url = os.getenv("ANTHROPIC_BASE_URL", "") + # GLM mode is when ANTHROPIC_BASE_URL is set but NOT pointing to Ollama + return bool(base_url) and not _is_ollama_mode() + + +def _is_ollama_mode() -> bool: + """Check if Ollama API is configured via environment variables.""" + base_url = os.getenv("ANTHROPIC_BASE_URL", "") + return "localhost:11434" in base_url or "127.0.0.1:11434" in base_url @router.get("/models", response_model=ModelsResponse) @@ -82,6 +90,7 @@ async def get_settings(): yolo_mode=_parse_yolo_mode(all_settings.get("yolo_mode")), model=all_settings.get("model", DEFAULT_MODEL), glm_mode=_is_glm_mode(), + ollama_mode=_is_ollama_mode(), testing_agent_ratio=_parse_int(all_settings.get("testing_agent_ratio"), 1), ) @@ -104,5 +113,6 @@ async def update_settings(update: SettingsUpdate): yolo_mode=_parse_yolo_mode(all_settings.get("yolo_mode")), model=all_settings.get("model", DEFAULT_MODEL), glm_mode=_is_glm_mode(), + ollama_mode=_is_ollama_mode(), testing_agent_ratio=_parse_int(all_settings.get("testing_agent_ratio"), 1), ) diff --git a/server/schemas.py b/server/schemas.py index 844aaa1..0a2807c 100644 --- a/server/schemas.py +++ b/server/schemas.py @@ -382,6 +382,7 @@ class SettingsResponse(BaseModel): yolo_mode: bool = False model: str = DEFAULT_MODEL glm_mode: bool = False # True if GLM API is configured via .env + ollama_mode: bool = False # True if Ollama API is configured via .env testing_agent_ratio: int = 1 # Regression testing agents (0-3) diff --git a/ui/public/ollama.png b/ui/public/ollama.png new file mode 100644 index 0000000000000000000000000000000000000000..9f559ae7db3cc2be98a19647e5e838420da278ef GIT binary patch literal 3706 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 Y4^M=S;B7=Er~m)}07*qoM6N<$f+*q<_5c6? literal 0 HcmV?d00001 diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 59ed0ab..0483ab7 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -298,6 +298,17 @@ function App() { + {/* Ollama Mode Indicator */} + {settings?.ollama_mode && ( +
+ Ollama + Ollama +
+ )} + {/* GLM Mode Badge */} {settings?.glm_mode && ( Date: Mon, 26 Jan 2026 09:49:21 +0200 Subject: [PATCH 2/2] chore: remove duplicate asset and gitignore local settings - Remove assets/ollama.png (duplicate of ui/public/ollama.png) - Remove .claude/settings.local.json from tracking - Add .claude/settings.local.json to .gitignore Co-Authored-By: Claude Opus 4.5 --- .claude/settings.local.json | 8 -------- .gitignore | 5 +++++ assets/ollama.png | Bin 3706 -> 0 bytes 3 files changed, 5 insertions(+), 8 deletions(-) delete mode 100644 .claude/settings.local.json delete mode 100644 assets/ollama.png diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index cf757bf..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(copy \"C:\\\\Projects\\\\autocoder\\\\assets\\\\ollama.png\" \"C:\\\\Projects\\\\autocoder\\\\ui\\\\public\\\\ollama.png\")", - "Bash(npm run build:*)" - ] - } -} diff --git a/.gitignore b/.gitignore index d90c6ca..bb20118 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,11 @@ ui/playwright-report/ .dmypy.json dmypy.json +# =================== +# Claude Code +# =================== +.claude/settings.local.json + # =================== # IDE / Editors # =================== diff --git a/assets/ollama.png b/assets/ollama.png deleted file mode 100644 index 9f559ae7db3cc2be98a19647e5e838420da278ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3706 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 Y4^M=S;B7=Er~m)}07*qoM6N<$f+*q<_5c6?