目标:在 Go 程序中程序化调用 OpenCode,构建自动化工具
SDK 架构
安装
go get github.com/anomalyco/opencode/sdk基础用法
创建客户端
package main
import (
"context"
"fmt"
"log"
"github.com/anomalyco/opencode/sdk"
)
func main() {
// 创建客户端
client, err := sdk.New(sdk.Config{
APIKey: "your-api-key",
BaseURL: "https://api.opencode.ai", // 可选
Model: "anthropic/claude-sonnet-4-5",
Timeout: 60 * time.Second,
})
if err != nil {
log.Fatal(err)
}
defer client.Close()
// 发送消息
resp, err := client.Send(context.Background(), sdk.Message{
Role: sdk.RoleUser,
Content: "请解释什么是依赖注入",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(resp.Content)
}流式响应
func streamExample() {
client, _ := sdk.New(sdk.Config{APIKey: "your-key"})
stream, err := client.SendStream(context.Background(), sdk.Message{
Content: "写一个快速排序算法",
})
if err != nil {
log.Fatal(err)
}
// 实时接收片段
for chunk := range stream {
fmt.Print(chunk.Content)
}
}工具调用
注册自定义工具
// 注册工具
client.RegisterTool(sdk.Tool{
Name: "search_docs",
Description: "搜索内部文档",
Parameters: map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"query": map[string]interface{}{
"type": "string",
"description": "搜索关键词",
},
},
"required": []string{"query"},
},
Handler: func(ctx context.Context, args map[string]interface{}) (string, error) {
query := args["query"].(string)
results := searchInternalDocs(query)
return fmt.Sprintf("搜索结果: %v", results), nil
},
})
// 使用工具
resp, err := client.Send(context.Background(), sdk.Message{
Content: "搜索关于认证的最新文档",
})工具调用流程
并发使用
func concurrentRequests() {
client, _ := sdk.New(sdk.Config{APIKey: "your-key"})
questions := []string{
"解释 REST API",
"什么是 GraphQL",
"比较 gRPC 和 REST",
}
var wg sync.WaitGroup
results := make([]string, len(questions))
for i, q := range questions {
wg.Add(1)
go func(index int, question string) {
defer wg.Done()
resp, err := client.Send(context.Background(), sdk.Message{
Content: question,
})
if err != nil {
results[index] = fmt.Sprintf("错误: %v", err)
return
}
results[index] = resp.Content
}(i, q)
}
wg.Wait()
for i, r := range results {
fmt.Printf("Q%d: %s\nA: %s\n\n", i+1, questions[i], r)
}
}完整示例:代码审查工具
package main
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/anomalyco/opencode/sdk"
)
// CodeReviewer 自动代码审查工具
type CodeReviewer struct {
client *sdk.Client
}
func NewCodeReviewer(apiKey string) (*CodeReviewer, error) {
client, err := sdk.New(sdk.Config{
APIKey: apiKey,
Model: "anthropic/claude-sonnet-4-5",
})
if err != nil {
return nil, err
}
return &CodeReviewer{client: client}, nil
}
func (r *CodeReviewer) ReviewFile(ctx context.Context, filePath string) (string, error) {
// 读取文件
content, err := os.ReadFile(filePath)
if err != nil {
return "", err
}
// 构建审查提示
prompt := fmt.Sprintf(`请审查以下代码,关注:
1. 潜在的 bug 和边界情况
2. 性能问题
3. 安全漏洞
4. 代码可读性和规范
文件: %s
代码:
%s
请用中文回复,格式:
- 🔴 严重: ...
- 🟡 警告: ...
- 🟢 建议: ...`, filePath, string(content))
resp, err := r.client.Send(ctx, sdk.Message{Content: prompt})
if err != nil {
return "", err
}
return resp.Content, nil
}
func (r *CodeReviewer) ReviewDirectory(ctx context.Context, dir string, extensions []string) error {
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
// 检查扩展名
ext := filepath.Ext(path)
for _, e := range extensions {
if ext == e {
fmt.Printf("正在审查: %s\n", path)
review, err := r.ReviewFile(ctx, path)
if err != nil {
fmt.Printf(" 错误: %v\n", err)
continue
}
fmt.Println(review)
fmt.Println(strings.Repeat("-", 50))
}
}
return nil
})
}
func main() {
reviewer, err := NewCodeReviewer(os.Getenv("OPENCODE_API_KEY"))
if err != nil {
panic(err)
}
ctx := context.Background()
// 审查单个文件
result, _ := reviewer.ReviewFile(ctx, "src/auth.ts")
fmt.Println(result)
// 审查整个目录
reviewer.ReviewDirectory(ctx, "./src", []string{".ts", ".tsx"})
}错误处理
func robustExample() {
client, err := sdk.New(sdk.Config{
APIKey: "your-key",
Timeout: 30 * time.Second,
Retries: 3, // 自动重试
})
if err != nil {
log.Fatal("创建客户端失败:", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
resp, err := client.Send(ctx, sdk.Message{
Content: "分析这段代码",
})
if err != nil {
switch {
case errors.Is(err, sdk.ErrRateLimited):
log.Println("请求过于频繁,请稍后再试")
case errors.Is(err, sdk.ErrInvalidAPIKey):
log.Fatal("API 密钥无效")
case errors.Is(err, context.DeadlineExceeded):
log.Println("请求超时")
default:
log.Printf("未知错误: %v\n", err)
}
return
}
fmt.Println(resp.Content)
}使用场景
| 场景 | 实现思路 |
|---|---|
| CI/CD 代码审查 | GitHub Action 调用 SDK,PR 自动审查 |
| IDE 插件 | 编辑器插件调用 SDK 提供 AI 补全 |
| 自动化文档 | 扫描代码自动生成 API 文档 |
| 智能测试 | 根据代码生成测试用例 |
| 代码迁移 | 批量将代码从一种语言转为另一种 |
性能优化
// 连接池
client, _ := sdk.New(sdk.Config{
APIKey: "your-key",
MaxConns: 10,
IdleTimeout: 30 * time.Second,
})
// 批量请求(节省 Token)
batchResp, _ := client.SendBatch(context.Background(), []sdk.Message{
{Content: "问题1"},
{Content: "问题2"},
{Content: "问题3"},
})下一篇:19. 服务器部署