Skip to content

Commit 15c00db

Browse files
authored
Merge pull request #2 from BukeLy/feature/support-agent-skill
feat: 实现 Skills 支持
2 parents 5f8cc06 + 247c18c commit 15c00db

17 files changed

Lines changed: 506 additions & 70 deletions

File tree

.github/workflows/docker-validate.yml

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,39 @@ name: Docker Build Validation
33
on:
44
push:
55
paths:
6-
- 'agent-sdk-server/Dockerfile'
6+
- 'agent-sdk-server/**'
77
pull_request:
88
paths:
9-
- 'agent-sdk-server/Dockerfile'
9+
- 'agent-sdk-server/**'
1010

1111
jobs:
12-
validate-docker:
12+
lint:
1313
runs-on: ubuntu-latest
1414
steps:
1515
- uses: actions/checkout@v4
1616

17-
- name: Build and validate file permissions
18-
run: |
19-
cd agent-sdk-server
20-
docker build -t agent-sdk-server:test .
17+
- name: Lint Dockerfile
18+
uses: hadolint/hadolint-action@v3.1.0
19+
with:
20+
dockerfile: agent-sdk-server/Dockerfile
2121

22-
# Simulate Lambda init - copy from /opt to /tmp
23-
docker run --rm agent-sdk-server:test python -c "
24-
import shutil
25-
from pathlib import Path
22+
build:
23+
runs-on: ubuntu-latest
24+
needs: lint
25+
steps:
26+
- uses: actions/checkout@v4
2627

27-
src = Path('/opt/claude-config')
28-
dst = Path('/tmp/.claude-code')
29-
dst.mkdir(exist_ok=True)
28+
- name: Set up QEMU
29+
uses: docker/setup-qemu-action@v3
3030

31-
for item in src.iterdir():
32-
target = dst / item.name
33-
if item.is_dir():
34-
shutil.copytree(item, target, dirs_exist_ok=True)
35-
else:
36-
shutil.copy2(item, target)
31+
- name: Set up Docker Buildx
32+
uses: docker/setup-buildx-action@v3
3733

38-
print('Lambda init simulation: OK')
39-
"
34+
- name: Build ARM64 image
35+
uses: docker/build-push-action@v6
36+
with:
37+
context: ./agent-sdk-server
38+
platforms: linux/arm64
39+
push: false
40+
cache-from: type=gha
41+
cache-to: type=gha,mode=max

CLAUDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
- Subagent的"tools"字段定义不支持通配符,而是需要具体的Tools名称才可以.
2+
- template.yaml中引用Parameter时必须使用`!Ref`而非字面字符串`'${ParamName}'`.
3+
- agents/*.md文件必须包含YAML frontmatter并定义`name`字段,否则SDK会跳过加载.
4+
- Lambda容器中uvx不可用,需要在Dockerfile中创建符号链接或使用uv安装脚本.

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Telegram User → Bot API → API Gateway → sdk-client Lambda
1919
- **Session 持久化**:DynamoDB 存储映射,S3 存储对话历史,支持跨请求恢复
2020
- **多租户隔离**:基于 Telegram chat_id + thread_id 实现客户端隔离
2121
- **SubAgent 支持**:可配置多个专业 Agent(如 AWS 支持)
22-
- **Skills 支持**:可复用的技能模块(计划中)
22+
- **Skills 支持**:可复用的技能模块
2323
- **MCP 集成**:支持 HTTP 和本地命令类型的 MCP 服务器
2424
- **自动清理**:25天 TTL + S3 生命周期管理
2525

@@ -33,7 +33,8 @@ Telegram User → Bot API → API Gateway → sdk-client Lambda
3333
│ └── claude-config/ # 配置文件
3434
│ ├── agents.json # SubAgent定义
3535
│ ├── mcp.json # MCP服务器配置
36-
│ ├── skills/ # Skills定义(计划中)
36+
│ ├── skills/ # Skills定义
37+
│ │ └── hello-world/ # 示例 Skill
3738
│ └── system_prompt.md # 系统提示
3839
3940
├── agent-sdk-client/ # Telegram客户端 (ZIP部署)
@@ -121,7 +122,6 @@ sam deploy --guided
121122

122123
## TODO
123124

124-
- [ ] 实现 Skills 支持(参考 `docs/anthropic-agent-sdk-official/skills-in-sdk.md`
125125
- [ ] 多租户 TenantID 隔离
126126

127127
## License

agent-sdk-client/handler.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import httpx
1010
from telegram import Bot, Update
1111
from telegram.constants import ParseMode, ChatAction
12+
from telegram.helpers import escape_markdown
13+
from telegram.error import BadRequest
1214

1315
from config import Config
1416

@@ -84,10 +86,25 @@ async def process_webhook(body: dict) -> None:
8486
if len(text) > 4000:
8587
text = text[:4000] + "\n\n... (truncated)"
8688

87-
await bot.send_message(
88-
chat_id=message.chat_id,
89-
text=text,
90-
parse_mode=ParseMode.MARKDOWN,
91-
message_thread_id=message.message_thread_id,
92-
reply_to_message_id=message.message_id,
93-
)
89+
# Try MARKDOWN_V2 first, fallback with escape on parse errors
90+
try:
91+
await bot.send_message(
92+
chat_id=message.chat_id,
93+
text=text,
94+
parse_mode=ParseMode.MARKDOWN_V2,
95+
message_thread_id=message.message_thread_id,
96+
reply_to_message_id=message.message_id,
97+
)
98+
except BadRequest as e:
99+
if "parse entities" in str(e).lower():
100+
print(f"[MARKDOWN_V2] Parse error: {e}, retrying with escaped text")
101+
safe_text = escape_markdown(text, version=2)
102+
await bot.send_message(
103+
chat_id=message.chat_id,
104+
text=safe_text,
105+
parse_mode=ParseMode.MARKDOWN_V2,
106+
message_thread_id=message.message_thread_id,
107+
reply_to_message_id=message.message_id,
108+
)
109+
else:
110+
raise

agent-sdk-client/pyproject.toml

Lines changed: 0 additions & 13 deletions
This file was deleted.

agent-sdk-server/Dockerfile

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,34 @@
11
FROM public.ecr.aws/lambda/python:3.12-arm64
22

3-
# Install uv
3+
# Set working directory (Lambda default)
4+
WORKDIR ${LAMBDA_TASK_ROOT}
5+
6+
# Install uv and create uvx symlink
47
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
8+
RUN ln -s /usr/local/bin/uv /usr/local/bin/uvx
59

610
# Install Claude Code CLI (npm package, requires nodejs)
7-
RUN dnf install -y nodejs npm && npm install -g @anthropic-ai/claude-code
11+
# hadolint ignore=DL3016,DL3041
12+
RUN dnf install -y nodejs npm && \
13+
dnf clean all && \
14+
npm install -g @anthropic-ai/claude-code
815

916
# Install Python dependencies
1017
RUN uv pip install --system boto3 claude-agent-sdk
1118

1219
# Copy Lambda code and ensure readable
13-
COPY *.py ./
14-
RUN chmod 644 *.py
20+
COPY ./*.py ./
21+
RUN chmod 644 ./*.py
1522

1623
# Copy config files (MCP servers, SubAgents) to /opt (read-only at runtime)
1724
# setup_lambda_environment() will copy to /tmp/.claude-code at runtime
1825
COPY claude-config/ /opt/claude-config/
1926
RUN chmod -R 755 /opt/claude-config/
2027

28+
# Copy skills to /opt/claude-skills (will be copied to /tmp/.claude-code/skills at runtime)
29+
COPY claude-config/skills/ /opt/claude-skills/
30+
RUN chmod -R 755 /opt/claude-skills/
31+
2132
# Create ~/.claude and ~/.aws directories and ensure writable
2233
RUN mkdir -p /root/.claude/projects /root/.claude/debug /root/.claude/todos /root/.aws && \
2334
touch /root/.claude.json && \

agent-sdk-server/agent_session.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
# Config source (in Docker image) and destination (Lambda writable)
2121
CONFIG_SRC = Path('/opt/claude-config')
2222
CONFIG_DST = Path('/tmp/.claude-code')
23+
SKILLS_SRC = Path('/opt/claude-skills')
24+
SKILLS_DST = CONFIG_DST / 'skills'
2325

2426

2527
def setup_lambda_environment():
@@ -58,6 +60,17 @@ def setup_lambda_environment():
5860
shutil.copy2(item, dst)
5961
print(f"Config copied from {CONFIG_SRC} to {CONFIG_DST}")
6062

63+
# Copy skills to CLAUDE_CONFIG_DIR/skills/ for SDK to discover
64+
if SKILLS_SRC.exists():
65+
SKILLS_DST.mkdir(parents=True, exist_ok=True)
66+
for item in SKILLS_SRC.iterdir():
67+
dst = SKILLS_DST / item.name
68+
if item.is_dir():
69+
shutil.copytree(item, dst, dirs_exist_ok=True)
70+
else:
71+
shutil.copy2(item, dst)
72+
print(f"Skills copied from {SKILLS_SRC} to {SKILLS_DST}")
73+
6174
print(f"Bedrock profile created at {credentials_file}")
6275

6376

@@ -154,11 +167,12 @@ async def process_message(
154167
permission_mode='bypassPermissions', # Lambda has no interactive terminal
155168
max_turns=max_turns,
156169
system_prompt=system_prompt,
170+
setting_sources=['user'], # Load skills from CLAUDE_CONFIG_DIR/skills/
157171
allowed_tools=[
158172
#'Bash', 'Read', 'Write', 'Edit',
159173
#'Glob', 'Grep', 'WebFetch',
160-
'Task',
161-
'Skill' # Required for SubAgent invocation
174+
'Task', # For SubAgents
175+
'Skill', # For Skills
162176
],
163177
mcp_servers=mcp_servers if mcp_servers else None,
164178
agents=agents if agents else None,

agent-sdk-server/claude-config/agents/aws-support.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
---
2+
name: aws-support
3+
description: AWS customer technical support agent that searches AWS documentation
4+
model: haiku
5+
tools:
6+
- mcp__aws-knowledge-mcp-server__aws___search_documentation
7+
---
8+
19
# AWS Customer Technical Support Agent
210

311
You are a document retrieval assistant. You can ONLY answer using MCP tool results.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
description: Hello World 示例 Skill,执行脚本输出消息
3+
---
4+
5+
执行以下操作:
6+
7+
1. 使用 Bash 工具运行脚本:`python3 scripts/print_message.py`
8+
2. 将脚本的输出结果直接返回给用户
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"message": "Hello World"
3+
}

0 commit comments

Comments
 (0)