From 3dd4e7c1656042e6cfd63bd4c7583b21c4d9f089 Mon Sep 17 00:00:00 2001 From: John Ho Date: Wed, 24 Jun 2026 14:26:55 -0400 Subject: [PATCH] added support for using claude code with ollama --- .env.example | 4 ++++ README.md | 29 +++++++++++++++++++++-------- src/bender/claude_code.py | 9 ++++++++- src/bender/config.py | 5 ++++- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/.env.example b/.env.example index cd55630..6901f09 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,10 @@ SLACK_APP_TOKEN=xapp-your-app-token # ------------------------------------------- ANTHROPIC_API_KEY=sk-ant-your-api-key # CLAUDE_CODE_OAUTH_TOKEN=your-oauth-token +# -- OR leave ANTHROPIC_API_KEY unset so ANTHROPIC_AUTH_TOKEN takes precedence for Ollama routing +# ANTHROPIC_AUTH_TOKEN=ollama +# ANTHROPIC_BASE_URL=http://localhost:11434 +# CLAUDE_MODEL=glm-4.7:cloud # ------------------------------------------- # Optional: Bender settings diff --git a/README.md b/README.md index 0d17539..d936574 100644 --- a/README.md +++ b/README.md @@ -90,10 +90,18 @@ LOG_LEVEL="info" # Logging level (default: info) - `chat:write` — Post messages and thread replies - `channels:history` — Read messages in public channels - `groups:history` — Read messages in private channels (if needed) -4. Subscribe to these **Events**: - - `app_mention` — Trigger on @mentions - - `message.channels` — Listen for thread replies -5. Install the app to your workspace and copy the Bot User OAuth Token (`xoxb-...`) +4. Configure **Event Subscriptions** (required — Socket Mode connects but delivers no events until this is enabled): + - Open the **Event Subscriptions** page and toggle **Enable Events** to **On**. + - Expand **Subscribe to bot events** and add: + - `app_mention` — Trigger on @mentions + - `message.channels` — Listen for thread replies in public channels + - `message.groups` — Listen for thread replies in private channels (if needed) + - Click **Save Changes**. + + > **Note:** Having the matching OAuth scopes is *not* sufficient — you must enable Event Subscriptions and subscribe to the bot events above, or no events will reach the app. +5. Install (or **reinstall**) the app to your workspace and copy the Bot User OAuth Token (`xoxb-...`) + + > **Note:** If you change scopes or event subscriptions after the first install, you must reinstall the app for the changes to take effect. ### Workspace Directory @@ -116,13 +124,18 @@ workspace/ # Example agent configuration ### Running Bender ```bash -# Using the module -python -m bender +# Copy and fill in your environment variables first +cp .env.example .env -# Or with environment variables inline -SLACK_BOT_TOKEN=xoxb-... SLACK_APP_TOKEN=xapp-... ANTHROPIC_API_KEY=sk-ant-... python -m bender +# Run with uv (loads .env into the process environment) +uv run --env-file .env python -m bender + +# Or, if the venv is activated and vars are exported in your shell +python -m bender ``` +> **Note:** Bender does not auto-load `.env` — the variables must be present in the process environment. `uv run --env-file .env` handles this for you; otherwise export them in your shell first. + Bender starts both the Slack Socket Mode handler and the FastAPI HTTP server concurrently. ### Slack Interaction diff --git a/src/bender/claude_code.py b/src/bender/claude_code.py index 1c5622d..05b791d 100644 --- a/src/bender/claude_code.py +++ b/src/bender/claude_code.py @@ -3,6 +3,7 @@ import asyncio import json import logging +import os from dataclasses import dataclass from pathlib import Path @@ -10,6 +11,7 @@ # Default timeout for Claude Code invocations (5 minutes) DEFAULT_TIMEOUT_SECONDS = 300 +DEFAULT_MODEL_NAME = os.getenv("CLAUDE_MODEL", None) @dataclass @@ -31,6 +33,7 @@ async def invoke_claude( session_id: str | None = None, resume: bool = False, timeout: int = DEFAULT_TIMEOUT_SECONDS, + model_name: str | None = DEFAULT_MODEL_NAME, ) -> ClaudeResponse: """Invoke Claude Code CLI in headless mode via subprocess. @@ -49,6 +52,8 @@ async def invoke_claude( """ cmd = ["claude", "--print", "--output-format", "json"] + if model_name: + cmd.extend(["--model", model_name]) if resume and session_id: cmd.extend(["--resume", session_id]) elif session_id: @@ -89,7 +94,9 @@ async def invoke_claude( if process.returncode != 0: error_msg = stderr.decode().strip() if stderr else "Unknown error" logger.error("Claude Code failed (exit=%d): %s", process.returncode, error_msg) - raise ClaudeCodeError(f"Claude Code exited with code {process.returncode}: {error_msg}") + raise ClaudeCodeError( + f"Claude Code exited with code {process.returncode}: {error_msg}" + ) return _parse_response(stdout.decode(), session_id or "") diff --git a/src/bender/config.py b/src/bender/config.py index d6e32e5..cb3b9c8 100644 --- a/src/bender/config.py +++ b/src/bender/config.py @@ -16,6 +16,7 @@ class Settings(BaseSettings): # Required: Claude Code authentication (at least one) anthropic_api_key: str | None = None claude_code_oauth_token: str | None = None + anthropic_base_url: str | None = None # Optional bender_workspace: Path = Path.cwd() @@ -29,7 +30,9 @@ class Settings(BaseSettings): def validate_auth(self) -> None: """Ensure at least one Claude Code authentication method is configured.""" - if not self.anthropic_api_key and not self.claude_code_oauth_token: + if self.anthropic_base_url: + logging.info(f"Using ANTHROPIC_BASE_URL: {self.anthropic_base_url}") + elif not self.anthropic_api_key and not self.claude_code_oauth_token: raise ValueError( "At least one authentication method is required: " "ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN"