ai-agent-bridge 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ai_agent_bridge-0.1.0/.dockerignore +7 -0
- ai_agent_bridge-0.1.0/.env.example +23 -0
- ai_agent_bridge-0.1.0/.gitignore +210 -0
- ai_agent_bridge-0.1.0/Dockerfile +38 -0
- ai_agent_bridge-0.1.0/LICENSE +21 -0
- ai_agent_bridge-0.1.0/PKG-INFO +247 -0
- ai_agent_bridge-0.1.0/README.md +223 -0
- ai_agent_bridge-0.1.0/docker-compose.yml +21 -0
- ai_agent_bridge-0.1.0/pyproject.toml +47 -0
- ai_agent_bridge-0.1.0/src/agent_bridge/__init__.py +91 -0
- ai_agent_bridge-0.1.0/src/agent_bridge/agents/__init__.py +0 -0
- ai_agent_bridge-0.1.0/src/agent_bridge/agents/claude/__init__.py +0 -0
- ai_agent_bridge-0.1.0/src/agent_bridge/agents/claude/config.py +51 -0
- ai_agent_bridge-0.1.0/src/agent_bridge/agents/claude/controller.py +167 -0
- ai_agent_bridge-0.1.0/src/agent_bridge/agents/claude/events.py +175 -0
- ai_agent_bridge-0.1.0/src/agent_bridge/bridge.py +40 -0
- ai_agent_bridge-0.1.0/src/agent_bridge/config.py +34 -0
- ai_agent_bridge-0.1.0/src/agent_bridge/events.py +32 -0
- ai_agent_bridge-0.1.0/src/agent_bridge/platforms/__init__.py +0 -0
- ai_agent_bridge-0.1.0/src/agent_bridge/platforms/slack/__init__.py +0 -0
- ai_agent_bridge-0.1.0/src/agent_bridge/platforms/slack/adapter.py +235 -0
- ai_agent_bridge-0.1.0/src/agent_bridge/platforms/slack/config.py +24 -0
- ai_agent_bridge-0.1.0/src/agent_bridge/protocols.py +35 -0
- ai_agent_bridge-0.1.0/src/agent_bridge/session.py +127 -0
- ai_agent_bridge-0.1.0/tests/__init__.py +0 -0
- ai_agent_bridge-0.1.0/tests/test_events.py +259 -0
- ai_agent_bridge-0.1.0/tests/test_session.py +214 -0
- ai_agent_bridge-0.1.0/uv.lock +658 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Slack Bot Token (xoxb-...)
|
|
2
|
+
SLACK_BOT_TOKEN=
|
|
3
|
+
|
|
4
|
+
# Slack App-Level Token for Socket Mode (xapp-...)
|
|
5
|
+
SLACK_APP_TOKEN=
|
|
6
|
+
|
|
7
|
+
# Anthropic API Key (required for Docker / non-interactive environments)
|
|
8
|
+
ANTHROPIC_API_KEY=
|
|
9
|
+
|
|
10
|
+
# Directory where Claude Code will operate
|
|
11
|
+
CLAUDE_WORK_DIR=.
|
|
12
|
+
|
|
13
|
+
# Claude Code permission mode: acceptEdits, dangerously-skip-permissions, default, plan
|
|
14
|
+
CLAUDE_PERMISSION_MODE=acceptEdits
|
|
15
|
+
|
|
16
|
+
# Path to session store JSON file
|
|
17
|
+
SESSION_STORE_PATH=./sessions.json
|
|
18
|
+
|
|
19
|
+
# Session TTL in hours (sessions inactive longer than this are expired)
|
|
20
|
+
SESSION_TTL_HOURS=72
|
|
21
|
+
|
|
22
|
+
# Timeout for a single Claude invocation in seconds
|
|
23
|
+
CLAUDE_TIMEOUT_SECONDS=300
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Agent Bridge runtime data
|
|
2
|
+
sessions.json
|
|
3
|
+
|
|
4
|
+
# Byte-compiled / optimized / DLL files
|
|
5
|
+
__pycache__/
|
|
6
|
+
*.py[codz]
|
|
7
|
+
*$py.class
|
|
8
|
+
|
|
9
|
+
# C extensions
|
|
10
|
+
*.so
|
|
11
|
+
|
|
12
|
+
# Distribution / packaging
|
|
13
|
+
.Python
|
|
14
|
+
build/
|
|
15
|
+
develop-eggs/
|
|
16
|
+
dist/
|
|
17
|
+
downloads/
|
|
18
|
+
eggs/
|
|
19
|
+
.eggs/
|
|
20
|
+
lib/
|
|
21
|
+
lib64/
|
|
22
|
+
parts/
|
|
23
|
+
sdist/
|
|
24
|
+
var/
|
|
25
|
+
wheels/
|
|
26
|
+
share/python-wheels/
|
|
27
|
+
*.egg-info/
|
|
28
|
+
.installed.cfg
|
|
29
|
+
*.egg
|
|
30
|
+
MANIFEST
|
|
31
|
+
|
|
32
|
+
# PyInstaller
|
|
33
|
+
# Usually these files are written by a python script from a template
|
|
34
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
35
|
+
*.manifest
|
|
36
|
+
*.spec
|
|
37
|
+
|
|
38
|
+
# Installer logs
|
|
39
|
+
pip-log.txt
|
|
40
|
+
pip-delete-this-directory.txt
|
|
41
|
+
|
|
42
|
+
# Unit test / coverage reports
|
|
43
|
+
htmlcov/
|
|
44
|
+
.tox/
|
|
45
|
+
.nox/
|
|
46
|
+
.coverage
|
|
47
|
+
.coverage.*
|
|
48
|
+
.cache
|
|
49
|
+
nosetests.xml
|
|
50
|
+
coverage.xml
|
|
51
|
+
*.cover
|
|
52
|
+
*.py.cover
|
|
53
|
+
.hypothesis/
|
|
54
|
+
.pytest_cache/
|
|
55
|
+
cover/
|
|
56
|
+
|
|
57
|
+
# Translations
|
|
58
|
+
*.mo
|
|
59
|
+
*.pot
|
|
60
|
+
|
|
61
|
+
# Django stuff:
|
|
62
|
+
*.log
|
|
63
|
+
local_settings.py
|
|
64
|
+
db.sqlite3
|
|
65
|
+
db.sqlite3-journal
|
|
66
|
+
|
|
67
|
+
# Flask stuff:
|
|
68
|
+
instance/
|
|
69
|
+
.webassets-cache
|
|
70
|
+
|
|
71
|
+
# Scrapy stuff:
|
|
72
|
+
.scrapy
|
|
73
|
+
|
|
74
|
+
# Sphinx documentation
|
|
75
|
+
docs/_build/
|
|
76
|
+
|
|
77
|
+
# PyBuilder
|
|
78
|
+
.pybuilder/
|
|
79
|
+
target/
|
|
80
|
+
|
|
81
|
+
# Jupyter Notebook
|
|
82
|
+
.ipynb_checkpoints
|
|
83
|
+
|
|
84
|
+
# IPython
|
|
85
|
+
profile_default/
|
|
86
|
+
ipython_config.py
|
|
87
|
+
|
|
88
|
+
# pyenv
|
|
89
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
90
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
91
|
+
# .python-version
|
|
92
|
+
|
|
93
|
+
# pipenv
|
|
94
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
95
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
96
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
97
|
+
# install all needed dependencies.
|
|
98
|
+
#Pipfile.lock
|
|
99
|
+
|
|
100
|
+
# UV
|
|
101
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
102
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
103
|
+
# commonly ignored for libraries.
|
|
104
|
+
#uv.lock
|
|
105
|
+
|
|
106
|
+
# poetry
|
|
107
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
108
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
109
|
+
# commonly ignored for libraries.
|
|
110
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
111
|
+
#poetry.lock
|
|
112
|
+
#poetry.toml
|
|
113
|
+
|
|
114
|
+
# pdm
|
|
115
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
116
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
117
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
118
|
+
#pdm.lock
|
|
119
|
+
#pdm.toml
|
|
120
|
+
.pdm-python
|
|
121
|
+
.pdm-build/
|
|
122
|
+
|
|
123
|
+
# pixi
|
|
124
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
125
|
+
#pixi.lock
|
|
126
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
127
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
128
|
+
.pixi
|
|
129
|
+
|
|
130
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
131
|
+
__pypackages__/
|
|
132
|
+
|
|
133
|
+
# Celery stuff
|
|
134
|
+
celerybeat-schedule
|
|
135
|
+
celerybeat.pid
|
|
136
|
+
|
|
137
|
+
# SageMath parsed files
|
|
138
|
+
*.sage.py
|
|
139
|
+
|
|
140
|
+
# Environments
|
|
141
|
+
.env
|
|
142
|
+
.envrc
|
|
143
|
+
.venv
|
|
144
|
+
env/
|
|
145
|
+
venv/
|
|
146
|
+
ENV/
|
|
147
|
+
env.bak/
|
|
148
|
+
venv.bak/
|
|
149
|
+
|
|
150
|
+
# Spyder project settings
|
|
151
|
+
.spyderproject
|
|
152
|
+
.spyproject
|
|
153
|
+
|
|
154
|
+
# Rope project settings
|
|
155
|
+
.ropeproject
|
|
156
|
+
|
|
157
|
+
# mkdocs documentation
|
|
158
|
+
/site
|
|
159
|
+
|
|
160
|
+
# mypy
|
|
161
|
+
.mypy_cache/
|
|
162
|
+
.dmypy.json
|
|
163
|
+
dmypy.json
|
|
164
|
+
|
|
165
|
+
# Pyre type checker
|
|
166
|
+
.pyre/
|
|
167
|
+
|
|
168
|
+
# pytype static type analyzer
|
|
169
|
+
.pytype/
|
|
170
|
+
|
|
171
|
+
# Cython debug symbols
|
|
172
|
+
cython_debug/
|
|
173
|
+
|
|
174
|
+
# PyCharm
|
|
175
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
176
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
177
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
178
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
179
|
+
#.idea/
|
|
180
|
+
|
|
181
|
+
# Abstra
|
|
182
|
+
# Abstra is an AI-powered process automation framework.
|
|
183
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
184
|
+
# Learn more at https://abstra.io/docs
|
|
185
|
+
.abstra/
|
|
186
|
+
|
|
187
|
+
# Visual Studio Code
|
|
188
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
189
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
190
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
191
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
192
|
+
# .vscode/
|
|
193
|
+
|
|
194
|
+
# Ruff stuff:
|
|
195
|
+
.ruff_cache/
|
|
196
|
+
|
|
197
|
+
# PyPI configuration file
|
|
198
|
+
.pypirc
|
|
199
|
+
|
|
200
|
+
# Cursor
|
|
201
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
202
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
203
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
204
|
+
.cursorignore
|
|
205
|
+
.cursorindexingignore
|
|
206
|
+
|
|
207
|
+
# Marimo
|
|
208
|
+
marimo/_static/
|
|
209
|
+
marimo/_lsp/
|
|
210
|
+
__marimo__/
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
FROM python:3.12-slim
|
|
2
|
+
|
|
3
|
+
# Install Node.js (required for Claude Code CLI)
|
|
4
|
+
RUN apt-get update && \
|
|
5
|
+
apt-get install -y --no-install-recommends curl git && \
|
|
6
|
+
curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
|
|
7
|
+
apt-get install -y --no-install-recommends nodejs && \
|
|
8
|
+
apt-get clean && rm -rf /var/lib/apt/lists/*
|
|
9
|
+
|
|
10
|
+
# Install Claude Code CLI globally
|
|
11
|
+
RUN npm install -g @anthropic-ai/claude-code
|
|
12
|
+
|
|
13
|
+
# Install uv
|
|
14
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
|
15
|
+
|
|
16
|
+
# Set up workspace
|
|
17
|
+
WORKDIR /app
|
|
18
|
+
|
|
19
|
+
# Install dependencies first (cache layer)
|
|
20
|
+
COPY pyproject.toml uv.lock ./
|
|
21
|
+
RUN uv sync --frozen --no-dev --no-install-project
|
|
22
|
+
|
|
23
|
+
# Copy source code (tests excluded from production image)
|
|
24
|
+
COPY src/ src/
|
|
25
|
+
RUN uv sync --frozen --no-dev
|
|
26
|
+
|
|
27
|
+
# Claude Code work directory (mount your project here)
|
|
28
|
+
RUN mkdir -p /workspace
|
|
29
|
+
VOLUME /workspace
|
|
30
|
+
|
|
31
|
+
# Session store persistence
|
|
32
|
+
VOLUME /app/data
|
|
33
|
+
|
|
34
|
+
ENV CLAUDE_WORK_DIR=/workspace \
|
|
35
|
+
SESSION_STORE_PATH=/app/data/sessions.json \
|
|
36
|
+
CLAUDE_PERMISSION_MODE=acceptEdits
|
|
37
|
+
|
|
38
|
+
CMD ["uv", "run", "agent-bridge"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 htkuan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ai-agent-bridge
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Modular bridge between chat platforms and AI agents
|
|
5
|
+
Project-URL: Repository, https://github.com/htkuan/agent-bridge
|
|
6
|
+
Author: htkuan
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Topic :: Communications :: Chat
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
17
|
+
Provides-Extra: all
|
|
18
|
+
Requires-Dist: aiohttp>=3.9.0; extra == 'all'
|
|
19
|
+
Requires-Dist: slack-bolt>=1.21.0; extra == 'all'
|
|
20
|
+
Provides-Extra: slack
|
|
21
|
+
Requires-Dist: aiohttp>=3.9.0; extra == 'slack'
|
|
22
|
+
Requires-Dist: slack-bolt>=1.21.0; extra == 'slack'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Agent Bridge
|
|
26
|
+
|
|
27
|
+
Modular bridge service that connects **chat platforms** to **AI agents**. The architecture cleanly separates platform concerns from agent concerns, making it easy to add new platforms or agents.
|
|
28
|
+
|
|
29
|
+
Currently supports **Slack** as the chat platform and **Claude Code** as the AI agent backend.
|
|
30
|
+
|
|
31
|
+
## Architecture
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
┌─────────────────────────┐
|
|
35
|
+
│ Platform (Slack) │ Defines session semantics (thread = session)
|
|
36
|
+
│ platforms/slack/ │ Manages per-session locking & flow control
|
|
37
|
+
│ - adapter.py │ Renders agent events (stream updates, final reply)
|
|
38
|
+
│ - config.py │
|
|
39
|
+
└──────────┬──────────────┘
|
|
40
|
+
│ session_key, text, context
|
|
41
|
+
▼
|
|
42
|
+
┌─────────────────────────┐
|
|
43
|
+
│ Bridge │ Pure routing — no platform or agent knowledge
|
|
44
|
+
│ bridge.py │ session_key → session_id (via SessionManager)
|
|
45
|
+
│ session.py │ Forwards to agent, yields BridgeEvents back
|
|
46
|
+
│ events.py │ TextDelta | StatusUpdate | Completion
|
|
47
|
+
│ protocols.py │ AgentController + PlatformAdapter protocols
|
|
48
|
+
└──────────┬──────────────┘
|
|
49
|
+
│ session_id, prompt, is_new, context
|
|
50
|
+
▼
|
|
51
|
+
┌─────────────────────────┐
|
|
52
|
+
│ Agent (Claude Code) │ Purely invoked: load session + input → output
|
|
53
|
+
│ agents/claude/ │ Translates Claude stream-json → BridgeEvents
|
|
54
|
+
│ - controller.py │ Does not define sessions or care about rendering
|
|
55
|
+
│ - events.py │
|
|
56
|
+
│ - config.py │
|
|
57
|
+
└─────────────────────────┘
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Design Principles
|
|
61
|
+
|
|
62
|
+
**Platform defines session semantics.** A Slack thread is a session. A Discord channel might be a session. This is platform knowledge — the bridge and agent don't care how sessions are defined.
|
|
63
|
+
|
|
64
|
+
**Agent is purely invoked.** It receives `(session_id, prompt, is_new, context)`, loads the session, executes, and yields events. It doesn't know where the session came from or how results will be rendered.
|
|
65
|
+
|
|
66
|
+
**Bridge is pure routing.** It resolves session keys to session IDs and forwards requests/events. No platform-specific or agent-specific logic.
|
|
67
|
+
|
|
68
|
+
### Generic Event Model
|
|
69
|
+
|
|
70
|
+
Platforms consume three event types — the common language between any agent and any platform:
|
|
71
|
+
|
|
72
|
+
| Event | Description |
|
|
73
|
+
|-------|-------------|
|
|
74
|
+
| `TextDelta` | Incremental text from the agent |
|
|
75
|
+
| `StatusUpdate` | Agent is performing an action (tool use, thinking, etc.) |
|
|
76
|
+
| `Completion` | Agent finished responding (with cost, duration, error status) |
|
|
77
|
+
|
|
78
|
+
Agent-internal events (init, thinking, tool results) are translated to these generic types within each agent module.
|
|
79
|
+
|
|
80
|
+
### Data Flow
|
|
81
|
+
|
|
82
|
+
1. User sends a message in Slack (via `@mention` in channel or direct message)
|
|
83
|
+
2. **Slack Adapter** receives the event, constructs a session key (`slack:{channel}:{thread_ts}`)
|
|
84
|
+
3. **Slack Adapter** acquires per-session lock (prevents concurrent processing)
|
|
85
|
+
4. **Bridge** resolves session key → session ID via **SessionManager**
|
|
86
|
+
5. **Agent (Claude Controller)** spawns `claude -p` with the session, yields `BridgeEvent`s
|
|
87
|
+
6. **Slack Adapter** renders events as real-time message updates (throttled to avoid rate limits)
|
|
88
|
+
|
|
89
|
+
### Session Management
|
|
90
|
+
|
|
91
|
+
- Each Slack thread maps to one agent session (defined by the platform)
|
|
92
|
+
- The bridge stores the mapping: `session_key → {session_id, created_at, last_used}`
|
|
93
|
+
- Mappings are persisted in a JSON file
|
|
94
|
+
- Sessions have a configurable TTL (default 72 hours) — expired sessions are automatically purged
|
|
95
|
+
|
|
96
|
+
## Tech Stack
|
|
97
|
+
|
|
98
|
+
| Component | Choice | Reason |
|
|
99
|
+
|-----------|--------|--------|
|
|
100
|
+
| Language | Python 3.12+ | Type union syntax (`X \| Y`), `match` statements, modern asyncio |
|
|
101
|
+
| Package manager | uv | Fast, supports pyproject.toml natively |
|
|
102
|
+
| Slack SDK | [slack-bolt](https://github.com/slackapi/bolt-python) | Official Slack SDK, async support, Socket Mode |
|
|
103
|
+
| Async HTTP | aiohttp | Required by slack-bolt for async Socket Mode |
|
|
104
|
+
| Env config | python-dotenv | Load `.env` files |
|
|
105
|
+
| Testing | pytest + pytest-asyncio | Standard Python testing |
|
|
106
|
+
| Claude CLI | `claude -p` with `--output-format stream-json` | Non-interactive mode with real-time streaming |
|
|
107
|
+
|
|
108
|
+
## Project Structure
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
agent-bridge/
|
|
112
|
+
├── pyproject.toml
|
|
113
|
+
├── .env.example
|
|
114
|
+
├── src/
|
|
115
|
+
│ └── agent_bridge/
|
|
116
|
+
│ ├── __init__.py # Entry point: wires platform + bridge + agent
|
|
117
|
+
│ ├── config.py # BridgeConfig (session store, TTL)
|
|
118
|
+
│ ├── bridge.py # Pure routing: session resolve → agent call
|
|
119
|
+
│ ├── events.py # TextDelta, StatusUpdate, Completion
|
|
120
|
+
│ ├── session.py # SessionManager (key → session_id mapping)
|
|
121
|
+
│ ├── protocols.py # AgentController + PlatformAdapter protocols
|
|
122
|
+
│ ├── agents/
|
|
123
|
+
│ │ └── claude/
|
|
124
|
+
│ │ ├── config.py # ClaudeConfig (work_dir, permissions, timeout)
|
|
125
|
+
│ │ ├── controller.py # Claude Code subprocess controller
|
|
126
|
+
│ │ └── events.py # Claude stream-json parser + BridgeEvent converter
|
|
127
|
+
│ └── platforms/
|
|
128
|
+
│ └── slack/
|
|
129
|
+
│ ├── config.py # SlackConfig (bot_token, app_token)
|
|
130
|
+
│ └── adapter.py # Slack adapter (session def, locking, rendering)
|
|
131
|
+
└── tests/
|
|
132
|
+
├── test_events.py # Claude event parsing + BridgeEvent conversion
|
|
133
|
+
└── test_session.py # Session manager tests
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Setup
|
|
137
|
+
|
|
138
|
+
### Prerequisites
|
|
139
|
+
|
|
140
|
+
- Python 3.12+
|
|
141
|
+
- [uv](https://docs.astral.sh/uv/)
|
|
142
|
+
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated
|
|
143
|
+
|
|
144
|
+
### Install
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
git clone <repo-url>
|
|
148
|
+
cd agent-bridge
|
|
149
|
+
uv sync
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Slack App Configuration
|
|
153
|
+
|
|
154
|
+
1. Create a Slack App at [api.slack.com/apps](https://api.slack.com/apps)
|
|
155
|
+
2. Enable **Socket Mode** and generate an App-Level Token (`xapp-...`)
|
|
156
|
+
3. Add the following **Bot Token Scopes** under OAuth & Permissions:
|
|
157
|
+
- `app_mentions:read` — receive @mention events
|
|
158
|
+
- `chat:write` — send and update messages
|
|
159
|
+
- `im:history` — read DM messages
|
|
160
|
+
- `im:read` — access DM channels
|
|
161
|
+
4. Subscribe to these **Events** under Event Subscriptions:
|
|
162
|
+
- `app_mention`
|
|
163
|
+
- `message.im`
|
|
164
|
+
5. Install the app to your workspace and copy the **Bot User OAuth Token** (`xoxb-...`)
|
|
165
|
+
|
|
166
|
+
### Environment Variables
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
cp .env.example .env
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Edit `.env`:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
SLACK_BOT_TOKEN=xoxb-your-bot-token
|
|
176
|
+
SLACK_APP_TOKEN=xapp-your-app-level-token
|
|
177
|
+
CLAUDE_WORK_DIR=/path/to/your/project
|
|
178
|
+
CLAUDE_PERMISSION_MODE=acceptEdits
|
|
179
|
+
SESSION_STORE_PATH=./sessions.json
|
|
180
|
+
SESSION_TTL_HOURS=72
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
| Variable | Required | Default | Description |
|
|
184
|
+
|----------|----------|---------|-------------|
|
|
185
|
+
| `SLACK_BOT_TOKEN` | Yes | — | Slack Bot User OAuth Token |
|
|
186
|
+
| `SLACK_APP_TOKEN` | Yes | — | Slack App-Level Token (Socket Mode) |
|
|
187
|
+
| `CLAUDE_WORK_DIR` | No | `.` | Working directory for Claude Code |
|
|
188
|
+
| `CLAUDE_PERMISSION_MODE` | No | `acceptEdits` | Claude permission mode |
|
|
189
|
+
| `SESSION_STORE_PATH` | No | `./sessions.json` | Path to session mapping file |
|
|
190
|
+
| `SESSION_TTL_HOURS` | No | `72` | Session TTL in hours |
|
|
191
|
+
|
|
192
|
+
### Run (Local)
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
uv run agent-bridge
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Run (Docker)
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
cp .env.example .env
|
|
202
|
+
# Edit .env with your tokens (including ANTHROPIC_API_KEY for Docker)
|
|
203
|
+
|
|
204
|
+
docker compose up --build
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Test
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
uv run pytest tests/ -v
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Usage
|
|
214
|
+
|
|
215
|
+
- **Channel**: Mention the bot — `@AgentBridge help me refactor this function`
|
|
216
|
+
- **DM**: Send a direct message — the bot responds in the same conversation
|
|
217
|
+
- **Thread continuity**: Reply in the same Slack thread to continue the agent session
|
|
218
|
+
|
|
219
|
+
## Extending
|
|
220
|
+
|
|
221
|
+
### Adding a new agent
|
|
222
|
+
|
|
223
|
+
Create `agents/<name>/` with `config.py`, `controller.py`, `events.py`. Implement the `AgentController` protocol — your `run()` method yields `BridgeEvent`s. Wire it up in `__init__.py`.
|
|
224
|
+
|
|
225
|
+
### Adding a new platform
|
|
226
|
+
|
|
227
|
+
Create `platforms/<name>/` with `config.py`, `adapter.py`. Define your own session key logic (e.g., `discord:{guild}:{channel}`), manage per-session locking, consume `BridgeEvent`s from `bridge.handle_message()`. Wire it up in `__init__.py`.
|
|
228
|
+
|
|
229
|
+
Neither change requires modifying the bridge, the other agent, or the other platform.
|
|
230
|
+
|
|
231
|
+
## Design Decisions
|
|
232
|
+
|
|
233
|
+
### One-shot per message (vs. long-running process)
|
|
234
|
+
|
|
235
|
+
Each user message spawns a new `claude -p` process that exits after completion. Session continuity is handled by Claude Code's built-in `--resume` flag.
|
|
236
|
+
|
|
237
|
+
**Why**: Simpler process lifecycle, no idle resource consumption, graceful handling of crashes.
|
|
238
|
+
|
|
239
|
+
### Per-session locking (platform-owned)
|
|
240
|
+
|
|
241
|
+
An `asyncio.Lock` per session key prevents concurrent agent processes for the same session. This is managed by the platform adapter, not the bridge, because locking strategy may vary by platform.
|
|
242
|
+
|
|
243
|
+
### Throttled Slack updates
|
|
244
|
+
|
|
245
|
+
Slack message updates are throttled to 1.5-second intervals during streaming.
|
|
246
|
+
|
|
247
|
+
**Why**: Slack's API rate limits are ~1 request/second per method.
|