Loading...
Loading...
Claude Code SDK integration for building AI-powered applications with OAuth and API Key authentication
npx skill4agent add miles990/claude-software-skills claude-code-sdkclaude-code-sdkclaude loginclaude login~/.claude/ANTHROPIC_API_KEYquery()READWRITEEDITBASHGLOBGREPWEB_FETCHdef check_auth_status() -> dict:
status = {
'api_key_set': bool(os.environ.get('ANTHROPIC_API_KEY')),
'oauth_logged_in': False,
}
# 檢查 OAuth 登入狀態
claude_dir = os.path.expanduser('~/.claude')
if os.path.exists(claude_dir):
auth_files = ['credentials.json', 'settings.json', '.credentials.json']
for auth_file in auth_files:
if os.path.exists(os.path.join(claude_dir, auth_file)):
status['oauth_logged_in'] = True
break
# 只需要其中一種認證
status['auth_available'] = status['api_key_set'] or status['oauth_logged_in']
return statustry:
from claude_code_sdk._internal.transport import subprocess_cli
subprocess_cli._MAX_BUFFER_SIZE = 50 * 1024 * 1024 # 50MB
except Exception as e:
print(f"Failed to patch SDK buffer size: {e}")_claude_service: Optional['ClaudeCodeService'] = None
def get_claude_code() -> 'ClaudeCodeService':
global _claude_service
if _claude_service is None:
_claude_service = ClaudeCodeService()
return _claude_service@dataclass
class AgentConfig:
allowed_tools: List[str]
max_turns: int = 30
cwd: Optional[str] = None
@classmethod
def coding(cls) -> 'AgentConfig':
"""開發模式:完整權限"""
return cls(
allowed_tools=['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep'],
max_turns=50
)
@classmethod
def research(cls) -> 'AgentConfig':
"""研究模式:可上網"""
return cls(
allowed_tools=['Read', 'Glob', 'Grep', 'WebFetch'],
max_turns=20
)
@classmethod
def readonly(cls) -> 'AgentConfig':
"""唯讀模式:最安全"""
return cls(
allowed_tools=['Read', 'Glob', 'Grep'],
max_turns=10
)# ❌ 錯誤:嘗試在前端使用
# JavaScript 無法直接使用 Python SDK
# ✅ 正確:建立 FastAPI 後端
from fastapi import FastAPI
from claude_code_sdk import query
app = FastAPI()
@app.post("/api/generate")
async def generate(prompt: str):
result = []
async for msg in query(prompt=prompt):
result.append(msg)
return {"result": result}# ❌ 錯誤
def run_sync():
for msg in query(prompt="Hello"): # TypeError
print(msg)
# ✅ 正確
async def run_async():
async for msg in query(prompt="Hello"):
print(msg)anthropicclaude-code-sdk| 特性 | Claude API | Claude Code SDK |
|---|---|---|
| 認證 | API Key only | OAuth + API Key |
| 功能 | 純對話 | Agent (檔案/終端操作) |
| 計費 | API 用量計費 | Max/Pro 訂閱可用 |
| 套件 | | |
from dataclasses import dataclass, field
from typing import AsyncIterator, Optional, List, Dict, Any
from claude_code_sdk import query, ClaudeCodeOptions, AssistantMessage, TextBlock
@dataclass
class ClaudeCodeService:
"""Claude Code 服務封裝"""
async def run(self, prompt: str, config: Optional[AgentConfig] = None) -> Dict[str, Any]:
"""執行並收集完整結果"""
options = ClaudeCodeOptions(
allowed_tools=config.allowed_tools if config else None,
max_turns=config.max_turns if config else 30,
cwd=config.cwd
)
result_texts = []
tool_calls = []
async for message in query(prompt=prompt, options=options):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
result_texts.append(block.text)
return {
'status': 'success',
'result': '\n'.join(result_texts),
'tool_calls': tool_calls
}
async def stream(self, prompt: str, config: Optional[AgentConfig] = None) -> AsyncIterator[dict]:
"""串流執行,即時回傳"""
options = ClaudeCodeOptions(
allowed_tools=config.allowed_tools if config else None,
max_turns=config.max_turns if config else 30
)
async for message in query(prompt=prompt, options=options):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
yield {'type': 'text', 'content': block.text}from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import json
app = FastAPI()
@app.post("/api/agent/stream")
async def agent_stream(prompt: str):
async def event_generator():
service = ClaudeCodeService()
async for msg in service.stream(prompt):
yield f"data: {json.dumps(msg)}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(
event_generator(),
media_type="text/event-stream"
)# ❌ 不要在每次請求都建立新服務
@app.post("/api/generate")
async def bad_generate(prompt: str):
service = ClaudeCodeService() # 每次都新建
return await service.run(prompt)
# ❌ 不要忽略錯誤處理
async def bad_run(prompt: str):
async for msg in query(prompt=prompt): # 可能拋出異常
print(msg)
# ❌ 不要給予過多權限
config = AgentConfig(
allowed_tools=['Read', 'Write', 'Edit', 'Bash', 'WebFetch'], # 全開
max_turns=100 # 太多
)| Tool | Purpose | Link |
|---|---|---|
| claude-code-sdk | 官方 Python SDK | |
| Claude Code CLI | 命令列工具 | |
| FastAPI | 後端框架 | fastapi.tiangolo.com |
anthropicanthropicanthropic-dangerous-direct-browser-access# backend/main.py
import os
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
import json
from claude_code_sdk import query, ClaudeCodeOptions, AssistantMessage, TextBlock
app = FastAPI()
# CORS 設定
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
class GenerateRequest(BaseModel):
prompt: str
max_turns: int = 30
def check_auth():
"""檢查認證狀態"""
api_key = bool(os.environ.get('ANTHROPIC_API_KEY'))
oauth = os.path.exists(os.path.expanduser('~/.claude/credentials.json'))
return api_key or oauth
@app.get("/api/health")
async def health():
return {
"status": "ok",
"auth_available": check_auth()
}
@app.post("/api/generate")
async def generate(request: GenerateRequest):
if not check_auth():
raise HTTPException(401, "請先執行 claude login 或設置 ANTHROPIC_API_KEY")
options = ClaudeCodeOptions(max_turns=request.max_turns)
results = []
async for message in query(prompt=request.prompt, options=options):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
results.append(block.text)
return {"result": "\n".join(results)}
@app.post("/api/stream")
async def stream(request: GenerateRequest):
if not check_auth():
raise HTTPException(401, "未認證")
async def event_generator():
options = ClaudeCodeOptions(max_turns=request.max_turns)
async for message in query(prompt=request.prompt, options=options):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
yield f"data: {json.dumps({'text': block.text})}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(event_generator(), media_type="text/event-stream")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)fastapi>=0.100.0
uvicorn>=0.23.0
claude-code-sdk>=0.1.0
python-dotenv>=1.0.0// 一般請求
async function generate(prompt) {
const response = await fetch('http://localhost:8000/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt })
});
const data = await response.json();
return data.result;
}
// 串流請求
async function streamGenerate(prompt, onChunk) {
const response = await fetch('http://localhost:8000/api/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt })
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ') && line !== 'data: [DONE]') {
const data = JSON.parse(line.slice(6));
onChunk(data.text);
}
}
}
}# Dockerfile
FROM python:3.11-slim
# 安裝 Node.js (Claude Code CLI 需要)
RUN apt-get update && apt-get install -y curl && \
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y nodejs && \
rm -rf /var/lib/apt/lists/*
# 安裝 Claude Code CLI
RUN npm install -g @anthropic-ai/claude-code
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]# docker-compose.yml
version: '3.8'
services:
backend:
build: .
ports:
- "8000:8000"
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
volumes:
- ~/.claude:/root/.claude:ro # 掛載 OAuth 憑證