diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml
index 01e01d3..c727f9d 100644
--- a/.github/workflows/go-ci.yml
+++ b/.github/workflows/go-ci.yml
@@ -27,29 +27,18 @@ jobs:
lint:
name: Lint
runs-on: ubuntu-latest
- timeout-minutes: 5
- env:
- GOMAXPROCS: 2
- GOMEMLIMIT: 256MiB
steps:
- - name: Checkout code
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
+ - uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
- go-version: "1.24"
- cache: true
+ go-version: "1.25"
- name: golangci-lint
- timeout-minutes: 4
- uses: golangci/golangci-lint-action@v7
+ uses: golangci/golangci-lint-action@v9
with:
- version: v2.1.6
- args: --timeout=2m --disable=errcheck --enable=govet,ineffassign,staticcheck,unused --new-from-rev=origin/main ./pkg/...
- skip-cache: false
+ version: v2.7.2
build:
name: Build
diff --git a/.golangci.yml b/.golangci.yml
index 53621b2..4703f39 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,33 +1,108 @@
-# golangci-lint configuration (v2.x)
version: "2"
-run:
- timeout: 5m
- tests: true
- go: "1.21"
-
linters:
- enable:
- - errcheck
- - govet
- - ineffassign
- - staticcheck
- - unused
+ default: all
+ disable:
+ # 过于严格/不适用
+ - depguard # 需要配置依赖白名单
+ - wrapcheck # 要求所有错误都 wrap
+ - exhaustruct # 要求所有字段都初始化
+ - ireturn # 禁止返回接口
+ - varnamelen # 变量名长度限制
+ - nlreturn # return 前必须空行
+ - wsl_v5 # 空行规则过严
+ - funlen # 函数长度限制
+ - gocognit # 认知复杂度
+ - cyclop # 圈复杂度
+ - lll # 行长度限制
+ - godox # 禁止 TODO/FIXME
+ - godot # 注释句号结尾
+ - mnd # magic number
+ - err113 # 错误定义规则过严
+ - gochecknoglobals # 禁止全局变量
+ - gochecknoinits # 禁止 init 函数
+ - nonamedreturns # 禁止命名返回
+ - interfacebloat # 接口方法数限制
+ - maintidx # 可维护性指数
+ - nestif # 嵌套 if 检测
+ - goconst # 常量提取
+ - dupl # 重复代码
+ - inamedparam # 接口参数命名
+ - tagliatelle # struct tag 命名风格
+ - tagalign # struct tag 对齐
+ - musttag # 强制 struct tag
+ - goheader # 文件头检查
+ - gomoddirectives # go.mod 指令检查
+ - gomodguard # go.mod 依赖检查
+ - grouper # 表达式分组
+ - decorder # 声明顺序
+ - funcorder # 函数顺序
+ - containedctx # context 包含检查
+ - contextcheck # context 检查
+ - noinlineerr # 禁止内联错误处理
+ - paralleltest # 要求 t.Parallel()
+ - testpackage # 要求 _test 包
+ - tparallel # t.Parallel 检查
+ # 特定框架
+ - arangolint # ArangoDB
+ - ginkgolinter # Ginkgo
+ - zerologlint # zerolog
+ - loggercheck # logger 检查
+ - spancheck # OpenTelemetry
+ - promlinter # Prometheus
+ - protogetter # protobuf
+ - gosmopolitan # i18n
+ # 已弃用
+ - wsl
+ # 注释要求过严
+ - revive # 与 staticcheck 重复,规则过严
+ - errname # 错误命名规则
+ - goprintffuncname # printf 函数命名
+ # 过于严格的额外检查
+ - forbidigo # 禁止 fmt.Print* 等函数(示例代码大量使用)
+ - forcetypeassert # 强制类型断言检查(大量使用 any 类型)
+ - gosec # 安全检查过严
+ - unparam # 未使用参数检查
+ - godoclint # 注释格式过严
+ - prealloc # 切片预分配建议
+ - nilnil # nil 返回检查
+ - noctx # http 请求 context 检查
+ - nilerr # nil error 检查
+ - exhaustive # switch 穷尽检查
+ - gocyclo # 圈复杂度(已有 cyclop)
+ - errchkjson # JSON 错误检查(示例代码忽略错误)
+ - errcheck # 错误检查(CI 配置禁用)
+ # 测试相关的过严检查
+ - testifylint # testify 用法检查
+ - thelper # t.Helper() 检查
+ - usetesting # testing 包使用建议
+ # 其他过严检查
+ - predeclared # 预定义标识符遮蔽
+ - intrange # 整数范围迭代建议
+ - modernize # 现代化建议
+ - gocritic # 代码风格建议
+ - rowserrcheck # sql.Rows 错误检查
+ - wastedassign # 无用赋值检查
+ - iface # 接口检查
+
settings:
- errcheck:
- check-type-assertions: false
- check-blank: false
- exclude-functions:
- - fmt.Fprintf
- - fmt.Fprintln
- - fmt.Printf
- - fmt.Println
- - (io.Closer).Close
govet:
enable-all: true
disable:
- fieldalignment
- - shadow
+ - shadow # 变量遮蔽检查(Go 常见模式)
+ misspell:
+ locale: US
+ nakedret:
+ max-func-lines: 30
+ gocritic: {}
+ exhaustive:
+ default-signifies-exhaustive: true
+
+formatters:
+ enable:
+ - gofmt
+ - goimports
issues:
max-issues-per-linter: 0
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3197335..44fba0d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,13 +1,4 @@
repos:
- - repo: https://github.com/tekwizely/pre-commit-golang
- rev: v1.0.0-rc.1
- hooks:
- - id: go-fmt
- args: [-w]
- - id: go-vet-repo-mod
- - id: golangci-lint-repo-mod
- args: [--timeout=5m]
-
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
@@ -18,3 +9,13 @@ repos:
- id: check-added-large-files
args: ["--maxkb=1000"]
- id: check-merge-conflict
+
+ - repo: local
+ hooks:
+ - id: golangci-lint
+ name: golangci-lint
+ description: Run golangci-lint to lint and fix Go code
+ entry: golangci-lint run --new --fix
+ types: [go]
+ language: system
+ pass_filenames: false
diff --git a/docs/content/02.core-concepts/13.session-recovery.md b/docs/content/02.core-concepts/13.session-recovery.md
index 00f716e..31cdec7 100644
--- a/docs/content/02.core-concepts/13.session-recovery.md
+++ b/docs/content/02.core-concepts/13.session-recovery.md
@@ -142,16 +142,16 @@ func createMyRecoveryHook() types.RecoveryHook {
if !shouldTriggerRecovery(ctx.OriginalMessage) {
return &types.RecoveryResult{ShouldRecover: false}
}
-
+
// 2. 检查项目状态
hasProjectFile := checkProjectFile(ctx.WorkDir)
if !ctx.SessionState.HasHistory && !hasProjectFile {
return &types.RecoveryResult{ShouldRecover: false}
}
-
+
// 3. 构建恢复消息
enhancedMsg := buildRecoveryMessage(ctx)
-
+
return &types.RecoveryResult{
ShouldRecover: true,
EnhancedMessage: enhancedMsg,
@@ -172,7 +172,7 @@ func sendMessage(ctx context.Context, ag *agent.Agent, message string) error {
if recovered {
log.Printf("Session recovery triggered")
}
-
+
return ag.Send(ctx, finalMessage)
}
```
@@ -189,7 +189,7 @@ import (
"os"
"path/filepath"
"strings"
-
+
"github.com/astercloud/aster/pkg/types"
)
@@ -201,7 +201,7 @@ func createRecoveryHook() types.RecoveryHook {
if !isRecoveryTriggerMessage(ctx.OriginalMessage) {
return &types.RecoveryResult{ShouldRecover: false}
}
-
+
// 检查项目文件
hasProjectFile := false
if ctx.WorkDir != "" {
@@ -210,12 +210,12 @@ func createRecoveryHook() types.RecoveryHook {
hasProjectFile = true
}
}
-
+
// 需要恢复的条件:有历史消息 或 有项目文件
if !ctx.SessionState.HasHistory && !hasProjectFile {
return &types.RecoveryResult{ShouldRecover: false}
}
-
+
// 构建恢复指令
var sb strings.Builder
sb.WriteString("【会话恢复】这是一个恢复的会话,你需要先理解当前项目状态再响应用户。\n\n")
@@ -223,15 +223,15 @@ func createRecoveryHook() types.RecoveryHook {
sb.WriteString("1. 首先使用 Read 工具读取 AGENTS.md 文件,了解项目信息和当前步骤\n")
sb.WriteString("2. 如果存在 _briefs 目录,读取其中的文件了解已收集的需求\n")
sb.WriteString("3. 根据上下文理解用户的意图,然后继续之前的工作\n\n")
-
+
if ctx.WorkDir != "" {
sb.WriteString(fmt.Sprintf("工作目录: %s\n\n", ctx.WorkDir))
}
-
+
sb.WriteString("---\n\n")
sb.WriteString("用户消息: ")
sb.WriteString(ctx.OriginalMessage)
-
+
return &types.RecoveryResult{
ShouldRecover: true,
EnhancedMessage: sb.String(),
@@ -245,19 +245,19 @@ func isRecoveryTriggerMessage(message string) bool {
if len(message) > 20 {
return false // 长消息通常是新的指令
}
-
+
triggers := []string{
"继续", "好", "好的", "可以", "下一步", "然后呢", "接下来",
"continue", "ok", "okay", "yes", "next", "go on",
}
-
+
messageLower := strings.ToLower(message)
for _, trigger := range triggers {
if strings.Contains(messageLower, trigger) {
return true
}
}
-
+
return false
}
```
@@ -311,13 +311,13 @@ sequenceDiagram
App->>Agent: SendMessage("继续")
Agent->>Agent: GetSessionState()
Note over Agent: HasHistory=true
IsResumed=true
-
+
Agent->>Hook: RecoveryHook(ctx)
Hook->>Hook: 检查触发条件
Hook->>Hook: 检查项目文件
Hook->>Hook: 构建增强消息
Hook-->>Agent: RecoveryResult{ShouldRecover: true, EnhancedMessage: "..."}
-
+
Agent->>Agent: Send(enhancedMessage)
Agent->>Agent: 读取 AGENTS.md
Agent->>Agent: 理解项目状态
@@ -340,9 +340,9 @@ func (a *Agent) GetSessionState(ctx context.Context) *types.SessionState
```go
func (a *Agent) ProcessRecovery(
- ctx context.Context,
- message string,
- workDir string,
+ ctx context.Context,
+ message string,
+ workDir string,
metadata map[string]any,
) (enhancedMessage string, recovered bool)
```
diff --git a/docs/content/02.core-concepts/14.session-sqlite.md b/docs/content/02.core-concepts/14.session-sqlite.md
index 71495d7..61de630 100644
--- a/docs/content/02.core-concepts/14.session-sqlite.md
+++ b/docs/content/02.core-concepts/14.session-sqlite.md
@@ -27,12 +27,12 @@ graph TB
Agent --> SessionService[Session Service]
SessionService --> SQLite[SQLite Store]
SQLite --> DBFile[(sessions.db)]
-
+
subgraph "数据表"
DBFile --> Sessions[sessions 表]
DBFile --> Events[events 表]
end
-
+
style SQLite fill:#3b82f6
style DBFile fill:#10b981
```
@@ -263,7 +263,7 @@ func cleanupOldSessions(service *sqlite.Service, maxAge time.Duration) error {
AppName: "my-app",
UserID: "user-123",
})
-
+
cutoff := time.Now().Add(-maxAge)
for _, sess := range sessions {
if sess.UpdatedAt().Before(cutoff) {
diff --git a/docs/content/02.core-concepts/15.recipe.md b/docs/content/02.core-concepts/15.recipe.md
index 580c3a1..da39239 100644
--- a/docs/content/02.core-concepts/15.recipe.md
+++ b/docs/content/02.core-concepts/15.recipe.md
@@ -315,7 +315,7 @@ instructions: |
1. 分析代码质量和可读性
2. 发现潜在的 Bug 和安全问题
3. 提供改进建议和最佳实践
-
+
请遵循以下原则:
- 给出具体的代码示例
- 解释为什么某些做法更好
@@ -388,11 +388,11 @@ version: "1.0" # 使用语义化版本
instructions: |
# 角色定义
你是一个专业的...
-
+
# 行为准则
1. 规则一
2. 规则二
-
+
# 输出格式
请按以下格式输出...
```
diff --git a/docs/content/04.memory/12.logic-memory-plan.md b/docs/content/04.memory/12.logic-memory-plan.md
index 864e21c..10e0a30 100644
--- a/docs/content/04.memory/12.logic-memory-plan.md
+++ b/docs/content/04.memory/12.logic-memory-plan.md
@@ -1404,7 +1404,6 @@ func TestLogicMemoryE2E(t *testing.T) {
---
-**计划制定时间**:2025-12-04
-**预计完成时间**:2025-12-30(4 周)
+**计划制定时间**:2025-12-04
+**预计完成时间**:2025-12-30(4 周)
**置信度**:高(基于 Aster SDK 和 YunJin 的实际代码分析)
-
diff --git a/docs/content/04.memory/14.rules-system.md b/docs/content/04.memory/14.rules-system.md
index 836fdca..5ebe450 100644
--- a/docs/content/04.memory/14.rules-system.md
+++ b/docs/content/04.memory/14.rules-system.md
@@ -149,7 +149,7 @@ type DatabaseLoader struct {
func (l *DatabaseLoader) LoadGlobalRules(ctx context.Context) ([]*Rule, error) {
// 从数据库查询全局规则
- rows, err := l.db.QueryContext(ctx,
+ rows, err := l.db.QueryContext(ctx,
"SELECT id, title, content, priority FROM rules WHERE scope = 'global' AND enabled = true")
// ...
}
diff --git a/docs/content/04.memory/16.dialog-extractor.md b/docs/content/04.memory/16.dialog-extractor.md
index 07e287d..6565dc6 100644
--- a/docs/content/04.memory/16.dialog-extractor.md
+++ b/docs/content/04.memory/16.dialog-extractor.md
@@ -71,7 +71,7 @@ message := "我想要轻松一点的风格,字数控制在2000字以内,不
preferences := extractor.Extract(message)
for _, pref := range preferences {
- fmt.Printf("[%s] %s (置信度: %.2f)\n",
+ fmt.Printf("[%s] %s (置信度: %.2f)\n",
pref.Category, pref.Content, pref.Confidence)
}
diff --git a/docs/content/08.security/1.guardrails.md b/docs/content/08.security/1.guardrails.md
index ba951f8..3bef780 100644
--- a/docs/content/08.security/1.guardrails.md
+++ b/docs/content/08.security/1.guardrails.md
@@ -300,4 +300,3 @@ type GuardrailError struct {
- [Permission 权限系统](/security/permission) - 工具执行的权限控制和审批流程
- [Human-in-the-Loop](/core-concepts/middleware#hitl) - HITL 中间件详解
-
diff --git a/docs/content/08.security/2.permission.md b/docs/content/08.security/2.permission.md
index 96d2868..987229c 100644
--- a/docs/content/08.security/2.permission.md
+++ b/docs/content/08.security/2.permission.md
@@ -43,23 +43,23 @@ aster 新增了 `EnhancedInspector`,提供以下增强功能:
graph TB
Agent[Agent] --> Middleware[HITL Middleware]
Middleware --> Inspector[Permission Inspector]
-
+
Inspector --> Mode{审批模式}
Mode -->|auto_approve| Auto[自动批准]
Mode -->|smart_approve| Smart[智能审批]
Mode -->|always_ask| Ask[总是询问]
-
+
Smart --> Risk[风险评估]
Risk --> Rules[规则匹配]
Rules --> Decision{决策}
-
+
Decision -->|Allow| Execute[执行工具]
Decision -->|Deny| Reject[拒绝执行]
Decision -->|Ask| Control[Control Channel]
-
+
Control --> User[用户确认]
User --> Execute
-
+
style Inspector fill:#3b82f6
style Smart fill:#10b981
style Control fill:#f59e0b
@@ -308,7 +308,7 @@ go func() {
case *types.ControlPermissionRequiredEvent:
// 显示审批对话框
approved := showApprovalDialog(e.ToolName, e.Arguments, e.RiskLevel)
-
+
// 发送决定
agent.RespondToPermission(e.RequestID, approved, "用户决定")
}
diff --git a/docs/content/09.deployment/7.desktop/index.md b/docs/content/09.deployment/7.desktop/index.md
index 38dde7f..8415739 100644
--- a/docs/content/09.deployment/7.desktop/index.md
+++ b/docs/content/09.deployment/7.desktop/index.md
@@ -25,20 +25,20 @@ graph TB
UI[前端 UI
HTML/CSS/JS]
Bridge[Framework Bridge]
end
-
+
subgraph Aster[Aster Core]
App[Desktop App]
Agent[Agent]
Permission[Permission]
Session[SQLite Session]
end
-
+
UI <-->|事件/消息| Bridge
Bridge <-->|通信协议| App
App --> Agent
App --> Permission
App --> Session
-
+
style UI fill:#3b82f6
style App fill:#10b981
style Bridge fill:#f59e0b
@@ -63,12 +63,12 @@ import (
func main() {
ctx := context.Background()
-
+
// 创建 Permission Inspector
inspector, _ := permission.NewInspector(
permission.WithMode(permission.ModeSmartApprove),
)
-
+
// 创建桌面应用
app, err := desktop.NewApp(&desktop.Config{
Framework: desktop.FrameworkWails, // 或 FrameworkTauri, FrameworkElectron
@@ -78,7 +78,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
-
+
// 启动应用
if err := app.Start(ctx); err != nil {
log.Fatal(err)
@@ -105,10 +105,10 @@ func main() {
app, _ := desktop.NewApp(&desktop.Config{
Framework: desktop.FrameworkWails,
})
-
+
// 获取 Wails 绑定
bridge := app.Bridge().(*desktop.WailsBridge)
-
+
// Wails 应用配置
err := wails.Run(&options.App{
Title: "Aster Desktop",
@@ -152,10 +152,10 @@ func main() {
HTTPPort: 8765, // HTTP 端口
WSPort: 8766, // WebSocket 端口
})
-
+
// 启动服务
app.Start(context.Background())
-
+
// Tauri 前端通过 HTTP/WS 连接
}
```
@@ -197,7 +197,7 @@ func main() {
HTTPPort: 8765,
WSPort: 8766,
})
-
+
app.Start(context.Background())
}
```
@@ -213,7 +213,7 @@ let asterProcess;
app.whenReady().then(() => {
// 启动 Aster 后端
asterProcess = spawn('./aster-desktop');
-
+
// 创建窗口
const win = new BrowserWindow({
width: 1024,
@@ -222,7 +222,7 @@ app.whenReady().then(() => {
nodeIntegration: true
}
});
-
+
win.loadFile('index.html');
});
diff --git a/docs/content/10.observability/5.studio/remote-agents.md b/docs/content/10.observability/5.studio/remote-agents.md
index dabf0a1..7caf964 100644
--- a/docs/content/10.observability/5.studio/remote-agents.md
+++ b/docs/content/10.observability/5.studio/remote-agents.md
@@ -46,9 +46,9 @@ func NewStudioClient(studioURL, agentID string) (*StudioClient, error) {
if err != nil {
return nil, err
}
-
+
client := &StudioClient{conn: conn, agentID: agentID}
-
+
// 注册 Agent
client.Send(map[string]any{
"type": "register",
@@ -58,7 +58,7 @@ func NewStudioClient(studioURL, agentID string) (*StudioClient, error) {
"status": "ready",
},
})
-
+
return client, nil
}
diff --git a/docs/content/12.examples/5.workflows/plan-explore-ui.md b/docs/content/12.examples/5.workflows/plan-explore-ui.md
index 5685436..d8547bc 100644
--- a/docs/content/12.examples/5.workflows/plan-explore-ui.md
+++ b/docs/content/12.examples/5.workflows/plan-explore-ui.md
@@ -274,4 +274,3 @@ Explore(分析当前工具实现状态)
```
- 子代理进程内部可以复用本示例的 UI 逻辑,在自己的终端中展示更细粒度的 Plan/Explore 视图。
-
diff --git a/docs/content/roadmap/desktop-roadmap.md b/docs/content/roadmap/desktop-roadmap.md
index ed8e690..0ee10c4 100644
--- a/docs/content/roadmap/desktop-roadmap.md
+++ b/docs/content/roadmap/desktop-roadmap.md
@@ -272,7 +272,7 @@ func (m *BackendManager) Start(ctx context.Context) error {
"--data-dir", m.dataDir,
"--store", "sqlite",
)
-
+
if err := m.cmd.Start(); err != nil {
return err
}
@@ -336,7 +336,7 @@ import (
"bufio"
"fmt"
"os"
-
+
"github.com/astercloud/aster/pkg/agent"
)
@@ -344,23 +344,23 @@ func runSession(args []string) error {
// 解析参数
recipeFile := flagSet.String("recipe", "", "Recipe file to use")
workDir := flagSet.String("dir", ".", "Working directory")
-
+
// 加载配置
cfg := loadConfig()
-
+
// 创建 Agent
a, err := agent.New(agent.WithConfig(cfg))
if err != nil {
return err
}
-
+
// REPL 循环
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("aster> ")
-
+
for scanner.Scan() {
input := scanner.Text()
-
+
// 处理特殊命令
switch input {
case "/exit", "/quit":
@@ -372,7 +372,7 @@ func runSession(args []string) error {
printHelp()
continue
}
-
+
// 发送到 Agent
for event := range a.Run(ctx, input) {
switch e := event.(type) {
@@ -387,10 +387,10 @@ func runSession(args []string) error {
}
}
}
-
+
fmt.Print("\naster> ")
}
-
+
return nil
}
```
@@ -438,7 +438,7 @@ providers:
provider: anthropic
model: claude-sonnet-4-20250514
env_api_key: ANTHROPIC_API_KEY
-
+
fast:
provider: openai
model: gpt-4o-mini
@@ -455,7 +455,7 @@ extensions:
- name: filesystem
type: builtin
enabled: true
-
+
- name: git
type: stdio
cmd: npx
diff --git a/docs/public/images/architecture-layers.svg b/docs/public/images/architecture-layers.svg
index d6eb51e..d1e38f4 100644
--- a/docs/public/images/architecture-layers.svg
+++ b/docs/public/images/architecture-layers.svg
@@ -23,27 +23,27 @@
Client Layer
-
+
Client SDKs (多语言、多框架支持)
-
+
client-js
(核心)
-
+
React
(集成)
-
+
AI SDK
(Vercel)
-
+
Vue
(未来)
-
+
15 个资源模块 • 事件驱动 • 类型安全
@@ -53,27 +53,27 @@
✨ Server Layer (server/) - 生产级
-
+
Production Server (企业级特性)
-
+
Auth & RBAC
(API Key+JWT)
-
+
Observability
(Metrics+Tracing)
-
+
Rate Limiting
(Token/SW)
-
+
Handlers
(8 core)
-
+
认证授权 • Prometheus • OpenTelemetry • 健康检查
@@ -83,10 +83,10 @@
HTTP Layer (cmd/aster) - 开发工具
-
+
Development Server (快速启动)
-
+
Gin HTTP Server • REST API • WebSocket
可替换为任何框架 (Echo, Chi, 标准库等)
简化配置 • 无认证 • 快速迭代
@@ -98,23 +98,23 @@
Core Layer (pkg/)
-
+
aster 核心
-
+
Agent Types (灵活编排)
Basic Agent • Workflow • SubAgent
-
+
Middleware Stack (洋葱模型)
Summarization • Filesystem • SubAgent
-
+
Backend Abstraction (存储抽象)
State • Store • Filesystem • Composite
-
+
零 HTTP 框架依赖 • 纯业务逻辑 • 可被任何项目导入
diff --git a/docs/public/images/logo-banner.svg b/docs/public/images/logo-banner.svg
index 32395b7..52a69a7 100644
--- a/docs/public/images/logo-banner.svg
+++ b/docs/public/images/logo-banner.svg
@@ -5,12 +5,12 @@
-
+
-
+
@@ -19,67 +19,67 @@
-
+
-
+
-
-
+
-
+
-
+
-
+
-
+
- Aster
-
- 星尘云枢
-
+
- WHERE STARDUST CONVERGES, INTELLIGENCE EMERGES
diff --git a/docs/public/images/logo-icon.svg b/docs/public/images/logo-icon.svg
index fa7e393..1cc61d4 100644
--- a/docs/public/images/logo-icon.svg
+++ b/docs/public/images/logo-icon.svg
@@ -5,13 +5,13 @@
-
+
-
+
@@ -19,39 +19,39 @@
-
+
-
+
-
+
-
+
-
-
+
-
+
-
+
@@ -67,40 +67,40 @@
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/docs/public/images/logo.svg b/docs/public/images/logo.svg
index 7b10ca4..51a8b6b 100644
--- a/docs/public/images/logo.svg
+++ b/docs/public/images/logo.svg
@@ -6,13 +6,13 @@
-
+
-
+
@@ -22,28 +22,28 @@
-
+
-
+
-
-
+
-
-
-
+
@@ -62,7 +62,7 @@
-
+
@@ -76,7 +76,7 @@
-
+
@@ -86,50 +86,50 @@
-
+
-
-
-
-
-
+
- Aster
-
+
- 星尘云枢
-
+
-
+
- AI Agent Framework
diff --git a/examples/a2a/main.go b/examples/a2a/main.go
index 062ac25..7401c69 100644
--- a/examples/a2a/main.go
+++ b/examples/a2a/main.go
@@ -25,7 +25,7 @@ func (a *SimpleAgent) Receive(ctx *actor.Context, msg actor.Message) {
time.Sleep(100 * time.Millisecond)
// 生成响应
- response := fmt.Sprintf("你好!我收到了你的消息: %s", m.Text)
+ response := "你好!我收到了你的消息: " + m.Text
result := &pkgagent.ChatResultMsg{
Result: &types.CompleteResult{
diff --git a/examples/actor/actor_test.go b/examples/actor/actor_test.go
index a5e672f..1fe0bd6 100644
--- a/examples/actor/actor_test.go
+++ b/examples/actor/actor_test.go
@@ -8,7 +8,6 @@ import (
"time"
"github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/astercloud/aster/pkg/actor"
@@ -20,6 +19,7 @@ import (
type ActorSuite struct {
suite.Suite
+
system *actor.System
}
@@ -48,12 +48,12 @@ func (s *ActorSuite) TestBasicPingPong() {
// 发送 Ping 并等待 Pong
resp, err := pid.Request(&PingMsg{Count: 42}, time.Second)
- require.NoError(s.T(), err, "请求不应失败")
- require.NotNil(s.T(), resp, "响应不应为空")
+ s.Require().NoError(err, "请求不应失败")
+ s.Require().NotNil(resp, "响应不应为空")
pong, ok := resp.(*PongMsg)
- require.True(s.T(), ok, "响应应为 PongMsg")
- assert.Equal(s.T(), 42, pong.Count, "计数应匹配")
+ s.Require().True(ok, "响应应为 PongMsg")
+ s.Equal(42, pong.Count, "计数应匹配")
}
func (s *ActorSuite) TestCounterConcurrency() {
@@ -77,11 +77,11 @@ func (s *ActorSuite) TestCounterConcurrency() {
numGoroutines := 10
incrementsPerGoroutine := 100
- for i := 0; i < numGoroutines; i++ {
+ for range numGoroutines {
wg.Add(1)
go func() {
defer wg.Done()
- for j := 0; j < incrementsPerGoroutine; j++ {
+ for range incrementsPerGoroutine {
pid.Tell(&IncrementMsg{Value: 1})
}
}()
@@ -97,7 +97,7 @@ func (s *ActorSuite) TestCounterConcurrency() {
select {
case count := <-replyCh:
expected := numGoroutines * incrementsPerGoroutine
- assert.Equal(s.T(), expected, count, "并发计数应正确")
+ s.Equal(expected, count, "并发计数应正确")
case <-time.After(time.Second):
s.T().Fatal("获取计数超时")
}
@@ -121,23 +121,23 @@ func (s *ActorSuite) TestSupervisorRestart() {
// 第一次请求会触发 panic
_, err := pid.Request(&PingMsg{Count: 1}, 500*time.Millisecond)
- assert.Error(s.T(), err, "第一次请求应超时(Actor panic)")
+ s.Error(err, "第一次请求应超时(Actor panic)")
time.Sleep(100 * time.Millisecond)
// 第二次请求也会触发 panic
_, err = pid.Request(&PingMsg{Count: 2}, 500*time.Millisecond)
- assert.Error(s.T(), err, "第二次请求应超时(Actor panic)")
+ s.Error(err, "第二次请求应超时(Actor panic)")
time.Sleep(100 * time.Millisecond)
// 第三次请求应该成功(Actor 已恢复)
resp, err := pid.Request(&PingMsg{Count: 3}, time.Second)
- require.NoError(s.T(), err, "第三次请求应成功")
+ s.Require().NoError(err, "第三次请求应成功")
pong, ok := resp.(*PongMsg)
- require.True(s.T(), ok, "响应应为 PongMsg")
- assert.Equal(s.T(), 3, pong.Count, "计数应匹配")
+ s.Require().True(ok, "响应应为 PongMsg")
+ s.Equal(3, pong.Count, "计数应匹配")
}
func (s *ActorSuite) TestPipeline() {
@@ -163,7 +163,7 @@ func (s *ActorSuite) TestPipeline() {
select {
case result := <-resultCh:
expected := "Input -> Stage1 -> Stage2 -> Stage3"
- assert.Equal(s.T(), expected, result, "流水线结果应正确")
+ s.Equal(expected, result, "流水线结果应正确")
case <-time.After(5 * time.Second):
s.T().Fatal("流水线处理超时")
}
@@ -175,7 +175,7 @@ func (s *ActorSuite) TestBroadcast() {
subscribers := make([]*actor.PID, numSubscribers)
actorInstances := make([]*SubscriberActor, numSubscribers)
- for i := 0; i < numSubscribers; i++ {
+ for i := range numSubscribers {
name := fmt.Sprintf("sub-%d", i)
sub := &SubscriberActor{name: name}
actorInstances[i] = sub
@@ -199,7 +199,7 @@ func (s *ActorSuite) TestBroadcast() {
sub.mu.Lock()
count := len(sub.received)
sub.mu.Unlock()
- assert.Equal(s.T(), len(messages), count,
+ s.Equal(len(messages), count,
"订阅者 %d 应收到 %d 条消息", i, len(messages))
}
}
@@ -213,7 +213,7 @@ func (s *ActorSuite) TestActorStats() {
time.Sleep(50 * time.Millisecond)
stats := s.system.Stats()
- assert.Equal(s.T(), int64(3), stats.TotalActors, "应有 3 个 Actor")
+ s.Equal(int64(3), stats.TotalActors, "应有 3 个 Actor")
}
func (s *ActorSuite) TestActorStop() {
@@ -222,7 +222,7 @@ func (s *ActorSuite) TestActorStop() {
// Actor 应该存在
_, exists := s.system.GetActor("echo")
- assert.True(s.T(), exists, "Actor 应存在")
+ s.True(exists, "Actor 应存在")
// 停止 Actor
s.system.Stop(pid)
@@ -230,7 +230,7 @@ func (s *ActorSuite) TestActorStop() {
// Actor 应该不存在
_, exists = s.system.GetActor("echo")
- assert.False(s.T(), exists, "Actor 应已停止")
+ s.False(exists, "Actor 应已停止")
}
// =============================================================================
@@ -244,8 +244,7 @@ func BenchmarkActorTell(b *testing.B) {
counter := &CounterActor{name: "counter"}
pid := system.Spawn(counter, "counter")
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
pid.Tell(&IncrementMsg{Value: 1})
}
}
@@ -257,8 +256,7 @@ func BenchmarkActorRequest(b *testing.B) {
echo := &EchoActor{name: "echo"}
pid := system.Spawn(echo, "echo")
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for i := 0; b.Loop(); i++ {
_, _ = pid.Request(&PingMsg{Count: i}, time.Second)
}
}
@@ -311,7 +309,7 @@ func TestActorHighThroughput(t *testing.T) {
numMessages := 100000
start := time.Now()
- for i := 0; i < numMessages; i++ {
+ for range numMessages {
pid.Tell(&IncrementMsg{Value: 1})
}
diff --git a/examples/comprehensive/main.go b/examples/comprehensive/main.go
index 887353b..0eb6fc2 100644
--- a/examples/comprehensive/main.go
+++ b/examples/comprehensive/main.go
@@ -59,7 +59,8 @@ func demoSafeAgent(ctx context.Context) {
err := guardChain.Check(ctx, guardInput)
if err != nil {
- if guardErr, ok := err.(*guardrails.GuardrailError); ok {
+ guardErr := &guardrails.GuardrailError{}
+ if errors.As(err, &guardErr) {
fmt.Printf(" ⚠️ 被 %s 拦截: %s\n", guardErr.GuardrailName, guardErr.Message)
if guardErr.ShouldMask {
fmt.Printf(" 掩码后: %s\n", guardErr.MaskedContent)
diff --git a/examples/config-paths/main.go b/examples/config-paths/main.go
index dbf3a7b..116b311 100644
--- a/examples/config-paths/main.go
+++ b/examples/config-paths/main.go
@@ -6,6 +6,7 @@ import (
"fmt"
"os"
"path/filepath"
+ "strings"
"github.com/astercloud/aster/pkg/config"
)
@@ -131,8 +132,10 @@ func demonstrateEnsureDir() {
func repeatStr(s string, n int) string {
result := ""
+ var resultSb134 strings.Builder
for i := 0; i < n; i++ {
- result += s
+ resultSb134.WriteString(s)
}
+ result += resultSb134.String()
return result
}
diff --git a/examples/custom_claude_api/test_redis_store.go b/examples/custom_claude_api/test_redis_store.go
index 42e422f..fe14669 100644
--- a/examples/custom_claude_api/test_redis_store.go
+++ b/examples/custom_claude_api/test_redis_store.go
@@ -79,10 +79,10 @@ func main() {
// 3. 创建 Sandbox
fmt.Println("\n【步骤 3】创建 Sandbox")
sb, err := sandbox.NewLocalSandbox(&sandbox.LocalSandboxConfig{
- WorkDir: "./workspace",
- EnforceBoundary: false,
- SecurityLevel: 1,
- AllowedCommands: nil,
+ WorkDir: "./workspace",
+ EnforceBoundary: false,
+ SecurityLevel: 1,
+ AllowedCommands: nil,
ForbiddenCommands: nil,
})
if err != nil {
diff --git a/examples/desktop/README.md b/examples/desktop/README.md
index cc4e6ee..3cbbdcd 100644
--- a/examples/desktop/README.md
+++ b/examples/desktop/README.md
@@ -48,9 +48,9 @@ func main() {
app, _ := desktop.NewApp(&desktop.AppConfig{
Framework: desktop.FrameworkWails,
})
-
+
// ... create and register agent ...
-
+
wails.Run(&options.App{
Title: "Aster Desktop",
Width: 1024,
@@ -91,7 +91,7 @@ fn main() {
.args(["--framework", "tauri", "--port", "9528"])
.spawn()
.expect("Failed to start Aster");
-
+
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running tauri application");
@@ -114,27 +114,27 @@ export async function chat(agentId, message) {
// SSE for streaming events
export function subscribeEvents(onEvent) {
const eventSource = new EventSource(`${API_URL}/api/events`);
-
+
eventSource.addEventListener('text_chunk', (e) => {
const data = JSON.parse(e.data);
onEvent('text_chunk', data);
});
-
+
eventSource.addEventListener('tool_start', (e) => {
const data = JSON.parse(e.data);
onEvent('tool_start', data);
});
-
+
eventSource.addEventListener('tool_end', (e) => {
const data = JSON.parse(e.data);
onEvent('tool_end', data);
});
-
+
eventSource.addEventListener('approval_required', (e) => {
const data = JSON.parse(e.data);
onEvent('approval_required', data);
});
-
+
return () => eventSource.close();
}
```
@@ -154,7 +154,7 @@ let asterProcess;
function startAster() {
const asterPath = path.join(__dirname, 'aster-desktop');
asterProcess = spawn(asterPath, ['--framework', 'electron', '--port', '9527']);
-
+
asterProcess.stdout.on('data', (data) => {
console.log(`Aster: ${data}`);
});
@@ -162,7 +162,7 @@ function startAster() {
app.whenReady().then(() => {
startAster();
-
+
const win = new BrowserWindow({
width: 1024,
height: 768,
@@ -170,7 +170,7 @@ app.whenReady().then(() => {
preload: path.join(__dirname, 'preload.js'),
},
});
-
+
win.loadFile('index.html');
});
@@ -196,7 +196,7 @@ contextBridge.exposeInMainWorld('aster', {
});
return res.json();
},
-
+
cancel: async (agentId) => {
const res = await fetch(`${API_URL}/api/cancel`, {
method: 'POST',
@@ -205,7 +205,7 @@ contextBridge.exposeInMainWorld('aster', {
});
return res.json();
},
-
+
approve: async (agentId, callId, decision, note) => {
const res = await fetch(`${API_URL}/api/approve`, {
method: 'POST',
@@ -219,17 +219,17 @@ contextBridge.exposeInMainWorld('aster', {
});
return res.json();
},
-
+
subscribeEvents: (onEvent) => {
const eventSource = new EventSource(`${API_URL}/api/events`);
-
+
['text_chunk', 'tool_start', 'tool_end', 'approval_required', 'error', 'done']
.forEach(type => {
eventSource.addEventListener(type, (e) => {
onEvent(type, JSON.parse(e.data));
});
});
-
+
return () => eventSource.close();
},
});
diff --git a/examples/guardrails/main.go b/examples/guardrails/main.go
index 719fded..0e9df33 100644
--- a/examples/guardrails/main.go
+++ b/examples/guardrails/main.go
@@ -4,6 +4,7 @@ package main
import (
"context"
+ "errors"
"fmt"
"log"
@@ -48,7 +49,8 @@ func testPIIDetection(ctx context.Context) {
err := piiGuard.Check(ctx, input)
if err != nil {
- if guardErr, ok := err.(*guardrails.GuardrailError); ok {
+ guardErr := &guardrails.GuardrailError{}
+ if errors.As(err, &guardErr) {
fmt.Printf(" ✅ 检测到 PII: %v\n", guardErr.Details["detected_pii"])
fmt.Printf(" 错误: %s\n", guardErr.Message)
}
@@ -68,7 +70,8 @@ func testPIIMasking(ctx context.Context) {
err := piiGuard.Check(ctx, input)
if err != nil {
- if guardErr, ok := err.(*guardrails.GuardrailError); ok {
+ guardErr := &guardrails.GuardrailError{}
+ if errors.As(err, &guardErr) {
fmt.Printf(" ✅ PII 已掩码\n")
fmt.Printf(" 原文: %s\n", input.Content)
fmt.Printf(" 掩码后: %s\n", guardErr.MaskedContent)
@@ -118,7 +121,8 @@ func testPromptInjection(ctx context.Context) {
if detected == tc.shouldDetect {
fmt.Printf(" ✅ %s: %v\n", tc.name, detected)
if detected {
- if guardErr, ok := err.(*guardrails.GuardrailError); ok {
+ guardErr := &guardrails.GuardrailError{}
+ if errors.As(err, &guardErr) {
fmt.Printf(" 检测到: %v\n", guardErr.Details["detected_patterns"])
}
}
@@ -151,7 +155,8 @@ func testGuardrailChain(ctx context.Context) {
err := chain.Check(ctx, input)
if err != nil {
- if guardErr, ok := err.(*guardrails.GuardrailError); ok {
+ guardErr := &guardrails.GuardrailError{}
+ if errors.As(err, &guardErr) {
fmt.Printf(" ⚠️ %s: 被 %s 拦截\n", tc.name, guardErr.GuardrailName)
}
} else {
diff --git a/examples/human-in-the-loop/main.go b/examples/human-in-the-loop/main.go
index f234766..e6f86f6 100644
--- a/examples/human-in-the-loop/main.go
+++ b/examples/human-in-the-loop/main.go
@@ -4,6 +4,7 @@ package main
import (
"context"
+ "errors"
"fmt"
"log"
"os"
@@ -159,7 +160,7 @@ func smartApprovalHandler(ctx context.Context, req *middleware.ReviewRequest) ([
}
}
- return nil, fmt.Errorf("no decision made")
+ return nil, errors.New("no decision made")
}
type RiskLevel int
diff --git a/examples/logic-memory/README.md b/examples/logic-memory/README.md
index 26da752..06e28c4 100644
--- a/examples/logic-memory/README.md
+++ b/examples/logic-memory/README.md
@@ -95,4 +95,3 @@ go fmt ./examples/logic-memory
- [Logic Memory 完整文档](../../docs/content/04.memory/11.logic-memory.md)
- [Logic Memory 设计计划](../../docs/content/04.memory/12.logic-memory-plan.md)
- [Memory 系统总览](../../docs/content/04.memory/1.overview.md)
-
diff --git a/examples/long-running-tools/main.go b/examples/long-running-tools/main.go
index 1318a87..d394381 100644
--- a/examples/long-running-tools/main.go
+++ b/examples/long-running-tools/main.go
@@ -4,8 +4,10 @@ package main
import (
"context"
+ "errors"
"fmt"
"log"
+ "strconv"
"time"
"github.com/astercloud/aster/pkg/tools"
@@ -226,6 +228,7 @@ func (t *MockDataProcessingTool) Execute(ctx context.Context, args map[string]an
// MockFileUploadTool 模拟文件上传工具
type MockFileUploadTool struct {
*tools.BaseLongRunningTool
+
executor *tools.LongRunningExecutor
}
@@ -242,7 +245,7 @@ func NewMockFileUploadTool(executor *tools.LongRunningExecutor) *MockFileUploadT
func (t *MockFileUploadTool) StartAsync(ctx context.Context, args map[string]any) (string, error) {
// 使用自定义的执行逻辑
- taskID := "task_" + fmt.Sprintf("%d", time.Now().UnixNano())
+ taskID := "task_" + strconv.FormatInt(time.Now().UnixNano(), 10)
// 创建任务状态
status := &tools.TaskStatus{
@@ -289,7 +292,7 @@ func (t *MockFileUploadTool) StartAsync(ctx context.Context, args map[string]any
func (t *MockFileUploadTool) Execute(ctx context.Context, args map[string]any) (any, error) {
// 简化的同步执行
- return nil, fmt.Errorf("use StartAsync for file upload")
+ return nil, errors.New("use StartAsync for file upload")
}
// ============================================================
diff --git a/examples/memory-advanced/main.go b/examples/memory-advanced/main.go
index 00137c9..8ba3773 100644
--- a/examples/memory-advanced/main.go
+++ b/examples/memory-advanced/main.go
@@ -4,6 +4,7 @@ package main
import (
"context"
+ "errors"
"fmt"
"log"
@@ -43,7 +44,7 @@ func (t *userPreferenceTool) InputSchema() map[string]any {
func (t *userPreferenceTool) Execute(ctx context.Context, input map[string]any, tc *tools.ToolContext) (any, error) {
rawPref, _ := input["preference"].(string)
if rawPref == "" {
- return nil, fmt.Errorf("preference is required")
+ return nil, errors.New("preference is required")
}
scope := memory.Scope{
@@ -103,7 +104,7 @@ func (t *projectFactTool) InputSchema() map[string]any {
func (t *projectFactTool) Execute(ctx context.Context, input map[string]any, tc *tools.ToolContext) (any, error) {
fact, _ := input["fact"].(string)
if fact == "" {
- return nil, fmt.Errorf("fact is required")
+ return nil, errors.New("fact is required")
}
scope := memory.Scope{
@@ -165,7 +166,7 @@ func (t *resourceNoteTool) InputSchema() map[string]any {
func (t *resourceNoteTool) Execute(ctx context.Context, input map[string]any, tc *tools.ToolContext) (any, error) {
note, _ := input["note"].(string)
if note == "" {
- return nil, fmt.Errorf("note is required")
+ return nil, errors.New("note is required")
}
scope := memory.Scope{
diff --git a/examples/openrouter/agent_test.go b/examples/openrouter/agent_test.go
index 08b91e4..ff39ae8 100644
--- a/examples/openrouter/agent_test.go
+++ b/examples/openrouter/agent_test.go
@@ -8,7 +8,6 @@ import (
"time"
"github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"github.com/astercloud/aster/pkg/agent"
@@ -45,7 +44,7 @@ func (s *AgentIntegrationSuite) SetupSuite() {
// 确保工作目录存在
err := os.MkdirAll(s.workspace, 0755)
- require.NoError(s.T(), err, "创建工作目录失败")
+ s.Require().NoError(err, "创建工作目录失败")
}
// TearDownSuite 在所有测试结束后执行一次
@@ -66,7 +65,7 @@ func (s *AgentIntegrationSuite) SetupTest() {
var err error
s.ag, err = createTestAgent(s.apiKey)
- require.NoError(s.T(), err, "创建 Agent 失败")
+ s.Require().NoError(err, "创建 Agent 失败")
// 订阅事件(用于调试)
s.eventCh = s.ag.Subscribe(
@@ -111,50 +110,50 @@ func (s *AgentIntegrationSuite) handleEvents() {
func (s *AgentIntegrationSuite) TestCreateFile() {
result, err := s.ag.Chat(s.ctx, "使用 Write 工具在当前目录创建文件 test.txt,文件内容为: Hello World")
- require.NoError(s.T(), err, "Chat 调用失败")
- require.NotNil(s.T(), result, "结果不应为空")
- assert.Equal(s.T(), "ok", result.Status, "状态应为 ok")
+ s.Require().NoError(err, "Chat 调用失败")
+ s.Require().NotNil(result, "结果不应为空")
+ s.Equal("ok", result.Status, "状态应为 ok")
// 等待文件操作完成
time.Sleep(300 * time.Millisecond)
// 验证文件创建
data, err := os.ReadFile(s.workspace + "/test.txt")
- require.NoError(s.T(), err, "文件应该已创建")
- assert.Equal(s.T(), "Hello World", strings.TrimSpace(string(data)), "文件内容不匹配")
+ s.Require().NoError(err, "文件应该已创建")
+ s.Equal("Hello World", strings.TrimSpace(string(data)), "文件内容不匹配")
}
func (s *AgentIntegrationSuite) TestReadFile() {
// 先创建测试文件
testContent := "这是测试内容"
err := os.WriteFile(s.workspace+"/test.txt", []byte(testContent), 0644)
- require.NoError(s.T(), err, "创建测试文件失败")
+ s.Require().NoError(err, "创建测试文件失败")
result, err := s.ag.Chat(s.ctx, "使用 Read 工具读取 test.txt 文件的内容")
- require.NoError(s.T(), err, "Chat 调用失败")
- require.NotNil(s.T(), result, "结果不应为空")
- assert.Equal(s.T(), "ok", result.Status, "状态应为 ok")
+ s.Require().NoError(err, "Chat 调用失败")
+ s.Require().NotNil(result, "结果不应为空")
+ s.Equal("ok", result.Status, "状态应为 ok")
}
func (s *AgentIntegrationSuite) TestBashCommand() {
result, err := s.ag.Chat(s.ctx, "使用 Bash 工具执行命令: ls -la")
- require.NoError(s.T(), err, "Chat 调用失败")
- require.NotNil(s.T(), result, "结果不应为空")
- assert.Equal(s.T(), "ok", result.Status, "状态应为 ok")
+ s.Require().NoError(err, "Chat 调用失败")
+ s.Require().NotNil(result, "结果不应为空")
+ s.Equal("ok", result.Status, "状态应为 ok")
}
func (s *AgentIntegrationSuite) TestAgentStatus() {
// 先执行一个简单操作确保有步骤记录
_, err := s.ag.Chat(s.ctx, "你好")
- require.NoError(s.T(), err)
+ s.Require().NoError(err)
status := s.ag.Status()
- assert.Equal(s.T(), types.AgentStateReady, status.State, "Agent 状态应为 Ready")
- assert.Greater(s.T(), status.StepCount, 0, "步骤计数应大于 0")
- assert.NotEmpty(s.T(), status.AgentID, "Agent ID 不应为空")
+ s.Equal(types.AgentStateReady, status.State, "Agent 状态应为 Ready")
+ assert.Positive(s.T(), status.StepCount, "步骤计数应大于 0")
+ s.NotEmpty(status.AgentID, "Agent ID 不应为空")
s.T().Logf("Agent 状态: ID=%s, State=%s, Steps=%d",
status.AgentID, status.State, status.StepCount)
@@ -175,9 +174,9 @@ func (s *AgentIntegrationSuite) TestMultipleBashCommands() {
s.Run(tc.name, func() {
result, err := s.ag.Chat(s.ctx, tc.prompt)
- require.NoError(s.T(), err, "Chat 调用失败")
- require.NotNil(s.T(), result, "结果不应为空")
- assert.Equal(s.T(), "ok", result.Status, "状态应为 ok")
+ s.Require().NoError(err, "Chat 调用失败")
+ s.Require().NotNil(result, "结果不应为空")
+ s.Equal("ok", result.Status, "状态应为 ok")
})
}
}
diff --git a/examples/openrouter/main.go b/examples/openrouter/main.go
index 6bc037a..74817d7 100644
--- a/examples/openrouter/main.go
+++ b/examples/openrouter/main.go
@@ -5,6 +5,7 @@ package main
import (
"bufio"
"context"
+ "errors"
"fmt"
"log/slog"
"os"
@@ -56,7 +57,7 @@ func run(ctx context.Context, cmd *cli.Command) error {
// 检查 API Key
apiKey := os.Getenv("OPENROUTER_API_KEY")
if apiKey == "" {
- return fmt.Errorf("需要设置 OPENROUTER_API_KEY 环境变量")
+ return errors.New("需要设置 OPENROUTER_API_KEY 环境变量")
}
baseURL := os.Getenv("OPENROUTER_BASE_URL")
diff --git a/examples/permission/README.md b/examples/permission/README.md
index 17efa01..5e33fa4 100644
--- a/examples/permission/README.md
+++ b/examples/permission/README.md
@@ -180,7 +180,7 @@ go func() {
if permEvent, ok := event.Event.(*types.ControlPermissionRequiredEvent); ok {
// 显示审批 UI
showApprovalDialog(permEvent)
-
+
// 发送审批决定
agent.ApprovePermission(permEvent.RequestID, true, "用户批准")
}
diff --git a/examples/permission/main.go b/examples/permission/main.go
index 8357b8f..54bff49 100644
--- a/examples/permission/main.go
+++ b/examples/permission/main.go
@@ -8,6 +8,7 @@ import (
"log"
"os"
"path/filepath"
+ "strings"
"github.com/astercloud/aster/pkg/permission"
"github.com/astercloud/aster/pkg/types"
@@ -251,8 +252,10 @@ func demonstrateRules(ctx context.Context, tmpDir string) {
func repeatStr(s string, n int) string {
result := ""
+ var resultSb254 strings.Builder
for i := 0; i < n; i++ {
- result += s
+ resultSb254.WriteString(s)
}
+ result += resultSb254.String()
return result
}
diff --git a/examples/ptc/local-test/main.go b/examples/ptc/local-test/main.go
index 1bf4cbd..05c5f34 100644
--- a/examples/ptc/local-test/main.go
+++ b/examples/ptc/local-test/main.go
@@ -190,7 +190,7 @@ async def main():
asyncio.run(main())
`
- for i := 0; i < iterations; i++ {
+ for range iterations {
start := time.Now()
_, _ = runtime.Execute(ctx, simpleCode, map[string]any{})
totalDuration += time.Since(start)
diff --git a/examples/recipe/main.go b/examples/recipe/main.go
index 83c70ea..f211f87 100644
--- a/examples/recipe/main.go
+++ b/examples/recipe/main.go
@@ -7,6 +7,7 @@ import (
"log"
"os"
"path/filepath"
+ "strings"
"github.com/astercloud/aster/pkg/recipe"
)
@@ -322,8 +323,10 @@ func demonstratePermissions(tmpDir string) {
func repeatStr(s string, n int) string {
result := ""
- for i := 0; i < n; i++ {
- result += s
+ var resultSb325 strings.Builder
+ for range n {
+ resultSb325.WriteString(s)
}
+ result += resultSb325.String()
return result
}
diff --git a/examples/recipes/code-review.yaml b/examples/recipes/code-review.yaml
index 31f488e..c832d9a 100644
--- a/examples/recipes/code-review.yaml
+++ b/examples/recipes/code-review.yaml
@@ -1,5 +1,5 @@
# Code Review Assistant Recipe
-#
+#
# This recipe configures an AI agent for code review tasks.
# Usage: aster run --recipe examples/recipes/code-review.yaml
#
@@ -18,28 +18,28 @@ description: |
# System instructions for the agent
instructions: |
You are a senior software engineer conducting a thorough code review.
-
+
## Your Responsibilities:
1. **Code Quality**: Check for clean code principles, proper naming, and code organization
2. **Security**: Identify potential security vulnerabilities (injection, auth issues, etc.)
3. **Performance**: Look for performance bottlenecks and optimization opportunities
4. **Best Practices**: Ensure the code follows language-specific best practices
5. **Testing**: Verify adequate test coverage and test quality
-
+
## Review Process:
1. First, understand the purpose of the code by reading any documentation
2. Analyze the code structure and architecture
3. Check individual functions/methods for issues
4. Look for patterns that might cause problems at scale
5. Provide actionable feedback with specific suggestions
-
+
## Output Format:
For each issue found, provide:
- **Severity**: Critical / High / Medium / Low / Info
- **Location**: File and line number
- **Issue**: Clear description of the problem
- **Suggestion**: How to fix it with code examples when helpful
-
+
Be constructive and educational in your feedback.
# Initial prompt when starting the session
@@ -57,7 +57,7 @@ extensions:
- type: builtin
name: filesystem
description: "File system access for reading code"
-
+
# Uncomment to enable git integration
# - type: stdio
# name: git
@@ -72,7 +72,7 @@ parameters:
requirement: optional
description: "Directory to review (relative to current working directory)"
default: "."
-
+
- key: language
input_type: select
requirement: optional
@@ -85,7 +85,7 @@ parameters:
- rust
- java
default: "go"
-
+
- key: focus
input_type: string
requirement: optional
diff --git a/examples/recipes/writing-assistant.yaml b/examples/recipes/writing-assistant.yaml
index 9fbbd4f..4ac07b0 100644
--- a/examples/recipes/writing-assistant.yaml
+++ b/examples/recipes/writing-assistant.yaml
@@ -6,20 +6,20 @@
version: "1.0"
title: "Writing Assistant"
description: |
- An AI writing assistant that helps with documentation, blog posts,
+ An AI writing assistant that helps with documentation, blog posts,
README files, and other written content.
instructions: |
- You are a skilled technical writer and editor. Help users create clear,
+ You are a skilled technical writer and editor. Help users create clear,
well-structured content.
-
+
## Guidelines:
- Use clear, concise language
- Structure content with proper headings
- Include code examples where appropriate
- Follow the target audience's technical level
- Suggest improvements for clarity and engagement
-
+
## Writing Style:
- Active voice preferred
- Short paragraphs (3-4 sentences max)
@@ -42,7 +42,7 @@ parameters:
- formal
- tutorial
default: "technical"
-
+
- key: audience
input_type: select
requirement: optional
diff --git a/examples/session-mysql/main.go b/examples/session-mysql/main.go
index ed39bf4..4a3eba4 100644
--- a/examples/session-mysql/main.go
+++ b/examples/session-mysql/main.go
@@ -6,6 +6,7 @@ import (
"context"
"fmt"
"log"
+ "strconv"
"time"
"github.com/astercloud/aster/pkg/session"
@@ -194,7 +195,7 @@ func queryOptimizationExample(ctx context.Context, service *mysql.Service, userI
// 辅助函数
func generateID() string {
- return fmt.Sprintf("%d", time.Now().UnixNano())
+ return strconv.FormatInt(time.Now().UnixNano(), 10)
}
// MySQL 8.0+ JSON 高级用法
diff --git a/examples/session-postgres/main.go b/examples/session-postgres/main.go
index 33975a5..5e17c40 100644
--- a/examples/session-postgres/main.go
+++ b/examples/session-postgres/main.go
@@ -6,6 +6,7 @@ import (
"context"
"fmt"
"log"
+ "strconv"
"time"
"github.com/astercloud/aster/pkg/session"
@@ -269,7 +270,7 @@ func queryExample(ctx context.Context, service *postgres.Service, userID string)
// 辅助函数
func generateID() string {
- return fmt.Sprintf("%d", time.Now().UnixNano())
+ return strconv.FormatInt(time.Now().UnixNano(), 10)
}
func truncate(s string, maxLen int) string {
diff --git a/examples/telemetry/main.go b/examples/telemetry/main.go
index 7a687a7..457bf41 100644
--- a/examples/telemetry/main.go
+++ b/examples/telemetry/main.go
@@ -192,7 +192,7 @@ func toolTracingExample() {
telemetry.WithSpanKind(telemetry.SpanKindInternal),
telemetry.WithAttributes(
telemetry.String(telemetry.AttrToolName, toolName),
- telemetry.String(telemetry.AttrToolCallID, fmt.Sprintf("call_%s", toolName)),
+ telemetry.String(telemetry.AttrToolCallID, "call_"+toolName),
),
)
@@ -272,12 +272,12 @@ func processMiddleware(ctx context.Context, _ telemetry.Span) {
for _, mw := range middlewares {
_, span := tracer.StartSpan(
ctx,
- fmt.Sprintf("middleware.%s", mw),
+ "middleware."+mw,
telemetry.WithSpanKind(telemetry.SpanKindInternal),
)
time.Sleep(10 * time.Millisecond)
- span.SetStatus(telemetry.StatusCodeOK, fmt.Sprintf("%s passed", mw))
+ span.SetStatus(telemetry.StatusCodeOK, mw+" passed")
span.End()
}
}
diff --git a/examples/tool-cache/main.go b/examples/tool-cache/main.go
index ccfaa33..e41abc7 100644
--- a/examples/tool-cache/main.go
+++ b/examples/tool-cache/main.go
@@ -3,6 +3,7 @@ package main
import (
"context"
+ "errors"
"fmt"
"log"
"time"
@@ -41,7 +42,7 @@ func (t *ExpensiveTool) Prompt() string {
func (t *ExpensiveTool) Execute(ctx context.Context, input map[string]any, tc *tools.ToolContext) (any, error) {
number, ok := input["number"].(float64)
if !ok {
- return nil, fmt.Errorf("invalid input: number must be a number")
+ return nil, errors.New("invalid input: number must be a number")
}
fmt.Printf(" [ExpensiveTool] 开始计算 %v...\n", number)
diff --git a/examples/workflow-semantic/main.go b/examples/workflow-semantic/main.go
index d506a33..de2e9be 100644
--- a/examples/workflow-semantic/main.go
+++ b/examples/workflow-semantic/main.go
@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"log"
+ "strings"
"time"
"github.com/astercloud/aster/pkg/agent"
@@ -184,13 +185,15 @@ func (a *SemanticQAWorkflowAgent) Execute(ctx context.Context, message string) *
}
contextText := ""
+ var contextTextSb187 strings.Builder
for i, h := range hits {
if i > 0 {
- contextText += "\n\n"
+ contextTextSb187.WriteString("\n\n")
}
text, _ := h.Metadata["text"].(string)
- contextText += fmt.Sprintf("[DOC %d] %s", i+1, text)
+ contextTextSb187.WriteString(fmt.Sprintf("[DOC %d] %s", i+1, text))
}
+ contextText += contextTextSb187.String()
if writer.Send(&session.Event{
ID: fmt.Sprintf("evt-%s-context-%d", a.name, time.Now().UnixNano()),
diff --git a/examples/workflow/main.go b/examples/workflow/main.go
index 320166b..6871780 100644
--- a/examples/workflow/main.go
+++ b/examples/workflow/main.go
@@ -48,7 +48,7 @@ func main() {
if data, ok := dataMap["data"].([]string); ok {
processed := make([]string, len(data))
for i, item := range data {
- processed[i] = fmt.Sprintf("processed_%s", item)
+ processed[i] = "processed_" + item
}
return &workflow.StepOutput{
Content: map[string]any{
@@ -60,7 +60,7 @@ func main() {
}
}
- return nil, fmt.Errorf("invalid input: expected map with 'data' field")
+ return nil, errors.New("invalid input: expected map with 'data' field")
}))
// 步骤 3: 转换数据
diff --git a/pkg/a2a/handler.go b/pkg/a2a/handler.go
index 88ad412..b90247c 100644
--- a/pkg/a2a/handler.go
+++ b/pkg/a2a/handler.go
@@ -48,7 +48,7 @@ func (h *Handler) GetAgentCard(c *gin.Context) {
"success": false,
"error": gin.H{
"code": "not_found",
- "message": fmt.Sprintf("Agent not found: %s", err.Error()),
+ "message": "Agent not found: " + err.Error(),
},
})
return
diff --git a/pkg/a2a/server.go b/pkg/a2a/server.go
index f14fc12..e83044f 100644
--- a/pkg/a2a/server.go
+++ b/pkg/a2a/server.go
@@ -5,7 +5,9 @@ import (
"crypto/rand"
"encoding/hex"
"encoding/json"
+ "errors"
"fmt"
+ "strconv"
"time"
"github.com/astercloud/aster/pkg/actor"
@@ -47,7 +49,7 @@ func (s *Server) HandleRequest(ctx context.Context, agentID string, req *JSONRPC
return s.handleTasksCancel(ctx, agentID, req)
default:
return NewErrorResponse(req.ID, ErrorCodeMethodNotFound,
- fmt.Sprintf("method not found: %s", req.Method), nil)
+ "method not found: "+req.Method, nil)
}
}
@@ -63,8 +65,8 @@ func (s *Server) GetAgentCard(agentID string) (*AgentCard, error) {
// TODO: 未来可以从 Agent 的元数据中动态获取这些信息
card := &AgentCard{
Name: agentID,
- Description: fmt.Sprintf("Aster AI Agent: %s", agentID),
- URL: fmt.Sprintf("/a2a/%s", agentID),
+ Description: "Aster AI Agent: " + agentID,
+ URL: "/a2a/" + agentID,
Provider: Provider{
Organization: "Aster",
URL: "https://github.com/astercloud/aster",
@@ -122,13 +124,13 @@ func (s *Server) handleMessageSend(ctx context.Context, agentID string, req *JSO
task.UpdateStatus(TaskStateFailed, &Message{
MessageID: generateID(),
Role: "agent",
- Parts: []Part{{Kind: "text", Text: fmt.Sprintf("agent not found: %s", agentID)}},
+ Parts: []Part{{Kind: "text", Text: "agent not found: " + agentID}},
Kind: "message",
})
if err := s.taskStore.Save(agentID, task); err != nil {
a2aLog.Warn(ctx, "save task error", map[string]any{"error": err})
}
- return NewErrorResponse(req.ID, ErrorCodeInternalError, fmt.Sprintf("agent not found: %s", agentID), nil)
+ return NewErrorResponse(req.ID, ErrorCodeInternalError, "agent not found: "+agentID, nil)
}
// 提取文本内容
@@ -237,7 +239,7 @@ func (s *Server) handleTasksCancel(_ context.Context, agentID string, req *JSONR
task.UpdateStatus(TaskStateCanceled, &Message{
MessageID: generateID(),
Role: "agent",
- Parts: []Part{{Kind: "text", Text: "Task cancelled by request."}},
+ Parts: []Part{{Kind: "text", Text: "Task canceled by request."}},
Kind: "message",
})
@@ -247,7 +249,7 @@ func (s *Server) handleTasksCancel(_ context.Context, agentID string, req *JSONR
return NewSuccessResponse(req.ID, &TasksCancelResult{
Success: true,
- Message: "Task cancelled successfully",
+ Message: "Task canceled successfully",
})
}
@@ -284,7 +286,7 @@ func (s *Server) loadOrCreateTask(agentID, taskID, contextID string, metadata Me
// parseParams 解析 JSON-RPC 参数
func parseParams(params any, target any) error {
if params == nil {
- return fmt.Errorf("params is required")
+ return errors.New("params is required")
}
// 先序列化再反序列化,确保类型正确
@@ -315,7 +317,7 @@ func generateID() string {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
a2aLog.Warn(context.Background(), "generate ID error", map[string]any{"error": err})
- return fmt.Sprintf("%d", time.Now().UnixNano())
+ return strconv.FormatInt(time.Now().UnixNano(), 10)
}
return hex.EncodeToString(b)
}
diff --git a/pkg/a2a/server_test.go b/pkg/a2a/server_test.go
index ba36fd6..f04d94d 100644
--- a/pkg/a2a/server_test.go
+++ b/pkg/a2a/server_test.go
@@ -227,8 +227,8 @@ func TestServer_HandleRequest_TasksCancel(t *testing.T) {
assert.NotNil(t, resp.Result)
// 验证取消标记已设置
- cancelled := taskStore.IsCanceled(task.ID)
- assert.True(t, cancelled)
+ canceled := taskStore.IsCanceled(task.ID)
+ assert.True(t, canceled)
// 验证任务状态已更新
updatedTask, err := taskStore.Load(agentID, task.ID)
diff --git a/pkg/a2a/store.go b/pkg/a2a/store.go
index 19ce283..242de83 100644
--- a/pkg/a2a/store.go
+++ b/pkg/a2a/store.go
@@ -2,6 +2,7 @@ package a2a
import (
"fmt"
+ "maps"
"sync"
)
@@ -167,9 +168,7 @@ func copyTask(task *Task) *Task {
// 拷贝 Metadata
if task.Metadata != nil {
copied.Metadata = make(Metadata)
- for k, v := range task.Metadata {
- copied.Metadata[k] = v
- }
+ maps.Copy(copied.Metadata, task.Metadata)
}
return copied
diff --git a/pkg/a2a/store_test.go b/pkg/a2a/store_test.go
index be0940f..e4f3014 100644
--- a/pkg/a2a/store_test.go
+++ b/pkg/a2a/store_test.go
@@ -93,15 +93,15 @@ func TestInMemoryTaskStore_Cancellation(t *testing.T) {
store.AddCancellation(task.ID)
// 检查取消状态
- cancelled := store.IsCanceled(task.ID)
- assert.True(t, cancelled)
+ canceled := store.IsCanceled(task.ID)
+ assert.True(t, canceled)
// 移除取消信号
store.RemoveCancellation(task.ID)
// 再次检查
- cancelled = store.IsCanceled(task.ID)
- assert.False(t, cancelled)
+ canceled = store.IsCanceled(task.ID)
+ assert.False(t, canceled)
}
func TestInMemoryTaskStore_Concurrency(t *testing.T) {
@@ -112,7 +112,7 @@ func TestInMemoryTaskStore_Concurrency(t *testing.T) {
const numTasks = 100
done := make(chan bool, numTasks)
- for i := 0; i < numTasks; i++ {
+ for i := range numTasks {
go func(id int) {
task := NewTask(string(rune('a'+id)), "context-1")
_ = store.Save(agentID, task)
@@ -121,14 +121,14 @@ func TestInMemoryTaskStore_Concurrency(t *testing.T) {
}
// 等待所有任务完成
- for i := 0; i < numTasks; i++ {
+ for range numTasks {
<-done
}
// 验证任务已保存(数量可能少于numTasks,因为有ID冲突)
tasks, err := store.List(agentID)
require.NoError(t, err)
- assert.Greater(t, len(tasks), 0)
+ assert.NotEmpty(t, tasks)
}
func TestTask_StateTransitions(t *testing.T) {
diff --git a/pkg/actor/actor_test.go b/pkg/actor/actor_test.go
index 587cb83..f572c77 100644
--- a/pkg/actor/actor_test.go
+++ b/pkg/actor/actor_test.go
@@ -222,11 +222,11 @@ func TestSystem_ConcurrentMessages(t *testing.T) {
numGoroutines := 100
messagesPerGoroutine := 100
- for i := 0; i < numGoroutines; i++ {
+ for range numGoroutines {
wg.Add(1)
go func() {
defer wg.Done()
- for j := 0; j < messagesPerGoroutine; j++ {
+ for range messagesPerGoroutine {
system.Send(pid, &CountMsg{Value: 1})
}
}()
@@ -367,8 +367,7 @@ func BenchmarkSystem_Send(b *testing.B) {
counter := &CounterActor{}
pid := system.Spawn(counter, "counter")
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
system.Send(pid, &CountMsg{Value: 1})
}
}
@@ -379,8 +378,7 @@ func BenchmarkSystem_RequestResponse(b *testing.B) {
pid := system.Spawn(&EchoActor{}, "echo")
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for i := 0; b.Loop(); i++ {
_, _ = system.Request(pid, &PingMsg{Count: i}, time.Second)
}
}
@@ -389,8 +387,7 @@ func BenchmarkSystem_SpawnStop(b *testing.B) {
system := NewSystem("bench")
defer system.Shutdown()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
pid := system.Spawn(&EchoActor{}, "actor")
system.Stop(pid)
time.Sleep(time.Microsecond) // 确保清理完成
diff --git a/pkg/actor/system.go b/pkg/actor/system.go
index 6b586e6..b248647 100644
--- a/pkg/actor/system.go
+++ b/pkg/actor/system.go
@@ -2,6 +2,7 @@ package actor
import (
"context"
+ "errors"
"fmt"
"runtime/debug"
"sync"
@@ -293,7 +294,7 @@ func (s *System) SendWithSender(target *PID, msg Message, sender *PID) {
// Request 同步请求(等待响应)
func (s *System) Request(target *PID, msg Message, timeout time.Duration) (Message, error) {
if !s.isRunning.Load() {
- return nil, fmt.Errorf("actor system is not running")
+ return nil, errors.New("actor system is not running")
}
// 使用 context 来取消请求
diff --git a/pkg/actor/types.go b/pkg/actor/types.go
index 75ad34b..45f8eb2 100644
--- a/pkg/actor/types.go
+++ b/pkg/actor/types.go
@@ -7,6 +7,7 @@ package actor
import (
"context"
+ "errors"
"fmt"
"time"
)
@@ -48,12 +49,12 @@ func (p *PID) Tell(msg Message) {
// Request 发送请求并等待响应(同步调用)
func (p *PID) Request(msg Message, timeout time.Duration) (Message, error) {
if p.system == nil {
- return nil, fmt.Errorf("actor system not available")
+ return nil, errors.New("actor system not available")
}
return p.system.Request(p, msg, timeout)
}
-// Actor Actor 接口
+// Actor
// 实现此接口即可成为 Actor
type Actor interface {
// Receive 处理接收到的消息
diff --git a/pkg/agent/actor.go b/pkg/agent/actor.go
index 5b9324d..322c11c 100644
--- a/pkg/agent/actor.go
+++ b/pkg/agent/actor.go
@@ -2,6 +2,7 @@ package agent
import (
"context"
+ "errors"
"fmt"
"sync"
"time"
@@ -268,7 +269,7 @@ func (a *AgentActor) handleSend(ctx *actor.Context, msg *SendMsg) {
go func() {
err := a.agent.Send(execCtx, msg.Text)
- if err != nil && err != context.Canceled {
+ if err != nil && !errors.Is(err, context.Canceled) {
a.recordError(err)
if ctx.Sender != nil {
ctx.Reply(&ErrorMsg{Error: err, Context: "send"})
@@ -290,7 +291,7 @@ func (a *AgentActor) handleChat(ctx *actor.Context, msg *ChatMsg) {
Error: err,
}
- if err != nil && err != context.Canceled {
+ if err != nil && !errors.Is(err, context.Canceled) {
a.recordError(err)
}
@@ -385,7 +386,7 @@ func (a *AgentActor) handleDirectToolCall(ctx *actor.Context, msg *DirectToolCal
Error: err,
}
- if err != nil && err != context.Canceled {
+ if err != nil && !errors.Is(err, context.Canceled) {
a.recordError(err)
}
diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go
index 50bf7ef..4e384da 100644
--- a/pkg/agent/agent.go
+++ b/pkg/agent/agent.go
@@ -2,8 +2,10 @@ package agent
import (
"context"
+ "errors"
"fmt"
"path/filepath"
+ "slices"
"sort"
"strings"
"sync"
@@ -55,19 +57,19 @@ type Agent struct {
semanticMemory *memory.SemanticMemory
// 状态管理
- mu sync.RWMutex
- state types.AgentRuntimeState
- breakpoint types.BreakpointState
- messages []types.Message
- toolRecords map[string]*types.ToolCallRecord
- runningTools map[string]*runningToolHandle
- stepCount int
- iterationCount int // 当前会话的模型调用次数
- maxIterations int // 最大迭代次数限制(默认50)
- initialThinkingSent bool // 是否已发送初始思考事件(用于防止重复发送"任务规划")
- lastSfpIndex int
- lastBookmark *types.Bookmark
- createdAt time.Time
+ mu sync.RWMutex
+ state types.AgentRuntimeState
+ breakpoint types.BreakpointState
+ messages []types.Message
+ toolRecords map[string]*types.ToolCallRecord
+ runningTools map[string]*runningToolHandle
+ stepCount int
+ iterationCount int // 当前会话的模型调用次数
+ maxIterations int // 最大迭代次数限制(默认50)
+ initialThinkingSent bool // 是否已发送初始思考事件(用于防止重复发送"任务规划")
+ lastSfpIndex int
+ lastBookmark *types.Bookmark
+ createdAt time.Time
// 权限管理
pendingPermissions map[string]chan string // callID -> decision channel
@@ -158,7 +160,7 @@ func Create(ctx context.Context, config *types.AgentConfig, deps *Dependencies)
}
if modelConfig == nil {
- return nil, fmt.Errorf("model config is required")
+ return nil, errors.New("model config is required")
}
prov, err := deps.ProviderFactory.Create(modelConfig)
@@ -351,13 +353,7 @@ func Create(ctx context.Context, config *types.AgentConfig, deps *Dependencies)
if template.Runtime != nil && template.Runtime.ConversationCompression != nil &&
template.Runtime.ConversationCompression.Enabled {
// 检查是否已配置 summarization
- hasSummarization := false
- for _, name := range middlewareNames {
- if name == "summarization" {
- hasSummarization = true
- break
- }
- }
+ hasSummarization := slices.Contains(middlewareNames, "summarization")
if !hasSummarization {
middlewareNames = append(middlewareNames, "summarization")
agentLog.Debug(ctx, "auto-enabled summarization middleware from template ConversationCompression config", nil)
@@ -436,18 +432,18 @@ func Create(ctx context.Context, config *types.AgentConfig, deps *Dependencies)
// 创建Agent
agent := &Agent{
- id: config.AgentID,
- template: template,
- config: config,
- deps: deps,
- eventBus: events.NewEventBus(),
- provider: prov,
- sandbox: sb,
- executor: executor,
- toolMap: toolMap,
- middlewareStack: middlewareStack,
- commandExecutor: cmdExecutor,
- skillInjector: skillInjector,
+ id: config.AgentID,
+ template: template,
+ config: config,
+ deps: deps,
+ eventBus: events.NewEventBus(),
+ provider: prov,
+ sandbox: sb,
+ executor: executor,
+ toolMap: toolMap,
+ middlewareStack: middlewareStack,
+ commandExecutor: cmdExecutor,
+ skillInjector: skillInjector,
semanticMemory: semanticMem,
state: types.AgentStateReady,
breakpoint: types.BreakpointReady,
@@ -1125,17 +1121,17 @@ func (a *Agent) RespondToPermissionRequest(callID string, approved bool) error {
a.mu.Lock()
ch, exists := a.pendingPermissions[callID]
a.mu.Unlock()
-
+
if !exists {
return fmt.Errorf("no pending permission request for call ID: %s", callID)
}
-
+
if approved {
ch <- "approved"
} else {
ch <- "rejected"
}
-
+
return nil
}
@@ -1230,7 +1226,7 @@ func (a *Agent) buildToolContext(ctx context.Context) *tools.ToolContext {
func (a *Agent) handleSlashCommand(ctx context.Context, text string) error {
if a.commandExecutor == nil {
agentLog.Error(ctx, "slash commands not enabled", map[string]any{"agent_id": a.id})
- return fmt.Errorf("slash commands not enabled")
+ return errors.New("slash commands not enabled")
}
// 解析命令和参数
@@ -1307,10 +1303,10 @@ type longRunningInterruptible struct {
}
func (l *longRunningInterruptible) Pause() error {
- return fmt.Errorf("pause not supported for long-running task")
+ return errors.New("pause not supported for long-running task")
}
func (l *longRunningInterruptible) Resume() error {
- return fmt.Errorf("resume not supported for long-running task")
+ return errors.New("resume not supported for long-running task")
}
func (l *longRunningInterruptible) Cancel() error {
return l.tool.Cancel(context.Background(), l.taskID)
@@ -1344,7 +1340,7 @@ func (a *Agent) ControlTool(callID, action, note string) error {
if ok && action == "cancel" {
a.eventBus.EmitProgress(&types.ProgressToolCancelledEvent{
Call: a.snapshotToolCall(callID),
- Reason: "cancelled",
+ Reason: "canceled",
})
}
@@ -1357,7 +1353,7 @@ func (a *Agent) controlRunningTool(callID, action string) error {
a.mu.RUnlock()
if !ok || handle == nil || handle.interruptible == nil {
- return fmt.Errorf("tool not interruptible or not running")
+ return errors.New("tool not interruptible or not running")
}
switch action {
@@ -1608,13 +1604,7 @@ func (a *Agent) validateMessageHistory(messages []types.Message) bool {
// 验证每个 tool_call ID 都有对应的 tool_result
for _, toolCallID := range toolCallIDs {
- found := false
- for _, resultID := range toolResultIDs {
- if resultID == toolCallID {
- found = true
- break
- }
- }
+ found := slices.Contains(toolResultIDs, toolCallID)
if !found {
return false
}
@@ -1668,13 +1658,7 @@ func (a *Agent) removeIncompleteToolCalls(messages []types.Message) []types.Mess
// 验证所有 tool_call 都有对应的 tool_result
allMatched := true
for _, toolCallID := range toolCallIDs {
- found := false
- for _, resultID := range toolResultIDs {
- if resultID == toolCallID {
- found = true
- break
- }
- }
+ found := slices.Contains(toolResultIDs, toolCallID)
if !found {
allMatched = false
break
diff --git a/pkg/agent/execution_plan.go b/pkg/agent/execution_plan.go
index 6046d76..5f991cd 100644
--- a/pkg/agent/execution_plan.go
+++ b/pkg/agent/execution_plan.go
@@ -2,6 +2,7 @@ package agent
import (
"context"
+ "errors"
"fmt"
"github.com/astercloud/aster/pkg/executionplan"
@@ -149,7 +150,7 @@ func (m *ExecutionPlanManager) GeneratePlan(ctx context.Context, request string,
// ApprovePlan 审批执行计划
func (m *ExecutionPlanManager) ApprovePlan(approvedBy string) error {
if m.currentPlan == nil {
- return fmt.Errorf("no pending plan to approve")
+ return errors.New("no pending plan to approve")
}
if m.currentPlan.Status != executionplan.StatusPendingApproval {
@@ -174,7 +175,7 @@ func (m *ExecutionPlanManager) ApprovePlan(approvedBy string) error {
// RejectPlan 拒绝执行计划
func (m *ExecutionPlanManager) RejectPlan(reason string) error {
if m.currentPlan == nil {
- return fmt.Errorf("no pending plan to reject")
+ return errors.New("no pending plan to reject")
}
m.currentPlan.Reject(reason)
@@ -195,7 +196,7 @@ func (m *ExecutionPlanManager) RejectPlan(reason string) error {
// ExecutePlan 执行当前计划
func (m *ExecutionPlanManager) ExecutePlan(ctx context.Context) error {
if m.currentPlan == nil {
- return fmt.Errorf("no plan to execute")
+ return errors.New("no plan to execute")
}
// 创建工具上下文
@@ -259,7 +260,7 @@ func (m *ExecutionPlanManager) FormatCurrentPlan() string {
// ValidateCurrentPlan 验证当前计划
func (m *ExecutionPlanManager) ValidateCurrentPlan() []error {
if m.currentPlan == nil {
- return []error{fmt.Errorf("no plan to validate")}
+ return []error{errors.New("no plan to validate")}
}
return m.generator.ValidatePlan(m.currentPlan)
}
@@ -271,7 +272,7 @@ func (m *ExecutionPlanManager) CancelPlan(reason string) {
}
m.executor.Cancel(m.currentPlan, reason)
- agentLog.Info(context.Background(), "execution plan cancelled", map[string]any{
+ agentLog.Info(context.Background(), "execution plan canceled", map[string]any{
"plan_id": m.currentPlan.ID,
"reason": reason,
})
@@ -280,7 +281,7 @@ func (m *ExecutionPlanManager) CancelPlan(reason string) {
// ResumePlan 恢复执行当前计划
func (m *ExecutionPlanManager) ResumePlan(ctx context.Context) error {
if m.currentPlan == nil {
- return fmt.Errorf("no plan to resume")
+ return errors.New("no plan to resume")
}
toolCtx := &tools.ToolContext{
diff --git a/pkg/agent/model_fallback.go b/pkg/agent/model_fallback.go
index 6a89a49..058ec8b 100644
--- a/pkg/agent/model_fallback.go
+++ b/pkg/agent/model_fallback.go
@@ -2,6 +2,7 @@ package agent
import (
"context"
+ "errors"
"fmt"
"time"
@@ -58,7 +59,7 @@ type FallbackStats struct {
// NewModelFallbackManager 创建模型降级管理器
func NewModelFallbackManager(fallbacks []*ModelFallback, deps *Dependencies) (*ModelFallbackManager, error) {
if len(fallbacks) == 0 {
- return nil, fmt.Errorf("at least one model fallback is required")
+ return nil, errors.New("at least one model fallback is required")
}
// 按优先级排序
@@ -66,8 +67,8 @@ func NewModelFallbackManager(fallbacks []*ModelFallback, deps *Dependencies) (*M
copy(sortedFallbacks, fallbacks)
// 简单的冒泡排序(因为通常模型数量不多)
- for i := 0; i < len(sortedFallbacks)-1; i++ {
- for j := 0; j < len(sortedFallbacks)-i-1; j++ {
+ for i := range len(sortedFallbacks) - 1 {
+ for j := range len(sortedFallbacks) - i - 1 {
if sortedFallbacks[j].Priority > sortedFallbacks[j+1].Priority {
sortedFallbacks[j], sortedFallbacks[j+1] = sortedFallbacks[j+1], sortedFallbacks[j]
}
diff --git a/pkg/agent/model_fallback_test.go b/pkg/agent/model_fallback_test.go
index bf2f29e..9287995 100644
--- a/pkg/agent/model_fallback_test.go
+++ b/pkg/agent/model_fallback_test.go
@@ -450,11 +450,13 @@ func TestModelFallbackManager_Stream(t *testing.T) {
// 读取流
var content string
+ var contentSb453 strings.Builder
for chunk := range stream {
if chunk.Type == "text" {
- content += chunk.TextDelta
+ contentSb453.WriteString(chunk.TextDelta)
}
}
+ content += contentSb453.String()
if content != "mock stream from anthropic/claude-3" {
t.Errorf("Expected stream from claude-3, got: %s", content)
diff --git a/pkg/agent/plan_mode.go b/pkg/agent/plan_mode.go
index 55f430b..9580215 100644
--- a/pkg/agent/plan_mode.go
+++ b/pkg/agent/plan_mode.go
@@ -3,6 +3,7 @@ package agent
import (
"context"
"fmt"
+ "slices"
"strings"
"sync"
@@ -163,8 +164,7 @@ func (m *PlanModeManager) validateWriteCall(input map[string]any) (bool, string)
}
}
- return false, fmt.Sprintf("In Plan Mode, Write is only allowed to plan files. Allowed path: %s",
- m.state.PlanFilePath)
+ return false, "In Plan Mode, Write is only allowed to plan files. Allowed path: " + m.state.PlanFilePath
}
// validateTaskCall 验证 Task 工具调用
@@ -176,13 +176,11 @@ func (m *PlanModeManager) validateTaskCall(input map[string]any) (bool, string)
// Plan 模式下只允许 Explore 类型的子代理
allowedSubagents := []string{"Explore"}
- for _, allowed := range allowedSubagents {
- if subagentType == allowed {
- return true, ""
- }
+ if slices.Contains(allowedSubagents, subagentType) {
+ return true, ""
}
- return false, fmt.Sprintf("In Plan Mode, only Explore subagent is allowed. Got: %s", subagentType)
+ return false, "In Plan Mode, only Explore subagent is allowed. Got: " + subagentType
}
// getAllowedToolsList 获取允许的工具列表字符串
diff --git a/pkg/agent/processor.go b/pkg/agent/processor.go
index abd1312..7adff6f 100644
--- a/pkg/agent/processor.go
+++ b/pkg/agent/processor.go
@@ -3,6 +3,7 @@ package agent
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"strings"
"time"
@@ -27,7 +28,7 @@ func (a *Agent) processMessages(ctx context.Context) {
return // 已经在处理中
}
a.state = types.AgentStateWorking
- a.iterationCount = 0 // 重置迭代计数
+ a.iterationCount = 0 // 重置迭代计数
a.initialThinkingSent = false // 重置初始思考事件标志,允许新用户消息触发新的"任务规划"
initialMsgCount := len(a.messages)
procLog.Info(ctx, "agent state changed to working", map[string]any{"agent_id": a.id, "message_count": initialMsgCount})
@@ -349,14 +350,14 @@ func (a *Agent) executeTools(ctx context.Context, toolUses []*types.ToolUseBlock
procLog.Warn(ctx, "iteration limit reached, waiting for user confirmation", map[string]any{
"agent_id": a.id, "iteration": currentIter, "max": maxIter,
})
-
+
// 发送迭代限制事件,等待用户确认是否继续
a.eventBus.EmitControl(&types.ControlIterationLimitEvent{
CurrentIteration: currentIter,
MaxIteration: maxIter,
Message: fmt.Sprintf("已执行 %d 次迭代,达到安全上限。是否继续?", currentIter),
})
-
+
// 等待用户决策
select {
case decision := <-a.iterationContinueCh:
@@ -411,7 +412,7 @@ func (a *Agent) executeSingleTool(ctx context.Context, tu *types.ToolUseBlock) t
if a.planMode != nil && a.planMode.IsActive() {
allowed, reason := a.planMode.ValidateToolCall(tu.Name, tu.Input)
if !allowed {
- errorMsg := fmt.Sprintf("Plan Mode restriction: %s", reason)
+ errorMsg := "Plan Mode restriction: " + reason
a.eventBus.EmitProgress(&types.ProgressToolErrorEvent{
Call: types.ToolCallSnapshot{
ID: tu.ID,
@@ -429,7 +430,7 @@ func (a *Agent) executeSingleTool(ctx context.Context, tu *types.ToolUseBlock) t
}
}
- //权限检查
+ // 权限检查
if a.permissionInspector != nil {
call := &types.ToolCallSnapshot{
ID: tu.ID,
@@ -488,7 +489,7 @@ func (a *Agent) executeSingleTool(ctx context.Context, tu *types.ToolUseBlock) t
if decision != "approved" {
// 用户拒绝
- errorMsg := fmt.Sprintf("Permission rejected by user for tool: %s", tu.Name)
+ errorMsg := "Permission rejected by user for tool: " + tu.Name
return &types.ToolResultBlock{
ToolUseID: tu.ID,
Content: fmt.Sprintf(`{"ok":false,"error":"%s"}`, errorMsg),
@@ -501,7 +502,7 @@ func (a *Agent) executeSingleTool(ctx context.Context, tu *types.ToolUseBlock) t
a.mu.Lock()
delete(a.pendingPermissions, tu.ID)
a.mu.Unlock()
- errorMsg := "Permission request cancelled"
+ errorMsg := "Permission request canceled"
return &types.ToolResultBlock{
ToolUseID: tu.ID,
Content: fmt.Sprintf(`{"ok":false,"error":"%s"}`, errorMsg),
@@ -540,7 +541,7 @@ func (a *Agent) executeSingleTool(ctx context.Context, tu *types.ToolUseBlock) t
tool, ok := a.toolMap[tu.Name]
if !ok {
// 工具未找到
- errorMsg := fmt.Sprintf("tool not found: %s", tu.Name)
+ errorMsg := "tool not found: " + tu.Name
a.updateToolRecord(tu.ID, types.ToolCallStateFailed, errorMsg)
a.eventBus.EmitProgress(&types.ProgressToolErrorEvent{
Call: types.ToolCallSnapshot{
@@ -659,7 +660,7 @@ func (a *Agent) executeSingleTool(ctx context.Context, tu *types.ToolUseBlock) t
if status.Error != nil {
taskErr = status.Error
} else {
- taskErr = fmt.Errorf("task failed")
+ taskErr = errors.New("task failed")
}
execResult = &tools.ExecuteResult{
Success: false,
@@ -672,7 +673,7 @@ func (a *Agent) executeSingleTool(ctx context.Context, tu *types.ToolUseBlock) t
if status.State == tools.TaskStateCancelled {
a.eventBus.EmitProgress(&types.ProgressToolCancelledEvent{
Call: a.snapshotToolCall(tu.ID),
- Reason: "cancelled",
+ Reason: "canceled",
})
}
}
@@ -696,7 +697,7 @@ func (a *Agent) executeSingleTool(ctx context.Context, tu *types.ToolUseBlock) t
_ = lrTool.Cancel(context.Background(), taskID)
a.eventBus.EmitProgress(&types.ProgressToolCancelledEvent{
Call: a.snapshotToolCall(tu.ID),
- Reason: "cancelled",
+ Reason: "canceled",
})
goto longRunningDone
case <-ticker.C:
@@ -874,8 +875,8 @@ func (a *Agent) handleStreamResponse(ctx context.Context, stream <-chan provider
currentBlockIndex := -1
textBuffers := make(map[int]string)
inputJSONBuffers := make(map[int]string)
- reasoningStarted := false // 追踪是否已发送思考开始事件
- reasoningBuffer := "" // 累积思考内容
+ reasoningStarted := false // 追踪是否已发送思考开始事件
+ var reasoningBuffer strings.Builder // 累积思考内容
// 只在用户消息后的第一次 LLM 调用时发送初始的任务规划思考事件
// 使用 initialThinkingSent 标志而不是 iterationCount,因为:
@@ -917,7 +918,7 @@ func (a *Agent) handleStreamResponse(ctx context.Context, stream <-chan provider
procLog.Debug(ctx, "reasoning started", map[string]any{"step": a.stepCount})
}
// 累积并发送思考内容增量
- reasoningBuffer += content
+ reasoningBuffer.WriteString(content)
a.eventBus.EmitProgress(&types.ProgressThinkChunkEvent{
Step: a.stepCount,
Stage: types.ThinkingStageReasoning,
@@ -1042,7 +1043,7 @@ func (a *Agent) handleStreamResponse(ctx context.Context, stream <-chan provider
thinking, _ := delta["thinking"].(string)
if thinking != "" {
// 累积思考内容
- reasoningBuffer += thinking
+ reasoningBuffer.WriteString(thinking)
// 发送思考增量事件
a.eventBus.EmitProgress(&types.ProgressThinkChunkEvent{
Step: a.stepCount,
@@ -1213,8 +1214,8 @@ func (a *Agent) handleStreamResponse(ctx context.Context, stream <-chan provider
"raw_length": len(jsonStr),
})
tu.Input = map[string]any{
- "__parse_error__": true,
- "__error_message__": fmt.Sprintf("工具参数解析失败,流式响应可能被截断。原始数据: %s", jsonStr),
+ "__parse_error__": true,
+ "__error_message__": "工具参数解析失败,流式响应可能被截断。原始数据: " + jsonStr,
}
}
} else {
@@ -1222,7 +1223,7 @@ func (a *Agent) handleStreamResponse(ctx context.Context, stream <-chan provider
if len(tu.Input) == 0 {
procLog.Warn(ctx, "empty input buffer for tool block", map[string]any{"block": i, "tool": tu.Name, "exists": exists, "json_str": jsonStr})
tu.Input = map[string]any{
- "__parse_error__": true,
+ "__parse_error__": true,
"__error_message__": "工具参数为空,流式响应可能未正确传输参数数据",
}
}
@@ -1236,7 +1237,7 @@ func (a *Agent) handleStreamResponse(ctx context.Context, stream <-chan provider
a.eventBus.EmitProgress(&types.ProgressThinkChunkEndEvent{
Step: a.stepCount,
})
- procLog.Debug(ctx, "reasoning ended", map[string]any{"step": a.stepCount, "total_length": len(reasoningBuffer)})
+ procLog.Debug(ctx, "reasoning ended", map[string]any{"step": a.stepCount, "total_length": len(reasoningBuffer.String())})
}
return types.Message{
@@ -1365,4 +1366,3 @@ func (a *Agent) trimMessagesInMemory(messages []types.Message, maxMessages int)
// 保留最近的 maxMessages 条消息
return messages[len(messages)-maxMessages:]
}
-
diff --git a/pkg/agent/prompt_builder.go b/pkg/agent/prompt_builder.go
index 7e432f6..3752caa 100644
--- a/pkg/agent/prompt_builder.go
+++ b/pkg/agent/prompt_builder.go
@@ -6,6 +6,7 @@ import (
"os"
"os/exec"
"runtime"
+ "slices"
"sort"
"strings"
"time"
@@ -106,13 +107,7 @@ func (pb *PromptBuilder) Build(ctx *PromptContext) (string, error) {
for _, module := range pb.modules {
// 检查是否被禁用
moduleName := module.Name()
- isDisabled := false
- for _, disabled := range disabledModules {
- if disabled == moduleName {
- isDisabled = true
- break
- }
- }
+ isDisabled := slices.Contains(disabledModules, moduleName)
if isDisabled {
continue
}
diff --git a/pkg/agent/prompt_compression_test.go b/pkg/agent/prompt_compression_test.go
index 5580218..3061ad7 100644
--- a/pkg/agent/prompt_compression_test.go
+++ b/pkg/agent/prompt_compression_test.go
@@ -247,9 +247,9 @@ func generateLongTestPrompt() string {
sb.WriteString("3. Use examples when helpful\n\n")
// 添加一些填充内容
- for i := 0; i < 10; i++ {
+ for i := range 10 {
sb.WriteString("## Section ")
- sb.WriteString(string(rune('A' + i)))
+ sb.WriteRune(rune('A' + i))
sb.WriteString("\n")
sb.WriteString("This is some filler content for testing purposes. ")
sb.WriteString("It helps verify that compression works correctly ")
diff --git a/pkg/agent/prompt_compressor_llm.go b/pkg/agent/prompt_compressor_llm.go
index 8e32811..8eaadde 100644
--- a/pkg/agent/prompt_compressor_llm.go
+++ b/pkg/agent/prompt_compressor_llm.go
@@ -2,6 +2,7 @@ package agent
import (
"context"
+ "errors"
"fmt"
"strings"
@@ -225,12 +226,12 @@ Please output the compressed paragraph directly:`, targetLength)
func (c *LLMPromptCompressor) validateCompression(original, compressed string, preserveSections []string) error {
// 检查压缩结果不为空
if strings.TrimSpace(compressed) == "" {
- return fmt.Errorf("compressed result is empty")
+ return errors.New("compressed result is empty")
}
// 检查压缩结果不能比原始内容更长
if len(compressed) > len(original) {
- return fmt.Errorf("compressed result is longer than original")
+ return errors.New("compressed result is longer than original")
}
// 检查必须保留的段落是否存在
diff --git a/pkg/agent/prompt_modules.go b/pkg/agent/prompt_modules.go
index 48d5037..2791bde 100644
--- a/pkg/agent/prompt_modules.go
+++ b/pkg/agent/prompt_modules.go
@@ -2,6 +2,7 @@ package agent
import (
"fmt"
+ "slices"
"sort"
"strings"
@@ -32,13 +33,13 @@ func (m *EnvironmentModule) Build(ctx *PromptContext) (string, error) {
var lines []string
lines = append(lines, "## Environment Information")
lines = append(lines, "")
- lines = append(lines, fmt.Sprintf("- Working Directory: %s", env.WorkingDir))
- lines = append(lines, fmt.Sprintf("- Platform: %s", env.Platform))
- lines = append(lines, fmt.Sprintf("- Date: %s", env.Date.Format("2006-01-02")))
+ lines = append(lines, "- Working Directory: "+env.WorkingDir)
+ lines = append(lines, "- Platform: "+env.Platform)
+ lines = append(lines, "- Date: "+env.Date.Format("2006-01-02"))
// 精简 Git 信息,只保留关键内容以减少 token 消耗
if env.GitRepo != nil && env.GitRepo.IsRepo {
- lines = append(lines, fmt.Sprintf("- Git Branch: %s", env.GitRepo.CurrentBranch))
+ lines = append(lines, "- Git Branch: "+env.GitRepo.CurrentBranch)
// 不再输出 git status 和 recent commits,这些可以通过工具获取
}
@@ -120,12 +121,12 @@ func (m *SandboxModule) Build(ctx *PromptContext) (string, error) {
lines = append(lines, "## Sandbox Environment")
lines = append(lines, "")
lines = append(lines, fmt.Sprintf("- Type: %s", sb.Kind))
- lines = append(lines, fmt.Sprintf("- Working Directory: %s", sb.WorkDir))
+ lines = append(lines, "- Working Directory: "+sb.WorkDir)
if len(sb.AllowPaths) > 0 {
lines = append(lines, "- Allowed Paths:")
for _, path := range sb.AllowPaths {
- lines = append(lines, fmt.Sprintf(" - %s", path))
+ lines = append(lines, " - "+path)
}
}
@@ -257,14 +258,14 @@ func (m *CollaborationModule) Build(ctx *PromptContext) (string, error) {
var lines []string
lines = append(lines, "## Multi-Agent Collaboration")
lines = append(lines, "")
- lines = append(lines, fmt.Sprintf("You are working in a collaborative room: %s", m.RoomInfo.RoomID))
+ lines = append(lines, "You are working in a collaborative room: "+m.RoomInfo.RoomID)
lines = append(lines, fmt.Sprintf("Total members: %d", m.RoomInfo.MemberCount))
if len(m.RoomInfo.Members) > 0 {
lines = append(lines, "")
lines = append(lines, "Room members:")
for _, member := range m.RoomInfo.Members {
- lines = append(lines, fmt.Sprintf("- %s", member))
+ lines = append(lines, "- "+member)
}
}
@@ -303,15 +304,15 @@ func (m *WorkflowModule) Build(ctx *PromptContext) (string, error) {
var lines []string
lines = append(lines, "## Workflow Context")
lines = append(lines, "")
- lines = append(lines, fmt.Sprintf("Workflow ID: %s", info.WorkflowID))
+ lines = append(lines, "Workflow ID: "+info.WorkflowID)
lines = append(lines, fmt.Sprintf("Current Step: %s (Step %d of %d)", info.CurrentStep, info.StepIndex+1, info.TotalSteps))
if info.PreviousStep != "" {
- lines = append(lines, fmt.Sprintf("Previous Step: %s", info.PreviousStep))
+ lines = append(lines, "Previous Step: "+info.PreviousStep)
}
if info.NextStep != "" {
- lines = append(lines, fmt.Sprintf("Next Step: %s", info.NextStep))
+ lines = append(lines, "Next Step: "+info.NextStep)
}
lines = append(lines, "")
@@ -331,7 +332,7 @@ func (m *CustomInstructionsModule) Condition(ctx *PromptContext) bool {
return m.Instructions != ""
}
func (m *CustomInstructionsModule) Build(ctx *PromptContext) (string, error) {
- return fmt.Sprintf("## Custom Instructions\n\n%s", m.Instructions), nil
+ return "## Custom Instructions\n\n" + m.Instructions, nil
}
// CapabilitiesModule Agent 能力说明模块
@@ -378,7 +379,7 @@ func (m *CapabilitiesModule) Build(ctx *PromptContext) (string, error) {
lines = append(lines, "")
lines = append(lines, "You can:")
for _, cap := range capabilities {
- lines = append(lines, fmt.Sprintf("- %s", cap))
+ lines = append(lines, "- "+cap)
}
return strings.Join(lines, "\n"), nil
@@ -438,7 +439,7 @@ func (m *ContextWindowModule) Build(ctx *PromptContext) (string, error) {
lines = append(lines, fmt.Sprintf("Maximum context tokens: %d", m.MaxTokens))
if m.Strategy != "" {
- lines = append(lines, fmt.Sprintf("Compression strategy: %s", m.Strategy))
+ lines = append(lines, "Compression strategy: "+m.Strategy)
}
lines = append(lines, "")
@@ -600,10 +601,5 @@ CRITICAL: Follow these git safety rules:
// 辅助函数
func contains(slice []string, item string) bool {
- for _, s := range slice {
- if s == item {
- return true
- }
- }
- return false
+ return slices.Contains(slice, item)
}
diff --git a/pkg/agent/prompt_optimizer.go b/pkg/agent/prompt_optimizer.go
index 9c70d83..d9d3c06 100644
--- a/pkg/agent/prompt_optimizer.go
+++ b/pkg/agent/prompt_optimizer.go
@@ -2,6 +2,7 @@ package agent
import (
"context"
+ "errors"
"fmt"
"strings"
@@ -202,7 +203,7 @@ func (c *EnhancedPromptCompressor) compressSimple(prompt string, opts *CompressO
// compressLLM LLM 驱动的压缩
func (c *EnhancedPromptCompressor) compressLLM(ctx context.Context, prompt string, opts *CompressOptions) (string, error) {
if c.llmCompressor == nil {
- return prompt, fmt.Errorf("LLM compressor not available")
+ return prompt, errors.New("LLM compressor not available")
}
targetLen := opts.TargetLength
@@ -283,7 +284,7 @@ func (c *EnhancedPromptCompressor) scoreSections(sections []string, preserveSect
}
// 按评分排序(高分在前)
- for i := 0; i < len(result)-1; i++ {
+ for i := range len(result) - 1 {
for j := i + 1; j < len(result); j++ {
if result[j].Score > result[i].Score {
result[i], result[j] = result[j], result[i]
@@ -544,8 +545,8 @@ func AnalyzePrompt(prompt string) *PromptStats {
}
// 统计段落数(以 ## 开头的行)
- lines := strings.Split(prompt, "\n")
- for _, line := range lines {
+ lines := strings.SplitSeq(prompt, "\n")
+ for line := range lines {
if strings.HasPrefix(strings.TrimSpace(line), "##") {
stats.SectionCount++
}
diff --git a/pkg/agent/remote_agent.go b/pkg/agent/remote_agent.go
index f38c8a8..d9dcabb 100644
--- a/pkg/agent/remote_agent.go
+++ b/pkg/agent/remote_agent.go
@@ -2,7 +2,9 @@ package agent
import (
"context"
+ "errors"
"fmt"
+ "maps"
"sync"
"time"
@@ -75,7 +77,7 @@ func (r *RemoteAgent) Status() *types.AgentStatus {
// 这是 RemoteAgent 的核心方法,用于接收来自远程进程的事件
func (r *RemoteAgent) PushEvent(envelope types.AgentEventEnvelope) error {
if envelope.Event == nil {
- return fmt.Errorf("event is nil")
+ return errors.New("event is nil")
}
// 根据事件类型推送到对应的通道
@@ -201,9 +203,7 @@ func (r *RemoteAgent) GetMetadata() map[string]any {
defer r.mu.RUnlock()
result := make(map[string]any)
- for k, v := range r.metadata {
- result[k] = v
- }
+ maps.Copy(result, r.metadata)
return result
}
@@ -222,22 +222,22 @@ func (r *RemoteAgent) SetMetadata(key string, value any) {
// Send 不支持 - RemoteAgent 不执行实际的 LLM 调用
func (r *RemoteAgent) Send(ctx context.Context, text string) error {
- return fmt.Errorf("RemoteAgent does not support Send operation")
+ return errors.New("RemoteAgent does not support Send operation")
}
// Chat 不支持 - RemoteAgent 不执行实际的 LLM 调用
func (r *RemoteAgent) Chat(ctx context.Context, text string) (*types.CompleteResult, error) {
- return nil, fmt.Errorf("RemoteAgent does not support Chat operation")
+ return nil, errors.New("RemoteAgent does not support Chat operation")
}
// ExecuteToolDirect 不支持 - RemoteAgent 不执行工具
func (r *RemoteAgent) ExecuteToolDirect(ctx context.Context, toolName string, input map[string]any) (any, error) {
- return nil, fmt.Errorf("RemoteAgent does not support ExecuteToolDirect operation")
+ return nil, errors.New("RemoteAgent does not support ExecuteToolDirect operation")
}
// RespondToPermissionRequest 不支持 - 权限管理在远程端
func (r *RemoteAgent) RespondToPermissionRequest(callID string, approved bool) error {
- return fmt.Errorf("RemoteAgent does not support RespondToPermissionRequest operation")
+ return errors.New("RemoteAgent does not support RespondToPermissionRequest operation")
}
// HasPendingPermission 不支持 - 权限管理在远程端
diff --git a/pkg/agent/streaming.go b/pkg/agent/streaming.go
index 86f4eed..d60405b 100644
--- a/pkg/agent/streaming.go
+++ b/pkg/agent/streaming.go
@@ -124,7 +124,7 @@ func (a *Agent) Stream(ctx context.Context, message string, opts ...Option) *str
Actions: session.EventActions{},
}
if writer.Send(taskPlanEvent, nil) {
- streamLog.Debug(ctx, "client cancelled stream during task planning", nil)
+ streamLog.Debug(ctx, "client canceled stream during task planning", nil)
return
}
streamLog.Debug(ctx, "sent task planning event", nil)
@@ -194,7 +194,7 @@ func StreamFirst(reader *stream.Reader[*session.Event]) (*session.Event, error)
event, err := reader.Recv()
if err != nil {
if errors.Is(err, io.EOF) {
- return nil, fmt.Errorf("no events in stream")
+ return nil, errors.New("no events in stream")
}
return nil, err
}
@@ -224,7 +224,7 @@ func StreamLast(reader *stream.Reader[*session.Event]) (*session.Event, error) {
return lastEvent, lastErr
}
if lastEvent == nil {
- return nil, fmt.Errorf("no events in stream")
+ return nil, errors.New("no events in stream")
}
return lastEvent, nil
}
@@ -266,7 +266,7 @@ type Option func(*streamConfig)
// validateMessage 验证消息
func (a *Agent) validateMessage(message string) error {
if message == "" {
- return fmt.Errorf("message cannot be empty")
+ return errors.New("message cannot be empty")
}
return nil
}
@@ -470,7 +470,7 @@ func (a *Agent) runModelStepStreaming(ctx context.Context, writer *stream.Writer
// 立即发送事件到流
if writer.Send(event, nil) {
- streamLog.Debug(ctx, "client cancelled stream during text streaming", nil)
+ streamLog.Debug(ctx, "client canceled stream during text streaming", nil)
return true, nil
}
}
@@ -491,7 +491,7 @@ func (a *Agent) runModelStepStreaming(ctx context.Context, writer *stream.Writer
// 立即发送事件到流
if writer.Send(event, nil) {
- streamLog.Debug(ctx, "client cancelled stream during reasoning streaming", nil)
+ streamLog.Debug(ctx, "client canceled stream during reasoning streaming", nil)
return true, nil
}
}
@@ -520,7 +520,7 @@ func (a *Agent) runModelStepStreaming(ctx context.Context, writer *stream.Writer
// 立即发送事件到流
if writer.Send(event, nil) {
- streamLog.Debug(ctx, "client cancelled stream during text streaming", nil)
+ streamLog.Debug(ctx, "client canceled stream during text streaming", nil)
return true, nil
}
}
@@ -630,7 +630,7 @@ func (a *Agent) runModelStepStreaming(ctx context.Context, writer *stream.Writer
// 8. 发送事件到流
if writer.Send(event, nil) {
- streamLog.Debug(ctx, "client cancelled stream during event yield", nil)
+ streamLog.Debug(ctx, "client canceled stream during event yield", nil)
return true, nil
}
diff --git a/pkg/agent/subagent.go b/pkg/agent/subagent.go
index 23730f1..1cf714a 100644
--- a/pkg/agent/subagent.go
+++ b/pkg/agent/subagent.go
@@ -3,6 +3,8 @@ package agent
import (
"context"
"fmt"
+ "maps"
+ "strings"
"sync"
"time"
@@ -37,7 +39,7 @@ type SubAgentHandle struct {
Agent *Agent
Request *types.SubAgentRequest
StartTime time.Time
- Status string // "running", "completed", "failed", "cancelled"
+ Status string // "running", "completed", "failed", "canceled"
Result *types.SubAgentResult
CancelFunc context.CancelFunc
ProgressChan chan *types.SubAgentProgressEvent
@@ -452,7 +454,7 @@ func (m *SubAgentManager) Cancel(taskID string) error {
}
handle.CancelFunc()
- handle.Status = "cancelled"
+ handle.Status = "canceled"
return nil
}
@@ -505,16 +507,14 @@ func (m *SubAgentManager) buildAgentConfig(spec *types.SubAgentSpec, req *types.
// 构建元数据
metadata := make(map[string]any)
if req.Context != nil {
- for k, v := range req.Context {
- metadata[k] = v
- }
+ maps.Copy(metadata, req.Context)
}
metadata["subagent_task_id"] = taskID
metadata["subagent_type"] = spec.Name
metadata["parent_agent_id"] = req.ParentAgentID
config := &types.AgentConfig{
- AgentID: fmt.Sprintf("subagent-%s", taskID[:8]),
+ AgentID: "subagent-" + taskID[:8],
TemplateID: templateID,
Metadata: metadata,
}
@@ -541,9 +541,11 @@ func (m *SubAgentManager) buildTaskMessage(req *types.SubAgentRequest, spec *typ
// 添加上下文信息
if len(req.Context) > 0 {
msg += "\n## Context\n\n"
+ var msgSb544 strings.Builder
for k, v := range req.Context {
- msg += fmt.Sprintf("- **%s**: %v\n", k, v)
+ msgSb544.WriteString(fmt.Sprintf("- **%s**: %v\n", k, v))
}
+ msg += msgSb544.String()
}
// 添加约束提醒
@@ -633,11 +635,11 @@ func (m *SubAgentManager) convertToProgressEvent(handle *SubAgentHandle, env typ
progress = 30
case *types.ProgressToolStartEvent:
phase = "tool_use"
- message = fmt.Sprintf("Using tool: %s", e.Call.Name)
+ message = "Using tool: " + e.Call.Name
progress = 50
case *types.ProgressToolEndEvent:
phase = "tool_use"
- message = fmt.Sprintf("Tool completed: %s", e.Call.Name)
+ message = "Tool completed: " + e.Call.Name
progress = 70
default:
return nil
diff --git a/pkg/agent/tool_manager.go b/pkg/agent/tool_manager.go
index 32f7090..da96be0 100644
--- a/pkg/agent/tool_manager.go
+++ b/pkg/agent/tool_manager.go
@@ -3,6 +3,8 @@ package agent
import (
"context"
"fmt"
+ "maps"
+ "slices"
"sync"
"github.com/astercloud/aster/pkg/logging"
@@ -99,12 +101,7 @@ func (tm *ToolManager) Initialize(toolNames []string, activeByDefault bool) erro
// isCoreToolLocked 检查是否是核心工具(需要持有锁)
func (tm *ToolManager) isCoreToolLocked(name string) bool {
- for _, core := range tm.coreTools {
- if core == name {
- return true
- }
- }
- return false
+ return slices.Contains(tm.coreTools, name)
}
// IsCoreTools 检查是否是核心工具
@@ -120,9 +117,7 @@ func (tm *ToolManager) GetActiveTools() map[string]tools.Tool {
defer tm.mu.RUnlock()
result := make(map[string]tools.Tool, len(tm.activeTools))
- for k, v := range tm.activeTools {
- result[k] = v
- }
+ maps.Copy(result, tm.activeTools)
return result
}
diff --git a/pkg/agent/workflow/loop.go b/pkg/agent/workflow/loop.go
index af72525..9856ff3 100644
--- a/pkg/agent/workflow/loop.go
+++ b/pkg/agent/workflow/loop.go
@@ -47,15 +47,15 @@ type LoopConfig struct {
// NewLoopAgent 创建循环 Agent
func NewLoopAgent(cfg LoopConfig) (*LoopAgent, error) {
if cfg.Name == "" {
- return nil, fmt.Errorf("agent name is required")
+ return nil, errors.New("agent name is required")
}
if len(cfg.SubAgents) == 0 {
- return nil, fmt.Errorf("at least one sub-agent is required")
+ return nil, errors.New("at least one sub-agent is required")
}
if cfg.MaxIterations == 0 && cfg.StopCondition == nil {
- return nil, fmt.Errorf("either MaxIterations or StopCondition must be specified")
+ return nil, errors.New("either MaxIterations or StopCondition must be specified")
}
// 默认停止条件:检查 Escalate 标志
diff --git a/pkg/agent/workflow/parallel.go b/pkg/agent/workflow/parallel.go
index 9c4baaf..8efcc17 100644
--- a/pkg/agent/workflow/parallel.go
+++ b/pkg/agent/workflow/parallel.go
@@ -47,11 +47,11 @@ type ParallelConfig struct {
// NewParallelAgent 创建并行 Agent
func NewParallelAgent(cfg ParallelConfig) (*ParallelAgent, error) {
if cfg.Name == "" {
- return nil, fmt.Errorf("agent name is required")
+ return nil, errors.New("agent name is required")
}
if len(cfg.SubAgents) == 0 {
- return nil, fmt.Errorf("at least one sub-agent is required")
+ return nil, errors.New("at least one sub-agent is required")
}
return &ParallelAgent{
diff --git a/pkg/agent/workflow/sequential.go b/pkg/agent/workflow/sequential.go
index b8751f8..5638c90 100644
--- a/pkg/agent/workflow/sequential.go
+++ b/pkg/agent/workflow/sequential.go
@@ -38,11 +38,11 @@ type SequentialConfig struct {
// NewSequentialAgent 创建顺序 Agent
func NewSequentialAgent(cfg SequentialConfig) (*SequentialAgent, error) {
if cfg.Name == "" {
- return nil, fmt.Errorf("agent name is required")
+ return nil, errors.New("agent name is required")
}
if len(cfg.SubAgents) == 0 {
- return nil, fmt.Errorf("at least one sub-agent is required")
+ return nil, errors.New("at least one sub-agent is required")
}
// SequentialAgent 是 LoopAgent 迭代 1 次的特例
diff --git a/pkg/asteros/asteros.go b/pkg/asteros/asteros.go
index cff7d14..3b6480f 100644
--- a/pkg/asteros/asteros.go
+++ b/pkg/asteros/asteros.go
@@ -364,7 +364,7 @@ func corsMiddleware() gin.HandlerFunc {
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
- if c.Request.Method == "OPTIONS" {
+ if c.Request.Method == http.MethodOptions {
c.AbortWithStatus(204)
return
}
diff --git a/pkg/asteros/interfaces/a2a.go b/pkg/asteros/interfaces/a2a.go
index e41dbac..fb9bb1e 100644
--- a/pkg/asteros/interfaces/a2a.go
+++ b/pkg/asteros/interfaces/a2a.go
@@ -28,6 +28,7 @@ type A2AInterfaceOptions struct {
// 支持消息传递、状态查询和协作。
type A2AInterface struct {
*asteros.BaseInterface
+
opts *A2AInterfaceOptions
os *asteros.AsterOS
diff --git a/pkg/asteros/interfaces/agui.go b/pkg/asteros/interfaces/agui.go
index 280d366..2e97b23 100644
--- a/pkg/asteros/interfaces/agui.go
+++ b/pkg/asteros/interfaces/agui.go
@@ -2,6 +2,7 @@ package interfaces
import (
"context"
+ "errors"
"fmt"
"sync"
"time"
@@ -35,6 +36,7 @@ type AGUIInterfaceOptions struct {
// 提供可视化的 Agent 管理和监控界面。
type AGUIInterface struct {
*asteros.BaseInterface
+
opts *AGUIInterfaceOptions
os *asteros.AsterOS
@@ -201,7 +203,7 @@ func (i *AGUIInterface) syncLoop(ctx context.Context) {
// syncAll 同步所有资源
func (i *AGUIInterface) syncAll() error {
if !i.isConnected() {
- return fmt.Errorf("not connected to control plane")
+ return errors.New("not connected to control plane")
}
// TODO: 实现实际的同步逻辑
diff --git a/pkg/asteros/interfaces/http.go b/pkg/asteros/interfaces/http.go
index 4fe607e..3a19d5d 100644
--- a/pkg/asteros/interfaces/http.go
+++ b/pkg/asteros/interfaces/http.go
@@ -27,6 +27,7 @@ type HTTPInterfaceOptions struct {
// 提供标准的 RESTful API 端点。
type HTTPInterface struct {
*asteros.BaseInterface
+
opts *HTTPInterfaceOptions
os *asteros.AsterOS
}
diff --git a/pkg/backends/state.go b/pkg/backends/state.go
index 9eb6909..5d3a0af 100644
--- a/pkg/backends/state.go
+++ b/pkg/backends/state.go
@@ -3,6 +3,7 @@ package backends
import (
"context"
"fmt"
+ "maps"
"path/filepath"
"regexp"
"strings"
@@ -181,7 +182,7 @@ func (b *StateBackend) Edit(ctx context.Context, path, oldStr, newStr string, re
data, exists := b.files[path]
if !exists {
return &EditResult{
- Error: fmt.Sprintf("file not found: %s", path),
+ Error: "file not found: " + path,
Path: path,
}, nil
}
@@ -334,9 +335,7 @@ func (b *StateBackend) GetFiles() map[string]*FileData {
defer b.mu.RUnlock()
result := make(map[string]*FileData, len(b.files))
- for k, v := range b.files {
- result[k] = v
- }
+ maps.Copy(result, b.files)
return result
}
@@ -346,7 +345,5 @@ func (b *StateBackend) LoadFiles(files map[string]*FileData) {
defer b.mu.Unlock()
b.files = make(map[string]*FileData, len(files))
- for k, v := range files {
- b.files[k] = v
- }
+ maps.Copy(b.files, files)
}
diff --git a/pkg/backends/store_backend.go b/pkg/backends/store_backend.go
index 7ea55ce..2867148 100644
--- a/pkg/backends/store_backend.go
+++ b/pkg/backends/store_backend.go
@@ -26,7 +26,7 @@ func NewStoreBackend(s store.Store, agentID string) *StoreBackend {
return &StoreBackend{
store: s,
agentID: agentID,
- namespace: fmt.Sprintf("files:%s", agentID),
+ namespace: "files:" + agentID,
}
}
@@ -170,7 +170,7 @@ func (b *StoreBackend) Edit(ctx context.Context, path, oldStr, newStr string, re
data, err := b.loadFileData(ctx, path)
if err != nil {
return &EditResult{
- Error: fmt.Sprintf("file not found: %s", path),
+ Error: "file not found: " + path,
Path: path,
}, nil
}
@@ -359,7 +359,7 @@ func (b *StoreBackend) saveFileData(ctx context.Context, path string, data *File
}
// 更新文件元信息索引
- metaKey := fmt.Sprintf("%s:meta", b.namespace)
+ metaKey := b.namespace + ":meta"
allMeta, _ := b.loadAllFileMeta(ctx)
if allMeta == nil {
allMeta = make(map[string]FileMeta)
@@ -381,7 +381,7 @@ func (b *StoreBackend) saveFileData(ctx context.Context, path string, data *File
// loadAllFileMeta 加载所有文件元信息
func (b *StoreBackend) loadAllFileMeta(ctx context.Context) (map[string]FileMeta, error) {
- metaKey := fmt.Sprintf("%s:meta", b.namespace)
+ metaKey := b.namespace + ":meta"
dataInterface, err := b.store.LoadTodos(ctx, metaKey)
if err != nil {
diff --git a/pkg/backends/utils_test.go b/pkg/backends/utils_test.go
index bf4878a..888793b 100644
--- a/pkg/backends/utils_test.go
+++ b/pkg/backends/utils_test.go
@@ -399,8 +399,8 @@ func TestIsTextFile(t *testing.T) {
// BenchmarkFormatContentWithLineNumbers 性能测试
func BenchmarkFormatContentWithLineNumbers(b *testing.B) {
content := strings.Repeat("line of text\n", 1000)
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
FormatContentWithLineNumbers(content, 1)
}
}
@@ -408,8 +408,8 @@ func BenchmarkFormatContentWithLineNumbers(b *testing.B) {
// BenchmarkSanitizeToolCallID 性能测试
func BenchmarkSanitizeToolCallID(b *testing.B) {
id := "../../../dangerous/path/to/file.txt"
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+
+ for b.Loop() {
SanitizeToolCallID(id)
}
}
diff --git a/pkg/commands/executor.go b/pkg/commands/executor.go
index 667d11c..929380e 100644
--- a/pkg/commands/executor.go
+++ b/pkg/commands/executor.go
@@ -2,6 +2,7 @@ package commands
import (
"context"
+ "errors"
"fmt"
"strings"
@@ -120,11 +121,11 @@ func (e *Executor) CheckCapabilities(cmd *CommandDefinition) error {
switch capability {
case "tool-calling":
if !e.capabilities.SupportToolCalling {
- return fmt.Errorf("command requires tool-calling support")
+ return errors.New("command requires tool-calling support")
}
case "system-prompt":
if !e.capabilities.SupportSystemPrompt {
- return fmt.Errorf("command requires system-prompt support")
+ return errors.New("command requires system-prompt support")
}
}
}
diff --git a/pkg/commands/loader.go b/pkg/commands/loader.go
index 686a13c..b8d828d 100644
--- a/pkg/commands/loader.go
+++ b/pkg/commands/loader.go
@@ -2,6 +2,7 @@ package commands
import (
"context"
+ "errors"
"fmt"
"os"
"path/filepath"
@@ -48,7 +49,7 @@ func (cl *CommandLoader) parse(name, content string) (*CommandDefinition, error)
// 格式: ---\nyaml\n---\nmarkdown
parts := strings.Split(content, "---")
if len(parts) < 3 {
- return nil, fmt.Errorf("invalid command format: missing YAML frontmatter")
+ return nil, errors.New("invalid command format: missing YAML frontmatter")
}
cmd := &CommandDefinition{
diff --git a/pkg/compression/service.go b/pkg/compression/service.go
index 5ce49da..828ab08 100644
--- a/pkg/compression/service.go
+++ b/pkg/compression/service.go
@@ -382,7 +382,7 @@ func (s *DefaultCompressionService) scoreSections(sections []string, preserveSec
}
// 按评分排序(高分在前)
- for i := 0; i < len(result)-1; i++ {
+ for i := range len(result) - 1 {
for j := i + 1; j < len(result); j++ {
if result[j].score > result[i].score {
result[i], result[j] = result[j], result[i]
diff --git a/pkg/compression/service_test.go b/pkg/compression/service_test.go
index 0c57c62..283559a 100644
--- a/pkg/compression/service_test.go
+++ b/pkg/compression/service_test.go
@@ -135,7 +135,7 @@ func TestDefaultCompressionService_Stats(t *testing.T) {
prompt := generateTestPrompt()
// 执行几次压缩
- for i := 0; i < 3; i++ {
+ for range 3 {
_, err := svc.CompressSystemPrompt(context.Background(), prompt, &CompressOptions{
Mode: ModeSimple,
Level: LevelModerate,
@@ -387,9 +387,9 @@ func generateTestPrompt() string {
sb.WriteString("3. Use examples when helpful\n\n")
// 添加一些填充内容
- for i := 0; i < 10; i++ {
+ for i := range 10 {
sb.WriteString("## Section ")
- sb.WriteString(string(rune('A' + i)))
+ sb.WriteRune(rune('A' + i))
sb.WriteString("\n")
sb.WriteString("This is some filler content for testing purposes. ")
sb.WriteString("It helps verify that compression works correctly ")
diff --git a/pkg/config/loader.go b/pkg/config/loader.go
index 3849b29..20b20bc 100644
--- a/pkg/config/loader.go
+++ b/pkg/config/loader.go
@@ -1,7 +1,9 @@
package config
import (
+ "errors"
"fmt"
+ "maps"
"os"
"regexp"
"strings"
@@ -174,7 +176,7 @@ func (l *Loader) expandVariables(content string) string {
if required && val == "" {
// 返回错误标记,后续验证会捕获
- return fmt.Sprintf("__ERROR__: %s", errorMsg)
+ return "__ERROR__: " + errorMsg
}
return defaultValue
@@ -219,12 +221,12 @@ func (l *Loader) expandVariables(content string) string {
func (l *Loader) validateAgentConfig(config *types.AgentConfig) error {
// 检查必需字段
if config.TemplateID == "" {
- return fmt.Errorf("template_id is required")
+ return errors.New("template_id is required")
}
// 检查是否有未展开的必需变量
if strings.Contains(fmt.Sprintf("%v", config), "__ERROR__:") {
- return fmt.Errorf("config contains unexpanded required variables")
+ return errors.New("config contains unexpanded required variables")
}
// 验证 ModelConfig
@@ -237,7 +239,7 @@ func (l *Loader) validateAgentConfig(config *types.AgentConfig) error {
// 验证 Multitenancy
if config.Multitenancy != nil && config.Multitenancy.Enabled {
if config.Multitenancy.OrgID == "" && config.Multitenancy.TenantID == "" {
- return fmt.Errorf("multitenancy enabled but neither org_id nor tenant_id specified")
+ return errors.New("multitenancy enabled but neither org_id nor tenant_id specified")
}
}
@@ -247,10 +249,10 @@ func (l *Loader) validateAgentConfig(config *types.AgentConfig) error {
// validateModelConfig 验证 ModelConfig
func (l *Loader) validateModelConfig(config *types.ModelConfig) error {
if config.Provider == "" {
- return fmt.Errorf("provider is required")
+ return errors.New("provider is required")
}
if config.Model == "" {
- return fmt.Errorf("model is required")
+ return errors.New("model is required")
}
return nil
}
@@ -314,9 +316,7 @@ func MergeConfigs(base *types.AgentConfig, overlays ...*types.AgentConfig) *type
if base.Metadata == nil {
base.Metadata = make(map[string]any)
}
- for k, v := range overlay.Metadata {
- base.Metadata[k] = v
- }
+ maps.Copy(base.Metadata, overlay.Metadata)
}
}
diff --git a/pkg/config/loader_test.go b/pkg/config/loader_test.go
index cb86856..c7bec27 100644
--- a/pkg/config/loader_test.go
+++ b/pkg/config/loader_test.go
@@ -577,10 +577,3 @@ func TestRequiredVariableError(t *testing.T) {
func containsErrorMarker(s string) bool {
return len(s) > 0 && s[:min(9, len(s))] == "__ERROR__"
}
-
-func min(a, b int) int {
- if a < b {
- return a
- }
- return b
-}
diff --git a/pkg/context/compression_strategy.go b/pkg/context/compression_strategy.go
index 9a4cf7f..4f06345 100644
--- a/pkg/context/compression_strategy.go
+++ b/pkg/context/compression_strategy.go
@@ -253,7 +253,7 @@ func (s *TokenBasedStrategy) Compress(
// 构建测试消息列表
testMessages := []Message{}
- for j := range len(messages) {
+ for j := range messages {
if testIndices[j] {
testMessages = append(testMessages, messages[j])
}
diff --git a/pkg/context/compression_strategy_test.go b/pkg/context/compression_strategy_test.go
index 0d5075a..1763e64 100644
--- a/pkg/context/compression_strategy_test.go
+++ b/pkg/context/compression_strategy_test.go
@@ -7,7 +7,7 @@ import (
func createTestMessages(count int) []Message {
messages := make([]Message, count)
- for i := 0; i < count; i++ {
+ for i := range count {
role := "user"
if i%2 == 0 {
role = "assistant"
@@ -446,8 +446,7 @@ func BenchmarkSlidingWindowStrategy_Compress(b *testing.B) {
messages := createTestMessages(100)
ctx := context.Background()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _ = strategy.Compress(ctx, messages, config)
}
}
@@ -459,8 +458,7 @@ func BenchmarkPriorityBasedStrategy_Compress(b *testing.B) {
messages := createTestMessages(100)
ctx := context.Background()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _ = strategy.Compress(ctx, messages, config)
}
}
@@ -472,8 +470,7 @@ func BenchmarkTokenBasedStrategy_Compress(b *testing.B) {
messages := createTestMessages(100)
ctx := context.Background()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _ = strategy.Compress(ctx, messages, config)
}
}
diff --git a/pkg/context/token_counter_test.go b/pkg/context/token_counter_test.go
index 963d5fd..86f8139 100644
--- a/pkg/context/token_counter_test.go
+++ b/pkg/context/token_counter_test.go
@@ -89,7 +89,7 @@ func TestSimpleTokenCounter_CountBatch(t *testing.T) {
}
// 验证非空字符串返回 > 0
- for i := 0; i < 3; i++ {
+ for i := range 3 {
if counts[i] <= 0 {
t.Errorf("CountBatch() for text[%d] = %v, want > 0", i, counts[i])
}
@@ -523,8 +523,7 @@ func BenchmarkSimpleTokenCounter_Count(b *testing.B) {
text := "This is a sample text for benchmarking token counting performance."
ctx := context.Background()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _ = counter.Count(ctx, text)
}
}
@@ -538,8 +537,7 @@ func BenchmarkSimpleTokenCounter_EstimateMessages(b *testing.B) {
}
ctx := context.Background()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _ = counter.EstimateMessages(ctx, messages)
}
}
diff --git a/pkg/context/window_manager.go b/pkg/context/window_manager.go
index e3e81de..e8087eb 100644
--- a/pkg/context/window_manager.go
+++ b/pkg/context/window_manager.go
@@ -2,6 +2,7 @@ package context
import (
"context"
+ "errors"
"fmt"
"sort"
"sync"
@@ -194,7 +195,7 @@ func (m *ContextWindowManager) Compress(ctx context.Context) error {
// compress 内部压缩方法(需要持有锁)
func (m *ContextWindowManager) compress(ctx context.Context) error {
if m.compressionStrategy == nil {
- return fmt.Errorf("compression strategy not configured")
+ return errors.New("compression strategy not configured")
}
beforeMessages := len(m.messages)
diff --git a/pkg/context/window_manager_test.go b/pkg/context/window_manager_test.go
index bb66e6d..c913684 100644
--- a/pkg/context/window_manager_test.go
+++ b/pkg/context/window_manager_test.go
@@ -226,7 +226,7 @@ func TestContextWindowManager_AutoCompress(t *testing.T) {
manager := NewContextWindowManager(config, counter, strategy)
// 添加多条消息,触发自动压缩
- for i := 0; i < 10; i++ {
+ for range 10 {
err := manager.AddMessage(context.Background(), Message{
Role: "user",
Content: "This is a test message that should trigger compression eventually.",
@@ -259,7 +259,7 @@ func TestContextWindowManager_ManualCompress(t *testing.T) {
manager := NewContextWindowManager(config, counter, strategy)
// 添加多条消息
- for i := 0; i < 10; i++ {
+ for range 10 {
err := manager.AddMessage(context.Background(), Message{
Role: "user",
Content: "Test message",
@@ -338,7 +338,7 @@ func TestContextWindowManager_Reset(t *testing.T) {
manager := NewContextWindowManager(config, counter, strategy)
// 添加消息并压缩
- for i := 0; i < 10; i++ {
+ for range 10 {
_ = manager.AddMessage(context.Background(), Message{
Role: "user",
Content: "Test",
diff --git a/pkg/core/pool.go b/pkg/core/pool.go
index 4cf751e..1b1c912 100644
--- a/pkg/core/pool.go
+++ b/pkg/core/pool.go
@@ -3,6 +3,7 @@ package core
import (
"context"
"fmt"
+ "maps"
"strings"
"sync"
@@ -202,9 +203,7 @@ func (p *Pool) ForEach(fn func(agentID string, ag *agent.Agent) error) error {
p.mu.RLock()
// 复制一份避免长时间持锁
agents := make(map[string]*agent.Agent, len(p.agents))
- for id, ag := range p.agents {
- agents[id] = ag
- }
+ maps.Copy(agents, p.agents)
p.mu.RUnlock()
for id, ag := range agents {
diff --git a/pkg/core/pool_test.go b/pkg/core/pool_test.go
index bdbc5da..06541c8 100644
--- a/pkg/core/pool_test.go
+++ b/pkg/core/pool_test.go
@@ -148,7 +148,7 @@ func TestPool_MaxCapacity(t *testing.T) {
ctx := context.Background()
// 创建 maxAgents 个 Agent
- for i := 0; i < maxAgents; i++ {
+ for i := range maxAgents {
config := createTestConfig("test-agent-" + string(rune('1'+i)))
_, err := pool.Create(ctx, config)
if err != nil {
@@ -312,7 +312,7 @@ func TestPool_ConcurrentAccess(t *testing.T) {
var wg sync.WaitGroup
// 并发创建 Agent
- for i := 0; i < concurrency; i++ {
+ for i := range concurrency {
wg.Add(1)
go func(idx int) {
defer wg.Done()
@@ -334,7 +334,7 @@ func TestPool_ConcurrentAccess(t *testing.T) {
}
// 并发读取 Agent
- for i := 0; i < concurrency; i++ {
+ for i := range concurrency {
wg.Add(1)
go func(idx int) {
defer wg.Done()
@@ -361,7 +361,7 @@ func TestPool_Shutdown(t *testing.T) {
ctx := context.Background()
// 创建多个 Agent
- for i := 0; i < 5; i++ {
+ for i := range 5 {
config := createTestConfig("test-agent-" + string(rune('1'+i)))
_, err := pool.Create(ctx, config)
if err != nil {
@@ -398,7 +398,7 @@ func TestPool_ForEach(t *testing.T) {
// 创建 Agent
agentCount := 5
- for i := 0; i < agentCount; i++ {
+ for i := range agentCount {
config := createTestConfig("test-agent-" + string(rune('1'+i)))
_, err := pool.Create(ctx, config)
if err != nil {
diff --git a/pkg/core/room.go b/pkg/core/room.go
index 5052f1b..a4c273f 100644
--- a/pkg/core/room.go
+++ b/pkg/core/room.go
@@ -3,6 +3,7 @@ package core
import (
"context"
"fmt"
+ "maps"
"regexp"
"sync"
"time"
@@ -164,9 +165,7 @@ func (r *Room) Broadcast(ctx context.Context, text string) error {
// 复制成员列表
targets := make(map[string]string, len(r.members))
- for name, agentID := range r.members {
- targets[name] = agentID
- }
+ maps.Copy(targets, r.members)
r.mu.RUnlock()
diff --git a/pkg/core/scheduler.go b/pkg/core/scheduler.go
index c44149e..a9eb934 100644
--- a/pkg/core/scheduler.go
+++ b/pkg/core/scheduler.go
@@ -133,15 +133,15 @@ func (s *Scheduler) OnStep(callback StepCallback) func() {
s.stepListeners = append(s.stepListeners, callback)
// 返回取消函数
- cancelled := false
+ canceled := false
return func() {
s.mu.Lock()
defer s.mu.Unlock()
- if cancelled {
+ if canceled {
return
}
- cancelled = true
+ canceled = true
// 从列表中移除
if index < len(s.stepListeners) {
diff --git a/pkg/culture/culture.go b/pkg/culture/culture.go
index 97607d8..9bbcc3c 100644
--- a/pkg/culture/culture.go
+++ b/pkg/culture/culture.go
@@ -2,7 +2,9 @@ package culture
import (
"encoding/json"
+ "errors"
"fmt"
+ "maps"
"sync"
"time"
@@ -731,9 +733,7 @@ func (cc *CultureContext) Clone() *CultureContext {
copy(clone.Resources, cc.Resources)
copy(clone.Stakeholders, cc.Stakeholders)
- for k, v := range cc.Attributes {
- clone.Attributes[k] = v
- }
+ maps.Copy(clone.Attributes, cc.Attributes)
return clone
}
@@ -976,7 +976,7 @@ const (
PlanStatusApproved PlanStatus = "approved" // 已批准
PlanStatusInProgress PlanStatus = "in_progress" // 进行中
PlanStatusCompleted PlanStatus = "completed" // 已完成
- PlanStatusCancelled PlanStatus = "cancelled" // 已取消
+ PlanStatusCancelled PlanStatus = "canceled" // 已取消
PlanStatusSuspended PlanStatus = "suspended" // 已暂停
)
@@ -1405,11 +1405,11 @@ func (c *Culture) Validate() error {
defer c.mu.RUnlock()
if c.ID == "" {
- return fmt.Errorf("culture ID is required")
+ return errors.New("culture ID is required")
}
if c.Name == "" {
- return fmt.Errorf("culture name is required")
+ return errors.New("culture name is required")
}
// 验证维度值范围
@@ -1468,9 +1468,7 @@ func (c *Culture) Clone() *Culture {
copy(clone.Tags, c.Tags)
// 复制map
- for k, v := range c.Metadata {
- clone.Metadata[k] = v
- }
+ maps.Copy(clone.Metadata, c.Metadata)
if c.Context != nil {
clone.Context = c.Context.Clone()
diff --git a/pkg/dashboard/aggregator.go b/pkg/dashboard/aggregator.go
index 5e786e6..6c3496e 100644
--- a/pkg/dashboard/aggregator.go
+++ b/pkg/dashboard/aggregator.go
@@ -2,6 +2,7 @@ package dashboard
import (
"context"
+ "slices"
"sort"
"sync"
"time"
@@ -19,11 +20,11 @@ type Aggregator struct {
traceBuilder *TraceBuilder
// 缓存
- mu sync.RWMutex
- tokenCache map[string]*TokenUsageStats // key: period
- traceCache map[string]*TraceDetail // key: traceID
- cacheTTL time.Duration
- lastCacheTime time.Time
+ mu sync.RWMutex
+ tokenCache map[string]*TokenUsageStats // key: period
+ traceCache map[string]*TraceDetail // key: traceID
+ cacheTTL time.Duration
+ lastCacheTime time.Time
}
// NewAggregator 创建聚合器
@@ -457,10 +458,7 @@ func (a *Aggregator) QueryTraces(ctx context.Context, opts TraceQueryOpts) (*Tra
}, nil
}
- end := offset + limit
- if end > len(filtered) {
- end = len(filtered)
- }
+ end := min(offset+limit, len(filtered))
return &TraceListResult{
Traces: filtered[offset:end],
@@ -649,8 +647,8 @@ func (a *Aggregator) GetInsights(ctx context.Context) ([]Insight, error) {
Description: "过去 24 小时的错误率超过 5%",
Suggestion: "检查错误日志,识别常见错误模式并修复",
Data: map[string]any{
- "error_rate": perfStats.ErrorRate,
- "error_count": perfStats.ErrorCount,
+ "error_rate": perfStats.ErrorRate,
+ "error_count": perfStats.ErrorCount,
"request_count": perfStats.RequestCount,
},
CreatedAt: time.Now(),
@@ -733,9 +731,7 @@ func calculatePercentiles(values []int64) LatencyPercentiles {
// 排序
sorted := make([]int64, len(values))
copy(sorted, values)
- sort.Slice(sorted, func(i, j int) bool {
- return sorted[i] < sorted[j]
- })
+ slices.Sort(sorted)
n := len(sorted)
@@ -831,5 +827,3 @@ func (a *Aggregator) getOverviewStatsFromStore(ctx context.Context, period strin
UpdatedAt: now,
}, nil
}
-
-
diff --git a/pkg/dashboard/cost_calculator.go b/pkg/dashboard/cost_calculator.go
index 64f5127..4f5844c 100644
--- a/pkg/dashboard/cost_calculator.go
+++ b/pkg/dashboard/cost_calculator.go
@@ -1,6 +1,7 @@
package dashboard
import (
+ "maps"
"strings"
"sync"
)
@@ -17,14 +18,10 @@ func NewCostCalculator(customPricing map[string]ModelPricing) *CostCalculator {
pricing := make(map[string]ModelPricing)
// 复制默认定价
- for k, v := range DefaultModelPricing {
- pricing[k] = v
- }
+ maps.Copy(pricing, DefaultModelPricing)
// 合并自定义定价
- for k, v := range customPricing {
- pricing[k] = v
- }
+ maps.Copy(pricing, customPricing)
return &CostCalculator{
pricing: pricing,
@@ -222,9 +219,7 @@ func (cc *CostCalculator) ListPricing() map[string]ModelPricing {
defer cc.mu.RUnlock()
result := make(map[string]ModelPricing)
- for k, v := range cc.pricing {
- result[k] = v
- }
+ maps.Copy(result, cc.pricing)
return result
}
@@ -314,7 +309,7 @@ func sprintf(format string, a float64) string {
func floatToString(f float64, precision int) string {
// 使用整数运算避免浮点精度问题
multiplier := int64(1)
- for i := 0; i < precision; i++ {
+ for range precision {
multiplier *= 10
}
diff --git a/pkg/dashboard/trace_builder.go b/pkg/dashboard/trace_builder.go
index b02ee42..a060b11 100644
--- a/pkg/dashboard/trace_builder.go
+++ b/pkg/dashboard/trace_builder.go
@@ -134,9 +134,9 @@ func (tb *TraceBuilder) buildTraceSummary(sessionID string, events []types.Agent
traceID := uuid.New().String()
return &TraceSummary{
- ID: traceID,
- Name: "agent.run",
- StartTime: startTime,
+ ID: traceID,
+ Name: "agent.run",
+ StartTime: startTime,
DurationMs: endTime.Sub(startTime).Milliseconds(),
Status: status,
SpanCount: spanCount,
diff --git a/pkg/dashboard/types.go b/pkg/dashboard/types.go
index ef731ca..5417d7a 100644
--- a/pkg/dashboard/types.go
+++ b/pkg/dashboard/types.go
@@ -8,15 +8,15 @@ import (
// OverviewStats 概览统计
type OverviewStats struct {
- ActiveAgents int `json:"active_agents"`
- ActiveSessions int `json:"active_sessions"`
- TotalRequests int64 `json:"total_requests"`
- TokenUsage TokenCount `json:"token_usage"`
- Cost CostAmount `json:"cost"`
- ErrorRate float64 `json:"error_rate"`
- AvgLatencyMs int64 `json:"avg_latency_ms"`
- Period string `json:"period"` // "24h", "7d", "30d"
- UpdatedAt time.Time `json:"updated_at"`
+ ActiveAgents int `json:"active_agents"`
+ ActiveSessions int `json:"active_sessions"`
+ TotalRequests int64 `json:"total_requests"`
+ TokenUsage TokenCount `json:"token_usage"`
+ Cost CostAmount `json:"cost"`
+ ErrorRate float64 `json:"error_rate"`
+ AvgLatencyMs int64 `json:"avg_latency_ms"`
+ Period string `json:"period"` // "24h", "7d", "30d"
+ UpdatedAt time.Time `json:"updated_at"`
}
// TokenCount Token 计数
@@ -34,15 +34,15 @@ type CostAmount struct {
// TraceNode 追踪节点
type TraceNode struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Type TraceNodeType `json:"type"`
- StartTime time.Time `json:"start_time"`
- EndTime *time.Time `json:"end_time,omitempty"`
- DurationMs int64 `json:"duration_ms"`
- Status TraceStatus `json:"status"`
- Attributes map[string]any `json:"attributes,omitempty"`
- Children []*TraceNode `json:"children,omitempty"`
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Type TraceNodeType `json:"type"`
+ StartTime time.Time `json:"start_time"`
+ EndTime *time.Time `json:"end_time,omitempty"`
+ DurationMs int64 `json:"duration_ms"`
+ Status TraceStatus `json:"status"`
+ Attributes map[string]any `json:"attributes,omitempty"`
+ Children []*TraceNode `json:"children,omitempty"`
}
// TraceNodeType 追踪节点类型
@@ -81,6 +81,7 @@ type TraceSummary struct {
// TraceDetail 追踪详情
type TraceDetail struct {
TraceSummary
+
RootSpan *TraceNode `json:"root_span"`
TokenUsage TokenCount `json:"token_usage"`
Cost CostAmount `json:"cost"`
@@ -105,12 +106,12 @@ type TraceListResult struct {
// TokenUsageStats Token 使用统计
type TokenUsageStats struct {
- Period string `json:"period"` // "hour", "day", "week", "month"
- Total TokenCount `json:"total"`
- ByAgent map[string]TokenCount `json:"by_agent,omitempty"`
- ByModel map[string]TokenCount `json:"by_model,omitempty"`
- Trend []TokenTrendPoint `json:"trend,omitempty"`
- Cost CostAmount `json:"cost"`
+ Period string `json:"period"` // "hour", "day", "week", "month"
+ Total TokenCount `json:"total"`
+ ByAgent map[string]TokenCount `json:"by_agent,omitempty"`
+ ByModel map[string]TokenCount `json:"by_model,omitempty"`
+ Trend []TokenTrendPoint `json:"trend,omitempty"`
+ Cost CostAmount `json:"cost"`
}
// TokenTrendPoint Token 趋势数据点
@@ -131,11 +132,11 @@ type TokenQueryOpts struct {
// CostBreakdown 成本分解
type CostBreakdown struct {
- Period string `json:"period"`
- Total CostAmount `json:"total"`
- ByAgent map[string]CostAmount `json:"by_agent,omitempty"`
- ByModel map[string]CostAmount `json:"by_model,omitempty"`
- Trend []CostTrendPoint `json:"trend,omitempty"`
+ Period string `json:"period"`
+ Total CostAmount `json:"total"`
+ ByAgent map[string]CostAmount `json:"by_agent,omitempty"`
+ ByModel map[string]CostAmount `json:"by_model,omitempty"`
+ Trend []CostTrendPoint `json:"trend,omitempty"`
}
// CostTrendPoint 成本趋势数据点
@@ -211,21 +212,21 @@ type EventStreamMessage struct {
// EventSubscription 事件订阅配置
type EventSubscription struct {
- Channels []string `json:"channels"` // "monitor", "progress", "control"
+ Channels []string `json:"channels"` // "monitor", "progress", "control"
AgentID string `json:"agent_id,omitempty"`
EventTypes []string `json:"event_types,omitempty"`
}
// PerformanceStats 性能统计
type PerformanceStats struct {
- Period string `json:"period"`
- TTFT LatencyPercentiles `json:"ttft"` // Time to First Token
- TPOT LatencyPercentiles `json:"tpot"` // Time Per Output Token
- ToolLatency map[string]LatencyPercentiles `json:"tool_latency"` // 按工具
- AvgLoopCount float64 `json:"avg_loop_count"` // 平均循环次数
- RequestCount int64 `json:"request_count"`
- ErrorCount int64 `json:"error_count"`
- ErrorRate float64 `json:"error_rate"`
+ Period string `json:"period"`
+ TTFT LatencyPercentiles `json:"ttft"` // Time to First Token
+ TPOT LatencyPercentiles `json:"tpot"` // Time Per Output Token
+ ToolLatency map[string]LatencyPercentiles `json:"tool_latency"` // 按工具
+ AvgLoopCount float64 `json:"avg_loop_count"` // 平均循环次数
+ RequestCount int64 `json:"request_count"`
+ ErrorCount int64 `json:"error_count"`
+ ErrorRate float64 `json:"error_rate"`
}
// LatencyPercentiles 延迟百分位数
@@ -253,10 +254,10 @@ type Insight struct {
type InsightType string
const (
- InsightTypePerformance InsightType = "performance"
- InsightTypeCost InsightType = "cost"
- InsightTypeReliability InsightType = "reliability"
- InsightTypeUsage InsightType = "usage"
+ InsightTypePerformance InsightType = "performance"
+ InsightTypeCost InsightType = "cost"
+ InsightTypeReliability InsightType = "reliability"
+ InsightTypeUsage InsightType = "usage"
)
// SessionTimelineEntry 会话时间线条目
@@ -268,9 +269,9 @@ type SessionTimelineEntry struct {
// SessionTimeline 会话时间线
type SessionTimeline struct {
- SessionID string `json:"session_id"`
- AgentID string `json:"agent_id"`
- Entries []SessionTimelineEntry `json:"entries"`
- StartTime time.Time `json:"start_time"`
- EndTime *time.Time `json:"end_time,omitempty"`
+ SessionID string `json:"session_id"`
+ AgentID string `json:"agent_id"`
+ Entries []SessionTimelineEntry `json:"entries"`
+ StartTime time.Time `json:"start_time"`
+ EndTime *time.Time `json:"end_time,omitempty"`
}
diff --git a/pkg/desktop/desktop.go b/pkg/desktop/desktop.go
index 2c17178..a9c99e3 100644
--- a/pkg/desktop/desktop.go
+++ b/pkg/desktop/desktop.go
@@ -359,7 +359,7 @@ func (a *App) handleChat(msg *FrontendMessage) (*BackendResponse, error) {
return &BackendResponse{
ID: msg.ID,
Success: false,
- Error: fmt.Sprintf("agent not found: %s", msg.AgentID),
+ Error: "agent not found: " + msg.AgentID,
}, nil
}
@@ -393,7 +393,7 @@ func (a *App) handleCancel(msg *FrontendMessage) (*BackendResponse, error) {
return &BackendResponse{
ID: msg.ID,
Success: false,
- Error: fmt.Sprintf("agent not found: %s", msg.AgentID),
+ Error: "agent not found: " + msg.AgentID,
}, nil
}
@@ -433,7 +433,7 @@ func (a *App) handleGetStatus(msg *FrontendMessage) (*BackendResponse, error) {
return &BackendResponse{
ID: msg.ID,
Success: false,
- Error: fmt.Sprintf("agent not found: %s", msg.AgentID),
+ Error: "agent not found: " + msg.AgentID,
}, nil
}
@@ -454,7 +454,7 @@ func (a *App) handleGetHistory(msg *FrontendMessage) (*BackendResponse, error) {
return &BackendResponse{
ID: msg.ID,
Success: false,
- Error: fmt.Sprintf("agent not found: %s", msg.AgentID),
+ Error: "agent not found: " + msg.AgentID,
}, nil
}
@@ -473,7 +473,7 @@ func (a *App) handleClearHistory(msg *FrontendMessage) (*BackendResponse, error)
return &BackendResponse{
ID: msg.ID,
Success: false,
- Error: fmt.Sprintf("agent not found: %s", msg.AgentID),
+ Error: "agent not found: " + msg.AgentID,
}, nil
}
diff --git a/pkg/evals/batch.go b/pkg/evals/batch.go
index 6bf6bc9..a79d84f 100644
--- a/pkg/evals/batch.go
+++ b/pkg/evals/batch.go
@@ -2,6 +2,7 @@ package evals
import (
"context"
+ "errors"
"fmt"
"sync"
"time"
@@ -72,10 +73,10 @@ type BatchConfig struct {
// RunBatch 批量运行评估
func RunBatch(ctx context.Context, cfg *BatchConfig) (*BatchEvalResult, error) {
if len(cfg.TestCases) == 0 {
- return nil, fmt.Errorf("no test cases provided")
+ return nil, errors.New("no test cases provided")
}
if len(cfg.Scorers) == 0 {
- return nil, fmt.Errorf("no scorers provided")
+ return nil, errors.New("no scorers provided")
}
// 设置默认并发数
diff --git a/pkg/evals/batch_test.go b/pkg/evals/batch_test.go
index 14c2be5..4edb020 100644
--- a/pkg/evals/batch_test.go
+++ b/pkg/evals/batch_test.go
@@ -91,7 +91,7 @@ func TestRunBatchConcurrent(t *testing.T) {
// 创建多个测试用例
testCases := make([]*BatchTestCase, 10)
- for i := 0; i < 10; i++ {
+ for i := range 10 {
testCases[i] = &BatchTestCase{
ID: string(rune('A' + i)),
Input: &TextEvalInput{
@@ -127,7 +127,7 @@ func TestRunBatch_ProgressCallback(t *testing.T) {
}
testCases := make([]*BatchTestCase, 5)
- for i := 0; i < 5; i++ {
+ for i := range 5 {
testCases[i] = &BatchTestCase{
ID: string(rune('A' + i)),
Input: &TextEvalInput{Answer: "测试"},
@@ -181,7 +181,7 @@ func TestRunBatch_StopOnError(t *testing.T) {
failingScorer := &failingTestScorer{}
testCases := make([]*BatchTestCase, 5)
- for i := 0; i < 5; i++ {
+ for i := range 5 {
testCases[i] = &BatchTestCase{
ID: string(rune('A' + i)),
Input: &TextEvalInput{Answer: "测试"},
diff --git a/pkg/events/bus_test.go b/pkg/events/bus_test.go
index c65cfd3..ef307e2 100644
--- a/pkg/events/bus_test.go
+++ b/pkg/events/bus_test.go
@@ -62,7 +62,7 @@ func TestCleanupBySize(t *testing.T) {
defer eb.Close()
// Emit 10 events
- for i := 0; i < 10; i++ {
+ for i := range 10 {
eb.EmitProgress(&types.ProgressTextChunkEvent{Step: i, Delta: "test"})
}
@@ -169,7 +169,7 @@ func TestGetTimelineRange(t *testing.T) {
defer eb.Close()
// 添加 10 个事件
- for i := 0; i < 10; i++ {
+ for i := range 10 {
eb.EmitProgress(&types.ProgressTextChunkEvent{Step: i, Delta: "test"})
}
@@ -204,7 +204,7 @@ func TestGetTimelineSince(t *testing.T) {
defer eb.Close()
// 添加事件
- for i := 0; i < 5; i++ {
+ for i := range 5 {
eb.EmitProgress(&types.ProgressTextChunkEvent{Step: i, Delta: "test"})
}
@@ -221,7 +221,7 @@ func TestGetTimelineFiltered(t *testing.T) {
defer eb.Close()
// 添加不同类型的事件
- for i := 0; i < 5; i++ {
+ for i := range 5 {
eb.EmitProgress(&types.ProgressTextChunkEvent{Step: i, Delta: "test"})
}
eb.EmitProgress(&types.ProgressDoneEvent{})
@@ -246,7 +246,7 @@ func TestGetTimelineCount(t *testing.T) {
t.Error("initial count should be 0")
}
- for i := 0; i < 10; i++ {
+ for i := range 10 {
eb.EmitProgress(&types.ProgressTextChunkEvent{Step: i, Delta: "test"})
}
@@ -266,7 +266,7 @@ func TestAutoCleanupWorker(t *testing.T) {
defer eb.Close()
// 添加 10 个事件
- for i := 0; i < 10; i++ {
+ for i := range 10 {
eb.EmitProgress(&types.ProgressTextChunkEvent{Step: i, Delta: "test"})
}
@@ -293,9 +293,9 @@ func TestMemoryStability(t *testing.T) {
batches := 100
eventsPerBatch := 100
- for batch := 0; batch < batches; batch++ {
+ for batch := range batches {
// 快速发送一批事件
- for i := 0; i < eventsPerBatch; i++ {
+ for i := range eventsPerBatch {
eb.EmitProgress(&types.ProgressTextChunkEvent{
Step: batch*eventsPerBatch + i,
Delta: "test data",
diff --git a/pkg/executionplan/executor.go b/pkg/executionplan/executor.go
index ab78702..686dfd0 100644
--- a/pkg/executionplan/executor.go
+++ b/pkg/executionplan/executor.go
@@ -3,6 +3,7 @@ package executionplan
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"sync"
"time"
@@ -71,10 +72,10 @@ func (e *Executor) Execute(ctx context.Context, plan *ExecutionPlan, toolCtx *to
// 检查计划是否可以执行
if !plan.CanExecute() {
if plan.Options != nil && plan.Options.RequireApproval && !plan.IsApproved() {
- return fmt.Errorf("plan requires user approval before execution")
+ return errors.New("plan requires user approval before execution")
}
if plan.Status == StatusExecuting {
- return fmt.Errorf("plan is already executing")
+ return errors.New("plan is already executing")
}
if plan.IsCompleted() {
return fmt.Errorf("plan has already completed with status: %s", plan.Status)
@@ -283,7 +284,7 @@ func (e *Executor) executeStep(ctx context.Context, plan *ExecutionPlan, step *S
tool, ok := e.tools[step.ToolName]
if !ok {
step.Status = StepStatusFailed
- step.Error = fmt.Sprintf("tool not found: %s", step.ToolName)
+ step.Error = "tool not found: " + step.ToolName
return fmt.Errorf("tool not found: %s", step.ToolName)
}
@@ -321,10 +322,9 @@ func (e *Executor) executeStep(ctx context.Context, plan *ExecutionPlan, step *S
// 重试逻辑
var result any
var execErr error
- maxRetries := step.MaxRetries
- if maxRetries <= 0 {
- maxRetries = 0 // 默认不重试
- }
+ maxRetries := max(step.MaxRetries,
+ // 默认不重试
+ 0)
retryLoop:
for attempt := 0; attempt <= maxRetries; attempt++ {
diff --git a/pkg/executionplan/generator.go b/pkg/executionplan/generator.go
index 3146d4e..49939ed 100644
--- a/pkg/executionplan/generator.go
+++ b/pkg/executionplan/generator.go
@@ -3,6 +3,7 @@ package executionplan
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"strings"
@@ -71,13 +72,13 @@ type planStepResp struct {
// Generate 生成执行计划
func (g *Generator) Generate(ctx context.Context, req *PlanRequest) (*ExecutionPlan, error) {
if req.UserRequest == "" {
- return nil, fmt.Errorf("user request cannot be empty")
+ return nil, errors.New("user request cannot be empty")
}
// 构建可用工具描述
toolDescriptions := g.buildToolDescriptions(req.AvailableTools)
if toolDescriptions == "" {
- return nil, fmt.Errorf("no tools available for plan generation")
+ return nil, errors.New("no tools available for plan generation")
}
// 构建提示词
@@ -293,7 +294,7 @@ func (g *Generator) parseResponse(content string) (*ExecutionPlan, error) {
// 如果直接解析失败,尝试提取 JSON 部分
jsonStr, extractErr := extractJSON(content)
if extractErr != nil {
- return nil, fmt.Errorf("failed to extract JSON from response: %w (original error: %v)", extractErr, err)
+ return nil, fmt.Errorf("failed to extract JSON from response: %w (original error: %w)", extractErr, err)
}
if err := json.Unmarshal([]byte(jsonStr), &planResp); err != nil {
return nil, fmt.Errorf("failed to parse extracted JSON: %w", err)
@@ -339,7 +340,7 @@ func extractJSON(text string) (string, error) {
end := strings.LastIndex(text, "}")
if start == -1 || end == -1 || end <= start {
- return "", fmt.Errorf("no valid JSON object found in text")
+ return "", errors.New("no valid JSON object found in text")
}
return text[start : end+1], nil
@@ -413,40 +414,40 @@ func getStatusIcon(status StepStatus) string {
// ValidatePlan 验证执行计划
func (g *Generator) ValidatePlan(plan *ExecutionPlan) []error {
- var errors []error
+ var errs []error
if plan.Description == "" {
- errors = append(errors, fmt.Errorf("plan description is required"))
+ errs = append(errs, errors.New("plan description is required"))
}
if len(plan.Steps) == 0 {
- errors = append(errors, fmt.Errorf("plan must have at least one step"))
+ errs = append(errs, errors.New("plan must have at least one step"))
}
for i, step := range plan.Steps {
// 验证工具是否存在
if _, ok := g.tools[step.ToolName]; !ok {
- errors = append(errors, fmt.Errorf("step %d: unknown tool '%s'", i+1, step.ToolName))
+ errs = append(errs, fmt.Errorf("step %d: unknown tool '%s'", i+1, step.ToolName))
}
if step.Description == "" {
- errors = append(errors, fmt.Errorf("step %d: description is required", i+1))
+ errs = append(errs, fmt.Errorf("step %d: description is required", i+1))
}
// 验证依赖关系
for _, depID := range step.DependsOn {
found := false
- for j := 0; j < i; j++ {
+ for j := range i {
if plan.Steps[j].ID == depID {
found = true
break
}
}
if !found {
- errors = append(errors, fmt.Errorf("step %d: invalid dependency '%s'", i+1, depID))
+ errs = append(errs, fmt.Errorf("step %d: invalid dependency '%s'", i+1, depID))
}
}
}
- return errors
+ return errs
}
diff --git a/pkg/executionplan/types.go b/pkg/executionplan/types.go
index a1dedd1..b352927 100644
--- a/pkg/executionplan/types.go
+++ b/pkg/executionplan/types.go
@@ -14,7 +14,7 @@ const (
StatusExecuting Status = "executing" // 执行中
StatusCompleted Status = "completed" // 执行完成
StatusFailed Status = "failed" // 执行失败
- StatusCancelled Status = "cancelled" // 已取消
+ StatusCancelled Status = "canceled" // 已取消
StatusPartial Status = "partial" // 部分完成
)
@@ -22,25 +22,25 @@ const (
type StepStatus string
const (
- StepStatusPending StepStatus = "pending" // 待执行
- StepStatusRunning StepStatus = "running" // 执行中
- StepStatusCompleted StepStatus = "completed" // 执行完成
- StepStatusFailed StepStatus = "failed" // 执行失败
- StepStatusSkipped StepStatus = "skipped" // 已跳过
+ StepStatusPending StepStatus = "pending" // 待执行
+ StepStatusRunning StepStatus = "running" // 执行中
+ StepStatusCompleted StepStatus = "completed" // 执行完成
+ StepStatusFailed StepStatus = "failed" // 执行失败
+ StepStatusSkipped StepStatus = "skipped" // 已跳过
)
// Step 执行计划中的单个步骤
type Step struct {
// 基础信息
- ID string `json:"id"` // 步骤唯一ID
- Index int `json:"index"` // 步骤序号(从0开始)
- ToolName string `json:"tool_name"` // 要调用的工具名称
- Description string `json:"description"` // 步骤描述(自然语言)
-
+ ID string `json:"id"` // 步骤唯一ID
+ Index int `json:"index"` // 步骤序号(从0开始)
+ ToolName string `json:"tool_name"` // 要调用的工具名称
+ Description string `json:"description"` // 步骤描述(自然语言)
+
// 参数
Input string `json:"input,omitempty"` // 原始输入(LLM 生成的字符串)
Parameters map[string]any `json:"parameters,omitempty"` // 解析后的参数
-
+
// 执行状态
Status StepStatus `json:"status"`
Result any `json:"result,omitempty"` // 执行结果
@@ -48,48 +48,48 @@ type Step struct {
StartedAt *time.Time `json:"started_at,omitempty"` // 开始时间
CompletedAt *time.Time `json:"completed_at,omitempty"` // 完成时间
DurationMs int64 `json:"duration_ms,omitempty"` // 执行耗时(毫秒)
-
+
// 依赖关系
DependsOn []string `json:"depends_on,omitempty"` // 依赖的步骤ID列表
-
+
// 重试信息
- RetryCount int `json:"retry_count,omitempty"` // 已重试次数
- MaxRetries int `json:"max_retries,omitempty"` // 最大重试次数
- RetryDelayMs int `json:"retry_delay_ms,omitempty"` // 重试间隔(毫秒)
+ RetryCount int `json:"retry_count,omitempty"` // 已重试次数
+ MaxRetries int `json:"max_retries,omitempty"` // 最大重试次数
+ RetryDelayMs int `json:"retry_delay_ms,omitempty"` // 重试间隔(毫秒)
}
// ExecutionPlan 执行计划
type ExecutionPlan struct {
// 基础信息
- ID string `json:"id"` // 计划唯一ID
- TaskID string `json:"task_id,omitempty"` // 关联的任务ID
- Name string `json:"name,omitempty"` // 计划名称
- Description string `json:"description"` // 计划描述(自然语言)
-
+ ID string `json:"id"` // 计划唯一ID
+ TaskID string `json:"task_id,omitempty"` // 关联的任务ID
+ Name string `json:"name,omitempty"` // 计划名称
+ Description string `json:"description"` // 计划描述(自然语言)
+
// 步骤列表
Steps []Step `json:"steps"`
-
+
// 审批状态
- UserApproved bool `json:"user_approved"` // 是否已用户审批
- ApprovedAt *time.Time `json:"approved_at,omitempty"` // 审批时间
- ApprovedBy string `json:"approved_by,omitempty"` // 审批人
- RejectionNote string `json:"rejection_note,omitempty"` // 拒绝原因
-
+ UserApproved bool `json:"user_approved"` // 是否已用户审批
+ ApprovedAt *time.Time `json:"approved_at,omitempty"` // 审批时间
+ ApprovedBy string `json:"approved_by,omitempty"` // 审批人
+ RejectionNote string `json:"rejection_note,omitempty"` // 拒绝原因
+
// 执行状态
- Status Status `json:"status"`
- CurrentStep int `json:"current_step"` // 当前执行到的步骤索引
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
- StartedAt *time.Time `json:"started_at,omitempty"`
- CompletedAt *time.Time `json:"completed_at,omitempty"`
- TotalDurationMs int64 `json:"total_duration_ms,omitempty"`
-
+ Status Status `json:"status"`
+ CurrentStep int `json:"current_step"` // 当前执行到的步骤索引
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+ StartedAt *time.Time `json:"started_at,omitempty"`
+ CompletedAt *time.Time `json:"completed_at,omitempty"`
+ TotalDurationMs int64 `json:"total_duration_ms,omitempty"`
+
// 上下文信息
AgentID string `json:"agent_id,omitempty"` // 关联的 Agent ID
OrgID string `json:"org_id,omitempty"` // 组织 ID(多租户)
TenantID string `json:"tenant_id,omitempty"` // 租户 ID(多租户)
Metadata map[string]any `json:"metadata,omitempty"` // 自定义元数据
-
+
// 执行选项
Options *ExecutionOptions `json:"options,omitempty"`
}
@@ -99,19 +99,19 @@ type ExecutionOptions struct {
// 并行执行
AllowParallel bool `json:"allow_parallel,omitempty"` // 是否允许并行执行无依赖的步骤
MaxParallelSteps int `json:"max_parallel_steps,omitempty"` // 最大并行步骤数
-
+
// 错误处理
- StopOnError bool `json:"stop_on_error,omitempty"` // 出错时是否停止
- ContinueOnError bool `json:"continue_on_error,omitempty"` // 出错时是否继续
-
+ StopOnError bool `json:"stop_on_error,omitempty"` // 出错时是否停止
+ ContinueOnError bool `json:"continue_on_error,omitempty"` // 出错时是否继续
+
// 超时控制
StepTimeoutMs int64 `json:"step_timeout_ms,omitempty"` // 单步超时(毫秒)
TotalTimeoutMs int64 `json:"total_timeout_ms,omitempty"` // 总超时(毫秒)
-
+
// 审批要求
- RequireApproval bool `json:"require_approval,omitempty"` // 是否需要用户审批
- AutoApprove bool `json:"auto_approve,omitempty"` // 是否自动审批
- ApprovalTimeoutMs int64 `json:"approval_timeout_ms,omitempty"` // 审批超时(毫秒)
+ RequireApproval bool `json:"require_approval,omitempty"` // 是否需要用户审批
+ AutoApprove bool `json:"auto_approve,omitempty"` // 是否自动审批
+ ApprovalTimeoutMs int64 `json:"approval_timeout_ms,omitempty"` // 审批超时(毫秒)
}
// NewExecutionPlan 创建新的执行计划
@@ -253,7 +253,7 @@ func (p *ExecutionPlan) Summary() PlanSummary {
running++
}
}
-
+
return PlanSummary{
ID: p.ID,
Description: p.Description,
diff --git a/pkg/executionplan/types_test.go b/pkg/executionplan/types_test.go
index cc47fda..0e57a92 100644
--- a/pkg/executionplan/types_test.go
+++ b/pkg/executionplan/types_test.go
@@ -162,7 +162,7 @@ func TestIsCompleted(t *testing.T) {
{"executing", StatusExecuting, false},
{"completed", StatusCompleted, true},
{"failed", StatusFailed, true},
- {"cancelled", StatusCancelled, true},
+ {"canceled", StatusCancelled, true},
{"partial", StatusPartial, false},
}
@@ -218,7 +218,7 @@ func TestCanExecute(t *testing.T) {
{"executing", StatusExecuting, true, true, false, false},
{"completed", StatusCompleted, true, true, false, false},
{"failed", StatusFailed, true, true, false, false},
- {"cancelled", StatusCancelled, true, true, false, false},
+ {"canceled", StatusCancelled, true, true, false, false},
{"approved status, approved", StatusApproved, true, true, false, true},
}
diff --git a/pkg/guardrails/openai_moderation.go b/pkg/guardrails/openai_moderation.go
index 65bfa2d..9ee7f22 100644
--- a/pkg/guardrails/openai_moderation.go
+++ b/pkg/guardrails/openai_moderation.go
@@ -2,6 +2,7 @@ package guardrails
import (
"context"
+ "errors"
"fmt"
"os"
@@ -85,7 +86,7 @@ func (g *OpenAIModerationGuardrail) Description() string {
// Check 检查内容
func (g *OpenAIModerationGuardrail) Check(ctx context.Context, input *GuardrailInput) error {
if g.apiKey == "" {
- return fmt.Errorf("OpenAI API key not configured")
+ return errors.New("OpenAI API key not configured")
}
client := openai.NewClient(g.apiKey)
diff --git a/pkg/guardrails/pii_detection.go b/pkg/guardrails/pii_detection.go
index 5b3f1ad..b1c78b7 100644
--- a/pkg/guardrails/pii_detection.go
+++ b/pkg/guardrails/pii_detection.go
@@ -2,6 +2,7 @@ package guardrails
import (
"context"
+ "maps"
"regexp"
"strings"
)
@@ -110,12 +111,8 @@ func (g *PIIDetectionGuardrail) Check(ctx context.Context, input *GuardrailInput
// 检查所有模式
allPatterns := make(map[string]*regexp.Regexp)
- for k, v := range g.piiPatterns {
- allPatterns[k] = v
- }
- for k, v := range g.customPatterns {
- allPatterns[k] = v
- }
+ maps.Copy(allPatterns, g.piiPatterns)
+ maps.Copy(allPatterns, g.customPatterns)
for piiType, pattern := range allPatterns {
if pattern.MatchString(content) {
diff --git a/pkg/knowledge/core/pipeline.go b/pkg/knowledge/core/pipeline.go
index a1f1fe7..504c337 100644
--- a/pkg/knowledge/core/pipeline.go
+++ b/pkg/knowledge/core/pipeline.go
@@ -2,7 +2,9 @@ package core
import (
"context"
+ "errors"
"fmt"
+ "maps"
"strings"
"time"
@@ -28,10 +30,10 @@ type Pipeline struct {
// NewPipeline 创建管线实例。
func NewPipeline(cfg PipelineConfig) (*Pipeline, error) {
if cfg.Store == nil {
- return nil, fmt.Errorf("knowledge core: store is required")
+ return nil, errors.New("knowledge core: store is required")
}
if cfg.Embedder == nil {
- return nil, fmt.Errorf("knowledge core: embedder is required")
+ return nil, errors.New("knowledge core: embedder is required")
}
ns := cfg.Namespace
if ns == "" {
@@ -51,7 +53,7 @@ func NewPipeline(cfg PipelineConfig) (*Pipeline, error) {
// Ingest 将文本切分并写入向量库。
func (p *Pipeline) Ingest(ctx context.Context, req IngestRequest) ([]Chunk, error) {
if strings.TrimSpace(req.Text) == "" {
- return nil, fmt.Errorf("knowledge core: text is empty")
+ return nil, errors.New("knowledge core: text is empty")
}
id := strings.TrimSpace(req.ID)
if id == "" {
@@ -63,16 +65,14 @@ func (p *Pipeline) Ingest(ctx context.Context, req IngestRequest) ([]Chunk, erro
}
meta := make(map[string]any)
- for k, v := range req.Metadata {
- meta[k] = v
- }
+ maps.Copy(meta, req.Metadata)
if ns != "" {
meta["namespace"] = ns
}
rawChunks := splitParagraphs(req.Text)
if len(rawChunks) == 0 {
- return nil, fmt.Errorf("knowledge core: no chunks after split")
+ return nil, errors.New("knowledge core: no chunks after split")
}
vecs, err := p.embedder.EmbedText(ctx, rawChunks)
@@ -88,9 +88,7 @@ func (p *Pipeline) Ingest(ctx context.Context, req IngestRequest) ([]Chunk, erro
for i, ctext := range rawChunks {
chunkID := fmt.Sprintf("%s#%d", id, i)
chunkMeta := make(map[string]any, len(meta)+2)
- for k, v := range meta {
- chunkMeta[k] = v
- }
+ maps.Copy(chunkMeta, meta)
chunkMeta["text"] = ctext
chunkMeta["chunk_index"] = i
@@ -119,7 +117,7 @@ func (p *Pipeline) Ingest(ctx context.Context, req IngestRequest) ([]Chunk, erro
// Search 执行向量检索。
func (p *Pipeline) Search(ctx context.Context, query string, topK int, metadata map[string]any) ([]SearchHit, error) {
if strings.TrimSpace(query) == "" {
- return nil, fmt.Errorf("knowledge core: query is empty")
+ return nil, errors.New("knowledge core: query is empty")
}
if topK <= 0 {
topK = p.defaultK
@@ -137,7 +135,7 @@ func (p *Pipeline) Search(ctx context.Context, query string, topK int, metadata
return nil, fmt.Errorf("embed query: %w", err)
}
if len(vecs) == 0 {
- return nil, fmt.Errorf("embedder returned empty vectors")
+ return nil, errors.New("embedder returned empty vectors")
}
hits, err := p.store.Query(ctx, vector.Query{
diff --git a/pkg/knowledge/manager.go b/pkg/knowledge/manager.go
index 0455062..cd8583f 100644
--- a/pkg/knowledge/manager.go
+++ b/pkg/knowledge/manager.go
@@ -3,7 +3,9 @@ package knowledge
import (
"context"
"encoding/json"
+ "errors"
"fmt"
+ "maps"
"strings"
"sync"
"time"
@@ -95,19 +97,19 @@ func (r *redactionStrategy) Sanitize(item *KnowledgeItem) *KnowledgeItem {
// NewManager 创建知识管理器
func NewManager(config *ManagerConfig) (Manager, error) {
if config == nil {
- return nil, fmt.Errorf("knowledge: config cannot be nil")
+ return nil, errors.New("knowledge: config cannot be nil")
}
if config.MemoryManager == nil {
- return nil, fmt.Errorf("knowledge: memory manager is required")
+ return nil, errors.New("knowledge: memory manager is required")
}
if config.VectorStore == nil {
- return nil, fmt.Errorf("knowledge: vector store is required")
+ return nil, errors.New("knowledge: vector store is required")
}
if config.Embedder == nil && config.AutoEmbed {
- return nil, fmt.Errorf("knowledge: embedder is required when auto embed is enabled")
+ return nil, errors.New("knowledge: embedder is required when auto embed is enabled")
}
m := &manager{
@@ -163,7 +165,7 @@ func (m *manager) Start(ctx context.Context) error {
defer m.mu.Unlock()
if m.running {
- return fmt.Errorf("knowledge: manager is already running")
+ return errors.New("knowledge: manager is already running")
}
m.running = true
@@ -180,7 +182,7 @@ func (m *manager) Stop(ctx context.Context) error {
defer m.mu.Unlock()
if !m.running {
- return fmt.Errorf("knowledge: manager is not running")
+ return errors.New("knowledge: manager is not running")
}
m.running = false
@@ -198,11 +200,11 @@ func (m *manager) Stop(ctx context.Context) error {
// Add 添加知识项
func (m *manager) Add(ctx context.Context, item *KnowledgeItem) error {
if item == nil {
- return fmt.Errorf("knowledge: item cannot be nil")
+ return errors.New("knowledge: item cannot be nil")
}
if item.ID == "" {
- return fmt.Errorf("knowledge: item ID cannot be empty")
+ return errors.New("knowledge: item ID cannot be empty")
}
m.mu.Lock()
@@ -282,7 +284,7 @@ func (m *manager) Add(ctx context.Context, item *KnowledgeItem) error {
// 记录审计
if m.config.EnableAudit {
- m.audit("add", "", item.ID, fmt.Sprintf("Added knowledge item: %s", item.Title))
+ m.audit("add", "", item.ID, "Added knowledge item: "+item.Title)
}
return nil
@@ -316,7 +318,7 @@ func (m *manager) Get(ctx context.Context, id string) (*KnowledgeItem, error) {
// 记录审计
if m.config.EnableAudit {
- m.audit("get", "", item.ID, fmt.Sprintf("Retrieved knowledge item: %s", item.Title))
+ m.audit("get", "", item.ID, "Retrieved knowledge item: "+item.Title)
}
return item, nil
@@ -325,11 +327,11 @@ func (m *manager) Get(ctx context.Context, id string) (*KnowledgeItem, error) {
// Update 更新知识项
func (m *manager) Update(ctx context.Context, item *KnowledgeItem) error {
if item == nil {
- return fmt.Errorf("knowledge: item cannot be nil")
+ return errors.New("knowledge: item cannot be nil")
}
if item.ID == "" {
- return fmt.Errorf("knowledge: item ID cannot be empty")
+ return errors.New("knowledge: item ID cannot be empty")
}
m.mu.Lock()
@@ -400,7 +402,7 @@ func (m *manager) Update(ctx context.Context, item *KnowledgeItem) error {
// 记录审计
if m.config.EnableAudit {
- m.audit("update", "", item.ID, fmt.Sprintf("Updated knowledge item: %s", item.Title))
+ m.audit("update", "", item.ID, "Updated knowledge item: "+item.Title)
}
return nil
@@ -438,7 +440,7 @@ func (m *manager) Delete(ctx context.Context, id string) error {
// 记录审计
if m.config.EnableAudit {
- m.audit("delete", "", id, fmt.Sprintf("Deleted knowledge item: %s", item.Title))
+ m.audit("delete", "", id, "Deleted knowledge item: "+item.Title)
}
return nil
@@ -447,15 +449,13 @@ func (m *manager) Delete(ctx context.Context, id string) error {
// Search 搜索知识
func (m *manager) Search(ctx context.Context, query *SearchQuery) ([]*SearchResult, error) {
if query == nil {
- return nil, fmt.Errorf("knowledge: search query cannot be nil")
+ return nil, errors.New("knowledge: search query cannot be nil")
}
// 轻量路径:仅使用向量检索,避开复杂策略
if m.corePipeline != nil && (query.Strategy == "" || query.Strategy == StrategyVector) {
meta := map[string]any{}
- for k, v := range query.Filters {
- meta[k] = v
- }
+ maps.Copy(meta, query.Filters)
if query.Namespace != "" {
meta["namespace"] = query.Namespace
}
@@ -524,7 +524,7 @@ func (m *manager) SearchSimilar(ctx context.Context, id string, maxResults int)
}
if len(item.Embedding) == 0 && m.corePipeline == nil {
- return nil, fmt.Errorf("knowledge: item has no embedding for similarity search")
+ return nil, errors.New("knowledge: item has no embedding for similarity search")
}
// 优先使用原有向量
@@ -549,7 +549,7 @@ func (m *manager) SearchSimilar(ctx context.Context, id string, maxResults int)
return convertCoreHits(hits), nil
}
- return nil, fmt.Errorf("knowledge: no embedding available for similarity search")
+ return nil, errors.New("knowledge: no embedding available for similarity search")
}
// AddRelation 添加知识关系
@@ -580,7 +580,7 @@ func (m *manager) AddRelation(ctx context.Context, fromID, toID string, relation
// 检查关系是否已存在
for _, existing := range fromItem.Relations {
if existing.Type == relationType && existing.TargetID == toID {
- return fmt.Errorf("knowledge: relation already exists")
+ return errors.New("knowledge: relation already exists")
}
}
@@ -622,7 +622,7 @@ func (m *manager) RemoveRelation(ctx context.Context, fromID, toID string, relat
}
if !removed {
- return fmt.Errorf("knowledge: relation not found")
+ return errors.New("knowledge: relation not found")
}
item.Relations = relations
@@ -764,7 +764,7 @@ func (m *manager) Compress(ctx context.Context, namespace string) error {
// 2. 合并相似知识
// 3. 删除低质量知识
// 4. 更新关系
- return fmt.Errorf("knowledge: compression not yet implemented")
+ return errors.New("knowledge: compression not yet implemented")
}
// Reason 知识推理
@@ -774,7 +774,7 @@ func (m *manager) Reason(ctx context.Context, query string, maxSteps int) ([]*Kn
// 2. 搜索相关知识
// 3. 应用推理规则
// 4. 生成推理链
- return nil, nil, fmt.Errorf("knowledge: reasoning not yet implemented")
+ return nil, nil, errors.New("knowledge: reasoning not yet implemented")
}
// 辅助方法
@@ -782,7 +782,7 @@ func (m *manager) Reason(ctx context.Context, query string, maxSteps int) ([]*Kn
// generateEmbedding 生成向量嵌入
func (m *manager) generateEmbedding(ctx context.Context, item *KnowledgeItem) ([]float32, error) {
if m.embedder == nil {
- return nil, fmt.Errorf("knowledge: no embedder available")
+ return nil, errors.New("knowledge: no embedder available")
}
text := item.Content
@@ -795,7 +795,7 @@ func (m *manager) generateEmbedding(ctx context.Context, item *KnowledgeItem) ([
return nil, fmt.Errorf("knowledge: embed failed: %w", err)
}
if len(embeddings) == 0 {
- return nil, fmt.Errorf("knowledge: no embedding generated")
+ return nil, errors.New("knowledge: no embedding generated")
}
embedding := embeddings[0]
diff --git a/pkg/knowledge/rag.go b/pkg/knowledge/rag.go
index 0766c3f..eb9b181 100644
--- a/pkg/knowledge/rag.go
+++ b/pkg/knowledge/rag.go
@@ -199,7 +199,7 @@ func (r *RAG) RetrieveMultiHop(ctx context.Context, query string, maxHops int) (
var allContextItems []*KnowledgeItem
var expandedQuery = query
- for hop := 0; hop < maxHops; hop++ {
+ for range maxHops {
result, err := r.Retrieve(ctx, expandedQuery, WithMaxResults(r.config.MaxRetrievalResults/2))
if err != nil {
break
@@ -575,7 +575,7 @@ func (r *RAG) formatContextItem(item *KnowledgeItem) string {
var parts []string
if item.Title != "" {
- parts = append(parts, fmt.Sprintf("## %s", item.Title))
+ parts = append(parts, "## "+item.Title)
}
if item.Description != "" {
@@ -587,11 +587,11 @@ func (r *RAG) formatContextItem(item *KnowledgeItem) string {
}
if len(item.Tags) > 0 {
- parts = append(parts, fmt.Sprintf("Tags: %s", strings.Join(item.Tags, ", ")))
+ parts = append(parts, "Tags: "+strings.Join(item.Tags, ", "))
}
if item.Source != "" {
- parts = append(parts, fmt.Sprintf("Source: %s", item.Source))
+ parts = append(parts, "Source: "+item.Source)
}
return strings.Join(parts, "\n")
@@ -618,8 +618,8 @@ func (r *RAG) enhanceQuery(originalQuery string, items []*RetrievalItem) string
keywords := make(map[string]bool)
for _, item := range items[:min(3, len(items))] {
if item.Item.Title != "" {
- words := strings.Fields(item.Item.Title)
- for _, word := range words {
+ words := strings.FieldsSeq(item.Item.Title)
+ for word := range words {
if len(word) > 2 {
keywords[strings.ToLower(word)] = true
}
@@ -681,14 +681,6 @@ func (r *RAG) deduplicate(items []*RetrievalItem) []*RetrievalItem {
return result
}
-// 辅助函数
-func min(a, b int) int {
- if a < b {
- return a
- }
- return b
-}
-
func containsItem(items []*KnowledgeItem, target *KnowledgeItem) bool {
for _, item := range items {
if item.ID == target.ID {
diff --git a/pkg/knowledge/simple/pipeline.go b/pkg/knowledge/simple/pipeline.go
index c82c9b7..8faaf62 100644
--- a/pkg/knowledge/simple/pipeline.go
+++ b/pkg/knowledge/simple/pipeline.go
@@ -2,7 +2,9 @@ package simple
import (
"context"
+ "errors"
"fmt"
+ "maps"
"strings"
"sync"
@@ -29,10 +31,10 @@ type Pipeline struct {
// NewPipeline 创建知识管线。
func NewPipeline(cfg PipelineConfig) (*Pipeline, error) {
if cfg.Store == nil {
- return nil, fmt.Errorf("knowledge: store is required")
+ return nil, errors.New("knowledge: store is required")
}
if cfg.Embedder == nil {
- return nil, fmt.Errorf("knowledge: embedder is required")
+ return nil, errors.New("knowledge: embedder is required")
}
if cfg.DefaultTopK <= 0 {
cfg.DefaultTopK = 5
@@ -73,11 +75,11 @@ func DefaultInMemoryPipeline() (*Pipeline, error) {
// - metadata 会透传到向量文档。
func (p *Pipeline) UpsertText(ctx context.Context, id, text string, metadata map[string]any) ([]string, error) {
if strings.TrimSpace(id) == "" {
- return nil, fmt.Errorf("knowledge: id is required")
+ return nil, errors.New("knowledge: id is required")
}
chunks := splitParagraphs(text)
if len(chunks) == 0 {
- return nil, fmt.Errorf("knowledge: text is empty")
+ return nil, errors.New("knowledge: text is empty")
}
vecs, err := p.embedder.EmbedText(ctx, chunks)
@@ -99,9 +101,7 @@ func (p *Pipeline) UpsertText(ctx context.Context, id, text string, metadata map
for i, chunk := range chunks {
chunkID := fmt.Sprintf("%s#%d", id, i)
metaCopy := make(map[string]any, len(metadata)+2)
- for k, v := range metadata {
- metaCopy[k] = v
- }
+ maps.Copy(metaCopy, metadata)
metaCopy["text"] = chunk
metaCopy["chunk_index"] = i
@@ -125,7 +125,7 @@ func (p *Pipeline) UpsertText(ctx context.Context, id, text string, metadata map
// 返回向量命中列表;不在此处做 rerank/过滤,保持简洁。
func (p *Pipeline) Search(ctx context.Context, query string, topK int, metadata map[string]any) ([]vector.Hit, error) {
if strings.TrimSpace(query) == "" {
- return nil, fmt.Errorf("knowledge: query is empty")
+ return nil, errors.New("knowledge: query is empty")
}
if topK <= 0 {
topK = p.defaultK
@@ -136,7 +136,7 @@ func (p *Pipeline) Search(ctx context.Context, query string, topK int, metadata
return nil, fmt.Errorf("embed query: %w", err)
}
if len(vecs) == 0 {
- return nil, fmt.Errorf("embedder returned empty vectors")
+ return nil, errors.New("embedder returned empty vectors")
}
ns := p.namespace
diff --git a/pkg/mcpserver/docstools.go b/pkg/mcpserver/docstools.go
index c0729dc..34e734f 100644
--- a/pkg/mcpserver/docstools.go
+++ b/pkg/mcpserver/docstools.go
@@ -3,6 +3,7 @@ package mcpserver
import (
"bufio"
"context"
+ "errors"
"fmt"
"os"
"path/filepath"
@@ -22,7 +23,7 @@ type DocsToolConfig struct {
// normalizeBaseDir 确保 baseDir 是绝对路径并移除尾部斜杠
func normalizeBaseDir(baseDir string) (string, error) {
if baseDir == "" {
- return "", fmt.Errorf("baseDir is required")
+ return "", errors.New("baseDir is required")
}
abs, err := filepath.Abs(baseDir)
if err != nil {
@@ -69,12 +70,12 @@ func (t *DocsGetTool) InputSchema() map[string]any {
func (t *DocsGetTool) Execute(ctx context.Context, input map[string]any, tc *tools.ToolContext) (any, error) {
relPath, _ := input["path"].(string)
if relPath == "" {
- return nil, fmt.Errorf("path is required")
+ return nil, errors.New("path is required")
}
// 防止路径遍历
if strings.Contains(relPath, "..") {
- return nil, fmt.Errorf("path traversal is not allowed")
+ return nil, errors.New("path traversal is not allowed")
}
fullPath := filepath.Join(t.baseDir, filepath.FromSlash(relPath))
@@ -84,7 +85,7 @@ func (t *DocsGetTool) Execute(ctx context.Context, input map[string]any, tc *too
}
if !strings.HasPrefix(abs, t.baseDir) {
- return nil, fmt.Errorf("path outside baseDir is not allowed")
+ return nil, errors.New("path outside baseDir is not allowed")
}
data, err := os.ReadFile(abs)
@@ -152,7 +153,7 @@ func (t *DocsSearchTool) InputSchema() map[string]any {
func (t *DocsSearchTool) Execute(ctx context.Context, input map[string]any, tc *tools.ToolContext) (any, error) {
query, _ := input["query"].(string)
if strings.TrimSpace(query) == "" {
- return nil, fmt.Errorf("query cannot be empty")
+ return nil, errors.New("query cannot be empty")
}
queryLower := strings.ToLower(query)
@@ -167,7 +168,7 @@ func (t *DocsSearchTool) Execute(ctx context.Context, input map[string]any, tc *
root := t.baseDir
if subdir != "" {
if strings.Contains(subdir, "..") {
- return nil, fmt.Errorf("subdir path traversal is not allowed")
+ return nil, errors.New("subdir path traversal is not allowed")
}
root = filepath.Join(t.baseDir, filepath.FromSlash(subdir))
}
@@ -177,7 +178,7 @@ func (t *DocsSearchTool) Execute(ctx context.Context, input map[string]any, tc *
return nil, fmt.Errorf("resolve root: %w", err)
}
if !strings.HasPrefix(rootAbs, t.baseDir) {
- return nil, fmt.Errorf("subdir outside baseDir is not allowed")
+ return nil, errors.New("subdir outside baseDir is not allowed")
}
type Match struct {
@@ -227,7 +228,7 @@ func (t *DocsSearchTool) Execute(ctx context.Context, input map[string]any, tc *
Line: line,
})
if len(matches) >= maxResults {
- return fmt.Errorf("max_results_reached")
+ return errors.New("max_results_reached")
}
}
}
diff --git a/pkg/mcpserver/server.go b/pkg/mcpserver/server.go
index 32420f5..d672213 100644
--- a/pkg/mcpserver/server.go
+++ b/pkg/mcpserver/server.go
@@ -3,6 +3,7 @@ package mcpserver
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"net/http"
"time"
@@ -35,10 +36,10 @@ type Config struct {
// New 创建一个 MCP Server 实例
func New(cfg *Config) (*Server, error) {
if cfg == nil {
- return nil, fmt.Errorf("mcpserver.Config cannot be nil")
+ return nil, errors.New("mcpserver.Config cannot be nil")
}
if cfg.Registry == nil {
- return nil, fmt.Errorf("registry is required")
+ return nil, errors.New("registry is required")
}
executor := cfg.Executor
@@ -127,7 +128,7 @@ func (s *Server) handleToolsCall(w http.ResponseWriter, ctx context.Context, req
tool, err := s.registry.Create(params.Name, nil)
if err != nil {
- writeError(w, req.ID, -32601, fmt.Sprintf("tool not found: %s", params.Name), err)
+ writeError(w, req.ID, -32601, "tool not found: "+params.Name, err)
return
}
diff --git a/pkg/media/processor.go b/pkg/media/processor.go
index 189f9ef..b90a3c8 100644
--- a/pkg/media/processor.go
+++ b/pkg/media/processor.go
@@ -7,6 +7,7 @@ import (
_ "image/jpeg"
_ "image/png"
"os"
+ "slices"
)
// Processor 媒体处理器
@@ -54,13 +55,7 @@ func (p *Processor) ValidateImage(img *Image) error {
// 检查类型
if img.MimeType != "" {
- valid := false
- for _, allowed := range p.AllowedImageTypes {
- if img.MimeType == allowed {
- valid = true
- break
- }
- }
+ valid := slices.Contains(p.AllowedImageTypes, img.MimeType)
if !valid {
return fmt.Errorf("image type %s not allowed", img.MimeType)
}
@@ -94,13 +89,7 @@ func (p *Processor) ValidateVideo(video *Video) error {
// 检查类型
if video.MimeType != "" {
- valid := false
- for _, allowed := range p.AllowedVideoTypes {
- if video.MimeType == allowed {
- valid = true
- break
- }
- }
+ valid := slices.Contains(p.AllowedVideoTypes, video.MimeType)
if !valid {
return fmt.Errorf("video type %s not allowed", video.MimeType)
}
@@ -118,13 +107,7 @@ func (p *Processor) ValidateAudio(audio *Audio) error {
// 检查类型
if audio.MimeType != "" {
- valid := false
- for _, allowed := range p.AllowedAudioTypes {
- if audio.MimeType == allowed {
- valid = true
- break
- }
- }
+ valid := slices.Contains(p.AllowedAudioTypes, audio.MimeType)
if !valid {
return fmt.Errorf("audio type %s not allowed", audio.MimeType)
}
diff --git a/pkg/memory/auto/types.go b/pkg/memory/auto/types.go
index 6f00f37..b609501 100644
--- a/pkg/memory/auto/types.go
+++ b/pkg/memory/auto/types.go
@@ -3,6 +3,7 @@
package auto
import (
+ "slices"
"time"
)
@@ -102,10 +103,8 @@ func (m *Memory) AddTag(tag string) {
tag = "#" + tag
}
// 检查重复
- for _, t := range m.Tags {
- if t == tag {
- return
- }
+ if slices.Contains(m.Tags, tag) {
+ return
}
m.Tags = append(m.Tags, tag)
m.UpdatedAt = time.Now()
@@ -116,12 +115,7 @@ func (m *Memory) HasTag(tag string) bool {
if len(tag) > 0 && tag[0] != '#' {
tag = "#" + tag
}
- for _, t := range m.Tags {
- if t == tag {
- return true
- }
- }
- return false
+ return slices.Contains(m.Tags, tag)
}
// MarkAccessed 标记访问
diff --git a/pkg/memory/bridge.go b/pkg/memory/bridge.go
index df2ecda..677a386 100644
--- a/pkg/memory/bridge.go
+++ b/pkg/memory/bridge.go
@@ -2,7 +2,9 @@ package memory
import (
"context"
+ "errors"
"fmt"
+ "maps"
"strings"
"github.com/astercloud/aster/pkg/session"
@@ -36,7 +38,7 @@ func (b *LongTermBridge) SaveSessionToSemanticMemory(
cfg *LongTermBridgeConfig,
) error {
if b == nil || b.Sessions == nil || b.SemanticMemory == nil || !b.SemanticMemory.Enabled() {
- return fmt.Errorf("long-term bridge is not properly configured")
+ return errors.New("long-term bridge is not properly configured")
}
// 加载 Session 事件
@@ -49,11 +51,11 @@ func (b *LongTermBridge) SaveSessionToSemanticMemory(
return fmt.Errorf("get session: %w", err)
}
if sess == nil {
- return fmt.Errorf("session not found")
+ return errors.New("session not found")
}
if sess.Events() == nil || sess.Events().Len() == 0 {
- return fmt.Errorf("session has no events")
+ return errors.New("session has no events")
}
events := sess.Events()
@@ -71,14 +73,14 @@ func (b *LongTermBridge) SaveSessionToSemanticMemory(
}
if len(lines) == 0 {
- return fmt.Errorf("no textual content to save")
+ return errors.New("no textual content to save")
}
joined := strings.Join(lines, "\n")
if cfg != nil && cfg.MinTokens > 0 {
if tokenCount(joined) < cfg.MinTokens {
- return fmt.Errorf("session content too short, skip saving")
+ return errors.New("session content too short, skip saving")
}
}
@@ -86,9 +88,7 @@ func (b *LongTermBridge) SaveSessionToSemanticMemory(
docID := fmt.Sprintf("%s/%s/%s", appName, userID, sessionID)
meta := make(map[string]any, len(scopeMeta)+2)
- for k, v := range scopeMeta {
- meta[k] = v
- }
+ maps.Copy(meta, scopeMeta)
meta["app_name"] = appName
meta["session_id"] = sessionID
diff --git a/pkg/memory/consolidation_strategies.go b/pkg/memory/consolidation_strategies.go
index 95f7e29..7767b2b 100644
--- a/pkg/memory/consolidation_strategies.go
+++ b/pkg/memory/consolidation_strategies.go
@@ -2,6 +2,7 @@ package memory
import (
"context"
+ "errors"
"fmt"
"strings"
"time"
@@ -45,7 +46,7 @@ func (s *RedundancyStrategy) ShouldConsolidate(ctx context.Context, memories []M
// Consolidate 执行合并。
func (s *RedundancyStrategy) Consolidate(ctx context.Context, memories []MemoryWithScore, llm LLMProvider) (*ConsolidatedMemory, error) {
if len(memories) == 0 {
- return nil, fmt.Errorf("no memories to consolidate")
+ return nil, errors.New("no memories to consolidate")
}
// 构建 LLM 提示
@@ -243,7 +244,7 @@ func (s *ConflictResolutionStrategy) detectConflict(memories []MemoryWithScore)
// Consolidate 执行合并。
func (s *ConflictResolutionStrategy) Consolidate(ctx context.Context, memories []MemoryWithScore, llm LLMProvider) (*ConsolidatedMemory, error) {
if len(memories) == 0 {
- return nil, fmt.Errorf("no memories to consolidate")
+ return nil, errors.New("no memories to consolidate")
}
// 构建冲突解决提示
@@ -371,7 +372,7 @@ func (s *SummarizationStrategy) ShouldConsolidate(ctx context.Context, memories
// Consolidate 执行合并。
func (s *SummarizationStrategy) Consolidate(ctx context.Context, memories []MemoryWithScore, llm LLMProvider) (*ConsolidatedMemory, error) {
if len(memories) == 0 {
- return nil, fmt.Errorf("no memories to consolidate")
+ return nil, errors.New("no memories to consolidate")
}
// 构建总结提示
diff --git a/pkg/memory/consolidation_test.go b/pkg/memory/consolidation_test.go
index 202af6a..c2fe309 100644
--- a/pkg/memory/consolidation_test.go
+++ b/pkg/memory/consolidation_test.go
@@ -2,7 +2,7 @@ package memory
import (
"context"
- "fmt"
+ "errors"
"strings"
"testing"
"time"
@@ -372,7 +372,7 @@ func TestMergeMetadata(t *testing.T) {
func TestLLMProviderError(t *testing.T) {
strategy := NewRedundancyStrategy(0.85)
llm := &MockLLMProvider{
- Error: fmt.Errorf("LLM API error"),
+ Error: errors.New("LLM API error"),
}
memories := []MemoryWithScore{
diff --git a/pkg/memory/dialog/extractor.go b/pkg/memory/dialog/extractor.go
index a380e4a..ee0bc81 100644
--- a/pkg/memory/dialog/extractor.go
+++ b/pkg/memory/dialog/extractor.go
@@ -196,14 +196,8 @@ func (e *Extractor) extractSentence(text, keyword string) string {
}
halfLen := e.config.MaxExtractLength / 2
- newStart := keywordRuneIdx - halfLen
- if newStart < 0 {
- newStart = 0
- }
- newEnd := keywordRuneIdx + len(keywordRunes) + halfLen
- if newEnd > len(sentenceRunes) {
- newEnd = len(sentenceRunes)
- }
+ newStart := max(keywordRuneIdx-halfLen, 0)
+ newEnd := min(keywordRuneIdx+len(keywordRunes)+halfLen, len(sentenceRunes))
sentence = "..." + strings.TrimSpace(string(sentenceRunes[newStart:newEnd])) + "..."
}
diff --git a/pkg/memory/lineage.go b/pkg/memory/lineage.go
index ea270ae..463005a 100644
--- a/pkg/memory/lineage.go
+++ b/pkg/memory/lineage.go
@@ -2,7 +2,9 @@ package memory
import (
"context"
+ "errors"
"fmt"
+ "slices"
"sync"
"github.com/astercloud/aster/pkg/logging"
@@ -122,11 +124,8 @@ func (lg *LineageGraph) GetMemoriesBySource(sourceID string) []string {
var result []string
for memID, metadata := range lg.memoryMetadata {
- for _, sid := range metadata.SourceIDs {
- if sid == sourceID {
- result = append(result, memID)
- break
- }
+ if slices.Contains(metadata.SourceIDs, sourceID) {
+ result = append(result, memID)
}
}
return result
@@ -182,7 +181,7 @@ func NewLineageManager() *LineageManager {
// TrackMemoryCreation 追踪记忆创建事件。
func (lm *LineageManager) TrackMemoryCreation(memoryID string, provenance *MemoryProvenance, derivedFromIDs []string) error {
if memoryID == "" {
- return fmt.Errorf("memoryID is required")
+ return errors.New("memoryID is required")
}
metadata := &LineageMetadata{
diff --git a/pkg/memory/logic/consolidation.go b/pkg/memory/logic/consolidation.go
index 48ac273..bbfa3e5 100644
--- a/pkg/memory/logic/consolidation.go
+++ b/pkg/memory/logic/consolidation.go
@@ -247,7 +247,7 @@ func (e *ConsolidationEngine) findSimilarGroups(memories []*LogicMemory) [][]*Lo
}
// 计算相似度并合并
- for i := 0; i < n; i++ {
+ for i := range n {
// 跳过高置信度的 Memory
if memories[i].Provenance != nil &&
memories[i].Provenance.Confidence >= e.config.PreserveHighConfidenceThreshold {
@@ -264,7 +264,7 @@ func (e *ConsolidationEngine) findSimilarGroups(memories []*LogicMemory) [][]*Lo
// 收集组
groups := make(map[int][]*LogicMemory)
- for i := 0; i < n; i++ {
+ for i := range n {
root := find(i)
groups[root] = append(groups[root], memories[i])
}
diff --git a/pkg/memory/logic/manager.go b/pkg/memory/logic/manager.go
index 547ccac..9e251f3 100644
--- a/pkg/memory/logic/manager.go
+++ b/pkg/memory/logic/manager.go
@@ -2,7 +2,8 @@ package logic
import (
"context"
- "fmt"
+ "errors"
+ "maps"
"time"
"github.com/astercloud/aster/pkg/memory"
@@ -46,11 +47,11 @@ type ManagerConfig struct {
// NewManager 创建 Logic Memory Manager
func NewManager(config *ManagerConfig) (*Manager, error) {
if config == nil {
- return nil, fmt.Errorf("config is required")
+ return nil, errors.New("config is required")
}
if config.Store == nil {
- return nil, fmt.Errorf("store is required")
+ return nil, errors.New("store is required")
}
// 设置默认值
@@ -241,9 +242,7 @@ func (m *Manager) mergeMemory(existing, new *LogicMemory) {
if existing.Metadata == nil {
existing.Metadata = make(map[string]any)
}
- for k, v := range new.Metadata {
- existing.Metadata[k] = v
- }
+ maps.Copy(existing.Metadata, new.Metadata)
}
// 6. 更新时间
diff --git a/pkg/memory/logic/metrics.go b/pkg/memory/logic/metrics.go
index ddc1005..de1a087 100644
--- a/pkg/memory/logic/metrics.go
+++ b/pkg/memory/logic/metrics.go
@@ -1,6 +1,7 @@
package logic
import (
+ "maps"
"sync"
"time"
)
@@ -150,15 +151,9 @@ func (m *Metrics) GetSnapshot() *MetricsSnapshot {
}
// 复制 map
- for k, v := range m.memoryTotal {
- snapshot.MemoryByNamespace[k] = v
- }
- for k, v := range m.memoryByType {
- snapshot.MemoryByType[k] = v
- }
- for k, v := range m.memoryByScope {
- snapshot.MemoryByScope[k] = v
- }
+ maps.Copy(snapshot.MemoryByNamespace, m.memoryTotal)
+ maps.Copy(snapshot.MemoryByType, m.memoryByType)
+ maps.Copy(snapshot.MemoryByScope, m.memoryByScope)
// 计算平均耗时
snapshot.AvgSaveDuration = m.calculateAvgDuration(m.saveDurations)
@@ -221,8 +216,8 @@ func (m *Metrics) calculateP99Duration(durations []time.Duration) time.Duration
copy(sorted, durations)
// 简单冒泡排序(样本量小,性能可接受)
- for i := 0; i < len(sorted)-1; i++ {
- for j := 0; j < len(sorted)-i-1; j++ {
+ for i := range len(sorted) - 1 {
+ for j := range len(sorted) - i - 1 {
if sorted[j] > sorted[j+1] {
sorted[j], sorted[j+1] = sorted[j+1], sorted[j]
}
diff --git a/pkg/memory/logic/store_mysql.go b/pkg/memory/logic/store_mysql.go
index 5853dad..b95ed80 100644
--- a/pkg/memory/logic/store_mysql.go
+++ b/pkg/memory/logic/store_mysql.go
@@ -4,7 +4,9 @@ import (
"context"
"database/sql"
"encoding/json"
+ "errors"
"fmt"
+ "strings"
"time"
"github.com/astercloud/aster/pkg/memory"
@@ -32,11 +34,11 @@ type MySQLStoreConfig struct {
// NewMySQLStore 创建 MySQL 存储
func NewMySQLStore(config *MySQLStoreConfig) (*MySQLStore, error) {
if config == nil {
- return nil, fmt.Errorf("config is required")
+ return nil, errors.New("config is required")
}
if config.DB == nil {
- return nil, fmt.Errorf("database connection is required")
+ return nil, errors.New("database connection is required")
}
tableName := config.TableName
@@ -440,9 +442,11 @@ func (s *MySQLStore) Prune(ctx context.Context, criteria PruneCriteria) (int, er
}
query := fmt.Sprintf("DELETE FROM %s WHERE %s", s.tableName, conditions[0])
+ var querySb443 strings.Builder
for i := 1; i < len(conditions); i++ {
- query += " OR " + conditions[i]
+ querySb443.WriteString(" OR " + conditions[i])
}
+ query += querySb443.String()
result, err := s.db.ExecContext(ctx, query, args...)
if err != nil {
diff --git a/pkg/memory/logic/store_postgres.go b/pkg/memory/logic/store_postgres.go
index edc66c0..2188f7d 100644
--- a/pkg/memory/logic/store_postgres.go
+++ b/pkg/memory/logic/store_postgres.go
@@ -4,7 +4,9 @@ import (
"context"
"database/sql"
"encoding/json"
+ "errors"
"fmt"
+ "strings"
"time"
"github.com/astercloud/aster/pkg/memory"
@@ -32,11 +34,11 @@ type PostgreSQLStoreConfig struct {
// NewPostgreSQLStore 创建 PostgreSQL 存储
func NewPostgreSQLStore(config *PostgreSQLStoreConfig) (*PostgreSQLStore, error) {
if config == nil {
- return nil, fmt.Errorf("config is required")
+ return nil, errors.New("config is required")
}
if config.DB == nil {
- return nil, fmt.Errorf("database connection is required")
+ return nil, errors.New("database connection is required")
}
tableName := config.TableName
@@ -460,9 +462,11 @@ func (s *PostgreSQLStore) Prune(ctx context.Context, criteria PruneCriteria) (in
WHERE %s
`, s.tableName, conditions[0])
+ var querySb463 strings.Builder
for i := 1; i < len(conditions); i++ {
- query += " OR " + conditions[i]
+ querySb463.WriteString(" OR " + conditions[i])
}
+ query += querySb463.String()
result, err := s.db.ExecContext(ctx, query, args...)
if err != nil {
diff --git a/pkg/memory/logic/store_test.go b/pkg/memory/logic/store_test.go
index c9a4bca..3a7fe05 100644
--- a/pkg/memory/logic/store_test.go
+++ b/pkg/memory/logic/store_test.go
@@ -204,7 +204,7 @@ func TestInMemoryStore_GetTopK(t *testing.T) {
ctx := context.Background()
namespace := "user:123"
- for i := 0; i < 5; i++ {
+ for i := range 5 {
require.NoError(t, store.Save(ctx, &LogicMemory{
Namespace: namespace,
Key: string(rune('a' + i)),
diff --git a/pkg/memory/memory.go b/pkg/memory/memory.go
index 58984d1..8d5e79c 100644
--- a/pkg/memory/memory.go
+++ b/pkg/memory/memory.go
@@ -2,6 +2,7 @@ package memory
import (
"context"
+ "errors"
"fmt"
"path/filepath"
"regexp"
@@ -54,10 +55,10 @@ type SearchMatch struct {
// NewManager 创建 Memory 管理器
func NewManager(cfg *ManagerConfig) (*Manager, error) {
if cfg == nil {
- return nil, fmt.Errorf("memory.ManagerConfig cannot be nil")
+ return nil, errors.New("memory.ManagerConfig cannot be nil")
}
if cfg.Backend == nil {
- return nil, fmt.Errorf("memory.Manager requires a non-nil Backend")
+ return nil, errors.New("memory.Manager requires a non-nil Backend")
}
memoryPath := cfg.MemoryPath
@@ -88,7 +89,7 @@ func (m *Manager) ListFiles(ctx context.Context) ([]backends.FileInfo, error) {
// name 为相对于 memoryPath 的路径,例如 "project_notes.md" 或 "user/alice.md"
func (m *Manager) ReadFile(ctx context.Context, name string) (string, error) {
if name == "" {
- return "", fmt.Errorf("memory.ReadFile: name cannot be empty")
+ return "", errors.New("memory.ReadFile: name cannot be empty")
}
path := m.resolvePath(name)
return m.backend.Read(ctx, path, 0, 0)
@@ -100,10 +101,10 @@ func (m *Manager) ReadFile(ctx context.Context, name string) (string, error) {
// content: 记忆内容正文
func (m *Manager) AppendNote(ctx context.Context, file, title, content string) (string, error) {
if file == "" {
- return "", fmt.Errorf("memory.AppendNote: file cannot be empty")
+ return "", errors.New("memory.AppendNote: file cannot be empty")
}
if strings.TrimSpace(content) == "" {
- return "", fmt.Errorf("memory.AppendNote: content cannot be empty")
+ return "", errors.New("memory.AppendNote: content cannot be empty")
}
path := m.resolvePath(file)
@@ -143,10 +144,10 @@ func (m *Manager) AppendNote(ctx context.Context, file, title, content string) (
// 与 AppendNote 不同,该方法会丢弃原有内容,仅保留新的标题与正文
func (m *Manager) OverwriteWithNote(ctx context.Context, file, title, content string) (string, error) {
if file == "" {
- return "", fmt.Errorf("memory.OverwriteWithNote: file cannot be empty")
+ return "", errors.New("memory.OverwriteWithNote: file cannot be empty")
}
if strings.TrimSpace(content) == "" {
- return "", fmt.Errorf("memory.OverwriteWithNote: content cannot be empty")
+ return "", errors.New("memory.OverwriteWithNote: content cannot be empty")
}
path := m.resolvePath(file)
@@ -169,11 +170,11 @@ func (m *Manager) OverwriteWithNote(ctx context.Context, file, title, content st
// 默认使用大小写不敏感的字面量匹配,可选正则模式
func (m *Manager) Search(ctx context.Context, opts *SearchOptions) ([]SearchMatch, error) {
if opts == nil {
- return nil, fmt.Errorf("memory.Search: options cannot be nil")
+ return nil, errors.New("memory.Search: options cannot be nil")
}
rawQuery := strings.TrimSpace(opts.Query)
if rawQuery == "" {
- return nil, fmt.Errorf("memory.Search: query cannot be empty")
+ return nil, errors.New("memory.Search: query cannot be empty")
}
var pattern string
diff --git a/pkg/memory/observation_compressor.go b/pkg/memory/observation_compressor.go
index c393542..b5f8640 100644
--- a/pkg/memory/observation_compressor.go
+++ b/pkg/memory/observation_compressor.go
@@ -4,6 +4,7 @@ import (
"context"
"crypto/sha256"
"encoding/hex"
+ "errors"
"fmt"
"regexp"
"strings"
@@ -475,7 +476,7 @@ func (c *DefaultObservationCompressor) CanRecover(compressed *CompressedObservat
// 注意:这需要外部工具(如文件系统访问)的支持
func (c *DefaultObservationCompressor) Recover(ctx context.Context, compressed *CompressedObservation) (string, error) {
if !compressed.Recoverable {
- return "", fmt.Errorf("content is not recoverable")
+ return "", errors.New("content is not recoverable")
}
// 对于文件引用,返回如何恢复的指令
@@ -485,7 +486,7 @@ func (c *DefaultObservationCompressor) Recover(ctx context.Context, compressed *
}
}
- return "", fmt.Errorf("no recovery method available")
+ return "", errors.New("no recovery method available")
}
// 辅助函数
diff --git a/pkg/memory/observation_compressor_test.go b/pkg/memory/observation_compressor_test.go
index 8be2821..86680d8 100644
--- a/pkg/memory/observation_compressor_test.go
+++ b/pkg/memory/observation_compressor_test.go
@@ -34,9 +34,9 @@ func TestDefaultObservationCompressor_Compress_LongContent(t *testing.T) {
// 生成长内容
var builder strings.Builder
- for i := 0; i < 200; i++ {
+ for i := range 200 {
builder.WriteString("Line ")
- builder.WriteString(string(rune('0' + i%10)))
+ builder.WriteRune(rune('0' + i%10))
builder.WriteString(": This is a test line with some content\n")
}
longContent := builder.String()
@@ -148,13 +148,13 @@ func TestDefaultObservationCompressor_CompressByToolType_Bash(t *testing.T) {
// 生成 Bash 输出(包含错误)
var builder strings.Builder
- for i := 0; i < 100; i++ {
+ for i := range 100 {
builder.WriteString("Output line ")
- builder.WriteString(string(rune('0' + i%10)))
+ builder.WriteRune(rune('0' + i%10))
builder.WriteString("\n")
}
builder.WriteString("Error: Something went wrong\n")
- for i := 0; i < 50; i++ {
+ for range 50 {
builder.WriteString("More output\n")
}
@@ -175,7 +175,7 @@ func TestDefaultObservationCompressor_CompressByToolType_Grep(t *testing.T) {
// 生成搜索结果
var builder strings.Builder
- for i := 0; i < 100; i++ {
+ for range 100 {
builder.WriteString("/path/to/file.go:123: match found\n")
}
diff --git a/pkg/memory/preference.go b/pkg/memory/preference.go
index 5f8b715..ddce085 100644
--- a/pkg/memory/preference.go
+++ b/pkg/memory/preference.go
@@ -2,6 +2,7 @@ package memory
import (
"context"
+ "errors"
"fmt"
"sort"
"strings"
@@ -197,7 +198,7 @@ func (pm *PreferenceManager) GetPreference(
pref := pm.findExistingPreference(userID, category, key)
if pref == nil {
- return nil, fmt.Errorf("preference not found")
+ return nil, errors.New("preference not found")
}
// 更新访问计数
@@ -238,7 +239,7 @@ func (pm *PreferenceManager) UpdatePreference(
pref := pm.findExistingPreference(userID, category, key)
if pref == nil {
- return fmt.Errorf("preference not found")
+ return errors.New("preference not found")
}
pref.Value = value
diff --git a/pkg/memory/preference_storage.go b/pkg/memory/preference_storage.go
index 8f5530b..3bfc8d4 100644
--- a/pkg/memory/preference_storage.go
+++ b/pkg/memory/preference_storage.go
@@ -61,7 +61,7 @@ func (fs *FilePreferenceStorage) Save(
}
// 写入文件
- filePath := filepath.Join(fs.dir, fmt.Sprintf("%s.json", userID))
+ filePath := filepath.Join(fs.dir, userID+".json")
if err := os.WriteFile(filePath, data, 0644); err != nil {
return fmt.Errorf("failed to write file: %w", err)
}
@@ -78,7 +78,7 @@ func (fs *FilePreferenceStorage) Load(
defer fs.mu.RUnlock()
// 读取文件
- filePath := filepath.Join(fs.dir, fmt.Sprintf("%s.json", userID))
+ filePath := filepath.Join(fs.dir, userID+".json")
data, err := os.ReadFile(filePath)
if err != nil {
if os.IsNotExist(err) {
@@ -101,7 +101,7 @@ func (fs *FilePreferenceStorage) Delete(ctx context.Context, userID string) erro
fs.mu.Lock()
defer fs.mu.Unlock()
- filePath := filepath.Join(fs.dir, fmt.Sprintf("%s.json", userID))
+ filePath := filepath.Join(fs.dir, userID+".json")
if err := os.Remove(filePath); err != nil {
if os.IsNotExist(err) {
return nil // 文件不存在视为成功
@@ -142,6 +142,7 @@ func (fs *FilePreferenceStorage) List(ctx context.Context) ([]string, error) {
// PersistentPreferenceManager 带持久化的偏好管理器
type PersistentPreferenceManager struct {
*PreferenceManager
+
storage PreferenceStorage
}
diff --git a/pkg/memory/provenance.go b/pkg/memory/provenance.go
index ebd00bb..2c3f1d4 100644
--- a/pkg/memory/provenance.go
+++ b/pkg/memory/provenance.go
@@ -1,6 +1,7 @@
package memory
import (
+ "slices"
"time"
)
@@ -117,10 +118,8 @@ func calculateInitialConfidence(sourceType SourceType, isExplicit bool) float64
// 用于追踪记忆被多个来源确认。
func (p *MemoryProvenance) AddSource(sourceID string) {
// 检查是否已存在
- for _, s := range p.Sources {
- if s == sourceID {
- return
- }
+ if slices.Contains(p.Sources, sourceID) {
+ return
}
p.Sources = append(p.Sources, sourceID)
p.UpdatedAt = time.Now()
diff --git a/pkg/memory/provenance_test.go b/pkg/memory/provenance_test.go
index 1c00efc..4dbc3bf 100644
--- a/pkg/memory/provenance_test.go
+++ b/pkg/memory/provenance_test.go
@@ -117,7 +117,7 @@ func TestProvenance_Corroborate(t *testing.T) {
}
// Multiple corroborations
- for i := 0; i < 5; i++ {
+ for i := range 5 {
p.Corroborate("source-" + string(rune('3'+i)))
}
diff --git a/pkg/memory/quality_analyzer.go b/pkg/memory/quality_analyzer.go
index 4fc2565..3fa6df2 100644
--- a/pkg/memory/quality_analyzer.go
+++ b/pkg/memory/quality_analyzer.go
@@ -75,7 +75,7 @@ func (qa *QualityAnalyzer) detectContradictions(memories []MemoryWithScore) []In
inconsistencies := []Inconsistency{}
// 简化版:检查内容中的否定关系
- for i := range len(memories) {
+ for i := range memories {
for j := i + 1; j < len(memories); j++ {
if qa.areContradictory(memories[i], memories[j]) {
severity := qa.calculateContradictionSeverity(memories[i], memories[j])
@@ -148,7 +148,7 @@ func (qa *QualityAnalyzer) calculateContradictionSeverity(mem1, mem2 MemoryWithS
func (qa *QualityAnalyzer) detectDuplicates(memories []MemoryWithScore) []Inconsistency {
inconsistencies := []Inconsistency{}
- for i := range len(memories) {
+ for i := range memories {
for j := i + 1; j < len(memories); j++ {
similarity := qa.calculateSimilarity(memories[i].Text, memories[j].Text)
diff --git a/pkg/memory/quality_analyzer_test.go b/pkg/memory/quality_analyzer_test.go
index 6e1ec0a..e3f9b82 100644
--- a/pkg/memory/quality_analyzer_test.go
+++ b/pkg/memory/quality_analyzer_test.go
@@ -336,7 +336,7 @@ func TestGenerateReport(t *testing.T) {
// 评估一些记忆
memories := []MemoryWithScore{}
- for i := 0; i < 5; i++ {
+ for i := range 5 {
prov := NewProvenance(SourceUserInput, "user-1")
prov.Confidence = float64(i+1) * 0.15
diff --git a/pkg/memory/quality_metrics.go b/pkg/memory/quality_metrics.go
index a655d3d..447f152 100644
--- a/pkg/memory/quality_metrics.go
+++ b/pkg/memory/quality_metrics.go
@@ -424,7 +424,7 @@ func RankByQuality(
copy(ranked, memories)
// 按质量综合得分排序
- for i := range len(ranked) {
+ for i := range ranked {
for j := i + 1; j < len(ranked); j++ {
// 获取质量分数
scoreI := ranked[i].Score
diff --git a/pkg/memory/quality_metrics_test.go b/pkg/memory/quality_metrics_test.go
index e515089..41f9e5b 100644
--- a/pkg/memory/quality_metrics_test.go
+++ b/pkg/memory/quality_metrics_test.go
@@ -118,7 +118,7 @@ func TestQualityMetrics_GetAll(t *testing.T) {
qm := NewQualityMetrics(config)
// 添加多个质量评估
- for i := 0; i < 3; i++ {
+ for i := range 3 {
memory := &MemoryWithScore{
DocID: string(rune('A' + i)),
Text: "Test content",
@@ -204,7 +204,7 @@ func TestQualityMetrics_Clear(t *testing.T) {
qm := NewQualityMetrics(config)
// 添加多个
- for i := 0; i < 3; i++ {
+ for i := range 3 {
memory := &MemoryWithScore{
DocID: string(rune('A' + i)),
Text: "Test",
diff --git a/pkg/memory/reference_registry.go b/pkg/memory/reference_registry.go
index a1bd664..ffbd473 100644
--- a/pkg/memory/reference_registry.go
+++ b/pkg/memory/reference_registry.go
@@ -254,7 +254,7 @@ func (r *InMemoryReferenceRegistry) evictOldest() {
// sortByLastAccessed 按最后访问时间排序(降序)
func sortByLastAccessed(refs []ReferenceInfo) {
// 简单的冒泡排序,因为通常数据量不大
- for i := 0; i < len(refs)-1; i++ {
+ for i := range len(refs) - 1 {
for j := i + 1; j < len(refs); j++ {
if refs[j].LastAccessed.After(refs[i].LastAccessed) {
refs[i], refs[j] = refs[j], refs[i]
diff --git a/pkg/memory/reference_registry_test.go b/pkg/memory/reference_registry_test.go
index 063e904..87d2320 100644
--- a/pkg/memory/reference_registry_test.go
+++ b/pkg/memory/reference_registry_test.go
@@ -95,7 +95,7 @@ func TestInMemoryReferenceRegistry_ListRecent(t *testing.T) {
ctx := context.Background()
// 注册多个引用
- for i := 0; i < 10; i++ {
+ for i := range 10 {
ref := Reference{
Type: ReferenceTypeFilePath,
Value: "/file" + string(rune('0'+i)) + ".go",
@@ -197,7 +197,7 @@ func TestInMemoryReferenceRegistry_MaxSize(t *testing.T) {
ctx := context.Background()
// 注册超过最大数量的引用
- for i := 0; i < 10; i++ {
+ for i := range 10 {
ref := Reference{
Type: ReferenceTypeFilePath,
Value: "/file" + string(rune('0'+i)) + ".go",
diff --git a/pkg/memory/rules/manager.go b/pkg/memory/rules/manager.go
index 0a04bbd..2699a59 100644
--- a/pkg/memory/rules/manager.go
+++ b/pkg/memory/rules/manager.go
@@ -200,11 +200,11 @@ func (m *Manager) parseMarkdownRules(content, sourcePath string, scope Scope) ([
// extractTitle 从 Markdown 中提取标题
func extractTitle(content string) string {
- lines := strings.Split(content, "\n")
- for _, line := range lines {
+ lines := strings.SplitSeq(content, "\n")
+ for line := range lines {
line = strings.TrimSpace(line)
- if strings.HasPrefix(line, "# ") {
- return strings.TrimPrefix(line, "# ")
+ if after, ok := strings.CutPrefix(line, "# "); ok {
+ return after
}
}
return ""
diff --git a/pkg/memory/rules/types.go b/pkg/memory/rules/types.go
index 1055d34..849ee32 100644
--- a/pkg/memory/rules/types.go
+++ b/pkg/memory/rules/types.go
@@ -3,6 +3,7 @@
package rules
import (
+ "strings"
"time"
)
@@ -136,15 +137,17 @@ func (rs *RuleSet) GetRulesContent(projectID string) string {
}
var content string
+ var contentSb139 strings.Builder
for _, r := range rules {
- content += r.Content + "\n\n"
+ contentSb139.WriteString(r.Content + "\n\n")
}
+ content += contentSb139.String()
return content
}
// sortRulesByPriority 按优先级排序
func sortRulesByPriority(rules []*Rule) {
- for i := 0; i < len(rules)-1; i++ {
+ for i := range len(rules) - 1 {
for j := i + 1; j < len(rules); j++ {
if rules[j].Priority > rules[i].Priority {
rules[i], rules[j] = rules[j], rules[i]
diff --git a/pkg/memory/schema.go b/pkg/memory/schema.go
index 5517467..0057141 100644
--- a/pkg/memory/schema.go
+++ b/pkg/memory/schema.go
@@ -2,7 +2,9 @@ package memory
import (
"encoding/json"
+ "errors"
"fmt"
+ "slices"
)
// JSONSchema JSON Schema 定义
@@ -21,7 +23,7 @@ type JSONSchema struct {
// Validate 验证 Schema 本身是否合法
func (s *JSONSchema) Validate() error {
if s == nil {
- return fmt.Errorf("schema cannot be nil")
+ return errors.New("schema cannot be nil")
}
validTypes := map[string]bool{
@@ -100,13 +102,7 @@ func (s *JSONSchema) validateValue(value any) error {
// 验证枚举
if len(s.Enum) > 0 {
- found := false
- for _, enumVal := range s.Enum {
- if value == enumVal {
- found = true
- break
- }
- }
+ found := slices.Contains(s.Enum, value)
if !found {
return fmt.Errorf("value %v is not in enum: %v", value, s.Enum)
}
diff --git a/pkg/memory/semantic.go b/pkg/memory/semantic.go
index a5385ed..d6129ed 100644
--- a/pkg/memory/semantic.go
+++ b/pkg/memory/semantic.go
@@ -2,7 +2,9 @@ package memory
import (
"context"
+ "errors"
"fmt"
+ "strings"
"github.com/astercloud/aster/pkg/vector"
)
@@ -115,10 +117,10 @@ func (sm *SemanticMemory) IndexWithProvenance(ctx context.Context, docID string,
return nil
}
if !sm.cfg.EnableProvenance {
- return fmt.Errorf("provenance not enabled")
+ return errors.New("provenance not enabled")
}
if provenance == nil {
- return fmt.Errorf("provenance is required")
+ return errors.New("provenance is required")
}
// 追踪谱系
@@ -135,7 +137,7 @@ func (sm *SemanticMemory) IndexWithProvenance(ctx context.Context, docID string,
// indexInternal 内部索引方法。
func (sm *SemanticMemory) indexInternal(ctx context.Context, docID string, text string, meta map[string]any, provenance *MemoryProvenance) error {
if docID == "" || text == "" {
- return fmt.Errorf("docID and text are required")
+ return errors.New("docID and text are required")
}
vecs, err := sm.cfg.Embedder.EmbedText(ctx, []string{text})
@@ -143,7 +145,7 @@ func (sm *SemanticMemory) indexInternal(ctx context.Context, docID string, text
return fmt.Errorf("embed text: %w", err)
}
if len(vecs) == 0 {
- return fmt.Errorf("embedder returned empty vectors")
+ return errors.New("embedder returned empty vectors")
}
// 将文本复制到 metadata 中, 方便检索结果直接携带原文片段。
@@ -190,7 +192,7 @@ func (sm *SemanticMemory) Search(ctx context.Context, query string, meta map[str
return nil, fmt.Errorf("embed query: %w", err)
}
if len(vecs) == 0 {
- return nil, fmt.Errorf("embedder returned empty vectors")
+ return nil, errors.New("embedder returned empty vectors")
}
return sm.cfg.Store.Query(ctx, vector.Query{
@@ -323,7 +325,7 @@ func (sm *SemanticMemory) SearchBySourceType(ctx context.Context, query string,
// 返回被删除的记忆ID列表。
func (sm *SemanticMemory) PruneMemories(ctx context.Context, namespace string) ([]string, error) {
if !sm.cfg.EnableProvenance || sm.cfg.ConfidenceCalculator == nil {
- return nil, fmt.Errorf("provenance or confidence calculator not enabled")
+ return nil, errors.New("provenance or confidence calculator not enabled")
}
// TODO: 这需要 VectorStore 支持列举所有文档的功能
@@ -357,7 +359,7 @@ func (sm *SemanticMemory) DeleteMemoryWithLineage(ctx context.Context, memoryID
// 这需要先检索记忆,然后提取 Provenance。
func (sm *SemanticMemory) GetMemoryProvenance(ctx context.Context, query string, meta map[string]any) (*MemoryProvenance, error) {
if !sm.cfg.EnableProvenance {
- return nil, fmt.Errorf("provenance not enabled")
+ return nil, errors.New("provenance not enabled")
}
hits, err := sm.Search(ctx, query, meta, 1)
@@ -366,7 +368,7 @@ func (sm *SemanticMemory) GetMemoryProvenance(ctx context.Context, query string,
}
if len(hits) == 0 {
- return nil, fmt.Errorf("no memory found")
+ return nil, errors.New("no memory found")
}
return FromMetadata(hits[0].Metadata), nil
@@ -395,7 +397,7 @@ func (sm *SemanticMemory) UpdateMetadata(ctx context.Context, docID string, meta
// 目前大多数向量数据库不支持原地更新元数据
// 需要重新索引或使用专门的更新 API
- return fmt.Errorf("UpdateMetadata not fully implemented yet")
+ return errors.New("UpdateMetadata not fully implemented yet")
}
// SearchAndFormat 执行向量检索并格式化为 Markdown,用于 RAG 场景。
@@ -420,6 +422,7 @@ func (sm *SemanticMemory) SearchAndFormat(ctx context.Context, query string, met
result += "## Relevant Context\n\n"
result += fmt.Sprintf("Found %d relevant documents:\n\n", len(hits))
+ var resultSb423 strings.Builder
for i, hit := range hits {
// 获取文本内容
text := ""
@@ -431,23 +434,26 @@ func (sm *SemanticMemory) SearchAndFormat(ctx context.Context, query string, met
score := hit.Score
scorePercent := int(score * 100)
- result += fmt.Sprintf("### %d. (Relevance: %d%%)\n\n", i+1, scorePercent)
+ resultSb423.WriteString(fmt.Sprintf("### %d. (Relevance: %d%%)\n\n", i+1, scorePercent))
if text != "" {
- result += text + "\n\n"
+ resultSb423.WriteString(text + "\n\n")
}
// 添加元数据(可选)
if len(hit.Metadata) > 1 { // 除了 text 之外还有其他元数据
- result += "**Metadata:**\n"
+ resultSb423.WriteString("**Metadata:**\n")
+ var resultSb443 strings.Builder
for k, v := range hit.Metadata {
if k != "text" { // 跳过已显示的 text
- result += fmt.Sprintf("- %s: %v\n", k, v)
+ resultSb443.WriteString(fmt.Sprintf("- %s: %v\n", k, v))
}
}
- result += "\n"
+ resultSb423.WriteString(resultSb443.String())
+ resultSb423.WriteString("\n")
}
}
+ result += resultSb423.String()
return result, nil
}
diff --git a/pkg/memory/session_compressor.go b/pkg/memory/session_compressor.go
index d00b0fa..e5ec134 100644
--- a/pkg/memory/session_compressor.go
+++ b/pkg/memory/session_compressor.go
@@ -368,7 +368,7 @@ func (m *MultiLevelCompressor) compressByTurns(ctx context.Context, messages []a
// 添加总结
compressed = append(compressed, agentext.Message{
Role: "assistant",
- Content: fmt.Sprintf("[对话轮次总结] %s", summary),
+ Content: "[对话轮次总结] " + summary,
})
}
diff --git a/pkg/memory/session_compressor_test.go b/pkg/memory/session_compressor_test.go
index 5fa657e..ed04f94 100644
--- a/pkg/memory/session_compressor_test.go
+++ b/pkg/memory/session_compressor_test.go
@@ -172,7 +172,7 @@ func TestMultiLevelCompressor_SummarizeSession(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
messages := make([]agentext.Message, tt.messageCount)
- for i := 0; i < tt.messageCount; i++ {
+ for i := range tt.messageCount {
role := "user"
if i%2 == 1 {
role = "assistant"
diff --git a/pkg/memory/session_manager.go b/pkg/memory/session_manager.go
index e970ef6..818e277 100644
--- a/pkg/memory/session_manager.go
+++ b/pkg/memory/session_manager.go
@@ -2,6 +2,7 @@ package memory
import (
"context"
+ "errors"
"fmt"
"sync"
"time"
@@ -158,17 +159,17 @@ func (m *SessionMemoryManager) ShareMemory(
// 检查权限(只有所有者可以共享)
if memory.OwnerID != fromSessionID {
- return fmt.Errorf("only owner can share memory")
+ return errors.New("only owner can share memory")
}
// 检查是否启用共享
if !m.config.EnableSharing {
- return fmt.Errorf("sharing is disabled")
+ return errors.New("sharing is disabled")
}
// 检查共享数量限制
if len(memory.SharedWith) >= m.config.MaxSharedSessions {
- return fmt.Errorf("max shared sessions limit reached")
+ return errors.New("max shared sessions limit reached")
}
// 添加共享权限
@@ -198,7 +199,7 @@ func (m *SessionMemoryManager) RevokeAccess(
// 检查权限
if memory.OwnerID != fromSessionID {
- return fmt.Errorf("only owner can revoke access")
+ return errors.New("only owner can revoke access")
}
// 删除共享权限
@@ -227,7 +228,7 @@ func (m *SessionMemoryManager) GetMemory(
// 检查访问权限
if !m.hasAccess(memory, sessionID, AccessRead) {
- return nil, fmt.Errorf("access denied")
+ return nil, errors.New("access denied")
}
return memory, nil
@@ -251,7 +252,7 @@ func (m *SessionMemoryManager) UpdateMemory(
// 检查写权限
if !m.hasAccess(memory, sessionID, AccessWrite) {
- return fmt.Errorf("write access denied")
+ return errors.New("write access denied")
}
// 更新内容
@@ -280,7 +281,7 @@ func (m *SessionMemoryManager) DeleteMemory(
// 只有所有者可以删除
if memory.OwnerID != sessionID {
- return fmt.Errorf("only owner can delete memory")
+ return errors.New("only owner can delete memory")
}
// 从所有索引中移除
diff --git a/pkg/memory/session_summary.go b/pkg/memory/session_summary.go
index 862bf12..3b4384e 100644
--- a/pkg/memory/session_summary.go
+++ b/pkg/memory/session_summary.go
@@ -3,6 +3,7 @@ package memory
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"strings"
"sync"
@@ -130,7 +131,7 @@ func (m *SessionSummaryManager) GenerateSummary(
messages []types.Message,
) (*SessionSummary, error) {
if !m.config.Enabled {
- return nil, fmt.Errorf("session summary is disabled")
+ return nil, errors.New("session summary is disabled")
}
// 构建提示词
@@ -184,7 +185,7 @@ func (m *SessionSummaryManager) UpdateSummary(
newMessages []types.Message,
) (*SessionSummary, error) {
if !m.config.Enabled {
- return nil, fmt.Errorf("session summary is disabled")
+ return nil, errors.New("session summary is disabled")
}
// 获取现有摘要
@@ -296,30 +297,38 @@ func (m *SessionSummaryManager) GetSummaryText(sessionID string) string {
if len(summary.Topics) > 0 {
text += "\n讨论主题:\n"
+ var textSb299 strings.Builder
for _, topic := range summary.Topics {
- text += fmt.Sprintf("- %s\n", topic)
+ textSb299.WriteString(fmt.Sprintf("- %s\n", topic))
}
+ text += textSb299.String()
}
if len(summary.KeyPoints) > 0 {
text += "\n关键要点:\n"
+ var textSb306 strings.Builder
for _, point := range summary.KeyPoints {
- text += fmt.Sprintf("- %s\n", point)
+ textSb306.WriteString(fmt.Sprintf("- %s\n", point))
}
+ text += textSb306.String()
}
if len(summary.Decisions) > 0 {
text += "\n决策:\n"
+ var textSb313 strings.Builder
for _, decision := range summary.Decisions {
- text += fmt.Sprintf("- %s\n", decision)
+ textSb313.WriteString(fmt.Sprintf("- %s\n", decision))
}
+ text += textSb313.String()
}
if len(summary.ActionItems) > 0 {
text += "\n行动项:\n"
+ var textSb320 strings.Builder
for _, item := range summary.ActionItems {
- text += fmt.Sprintf("- %s\n", item)
+ textSb320.WriteString(fmt.Sprintf("- %s\n", item))
}
+ text += textSb320.String()
}
return text
@@ -330,9 +339,11 @@ func (m *SessionSummaryManager) GetSummaryText(sessionID string) string {
func (m *SessionSummaryManager) buildPrompt(messages []types.Message) string {
// 格式化消息
messagesText := ""
+ var messagesTextSb333 strings.Builder
for i, msg := range messages {
- messagesText += fmt.Sprintf("[%d] %s: %s\n", i+1, msg.Role, msg.Content)
+ messagesTextSb333.WriteString(fmt.Sprintf("[%d] %s: %s\n", i+1, msg.Role, msg.Content))
}
+ messagesText += messagesTextSb333.String()
// 替换占位符
prompt := m.config.SummaryPrompt
@@ -353,9 +364,11 @@ func (m *SessionSummaryManager) buildIncrementalPrompt(existingSummary *SessionS
// 格式化新消息
newMessagesText := ""
+ var newMessagesTextSb356 strings.Builder
for i, msg := range newMessages {
- newMessagesText += fmt.Sprintf("[%d] %s: %s\n", i+1, msg.Role, msg.Content)
+ newMessagesTextSb356.WriteString(fmt.Sprintf("[%d] %s: %s\n", i+1, msg.Role, msg.Content))
}
+ newMessagesText += newMessagesTextSb356.String()
prompt := fmt.Sprintf(`现有的会话摘要:
%s
diff --git a/pkg/memory/session_summary_test.go b/pkg/memory/session_summary_test.go
index 5d85c7d..45c6ac2 100644
--- a/pkg/memory/session_summary_test.go
+++ b/pkg/memory/session_summary_test.go
@@ -2,6 +2,7 @@ package memory
import (
"context"
+ "errors"
"fmt"
"strings"
"testing"
@@ -263,7 +264,7 @@ func TestSessionSummaryManager_ListSummaries(t *testing.T) {
}
// 生成多个摘要
- for i := 0; i < 3; i++ {
+ for i := range 3 {
sessionID := fmt.Sprintf("test-session-%d", i)
_, err := manager.GenerateSummary(ctx, sessionID, messages)
if err != nil {
@@ -488,11 +489,11 @@ func TestSessionSummaryManager_ConcurrentAccess(t *testing.T) {
done := make(chan bool, numGoroutines)
errors := make(chan error, numGoroutines*numSessions)
- for i := 0; i < numGoroutines; i++ {
+ for i := range numGoroutines {
go func(goroutineID int) {
defer func() { done <- true }()
- for j := 0; j < numSessions; j++ {
+ for j := range numSessions {
sessionID := fmt.Sprintf("session-%d-%d", goroutineID, j)
// 生成摘要
@@ -520,7 +521,7 @@ func TestSessionSummaryManager_ConcurrentAccess(t *testing.T) {
}
// 等待所有 goroutine 完成
- for i := 0; i < numGoroutines; i++ {
+ for range numGoroutines {
<-done
}
close(errors)
@@ -549,11 +550,11 @@ func TestSessionSummaryManager_ConcurrentAccess(t *testing.T) {
done = make(chan bool, numGoroutines)
errors = make(chan error, numGoroutines*numSessions)
- for i := 0; i < numGoroutines; i++ {
+ for i := range numGoroutines {
go func(goroutineID int) {
defer func() { done <- true }()
- for j := 0; j < numSessions; j++ {
+ for j := range numSessions {
sessionID := fmt.Sprintf("session-%d-%d", goroutineID, j)
err := manager.DeleteSummary(sessionID)
if err != nil {
@@ -564,7 +565,7 @@ func TestSessionSummaryManager_ConcurrentAccess(t *testing.T) {
}
// 等待所有 goroutine 完成
- for i := 0; i < numGoroutines; i++ {
+ for range numGoroutines {
<-done
}
close(errors)
@@ -620,7 +621,7 @@ func TestSessionSummaryManager_InvalidJSON(t *testing.T) {
func TestSessionSummaryManager_ProviderError(t *testing.T) {
// 创建一个会返回错误的 mock provider
mockProvider := &MockProviderWithError{
- err: fmt.Errorf("provider connection failed"),
+ err: errors.New("provider connection failed"),
}
config := SessionSummaryConfig{
diff --git a/pkg/memory/working.go b/pkg/memory/working.go
index efefd78..2054125 100644
--- a/pkg/memory/working.go
+++ b/pkg/memory/working.go
@@ -3,6 +3,7 @@ package memory
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"path/filepath"
"strings"
@@ -64,10 +65,10 @@ type WorkingMemoryData struct {
// NewWorkingMemoryManager 创建 Working Memory 管理器
func NewWorkingMemoryManager(cfg *WorkingMemoryConfig) (*WorkingMemoryManager, error) {
if cfg == nil {
- return nil, fmt.Errorf("working memory config cannot be nil")
+ return nil, errors.New("working memory config cannot be nil")
}
if cfg.Backend == nil {
- return nil, fmt.Errorf("working memory requires a non-nil Backend")
+ return nil, errors.New("working memory requires a non-nil Backend")
}
basePath := cfg.BasePath
@@ -107,7 +108,7 @@ func NewWorkingMemoryManager(cfg *WorkingMemoryConfig) (*WorkingMemoryManager, e
// - resource scope: /working_memory/resources/.json
func (wm *WorkingMemoryManager) Get(ctx context.Context, threadID, resourceID string) (string, error) {
if threadID == "" && resourceID == "" {
- return "", fmt.Errorf("threadID and resourceID cannot both be empty")
+ return "", errors.New("threadID and resourceID cannot both be empty")
}
path := wm.resolvePath(threadID, resourceID)
@@ -140,12 +141,12 @@ func (wm *WorkingMemoryManager) Get(ctx context.Context, threadID, resourceID st
// 如果配置了 Schema,会先进行验证
func (wm *WorkingMemoryManager) Update(ctx context.Context, threadID, resourceID, content string) error {
if threadID == "" && resourceID == "" {
- return fmt.Errorf("threadID and resourceID cannot both be empty")
+ return errors.New("threadID and resourceID cannot both be empty")
}
content = strings.TrimSpace(content)
if content == "" {
- return fmt.Errorf("content cannot be empty")
+ return errors.New("content cannot be empty")
}
// Schema 验证(如果配置)
@@ -210,7 +211,7 @@ func (wm *WorkingMemoryManager) Update(ctx context.Context, threadID, resourceID
// 如果 searchString 为空,则追加到末尾
func (wm *WorkingMemoryManager) FindAndReplace(ctx context.Context, threadID, resourceID, searchString, newContent string) error {
if threadID == "" && resourceID == "" {
- return fmt.Errorf("threadID and resourceID cannot both be empty")
+ return errors.New("threadID and resourceID cannot both be empty")
}
// 读取现有内容
@@ -230,7 +231,7 @@ func (wm *WorkingMemoryManager) FindAndReplace(ctx context.Context, threadID, re
} else {
// 查找替换模式
if !strings.Contains(existing, searchString) {
- return fmt.Errorf("search string not found in working memory")
+ return errors.New("search string not found in working memory")
}
updated = strings.Replace(existing, searchString, newContent, 1)
}
@@ -241,7 +242,7 @@ func (wm *WorkingMemoryManager) FindAndReplace(ctx context.Context, threadID, re
// Delete 删除 Working Memory
func (wm *WorkingMemoryManager) Delete(ctx context.Context, threadID, resourceID string) error {
if threadID == "" && resourceID == "" {
- return fmt.Errorf("threadID and resourceID cannot both be empty")
+ return errors.New("threadID and resourceID cannot both be empty")
}
path := wm.resolvePath(threadID, resourceID)
diff --git a/pkg/middleware/agent_memory.go b/pkg/middleware/agent_memory.go
index cc36e15..7133c4f 100644
--- a/pkg/middleware/agent_memory.go
+++ b/pkg/middleware/agent_memory.go
@@ -2,6 +2,7 @@ package middleware
import (
"context"
+ "errors"
"fmt"
"github.com/astercloud/aster/pkg/backends"
@@ -23,6 +24,7 @@ const (
// 3. 提供长期记忆使用指南
type AgentMemoryMiddleware struct {
*BaseMiddleware
+
backend backends.BackendProtocol
memoryPath string
systemPromptTemplate string
@@ -45,11 +47,11 @@ type AgentMemoryMiddlewareConfig struct {
// NewAgentMemoryMiddleware 创建中间件
func NewAgentMemoryMiddleware(config *AgentMemoryMiddlewareConfig) (*AgentMemoryMiddleware, error) {
if config == nil {
- return nil, fmt.Errorf("agent memory not configured properly")
+ return nil, errors.New("agent memory not configured properly")
}
if config.Backend == nil {
- return nil, fmt.Errorf("backend is required")
+ return nil, errors.New("backend is required")
}
if config.MemoryPath == "" {
diff --git a/pkg/middleware/filesystem.go b/pkg/middleware/filesystem.go
index 94f58e8..771d6fa 100644
--- a/pkg/middleware/filesystem.go
+++ b/pkg/middleware/filesystem.go
@@ -34,6 +34,7 @@ type FilesystemMiddlewareConfig struct {
// 4. 路径安全验证
type FilesystemMiddleware struct {
*BaseMiddleware
+
backend backends.BackendProtocol
tokenLimit int
enableEviction bool
@@ -220,9 +221,11 @@ func splitLines(s string, limit int) []string {
func joinLines(lines []string) string {
result := ""
+ var resultSb223 strings.Builder
for _, line := range lines {
- result += line
+ resultSb223.WriteString(line)
}
+ result += resultSb223.String()
return result
}
diff --git a/pkg/middleware/filesystem_security_test.go b/pkg/middleware/filesystem_security_test.go
index 8d3ca80..52d88fe 100644
--- a/pkg/middleware/filesystem_security_test.go
+++ b/pkg/middleware/filesystem_security_test.go
@@ -389,8 +389,7 @@ func BenchmarkPathValidation(b *testing.B) {
"/workspace/./redundant/../path/file.txt",
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for i := 0; b.Loop(); i++ {
path := paths[i%len(paths)]
_, _ = middleware.validatePath(path)
}
@@ -410,8 +409,7 @@ func BenchmarkPathValidation_Disabled(b *testing.B) {
"~/secrets.txt",
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for i := 0; b.Loop(); i++ {
path := paths[i%len(paths)]
_, _ = middleware.validatePath(path)
}
diff --git a/pkg/middleware/hitl.go b/pkg/middleware/hitl.go
index cd39f2d..cd0de67 100644
--- a/pkg/middleware/hitl.go
+++ b/pkg/middleware/hitl.go
@@ -74,6 +74,7 @@ type HumanInTheLoopMiddlewareConfig struct {
// 4. 灵活的审核配置
type HumanInTheLoopMiddleware struct {
*BaseMiddleware
+
interruptConfigs map[string]*InterruptConfig
approvalHandler ApprovalHandler
defaultAllowedDecisions []DecisionType
@@ -231,7 +232,7 @@ func (m *HumanInTheLoopMiddleware) WrapToolCall(ctx context.Context, req *ToolCa
"ok": false,
"rejected": true,
"reason": decision.Reason,
- "message": fmt.Sprintf("Tool execution rejected by human reviewer: %s", decision.Reason),
+ "message": "Tool execution rejected by human reviewer: " + decision.Reason,
},
}, nil
diff --git a/pkg/middleware/hitl_test.go b/pkg/middleware/hitl_test.go
index ba717e9..73d97df 100644
--- a/pkg/middleware/hitl_test.go
+++ b/pkg/middleware/hitl_test.go
@@ -2,6 +2,7 @@ package middleware
import (
"context"
+ "maps"
"testing"
"github.com/astercloud/aster/pkg/tools"
@@ -213,9 +214,7 @@ func TestHumanInTheLoopMiddleware_Edit(t *testing.T) {
ApprovalHandler: func(ctx context.Context, request *ReviewRequest) ([]Decision, error) {
// 编辑参数
editedInput := make(map[string]any)
- for k, v := range request.ActionRequests[0].Input {
- editedInput[k] = v
- }
+ maps.Copy(editedInput, request.ActionRequests[0].Input)
editedInput["param"] = "edited_value"
return []Decision{
diff --git a/pkg/middleware/integration_test.go b/pkg/middleware/integration_test.go
index 07a2e08..3a0bfd5 100644
--- a/pkg/middleware/integration_test.go
+++ b/pkg/middleware/integration_test.go
@@ -242,8 +242,7 @@ func BenchmarkMiddlewareStack(b *testing.B) {
})
stack := NewStack([]Middleware{middleware})
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_ = stack.Tools()
}
}
@@ -253,8 +252,7 @@ func BenchmarkBackendWrite(b *testing.B) {
backend := backends.NewStateBackend()
ctx := context.Background()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _ = backend.Write(ctx, "/bench.txt", "test content")
}
}
diff --git a/pkg/middleware/logic_memory.go b/pkg/middleware/logic_memory.go
index 10906d3..0c9f741 100644
--- a/pkg/middleware/logic_memory.go
+++ b/pkg/middleware/logic_memory.go
@@ -2,7 +2,9 @@ package middleware
import (
"context"
+ "errors"
"fmt"
+ "maps"
"strings"
"sync"
"time"
@@ -22,6 +24,7 @@ var lmLog = logging.ForComponent("LogicMemoryMiddleware")
// 3. 提供 Logic Memory 管理工具供 Agent 主动查询和更新
type LogicMemoryMiddleware struct {
*BaseMiddleware
+
manager *logic.Manager
config *LogicMemoryMiddlewareConfig
logicMemoryTools []tools.Tool
@@ -83,11 +86,11 @@ type LogicMemoryMiddlewareConfig struct {
// NewLogicMemoryMiddleware 创建 Logic Memory 中间件
func NewLogicMemoryMiddleware(config *LogicMemoryMiddlewareConfig) (*LogicMemoryMiddleware, error) {
if config == nil {
- return nil, fmt.Errorf("logic memory config is required")
+ return nil, errors.New("logic memory config is required")
}
if config.Manager == nil {
- return nil, fmt.Errorf("logic memory manager is required")
+ return nil, errors.New("logic memory manager is required")
}
// 设置默认值
@@ -296,9 +299,7 @@ func (m *LogicMemoryMiddleware) CaptureUserMessage(namespace, content string, me
Data: map[string]any{"content": content},
Timestamp: time.Now(),
}
- for k, v := range metadata {
- event.Data[k] = v
- }
+ maps.Copy(event.Data, metadata)
m.captureEvent(event)
}
@@ -313,9 +314,7 @@ func (m *LogicMemoryMiddleware) CaptureUserFeedback(namespace string, feedback s
},
Timestamp: time.Now(),
}
- for k, v := range metadata {
- event.Data[k] = v
- }
+ maps.Copy(event.Data, metadata)
m.captureEvent(event)
}
@@ -330,9 +329,7 @@ func (m *LogicMemoryMiddleware) CaptureUserRevision(namespace string, original,
},
Timestamp: time.Now(),
}
- for k, v := range metadata {
- event.Data[k] = v
- }
+ maps.Copy(event.Data, metadata)
m.captureEvent(event)
}
diff --git a/pkg/middleware/logic_memory_test.go b/pkg/middleware/logic_memory_test.go
index 043aa2e..13add0a 100644
--- a/pkg/middleware/logic_memory_test.go
+++ b/pkg/middleware/logic_memory_test.go
@@ -188,7 +188,7 @@ func TestLogicMemoryMiddleware_WrapModelCall(t *testing.T) {
require.NoError(t, err)
// 验证 Memory 在开头
- assert.True(t, len(capturedSystemPrompt) > len("Original prompt."))
+ assert.Greater(t, len(capturedSystemPrompt), len("Original prompt."))
// Memory 应该在原始 prompt 之前
assert.Contains(t, capturedSystemPrompt, "User Preferences")
})
@@ -446,7 +446,7 @@ func TestDefaultNamespaceExtractor(t *testing.T) {
Metadata: nil,
}
ns := defaultNamespaceExtractor(req)
- assert.Equal(t, "", ns)
+ assert.Empty(t, ns)
})
t.Run("priority: namespace > user_id > tenant_id > agent_id", func(t *testing.T) {
@@ -510,7 +510,7 @@ func TestBuildMemorySection(t *testing.T) {
t.Run("empty memories", func(t *testing.T) {
section := mw.buildMemorySection([]*logic.LogicMemory{})
- assert.Equal(t, "", section)
+ assert.Empty(t, section)
})
t.Run("nil provenance", func(t *testing.T) {
diff --git a/pkg/middleware/logic_memory_tools.go b/pkg/middleware/logic_memory_tools.go
index 61524de..06b5f8b 100644
--- a/pkg/middleware/logic_memory_tools.go
+++ b/pkg/middleware/logic_memory_tools.go
@@ -3,6 +3,7 @@ package middleware
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"strings"
@@ -89,7 +90,7 @@ func (t *LogicMemoryQueryTool) Execute(ctx context.Context, input map[string]any
}
}
if namespace == "" {
- return nil, fmt.Errorf("namespace is required")
+ return nil, errors.New("namespace is required")
}
switch action {
@@ -157,7 +158,7 @@ func (t *LogicMemoryQueryTool) list(ctx context.Context, namespace string, input
func (t *LogicMemoryQueryTool) get(ctx context.Context, namespace string, input map[string]any) (string, error) {
key, ok := input["key"].(string)
if !ok || key == "" {
- return "", fmt.Errorf("key is required for 'get' action")
+ return "", errors.New("key is required for 'get' action")
}
memory, err := t.manager.GetMemory(ctx, namespace, key)
@@ -171,7 +172,7 @@ func (t *LogicMemoryQueryTool) get(ctx context.Context, namespace string, input
func (t *LogicMemoryQueryTool) search(ctx context.Context, namespace string, input map[string]any) (string, error) {
memoryType, ok := input["type"].(string)
if !ok || memoryType == "" {
- return "", fmt.Errorf("type is required for 'search' action")
+ return "", errors.New("type is required for 'search' action")
}
filters := []logic.Filter{logic.WithType(memoryType)}
@@ -367,12 +368,12 @@ func (t *LogicMemoryUpdateTool) Execute(ctx context.Context, input map[string]an
}
}
if namespace == "" {
- return nil, fmt.Errorf("namespace is required")
+ return nil, errors.New("namespace is required")
}
key, _ := input["key"].(string)
if key == "" {
- return nil, fmt.Errorf("key is required")
+ return nil, errors.New("key is required")
}
switch action {
@@ -433,7 +434,7 @@ func (t *LogicMemoryUpdateTool) record(ctx context.Context, namespace, key strin
description, _ := input["description"].(string)
if description == "" {
- return "", fmt.Errorf("description is required for recording memories")
+ return "", errors.New("description is required for recording memories")
}
value := input["value"]
diff --git a/pkg/middleware/observation_compression.go b/pkg/middleware/observation_compression.go
index c8d774f..17e6303 100644
--- a/pkg/middleware/observation_compression.go
+++ b/pkg/middleware/observation_compression.go
@@ -15,6 +15,7 @@ var ocLog = logging.ForComponent("ObservationCompression")
// 这是 Manus 团队"文件系统作为上下文"理念的实现
type ObservationCompressionMiddleware struct {
*BaseMiddleware
+
compressor memory.ObservationCompressor
referenceRegistry memory.ReferenceRegistry
diff --git a/pkg/middleware/patch_tool_calls.go b/pkg/middleware/patch_tool_calls.go
index 095fe77..281b050 100644
--- a/pkg/middleware/patch_tool_calls.go
+++ b/pkg/middleware/patch_tool_calls.go
@@ -17,6 +17,7 @@ var ptcLog = logging.ForComponent("PatchToolCallsMiddleware")
// 3. 记录失败的工具调用供调试
type PatchToolCallsMiddleware struct {
*BaseMiddleware
+
enableLogging bool
failedCalls []FailedToolCall
maxFailedCalls int
diff --git a/pkg/middleware/patch_tool_calls_test.go b/pkg/middleware/patch_tool_calls_test.go
index 5641608..29116e5 100644
--- a/pkg/middleware/patch_tool_calls_test.go
+++ b/pkg/middleware/patch_tool_calls_test.go
@@ -213,7 +213,7 @@ func TestPatchToolCallsMiddleware_FailedCallsTracking(t *testing.T) {
})
// 生成多个失败调用
- for i := 0; i < 10; i++ {
+ for range 10 {
req := &ToolCallRequest{
ToolCallID: "test",
ToolName: "fail_tool",
diff --git a/pkg/middleware/phase4_bench_test.go b/pkg/middleware/phase4_bench_test.go
index 0a6ddda..194fdb3 100644
--- a/pkg/middleware/phase4_bench_test.go
+++ b/pkg/middleware/phase4_bench_test.go
@@ -33,8 +33,7 @@ func BenchmarkSummarizationMiddleware_NoSummarization(b *testing.B) {
}, nil
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _ = middleware.WrapModelCall(ctx, req, handler)
}
}
@@ -68,8 +67,7 @@ func BenchmarkSummarizationMiddleware_WithSummarization(b *testing.B) {
}, nil
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _ = middleware.WrapModelCall(ctx, req, handler)
}
}
@@ -100,8 +98,7 @@ func BenchmarkAgentMemoryMiddleware_LazyLoad(b *testing.B) {
}, nil
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
// 第一次会触发加载,后续直接使用缓存
_, _ = middleware.WrapModelCall(ctx, req, handler)
}
@@ -138,8 +135,7 @@ func BenchmarkAgentMemoryMiddleware_AlreadyLoaded(b *testing.B) {
}, nil
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _ = middleware.WrapModelCall(ctx, req, handler)
}
}
@@ -153,8 +149,7 @@ func BenchmarkDefaultTokenCounter(b *testing.B) {
{Role: types.MessageRoleAssistant, ContentBlocks: []types.ContentBlock{&types.TextBlock{Text: "Of course! What would you like to know?"}}},
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_ = defaultTokenCounter(messages)
}
}
@@ -172,8 +167,7 @@ func BenchmarkDefaultSummarizer(b *testing.B) {
{Role: types.MessageRoleAssistant, ContentBlocks: []types.ContentBlock{&types.TextBlock{Text: "Sure, what do you need?"}}},
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _ = defaultSummarizer(ctx, messages)
}
}
@@ -220,8 +214,7 @@ func BenchmarkPhase4Stack(b *testing.B) {
return summarizationMW.WrapModelCall(ctx, req, handler)
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _ = memoryMW.WrapModelCall(ctx, req, summarizationHandler)
}
}
@@ -237,8 +230,7 @@ func BenchmarkExtractMessageContent(b *testing.B) {
},
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_ = extractMessageContent(msg)
}
}
diff --git a/pkg/middleware/reasoning.go b/pkg/middleware/reasoning.go
index 5d06982..630feba 100644
--- a/pkg/middleware/reasoning.go
+++ b/pkg/middleware/reasoning.go
@@ -3,6 +3,7 @@ package middleware
import (
"context"
"fmt"
+ "strings"
"github.com/astercloud/aster/pkg/logging"
"github.com/astercloud/aster/pkg/provider"
@@ -16,6 +17,7 @@ var reasonLog = logging.ForComponent("ReasoningMiddleware")
// ReasoningMiddleware 推理中间件
type ReasoningMiddleware struct {
*BaseMiddleware
+
engine *reasoning.Engine
enabled bool
priority int
@@ -130,11 +132,13 @@ func (rm *ReasoningMiddleware) containsReasoningMarkersInResponse(response *Mode
// 从 Message 的 ContentBlocks 中提取文本
text := ""
+ var textSb133 strings.Builder
for _, block := range response.Message.ContentBlocks {
if textBlock, ok := block.(*types.TextBlock); ok {
- text += textBlock.Text
+ textSb133.WriteString(textBlock.Text)
}
}
+ text += textSb133.String()
if text == "" {
return false
diff --git a/pkg/middleware/registry.go b/pkg/middleware/registry.go
index c4c30d3..1850591 100644
--- a/pkg/middleware/registry.go
+++ b/pkg/middleware/registry.go
@@ -2,6 +2,7 @@ package middleware
import (
"context"
+ "errors"
"fmt"
"sync"
@@ -87,7 +88,7 @@ func (r *Registry) registerBuiltin() {
// Summarization Middleware
r.Register("summarization", func(config *MiddlewareFactoryConfig) (Middleware, error) {
if config.Provider == nil {
- return nil, fmt.Errorf("summarization middleware requires provider")
+ return nil, errors.New("summarization middleware requires provider")
}
// 自定义配置(可选) - 优化: 降低默认阈值以更早触发压缩
@@ -127,7 +128,7 @@ func (r *Registry) registerBuiltin() {
// Filesystem Middleware (默认使用 Sandbox 文件系统)
r.Register("filesystem", func(config *MiddlewareFactoryConfig) (Middleware, error) {
if config.Sandbox == nil {
- return nil, fmt.Errorf("filesystem middleware requires sandbox")
+ return nil, errors.New("filesystem middleware requires sandbox")
}
fsBackend := backends.NewFilesystemBackend(config.Sandbox.FS())
@@ -190,7 +191,7 @@ func (r *Registry) registerBuiltin() {
// AgentMemory Middleware (默认使用 Sandbox 文件系统, /memories/ 作为记忆根目录)
r.Register("agent_memory", func(config *MiddlewareFactoryConfig) (Middleware, error) {
if config.Sandbox == nil {
- return nil, fmt.Errorf("agent_memory middleware requires sandbox")
+ return nil, errors.New("agent_memory middleware requires sandbox")
}
fsBackend := backends.NewFilesystemBackend(config.Sandbox.FS())
@@ -206,7 +207,7 @@ func (r *Registry) registerBuiltin() {
baseNamespace := ""
if config.Metadata != nil {
if userID, ok := config.Metadata["user_id"].(string); ok && userID != "" {
- baseNamespace = fmt.Sprintf("users/%s", userID)
+ baseNamespace = "users/" + userID
}
}
@@ -220,7 +221,7 @@ func (r *Registry) registerBuiltin() {
// WorkingMemory Middleware (跨会话状态管理)
r.Register("working_memory", func(config *MiddlewareFactoryConfig) (Middleware, error) {
if config.Sandbox == nil {
- return nil, fmt.Errorf("working_memory middleware requires sandbox")
+ return nil, errors.New("working_memory middleware requires sandbox")
}
fsBackend := backends.NewFilesystemBackend(config.Sandbox.FS())
@@ -295,7 +296,7 @@ func (r *Registry) registerBuiltin() {
// Reasoning Middleware (推理链)
r.Register("reasoning", func(config *MiddlewareFactoryConfig) (Middleware, error) {
if config.Provider == nil {
- return nil, fmt.Errorf("reasoning middleware requires provider")
+ return nil, errors.New("reasoning middleware requires provider")
}
// 默认配置
diff --git a/pkg/middleware/simplicity_checker.go b/pkg/middleware/simplicity_checker.go
index 70b857a..eec1e04 100644
--- a/pkg/middleware/simplicity_checker.go
+++ b/pkg/middleware/simplicity_checker.go
@@ -15,6 +15,7 @@ var scLog = logging.ForComponent("SimplicityChecker")
// 检测过度工程和未请求的功能添加,发出警告但不阻断执行
type SimplicityCheckerMiddleware struct {
*BaseMiddleware
+
config *SimplicityCheckerConfig
// 会话级统计
diff --git a/pkg/middleware/structured_output.go b/pkg/middleware/structured_output.go
index 39d78b1..4f30e77 100644
--- a/pkg/middleware/structured_output.go
+++ b/pkg/middleware/structured_output.go
@@ -2,6 +2,7 @@ package middleware
import (
"context"
+ "errors"
"fmt"
"github.com/astercloud/aster/pkg/logging"
@@ -15,6 +16,7 @@ var soLog = logging.ForComponent("StructuredOutputMiddleware")
// - 若解析失败: 根据配置决定是否回退;错误记录在 Metadata["structured_error"]
type StructuredOutputMiddleware struct {
*BaseMiddleware
+
spec structured.OutputSpec
parser structured.Parser
allowError bool
@@ -31,7 +33,7 @@ type StructuredOutputMiddlewareConfig struct {
// NewStructuredOutputMiddleware 创建中间件实例
func NewStructuredOutputMiddleware(cfg *StructuredOutputMiddlewareConfig) (*StructuredOutputMiddleware, error) {
if cfg == nil {
- return nil, fmt.Errorf("structured output config is nil")
+ return nil, errors.New("structured output config is nil")
}
parser := cfg.Parser
diff --git a/pkg/middleware/subagent.go b/pkg/middleware/subagent.go
index 731ad23..534c589 100644
--- a/pkg/middleware/subagent.go
+++ b/pkg/middleware/subagent.go
@@ -2,7 +2,9 @@ package middleware
import (
"context"
+ "errors"
"fmt"
+ "strings"
"sync"
"time"
@@ -67,6 +69,7 @@ type SubAgentMiddlewareConfig struct {
// 7. 支持资源监控和限制
type SubAgentMiddleware struct {
*BaseMiddleware
+
agents map[string]SubAgent
factory SubAgentFactory
manager builtin.SubagentManager
@@ -256,12 +259,12 @@ func (t *TaskTool) InputSchema() map[string]any {
func (t *TaskTool) Execute(ctx context.Context, input map[string]any, tc *tools.ToolContext) (any, error) {
description, ok := input["description"].(string)
if !ok {
- return nil, fmt.Errorf("description must be a string")
+ return nil, errors.New("description must be a string")
}
subagentType, ok := input["subagent_type"].(string)
if !ok {
- return nil, fmt.Errorf("subagent_type must be a string")
+ return nil, errors.New("subagent_type must be a string")
}
// 获取上下文(可选)
@@ -361,9 +364,11 @@ func (t *TaskTool) Prompt() string {
// 获取可用的子代理列表
subagentTypes := t.middleware.ListSubAgents()
agentList := "可用的子代理类型:\n"
+ var agentListSb364 strings.Builder
for _, name := range subagentTypes {
- agentList += fmt.Sprintf(" - %s\n", name)
+ agentListSb364.WriteString(fmt.Sprintf(" - %s\n", name))
}
+ agentList += agentListSb364.String()
return fmt.Sprintf(`启动短生命周期的子代理来处理复杂的、多步骤的独立任务,实现上下文隔离。
@@ -622,7 +627,7 @@ func (t *QuerySubagentTool) InputSchema() map[string]any {
func (t *QuerySubagentTool) Execute(ctx context.Context, input map[string]any, tc *tools.ToolContext) (any, error) {
taskID, ok := input["task_id"].(string)
if !ok {
- return nil, fmt.Errorf("task_id must be a string")
+ return nil, errors.New("task_id must be a string")
}
// 获取子代理实例
@@ -721,7 +726,7 @@ func (t *StopSubagentTool) InputSchema() map[string]any {
func (t *StopSubagentTool) Execute(ctx context.Context, input map[string]any, tc *tools.ToolContext) (any, error) {
taskID, ok := input["task_id"].(string)
if !ok {
- return nil, fmt.Errorf("task_id must be a string")
+ return nil, errors.New("task_id must be a string")
}
// 停止子代理
@@ -788,7 +793,7 @@ func (t *ResumeSubagentTool) InputSchema() map[string]any {
func (t *ResumeSubagentTool) Execute(ctx context.Context, input map[string]any, tc *tools.ToolContext) (any, error) {
taskID, ok := input["task_id"].(string)
if !ok {
- return nil, fmt.Errorf("task_id must be a string")
+ return nil, errors.New("task_id must be a string")
}
// 恢复子代理
diff --git a/pkg/middleware/subagent_async_manager.go b/pkg/middleware/subagent_async_manager.go
index 6b551e6..6b60918 100644
--- a/pkg/middleware/subagent_async_manager.go
+++ b/pkg/middleware/subagent_async_manager.go
@@ -2,7 +2,9 @@ package middleware
import (
"context"
+ "errors"
"fmt"
+ "maps"
"sync"
"time"
@@ -30,7 +32,7 @@ func newGoroutineSubagentManager(mw *SubAgentMiddleware) *goroutineSubagentManag
func (gm *goroutineSubagentManager) StartSubagent(ctx context.Context, config *builtin.SubagentConfig) (*builtin.SubagentInstance, error) {
if config == nil {
- return nil, fmt.Errorf("subagent config cannot be nil")
+ return nil, errors.New("subagent config cannot be nil")
}
subagent, err := gm.middleware.GetSubAgent(config.Type)
@@ -56,9 +58,7 @@ func (gm *goroutineSubagentManager) StartSubagent(ctx context.Context, config *b
LastUpdate: time.Now(),
Command: "goroutine",
}
- for k, v := range configCopy.Metadata {
- instance.Metadata[k] = v
- }
+ maps.Copy(instance.Metadata, configCopy.Metadata)
var execCtx context.Context
var cancel context.CancelFunc
diff --git a/pkg/middleware/subagent_async_test.go b/pkg/middleware/subagent_async_test.go
index 64cc887..9f6ec87 100644
--- a/pkg/middleware/subagent_async_test.go
+++ b/pkg/middleware/subagent_async_test.go
@@ -281,7 +281,7 @@ func TestSubAgentMiddleware_ListSubagents(t *testing.T) {
taskTool := getTool[*TaskTool](t, mw, "task")
ctx := context.Background()
- for i := 0; i < 3; i++ {
+ for range 3 {
_, err := taskTool.Execute(ctx, map[string]any{
"description": "list job",
"subagent_type": "worker",
diff --git a/pkg/middleware/subagent_enhanced_test.go b/pkg/middleware/subagent_enhanced_test.go
index 44639ba..77cd0fd 100644
--- a/pkg/middleware/subagent_enhanced_test.go
+++ b/pkg/middleware/subagent_enhanced_test.go
@@ -2,6 +2,7 @@ package middleware
import (
"context"
+ "slices"
"testing"
)
@@ -56,13 +57,7 @@ func TestSubAgentMiddleware_GeneralPurpose(t *testing.T) {
}
// 验证 general-purpose 存在
- found := false
- for _, name := range agents {
- if name == "general-purpose" {
- found = true
- break
- }
- }
+ found := slices.Contains(agents, "general-purpose")
if !found {
t.Error("Expected 'general-purpose' agent to be present")
}
diff --git a/pkg/middleware/summarization.go b/pkg/middleware/summarization.go
index d23ddce..852f208 100644
--- a/pkg/middleware/summarization.go
+++ b/pkg/middleware/summarization.go
@@ -2,6 +2,7 @@ package middleware
import (
"context"
+ "errors"
"fmt"
"strings"
@@ -19,6 +20,7 @@ var sumLog = logging.ForComponent("SummarizationMiddleware")
// 4. 用总结消息替换旧的历史记录
type SummarizationMiddleware struct {
*BaseMiddleware
+
maxTokensBeforeSummary int
messagesToKeep int
summaryPrefix string
@@ -46,7 +48,7 @@ type SummarizationMiddlewareConfig struct {
// NewSummarizationMiddleware 创建中间件
func NewSummarizationMiddleware(config *SummarizationMiddlewareConfig) (*SummarizationMiddleware, error) {
if config == nil {
- return nil, fmt.Errorf("config cannot be nil")
+ return nil, errors.New("config cannot be nil")
}
if config.MaxTokensBeforeSummary <= 0 {
@@ -316,7 +318,7 @@ func extractConversationPhases(messages []types.Message) []ConversationPhase {
// 提取关键点
point := ""
if msg.Role == types.MessageRoleUser {
- point = fmt.Sprintf("User: %s", truncateString(content, 100))
+ point = "User: " + truncateString(content, 100)
} else if msg.Role == types.MessageRoleAssistant {
// 检查是否有工具调用
hasToolUse := false
@@ -329,7 +331,7 @@ func extractConversationPhases(messages []types.Message) []ConversationPhase {
if hasToolUse {
point = "Assistant executed tools"
} else {
- point = fmt.Sprintf("Assistant: %s", truncateString(content, 80))
+ point = "Assistant: " + truncateString(content, 80)
}
}
@@ -406,8 +408,8 @@ func extractFileReferences(messages []types.Message) []string {
content := extractMessageContent(msg)
// 简单的文件路径检测
- words := strings.Fields(content)
- for _, word := range words {
+ words := strings.FieldsSeq(content)
+ for word := range words {
// 检测常见文件扩展名
if strings.HasSuffix(word, ".go") ||
strings.HasSuffix(word, ".ts") ||
@@ -522,8 +524,8 @@ func extractPendingTasks(messages []types.Message) []string {
// 检查 TODO 标记
if strings.Contains(content, "TODO") || strings.Contains(content, "待办") ||
strings.Contains(content, "需要") || strings.Contains(content, "接下来") {
- lines := strings.Split(content, "\n")
- for _, line := range lines {
+ lines := strings.SplitSeq(content, "\n")
+ for line := range lines {
if strings.Contains(line, "TODO") || strings.Contains(line, "- [ ]") {
tasks = append(tasks, strings.TrimSpace(line))
}
@@ -589,7 +591,7 @@ func defaultTokenCounter(messages []types.Message) int {
// 估算 input 的大小
totalChars += len(fmt.Sprintf("%v", b.Input))
case *types.ToolResultBlock:
- totalChars += len(fmt.Sprintf("%v", b.Content))
+ totalChars += len(b.Content)
}
}
}
diff --git a/pkg/middleware/telemetry.go b/pkg/middleware/telemetry.go
index 3518a19..501af21 100644
--- a/pkg/middleware/telemetry.go
+++ b/pkg/middleware/telemetry.go
@@ -44,6 +44,7 @@ type TelemetryMiddlewareConfig struct {
// 用于追踪 Agent 的 LLM 调用和工具执行
type TelemetryMiddleware struct {
*BaseMiddleware
+
tracer telemetry.Tracer
agentID string
agentName string
diff --git a/pkg/middleware/telemetry_test.go b/pkg/middleware/telemetry_test.go
index 19b01c7..c654871 100644
--- a/pkg/middleware/telemetry_test.go
+++ b/pkg/middleware/telemetry_test.go
@@ -167,7 +167,7 @@ func TestTelemetryMiddleware_WrapModelCall_Error(t *testing.T) {
}
_, err := m.WrapModelCall(ctx, req, handler)
- if err != expectedErr {
+ if !errors.Is(err, expectedErr) {
t.Fatalf("expected error '%v', got '%v'", expectedErr, err)
}
@@ -291,7 +291,7 @@ func TestTelemetryMiddleware_WrapToolCall_Error(t *testing.T) {
}
_, err := m.WrapToolCall(ctx, req, handler)
- if err != expectedErr {
+ if !errors.Is(err, expectedErr) {
t.Fatalf("expected error '%v', got '%v'", expectedErr, err)
}
diff --git a/pkg/middleware/todolist.go b/pkg/middleware/todolist.go
index fa31ca9..087afe6 100644
--- a/pkg/middleware/todolist.go
+++ b/pkg/middleware/todolist.go
@@ -2,6 +2,7 @@ package middleware
import (
"context"
+ "errors"
"fmt"
"github.com/astercloud/aster/pkg/logging"
@@ -33,6 +34,7 @@ type TodoItem struct {
// 3. 引导 Agent 使用任务分解策略
type TodoListMiddleware struct {
*BaseMiddleware
+
todos []TodoItem
storeGetter func() any // 获取当前任务列表
storeSetter func([]TodoItem) // 设置任务列表
@@ -168,7 +170,7 @@ func (t *WriteTodosTool) Execute(ctx context.Context, input map[string]any, tc *
// 解析 todos 列表
todosInterface, ok := input["todos"].([]any)
if !ok {
- return nil, fmt.Errorf("todos must be an array")
+ return nil, errors.New("todos must be an array")
}
var todos []TodoItem
diff --git a/pkg/middleware/working_memory.go b/pkg/middleware/working_memory.go
index e324140..fe1bc69 100644
--- a/pkg/middleware/working_memory.go
+++ b/pkg/middleware/working_memory.go
@@ -2,6 +2,7 @@ package middleware
import (
"context"
+ "errors"
"fmt"
"time"
@@ -20,6 +21,7 @@ var wmLog = logging.ForComponent("WorkingMemoryMiddleware")
// 3. 提供 update_working_memory 工具
type WorkingMemoryMiddleware struct {
*BaseMiddleware
+
manager *memory.WorkingMemoryManager
systemPromptTemplate string
workingMemoryTools []tools.Tool
@@ -41,11 +43,11 @@ type WorkingMemoryMiddlewareConfig struct {
// NewWorkingMemoryMiddleware 创建 Working Memory 中间件
func NewWorkingMemoryMiddleware(config *WorkingMemoryMiddlewareConfig) (*WorkingMemoryMiddleware, error) {
if config == nil {
- return nil, fmt.Errorf("working memory not configured properly")
+ return nil, errors.New("working memory not configured properly")
}
if config.Backend == nil {
- return nil, fmt.Errorf("backend is required")
+ return nil, errors.New("backend is required")
}
// 创建 Working Memory 管理器
diff --git a/pkg/permission/inspector.go b/pkg/permission/inspector.go
index d274667..1c1bc4f 100644
--- a/pkg/permission/inspector.go
+++ b/pkg/permission/inspector.go
@@ -6,6 +6,7 @@ import (
"fmt"
"path/filepath"
"regexp"
+ "slices"
"strings"
"sync"
"time"
@@ -385,13 +386,7 @@ func (i *EnhancedInspector) isExcludedCommand(toolName string, args map[string]a
}
// 检查工具名称
- for _, excluded := range settings.ExcludedCommands {
- if toolName == excluded {
- return true
- }
- }
-
- return false
+ return slices.Contains(settings.ExcludedCommands, toolName)
}
// isEditTool 检查是否为编辑工具
@@ -653,12 +648,12 @@ func (i *EnhancedInspector) matchPattern(pattern, toolName string) bool {
if pattern == toolName {
return true
}
- if strings.HasSuffix(pattern, "*") {
- prefix := strings.TrimSuffix(pattern, "*")
+ if before, ok := strings.CutSuffix(pattern, "*"); ok {
+ prefix := before
return strings.HasPrefix(toolName, prefix)
}
- if strings.HasPrefix(pattern, "*") {
- suffix := strings.TrimPrefix(pattern, "*")
+ if after, ok := strings.CutPrefix(pattern, "*"); ok {
+ suffix := after
return strings.HasSuffix(toolName, suffix)
}
return false
@@ -737,7 +732,7 @@ func (i *EnhancedInspector) RecordDecision(req *Request, decision Decision, note
Decision: DecisionAllow,
RiskLevel: req.RiskLevel,
CreatedAt: time.Now(),
- Note: fmt.Sprintf("Auto-created from allow_always: %s", note),
+ Note: "Auto-created from allow_always: " + note,
})
case DecisionDenyAlways:
i.AddRule(Rule{
@@ -745,7 +740,7 @@ func (i *EnhancedInspector) RecordDecision(req *Request, decision Decision, note
Decision: DecisionDeny,
RiskLevel: req.RiskLevel,
CreatedAt: time.Now(),
- Note: fmt.Sprintf("Auto-created from deny_always: %s", note),
+ Note: "Auto-created from deny_always: " + note,
})
}
diff --git a/pkg/permission/permission.go b/pkg/permission/permission.go
index c3f14c1..9ff7327 100644
--- a/pkg/permission/permission.go
+++ b/pkg/permission/permission.go
@@ -172,12 +172,12 @@ func NewInspector(mode Mode, opts ...InspectorOption) *Inspector {
"web_search": RiskLevelLow,
"get_file_info": RiskLevelLow,
"semantic_search": RiskLevelLow,
- "AskUserQuestion": RiskLevelLow, // User interaction - no side effects
- "Glob": RiskLevelLow, // File pattern matching - read only
- "Read": RiskLevelLow, // Read file content - read only
+ "AskUserQuestion": RiskLevelLow, // User interaction - no side effects
+ "Glob": RiskLevelLow, // File pattern matching - read only
+ "Read": RiskLevelLow, // Read file content - read only
// Medium risk - write operations
- "ExitPlanMode": RiskLevelMedium, // Plan submission requires approval
+ "ExitPlanMode": RiskLevelMedium, // Plan submission requires approval
"write_file": RiskLevelMedium,
"create_file": RiskLevelMedium,
"edit_file": RiskLevelMedium,
@@ -388,12 +388,12 @@ func (i *Inspector) matchPattern(pattern, toolName string) bool {
if pattern == toolName {
return true
}
- if strings.HasSuffix(pattern, "*") {
- prefix := strings.TrimSuffix(pattern, "*")
+ if before, ok := strings.CutSuffix(pattern, "*"); ok {
+ prefix := before
return strings.HasPrefix(toolName, prefix)
}
- if strings.HasPrefix(pattern, "*") {
- suffix := strings.TrimPrefix(pattern, "*")
+ if after, ok := strings.CutPrefix(pattern, "*"); ok {
+ suffix := after
return strings.HasSuffix(toolName, suffix)
}
return false
@@ -490,7 +490,7 @@ func (i *Inspector) RecordDecision(req *Request, decision Decision, note string)
Decision: DecisionAllow,
RiskLevel: req.RiskLevel,
CreatedAt: time.Now(),
- Note: fmt.Sprintf("Auto-created from allow_always decision: %s", note),
+ Note: "Auto-created from allow_always decision: " + note,
})
case DecisionDenyAlways:
i.AddRule(Rule{
@@ -498,7 +498,7 @@ func (i *Inspector) RecordDecision(req *Request, decision Decision, note string)
Decision: DecisionDeny,
RiskLevel: req.RiskLevel,
CreatedAt: time.Now(),
- Note: fmt.Sprintf("Auto-created from deny_always decision: %s", note),
+ Note: "Auto-created from deny_always decision: " + note,
})
}
diff --git a/pkg/permission/permission_test.go b/pkg/permission/permission_test.go
index 7990778..bcc3584 100644
--- a/pkg/permission/permission_test.go
+++ b/pkg/permission/permission_test.go
@@ -504,8 +504,7 @@ func BenchmarkCheck(b *testing.B) {
Arguments: map[string]any{"path": "/tmp/test.txt"},
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _ = inspector.Check(ctx, call)
}
}
@@ -515,7 +514,7 @@ func BenchmarkCheckWithRules(b *testing.B) {
ctx := context.Background()
// Add some rules
- for i := 0; i < 100; i++ {
+ for i := range 100 {
inspector.AddRule(Rule{
Pattern: "tool_" + string(rune('a'+i%26)),
Decision: DecisionAllow,
@@ -529,8 +528,7 @@ func BenchmarkCheckWithRules(b *testing.B) {
Arguments: map[string]any{"path": "/tmp/test.txt"},
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _ = inspector.Check(ctx, call)
}
}
diff --git a/pkg/provider/anthropic.go b/pkg/provider/anthropic.go
index eb50d2d..d563ef4 100644
--- a/pkg/provider/anthropic.go
+++ b/pkg/provider/anthropic.go
@@ -5,6 +5,7 @@ import (
"bytes"
"context"
"encoding/json"
+ "errors"
"fmt"
"io"
"net"
@@ -36,7 +37,7 @@ type AnthropicProvider struct {
// NewAnthropicProvider 创建Anthropic提供商
func NewAnthropicProvider(config *types.ModelConfig) (*AnthropicProvider, error) {
if config.APIKey == "" {
- return nil, fmt.Errorf("anthropic api key is required")
+ return nil, errors.New("anthropic api key is required")
}
baseURL := config.BaseURL
@@ -82,15 +83,15 @@ func (ap *AnthropicProvider) Complete(ctx context.Context, messages []types.Mess
}
// 创建HTTP请求
- req, err := http.NewRequestWithContext(ctx, "POST", ap.baseURL+"/v1/messages", bytes.NewReader(jsonData))
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, ap.baseURL+"/v1/messages", bytes.NewReader(jsonData))
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
// 设置请求头
req.Header.Set("Content-Type", "application/json")
- req.Header.Set("x-api-key", ap.config.APIKey)
- req.Header.Set("anthropic-version", ap.version)
+ req.Header.Set("X-Api-Key", ap.config.APIKey)
+ req.Header.Set("Anthropic-Version", ap.version)
// 发送请求
resp, err := ap.client.Do(req)
@@ -161,15 +162,15 @@ func (ap *AnthropicProvider) Stream(ctx context.Context, messages []types.Messag
}
// 创建HTTP请求
- req, err := http.NewRequestWithContext(ctx, "POST", ap.baseURL+"/v1/messages", bytes.NewReader(jsonData))
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, ap.baseURL+"/v1/messages", bytes.NewReader(jsonData))
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
// 设置请求头
req.Header.Set("Content-Type", "application/json")
- req.Header.Set("x-api-key", ap.config.APIKey)
- req.Header.Set("anthropic-version", ap.version)
+ req.Header.Set("X-Api-Key", ap.config.APIKey)
+ req.Header.Set("Anthropic-Version", ap.version)
// 发送请求
resp, err := ap.client.Do(req)
@@ -518,7 +519,7 @@ func (ap *AnthropicProvider) parseCompleteResponse(apiResp map[string]any) (type
// Anthropic 响应格式: content 是一个数组
content, ok := apiResp["content"].([]any)
if !ok || len(content) == 0 {
- return types.Message{}, fmt.Errorf("no content in response")
+ return types.Message{}, errors.New("no content in response")
}
// 遍历所有 content blocks
diff --git a/pkg/provider/custom_claude.go b/pkg/provider/custom_claude.go
index 59e2db4..3785ff0 100644
--- a/pkg/provider/custom_claude.go
+++ b/pkg/provider/custom_claude.go
@@ -5,6 +5,7 @@ import (
"bytes"
"context"
"encoding/json"
+ "errors"
"fmt"
"io"
"net"
@@ -32,11 +33,11 @@ type CustomClaudeProvider struct {
// NewCustomClaudeProvider 创建自定义 Claude 提供商
func NewCustomClaudeProvider(config *types.ModelConfig) (*CustomClaudeProvider, error) {
if config.APIKey == "" {
- return nil, fmt.Errorf("api key is required")
+ return nil, errors.New("api key is required")
}
if config.BaseURL == "" {
- return nil, fmt.Errorf("base url is required for custom claude provider")
+ return nil, errors.New("base url is required for custom claude provider")
}
// 配置 HTTP 客户端超时,避免无限等待
@@ -74,14 +75,14 @@ func (cp *CustomClaudeProvider) Complete(ctx context.Context, messages []types.M
return nil, fmt.Errorf("marshal request: %w", err)
}
- req, err := http.NewRequestWithContext(ctx, "POST", cp.getEndpoint(), bytes.NewReader(jsonData))
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, cp.getEndpoint(), bytes.NewReader(jsonData))
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
- req.Header.Set("x-api-key", cp.config.APIKey)
- req.Header.Set("anthropic-version", cp.version)
+ req.Header.Set("X-Api-Key", cp.config.APIKey)
+ req.Header.Set("Anthropic-Version", cp.version)
resp, err := cp.client.Do(req)
if err != nil {
@@ -131,14 +132,14 @@ func (cp *CustomClaudeProvider) Stream(ctx context.Context, messages []types.Mes
"preview": string(jsonData[:min(len(jsonData), 2000)]),
})
- req, err := http.NewRequestWithContext(ctx, "POST", cp.getEndpoint(), bytes.NewReader(jsonData))
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, cp.getEndpoint(), bytes.NewReader(jsonData))
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
- req.Header.Set("x-api-key", cp.config.APIKey)
- req.Header.Set("anthropic-version", cp.version)
+ req.Header.Set("X-Api-Key", cp.config.APIKey)
+ req.Header.Set("Anthropic-Version", cp.version)
resp, err := cp.client.Do(req)
if err != nil {
@@ -151,7 +152,7 @@ func (cp *CustomClaudeProvider) Stream(ctx context.Context, messages []types.Mes
// 调试:当出错时打印完整请求体
// 对于 400 错误,打印更多内容以便诊断 invalid JSON body 问题
previewLen := 5000
- if resp.StatusCode == 400 {
+ if resp.StatusCode == http.StatusBadRequest {
previewLen = 20000 // 400 错误时打印更多内容
}
customClaudeLog.Error(ctx, "API request failed", map[string]any{
@@ -161,7 +162,7 @@ func (cp *CustomClaudeProvider) Stream(ctx context.Context, messages []types.Mes
"request_body": string(jsonData[:min(len(jsonData), previewLen)]),
})
// 如果是 400 错误且包含 invalid JSON,额外打印请求体的最后部分(可能是截断位置)
- if resp.StatusCode == 400 && len(jsonData) > previewLen {
+ if resp.StatusCode == http.StatusBadRequest && len(jsonData) > previewLen {
customClaudeLog.Error(ctx, "API request body tail (for invalid JSON diagnosis)", map[string]any{
"tail": string(jsonData[max(0, len(jsonData)-5000):]),
})
@@ -384,7 +385,7 @@ func (cp *CustomClaudeProvider) processStream(body io.ReadCloser, chunkCh chan<-
data := strings.TrimPrefix(line, "data: ")
if data == "[DONE]" {
customClaudeLog.Info(context.Background(), "stream done", map[string]any{
- "total_lines": lineCount,
+ "total_lines": lineCount,
"tool_input_buffers": toolInputBuffers,
})
break
@@ -402,7 +403,7 @@ func (cp *CustomClaudeProvider) processStream(body io.ReadCloser, chunkCh chan<-
// 调试:记录所有事件类型和完整内容
eventType, _ := event["type"].(string)
-
+
// 记录所有事件的完整内容(用于调试中转站格式)
// 使用 Debug 级别避免生产环境日志过于冗长
customClaudeLog.Debug(context.Background(), "SSE EVENT", map[string]any{
@@ -495,11 +496,11 @@ func (cp *CustomClaudeProvider) parseStreamEvent(event map[string]any) *StreamCh
// 某些中转站可能不发送 input_json_delta,而是直接在这里提供完整的 input
if input, ok := contentBlock["input"].(map[string]any); ok && len(input) > 0 {
customClaudeLog.Info(context.Background(), "tool_use block start with input", map[string]any{
- "index": chunk.Index,
- "tool_id": contentBlock["id"],
- "tool_name": contentBlock["name"],
- "input_keys": len(input),
- "has_input": true,
+ "index": chunk.Index,
+ "tool_id": contentBlock["id"],
+ "tool_name": contentBlock["name"],
+ "input_keys": len(input),
+ "has_input": true,
})
} else {
customClaudeLog.Debug(context.Background(), "tool_use block start without input", map[string]any{
@@ -593,7 +594,7 @@ func (cp *CustomClaudeProvider) parseCompleteResponse(apiResp map[string]any) (t
content, ok := apiResp["content"].([]any)
if !ok || len(content) == 0 {
- return types.Message{}, fmt.Errorf("no content in response")
+ return types.Message{}, errors.New("no content in response")
}
for _, item := range content {
diff --git a/pkg/provider/deepseek.go b/pkg/provider/deepseek.go
index 128f495..e2f406c 100644
--- a/pkg/provider/deepseek.go
+++ b/pkg/provider/deepseek.go
@@ -5,6 +5,7 @@ import (
"bytes"
"context"
"encoding/json"
+ "errors"
"fmt"
"io"
"net/http"
@@ -34,7 +35,7 @@ type DeepseekProvider struct {
// NewDeepseekProvider 创建 Deepseek 提供商
func NewDeepseekProvider(config *types.ModelConfig) (*DeepseekProvider, error) {
if config.APIKey == "" {
- return nil, fmt.Errorf("deepseek api key is required")
+ return nil, errors.New("deepseek api key is required")
}
baseURL := config.BaseURL
@@ -80,7 +81,7 @@ func (dp *DeepseekProvider) Complete(ctx context.Context, messages []types.Messa
fullURL := dp.baseURL + endpoint
deepseekLog.Info(ctx, "API endpoint", map[string]any{"url": fullURL})
- req, err := http.NewRequestWithContext(ctx, "POST", fullURL, bytes.NewReader(jsonData))
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, fullURL, bytes.NewReader(jsonData))
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
@@ -169,7 +170,7 @@ func (dp *DeepseekProvider) Stream(ctx context.Context, messages []types.Message
endpoint = "/v1/chat/completions"
}
}
- req, err := http.NewRequestWithContext(ctx, "POST", dp.baseURL+endpoint, bytes.NewReader(jsonData))
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, dp.baseURL+endpoint, bytes.NewReader(jsonData))
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
@@ -595,17 +596,17 @@ func (dp *DeepseekProvider) parseCompleteResponse(apiResp map[string]any) (types
// 获取第一个choice
choices, ok := apiResp["choices"].([]any)
if !ok || len(choices) == 0 {
- return types.Message{}, fmt.Errorf("no choices in response")
+ return types.Message{}, errors.New("no choices in response")
}
choice, ok := choices[0].(map[string]any)
if !ok {
- return types.Message{}, fmt.Errorf("invalid choice format")
+ return types.Message{}, errors.New("invalid choice format")
}
message, ok := choice["message"].(map[string]any)
if !ok {
- return types.Message{}, fmt.Errorf("no message in choice")
+ return types.Message{}, errors.New("no message in choice")
}
// 解析文本内容
diff --git a/pkg/provider/doubao.go b/pkg/provider/doubao.go
index 9a32d3c..ab8182b 100644
--- a/pkg/provider/doubao.go
+++ b/pkg/provider/doubao.go
@@ -1,7 +1,7 @@
package provider
import (
- "fmt"
+ "errors"
"github.com/astercloud/aster/pkg/types"
)
@@ -15,6 +15,7 @@ const (
// 字节跳动的企业级 AI 服务,基于火山引擎
type DoubaoProvider struct {
*OpenAICompatibleProvider
+
endpointID string // 模型端点 ID
}
@@ -29,7 +30,7 @@ func NewDoubaoProvider(config *types.ModelConfig, dbConfig *DoubaoConfig) (Provi
if dbConfig == nil || dbConfig.EndpointID == "" {
// 尝试从 Model 字段获取 endpoint_id
if config.Model == "" {
- return nil, fmt.Errorf("doubao: endpoint_id is required")
+ return nil, errors.New("doubao: endpoint_id is required")
}
dbConfig = &DoubaoConfig{
EndpointID: config.Model,
diff --git a/pkg/provider/gemini.go b/pkg/provider/gemini.go
index bcd0489..1e9dc28 100644
--- a/pkg/provider/gemini.go
+++ b/pkg/provider/gemini.go
@@ -5,6 +5,7 @@ import (
"bytes"
"context"
"encoding/json"
+ "errors"
"fmt"
"io"
"net/http"
@@ -83,7 +84,7 @@ type GeminiFunctionDeclaration struct {
// NewGeminiProvider 创建 Gemini 提供商
func NewGeminiProvider(config *types.ModelConfig) (Provider, error) {
if config.APIKey == "" {
- return nil, fmt.Errorf("gemini: API key is required")
+ return nil, errors.New("gemini: API key is required")
}
// 设置默认模型
@@ -123,7 +124,7 @@ func (p *GeminiProvider) Stream(
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(bodyBytes))
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(bodyBytes))
if err != nil {
return nil, err
}
@@ -170,7 +171,7 @@ func (p *GeminiProvider) Complete(
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
- req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(bodyBytes))
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(bodyBytes))
if err != nil {
return nil, err
}
@@ -505,18 +506,18 @@ func (p *GeminiProvider) parseStreamChunk(chunk map[string]any) []StreamChunk {
func (p *GeminiProvider) parseCompleteResponse(apiResp map[string]any) (types.Message, error) {
candidates, ok := apiResp["candidates"].([]any)
if !ok || len(candidates) == 0 {
- return types.Message{}, fmt.Errorf("no candidates in response")
+ return types.Message{}, errors.New("no candidates in response")
}
candidate := candidates[0].(map[string]any)
content, ok := candidate["content"].(map[string]any)
if !ok {
- return types.Message{}, fmt.Errorf("no content in candidate")
+ return types.Message{}, errors.New("no content in candidate")
}
parts, ok := content["parts"].([]any)
if !ok {
- return types.Message{}, fmt.Errorf("no parts in content")
+ return types.Message{}, errors.New("no parts in content")
}
// 构建消息
diff --git a/pkg/provider/glm.go b/pkg/provider/glm.go
index aead686..0bb257a 100644
--- a/pkg/provider/glm.go
+++ b/pkg/provider/glm.go
@@ -5,6 +5,7 @@ import (
"bytes"
"context"
"encoding/json"
+ "errors"
"fmt"
"io"
"net/http"
@@ -33,7 +34,7 @@ type GLMProvider struct {
// NewGLMProvider 创建 GLM 提供商
func NewGLMProvider(config *types.ModelConfig) (*GLMProvider, error) {
if config.APIKey == "" {
- return nil, fmt.Errorf("glm api key is required")
+ return nil, errors.New("glm api key is required")
}
baseURL := config.BaseURL
@@ -70,7 +71,7 @@ func (gp *GLMProvider) Complete(ctx context.Context, messages []types.Message, o
endpoint = "/v4/chat/completions"
}
}
- req, err := http.NewRequestWithContext(ctx, "POST", gp.baseURL+endpoint, bytes.NewReader(jsonData))
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, gp.baseURL+endpoint, bytes.NewReader(jsonData))
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
@@ -150,7 +151,7 @@ func (gp *GLMProvider) Stream(ctx context.Context, messages []types.Message, opt
endpoint = "/v4/chat/completions"
}
}
- req, err := http.NewRequestWithContext(ctx, "POST", gp.baseURL+endpoint, bytes.NewReader(jsonData))
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, gp.baseURL+endpoint, bytes.NewReader(jsonData))
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
@@ -541,17 +542,17 @@ func (gp *GLMProvider) parseCompleteResponse(apiResp map[string]any) (types.Mess
// 获取第一个choice
choices, ok := apiResp["choices"].([]any)
if !ok || len(choices) == 0 {
- return types.Message{}, fmt.Errorf("no choices in response")
+ return types.Message{}, errors.New("no choices in response")
}
choice, ok := choices[0].(map[string]any)
if !ok {
- return types.Message{}, fmt.Errorf("invalid choice format")
+ return types.Message{}, errors.New("invalid choice format")
}
message, ok := choice["message"].(map[string]any)
if !ok {
- return types.Message{}, fmt.Errorf("no message in choice")
+ return types.Message{}, errors.New("no message in choice")
}
// 解析文本内容
diff --git a/pkg/provider/interface.go b/pkg/provider/interface.go
index 4ddfdcf..45dcd0c 100644
--- a/pkg/provider/interface.go
+++ b/pkg/provider/interface.go
@@ -210,10 +210,10 @@ type ProviderCapabilities struct {
SupportVideo bool // 是否支持视频
// 高级能力
- SupportReasoning bool // 是否支持推理模型(o1/o3/R1)
- SupportPromptCache bool // 是否支持 Prompt Caching
- SupportJSONMode bool // 是否支持 JSON 模式
- SupportFunctionCall bool // 是否支持 Function Calling
+ SupportReasoning bool // 是否支持推理模型(o1/o3/R1)
+ SupportPromptCache bool // 是否支持 Prompt Caching
+ SupportJSONMode bool // 是否支持 JSON 模式
+ SupportFunctionCall bool // 是否支持 Function Calling
SupportStructuredOutput bool // 是否支持结构化输出(JSON Schema)
// 限制
diff --git a/pkg/provider/openai.go b/pkg/provider/openai.go
index fce23f7..7979217 100644
--- a/pkg/provider/openai.go
+++ b/pkg/provider/openai.go
@@ -1,6 +1,8 @@
package provider
import (
+ "slices"
+
"github.com/astercloud/aster/pkg/types"
)
@@ -76,12 +78,7 @@ func (p *OpenAIProvider) Capabilities() ProviderCapabilities {
// isReasoningModel 检查是否是推理模型
func isReasoningModel(model string) bool {
reasoningModels := []string{"o1", "o3", "o1-mini", "o3-mini", "o1-preview"}
- for _, rm := range reasoningModels {
- if model == rm {
- return true
- }
- }
- return false
+ return slices.Contains(reasoningModels, model)
}
// OpenAIFactory OpenAI 工厂
diff --git a/pkg/provider/openai_compatible.go b/pkg/provider/openai_compatible.go
index 8e4b88f..9c2a5f1 100644
--- a/pkg/provider/openai_compatible.go
+++ b/pkg/provider/openai_compatible.go
@@ -5,6 +5,7 @@ import (
"bytes"
"context"
"encoding/json"
+ "errors"
"fmt"
"io"
"net/http"
@@ -69,10 +70,10 @@ type OpenAICompatibleOptions struct {
// 用于用户自带 API Key 场景,如 new-api、API2D 等
func NewCustomProvider(config *types.ModelConfig) (*OpenAICompatibleProvider, error) {
if config.BaseURL == "" {
- return nil, fmt.Errorf("custom provider: base_url is required")
+ return nil, errors.New("custom provider: base_url is required")
}
if config.APIKey == "" {
- return nil, fmt.Errorf("custom provider: api_key is required")
+ return nil, errors.New("custom provider: api_key is required")
}
providerName := "custom"
@@ -192,11 +193,11 @@ func (p *OpenAICompatibleProvider) Stream(
// 处理特定错误类型
switch errType {
case "engine_overloaded_error":
- return nil, fmt.Errorf("server_overloaded: 服务器当前负载过高,请稍后重试")
+ return nil, errors.New("server_overloaded: 服务器当前负载过高,请稍后重试")
case "rate_limit_error":
- return nil, fmt.Errorf("rate_limited: 请求过于频繁,请稍后重试")
+ return nil, errors.New("rate_limited: 请求过于频繁,请稍后重试")
case "invalid_api_key":
- return nil, fmt.Errorf("auth_error: API Key 无效或已过期")
+ return nil, errors.New("auth_error: API Key 无效或已过期")
default:
if errMsg != "" {
return nil, fmt.Errorf("%s: %s", errType, errMsg)
@@ -514,7 +515,7 @@ func (p *OpenAICompatibleProvider) createHTTPRequest(ctx context.Context, reques
}
url := p.baseURL + "/chat/completions"
- req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(bodyBytes))
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(bodyBytes))
if err != nil {
return nil, err
}
@@ -561,7 +562,7 @@ func (p *OpenAICompatibleProvider) doRequestWithRetry(req *http.Request) (*http.
// 检查是否需要重试
shouldRetry := false
- if resp.StatusCode == 429 && p.options.RetryOn429 {
+ if resp.StatusCode == http.StatusTooManyRequests && p.options.RetryOn429 {
shouldRetry = true
} else if resp.StatusCode >= 500 && p.options.RetryOn500 {
shouldRetry = true
@@ -745,13 +746,13 @@ func (p *OpenAICompatibleProvider) parseStreamChunk(chunk map[string]any) []Stre
func (p *OpenAICompatibleProvider) parseCompleteResponse(apiResp map[string]any) (types.Message, error) {
choices, ok := apiResp["choices"].([]any)
if !ok || len(choices) == 0 {
- return types.Message{}, fmt.Errorf("no choices in response")
+ return types.Message{}, errors.New("no choices in response")
}
choice := choices[0].(map[string]any)
message, ok := choice["message"].(map[string]any)
if !ok {
- return types.Message{}, fmt.Errorf("no message in choice")
+ return types.Message{}, errors.New("no message in choice")
}
// 解析角色
diff --git a/pkg/provider/openai_compatible_test.go b/pkg/provider/openai_compatible_test.go
index 3da5da2..8623c37 100644
--- a/pkg/provider/openai_compatible_test.go
+++ b/pkg/provider/openai_compatible_test.go
@@ -564,8 +564,7 @@ func BenchmarkProviderCreation(b *testing.B) {
factory := NewMultiProviderFactory()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_, _ = factory.Create(config)
}
}
@@ -588,8 +587,7 @@ func BenchmarkMessageConversion(b *testing.B) {
{Role: types.RoleUser, Content: "How are you?"},
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
_ = baseProvider.convertMessages(messages)
}
}
diff --git a/pkg/reasoning/engine.go b/pkg/reasoning/engine.go
index 5165bf4..31d9961 100644
--- a/pkg/reasoning/engine.go
+++ b/pkg/reasoning/engine.go
@@ -179,11 +179,13 @@ func (e *Engine) buildStepPrompt(chain *Chain, basePrompt string) string {
func (e *Engine) parseStepResponse(response *provider.CompleteResponse) (*Step, error) {
// 提取文本内容
text := ""
+ var textSb182 strings.Builder
for _, block := range response.Message.ContentBlocks {
if textBlock, ok := block.(*types.TextBlock); ok {
- text += textBlock.Text
+ textSb182.WriteString(textBlock.Text)
}
}
+ text += textSb182.String()
// 尝试 JSON 解析
if e.config.UseJSON {
diff --git a/pkg/reasoning/reasoning.go b/pkg/reasoning/reasoning.go
index 95c9ddb..c2ff1e3 100644
--- a/pkg/reasoning/reasoning.go
+++ b/pkg/reasoning/reasoning.go
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
+ "strings"
"time"
)
@@ -184,16 +185,18 @@ func (c *Chain) Summary() string {
summary += fmt.Sprintf("Steps: %d/%d (min: %d, max: %d)\n", len(c.Steps), c.MaxSteps, c.MinSteps, c.MaxSteps)
summary += fmt.Sprintf("Status: %s\n\n", c.Status)
+ var summarySb187 strings.Builder
for i, step := range c.Steps {
- summary += fmt.Sprintf("Step %d: %s\n", i+1, step.Title)
- summary += fmt.Sprintf(" Action: %s\n", step.Action)
- summary += fmt.Sprintf(" Confidence: %.2f\n", step.Confidence)
- summary += fmt.Sprintf(" Status: %s\n", step.Status)
+ summarySb187.WriteString(fmt.Sprintf("Step %d: %s\n", i+1, step.Title))
+ summarySb187.WriteString(fmt.Sprintf(" Action: %s\n", step.Action))
+ summarySb187.WriteString(fmt.Sprintf(" Confidence: %.2f\n", step.Confidence))
+ summarySb187.WriteString(fmt.Sprintf(" Status: %s\n", step.Status))
if step.Result != "" {
- summary += fmt.Sprintf(" Result: %s\n", truncate(step.Result, 100))
+ summarySb187.WriteString(fmt.Sprintf(" Result: %s\n", truncate(step.Result, 100)))
}
- summary += "\n"
+ summarySb187.WriteString("\n")
}
+ summary += summarySb187.String()
return summary
}
diff --git a/pkg/reasoning/reasoning_test.go b/pkg/reasoning/reasoning_test.go
index a1606f7..f96bec2 100644
--- a/pkg/reasoning/reasoning_test.go
+++ b/pkg/reasoning/reasoning_test.go
@@ -126,7 +126,7 @@ func TestChainShouldContinue(t *testing.T) {
})
// 添加步骤
- for i := 0; i < tt.stepCount; i++ {
+ for range tt.stepCount {
step := Step{
Title: "Test Step",
Confidence: 0.9,
diff --git a/pkg/recipe/recipe.go b/pkg/recipe/recipe.go
index e991a94..39d250f 100644
--- a/pkg/recipe/recipe.go
+++ b/pkg/recipe/recipe.go
@@ -4,6 +4,7 @@
package recipe
import (
+ "errors"
"fmt"
"os"
"path/filepath"
@@ -195,11 +196,11 @@ func LoadFromBytes(data []byte) (*Recipe, error) {
// Validate checks if the recipe is valid.
func (r *Recipe) Validate() error {
if r.Title == "" {
- return fmt.Errorf("title is required")
+ return errors.New("title is required")
}
if r.Description == "" {
- return fmt.Errorf("description is required")
+ return errors.New("description is required")
}
// At least one of instructions or prompt should be set for a useful recipe
@@ -225,21 +226,21 @@ func (r *Recipe) Validate() error {
// Validate checks if the parameter is valid.
func (p *Parameter) Validate() error {
if p.Key == "" {
- return fmt.Errorf("key is required")
+ return errors.New("key is required")
}
if p.Type == "" {
- return fmt.Errorf("input_type is required")
+ return errors.New("input_type is required")
}
// File type cannot have default values (security)
if p.Type == ParamTypeFile && p.Default != "" {
- return fmt.Errorf("file parameters cannot have default values")
+ return errors.New("file parameters cannot have default values")
}
// Select type requires options
if p.Type == ParamTypeSelect && len(p.Options) == 0 {
- return fmt.Errorf("select parameters require options")
+ return errors.New("select parameters require options")
}
return nil
@@ -248,21 +249,21 @@ func (p *Parameter) Validate() error {
// Validate checks if the extension is valid.
func (e *ExtensionConfig) Validate() error {
if e.Name == "" {
- return fmt.Errorf("name is required")
+ return errors.New("name is required")
}
if e.Type == "" {
- return fmt.Errorf("type is required")
+ return errors.New("type is required")
}
switch e.Type {
case "stdio":
if e.Cmd == "" {
- return fmt.Errorf("cmd is required for stdio extensions")
+ return errors.New("cmd is required for stdio extensions")
}
case "sse":
if e.URL == "" {
- return fmt.Errorf("url is required for sse extensions")
+ return errors.New("url is required for sse extensions")
}
case "builtin":
// No additional validation needed
diff --git a/pkg/router/router.go b/pkg/router/router.go
index 5e7e92d..0a0c646 100644
--- a/pkg/router/router.go
+++ b/pkg/router/router.go
@@ -2,6 +2,7 @@ package router
import (
"context"
+ "errors"
"fmt"
"github.com/astercloud/aster/pkg/types"
@@ -73,7 +74,7 @@ func (r *StaticRouter) SelectModel(_ context.Context, intent *RouteIntent) (*typ
if r.defaultModel != nil {
return r.defaultModel, nil
}
- return nil, fmt.Errorf("route intent is nil and no default model configured")
+ return nil, errors.New("route intent is nil and no default model configured")
}
// 1. Task + Priority 精确匹配
diff --git a/pkg/run/context.go b/pkg/run/context.go
index 8e4ebfa..43fb596 100644
--- a/pkg/run/context.go
+++ b/pkg/run/context.go
@@ -2,6 +2,7 @@ package run
import (
"context"
+ "maps"
"time"
)
@@ -130,18 +131,10 @@ func (c *Context) Clone() *Context {
}
// 复制 map
- for k, v := range c.Metadata {
- clone.Metadata[k] = v
- }
- for k, v := range c.SessionState {
- clone.SessionState[k] = v
- }
- for k, v := range c.KnowledgeFilters {
- clone.KnowledgeFilters[k] = v
- }
- for k, v := range c.Dependencies {
- clone.Dependencies[k] = v
- }
+ maps.Copy(clone.Metadata, c.Metadata)
+ maps.Copy(clone.SessionState, c.SessionState)
+ maps.Copy(clone.KnowledgeFilters, c.KnowledgeFilters)
+ maps.Copy(clone.Dependencies, c.Dependencies)
return clone
}
diff --git a/pkg/run/event.go b/pkg/run/event.go
index fb052ba..4ad2ebc 100644
--- a/pkg/run/event.go
+++ b/pkg/run/event.go
@@ -112,6 +112,7 @@ func NewBaseEvent(eventType EventType, runID string) *BaseEvent {
// AgentEvent Agent 事件
type AgentEvent struct {
BaseEvent
+
AgentID string `json:"agent_id,omitempty"`
Content string `json:"content,omitempty"`
Delta string `json:"delta,omitempty"`
@@ -121,6 +122,7 @@ type AgentEvent struct {
// WorkflowEvent Workflow 事件
type WorkflowEvent struct {
BaseEvent
+
WorkflowID string `json:"workflow_id,omitempty"`
StepID string `json:"step_id,omitempty"`
StepName string `json:"step_name,omitempty"`
@@ -132,6 +134,7 @@ type WorkflowEvent struct {
// TeamEvent Team 事件
type TeamEvent struct {
BaseEvent
+
TeamID string `json:"team_id,omitempty"`
MemberID string `json:"member_id,omitempty"`
Role string `json:"role,omitempty"`
@@ -141,6 +144,7 @@ type TeamEvent struct {
// StatusChangeEvent 状态变更事件
type StatusChangeEvent struct {
BaseEvent
+
OldStatus Status `json:"old_status"`
NewStatus Status `json:"new_status"`
Reason string `json:"reason,omitempty"`
@@ -149,6 +153,7 @@ type StatusChangeEvent struct {
// MetricsEvent 指标事件
type MetricsEvent struct {
BaseEvent
+
TokensUsed int `json:"tokens_used,omitempty"`
TokensInput int `json:"tokens_input,omitempty"`
TokensOutput int `json:"tokens_output,omitempty"`
diff --git a/pkg/run/status.go b/pkg/run/status.go
index 9a95f5f..76e1820 100644
--- a/pkg/run/status.go
+++ b/pkg/run/status.go
@@ -17,7 +17,7 @@ const (
StatusPaused Status = "PAUSED"
// StatusCancelled 取消
- StatusCancelled Status = "CANCELLED"
+ StatusCancelled Status = "CANCELED"
// StatusError 错误
StatusError Status = "ERROR"
diff --git a/pkg/sandbox/cloud/aliyun.go b/pkg/sandbox/cloud/aliyun.go
index 935cd7b..63e5934 100644
--- a/pkg/sandbox/cloud/aliyun.go
+++ b/pkg/sandbox/cloud/aliyun.go
@@ -3,6 +3,7 @@ package cloud
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"path/filepath"
"strings"
@@ -14,6 +15,7 @@ import (
// AliyunSandbox 阿里云 AgentBay 沙箱
type AliyunSandbox struct {
*sandbox.RemoteSandbox
+
config *AliyunConfig
mcpClient *MCPClient
}
@@ -43,10 +45,10 @@ type AliyunConfig struct {
// NewAliyunSandbox 创建阿里云沙箱
func NewAliyunSandbox(config *AliyunConfig) (*AliyunSandbox, error) {
if config.MCPEndpoint == "" {
- return nil, fmt.Errorf("MCP endpoint is required")
+ return nil, errors.New("MCP endpoint is required")
}
if config.AccessKeyID == "" || config.AccessKeySecret == "" {
- return nil, fmt.Errorf("access credentials are required")
+ return nil, errors.New("access credentials are required")
}
// 设置默认值
diff --git a/pkg/sandbox/cloud/mcp.go b/pkg/sandbox/cloud/mcp.go
index 2eea359..fa884d8 100644
--- a/pkg/sandbox/cloud/mcp.go
+++ b/pkg/sandbox/cloud/mcp.go
@@ -64,7 +64,7 @@ func (mc *MCPClient) CallTool(ctx context.Context, toolName string, params map[s
}
// 创建 HTTP 请求
- httpReq, err := http.NewRequestWithContext(ctx, "POST", mc.endpoint, bytes.NewReader(reqBody))
+ httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, mc.endpoint, bytes.NewReader(reqBody))
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
@@ -122,7 +122,7 @@ func (mc *MCPClient) ListTools(ctx context.Context) ([]MCPTool, error) {
return nil, fmt.Errorf("marshal request: %w", err)
}
- httpReq, err := http.NewRequestWithContext(ctx, "POST", mc.endpoint, bytes.NewReader(reqBody))
+ httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, mc.endpoint, bytes.NewReader(reqBody))
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
diff --git a/pkg/sandbox/cloud/volcengine.go b/pkg/sandbox/cloud/volcengine.go
index a546eeb..aa1fa3e 100644
--- a/pkg/sandbox/cloud/volcengine.go
+++ b/pkg/sandbox/cloud/volcengine.go
@@ -3,6 +3,7 @@ package cloud
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"path/filepath"
"strings"
@@ -14,6 +15,7 @@ import (
// VolcengineSandbox 火山引擎沙箱
type VolcengineSandbox struct {
*sandbox.RemoteSandbox
+
config *VolcengineConfig
mcpClient *MCPClient
sessionID string
@@ -41,10 +43,10 @@ type VolcengineConfig struct {
// NewVolcengineSandbox 创建火山引擎沙箱
func NewVolcengineSandbox(config *VolcengineConfig) (*VolcengineSandbox, error) {
if config.Endpoint == "" {
- return nil, fmt.Errorf("endpoint is required")
+ return nil, errors.New("endpoint is required")
}
if config.AccessKey == "" || config.SecretKey == "" {
- return nil, fmt.Errorf("access credentials are required")
+ return nil, errors.New("access credentials are required")
}
// 设置默认值
diff --git a/pkg/sandbox/factory.go b/pkg/sandbox/factory.go
index 7957d23..28d7606 100644
--- a/pkg/sandbox/factory.go
+++ b/pkg/sandbox/factory.go
@@ -1,6 +1,7 @@
package sandbox
import (
+ "errors"
"fmt"
"time"
@@ -38,23 +39,23 @@ func (f *Factory) Create(config *types.SandboxConfig) (Sandbox, error) {
})
case types.SandboxKindDocker:
- return nil, fmt.Errorf("docker sandbox not implemented yet")
+ return nil, errors.New("docker sandbox not implemented yet")
case types.SandboxKindK8s:
- return nil, fmt.Errorf("k8s sandbox not implemented yet")
+ return nil, errors.New("k8s sandbox not implemented yet")
case types.SandboxKindAliyun:
// 阿里云沙箱需要使用 cloud.NewAliyunSandbox() 直接创建
- return nil, fmt.Errorf("aliyun sandbox: use cloud.NewAliyunSandbox() directly")
+ return nil, errors.New("aliyun sandbox: use cloud.NewAliyunSandbox() directly")
case types.SandboxKindVolcengine:
// 火山引擎沙箱需要使用 cloud.NewVolcengineSandbox() 直接创建
- return nil, fmt.Errorf("volcengine sandbox: use cloud.NewVolcengineSandbox() directly")
+ return nil, errors.New("volcengine sandbox: use cloud.NewVolcengineSandbox() directly")
case types.SandboxKindRemote:
// 通用远程沙箱
if config.Extra == nil {
- return nil, fmt.Errorf("remote sandbox requires extra configuration")
+ return nil, errors.New("remote sandbox requires extra configuration")
}
baseURL, _ := config.Extra["base_url"].(string)
diff --git a/pkg/sandbox/local.go b/pkg/sandbox/local.go
index a16c797..f072590 100644
--- a/pkg/sandbox/local.go
+++ b/pkg/sandbox/local.go
@@ -4,12 +4,14 @@ import (
"context"
"crypto/rand"
"encoding/hex"
+ "errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
+ "slices"
"strings"
"sync"
"time"
@@ -412,7 +414,7 @@ func (ls *LocalSandbox) Exec(ctx context.Context, cmd string, opts *ExecOptions)
return &ExecResult{
Code: 1,
Stdout: "",
- Stderr: fmt.Sprintf("Dangerous command blocked: %s", blockReason),
+ Stderr: "Dangerous command blocked: " + blockReason,
}, nil
}
@@ -423,7 +425,7 @@ func (ls *LocalSandbox) Exec(ctx context.Context, cmd string, opts *ExecOptions)
return &ExecResult{
Code: 1,
Stdout: "",
- Stderr: fmt.Sprintf("Path security violation: %s", pathIssue),
+ Stderr: "Path security violation: " + pathIssue,
}, nil
}
}
@@ -499,7 +501,8 @@ func (ls *LocalSandbox) execWithLimits(ctx context.Context, cmd string, opts *Ex
}
if err != nil {
- if exitErr, ok := err.(*exec.ExitError); ok {
+ exitErr := &exec.ExitError{}
+ if errors.As(err, &exitErr) {
return &ExecResult{
Code: exitErr.ExitCode(),
Stdout: string(output),
@@ -628,7 +631,7 @@ func (ls *LocalSandbox) checkDangerousCommand(cmd string) string {
for _, pattern := range dangerousPatterns {
if pattern.MatchString(normalizedCmd) {
- return fmt.Sprintf("matches dangerous pattern: %s", pattern.String()[:min(50, len(pattern.String()))])
+ return "matches dangerous pattern: " + pattern.String()[:min(50, len(pattern.String()))]
}
}
@@ -718,7 +721,7 @@ func (ls *LocalSandbox) checkPathSecurity(cmd string) string {
// 检查是否访问敏感路径
for _, sensitive := range sensitivePaths {
if strings.HasPrefix(absPath, sensitive) {
- return fmt.Sprintf("access to sensitive path: %s", sensitive)
+ return "access to sensitive path: " + sensitive
}
}
@@ -726,7 +729,7 @@ func (ls *LocalSandbox) checkPathSecurity(cmd string) string {
if strings.Contains(path, "..") {
// 检查规范化后是否超出工作目录
if ls.enforceBoundary && !ls.fs.IsInside(absPath) {
- return fmt.Sprintf("path traversal detected: %s", path)
+ return "path traversal detected: " + path
}
}
}
@@ -895,14 +898,6 @@ func (ls *LocalSandbox) RemoveBlockedCommand(cmd string) {
delete(ls.blockedCommands, cmd)
}
-// min 返回两个整数中的较小值
-func min(a, b int) int {
- if a < b {
- return a
- }
- return b
-}
-
// Watch 监听文件变更
func (ls *LocalSandbox) Watch(paths []string, listener FileChangeListener) (string, error) {
if !ls.watchEnabled {
@@ -1105,7 +1100,8 @@ func (ls *LocalSandbox) execDirect(ctx context.Context, cmd string, opts *ExecOp
output, err := command.CombinedOutput()
if err != nil {
- if exitErr, ok := err.(*exec.ExitError); ok {
+ exitErr := &exec.ExitError{}
+ if errors.As(err, &exitErr) {
return &ExecResult{
Code: exitErr.ExitCode(),
Stdout: string(output),
@@ -1218,11 +1214,5 @@ func (ls *LocalSandbox) CheckUnixSocketAccess(socketPath string) bool {
return true
}
- for _, allowed := range ls.networkConfig.AllowUnixSockets {
- if socketPath == allowed {
- return true
- }
- }
-
- return false
+ return slices.Contains(ls.networkConfig.AllowUnixSockets, socketPath)
}
diff --git a/pkg/sandbox/local_test.go b/pkg/sandbox/local_test.go
index 3ace2fc..f180cc3 100644
--- a/pkg/sandbox/local_test.go
+++ b/pkg/sandbox/local_test.go
@@ -495,7 +495,7 @@ func TestLocalSandbox_CommandStats(t *testing.T) {
}
// Execute echo multiple times
- for i := 0; i < 5; i++ {
+ for range 5 {
_, _ = sb.Exec(context.Background(), "echo test", nil)
}
diff --git a/pkg/sandbox/mock.go b/pkg/sandbox/mock.go
index 96cf6c5..1c38daf 100644
--- a/pkg/sandbox/mock.go
+++ b/pkg/sandbox/mock.go
@@ -38,7 +38,7 @@ func (ms *MockSandbox) Exec(ctx context.Context, cmd string, opts *ExecOptions)
// 模拟命令执行
return &ExecResult{
Code: 0,
- Stdout: fmt.Sprintf("Mock output for: %s", cmd),
+ Stdout: "Mock output for: " + cmd,
Stderr: "",
}, nil
}
diff --git a/pkg/sandbox/remote.go b/pkg/sandbox/remote.go
index e5014f1..45d74b5 100644
--- a/pkg/sandbox/remote.go
+++ b/pkg/sandbox/remote.go
@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
+ "errors"
"fmt"
"io"
"net/http"
@@ -65,7 +66,7 @@ func (rc *RemoteClient) Call(ctx context.Context, method, path string, body any)
// 设置通用请求头
req.Header.Set("Content-Type", "application/json")
if rc.apiKey != "" {
- req.Header.Set("X-API-Key", rc.apiKey)
+ req.Header.Set("X-Api-Key", rc.apiKey)
}
// 设置自定义请求头
@@ -169,7 +170,7 @@ func (rs *RemoteSandbox) Kind() string {
// Exec 执行命令 (需要子类实现具体的 API 调用)
func (rs *RemoteSandbox) Exec(ctx context.Context, cmd string, opts *ExecOptions) (*ExecResult, error) {
- return nil, fmt.Errorf("exec not implemented in base RemoteSandbox")
+ return nil, errors.New("exec not implemented in base RemoteSandbox")
}
// FS 返回文件系统接口
@@ -184,12 +185,12 @@ func (rs *RemoteSandbox) WorkDir() string {
// Watch 监听文件变化 (远程沙箱通常不支持)
func (rs *RemoteSandbox) Watch(paths []string, listener FileChangeListener) (string, error) {
- return "", fmt.Errorf("watch not supported in remote sandbox")
+ return "", errors.New("watch not supported in remote sandbox")
}
// Unwatch 取消监听 (远程沙箱通常不支持)
func (rs *RemoteSandbox) Unwatch(watchID string) error {
- return fmt.Errorf("unwatch not supported in remote sandbox")
+ return errors.New("unwatch not supported in remote sandbox")
}
// Dispose 清理资源
@@ -228,12 +229,12 @@ func (rfs *RemoteFS) IsInside(path string) bool {
// Read 读取文件 (需要子类实现)
func (rfs *RemoteFS) Read(ctx context.Context, path string) (string, error) {
- return "", fmt.Errorf("read not implemented in base RemoteFS")
+ return "", errors.New("read not implemented in base RemoteFS")
}
// Write 写入文件 (需要子类实现)
func (rfs *RemoteFS) Write(ctx context.Context, path string, content string) error {
- return fmt.Errorf("write not implemented in base RemoteFS")
+ return errors.New("write not implemented in base RemoteFS")
}
// Temp 生成临时文件路径
@@ -243,10 +244,10 @@ func (rfs *RemoteFS) Temp(name string) string {
// Stat 获取文件信息 (需要子类实现)
func (rfs *RemoteFS) Stat(ctx context.Context, path string) (FileInfo, error) {
- return FileInfo{}, fmt.Errorf("stat not implemented in base RemoteFS")
+ return FileInfo{}, errors.New("stat not implemented in base RemoteFS")
}
// Glob 匹配文件 (需要子类实现)
func (rfs *RemoteFS) Glob(ctx context.Context, pattern string, opts *GlobOptions) ([]string, error) {
- return nil, fmt.Errorf("glob not implemented in base RemoteFS")
+ return nil, errors.New("glob not implemented in base RemoteFS")
}
diff --git a/pkg/security/access_control.go b/pkg/security/access_control.go
index acb794d..1f5d326 100644
--- a/pkg/security/access_control.go
+++ b/pkg/security/access_control.go
@@ -3,7 +3,9 @@ package security
import (
"crypto/sha256"
"encoding/hex"
+ "errors"
"fmt"
+ "slices"
"sync"
"time"
)
@@ -312,7 +314,7 @@ func (ac *AccessController) CreateUser(user *User) error {
defer ac.mu.Unlock()
if user.ID == "" {
- return fmt.Errorf("user ID is required")
+ return errors.New("user ID is required")
}
if _, exists := ac.users[user.ID]; exists {
@@ -320,7 +322,7 @@ func (ac *AccessController) CreateUser(user *User) error {
}
if user.Username == "" {
- return fmt.Errorf("username is required")
+ return errors.New("username is required")
}
// 检查用户名是否已存在
@@ -719,10 +721,8 @@ func (ac *AccessController) compareValues(actual any, operator string, expected
func (ac *AccessController) valueInList(value string, list any) bool {
switch list := list.(type) {
case []string:
- for _, item := range list {
- if item == value {
- return true
- }
+ if slices.Contains(list, value) {
+ return true
}
case []any:
for _, item := range list {
@@ -740,7 +740,7 @@ func (ac *AccessController) sortPoliciesByPriority(policies []*AccessPolicy) []*
sorted := make([]*AccessPolicy, len(policies))
copy(sorted, policies)
- for i := 0; i < len(sorted)-1; i++ {
+ for i := range len(sorted) - 1 {
for j := i + 1; j < len(sorted); j++ {
if sorted[i].Priority > sorted[j].Priority {
sorted[i], sorted[j] = sorted[j], sorted[i]
@@ -822,7 +822,6 @@ func (ac *AccessController) checkPermissionList(permissionIDs []string, resource
// 检查资源和操作匹配
if (permission.Resource == "*" || permission.Resource == resource) &&
(permission.Action == "*" || permission.Action == action) {
-
// 检查权限条件
if ac.checkPermissionConditions(permission.Conditions, context) {
return true
@@ -1052,12 +1051,7 @@ func (cache *AccessCache) set(key string, decision *AccessDecision, ttl time.Dur
// 辅助函数
func contains(slice []string, item string) bool {
- for _, s := range slice {
- if s == item {
- return true
- }
- }
- return false
+ return slices.Contains(slice, item)
}
func removeString(slice []string, item string) []string {
diff --git a/pkg/security/access_control_test.go b/pkg/security/access_control_test.go
index be829ec..ed381ad 100644
--- a/pkg/security/access_control_test.go
+++ b/pkg/security/access_control_test.go
@@ -2,6 +2,7 @@ package security
import (
"context"
+ "errors"
"fmt"
"testing"
"time"
@@ -41,7 +42,7 @@ func (m *mockAuditLog) GetEvent(eventID string) (*AuditEvent, error) {
return &m.events[i], nil
}
}
- return nil, fmt.Errorf("event not found")
+ return nil, errors.New("event not found")
}
func (m *mockAuditLog) GetEventsByUser(userID string, limit int) ([]*AuditEvent, error) {
@@ -390,7 +391,7 @@ func TestAccessController_ConcurrentAccess(t *testing.T) {
// 并发创建多个用户
done := make(chan bool)
- for i := 0; i < 10; i++ {
+ for i := range 10 {
go func(id int) {
user := &User{
ID: fmt.Sprintf("user%d", id),
@@ -403,12 +404,12 @@ func TestAccessController_ConcurrentAccess(t *testing.T) {
}
// 等待所有操作完成
- for i := 0; i < 10; i++ {
+ for range 10 {
<-done
}
// 验证所有用户都创建成功
- for i := 0; i < 10; i++ {
+ for i := range 10 {
_, err := ac.GetUser(fmt.Sprintf("user%d", i))
if err != nil {
t.Errorf("user%d not found", i)
diff --git a/pkg/security/audit.go b/pkg/security/audit.go
index 4853cc3..1048623 100644
--- a/pkg/security/audit.go
+++ b/pkg/security/audit.go
@@ -3,6 +3,7 @@ package security
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"strings"
"sync"
@@ -421,7 +422,7 @@ func NewInMemoryAuditLog(config *AuditConfiguration) *InMemoryAuditLog {
}
// 启动工作协程
- for i := 0; i < al.workers; i++ {
+ for range al.workers {
go al.eventWorker()
}
@@ -457,7 +458,7 @@ func (al *InMemoryAuditLog) LogEventAsync(event AuditEvent) error {
case al.eventChan <- event:
return nil
case <-time.After(time.Second):
- return fmt.Errorf("audit log buffer full, event dropped")
+ return errors.New("audit log buffer full, event dropped")
}
}
@@ -594,7 +595,7 @@ func (al *InMemoryAuditLog) sortEvents(events []*AuditEvent, orderBy string, des
switch orderBy {
case "timestamp":
if desc {
- for i := 0; i < len(events)-1; i++ {
+ for i := range len(events) - 1 {
for j := i + 1; j < len(events); j++ {
if events[i].Timestamp.Before(events[j].Timestamp) {
events[i], events[j] = events[j], events[i]
@@ -602,7 +603,7 @@ func (al *InMemoryAuditLog) sortEvents(events []*AuditEvent, orderBy string, des
}
}
} else {
- for i := 0; i < len(events)-1; i++ {
+ for i := range len(events) - 1 {
for j := i + 1; j < len(events); j++ {
if events[i].Timestamp.After(events[j].Timestamp) {
events[i], events[j] = events[j], events[i]
@@ -831,16 +832,18 @@ func (al *InMemoryAuditLog) exportToCSV(events []*AuditEvent) ([]byte, error) {
var csv string
csv += "ID,Timestamp,Type,Severity,UserID,Message\n"
+ var csvSb834 strings.Builder
for _, event := range events {
- csv += fmt.Sprintf("%s,%s,%s,%s,%s,\"%s\"\n",
+ csvSb834.WriteString(fmt.Sprintf("%s,%s,%s,%s,%s,\"%s\"\n",
event.ID,
event.Timestamp.Format(time.RFC3339),
event.Type,
event.Severity,
event.UserID,
event.Message,
- )
+ ))
}
+ csv += csvSb834.String()
return []byte(csv), nil
}
diff --git a/pkg/security/audit_test.go b/pkg/security/audit_test.go
index b0c1a2f..38f212f 100644
--- a/pkg/security/audit_test.go
+++ b/pkg/security/audit_test.go
@@ -299,7 +299,7 @@ func TestInMemoryAuditLog_ConcurrentWrites(t *testing.T) {
// 并发写入事件
done := make(chan bool)
- for i := 0; i < 10; i++ {
+ for i := range 10 {
go func(id int) {
event := AuditEvent{
Type: AuditTypeUserLogin,
@@ -312,7 +312,7 @@ func TestInMemoryAuditLog_ConcurrentWrites(t *testing.T) {
}
// 等待所有操作完成
- for i := 0; i < 10; i++ {
+ for range 10 {
<-done
}
diff --git a/pkg/security/pii_detector.go b/pkg/security/pii_detector.go
index e488e9a..217a8e8 100644
--- a/pkg/security/pii_detector.go
+++ b/pkg/security/pii_detector.go
@@ -121,8 +121,8 @@ func (d *RegexPIIDetector) ContainsPII(ctx context.Context, text string) (bool,
// sortMatchesByPosition 按起始位置排序匹配结果。
func sortMatchesByPosition(matches []PIIMatch) {
// 简单的冒泡排序
- for i := 0; i < len(matches)-1; i++ {
- for j := 0; j < len(matches)-i-1; j++ {
+ for i := range len(matches) - 1 {
+ for j := range len(matches) - i - 1 {
if matches[j].Start > matches[j+1].Start {
matches[j], matches[j+1] = matches[j+1], matches[j]
}
diff --git a/pkg/security/pii_middleware.go b/pkg/security/pii_middleware.go
index b4b7e03..8bde3a9 100644
--- a/pkg/security/pii_middleware.go
+++ b/pkg/security/pii_middleware.go
@@ -13,6 +13,7 @@ import (
// 在消息发送到 LLM 前自动检测和脱敏 PII。
type PIIRedactionMiddleware struct {
*middleware.BaseMiddleware
+
redactor *Redactor
enableTracking bool // 是否启用追踪(用于还原)
tracking map[string][]PIIMatch // 追踪每个 Agent 的 PII 匹配
@@ -245,6 +246,7 @@ type PIIDetectionSummary struct {
// 根据上下文条件决定是否脱敏。
type ConditionalPIIMiddleware struct {
*middleware.BaseMiddleware
+
redactor *Redactor
condition func(context.Context, *middleware.ModelRequest) bool
}
diff --git a/pkg/security/policy.go b/pkg/security/policy.go
index 29c458e..eb2784a 100644
--- a/pkg/security/policy.go
+++ b/pkg/security/policy.go
@@ -1,8 +1,10 @@
package security
import (
+ "errors"
"fmt"
"regexp"
+ "slices"
"strings"
"sync"
"time"
@@ -441,7 +443,7 @@ func (bpe *BasicPolicyEngine) AddPolicy(policy *SecurityPolicy) error {
defer bpe.mu.Unlock()
if policy.ID == "" {
- return fmt.Errorf("policy ID is required")
+ return errors.New("policy ID is required")
}
if _, exists := bpe.policies[policy.ID]; exists {
@@ -489,7 +491,7 @@ func (bpe *BasicPolicyEngine) UpdatePolicy(policy *SecurityPolicy) error {
defer bpe.mu.Unlock()
if policy.ID == "" {
- return fmt.Errorf("policy ID is required")
+ return errors.New("policy ID is required")
}
existing, exists := bpe.policies[policy.ID]
@@ -612,10 +614,8 @@ func (bpe *BasicPolicyEngine) matchesFilters(policy *SecurityPolicy, filters map
// containsAny 检查数组是否包含任意一个元素
func containsAny(slice []string, elements []string) bool {
for _, elem := range elements {
- for _, item := range slice {
- if item == elem {
- return true
- }
+ if slices.Contains(slice, elem) {
+ return true
}
}
return false
@@ -797,7 +797,7 @@ func (bpe *BasicPolicyEngine) sortPoliciesByPriority(policies []*SecurityPolicy)
sorted := make([]*SecurityPolicy, len(policies))
copy(sorted, policies)
- for i := 0; i < len(sorted)-1; i++ {
+ for i := range len(sorted) - 1 {
for j := i + 1; j < len(sorted); j++ {
if sorted[i].Priority < sorted[j].Priority {
sorted[i], sorted[j] = sorted[j], sorted[i]
@@ -965,10 +965,8 @@ func (bpe *BasicPolicyEngine) valueInList(value, list any) bool {
}
}
case []string:
- for _, item := range list {
- if item == valStr {
- return true
- }
+ if slices.Contains(list, valStr) {
+ return true
}
}
@@ -1039,10 +1037,5 @@ func (bpe *BasicPolicyEngine) determineRiskLevel(score float64) RiskLevel {
// containsPolicy 检查字符串是否在数组中
func containsPolicy(slice []string, item string) bool {
- for _, s := range slice {
- if s == item {
- return true
- }
- }
- return false
+ return slices.Contains(slice, item)
}
diff --git a/pkg/security/policy_test.go b/pkg/security/policy_test.go
index 6afcfc8..d8b9b3a 100644
--- a/pkg/security/policy_test.go
+++ b/pkg/security/policy_test.go
@@ -129,7 +129,7 @@ func TestBasicPolicyEngine_ListPolicies(t *testing.T) {
engine := NewBasicPolicyEngine(nil, &mockAuditLog{})
// 添加多个策略
- for i := 0; i < 3; i++ {
+ for i := range 3 {
policy := &SecurityPolicy{
ID: fmt.Sprintf("policy%d", i),
Name: fmt.Sprintf("Policy %d", i),
diff --git a/pkg/security/redaction.go b/pkg/security/redaction.go
index 049dd28..3683fc1 100644
--- a/pkg/security/redaction.go
+++ b/pkg/security/redaction.go
@@ -179,7 +179,7 @@ type RedactionResult struct {
// GetSummary 获取脱敏摘要
func (r *RedactionResult) GetSummary() string {
if r.Error != "" {
- return fmt.Sprintf("Error during redaction: %s", r.Error)
+ return "Error during redaction: " + r.Error
}
if !r.PIIFound {
diff --git a/pkg/security/redaction_strategies.go b/pkg/security/redaction_strategies.go
index 9413f26..c04e7ef 100644
--- a/pkg/security/redaction_strategies.go
+++ b/pkg/security/redaction_strategies.go
@@ -3,6 +3,7 @@ package security
import (
"context"
"crypto/sha256"
+ "encoding/hex"
"fmt"
"strings"
)
@@ -164,7 +165,7 @@ func (s *HashStrategy) Redact(match PIIMatch) string {
// 使用 SHA256 哈希
data := match.Value + s.Salt
hash := sha256.Sum256([]byte(data))
- hashStr := fmt.Sprintf("%x", hash)
+ hashStr := hex.EncodeToString(hash[:])
if s.ShowPrefix {
prefix := hashStr[:min(s.PrefixLength, len(hashStr))]
@@ -347,17 +348,3 @@ func buildByteToRuneMap(text string) []int {
return byteToRune
}
-
-func max(a, b int) int {
- if a > b {
- return a
- }
- return b
-}
-
-func min(a, b int) int {
- if a < b {
- return a
- }
- return b
-}
diff --git a/pkg/server/workflow_demo.go b/pkg/server/workflow_demo.go
index f1f28a2..2b8721e 100644
--- a/pkg/server/workflow_demo.go
+++ b/pkg/server/workflow_demo.go
@@ -68,6 +68,7 @@ type WorkflowRunEvalRequest struct {
// WorkflowRunEvalResponse 在 WorkflowRunResponse 基础上增加 eval_scores 字段。
type WorkflowRunEvalResponse struct {
WorkflowRunResponse
+
EvalScores []evals.ScoreResult `json:"eval_scores,omitempty"`
}
diff --git a/pkg/session/inmemory.go b/pkg/session/inmemory.go
index 74ee438..e6d513e 100644
--- a/pkg/session/inmemory.go
+++ b/pkg/session/inmemory.go
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"iter"
+ "maps"
"sync"
"time"
@@ -78,9 +79,7 @@ func (s *InMemoryService) Update(ctx context.Context, req *UpdateRequest) error
}
if req.Metadata != nil {
- for k, v := range req.Metadata {
- session.metadata[k] = v
- }
+ maps.Copy(session.metadata, req.Metadata)
}
session.lastUpdateTime = time.Now()
@@ -306,9 +305,7 @@ func (s *inMemoryState) All() iter.Seq2[string, any] {
// 复制数据以避免并发问题
snapshot := make(map[string]any, len(s.data))
- for k, v := range s.data {
- snapshot[k] = v
- }
+ maps.Copy(snapshot, s.data)
return func(yield func(string, any) bool) {
for k, v := range snapshot {
diff --git a/pkg/session/inmemory_test.go b/pkg/session/inmemory_test.go
index 2d0c90c..ff06815 100644
--- a/pkg/session/inmemory_test.go
+++ b/pkg/session/inmemory_test.go
@@ -88,7 +88,7 @@ func TestInMemoryService_List(t *testing.T) {
service := NewInMemoryService()
// 准备测试数据
userID := "user-list-test"
- for i := 0; i < 5; i++ {
+ for range 5 {
if _, err := service.Create(ctx, &CreateRequest{
AppName: "test-app",
UserID: userID,
@@ -108,7 +108,7 @@ func TestInMemoryService_List(t *testing.T) {
t.Run("限制返回数量", func(t *testing.T) {
service := NewInMemoryService()
userID := "user-list-test"
- for i := 0; i < 5; i++ {
+ for range 5 {
if _, err := service.Create(ctx, &CreateRequest{
AppName: "test-app",
UserID: userID,
@@ -129,7 +129,7 @@ func TestInMemoryService_List(t *testing.T) {
t.Run("使用偏移量", func(t *testing.T) {
service := NewInMemoryService()
userID := "user-list-test"
- for i := 0; i < 5; i++ {
+ for range 5 {
if _, err := service.Create(ctx, &CreateRequest{
AppName: "test-app",
UserID: userID,
@@ -182,7 +182,7 @@ func TestInMemoryService_List(t *testing.T) {
UserID: "non-existent-user",
})
require.NoError(t, err)
- assert.Len(t, sessions, 0)
+ assert.Empty(t, sessions)
})
}
@@ -250,7 +250,7 @@ func TestInMemoryService_AppendEvent(t *testing.T) {
})
t.Run("追加多个事件", func(t *testing.T) {
- for i := 0; i < 3; i++ {
+ for i := range 3 {
event := &Event{
ID: "evt-" + string(rune('2'+i)),
Timestamp: time.Now(),
@@ -298,7 +298,7 @@ func TestInMemoryService_AppendEvent(t *testing.T) {
// 验证状态已更新(通过GetEvents验证)
events, err := service.GetEvents(ctx, sess.ID(), nil)
require.NoError(t, err)
- assert.True(t, len(events) > 0)
+ assert.Positive(t, len(events))
})
t.Run("事件带工件变更", func(t *testing.T) {
@@ -349,7 +349,7 @@ func TestInMemoryService_GetEvents(t *testing.T) {
})
// 准备测试数据
- for i := 0; i < 10; i++ {
+ for i := range 10 {
event := &Event{
ID: "evt-" + string(rune('0'+i)),
Timestamp: time.Now(),
@@ -555,9 +555,9 @@ func TestInMemoryService_Concurrency(t *testing.T) {
numGoroutines := 10
eventsPerGoroutine := 10
- for i := 0; i < numGoroutines; i++ {
+ for i := range numGoroutines {
go func(id int) {
- for j := 0; j < eventsPerGoroutine; j++ {
+ for j := range eventsPerGoroutine {
event := &Event{
ID: "evt-concurrent-" + string(rune('0'+id)) + "-" + string(rune('0'+j)),
Timestamp: time.Now(),
@@ -575,7 +575,7 @@ func TestInMemoryService_Concurrency(t *testing.T) {
}
// 等待所有 goroutine 完成
- for i := 0; i < numGoroutines; i++ {
+ for range numGoroutines {
<-done
}
diff --git a/pkg/session/mysql/service.go b/pkg/session/mysql/service.go
index 388d187..0891dc3 100644
--- a/pkg/session/mysql/service.go
+++ b/pkg/session/mysql/service.go
@@ -3,6 +3,7 @@ package mysql
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"strings"
"time"
@@ -61,7 +62,7 @@ func NewService(cfg *Config) (*Service, error) {
}
if cfg.DSN == "" {
- return nil, fmt.Errorf("DSN is required")
+ return nil, errors.New("DSN is required")
}
// 打开数据库连接
@@ -135,7 +136,7 @@ func (s *Service) Get(ctx context.Context, sessionID string) (*session.SessionDa
if err := s.db.WithContext(ctx).
Where("id = ?", sessionID).
First(&model).Error; err != nil {
- if err == gorm.ErrRecordNotFound {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, session.ErrSessionNotFound
}
return nil, fmt.Errorf("get session: %w", err)
diff --git a/pkg/session/mysql/service_test.go b/pkg/session/mysql/service_test.go
index 5325184..58c4a0a 100644
--- a/pkg/session/mysql/service_test.go
+++ b/pkg/session/mysql/service_test.go
@@ -164,7 +164,7 @@ func TestMySQLService_AppendEvent(t *testing.T) {
// 验证事件已存储
events, err := service.GetEvents(ctx, sess.ID, nil)
require.NoError(t, err)
- assert.Equal(t, 1, len(events))
+ assert.Len(t, events, 1)
assert.Equal(t, event.ID, events[0].ID)
})
@@ -232,7 +232,7 @@ func TestMySQLService_AppendEvent(t *testing.T) {
for _, e := range events {
if e.ID == "evt-003" {
found = true
- assert.Equal(t, 1, len(e.Content.ToolCalls))
+ assert.Len(t, e.Content.ToolCalls, 1)
assert.Equal(t, "search", e.Content.ToolCalls[0].Name)
}
}
@@ -249,7 +249,7 @@ func TestMySQLService_List(t *testing.T) {
// 创建多个 Sessions
userID := "user-list-test"
- for i := 0; i < 5; i++ {
+ for i := range 5 {
_, err := service.Create(ctx, &session.CreateRequest{
AppName: "test-app",
UserID: userID,
@@ -261,7 +261,7 @@ func TestMySQLService_List(t *testing.T) {
t.Run("list all for user", func(t *testing.T) {
sessions, err := service.List(ctx, userID, nil)
require.NoError(t, err)
- assert.Equal(t, 5, len(sessions))
+ assert.Len(t, sessions, 5)
})
t.Run("list with limit", func(t *testing.T) {
@@ -269,7 +269,7 @@ func TestMySQLService_List(t *testing.T) {
Limit: 3,
})
require.NoError(t, err)
- assert.Equal(t, 3, len(sessions))
+ assert.Len(t, sessions, 3)
})
t.Run("list with offset", func(t *testing.T) {
@@ -278,7 +278,7 @@ func TestMySQLService_List(t *testing.T) {
Offset: 2,
})
require.NoError(t, err)
- assert.Equal(t, 2, len(sessions))
+ assert.Len(t, sessions, 2)
})
}
@@ -336,7 +336,7 @@ func TestMySQLService_GetEvents(t *testing.T) {
require.NoError(t, err)
// 创建多个事件
- for i := 0; i < 10; i++ {
+ for i := range 10 {
event := &session.Event{
ID: fmt.Sprintf("evt-%03d", i),
Timestamp: time.Now().Add(time.Duration(i) * time.Millisecond),
@@ -357,7 +357,7 @@ func TestMySQLService_GetEvents(t *testing.T) {
t.Run("get all events", func(t *testing.T) {
events, err := service.GetEvents(ctx, sess.ID, nil)
require.NoError(t, err)
- assert.Equal(t, 10, len(events))
+ assert.Len(t, events, 10)
})
t.Run("get events with limit", func(t *testing.T) {
@@ -365,7 +365,7 @@ func TestMySQLService_GetEvents(t *testing.T) {
Limit: 5,
})
require.NoError(t, err)
- assert.Equal(t, 5, len(events))
+ assert.Len(t, events, 5)
})
t.Run("filter by invocation_id", func(t *testing.T) {
@@ -401,9 +401,9 @@ func TestMySQLService_Concurrency(t *testing.T) {
errCh := make(chan error, numGoroutines*eventsPerGoroutine)
doneCh := make(chan struct{})
- for i := 0; i < numGoroutines; i++ {
+ for i := range numGoroutines {
go func(goroutineID int) {
- for j := 0; j < eventsPerGoroutine; j++ {
+ for j := range eventsPerGoroutine {
event := &session.Event{
ID: fmt.Sprintf("evt-g%d-e%d", goroutineID, j),
Timestamp: time.Now(),
@@ -426,7 +426,7 @@ func TestMySQLService_Concurrency(t *testing.T) {
}
// 等待所有 goroutine 完成
- for i := 0; i < numGoroutines; i++ {
+ for range numGoroutines {
<-doneCh
}
close(errCh)
@@ -436,12 +436,12 @@ func TestMySQLService_Concurrency(t *testing.T) {
for err := range errCh {
errors = append(errors, err)
}
- assert.Equal(t, 0, len(errors), "No errors should occur during concurrent operations")
+ assert.Empty(t, errors, "No errors should occur during concurrent operations")
// 验证所有事件都已插入
events, err := service.GetEvents(ctx, sess.ID, nil)
require.NoError(t, err)
- assert.Equal(t, numGoroutines*eventsPerGoroutine, len(events))
+ assert.Len(t, events, numGoroutines*eventsPerGoroutine)
}
// TestMySQLService_JSONColumns 测试 MySQL JSON 列功能
@@ -488,7 +488,7 @@ func TestMySQLService_JSONColumns(t *testing.T) {
// 验证 JSON 数据正确存储和检索
events, err := service.GetEvents(ctx, sess.ID, nil)
require.NoError(t, err)
- assert.Equal(t, 1, len(events))
+ assert.Len(t, events, 1)
metadata := events[0].Metadata
assert.Equal(t, "测试中文", metadata["chinese"])
diff --git a/pkg/session/postgres/service.go b/pkg/session/postgres/service.go
index b22b2ed..c539959 100644
--- a/pkg/session/postgres/service.go
+++ b/pkg/session/postgres/service.go
@@ -3,6 +3,7 @@ package postgres
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"strings"
"time"
@@ -61,7 +62,7 @@ func NewService(cfg *Config) (*Service, error) {
}
if cfg.DSN == "" {
- return nil, fmt.Errorf("DSN is required")
+ return nil, errors.New("DSN is required")
}
// 打开数据库连接
@@ -150,7 +151,7 @@ func (s *Service) Get(ctx context.Context, sessionID string) (*session.SessionDa
if err := s.db.WithContext(ctx).
Where("id = ?", sessionID).
First(&model).Error; err != nil {
- if err == gorm.ErrRecordNotFound {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, session.ErrSessionNotFound
}
return nil, fmt.Errorf("get session: %w", err)
diff --git a/pkg/session/postgres/service_test.go b/pkg/session/postgres/service_test.go
index dfdac5a..0c63b67 100644
--- a/pkg/session/postgres/service_test.go
+++ b/pkg/session/postgres/service_test.go
@@ -166,7 +166,7 @@ func TestPostgresService_AppendEvent(t *testing.T) {
// 验证事件已存储
events, err := service.GetEvents(ctx, sess.ID, nil)
require.NoError(t, err)
- assert.Equal(t, 1, len(events))
+ assert.Len(t, events, 1)
assert.Equal(t, event.ID, events[0].ID)
})
@@ -210,7 +210,7 @@ func TestPostgresService_List(t *testing.T) {
// 创建多个 Sessions
userID := "user-list-test"
- for i := 0; i < 5; i++ {
+ for i := range 5 {
_, err := service.Create(ctx, &session.CreateRequest{
AppName: "test-app",
UserID: userID,
@@ -222,7 +222,7 @@ func TestPostgresService_List(t *testing.T) {
t.Run("list all for user", func(t *testing.T) {
sessions, err := service.List(ctx, userID, nil)
require.NoError(t, err)
- assert.Equal(t, 5, len(sessions))
+ assert.Len(t, sessions, 5)
})
t.Run("list with limit", func(t *testing.T) {
@@ -230,7 +230,7 @@ func TestPostgresService_List(t *testing.T) {
Limit: 3,
})
require.NoError(t, err)
- assert.Equal(t, 3, len(sessions))
+ assert.Len(t, sessions, 3)
})
}
@@ -278,9 +278,9 @@ func TestPostgresService_Concurrency(t *testing.T) {
errCh := make(chan error, numGoroutines*eventsPerGoroutine)
doneCh := make(chan struct{})
- for i := 0; i < numGoroutines; i++ {
+ for i := range numGoroutines {
go func(goroutineID int) {
- for j := 0; j < eventsPerGoroutine; j++ {
+ for j := range eventsPerGoroutine {
event := &session.Event{
ID: fmt.Sprintf("evt-g%d-e%d", goroutineID, j),
Timestamp: time.Now(),
@@ -303,7 +303,7 @@ func TestPostgresService_Concurrency(t *testing.T) {
}
// 等待所有 goroutine 完成
- for i := 0; i < numGoroutines; i++ {
+ for range numGoroutines {
<-doneCh
}
close(errCh)
@@ -313,10 +313,10 @@ func TestPostgresService_Concurrency(t *testing.T) {
for err := range errCh {
errors = append(errors, err)
}
- assert.Equal(t, 0, len(errors), "No errors should occur during concurrent operations")
+ assert.Empty(t, errors, "No errors should occur during concurrent operations")
// 验证所有事件都已插入
events, err := service.GetEvents(ctx, sess.ID, nil)
require.NoError(t, err)
- assert.Equal(t, numGoroutines*eventsPerGoroutine, len(events))
+ assert.Len(t, events, numGoroutines*eventsPerGoroutine)
}
diff --git a/pkg/session/search.go b/pkg/session/search.go
index 9e816f4..03a9f0a 100644
--- a/pkg/session/search.go
+++ b/pkg/session/search.go
@@ -2,6 +2,7 @@ package session
import (
"context"
+ "errors"
"fmt"
"strings"
"time"
@@ -53,7 +54,7 @@ type SearchResult struct {
// SearchHistory 搜索历史消息
func (s *Searcher) SearchHistory(ctx context.Context, opts SearchOptions) ([]SearchResult, error) {
if opts.Query == "" {
- return nil, fmt.Errorf("search query cannot be empty")
+ return nil, errors.New("search query cannot be empty")
}
if opts.Limit <= 0 {
@@ -142,7 +143,7 @@ func (s *Searcher) SearchHistory(ctx context.Context, opts SearchOptions) ([]Sea
// SearchAcrossSessions 跨 Session 搜索
func (s *Searcher) SearchAcrossSessions(ctx context.Context, agentIDs []string, query string, limit int) ([]SearchResult, error) {
if query == "" {
- return nil, fmt.Errorf("search query cannot be empty")
+ return nil, errors.New("search query cannot be empty")
}
if limit <= 0 {
@@ -262,8 +263,8 @@ func generateSnippet(content, query string, maxLen int) string {
func sortByRelevance(results []SearchResult) {
// 简单的冒泡排序(对于小数据集足够)
n := len(results)
- for i := 0; i < n-1; i++ {
- for j := 0; j < n-i-1; j++ {
+ for i := range n - 1 {
+ for j := range n - i - 1 {
if results[j].Relevance < results[j+1].Relevance {
results[j], results[j+1] = results[j+1], results[j]
}
diff --git a/pkg/session/sqlite/store.go b/pkg/session/sqlite/store.go
index 6bf2145..ef4c712 100644
--- a/pkg/session/sqlite/store.go
+++ b/pkg/session/sqlite/store.go
@@ -7,8 +7,10 @@ import (
"context"
"database/sql"
"encoding/json"
+ "errors"
"fmt"
"iter"
+ "maps"
"sync"
"time"
@@ -180,7 +182,7 @@ func (s *Service) Update(ctx context.Context, req *session.UpdateRequest) error
req.SessionID,
).Scan(&existingJSON)
- if err == sql.ErrNoRows {
+ if errors.Is(err, sql.ErrNoRows) {
return session.ErrSessionNotFound
}
if err != nil {
@@ -195,9 +197,7 @@ func (s *Service) Update(ctx context.Context, req *session.UpdateRequest) error
}
}
- for k, v := range req.Metadata {
- existing[k] = v
- }
+ maps.Copy(existing, req.Metadata)
newJSON, err := json.Marshal(existing)
if err != nil {
@@ -279,7 +279,7 @@ func (s *Service) AppendEvent(ctx context.Context, sessionID string, event *sess
// Check session exists
var exists bool
err := s.db.QueryRowContext(ctx, `SELECT 1 FROM sessions WHERE id = ?`, sessionID).Scan(&exists)
- if err == sql.ErrNoRows {
+ if errors.Is(err, sql.ErrNoRows) {
return session.ErrSessionNotFound
}
if err != nil {
diff --git a/pkg/session/sqlite/store_test.go b/pkg/session/sqlite/store_test.go
index 2b74c66..46f67e3 100644
--- a/pkg/session/sqlite/store_test.go
+++ b/pkg/session/sqlite/store_test.go
@@ -2,6 +2,7 @@ package sqlite
import (
"context"
+ "errors"
"os"
"path/filepath"
"testing"
@@ -80,7 +81,7 @@ func TestService(t *testing.T) {
UserID: "user-1",
SessionID: "non-existent",
})
- if err != session.ErrSessionNotFound {
+ if !errors.Is(err, session.ErrSessionNotFound) {
t.Errorf("Expected ErrSessionNotFound, got %v", err)
}
})
@@ -124,7 +125,7 @@ func TestService(t *testing.T) {
// Test List
t.Run("List", func(t *testing.T) {
// Create multiple sessions
- for i := 0; i < 3; i++ {
+ for range 3 {
_, _ = svc.Create(ctx, &session.CreateRequest{
AppName: "list-app",
UserID: "list-user",
@@ -163,7 +164,7 @@ func TestService(t *testing.T) {
UserID: "user-1",
SessionID: created.ID(),
})
- if err != session.ErrSessionNotFound {
+ if !errors.Is(err, session.ErrSessionNotFound) {
t.Error("Session should be deleted")
}
})
@@ -294,7 +295,7 @@ func TestState(t *testing.T) {
// Test Get not found
t.Run("GetNotFound", func(t *testing.T) {
_, err := state.Get("non-existent")
- if err != session.ErrStateKeyNotExist {
+ if !errors.Is(err, session.ErrStateKeyNotExist) {
t.Errorf("Expected ErrStateKeyNotExist, got %v", err)
}
})
diff --git a/pkg/session/summary.go b/pkg/session/summary.go
index 4f032b2..011a3bb 100644
--- a/pkg/session/summary.go
+++ b/pkg/session/summary.go
@@ -2,6 +2,7 @@ package session
import (
"context"
+ "errors"
"fmt"
"strings"
"time"
@@ -118,7 +119,7 @@ func (s *Summarizer) SummarizeSession(ctx context.Context, messages []types.Mess
// SummarizeIncremental 增量摘要(基于之前的摘要)
func (s *Summarizer) SummarizeIncremental(ctx context.Context, previousSummary string, newMessages []types.Message) (*SessionSummary, error) {
if len(newMessages) == 0 {
- return nil, fmt.Errorf("no new messages to summarize")
+ return nil, errors.New("no new messages to summarize")
}
prompt := s.buildIncrementalSummaryPrompt(previousSummary, newMessages)
diff --git a/pkg/skills/injector.go b/pkg/skills/injector.go
index 8233ee0..fd719b0 100644
--- a/pkg/skills/injector.go
+++ b/pkg/skills/injector.go
@@ -124,7 +124,7 @@ func (i *Injector) InjectToUserMessage(userMessage string, skills []*SkillDefini
if baseDir != "" {
skillFileHint = fmt.Sprintf("%s/%s/SKILL.md", baseDir, path)
} else {
- skillFileHint = fmt.Sprintf("%s/SKILL.md", path)
+ skillFileHint = path + "/SKILL.md"
}
}
@@ -183,7 +183,7 @@ func (i *Injector) injectToSystemPrompt(basePrompt string, skills []*SkillDefini
if baseDir != "" {
skillFileHint = fmt.Sprintf("%s/%s/SKILL.md", baseDir, path)
} else {
- skillFileHint = fmt.Sprintf("%s/SKILL.md", path)
+ skillFileHint = path + "/SKILL.md"
}
}
diff --git a/pkg/skills/loader.go b/pkg/skills/loader.go
index c8758b9..3befaed 100644
--- a/pkg/skills/loader.go
+++ b/pkg/skills/loader.go
@@ -2,6 +2,7 @@ package skills
import (
"context"
+ "errors"
"fmt"
"path/filepath"
"regexp"
@@ -50,7 +51,7 @@ func (sl *SkillLoader) parse(name, content string) (*SkillDefinition, error) {
// 分割 YAML frontmatter 和 Markdown 内容
parts := strings.Split(content, "---")
if len(parts) < 3 {
- return nil, fmt.Errorf("invalid skill format: missing YAML frontmatter")
+ return nil, errors.New("invalid skill format: missing YAML frontmatter")
}
skill := &SkillDefinition{
@@ -119,7 +120,7 @@ func (sl *SkillLoader) parseYAML(yamlContent string, skill *SkillDefinition) err
// 验证名称和描述
if skill.Name == "" {
- return fmt.Errorf("skill name is required")
+ return errors.New("skill name is required")
}
if !skillNamePattern.MatchString(skill.Name) {
return fmt.Errorf("invalid skill name %q: must be 1-64 characters of lowercase letters, numbers, and hyphens", skill.Name)
diff --git a/pkg/skills/manager.go b/pkg/skills/manager.go
index 089ff52..cd154c9 100644
--- a/pkg/skills/manager.go
+++ b/pkg/skills/manager.go
@@ -4,6 +4,7 @@ import (
"archive/zip"
"bytes"
"context"
+ "errors"
"fmt"
"io"
"os"
@@ -96,7 +97,7 @@ func (m *Manager) List(ctx context.Context) ([]Info, error) {
// zip 内部应包含单个根目录或直接包含 SKILL.md;会被展开到 baseDir/skillID 下。
func (m *Manager) InstallFromZip(ctx context.Context, skillID string, r io.ReaderAt, size int64) error {
if strings.TrimSpace(skillID) == "" {
- return fmt.Errorf("skill id must not be empty")
+ return errors.New("skill id must not be empty")
}
zr, err := zip.NewReader(r, size)
@@ -147,7 +148,7 @@ func (m *Manager) InstallFromZip(ctx context.Context, skillID string, r io.Reade
// srcDir 应包含 SKILL.md。
func (m *Manager) InstallFromDir(ctx context.Context, skillID string, srcDir string) error {
if strings.TrimSpace(skillID) == "" {
- return fmt.Errorf("skill id must not be empty")
+ return errors.New("skill id must not be empty")
}
srcDir = filepath.Clean(srcDir)
@@ -191,7 +192,7 @@ func (m *Manager) InstallFromDir(ctx context.Context, skillID string, srcDir str
// 同时假设 baseDir 对应的是本地可写路径。
func (m *Manager) Uninstall(ctx context.Context, skillID string) error {
if strings.TrimSpace(skillID) == "" {
- return fmt.Errorf("skill id must not be empty")
+ return errors.New("skill id must not be empty")
}
// 删除主目录 skills/skillID
@@ -238,10 +239,10 @@ func (m *Manager) InstallFromZipBytes(ctx context.Context, skillID string, data
// files 的 key 为相对路径(相对于 skillID 根目录),value 为文件内容。
func (m *Manager) InstallFromFiles(ctx context.Context, skillID string, files map[string]string) error {
if strings.TrimSpace(skillID) == "" {
- return fmt.Errorf("skill id must not be empty")
+ return errors.New("skill id must not be empty")
}
if len(files) == 0 {
- return fmt.Errorf("files must not be empty")
+ return errors.New("files must not be empty")
}
for rel, content := range files {
@@ -260,7 +261,7 @@ func (m *Manager) InstallFromFiles(ctx context.Context, skillID string, files ma
// ListVersions 列出指定 Skill ID 的所有版本(包括无版本的主版本)。
func (m *Manager) ListVersions(ctx context.Context, skillID string) ([]Info, error) {
if strings.TrimSpace(skillID) == "" {
- return nil, fmt.Errorf("skill id must not be empty")
+ return nil, errors.New("skill id must not be empty")
}
all, err := m.List(ctx)
@@ -284,7 +285,7 @@ func (m *Manager) ListVersions(ctx context.Context, skillID string) ([]Info, err
// 如果 version 为空, 等价于 InstallFromFiles(skillID, files)。
func (m *Manager) InstallVersionFromFiles(ctx context.Context, skillID, version string, files map[string]string) error {
if strings.TrimSpace(skillID) == "" {
- return fmt.Errorf("skill id must not be empty")
+ return errors.New("skill id must not be empty")
}
id := skillID
if strings.TrimSpace(version) != "" {
@@ -297,7 +298,7 @@ func (m *Manager) InstallVersionFromFiles(ctx context.Context, skillID, version
// 如果 version 为空, 等价于 Uninstall(skillID)。
func (m *Manager) DeleteVersion(ctx context.Context, skillID, version string) error {
if strings.TrimSpace(skillID) == "" {
- return fmt.Errorf("skill id must not be empty")
+ return errors.New("skill id must not be empty")
}
if strings.TrimSpace(version) == "" {
return m.Uninstall(ctx, skillID)
diff --git a/pkg/skills/runtime.go b/pkg/skills/runtime.go
index d18bba5..c481374 100644
--- a/pkg/skills/runtime.go
+++ b/pkg/skills/runtime.go
@@ -2,6 +2,7 @@ package skills
import (
"context"
+ "errors"
"fmt"
"strings"
"time"
@@ -38,7 +39,7 @@ func NewRuntime(loader *SkillLoader, sb sandbox.Sandbox) *Runtime {
// 具体参数到命令行的映射由脚本自身负责,Runtime 不做 Skill 特定解析。
func (r *Runtime) Execute(ctx context.Context, skillName string, params map[string]string) (*ExecutionResult, error) {
if r.loader == nil || r.sandbox == nil {
- return nil, fmt.Errorf("skills runtime not properly initialized")
+ return nil, errors.New("skills runtime not properly initialized")
}
// 从 loader 加载 Skill 定义
@@ -116,22 +117,22 @@ func buildCommand(workDir string, skill *SkillDefinition) string {
if entry == "" {
return ""
}
- return fmt.Sprintf("python %s", entry)
+ return "python " + entry
case "node", "nodejs":
if entry == "" {
return ""
}
- return fmt.Sprintf("node %s", entry)
+ return "node " + entry
case "bash":
if entry == "" {
return ""
}
- return fmt.Sprintf("bash %s", entry)
+ return "bash " + entry
case "sh":
if entry == "" {
return ""
}
- return fmt.Sprintf("sh %s", entry)
+ return "sh " + entry
default:
// 未知 runtime:如果 entry 不是空,就直接当作命令执行
if entry != "" {
diff --git a/pkg/store/factory.go b/pkg/store/factory.go
index f50c1ac..5e69fe3 100644
--- a/pkg/store/factory.go
+++ b/pkg/store/factory.go
@@ -1,6 +1,7 @@
package store
import (
+ "errors"
"fmt"
"time"
)
@@ -48,7 +49,7 @@ func NewStore(config Config) (Store, error) {
case StoreTypeRedis:
if config.RedisAddr == "" {
- return nil, fmt.Errorf("redis_addr is required for redis store")
+ return nil, errors.New("redis_addr is required for redis store")
}
redisConfig := RedisConfig{
@@ -63,7 +64,7 @@ func NewStore(config Config) (Store, error) {
case StoreTypeMySQL:
if config.MySQLDSN == "" {
- return nil, fmt.Errorf("mysql_dsn is required for mysql store")
+ return nil, errors.New("mysql_dsn is required for mysql store")
}
mysqlConfig := MySQLConfig{
diff --git a/pkg/store/json.go b/pkg/store/json.go
index 978aee9..aac3d7b 100644
--- a/pkg/store/json.go
+++ b/pkg/store/json.go
@@ -166,7 +166,6 @@ func (js *JSONStore) TrimMessages(ctx context.Context, agentID string, maxMessag
return js.saveJSON(path, trimmedMessages)
}
-
// SaveToolCallRecords 保存工具调用记录
func (js *JSONStore) SaveToolCallRecords(ctx context.Context, agentID string, records []types.ToolCallRecord) error {
js.mu.Lock()
diff --git a/pkg/store/mysql.go b/pkg/store/mysql.go
index 6ca5866..e321987 100644
--- a/pkg/store/mysql.go
+++ b/pkg/store/mysql.go
@@ -3,6 +3,7 @@ package store
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"time"
@@ -50,7 +51,6 @@ type AgentSnapshot struct {
CreatedAt time.Time `gorm:"autoCreateTime"`
}
-
type AgentInfo struct {
ID uint `gorm:"primaryKey"`
AgentID string `gorm:"uniqueIndex;size:255"`
@@ -126,7 +126,6 @@ func NewMySQLStore(config MySQLConfig) (*MySQLStore, error) {
return &MySQLStore{db: db}, nil
}
-
// SaveMessages 保存消息列表
func (s *MySQLStore) SaveMessages(ctx context.Context, agentID string, messages []types.Message) error {
data, err := json.Marshal(messages)
@@ -143,7 +142,7 @@ func (s *MySQLStore) SaveMessages(ctx context.Context, agentID string, messages
func (s *MySQLStore) LoadMessages(ctx context.Context, agentID string) ([]types.Message, error) {
var record AgentMessage
if err := s.db.WithContext(ctx).Where("agent_id = ?", agentID).First(&record).Error; err != nil {
- if err == gorm.ErrRecordNotFound {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
return []types.Message{}, nil
}
return nil, err
@@ -191,7 +190,7 @@ func (s *MySQLStore) SaveToolCallRecords(ctx context.Context, agentID string, re
func (s *MySQLStore) LoadToolCallRecords(ctx context.Context, agentID string) ([]types.ToolCallRecord, error) {
var record AgentToolRecord
if err := s.db.WithContext(ctx).Where("agent_id = ?", agentID).First(&record).Error; err != nil {
- if err == gorm.ErrRecordNotFound {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
return []types.ToolCallRecord{}, nil
}
return nil, err
@@ -204,7 +203,6 @@ func (s *MySQLStore) LoadToolCallRecords(ctx context.Context, agentID string) ([
return records, nil
}
-
// SaveSnapshot 保存快照
func (s *MySQLStore) SaveSnapshot(ctx context.Context, agentID string, snapshot types.Snapshot) error {
data, err := json.Marshal(snapshot)
@@ -220,7 +218,7 @@ func (s *MySQLStore) SaveSnapshot(ctx context.Context, agentID string, snapshot
func (s *MySQLStore) LoadSnapshot(ctx context.Context, agentID string, snapshotID string) (*types.Snapshot, error) {
var record AgentSnapshot
if err := s.db.WithContext(ctx).Where("agent_id = ? AND snapshot_id = ?", agentID, snapshotID).First(&record).Error; err != nil {
- if err == gorm.ErrRecordNotFound {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
@@ -267,7 +265,7 @@ func (s *MySQLStore) SaveInfo(ctx context.Context, agentID string, info types.Ag
func (s *MySQLStore) LoadInfo(ctx context.Context, agentID string) (*types.AgentInfo, error) {
var record AgentInfo
if err := s.db.WithContext(ctx).Where("agent_id = ?", agentID).First(&record).Error; err != nil {
- if err == gorm.ErrRecordNotFound {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
@@ -296,7 +294,7 @@ func (s *MySQLStore) SaveTodos(ctx context.Context, agentID string, todos any) e
func (s *MySQLStore) LoadTodos(ctx context.Context, agentID string) (any, error) {
var record AgentTodo
if err := s.db.WithContext(ctx).Where("agent_id = ?", agentID).First(&record).Error; err != nil {
- if err == gorm.ErrRecordNotFound {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
@@ -309,7 +307,6 @@ func (s *MySQLStore) LoadTodos(ctx context.Context, agentID string) (any, error)
return todos, nil
}
-
// DeleteAgent 删除Agent所有数据
func (s *MySQLStore) DeleteAgent(ctx context.Context, agentID string) error {
return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
@@ -352,7 +349,7 @@ func (s *MySQLStore) ListAgents(ctx context.Context) ([]string, error) {
func (s *MySQLStore) Get(ctx context.Context, collection, key string, dest any) error {
var item CollectionItem
if err := s.db.WithContext(ctx).Where("collection = ? AND `key` = ?", collection, key).First(&item).Error; err != nil {
- if err == gorm.ErrRecordNotFound {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrNotFound
}
return err
diff --git a/pkg/store/redis.go b/pkg/store/redis.go
index 83bf5e7..b8a6760 100644
--- a/pkg/store/redis.go
+++ b/pkg/store/redis.go
@@ -3,6 +3,7 @@ package store
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"time"
@@ -30,7 +31,7 @@ type RedisConfig struct {
// NewRedisStore 创建 Redis Store
func NewRedisStore(config RedisConfig) (*RedisStore, error) {
if config.Addr == "" {
- return nil, fmt.Errorf("redis addr is required")
+ return nil, errors.New("redis addr is required")
}
client := redis.NewClient(&redis.Options{
@@ -91,7 +92,7 @@ func (rs *RedisStore) LoadMessages(ctx context.Context, agentID string) ([]types
key := rs.prefix + "messages:" + agentID
data, err := rs.client.Get(ctx, key).Bytes()
- if err == redis.Nil {
+ if errors.Is(err, redis.Nil) {
return []types.Message{}, nil // 未找到返回空列表
}
if err != nil {
@@ -118,7 +119,7 @@ func (rs *RedisStore) TrimMessages(ctx context.Context, agentID string, maxMessa
return rs.client.Watch(ctx, func(tx *redis.Tx) error {
// 读取当前消息
data, err := tx.Get(ctx, key).Bytes()
- if err == redis.Nil {
+ if errors.Is(err, redis.Nil) {
return nil // 不存在,无需修剪
}
if err != nil {
@@ -168,7 +169,7 @@ func (rs *RedisStore) LoadToolCallRecords(ctx context.Context, agentID string) (
key := rs.prefix + "tools:" + agentID
data, err := rs.client.Get(ctx, key).Bytes()
- if err == redis.Nil {
+ if errors.Is(err, redis.Nil) {
return []types.ToolCallRecord{}, nil
}
if err != nil {
@@ -200,7 +201,7 @@ func (rs *RedisStore) LoadSnapshot(ctx context.Context, agentID string, snapshot
key := rs.prefix + "snapshot:" + agentID + ":" + snapshotID
data, err := rs.client.Get(ctx, key).Bytes()
- if err == redis.Nil {
+ if errors.Is(err, redis.Nil) {
return nil, ErrNotFound
}
if err != nil {
@@ -257,7 +258,7 @@ func (rs *RedisStore) LoadInfo(ctx context.Context, agentID string) (*types.Agen
key := rs.prefix + "info:" + agentID
data, err := rs.client.Get(ctx, key).Bytes()
- if err == redis.Nil {
+ if errors.Is(err, redis.Nil) {
return nil, ErrNotFound
}
if err != nil {
@@ -289,7 +290,7 @@ func (rs *RedisStore) LoadTodos(ctx context.Context, agentID string) (any, error
key := rs.prefix + "todos:" + agentID
data, err := rs.client.Get(ctx, key).Bytes()
- if err == redis.Nil {
+ if errors.Is(err, redis.Nil) {
return nil, nil
}
if err != nil {
@@ -348,7 +349,7 @@ func (rs *RedisStore) Get(ctx context.Context, collection, key string, dest any)
redisKey := rs.prefix + collection + ":" + key
data, err := rs.client.Get(ctx, redisKey).Bytes()
- if err == redis.Nil {
+ if errors.Is(err, redis.Nil) {
return ErrNotFound
}
if err != nil {
diff --git a/pkg/stream/stream_test.go b/pkg/stream/stream_test.go
index f5ee1fc..ab6ad4e 100644
--- a/pkg/stream/stream_test.go
+++ b/pkg/stream/stream_test.go
@@ -123,7 +123,7 @@ func TestCopy_Concurrent(t *testing.T) {
// 开始写入
go func() {
defer writer.Close()
- for i := 0; i < 100; i++ {
+ for i := range 100 {
writer.Send(i, nil)
}
}()
@@ -453,8 +453,7 @@ func BenchmarkCopy(b *testing.B) {
data[i] = i
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
reader := FromSlice(data)
copies := reader.Copy(3)
for _, c := range copies {
@@ -469,8 +468,7 @@ func BenchmarkTransform(b *testing.B) {
data[i] = i
}
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
reader := FromSlice(data)
transformed := Transform(reader, func(v int) (int, error) {
return v * 2, nil
diff --git a/pkg/structured/schema.go b/pkg/structured/schema.go
index 1f37315..7a89692 100644
--- a/pkg/structured/schema.go
+++ b/pkg/structured/schema.go
@@ -2,7 +2,9 @@ package structured
import (
"encoding/json"
+ "errors"
"fmt"
+ "maps"
"reflect"
"strings"
)
@@ -196,25 +198,25 @@ func (g *SchemaGenerator) typeToSchema(typ reflect.Type) (map[string]any, error)
func (g *SchemaGenerator) Validate(schema map[string]any) error {
// 检查必需字段
if _, ok := schema["type"]; !ok {
- return fmt.Errorf("schema must have 'type' field")
+ return errors.New("schema must have 'type' field")
}
schemaType, ok := schema["type"].(string)
if !ok {
- return fmt.Errorf("'type' must be a string")
+ return errors.New("'type' must be a string")
}
switch schemaType {
case "object":
// 对象类型应有 properties
if _, ok := schema["properties"]; !ok {
- return fmt.Errorf("object type must have 'properties'")
+ return errors.New("object type must have 'properties'")
}
case "array":
// 数组类型应有 items
if _, ok := schema["items"]; !ok {
- return fmt.Errorf("array type must have 'items'")
+ return errors.New("array type must have 'items'")
}
}
@@ -224,7 +226,7 @@ func (g *SchemaGenerator) Validate(schema map[string]any) error {
// MergeSchemas 合并多个 Schema(用于复杂场景)
func (g *SchemaGenerator) MergeSchemas(schemas ...map[string]any) (map[string]any, error) {
if len(schemas) == 0 {
- return nil, fmt.Errorf("no schemas to merge")
+ return nil, errors.New("no schemas to merge")
}
result := make(map[string]any)
@@ -234,9 +236,7 @@ func (g *SchemaGenerator) MergeSchemas(schemas ...map[string]any) (map[string]an
for _, schema := range schemas {
// 合并 properties
if props, ok := schema["properties"].(map[string]any); ok {
- for k, v := range props {
- properties[k] = v
- }
+ maps.Copy(properties, props)
}
// 合并 required
@@ -256,17 +256,17 @@ func (g *SchemaGenerator) MergeSchemas(schemas ...map[string]any) (map[string]an
// JSONSchema JSON Schema 定义(结构化版本)
type JSONSchema struct {
- Type string `json:"type,omitempty"`
- Description string `json:"description,omitempty"`
- Properties map[string]*JSONSchema `json:"properties,omitempty"`
- Items *JSONSchema `json:"items,omitempty"`
- Required []string `json:"required,omitempty"`
- Enum []any `json:"enum,omitempty"`
- Minimum *float64 `json:"minimum,omitempty"`
- Maximum *float64 `json:"maximum,omitempty"`
- Pattern string `json:"pattern,omitempty"`
- Format string `json:"format,omitempty"`
- Default any `json:"default,omitempty"`
+ Type string `json:"type,omitempty"`
+ Description string `json:"description,omitempty"`
+ Properties map[string]*JSONSchema `json:"properties,omitempty"`
+ Items *JSONSchema `json:"items,omitempty"`
+ Required []string `json:"required,omitempty"`
+ Enum []any `json:"enum,omitempty"`
+ Minimum *float64 `json:"minimum,omitempty"`
+ Maximum *float64 `json:"maximum,omitempty"`
+ Pattern string `json:"pattern,omitempty"`
+ Format string `json:"format,omitempty"`
+ Default any `json:"default,omitempty"`
}
// ToJSON 将 JSONSchema 转换为 JSON 字符串
diff --git a/pkg/structured/typed.go b/pkg/structured/typed.go
index 674f706..57f9a2f 100644
--- a/pkg/structured/typed.go
+++ b/pkg/structured/typed.go
@@ -3,6 +3,7 @@ package structured
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"reflect"
)
@@ -29,13 +30,13 @@ func NewTypedParser(schema *JSONSchema) *TypedParser {
// ParseInto 解析并绑定到目标 struct
func (tp *TypedParser) ParseInto(ctx context.Context, text string, target any) error {
if target == nil {
- return fmt.Errorf("target cannot be nil")
+ return errors.New("target cannot be nil")
}
// 检查 target 是否为指针
rv := reflect.ValueOf(target)
if rv.Kind() != reflect.Ptr {
- return fmt.Errorf("target must be a pointer")
+ return errors.New("target must be a pointer")
}
// 提取 JSON
@@ -118,7 +119,7 @@ func ParseTyped(ctx context.Context, text string, spec TypedOutputSpec) (*TypedP
// 创建目标实例
if spec.StructType == nil {
- return nil, fmt.Errorf("struct type is required")
+ return nil, errors.New("struct type is required")
}
targetType := reflect.TypeOf(spec.StructType)
@@ -195,7 +196,7 @@ func GenerateSchema(structType any) (*JSONSchema, error) {
// 解析 json tag
fieldName := jsonTag
- for idx := 0; idx < len(jsonTag); idx++ {
+ for idx := range len(jsonTag) {
if jsonTag[idx] == ',' {
fieldName = jsonTag[:idx]
break
diff --git a/pkg/telemetry/genai/attributes.go b/pkg/telemetry/genai/attributes.go
index 43fca88..ddee5f3 100644
--- a/pkg/telemetry/genai/attributes.go
+++ b/pkg/telemetry/genai/attributes.go
@@ -2,6 +2,8 @@
// Based on: https://opentelemetry.io/docs/specs/semconv/gen-ai/
package genai
+import "maps"
+
// Operation names for GenAI spans
const (
// OpInvokeAgent represents an agent invocation operation
@@ -68,10 +70,10 @@ const (
AttrErrorType = "error.type"
// Performance attributes (Aster-specific extensions)
- AttrLatencyTTFT = "gen_ai.latency.ttft_ms" // Time to First Token
- AttrLatencyTPOT = "gen_ai.latency.tpot_ms" // Time Per Output Token
- AttrLatencyTotal = "gen_ai.latency.total_ms" // Total latency
- AttrIterationCount = "gen_ai.agent.iteration_count" // Agent loop iterations
+ AttrLatencyTTFT = "gen_ai.latency.ttft_ms" // Time to First Token
+ AttrLatencyTPOT = "gen_ai.latency.tpot_ms" // Time Per Output Token
+ AttrLatencyTotal = "gen_ai.latency.total_ms" // Total latency
+ AttrIterationCount = "gen_ai.agent.iteration_count" // Agent loop iterations
// Cost attributes (Aster-specific extensions)
AttrCostInput = "gen_ai.cost.input"
@@ -92,24 +94,24 @@ const (
// Error types
const (
- ErrorTypeTimeout = "timeout"
- ErrorTypeRateLimit = "rate_limit"
- ErrorTypeInvalidRequest = "invalid_request"
- ErrorTypeAuthentication = "authentication"
- ErrorTypePermission = "permission"
- ErrorTypeNotFound = "not_found"
- ErrorTypeServerError = "server_error"
- ErrorTypeContentFilter = "content_filter"
+ ErrorTypeTimeout = "timeout"
+ ErrorTypeRateLimit = "rate_limit"
+ ErrorTypeInvalidRequest = "invalid_request"
+ ErrorTypeAuthentication = "authentication"
+ ErrorTypePermission = "permission"
+ ErrorTypeNotFound = "not_found"
+ ErrorTypeServerError = "server_error"
+ ErrorTypeContentFilter = "content_filter"
ErrorTypeContextLengthExceeded = "context_length_exceeded"
)
// Finish reasons
const (
- FinishReasonStop = "stop"
- FinishReasonLength = "length"
- FinishReasonToolUse = "tool_use"
+ FinishReasonStop = "stop"
+ FinishReasonLength = "length"
+ FinishReasonToolUse = "tool_use"
FinishReasonContentFilter = "content_filter"
- FinishReasonError = "error"
+ FinishReasonError = "error"
)
// SpanKind constants for GenAI operations
@@ -224,9 +226,7 @@ func (b *AttributeBuilder) WithCost(input, output, total float64, currency strin
// Build returns the built attributes map
func (b *AttributeBuilder) Build() map[string]any {
result := make(map[string]any, len(b.attrs))
- for k, v := range b.attrs {
- result[k] = v
- }
+ maps.Copy(result, b.attrs)
return result
}
diff --git a/pkg/telemetry/integration_example.go b/pkg/telemetry/integration_example.go
index 69211d6..24a763f 100644
--- a/pkg/telemetry/integration_example.go
+++ b/pkg/telemetry/integration_example.go
@@ -2,6 +2,7 @@ package telemetry
import (
"context"
+ "errors"
"fmt"
"time"
)
@@ -105,7 +106,7 @@ func ExampleWithError() {
defer span.End()
// 模拟错误
- err := fmt.Errorf("model API timeout")
+ err := errors.New("model API timeout")
span.RecordError(err)
span.SetStatus(StatusCodeError, err.Error())
diff --git a/pkg/telemetry/metrics.go b/pkg/telemetry/metrics.go
index 9536a07..2b15691 100644
--- a/pkg/telemetry/metrics.go
+++ b/pkg/telemetry/metrics.go
@@ -1,6 +1,7 @@
package telemetry
import (
+ "strings"
"sync"
"time"
)
@@ -259,9 +260,11 @@ func makeKey(name string, labels map[string]string) string {
}
key := name
+ var keySb262 strings.Builder
for k, v := range labels {
- key += ":" + k + "=" + v
+ keySb262.WriteString(":" + k + "=" + v)
}
+ key += keySb262.String()
return key
}
diff --git a/pkg/telemetry/otel_adapter.go b/pkg/telemetry/otel_adapter.go
index 6fc7b4a..499df7d 100644
--- a/pkg/telemetry/otel_adapter.go
+++ b/pkg/telemetry/otel_adapter.go
@@ -2,6 +2,7 @@ package telemetry
import (
"context"
+ "errors"
"fmt"
"go.opentelemetry.io/otel"
@@ -192,7 +193,7 @@ func (t *OTelTracer) Inject(ctx context.Context, carrier any) error {
t.propagator.Inject(ctx, textMapCarrier)
return nil
}
- return fmt.Errorf("carrier is not a TextMapCarrier")
+ return errors.New("carrier is not a TextMapCarrier")
}
// Shutdown 关闭 tracer,刷新所有待处理的 spans
diff --git a/pkg/tools/bridge/http_server.go b/pkg/tools/bridge/http_server.go
index 2ed3c34..3804a44 100644
--- a/pkg/tools/bridge/http_server.go
+++ b/pkg/tools/bridge/http_server.go
@@ -3,6 +3,7 @@ package bridge
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"net/http"
"os"
@@ -258,7 +259,7 @@ func (s *HTTPBridgeServer) Start() error {
// StartAsync 异步启动服务器
func (s *HTTPBridgeServer) StartAsync() error {
go func() {
- if err := s.Start(); err != nil && err != http.ErrServerClosed {
+ if err := s.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) {
fmt.Printf("HTTP Bridge Server error: %v\n", err)
}
}()
diff --git a/pkg/tools/bridge/runtime.go b/pkg/tools/bridge/runtime.go
index 0ffe400..7acb558 100644
--- a/pkg/tools/bridge/runtime.go
+++ b/pkg/tools/bridge/runtime.go
@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
+ "errors"
"fmt"
"os"
"os/exec"
@@ -155,7 +156,7 @@ func (r *PythonRuntime) Execute(ctx context.Context, code string, input map[stri
if execCtx.Err() == context.DeadlineExceeded {
result.Error = "execution timeout"
result.ExitCode = -1
- } else if exitErr, ok := err.(*exec.ExitError); ok {
+ } else if exitErr := (&exec.ExitError{}); errors.As(err, &exitErr) {
result.ExitCode = exitErr.ExitCode()
result.Error = stderr.String()
} else {
@@ -422,7 +423,7 @@ func (r *NodeJSRuntime) Execute(ctx context.Context, code string, input map[stri
if execCtx.Err() == context.DeadlineExceeded {
result.Error = "execution timeout"
result.ExitCode = -1
- } else if exitErr, ok := err.(*exec.ExitError); ok {
+ } else if exitErr := (&exec.ExitError{}); errors.As(err, &exitErr) {
result.ExitCode = exitErr.ExitCode()
result.Error = stderr.String()
} else {
@@ -552,7 +553,7 @@ func (r *BashRuntime) Execute(ctx context.Context, code string, input map[string
if execCtx.Err() == context.DeadlineExceeded {
result.Error = "execution timeout"
result.ExitCode = -1
- } else if exitErr, ok := err.(*exec.ExitError); ok {
+ } else if exitErr := (&exec.ExitError{}); errors.As(err, &exitErr) {
result.ExitCode = exitErr.ExitCode()
result.Error = stderr.String()
} else {
diff --git a/pkg/tools/builtin/ask_user.go b/pkg/tools/builtin/ask_user.go
index 6624137..4f07a8b 100644
--- a/pkg/tools/builtin/ask_user.go
+++ b/pkg/tools/builtin/ask_user.go
@@ -2,6 +2,7 @@ package builtin
import (
"context"
+ "errors"
"fmt"
"sync"
"time"
@@ -243,7 +244,7 @@ func (t *AskUserQuestionTool) Execute(ctx context.Context, input map[string]any,
// 但我们不清理 pending request,让用户仍然可以响应
// 启动一个后台 goroutine 来等待用户响应并清理
// 重要:创建新的 timeout,因为原来的 timeout 可能已经过了一段时间
- askUserLog.Info(context.Background(), "context cancelled, but keeping request alive for user response", map[string]any{"request_id": requestID})
+ askUserLog.Info(context.Background(), "context canceled, but keeping request alive for user response", map[string]any{"request_id": requestID})
newTimeout := time.After(2 * time.Hour) // 创建新的 2 小时超时
go func() {
select {
@@ -271,7 +272,7 @@ func (t *AskUserQuestionTool) Execute(ctx context.Context, input map[string]any,
func (t *AskUserQuestionTool) parseQuestions(value any) ([]types.Question, error) {
questionsRaw, ok := value.([]any)
if !ok {
- return nil, fmt.Errorf("questions must be an array")
+ return nil, errors.New("questions must be an array")
}
questions := make([]types.Question, 0, len(questionsRaw))
@@ -364,7 +365,7 @@ func (t *AskUserQuestionTool) ReceiveAnswer(requestID string, answers map[string
case ch <- answers:
return nil
default:
- return fmt.Errorf("response channel is full or closed")
+ return errors.New("response channel is full or closed")
}
}
diff --git a/pkg/tools/builtin/bash.go b/pkg/tools/builtin/bash.go
index aeca338..1779446 100644
--- a/pkg/tools/builtin/bash.go
+++ b/pkg/tools/builtin/bash.go
@@ -2,6 +2,7 @@ package builtin
import (
"context"
+ "errors"
"fmt"
"regexp"
"strings"
@@ -122,7 +123,7 @@ func (t *BashTool) Execute(ctx context.Context, input map[string]any, tc *tools.
}
if command == "" {
- return NewClaudeErrorResponse(fmt.Errorf("command cannot be empty")), nil
+ return NewClaudeErrorResponse(errors.New("command cannot be empty")), nil
}
// Git 安全检查
@@ -160,7 +161,7 @@ func (t *BashTool) Execute(ctx context.Context, input map[string]any, tc *tools.
// 验证命令安全性
if err := t.validateCommand(command); err != nil {
return NewClaudeErrorResponse(
- fmt.Errorf("command validation failed: %v", err),
+ fmt.Errorf("command validation failed: %w", err),
"避免使用危险命令如 rm -rf /, sudo rm 等",
"如需执行敏感操作,请确认安全性",
), nil
diff --git a/pkg/tools/builtin/bash_test.go b/pkg/tools/builtin/bash_test.go
index ed0e408..ce86a4c 100644
--- a/pkg/tools/builtin/bash_test.go
+++ b/pkg/tools/builtin/bash_test.go
@@ -1,7 +1,7 @@
package builtin
import (
- "fmt"
+ "errors"
"os"
"strings"
"testing"
@@ -289,7 +289,7 @@ func TestBashTool_ConcurrentExecution(t *testing.T) {
}
result := ExecuteToolWithInput(t, tool, input)
if !result["ok"].(bool) {
- return fmt.Errorf("Tool execution failed")
+ return errors.New("Tool execution failed")
}
return nil
})
diff --git a/pkg/tools/builtin/bashoutput.go b/pkg/tools/builtin/bashoutput.go
index d14dcb8..4352e8a 100644
--- a/pkg/tools/builtin/bashoutput.go
+++ b/pkg/tools/builtin/bashoutput.go
@@ -2,8 +2,10 @@ package builtin
import (
"context"
+ "errors"
"fmt"
"os/exec"
+ "strconv"
"strings"
"time"
@@ -91,7 +93,7 @@ func (t *BashOutputTool) Execute(ctx context.Context, input map[string]any, tc *
clearCache := GetBoolParam(input, "clear_cache", false)
if bashID == "" {
- return NewClaudeErrorResponse(fmt.Errorf("bash_id cannot be empty")), nil
+ return NewClaudeErrorResponse(errors.New("bash_id cannot be empty")), nil
}
start := time.Now()
@@ -104,7 +106,7 @@ func (t *BashOutputTool) Execute(ctx context.Context, input map[string]any, tc *
if err != nil {
return map[string]any{
"ok": false,
- "error": fmt.Sprintf("background task not found: %s", bashID),
+ "error": "background task not found: " + bashID,
"recommendations": []string{
"确认bash_id是否正确",
"检查任务是否还在运行",
@@ -334,7 +336,7 @@ func (t *BashOutputTool) getResourceUsage(pid int) *ResourceUsage {
}
// 使用ps命令获取资源使用情况
- cmd := exec.Command("ps", "-p", fmt.Sprintf("%d", pid), "-o", "%cpu,rss,vsz", "--no-headers")
+ cmd := exec.Command("ps", "-p", strconv.Itoa(pid), "-o", "%cpu,rss,vsz", "--no-headers")
output, err := cmd.Output()
if err != nil {
return nil
diff --git a/pkg/tools/builtin/codeexecute.go b/pkg/tools/builtin/codeexecute.go
index 33443f8..f51ede5 100644
--- a/pkg/tools/builtin/codeexecute.go
+++ b/pkg/tools/builtin/codeexecute.go
@@ -2,6 +2,7 @@ package builtin
import (
"context"
+ "errors"
"fmt"
"sync"
"time"
@@ -137,12 +138,12 @@ func (t *CodeExecuteTool) Execute(ctx context.Context, input map[string]any, tc
// 解析参数
langStr, ok := input["language"].(string)
if !ok {
- return nil, fmt.Errorf("language must be a string")
+ return nil, errors.New("language must be a string")
}
code, ok := input["code"].(string)
if !ok || code == "" {
- return nil, fmt.Errorf("code must be a non-empty string")
+ return nil, errors.New("code must be a non-empty string")
}
// 转换语言类型
@@ -157,7 +158,7 @@ func (t *CodeExecuteTool) Execute(ctx context.Context, input map[string]any, tc
default:
return map[string]any{
"success": false,
- "error": fmt.Sprintf("unsupported language: %s", langStr),
+ "error": "unsupported language: " + langStr,
}, nil
}
diff --git a/pkg/tools/builtin/edit.go b/pkg/tools/builtin/edit.go
index d96c43b..b9ee90f 100644
--- a/pkg/tools/builtin/edit.go
+++ b/pkg/tools/builtin/edit.go
@@ -2,6 +2,7 @@ package builtin
import (
"context"
+ "errors"
"fmt"
"path/filepath"
"strings"
@@ -74,11 +75,11 @@ func (t *EditTool) Execute(ctx context.Context, input map[string]any, tc *tools.
backup := t.getBoolParam(input, "backup", true)
if filePath == "" {
- return NewClaudeErrorResponse(fmt.Errorf("file_path cannot be empty")), nil
+ return NewClaudeErrorResponse(errors.New("file_path cannot be empty")), nil
}
if oldString == "" {
- return NewClaudeErrorResponse(fmt.Errorf("old_string cannot be empty")), nil
+ return NewClaudeErrorResponse(errors.New("old_string cannot be empty")), nil
}
// 如果 old_string 和 new_string 相同,直接返回成功但没有修改
@@ -96,7 +97,7 @@ func (t *EditTool) Execute(ctx context.Context, input map[string]any, tc *tools.
// 验证文件路径安全性
if err := t.validatePath(filePath); err != nil {
return NewClaudeErrorResponse(
- fmt.Errorf("invalid file path: %v", err),
+ fmt.Errorf("invalid file path: %w", err),
"使用相对路径或允许的绝对路径",
"确保路径不包含 '..' 避免路径遍历攻击",
), nil
diff --git a/pkg/tools/builtin/exitplanmode.go b/pkg/tools/builtin/exitplanmode.go
index e1d5845..871f02d 100644
--- a/pkg/tools/builtin/exitplanmode.go
+++ b/pkg/tools/builtin/exitplanmode.go
@@ -1,12 +1,13 @@
package builtin
import (
-"context"
-"fmt"
-"strings"
-"time"
+ "context"
+ "errors"
+ "fmt"
+ "strings"
+ "time"
-"github.com/astercloud/aster/pkg/tools"
+ "github.com/astercloud/aster/pkg/tools"
)
// ExitPlanModeTool 规划模式退出工具
@@ -100,9 +101,9 @@ func (t *ExitPlanModeTool) Execute(ctx context.Context, input map[string]any, tc
if len(plans) == 0 {
return NewClaudeErrorResponse(
-fmt.Errorf("no plan content provided and no plan files found"),
-"Please provide the plan content directly using the 'plan' parameter",
-), nil
+ errors.New("no plan content provided and no plan files found"),
+ "Please provide the plan content directly using the 'plan' parameter",
+ ), nil
}
latestPlan := plans[len(plans)-1]
@@ -122,16 +123,16 @@ fmt.Errorf("no plan content provided and no plan files found"),
maxRetries := 3
retryDelay := 500 * time.Millisecond
- for i := 0; i < maxRetries; i++ {
+ for i := range maxRetries {
if !planManager.Exists(planFilePath) {
if i < maxRetries-1 {
time.Sleep(retryDelay)
continue
}
return NewClaudeErrorResponse(
-fmt.Errorf("plan file not found: %s", planFilePath),
-"Please provide the plan content directly using the 'plan' parameter",
-), nil
+ fmt.Errorf("plan file not found: %s", planFilePath),
+ "Please provide the plan content directly using the 'plan' parameter",
+ ), nil
}
var err error
@@ -150,9 +151,9 @@ fmt.Errorf("plan file not found: %s", planFilePath),
continue
}
return NewClaudeErrorResponse(
-fmt.Errorf("plan file is empty: %s", planFilePath),
-"Please provide the plan content directly using the 'plan' parameter",
-), nil
+ fmt.Errorf("plan file is empty: %s", planFilePath),
+ "Please provide the plan content directly using the 'plan' parameter",
+ ), nil
}
break
diff --git a/pkg/tools/builtin/exitplanmode_test.go b/pkg/tools/builtin/exitplanmode_test.go
index 7ca9f7c..ecb5fe0 100644
--- a/pkg/tools/builtin/exitplanmode_test.go
+++ b/pkg/tools/builtin/exitplanmode_test.go
@@ -1,6 +1,7 @@
package builtin
import (
+ "errors"
"fmt"
"os"
"path/filepath"
@@ -391,16 +392,16 @@ func TestExitPlanModeTool_ConcurrentAccess(t *testing.T) {
result := ExecuteToolWithInput(t, tool, input)
if !result["ok"].(bool) {
- return fmt.Errorf("ExitPlanMode operation failed")
+ return errors.New("ExitPlanMode operation failed")
}
// 验证基本响应
if _, exists := result["plan_id"]; !exists {
- return fmt.Errorf("Missing plan_id in result")
+ return errors.New("Missing plan_id in result")
}
if result["status"].(string) != "pending_approval" {
- return fmt.Errorf("Expected pending_approval status")
+ return errors.New("Expected pending_approval status")
}
return nil
diff --git a/pkg/tools/builtin/git_safety.go b/pkg/tools/builtin/git_safety.go
index e7dcc30..138b190 100644
--- a/pkg/tools/builtin/git_safety.go
+++ b/pkg/tools/builtin/git_safety.go
@@ -311,16 +311,20 @@ func (c *GitSafetyCheck) FormatCheckResult() string {
if len(c.Warnings) > 0 {
msg += "\nWarnings:\n"
+ var msgSb314 strings.Builder
for _, w := range c.Warnings {
- msg += fmt.Sprintf(" • %s\n", w)
+ msgSb314.WriteString(fmt.Sprintf(" • %s\n", w))
}
+ msg += msgSb314.String()
}
if len(c.Recommendations) > 0 {
msg += "\nRecommendations:\n"
+ var msgSb321 strings.Builder
for _, r := range c.Recommendations {
- msg += fmt.Sprintf(" • %s\n", r)
+ msgSb321.WriteString(fmt.Sprintf(" • %s\n", r))
}
+ msg += msgSb321.String()
}
msg += "\nThis command requires user approval before execution."
diff --git a/pkg/tools/builtin/glob.go b/pkg/tools/builtin/glob.go
index ac0a37c..0806844 100644
--- a/pkg/tools/builtin/glob.go
+++ b/pkg/tools/builtin/glob.go
@@ -2,6 +2,7 @@ package builtin
import (
"context"
+ "errors"
"fmt"
"path/filepath"
"strings"
@@ -88,13 +89,13 @@ func (t *GlobTool) Execute(ctx context.Context, input map[string]any, tc *tools.
recursive := t.getBoolParam(input, "recursive", true)
if pattern == "" {
- return NewClaudeErrorResponse(fmt.Errorf("pattern cannot be empty")), nil
+ return NewClaudeErrorResponse(errors.New("pattern cannot be empty")), nil
}
// 验证搜索路径
if err := t.validatePath(path); err != nil {
return NewClaudeErrorResponse(
- fmt.Errorf("invalid search path: %v", err),
+ fmt.Errorf("invalid search path: %w", err),
"使用相对路径或允许的绝对路径",
"确保路径不包含 '..' 避免路径遍历攻击",
), nil
diff --git a/pkg/tools/builtin/glob_test.go b/pkg/tools/builtin/glob_test.go
index 34e65cd..ebb5a30 100644
--- a/pkg/tools/builtin/glob_test.go
+++ b/pkg/tools/builtin/glob_test.go
@@ -1,7 +1,7 @@
package builtin
import (
- "fmt"
+ "errors"
"strings"
"testing"
)
@@ -427,16 +427,16 @@ func TestGlobTool_ConcurrentOperations(t *testing.T) {
result := ExecuteToolWithInput(t, tool, input)
if !result["ok"].(bool) {
- return fmt.Errorf("Glob operation failed")
+ return errors.New("Glob operation failed")
}
// 验证基本响应
if _, exists := result["matches"]; !exists {
- return fmt.Errorf("Missing matches in result")
+ return errors.New("Missing matches in result")
}
if _, exists := result["count"]; !exists {
- return fmt.Errorf("Missing count in result")
+ return errors.New("Missing count in result")
}
return nil
diff --git a/pkg/tools/builtin/grep.go b/pkg/tools/builtin/grep.go
index 9786c15..8109dc7 100644
--- a/pkg/tools/builtin/grep.go
+++ b/pkg/tools/builtin/grep.go
@@ -2,7 +2,10 @@ package builtin
import (
"context"
+ "errors"
"fmt"
+ "slices"
+ "strconv"
"strings"
"time"
@@ -114,18 +117,12 @@ func (t *GrepTool) Execute(ctx context.Context, input map[string]any, tc *tools.
multiline := t.getBoolParam(input, "multiline", false)
if pattern == "" {
- return NewClaudeErrorResponse(fmt.Errorf("pattern cannot be empty")), nil
+ return NewClaudeErrorResponse(errors.New("pattern cannot be empty")), nil
}
// 验证输出模式
validModes := []string{"content", "files_with_matches", "count"}
- modeValid := false
- for _, mode := range validModes {
- if outputMode == mode {
- modeValid = true
- break
- }
- }
+ modeValid := slices.Contains(validModes, outputMode)
if !modeValid {
return NewClaudeErrorResponse(
fmt.Errorf("invalid output_mode: %s", outputMode),
@@ -136,7 +133,7 @@ func (t *GrepTool) Execute(ctx context.Context, input map[string]any, tc *tools.
// 验证搜索路径
if err := t.validatePath(path); err != nil {
return NewClaudeErrorResponse(
- fmt.Errorf("invalid search path: %v", err),
+ fmt.Errorf("invalid search path: %w", err),
"使用相对路径或允许的绝对路径",
"确保路径不包含 '..' 避免路径遍历攻击",
), nil
@@ -264,7 +261,7 @@ func (t *GrepTool) getBoolParam(input map[string]any, key string, defaultValue b
func (t *GrepTool) validatePath(path string) error {
if strings.Contains(path, "..") {
- return fmt.Errorf("path traversal not allowed")
+ return errors.New("path traversal not allowed")
}
return nil
}
@@ -299,7 +296,7 @@ func (t *GrepTool) buildGrepCommand(pattern, path, glob, fileType, outputMode st
// 上下文行数
if contextLines > 0 {
- parts = append(parts, "-C", fmt.Sprintf("%d", contextLines))
+ parts = append(parts, "-C", strconv.Itoa(contextLines))
}
// 输出模式
@@ -322,7 +319,7 @@ func (t *GrepTool) buildGrepCommand(pattern, path, glob, fileType, outputMode st
// 结果限制
if maxResults > 0 && outputMode == "content" {
- parts = append(parts, "-m", fmt.Sprintf("%d", maxResults))
+ parts = append(parts, "-m", strconv.Itoa(maxResults))
}
// 搜索模式
@@ -438,12 +435,7 @@ func (t *GrepTool) parseCountOutput(lines []string, result *GrepResult) {
}
func (t *GrepTool) containsString(slice []string, item string) bool {
- for _, s := range slice {
- if s == item {
- return true
- }
- }
- return false
+ return slices.Contains(slice, item)
}
// 数据结构
diff --git a/pkg/tools/builtin/grep_test.go b/pkg/tools/builtin/grep_test.go
index 55305d7..3223c5b 100644
--- a/pkg/tools/builtin/grep_test.go
+++ b/pkg/tools/builtin/grep_test.go
@@ -1,7 +1,7 @@
package builtin
import (
- "fmt"
+ "errors"
"reflect"
"strings"
"testing"
@@ -407,16 +407,16 @@ func TestGrepTool_ConcurrentOperations(t *testing.T) {
result := ExecuteToolWithInput(t, tool, input)
if !result["ok"].(bool) {
- return fmt.Errorf("Grep operation failed")
+ return errors.New("Grep operation failed")
}
// 验证基本响应
if _, exists := result["matches"]; !exists {
- return fmt.Errorf("Missing matches in result")
+ return errors.New("Missing matches in result")
}
if _, exists := result["pattern"]; !exists {
- return fmt.Errorf("Missing pattern in result")
+ return errors.New("Missing pattern in result")
}
return nil
diff --git a/pkg/tools/builtin/killshell.go b/pkg/tools/builtin/killshell.go
index 9d46b03..85166d3 100644
--- a/pkg/tools/builtin/killshell.go
+++ b/pkg/tools/builtin/killshell.go
@@ -2,6 +2,7 @@ package builtin
import (
"context"
+ "errors"
"fmt"
"time"
@@ -72,7 +73,7 @@ func (t *KillShellTool) Execute(ctx context.Context, input map[string]any, tc *t
cleanup := GetBoolParam(input, "cleanup", true)
if shellID == "" {
- return NewClaudeErrorResponse(fmt.Errorf("shell_id cannot be empty")), nil
+ return NewClaudeErrorResponse(errors.New("shell_id cannot be empty")), nil
}
start := time.Now()
@@ -85,7 +86,7 @@ func (t *KillShellTool) Execute(ctx context.Context, input map[string]any, tc *t
if err != nil {
return map[string]any{
"ok": false,
- "error": fmt.Sprintf("background shell not found: %s", shellID),
+ "error": "background shell not found: " + shellID,
"recommendations": []string{
"确认shell_id是否正确",
"检查任务是否已经被终止",
@@ -100,7 +101,7 @@ func (t *KillShellTool) Execute(ctx context.Context, input map[string]any, tc *t
if task.Status != "running" {
return map[string]any{
"ok": false,
- "error": fmt.Sprintf("task is not running, current status: %s", task.Status),
+ "error": "task is not running, current status: " + task.Status,
"shell_id": shellID,
"command": task.Command,
"pid": task.PID,
diff --git a/pkg/tools/builtin/killshell_test.go b/pkg/tools/builtin/killshell_test.go
index 79f245a..bcbf13a 100644
--- a/pkg/tools/builtin/killshell_test.go
+++ b/pkg/tools/builtin/killshell_test.go
@@ -3,6 +3,7 @@ package builtin
import (
"fmt"
"os"
+ "slices"
"strings"
"testing"
"time"
@@ -410,13 +411,7 @@ func TestKillShellTool_InvalidSignal(t *testing.T) {
// 如果成功,验证使用了合理的信号
validSignals := []string{"SIGTERM", "SIGINT", "SIGHUP", "SIGQUIT"}
signal := result["signal"].(string)
- signalValid := false
- for _, valid := range validSignals {
- if signal == valid {
- signalValid = true
- break
- }
- }
+ signalValid := slices.Contains(validSignals, signal)
if !signalValid {
t.Logf("Unknown signal used: %s", signal)
}
@@ -462,7 +457,7 @@ func TestKillShellTool_ConcurrentKill(t *testing.T) {
taskIDs := []string{}
numTasks := 3
- for i := 0; i < numTasks; i++ {
+ for i := range numTasks {
bashInput := map[string]any{
"command": fmt.Sprintf("sleep 5 && echo 'Task %d completed'", i),
"background": true,
diff --git a/pkg/tools/builtin/mcp_resources.go b/pkg/tools/builtin/mcp_resources.go
index 31f581a..25196e4 100644
--- a/pkg/tools/builtin/mcp_resources.go
+++ b/pkg/tools/builtin/mcp_resources.go
@@ -2,6 +2,7 @@ package builtin
import (
"context"
+ "errors"
"fmt"
"time"
@@ -198,11 +199,11 @@ func (t *ReadMcpResourceTool) Execute(ctx context.Context, input map[string]any,
start := time.Now()
if server == "" {
- return NewClaudeErrorResponse(fmt.Errorf("server is required")), nil
+ return NewClaudeErrorResponse(errors.New("server is required")), nil
}
if uri == "" {
- return NewClaudeErrorResponse(fmt.Errorf("uri is required")), nil
+ return NewClaudeErrorResponse(errors.New("uri is required")), nil
}
// 读取 MCP 资源
@@ -233,7 +234,7 @@ func (t *ReadMcpResourceTool) Execute(ctx context.Context, input map[string]any,
func (t *ReadMcpResourceTool) readResource(ctx context.Context, serverName, uri string, tc *tools.ToolContext) ([]MCPResourceContent, error) {
if tc == nil || tc.MCPManager == nil {
- return nil, fmt.Errorf("MCP manager not available")
+ return nil, errors.New("MCP manager not available")
}
server, exists := tc.MCPManager.GetServer(serverName)
@@ -245,7 +246,7 @@ func (t *ReadMcpResourceTool) readResource(ctx context.Context, serverName, uri
// 这里需要根据实际的 MCP 服务器接口来实现
_ = server // 使用 server 变量
- return nil, fmt.Errorf("resource reading not yet implemented for this server")
+ return nil, errors.New("resource reading not yet implemented for this server")
}
func (t *ReadMcpResourceTool) Prompt() string {
diff --git a/pkg/tools/builtin/rag_search.go b/pkg/tools/builtin/rag_search.go
index d382bac..129f586 100644
--- a/pkg/tools/builtin/rag_search.go
+++ b/pkg/tools/builtin/rag_search.go
@@ -2,6 +2,7 @@ package builtin
import (
"context"
+ "errors"
"fmt"
"github.com/astercloud/aster/pkg/memory"
@@ -78,13 +79,13 @@ func (t *RAGSearchTool) InputSchema() map[string]any {
func (t *RAGSearchTool) Execute(ctx context.Context, input map[string]any, tc *tools.ToolContext) (any, error) {
// 检查 SemanticMemory 是否已配置
if t.sm == nil || !t.sm.Enabled() {
- return nil, fmt.Errorf("semantic memory not configured or enabled")
+ return nil, errors.New("semantic memory not configured or enabled")
}
// 提取查询文本
rawQuery, ok := input["query"].(string)
if !ok || rawQuery == "" {
- return nil, fmt.Errorf("query is required and must be a non-empty string")
+ return nil, errors.New("query is required and must be a non-empty string")
}
// 可选参数:top_k
diff --git a/pkg/tools/builtin/read_test.go b/pkg/tools/builtin/read_test.go
index 5381291..4fb9bca 100644
--- a/pkg/tools/builtin/read_test.go
+++ b/pkg/tools/builtin/read_test.go
@@ -1,7 +1,7 @@
package builtin
import (
- "fmt"
+ "errors"
"os"
"path/filepath"
"runtime"
@@ -281,7 +281,7 @@ func TestReadTool_ConcurrentReads(t *testing.T) {
}
result := ExecuteToolWithRealFS(t, tool, input)
if !result["ok"].(bool) {
- return fmt.Errorf("Tool execution failed")
+ return errors.New("Tool execution failed")
}
return nil
})
diff --git a/pkg/tools/builtin/semanticmemory.go b/pkg/tools/builtin/semanticmemory.go
index 3e892b4..d936b5e 100644
--- a/pkg/tools/builtin/semanticmemory.go
+++ b/pkg/tools/builtin/semanticmemory.go
@@ -2,7 +2,7 @@ package builtin
import (
"context"
- "fmt"
+ "errors"
"github.com/astercloud/aster/pkg/memory"
"github.com/astercloud/aster/pkg/tools"
@@ -73,12 +73,12 @@ func (t *SemanticSearchTool) InputSchema() map[string]any {
func (t *SemanticSearchTool) Execute(ctx context.Context, input map[string]any, tc *tools.ToolContext) (any, error) {
if t.sm == nil || !t.sm.Enabled() {
- return nil, fmt.Errorf("semantic memory not configured")
+ return nil, errors.New("semantic memory not configured")
}
rawQuery, _ := input["query"].(string)
if rawQuery == "" {
- return nil, fmt.Errorf("query is required")
+ return nil, errors.New("query is required")
}
// 可选 top_k
diff --git a/pkg/tools/builtin/skillcall.go b/pkg/tools/builtin/skillcall.go
index 11187f5..90e2e37 100644
--- a/pkg/tools/builtin/skillcall.go
+++ b/pkg/tools/builtin/skillcall.go
@@ -2,7 +2,7 @@ package builtin
import (
"context"
- "fmt"
+ "errors"
"github.com/astercloud/aster/pkg/skills"
"github.com/astercloud/aster/pkg/tools"
@@ -50,23 +50,23 @@ func (t *SkillTool) InputSchema() map[string]any {
func (t *SkillTool) Execute(ctx context.Context, input map[string]any, tc *tools.ToolContext) (any, error) {
// 获取 Runtime
if tc == nil || tc.Services == nil {
- return nil, fmt.Errorf("skill_call: ToolContext.Services is nil; skills runtime not available")
+ return nil, errors.New("skill_call: ToolContext.Services is nil; skills runtime not available")
}
rtAny, ok := tc.Services["skills_runtime"]
if !ok {
- return nil, fmt.Errorf("skill_call: skills runtime not found in ToolContext.Services (key: \"skills_runtime\")")
+ return nil, errors.New("skill_call: skills runtime not found in ToolContext.Services (key: \"skills_runtime\")")
}
rt, ok := rtAny.(*skills.Runtime)
if !ok || rt == nil {
- return nil, fmt.Errorf("skill_call: invalid skills runtime type in ToolContext.Services")
+ return nil, errors.New("skill_call: invalid skills runtime type in ToolContext.Services")
}
// 解析输入
skillName, ok := input["skill"].(string)
if !ok || skillName == "" {
- return nil, fmt.Errorf("skill_call: \"skill\" must be a non-empty string")
+ return nil, errors.New("skill_call: \"skill\" must be a non-empty string")
}
// params 期望为 map[string]string,但 JSON 反序列化会是 map[string]any
diff --git a/pkg/tools/builtin/skillcall_test.go b/pkg/tools/builtin/skillcall_test.go
index ed97d25..22607ad 100644
--- a/pkg/tools/builtin/skillcall_test.go
+++ b/pkg/tools/builtin/skillcall_test.go
@@ -1,7 +1,7 @@
package builtin
import (
- "fmt"
+ "errors"
"testing"
)
@@ -208,7 +208,7 @@ func TestSkillCallTool_ConcurrentCalls(t *testing.T) {
// 验证基本响应
if _, exists := result["skill"]; !exists {
- return fmt.Errorf("Missing skill in result")
+ return errors.New("Missing skill in result")
}
return nil
diff --git a/pkg/tools/builtin/storage_manager.go b/pkg/tools/builtin/storage_manager.go
index 67007b8..1b75a27 100644
--- a/pkg/tools/builtin/storage_manager.go
+++ b/pkg/tools/builtin/storage_manager.go
@@ -106,17 +106,17 @@ func (fsm *FileStorageManager) StoreData(key string, data any) error {
// 创建目录
dir := filepath.Dir(filepath.Join(fsm.dataDir, key))
if err := os.MkdirAll(dir, 0755); err != nil {
- return fmt.Errorf("failed to create directory: %v", err)
+ return fmt.Errorf("failed to create directory: %w", err)
}
// 序列化数据
jsonData, err := json.MarshalIndent(data, "", " ")
if err != nil {
- return fmt.Errorf("failed to marshal data: %v", err)
+ return fmt.Errorf("failed to marshal data: %w", err)
}
// 写入文件
- filePath := filepath.Join(fsm.dataDir, fmt.Sprintf("%s.json", key))
+ filePath := filepath.Join(fsm.dataDir, key+".json")
return os.WriteFile(filePath, jsonData, 0644)
}
@@ -125,10 +125,10 @@ func (fsm *FileStorageManager) LoadData(key string, target any) error {
fsm.mu.RLock()
defer fsm.mu.RUnlock()
- filePath := filepath.Join(fsm.dataDir, fmt.Sprintf("%s.json", key))
+ filePath := filepath.Join(fsm.dataDir, key+".json")
data, err := os.ReadFile(filePath)
if err != nil {
- return fmt.Errorf("failed to read file: %v", err)
+ return fmt.Errorf("failed to read file: %w", err)
}
return json.Unmarshal(data, target)
@@ -139,10 +139,10 @@ func (fsm *FileStorageManager) DeleteData(key string) error {
fsm.mu.Lock()
defer fsm.mu.Unlock()
- filePath := filepath.Join(fsm.dataDir, fmt.Sprintf("%s.json", key))
+ filePath := filepath.Join(fsm.dataDir, key+".json")
err := os.Remove(filePath)
if err != nil && !os.IsNotExist(err) {
- return fmt.Errorf("failed to delete file: %v", err)
+ return fmt.Errorf("failed to delete file: %w", err)
}
return nil
@@ -150,7 +150,7 @@ func (fsm *FileStorageManager) DeleteData(key string) error {
// Exists 检查数据是否存在
func (fsm *FileStorageManager) Exists(key string) bool {
- filePath := filepath.Join(fsm.dataDir, fmt.Sprintf("%s.json", key))
+ filePath := filepath.Join(fsm.dataDir, key+".json")
_, err := os.Stat(filePath)
return err == nil
}
@@ -167,7 +167,7 @@ func (fsm *FileStorageManager) Backup(backupPath string) error {
// 创建备份目录
if err := os.MkdirAll(filepath.Dir(backupPath), 0755); err != nil {
- return fmt.Errorf("failed to create backup directory: %v", err)
+ return fmt.Errorf("failed to create backup directory: %w", err)
}
// 创建备份压缩文件
@@ -185,7 +185,7 @@ func (fsm *FileStorageManager) Restore(backupPath string) error {
// 创建数据目录
if err := os.MkdirAll(fsm.dataDir, 0755); err != nil {
- return fmt.Errorf("failed to create data directory: %v", err)
+ return fmt.Errorf("failed to create data directory: %w", err)
}
// 从备份恢复
@@ -207,14 +207,14 @@ func NewFilePlanManager(storageMgr StorageManager) *FilePlanManager {
// StorePlan 存储计划
func (fpm *FilePlanManager) StorePlan(plan *PlanRecord) error {
- key := fmt.Sprintf("plans/%s", plan.ID)
+ key := "plans/" + plan.ID
return fpm.storageManager.StoreData(key, plan)
}
// LoadPlan 加载计划
func (fpm *FilePlanManager) LoadPlan(planID string) (*PlanRecord, error) {
var plan PlanRecord
- key := fmt.Sprintf("plans/%s", planID)
+ key := "plans/" + planID
err := fpm.storageManager.LoadData(key, &plan)
return &plan, err
}
@@ -223,7 +223,7 @@ func (fpm *FilePlanManager) LoadPlan(planID string) (*PlanRecord, error) {
func (fpm *FilePlanManager) UpdatePlanStatus(planID string, status string) error {
plan, err := fpm.LoadPlan(planID)
if err != nil {
- return fmt.Errorf("failed to load plan: %v", err)
+ return fmt.Errorf("failed to load plan: %w", err)
}
plan.Status = status
@@ -281,7 +281,7 @@ func (fpm *FilePlanManager) ListPlans() ([]*PlanRecord, error) {
// DeletePlan 删除计划
func (fpm *FilePlanManager) DeletePlan(planID string) error {
- key := fmt.Sprintf("plans/%s", planID)
+ key := "plans/" + planID
return fpm.storageManager.DeleteData(key)
}
@@ -299,14 +299,14 @@ func NewFileTodoManager(storageMgr StorageManager) *FileTodoManager {
// StoreTodoList 存储任务列表
func (ftm *FileTodoManager) StoreTodoList(list *TodoList) error {
- key := fmt.Sprintf("todos/%s", list.Name)
+ key := "todos/" + list.Name
return ftm.storageManager.StoreData(key, list)
}
// LoadTodoList 加载任务列表
func (ftm *FileTodoManager) LoadTodoList(listName string) (*TodoList, error) {
var list TodoList
- key := fmt.Sprintf("todos/%s", listName)
+ key := "todos/" + listName
err := ftm.storageManager.LoadData(key, &list)
return &list, err
}
@@ -329,7 +329,7 @@ func (ftm *FileTodoManager) ListTodoLists() ([]string, error) {
// DeleteTodoList 删除任务列表
func (ftm *FileTodoManager) DeleteTodoList(listName string) error {
- key := fmt.Sprintf("todos/%s", listName)
+ key := "todos/" + listName
return ftm.storageManager.DeleteData(key)
}
@@ -356,7 +356,7 @@ func (ftm *FileTodoManager) BackupTodoLists() (map[string]*TodoList, error) {
func (ftm *FileTodoManager) RestoreTodoLists(backup map[string]*TodoList) error {
for listName, list := range backup {
if err := ftm.StoreTodoList(list); err != nil {
- return fmt.Errorf("failed to restore todo list %s: %v", listName, err)
+ return fmt.Errorf("failed to restore todo list %s: %w", listName, err)
}
}
return nil
diff --git a/pkg/tools/builtin/subagent_manager.go b/pkg/tools/builtin/subagent_manager.go
index 458066b..2f2d19a 100644
--- a/pkg/tools/builtin/subagent_manager.go
+++ b/pkg/tools/builtin/subagent_manager.go
@@ -3,10 +3,13 @@ package builtin
import (
"context"
"encoding/json"
+ "errors"
"fmt"
+ "maps"
"os"
"os/exec"
"path/filepath"
+ "strconv"
"strings"
"sync"
"time"
@@ -132,7 +135,7 @@ func (sm *FileSubagentManager) StartSubagent(ctx context.Context, config *Subage
// 构建启动命令
cmd, err := sm.buildSubagentCommand(config)
if err != nil {
- return nil, fmt.Errorf("failed to build subagent command: %v", err)
+ return nil, fmt.Errorf("failed to build subagent command: %w", err)
}
// 启动子代理进程
@@ -149,10 +152,10 @@ func (sm *FileSubagentManager) StartSubagent(ctx context.Context, config *Subage
}
// 创建输出文件
- outputFile := filepath.Join(sm.dataDir, fmt.Sprintf("%s.output", config.ID))
+ outputFile := filepath.Join(sm.dataDir, config.ID+".output")
outFile, err := os.Create(outputFile)
if err != nil {
- return nil, fmt.Errorf("failed to create output file: %v", err)
+ return nil, fmt.Errorf("failed to create output file: %w", err)
}
cmdObj.Stdout = outFile
@@ -162,7 +165,7 @@ func (sm *FileSubagentManager) StartSubagent(ctx context.Context, config *Subage
err = cmdObj.Start()
if err != nil {
_ = outFile.Close()
- return nil, fmt.Errorf("failed to start subagent: %v", err)
+ return nil, fmt.Errorf("failed to start subagent: %w", err)
}
// 更新实例信息
@@ -196,13 +199,11 @@ func (sm *FileSubagentManager) ResumeSubagent(taskID string) (*SubagentInstance,
ctx := context.Background()
newInstance, err := sm.StartSubagent(ctx, instance.Config)
if err != nil {
- return nil, fmt.Errorf("failed to resume subagent: %v", err)
+ return nil, fmt.Errorf("failed to resume subagent: %w", err)
}
// 保留原有元数据
- for k, v := range instance.Metadata {
- newInstance.Metadata[k] = v
- }
+ maps.Copy(newInstance.Metadata, instance.Metadata)
return newInstance, nil
}
@@ -287,10 +288,10 @@ func (sm *FileSubagentManager) GetSubagentOutput(taskID string) (string, error)
return "", err
}
- outputFile := filepath.Join(sm.dataDir, fmt.Sprintf("%s.output", taskID))
+ outputFile := filepath.Join(sm.dataDir, taskID+".output")
data, err := os.ReadFile(outputFile)
if err != nil {
- return "", fmt.Errorf("failed to read output file: %v", err)
+ return "", fmt.Errorf("failed to read output file: %w", err)
}
return string(data), nil
@@ -315,14 +316,14 @@ func (sm *FileSubagentManager) CleanupSubagent(taskID string) error {
}
// 删除输出文件
- outputFile := filepath.Join(sm.dataDir, fmt.Sprintf("%s.output", taskID))
+ outputFile := filepath.Join(sm.dataDir, taskID+".output")
_ = os.Remove(outputFile)
// 删除实例记录
delete(sm.agents, taskID)
// 删除实例文件
- instanceFile := filepath.Join(sm.dataDir, fmt.Sprintf("%s.json", taskID))
+ instanceFile := filepath.Join(sm.dataDir, taskID+".json")
_ = os.Remove(instanceFile)
return nil
@@ -348,11 +349,11 @@ func (sm *FileSubagentManager) buildSubagentCommand(config *SubagentConfig) (str
subagentCmd := fmt.Sprintf("%s subagent --type=%s --prompt='%s'", exePath, config.Type, strings.ReplaceAll(config.Prompt, "'", "'\"'\"'"))
if config.Model != "" {
- subagentCmd += fmt.Sprintf(" --model=%s", config.Model)
+ subagentCmd += " --model=" + config.Model
}
if config.Timeout > 0 {
- subagentCmd += fmt.Sprintf(" --timeout=%s", config.Timeout.String())
+ subagentCmd += " --timeout=" + config.Timeout.String()
}
if config.MaxTokens > 0 {
@@ -395,7 +396,8 @@ func (sm *FileSubagentManager) monitorSubagent(ctx context.Context, instance *Su
instance.LastUpdate = now
if err != nil {
- if exitErr, ok := err.(*exec.ExitError); ok {
+ exitErr := &exec.ExitError{}
+ if errors.As(err, &exitErr) {
instance.ExitCode = exitErr.ExitCode()
instance.Status = "failed"
instance.Error = err.Error()
@@ -416,7 +418,7 @@ func (sm *FileSubagentManager) monitorSubagent(ctx context.Context, instance *Su
// updateSubagentOutput 更新子代理输出
func (sm *FileSubagentManager) updateSubagentOutput(instance *SubagentInstance) {
- outputFile := filepath.Join(sm.dataDir, fmt.Sprintf("%s.output", instance.ID))
+ outputFile := filepath.Join(sm.dataDir, instance.ID+".output")
data, err := os.ReadFile(outputFile)
if err == nil {
instance.Output = string(data)
@@ -430,7 +432,7 @@ func (sm *FileSubagentManager) updateResourceUsage(instance *SubagentInstance) {
}
// 简化实现:使用ps命令获取进程资源信息
- cmd := exec.Command("ps", "-p", fmt.Sprintf("%d", instance.PID), "-o", "rss,pcpu", "--no-headers")
+ cmd := exec.Command("ps", "-p", strconv.Itoa(instance.PID), "-o", "rss,pcpu", "--no-headers")
output, err := cmd.Output()
if err != nil {
return
@@ -451,11 +453,11 @@ func (sm *FileSubagentManager) updateResourceUsage(instance *SubagentInstance) {
// saveSubagent 保存子代理信息到文件
func (sm *FileSubagentManager) saveSubagent(instance *SubagentInstance) error {
- instanceFile := filepath.Join(sm.dataDir, fmt.Sprintf("%s.json", instance.ID))
+ instanceFile := filepath.Join(sm.dataDir, instance.ID+".json")
data, err := json.MarshalIndent(instance, "", " ")
if err != nil {
- return fmt.Errorf("failed to marshal subagent: %v", err)
+ return fmt.Errorf("failed to marshal subagent: %w", err)
}
return os.WriteFile(instanceFile, data, 0644)
@@ -465,7 +467,7 @@ func (sm *FileSubagentManager) saveSubagent(instance *SubagentInstance) error {
func (sm *FileSubagentManager) loadSubagents() error {
files, err := os.ReadDir(sm.dataDir)
if err != nil {
- return fmt.Errorf("failed to read data directory: %v", err)
+ return fmt.Errorf("failed to read data directory: %w", err)
}
for _, file := range files {
diff --git a/pkg/tools/builtin/task.go b/pkg/tools/builtin/task.go
index 1727140..d15c298 100644
--- a/pkg/tools/builtin/task.go
+++ b/pkg/tools/builtin/task.go
@@ -2,7 +2,10 @@ package builtin
import (
"context"
+ "errors"
"fmt"
+ "slices"
+ "strconv"
"time"
"github.com/astercloud/aster/pkg/tools"
@@ -107,13 +110,13 @@ func (t *TaskTool) Execute(ctx context.Context, input map[string]any, tc *tools.
case "status":
taskID := GetStringParam(input, "task_id", "")
if taskID == "" {
- return NewClaudeErrorResponse(fmt.Errorf("task_id is required for status action")), nil
+ return NewClaudeErrorResponse(errors.New("task_id is required for status action")), nil
}
return t.getTaskStatus(taskID)
case "cancel":
taskID := GetStringParam(input, "task_id", "")
if taskID == "" {
- return NewClaudeErrorResponse(fmt.Errorf("task_id is required for cancel action")), nil
+ return NewClaudeErrorResponse(errors.New("task_id is required for cancel action")), nil
}
return t.cancelTask(taskID)
case "run":
@@ -223,21 +226,15 @@ func (t *TaskTool) runTask(ctx context.Context, input map[string]any) (any, erro
async := GetBoolParam(input, "async", true)
if subagentType == "" {
- return NewClaudeErrorResponse(fmt.Errorf("subagent_type is required for run action")), nil
+ return NewClaudeErrorResponse(errors.New("subagent_type is required for run action")), nil
}
if prompt == "" {
- return NewClaudeErrorResponse(fmt.Errorf("prompt is required for run action")), nil
+ return NewClaudeErrorResponse(errors.New("prompt is required for run action")), nil
}
// 验证子代理类型
validSubagents := []string{"general-purpose", "Explore", "Plan"}
- subagentValid := false
- for _, valid := range validSubagents {
- if subagentType == valid {
- subagentValid = true
- break
- }
- }
+ subagentValid := slices.Contains(validSubagents, subagentType)
if !subagentValid {
return NewClaudeErrorResponse(
fmt.Errorf("invalid subagent_type: %s", subagentType),
@@ -365,9 +362,9 @@ func (t *TaskTool) executeWithSubagentManager(ctx context.Context, subagentType,
Model: model,
Timeout: time.Duration(timeoutMinutes) * time.Minute,
Metadata: map[string]string{
- "priority": fmt.Sprintf("%d", priority),
- "async": fmt.Sprintf("%t", async),
- "created": fmt.Sprintf("%d", time.Now().Unix()),
+ "priority": strconv.Itoa(priority),
+ "async": strconv.FormatBool(async),
+ "created": strconv.FormatInt(time.Now().Unix(), 10),
},
}
diff --git a/pkg/tools/builtin/task_executor.go b/pkg/tools/builtin/task_executor.go
index 7700103..a3af117 100644
--- a/pkg/tools/builtin/task_executor.go
+++ b/pkg/tools/builtin/task_executor.go
@@ -2,6 +2,7 @@ package builtin
import (
"context"
+ "errors"
"fmt"
"sync"
"time"
@@ -34,7 +35,7 @@ type SubAgentExecutorFactory interface {
type TaskExecutionHandle struct {
TaskID string
AgentType string
- Status string // "pending", "running", "completed", "failed", "cancelled"
+ Status string // "pending", "running", "completed", "failed", "canceled"
StartTime time.Time
EndTime *time.Time
Result *types.SubAgentResult
@@ -63,7 +64,7 @@ func (te *TaskExecutor) Execute(ctx context.Context, agentType, prompt string, o
te.mu.RUnlock()
if factory == nil {
- return nil, fmt.Errorf("executor factory not configured, subagent execution not available")
+ return nil, errors.New("executor factory not configured, subagent execution not available")
}
// 创建执行器
@@ -132,7 +133,7 @@ func (te *TaskExecutor) ExecuteAsync(ctx context.Context, agentType, prompt stri
te.mu.RUnlock()
if factory == nil {
- return nil, fmt.Errorf("executor factory not configured")
+ return nil, errors.New("executor factory not configured")
}
// 创建执行器
diff --git a/pkg/tools/builtin/task_manager.go b/pkg/tools/builtin/task_manager.go
index 956f08e..937b2cb 100644
--- a/pkg/tools/builtin/task_manager.go
+++ b/pkg/tools/builtin/task_manager.go
@@ -3,6 +3,7 @@ package builtin
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"os"
"os/exec"
@@ -155,19 +156,19 @@ func (tm *FileTaskManager) StartTask(ctx context.Context, cmd string, opts *Task
}
// 创建输出文件
- outputFile := filepath.Join(opts.OutputDir, fmt.Sprintf("%s.stdout", taskID))
- errorFile := filepath.Join(opts.OutputDir, fmt.Sprintf("%s.stderr", taskID))
+ outputFile := filepath.Join(opts.OutputDir, taskID+".stdout")
+ errorFile := filepath.Join(opts.OutputDir, taskID+".stderr")
if opts.CaptureOutput {
outFile, err := os.Create(outputFile)
if err != nil {
- return nil, fmt.Errorf("failed to create output file: %v", err)
+ return nil, fmt.Errorf("failed to create output file: %w", err)
}
errFile, err := os.Create(errorFile)
if err != nil {
_ = outFile.Close()
- return nil, fmt.Errorf("failed to create error file: %v", err)
+ return nil, fmt.Errorf("failed to create error file: %w", err)
}
cmdObj.Stdout = outFile
@@ -183,7 +184,7 @@ func (tm *FileTaskManager) StartTask(ctx context.Context, cmd string, opts *Task
// 启动进程
err := cmdObj.Start()
if err != nil {
- return nil, fmt.Errorf("failed to start command: %v", err)
+ return nil, fmt.Errorf("failed to start command: %w", err)
}
// 更新任务信息
@@ -225,19 +226,19 @@ func (tm *FileTaskManager) GetTaskOutput(taskID string, filter string, lines int
return "", "", err
}
- outputFile := filepath.Join(task.Options.OutputDir, fmt.Sprintf("%s.stdout", taskID))
- errorFile := filepath.Join(task.Options.OutputDir, fmt.Sprintf("%s.stderr", taskID))
+ outputFile := filepath.Join(task.Options.OutputDir, taskID+".stdout")
+ errorFile := filepath.Join(task.Options.OutputDir, taskID+".stderr")
// 读取标准输出
stdout, err := os.ReadFile(outputFile)
if err != nil && !os.IsNotExist(err) {
- return "", "", fmt.Errorf("failed to read stdout: %v", err)
+ return "", "", fmt.Errorf("failed to read stdout: %w", err)
}
// 读取错误输出
stderr, err := os.ReadFile(errorFile)
if err != nil && !os.IsNotExist(err) {
- return "", "", fmt.Errorf("failed to read stderr: %v", err)
+ return "", "", fmt.Errorf("failed to read stderr: %w", err)
}
stdoutStr := string(stdout)
@@ -278,12 +279,12 @@ func (tm *FileTaskManager) KillTask(taskID string, signal string, timeout int) e
// 发送信号
proc, err := os.FindProcess(task.PID)
if err != nil {
- return fmt.Errorf("failed to find process %d: %v", task.PID, err)
+ return fmt.Errorf("failed to find process %d: %w", task.PID, err)
}
err = proc.Signal(syscall.Signal(signalNum))
if err != nil {
- return fmt.Errorf("failed to send signal %s to process %d: %v", signal, task.PID, err)
+ return fmt.Errorf("failed to send signal %s to process %d: %w", signal, task.PID, err)
}
// 等待进程退出
@@ -337,8 +338,8 @@ func (tm *FileTaskManager) CleanupTask(taskID string) error {
}
// 删除输出文件
- outputFile := filepath.Join(task.Options.OutputDir, fmt.Sprintf("%s.stdout", taskID))
- errorFile := filepath.Join(task.Options.OutputDir, fmt.Sprintf("%s.stderr", taskID))
+ outputFile := filepath.Join(task.Options.OutputDir, taskID+".stdout")
+ errorFile := filepath.Join(task.Options.OutputDir, taskID+".stderr")
_ = os.Remove(outputFile)
_ = os.Remove(errorFile)
@@ -347,7 +348,7 @@ func (tm *FileTaskManager) CleanupTask(taskID string) error {
delete(tm.tasks, taskID)
// 删除任务文件
- taskFile := filepath.Join(tm.dataDir, fmt.Sprintf("%s.json", taskID))
+ taskFile := filepath.Join(tm.dataDir, taskID+".json")
_ = os.Remove(taskFile)
return nil
@@ -399,7 +400,8 @@ func (tm *FileTaskManager) monitorTask(ctx context.Context, task *TaskInfo, cmd
task.LastUpdate = now
if err != nil {
- if exitErr, ok := err.(*exec.ExitError); ok {
+ exitErr := &exec.ExitError{}
+ if errors.As(err, &exitErr) {
task.ExitCode = exitErr.ExitCode()
task.Status = "failed"
} else {
@@ -465,11 +467,11 @@ func (tm *FileTaskManager) waitForProcessExit(task *TaskInfo, timeout int) {
// saveTask 保存任务信息到文件
func (tm *FileTaskManager) saveTask(task *TaskInfo) error {
- taskFile := filepath.Join(tm.dataDir, fmt.Sprintf("%s.json", task.ID))
+ taskFile := filepath.Join(tm.dataDir, task.ID+".json")
data, err := json.MarshalIndent(task, "", " ")
if err != nil {
- return fmt.Errorf("failed to marshal task: %v", err)
+ return fmt.Errorf("failed to marshal task: %w", err)
}
return os.WriteFile(taskFile, data, 0644)
@@ -479,7 +481,7 @@ func (tm *FileTaskManager) saveTask(task *TaskInfo) error {
func (tm *FileTaskManager) loadTasks() error {
files, err := os.ReadDir(tm.dataDir)
if err != nil {
- return fmt.Errorf("failed to read data directory: %v", err)
+ return fmt.Errorf("failed to read data directory: %w", err)
}
for _, file := range files {
@@ -528,7 +530,7 @@ func (tm *FileTaskManager) cleanupCompletedTasks() {
if time.Since(*task.EndTime) > time.Hour {
delete(tm.tasks, task.ID)
- taskFile := filepath.Join(tm.dataDir, fmt.Sprintf("%s.json", task.ID))
+ taskFile := filepath.Join(tm.dataDir, task.ID+".json")
_ = os.Remove(taskFile)
}
}
@@ -620,16 +622,16 @@ func (tm *FileTaskManager) cleanupOutputFiles(taskID string) error {
return fmt.Errorf("task not found: %s", taskID)
}
- outputFile := filepath.Join(task.Options.OutputDir, fmt.Sprintf("%s.stdout", taskID))
- errorFile := filepath.Join(task.Options.OutputDir, fmt.Sprintf("%s.stderr", taskID))
+ outputFile := filepath.Join(task.Options.OutputDir, taskID+".stdout")
+ errorFile := filepath.Join(task.Options.OutputDir, taskID+".stderr")
// 清空文件内容
if err := os.WriteFile(outputFile, []byte{}, 0644); err != nil {
- return fmt.Errorf("failed to clear output file: %v", err)
+ return fmt.Errorf("failed to clear output file: %w", err)
}
if err := os.WriteFile(errorFile, []byte{}, 0644); err != nil {
- return fmt.Errorf("failed to clear error file: %v", err)
+ return fmt.Errorf("failed to clear error file: %w", err)
}
return nil
diff --git a/pkg/tools/builtin/task_test.go b/pkg/tools/builtin/task_test.go
index 83f423c..4a5b5c9 100644
--- a/pkg/tools/builtin/task_test.go
+++ b/pkg/tools/builtin/task_test.go
@@ -1,6 +1,7 @@
package builtin
import (
+ "errors"
"fmt"
"strings"
"testing"
@@ -267,13 +268,13 @@ func TestTaskTool_ConcurrentSubagentLaunch(t *testing.T) {
result := ExecuteToolWithInput(t, tool, input)
if !result["ok"].(bool) {
- return fmt.Errorf("Task launch failed")
+ return errors.New("Task launch failed")
}
// 验证task_id不为空
taskID := result["task_id"].(string)
if taskID == "" {
- return fmt.Errorf("Empty task_id returned")
+ return errors.New("Empty task_id returned")
}
return nil
diff --git a/pkg/tools/builtin/testdata/scripts/harmless.sh b/pkg/tools/builtin/testdata/scripts/harmless.sh
index e472842..0f944de 100755
--- a/pkg/tools/builtin/testdata/scripts/harmless.sh
+++ b/pkg/tools/builtin/testdata/scripts/harmless.sh
@@ -20,4 +20,4 @@ if [ "$1" = "fail" ]; then
fi
echo "Script completed successfully"
-exit 0
\ No newline at end of file
+exit 0
diff --git a/pkg/tools/builtin/testdata/scripts/timeout_test.sh b/pkg/tools/builtin/testdata/scripts/timeout_test.sh
index 40c3b97..5fea2c6 100755
--- a/pkg/tools/builtin/testdata/scripts/timeout_test.sh
+++ b/pkg/tools/builtin/testdata/scripts/timeout_test.sh
@@ -14,4 +14,4 @@ for i in $(seq 1 $DURATION); do
done
echo "Process completed successfully"
-exit 0
\ No newline at end of file
+exit 0
diff --git a/pkg/tools/builtin/testutils.go b/pkg/tools/builtin/testutils.go
index b2b6ef9..4d470fe 100644
--- a/pkg/tools/builtin/testutils.go
+++ b/pkg/tools/builtin/testutils.go
@@ -2,7 +2,7 @@ package builtin
import (
"context"
- "fmt"
+ "errors"
"os"
"path/filepath"
"runtime"
@@ -208,11 +208,11 @@ func (cs *CustomWorkDirSandbox) FS() sandbox.SandboxFS {
}
func (cs *CustomWorkDirSandbox) Exec(ctx context.Context, cmd string, opts *sandbox.ExecOptions) (*sandbox.ExecResult, error) {
- return nil, fmt.Errorf("exec not supported in test sandbox")
+ return nil, errors.New("exec not supported in test sandbox")
}
func (cs *CustomWorkDirSandbox) Watch(paths []string, listener sandbox.FileChangeListener) (string, error) {
- return "", fmt.Errorf("watch not supported in test sandbox")
+ return "", errors.New("watch not supported in test sandbox")
}
func (cs *CustomWorkDirSandbox) Unwatch(watchID string) error {
@@ -239,11 +239,11 @@ func (rs *RealSandbox) FS() sandbox.SandboxFS {
}
func (rs *RealSandbox) Exec(ctx context.Context, cmd string, opts *sandbox.ExecOptions) (*sandbox.ExecResult, error) {
- return nil, fmt.Errorf("exec not supported in test sandbox")
+ return nil, errors.New("exec not supported in test sandbox")
}
func (rs *RealSandbox) Watch(paths []string, listener sandbox.FileChangeListener) (string, error) {
- return "", fmt.Errorf("watch not supported in test sandbox")
+ return "", errors.New("watch not supported in test sandbox")
}
func (rs *RealSandbox) Unwatch(watchID string) error {
@@ -467,7 +467,7 @@ func BenchmarkTool(b *testing.B, tool tools.Tool, input map[string]any) {
}
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for range b.N {
_, err := tool.Execute(ctx, input, tc)
if err != nil {
b.Fatalf("Tool execution failed: %v", err)
diff --git a/pkg/tools/builtin/todowrite.go b/pkg/tools/builtin/todowrite.go
index 7eff0be..241baf5 100644
--- a/pkg/tools/builtin/todowrite.go
+++ b/pkg/tools/builtin/todowrite.go
@@ -2,7 +2,10 @@ package builtin
import (
"context"
+ "errors"
"fmt"
+ "maps"
+ "slices"
"time"
"github.com/astercloud/aster/pkg/tools"
@@ -110,7 +113,7 @@ func (t *TodoWriteTool) Execute(ctx context.Context, input map[string]any, tc *t
// 获取任务项数据
todosData, ok := input["todos"].([]any)
if !ok {
- return NewClaudeErrorResponse(fmt.Errorf("todos must be an array")), nil
+ return NewClaudeErrorResponse(errors.New("todos must be an array")), nil
}
// 转换为TodoItem
@@ -119,7 +122,7 @@ func (t *TodoWriteTool) Execute(ctx context.Context, input map[string]any, tc *t
for i, todoData := range todosData {
todoMap, ok := todoData.(map[string]any)
if !ok {
- return NewClaudeErrorResponse(fmt.Errorf("each todo must be an object")), nil
+ return NewClaudeErrorResponse(errors.New("each todo must be an object")), nil
}
content := GetStringParam(todoMap, "content", "")
@@ -128,22 +131,16 @@ func (t *TodoWriteTool) Execute(ctx context.Context, input map[string]any, tc *t
priority := GetIntParam(todoMap, "priority", 0)
if content == "" {
- return NewClaudeErrorResponse(fmt.Errorf("todo content cannot be empty")), nil
+ return NewClaudeErrorResponse(errors.New("todo content cannot be empty")), nil
}
if activeForm == "" {
- return NewClaudeErrorResponse(fmt.Errorf("todo activeForm cannot be empty")), nil
+ return NewClaudeErrorResponse(errors.New("todo activeForm cannot be empty")), nil
}
// 验证状态
validStatuses := []string{"pending", "in_progress", "completed"}
- statusValid := false
- for _, validStatus := range validStatuses {
- if status == validStatus {
- statusValid = true
- break
- }
- }
+ statusValid := slices.Contains(validStatuses, status)
if !statusValid {
return NewClaudeErrorResponse(
fmt.Errorf("invalid status: %s", status),
@@ -219,13 +216,13 @@ func (t *TodoWriteTool) Execute(ctx context.Context, input map[string]any, tc *t
operationErr = todoManager.StoreTodoList(todoList)
case "update":
if todoID == "" {
- return NewClaudeErrorResponse(fmt.Errorf("todo_id is required for update action")), nil
+ return NewClaudeErrorResponse(errors.New("todo_id is required for update action")), nil
}
result = t.updateTodo(todoList, todoID, todos[0])
operationErr = todoManager.StoreTodoList(todoList)
case "delete":
if todoID == "" {
- return NewClaudeErrorResponse(fmt.Errorf("todo_id is required for delete action")), nil
+ return NewClaudeErrorResponse(errors.New("todo_id is required for delete action")), nil
}
result = t.deleteTodo(todoList, todoID)
operationErr = todoManager.StoreTodoList(todoList)
@@ -273,9 +270,7 @@ func (t *TodoWriteTool) Execute(ctx context.Context, input map[string]any, tc *t
// 添加操作结果
if resultMap, ok := result.(map[string]any); ok {
- for k, v := range resultMap {
- response[k] = v
- }
+ maps.Copy(response, resultMap)
}
// 发送 Todo 更新事件到 Progress 通道
diff --git a/pkg/tools/builtin/todowrite_test.go b/pkg/tools/builtin/todowrite_test.go
index 29dd39b..b52d67c 100644
--- a/pkg/tools/builtin/todowrite_test.go
+++ b/pkg/tools/builtin/todowrite_test.go
@@ -1,6 +1,7 @@
package builtin
import (
+ "errors"
"fmt"
"reflect"
"strings"
@@ -12,7 +13,7 @@ import (
func getFirstTodoID(result map[string]any) (string, error) {
todos, exists := result["todos"]
if !exists {
- return "", fmt.Errorf("result does not contain 'todos' field")
+ return "", errors.New("result does not contain 'todos' field")
}
// 使用反射处理不同类型的切片
@@ -22,7 +23,7 @@ func getFirstTodoID(result map[string]any) (string, error) {
}
if reflectVal.Len() == 0 {
- return "", fmt.Errorf("no todos found")
+ return "", errors.New("no todos found")
}
// 获取第一个元素
@@ -45,7 +46,7 @@ func getFirstTodoID(result map[string]any) (string, error) {
// 从map中获取ID
id, exists := todoMap["id"]
if !exists {
- return "", fmt.Errorf("todo item does not have 'id' field")
+ return "", errors.New("todo item does not have 'id' field")
}
idStr, ok := id.(string)
@@ -581,13 +582,13 @@ func TestTodoWriteTool_ConcurrentOperations(t *testing.T) {
result := ExecuteToolWithInput(t, tool, input)
if !result["ok"].(bool) {
- return fmt.Errorf("TodoWrite operation failed")
+ return errors.New("TodoWrite operation failed")
}
// 验证todo被创建
todos := result["todos"].([]any)
if len(todos) == 0 {
- return fmt.Errorf("No todos created")
+ return errors.New("No todos created")
}
return nil
diff --git a/pkg/tools/builtin/toolsearch.go b/pkg/tools/builtin/toolsearch.go
index af98bdf..f4783bc 100644
--- a/pkg/tools/builtin/toolsearch.go
+++ b/pkg/tools/builtin/toolsearch.go
@@ -2,7 +2,7 @@ package builtin
import (
"context"
- "fmt"
+ "errors"
"time"
"github.com/astercloud/aster/pkg/tools"
@@ -78,7 +78,7 @@ func (t *ToolSearchTool) Execute(ctx context.Context, input map[string]any, tc *
activateTools := GetStringSlice(input, "activate")
if query == "" && category == "" && len(activateTools) == 0 {
- return NewClaudeErrorResponse(fmt.Errorf("query, category or activate is required")), nil
+ return NewClaudeErrorResponse(errors.New("query, category or activate is required")), nil
}
start := time.Now()
diff --git a/pkg/tools/builtin/webfetch.go b/pkg/tools/builtin/webfetch.go
index 9f08c7e..d1eab56 100644
--- a/pkg/tools/builtin/webfetch.go
+++ b/pkg/tools/builtin/webfetch.go
@@ -76,7 +76,7 @@ func (t *WebFetchTool) InputSchema() map[string]any {
func (t *WebFetchTool) Execute(ctx context.Context, input map[string]any, tc *tools.ToolContext) (any, error) {
url, ok := input["url"].(string)
if !ok || url == "" {
- return nil, fmt.Errorf("url must be a non-empty string")
+ return nil, errors.New("url must be a non-empty string")
}
method := "GET"
diff --git a/pkg/tools/builtin/websearch.go b/pkg/tools/builtin/websearch.go
index 4a97b2a..661ab70 100644
--- a/pkg/tools/builtin/websearch.go
+++ b/pkg/tools/builtin/websearch.go
@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
+ "errors"
"fmt"
"net/http"
"os"
@@ -97,7 +98,7 @@ func (t *WebSearchTool) Execute(ctx context.Context, input map[string]any, tc *t
// 2. 解析参数
query, ok := input["query"].(string)
if !ok || query == "" {
- return nil, fmt.Errorf("query must be a non-empty string")
+ return nil, errors.New("query must be a non-empty string")
}
maxResults := 5
@@ -165,7 +166,7 @@ func (t *WebSearchTool) Execute(ctx context.Context, input map[string]any, tc *t
}
// 4. 发送请求到 Tavily API
- req, err := http.NewRequestWithContext(ctx, "POST", "https://api.tavily.com/search", bytes.NewReader(jsonData))
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.tavily.com/search", bytes.NewReader(jsonData))
if err != nil {
return map[string]any{
"error": fmt.Sprintf("failed to create request: %v", err),
diff --git a/pkg/tools/builtin/websearch_test.go b/pkg/tools/builtin/websearch_test.go
index f230c42..d3eba64 100644
--- a/pkg/tools/builtin/websearch_test.go
+++ b/pkg/tools/builtin/websearch_test.go
@@ -75,7 +75,7 @@ func TestWebSearchTool_SuccessfulSearch(t *testing.T) {
// 创建模拟 Tavily API 的测试服务器
server := newLocalHTTPServerWS(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 验证请求方法和头部
- if r.Method != "POST" {
+ if r.Method != http.MethodPost {
t.Errorf("Expected POST method, got %s", r.Method)
}
diff --git a/pkg/tools/builtin/write.go b/pkg/tools/builtin/write.go
index 5637ff9..6b4e960 100644
--- a/pkg/tools/builtin/write.go
+++ b/pkg/tools/builtin/write.go
@@ -2,6 +2,7 @@ package builtin
import (
"context"
+ "errors"
"fmt"
"path/filepath"
"strings"
@@ -72,13 +73,13 @@ func (t *WriteTool) Execute(ctx context.Context, input map[string]any, tc *tools
append := GetBoolParam(input, "append", false)
if filePath == "" {
- return NewClaudeErrorResponse(fmt.Errorf("file_path cannot be empty")), nil
+ return NewClaudeErrorResponse(errors.New("file_path cannot be empty")), nil
}
// 验证文件路径安全性
if err := t.validatePath(filePath); err != nil {
return NewClaudeErrorResponse(
- fmt.Errorf("invalid file path: %v", err),
+ fmt.Errorf("invalid file path: %w", err),
"使用相对路径或允许的绝对路径",
"确保路径不包含 '..' 避免路径遍历攻击",
), nil
diff --git a/pkg/tools/cache_test.go b/pkg/tools/cache_test.go
index 5d97fa4..523256c 100644
--- a/pkg/tools/cache_test.go
+++ b/pkg/tools/cache_test.go
@@ -241,7 +241,7 @@ func TestToolCache_MaxMemoryItems(t *testing.T) {
ctx := context.Background()
// 添加4个条目,应该驱逐最旧的
- for i := 0; i < 4; i++ {
+ for i := range 4 {
key := cache.GenerateKey("tool", map[string]any{"index": i})
err := cache.Set(ctx, key, i, config.TTL)
if err != nil {
@@ -315,7 +315,7 @@ func TestToolCache_Clear(t *testing.T) {
ctx := context.Background()
// 添加多个条目
- for i := 0; i < 5; i++ {
+ for i := range 5 {
key := cache.GenerateKey("tool", map[string]any{"index": i})
err := cache.Set(ctx, key, i, config.TTL)
if err != nil {
@@ -516,7 +516,7 @@ func TestToolCache_Cleanup(t *testing.T) {
ctx := context.Background()
// 添加多个条目
- for i := 0; i < 5; i++ {
+ for i := range 5 {
key := cache.GenerateKey("tool", map[string]any{"index": i})
err := cache.Set(ctx, key, i, config.TTL)
if err != nil {
diff --git a/pkg/tools/constraints.go b/pkg/tools/constraints.go
index 07e3556..a94e3c5 100644
--- a/pkg/tools/constraints.go
+++ b/pkg/tools/constraints.go
@@ -2,6 +2,7 @@ package tools
import (
"context"
+ "errors"
"fmt"
)
@@ -301,19 +302,19 @@ func (b *ConstraintsBuilder) Build() (ToolConstraints, error) {
switch b.constraintType {
case ConstraintTypeWhitelist:
if len(b.tools) == 0 {
- return nil, fmt.Errorf("whitelist requires at least one tool")
+ return nil, errors.New("whitelist requires at least one tool")
}
return NewWhitelistConstraints(b.tools), nil
case ConstraintTypeBlacklist:
if len(b.tools) == 0 {
- return nil, fmt.Errorf("blacklist requires at least one tool")
+ return nil, errors.New("blacklist requires at least one tool")
}
return NewBlacklistConstraints(b.tools), nil
case ConstraintTypeRequired:
if len(b.tools) != 1 {
- return nil, fmt.Errorf("required constraint needs exactly one tool")
+ return nil, errors.New("required constraint needs exactly one tool")
}
return NewRequiredToolConstraints(b.tools[0]), nil
diff --git a/pkg/tools/knowledge/factory.go b/pkg/tools/knowledge/factory.go
index cf478b9..53e54e3 100644
--- a/pkg/tools/knowledge/factory.go
+++ b/pkg/tools/knowledge/factory.go
@@ -2,7 +2,9 @@ package knowledge
import (
"context"
+ "errors"
"fmt"
+ "maps"
"time"
"github.com/astercloud/aster/pkg/knowledge/core"
@@ -34,7 +36,7 @@ func NewFactory(p *core.Pipeline) *Factory {
// KnowledgeAddTool 将文本写入核心管线。
func (f *Factory) KnowledgeAddTool() (tools.Tool, error) {
if f.Pipeline == nil {
- return nil, fmt.Errorf("knowledge tool: pipeline is nil")
+ return nil, errors.New("knowledge tool: pipeline is nil")
}
return &addTool{pipe: f.Pipeline}, nil
}
@@ -42,7 +44,7 @@ func (f *Factory) KnowledgeAddTool() (tools.Tool, error) {
// KnowledgeSearchTool 在核心管线中执行向量检索。
func (f *Factory) KnowledgeSearchTool() (tools.Tool, error) {
if f.Pipeline == nil {
- return nil, fmt.Errorf("knowledge tool: pipeline is nil")
+ return nil, errors.New("knowledge tool: pipeline is nil")
}
return &searchTool{pipe: f.Pipeline}, nil
}
@@ -83,9 +85,7 @@ func (t *addTool) Execute(ctx context.Context, input map[string]any, _ *tools.To
meta := map[string]any{}
if m, ok := input["metadata"].(map[string]any); ok {
- for k, v := range m {
- meta[k] = v
- }
+ maps.Copy(meta, m)
}
if ns != "" {
meta["namespace"] = ns
@@ -148,9 +148,7 @@ func (t *searchTool) Execute(ctx context.Context, input map[string]any, _ *tools
ns, _ := input["namespace"].(string)
meta := map[string]any{}
if m, ok := input["metadata"].(map[string]any); ok {
- for k, v2 := range m {
- meta[k] = v2
- }
+ maps.Copy(meta, m)
}
if ns != "" {
meta["namespace"] = ns
diff --git a/pkg/tools/long_running.go b/pkg/tools/long_running.go
index 7eefe17..90ca14a 100644
--- a/pkg/tools/long_running.go
+++ b/pkg/tools/long_running.go
@@ -2,7 +2,9 @@ package tools
import (
"context"
+ "errors"
"fmt"
+ "maps"
"sync"
"time"
@@ -69,7 +71,7 @@ func (s TaskState) String() string {
case TaskStateFailed:
return "failed"
case TaskStateCancelled:
- return "cancelled"
+ return "canceled"
default:
return "unknown"
}
@@ -131,7 +133,7 @@ func (e *LongRunningExecutor) StartAsync(
if taskCtx.Err() == context.Canceled {
_ = e.updateStatus(taskID, func(s *TaskStatus) {
s.State = TaskStateCancelled
- s.Error = fmt.Errorf("task cancelled")
+ s.Error = errors.New("task canceled")
s.EndTime = &now
})
} else {
@@ -253,9 +255,7 @@ func (e *LongRunningExecutor) Cleanup(before time.Time) int {
func (e *LongRunningExecutor) UpdateProgress(taskID string, progress float64, metadata map[string]any) error {
return e.updateStatus(taskID, func(s *TaskStatus) {
s.Progress = progress
- for k, v := range metadata {
- s.Metadata[k] = v
- }
+ maps.Copy(s.Metadata, metadata)
})
}
@@ -289,9 +289,7 @@ func copyMetadata(src map[string]any) map[string]any {
}
dst := make(map[string]any, len(src))
- for k, v := range src {
- dst[k] = v
- }
+ maps.Copy(dst, src)
return dst
}
@@ -304,6 +302,7 @@ func generateTaskID() string {
// 可以嵌入到具体工具中
type BaseLongRunningTool struct {
BaseTool
+
executor *LongRunningExecutor
}
@@ -340,7 +339,7 @@ func (t *BaseLongRunningTool) Cancel(ctx context.Context, taskID string) error {
// Execute 需要由具体工具实现
func (t *BaseLongRunningTool) Execute(ctx context.Context, args map[string]any, tc *ToolContext) (any, error) {
- return nil, fmt.Errorf("Execute() must be implemented by concrete tool")
+ return nil, errors.New("Execute() must be implemented by concrete tool")
}
// WaitFor 等待任务完成(辅助函数)
diff --git a/pkg/tools/mcp/server.go b/pkg/tools/mcp/server.go
index f4156db..2018724 100644
--- a/pkg/tools/mcp/server.go
+++ b/pkg/tools/mcp/server.go
@@ -2,6 +2,7 @@ package mcp
import (
"context"
+ "errors"
"fmt"
"sync"
@@ -31,11 +32,11 @@ type MCPServerConfig struct {
// NewMCPServer 创建 MCP Server 连接
func NewMCPServer(config *MCPServerConfig, registry *tools.Registry) (*MCPServer, error) {
if config.ServerID == "" {
- return nil, fmt.Errorf("server_id is required")
+ return nil, errors.New("server_id is required")
}
if config.Endpoint == "" {
- return nil, fmt.Errorf("endpoint is required")
+ return nil, errors.New("endpoint is required")
}
// 创建 MCP 客户端
@@ -75,7 +76,7 @@ func (s *MCPServer) RegisterTools() error {
defer s.mu.RUnlock()
if len(s.tools) == 0 {
- return fmt.Errorf("no tools available, call Connect() first")
+ return errors.New("no tools available, call Connect() first")
}
// 为每个 MCP 工具创建工厂并注册
diff --git a/pkg/tools/search/bm25_test.go b/pkg/tools/search/bm25_test.go
index 2f41f51..77ba6f4 100644
--- a/pkg/tools/search/bm25_test.go
+++ b/pkg/tools/search/bm25_test.go
@@ -97,7 +97,7 @@ func TestBM25_ScoreOrdering(t *testing.T) {
// 文档中 "file" 出现次数不同
docs := []Document{
- {ID: "many", Content: "file file file file file"},
+ {ID: "many", Content: "file "},
{ID: "few", Content: "file operations"},
{ID: "none", Content: "bash terminal commands"},
}
diff --git a/pkg/tools/search/tool_index.go b/pkg/tools/search/tool_index.go
index 04fde63..5561121 100644
--- a/pkg/tools/search/tool_index.go
+++ b/pkg/tools/search/tool_index.go
@@ -3,6 +3,7 @@ package search
import (
"encoding/json"
"fmt"
+ "slices"
"strings"
"sync"
@@ -350,10 +351,8 @@ func (ti *ToolIndex) addToCategory(category, toolName string) {
ti.categories[category] = make([]string, 0)
}
// 检查是否已存在
- for _, name := range ti.categories[category] {
- if name == toolName {
- return
- }
+ if slices.Contains(ti.categories[category], toolName) {
+ return
}
ti.categories[category] = append(ti.categories[category], toolName)
}
diff --git a/pkg/types/config.go b/pkg/types/config.go
index 0208092..a711371 100644
--- a/pkg/types/config.go
+++ b/pkg/types/config.go
@@ -245,7 +245,6 @@ type MemoryConfig struct {
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
}
-
// AgentTemplateRuntime Agent模板运行时配置
type AgentTemplateRuntime struct {
ExposeThinking bool `json:"expose_thinking,omitempty"`
@@ -273,7 +272,7 @@ type AgentTemplateDefinition struct {
// ModelConfig 模型配置
type ModelConfig struct {
- Provider string `json:"provider" yaml:"provider"` // "anthropic", "openai", etc.
+ Provider string `json:"provider" yaml:"provider"` // "anthropic", "openai", etc.
Model string `json:"model" yaml:"model"`
APIKey string `json:"api_key,omitempty" yaml:"api_key,omitempty"`
BaseURL string `json:"base_url,omitempty" yaml:"base_url,omitempty"`
diff --git a/pkg/types/content.go b/pkg/types/content.go
index f3ad878..90d342f 100644
--- a/pkg/types/content.go
+++ b/pkg/types/content.go
@@ -1,5 +1,7 @@
package types
+import "strings"
+
// MultimodalContent 多模态内容接口
// 扩展 ContentBlock 以支持图片、音频、视频等多模态输入
type MultimodalContent interface {
@@ -120,11 +122,13 @@ type ContentBlockHelper struct{}
// ExtractText 从 ContentBlocks 中提取所有文本
func (h ContentBlockHelper) ExtractText(blocks []ContentBlock) string {
var text string
+ var textSb123 strings.Builder
for _, block := range blocks {
if tb, ok := block.(*TextBlock); ok {
- text += tb.Text
+ textSb123.WriteString(tb.Text)
}
}
+ text += textSb123.String()
return text
}
diff --git a/pkg/types/events.go b/pkg/types/events.go
index 45aae11..69a5e34 100644
--- a/pkg/types/events.go
+++ b/pkg/types/events.go
@@ -152,7 +152,7 @@ type ProgressToolCancelledEvent struct {
}
func (e *ProgressToolCancelledEvent) Channel() AgentChannel { return ChannelProgress }
-func (e *ProgressToolCancelledEvent) EventType() string { return "tool:cancelled" }
+func (e *ProgressToolCancelledEvent) EventType() string { return "tool:canceled" }
// ProgressToolErrorEvent 工具执行错误事件
type ProgressToolErrorEvent struct {
diff --git a/pkg/types/store.go b/pkg/types/store.go
index ac13745..330df3a 100644
--- a/pkg/types/store.go
+++ b/pkg/types/store.go
@@ -37,7 +37,7 @@ const (
ToolCallStatusRunning ToolCallStatus = "running" // 执行中
ToolCallStatusCompleted ToolCallStatus = "completed" // 已完成
ToolCallStatusFailed ToolCallStatus = "failed" // 失败
- ToolCallStatusCancelled ToolCallStatus = "cancelled" // 已取消
+ ToolCallStatusCancelled ToolCallStatus = "canceled" // 已取消
)
// Snapshot Agent 状态快照
@@ -63,7 +63,7 @@ const (
AgentStateWaiting AgentState = "waiting" // 等待中(等待工具调用结果)
AgentStateCompleted AgentState = "completed" // 已完成
AgentStateFailed AgentState = "failed" // 失败
- AgentStateCancelled AgentState = "cancelled" // 已取消
+ AgentStateCancelled AgentState = "canceled" // 已取消
)
// AgentStatus Agent 实时状态
@@ -135,13 +135,13 @@ type PropertySchema struct {
type ToolCallState string
const (
- ToolCallStatePending ToolCallState = "pending" // 待执行
- ToolCallStateQueued ToolCallState = "queued" // 已排队
- ToolCallStateExecuting ToolCallState = "executing" // 执行中
- ToolCallStateCompleted ToolCallState = "completed" // 已完成
- ToolCallStateFailed ToolCallState = "failed" // 失败
- ToolCallStateCancelling ToolCallState = "cancelling" // 取消中
- ToolCallStateCancelled ToolCallState = "cancelled" // 已取消
+ ToolCallStatePending ToolCallState = "pending" // 待执行
+ ToolCallStateQueued ToolCallState = "queued" // 已排队
+ ToolCallStateExecuting ToolCallState = "executing" // 执行中
+ ToolCallStateCompleted ToolCallState = "completed" // 已完成
+ ToolCallStateFailed ToolCallState = "failed" // 失败
+ ToolCallStateCancelling ToolCallState = "canceling" // 取消中
+ ToolCallStateCancelled ToolCallState = "canceled" // 已取消
)
// ToolCallApproval 工具调用审批
diff --git a/pkg/types/streaming.go b/pkg/types/streaming.go
index 3757b7b..fc1252f 100644
--- a/pkg/types/streaming.go
+++ b/pkg/types/streaming.go
@@ -1,5 +1,7 @@
package types
+import "strings"
+
// StreamChunkType 流式响应块类型
type StreamChunkType string
@@ -236,9 +238,11 @@ func (acc *StreamAccumulator) ToMessage() Message {
// GetReasoningText 获取所有推理文本
func (acc *StreamAccumulator) GetReasoningText() string {
var text string
+ var textSb239 strings.Builder
for _, r := range acc.Reasoning {
- text += r.Thought + "\n"
+ textSb239.WriteString(r.Thought + "\n")
}
+ text += textSb239.String()
return text
}
diff --git a/pkg/util/json_test.go b/pkg/util/json_test.go
index fa08e97..d0c9578 100644
--- a/pkg/util/json_test.go
+++ b/pkg/util/json_test.go
@@ -16,7 +16,7 @@ func TestMarshalDeterministic_Map(t *testing.T) {
// 多次序列化,确保结果一致
var results []string
- for i := 0; i < 10; i++ {
+ for range 10 {
data, err := MarshalDeterministic(m)
if err != nil {
t.Fatalf("MarshalDeterministic failed: %v", err)
@@ -115,7 +115,7 @@ func TestMarshalDeterministic_Struct(t *testing.T) {
// 验证结果是确定性的
var results []string
- for i := 0; i < 5; i++ {
+ for range 5 {
d, _ := MarshalDeterministic(o)
results = append(results, string(d))
}
@@ -289,7 +289,7 @@ func TestStandardJSONIsNonDeterministic(t *testing.T) {
// 多次序列化,看是否产生不同结果
seen := make(map[string]bool)
- for i := 0; i < 100; i++ {
+ for range 100 {
data, _ := json.Marshal(m)
seen[string(data)] = true
}
diff --git a/pkg/vector/factory/factory.go b/pkg/vector/factory/factory.go
index 4dfe5b8..4fa41b4 100644
--- a/pkg/vector/factory/factory.go
+++ b/pkg/vector/factory/factory.go
@@ -1,6 +1,7 @@
package factory
import (
+ "errors"
"fmt"
"github.com/astercloud/aster/pkg/vector"
@@ -122,7 +123,7 @@ func createOpenAIEmbedder(config map[string]any) (vector.Embedder, error) {
}
if apiKey == "" {
- return nil, fmt.Errorf("api_key is required for OpenAI embedder")
+ return nil, errors.New("api_key is required for OpenAI embedder")
}
return vector.NewOpenAIEmbedder(baseURL, apiKey, model), nil
diff --git a/pkg/vector/memory_store.go b/pkg/vector/memory_store.go
index 6e8dd6b..dae9dd7 100644
--- a/pkg/vector/memory_store.go
+++ b/pkg/vector/memory_store.go
@@ -3,6 +3,7 @@ package vector
import (
"context"
"math"
+ "slices"
"sync"
)
@@ -39,13 +40,7 @@ func (s *MemoryStore) Upsert(_ context.Context, docs []Document) error {
ns = "default"
}
ids := s.index[ns]
- found := false
- for _, id := range ids {
- if id == d.ID {
- found = true
- break
- }
- }
+ found := slices.Contains(ids, d.ID)
if !found {
s.index[ns] = append(ids, d.ID)
}
diff --git a/pkg/vector/mock_embedder.go b/pkg/vector/mock_embedder.go
index 67849d5..36f3e9d 100644
--- a/pkg/vector/mock_embedder.go
+++ b/pkg/vector/mock_embedder.go
@@ -29,7 +29,7 @@ func (e *MockEmbedder) EmbedText(_ context.Context, texts []string) ([][]float32
result[i] = vec
continue
}
- for j := 0; j < e.Dim; j++ {
+ for j := range e.Dim {
b := t[j%len(t)]
vec[j] = float32(int(b%97)) / 100.0 // 稍微分布一下
}
diff --git a/pkg/vector/openai_embedder.go b/pkg/vector/openai_embedder.go
index e13a80d..0f2d480 100644
--- a/pkg/vector/openai_embedder.go
+++ b/pkg/vector/openai_embedder.go
@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
+ "errors"
"fmt"
"net/http"
"time"
@@ -56,7 +57,7 @@ func (e *OpenAIEmbedder) EmbedText(ctx context.Context, texts []string) ([][]flo
return [][]float32{}, nil
}
if e.APIKey == "" {
- return nil, fmt.Errorf("API key is required for OpenAIEmbedder")
+ return nil, errors.New("API key is required for OpenAIEmbedder")
}
reqBody := openAIEmbeddingRequest{
diff --git a/pkg/vector/pgvector/store.go b/pkg/vector/pgvector/store.go
index eb8744b..ad1bdf6 100644
--- a/pkg/vector/pgvector/store.go
+++ b/pkg/vector/pgvector/store.go
@@ -2,6 +2,7 @@ package pgvector
import (
"context"
+ "errors"
"fmt"
"strings"
@@ -47,17 +48,17 @@ type Store struct {
// New 创建 PgVector 向量存储。
func New(cfg *Config) (*Store, error) {
if cfg == nil {
- return nil, fmt.Errorf("config is required")
+ return nil, errors.New("config is required")
}
if cfg.DSN == "" {
- return nil, fmt.Errorf("dsn is required")
+ return nil, errors.New("dsn is required")
}
table := cfg.Table
if table == "" {
table = "agent_vectors"
}
if cfg.Dimension <= 0 {
- return nil, fmt.Errorf("dimension must be > 0")
+ return nil, errors.New("dimension must be > 0")
}
metric := strings.ToLower(cfg.Metric)
if metric == "" {
diff --git a/pkg/vector/weaviate/store.go b/pkg/vector/weaviate/store.go
index 0c78e55..b668e82 100644
--- a/pkg/vector/weaviate/store.go
+++ b/pkg/vector/weaviate/store.go
@@ -2,7 +2,9 @@ package weaviate
import (
"context"
+ "errors"
"fmt"
+ "maps"
"time"
"github.com/astercloud/aster/pkg/logging"
@@ -37,10 +39,10 @@ type Store struct {
// NewStore 创建 Weaviate 存储实例
func NewStore(cfg *Config) (*Store, error) {
if cfg.Host == "" {
- return nil, fmt.Errorf("weaviate host is required")
+ return nil, errors.New("weaviate host is required")
}
if cfg.ClassName == "" {
- return nil, fmt.Errorf("weaviate class name is required")
+ return nil, errors.New("weaviate class name is required")
}
scheme := cfg.Scheme
@@ -176,9 +178,7 @@ func (s *Store) Upsert(ctx context.Context, docs []vector.Document) error {
}
// 添加自定义元数据
- for k, v := range doc.Metadata {
- properties[k] = v
- }
+ maps.Copy(properties, doc.Metadata)
// 添加对象到批处理
obj := &models.Object{
@@ -221,7 +221,7 @@ func (s *Store) Upsert(ctx context.Context, docs []vector.Document) error {
// Query 向量检索
func (s *Store) Query(ctx context.Context, q vector.Query) ([]vector.Hit, error) {
if len(q.Vector) == 0 {
- return nil, fmt.Errorf("vector is required for query")
+ return nil, errors.New("vector is required for query")
}
startTime := time.Now()
diff --git a/pkg/workflow/actor_engine.go b/pkg/workflow/actor_engine.go
index d9784f9..42369ff 100644
--- a/pkg/workflow/actor_engine.go
+++ b/pkg/workflow/actor_engine.go
@@ -2,6 +2,7 @@ package workflow
import (
"context"
+ "errors"
"fmt"
"sync"
"time"
@@ -149,7 +150,7 @@ func (e *ActorEngine) SpawnAgent(ctx context.Context, nodeID string, agentConfig
// 设置属性
props := &actor.Props{
- Name: fmt.Sprintf("agent-%s", nodeID),
+ Name: "agent-" + nodeID,
MailboxSize: 100,
SupervisorStrategy: e.actorConfig.SupervisorStrategy,
}
@@ -319,7 +320,7 @@ func (c *CoordinatorActor) executeWorkflowAsync(execution *WorkflowExecution, st
// 查找开始节点
startNodes := c.findStartNodes(execution.Definition)
if len(startNodes) == 0 {
- state.errorCh <- fmt.Errorf("no start node found")
+ state.errorCh <- errors.New("no start node found")
return
}
@@ -379,7 +380,7 @@ func (c *CoordinatorActor) executeNodes(execution *WorkflowExecution, state *wor
// executeTaskNodeWithActor 使用 Actor 执行任务节点
func (c *CoordinatorActor) executeTaskNodeWithActor(execution *WorkflowExecution, state *workflowState, node *NodeDef) error {
if node.Agent == nil {
- return fmt.Errorf("task node requires agent configuration")
+ return errors.New("task node requires agent configuration")
}
// 获取或创建 Agent PID
@@ -437,7 +438,7 @@ func (c *CoordinatorActor) executeTaskNodeWithActor(execution *WorkflowExecution
// executeParallelNodeWithActors 使用 Actor 执行并行节点
func (c *CoordinatorActor) executeParallelNodeWithActors(execution *WorkflowExecution, state *workflowState, node *NodeDef) error {
if node.Parallel == nil || len(node.Parallel.Branches) == 0 {
- return fmt.Errorf("parallel node requires branches")
+ return errors.New("parallel node requires branches")
}
var wg sync.WaitGroup
diff --git a/pkg/workflow/conditional.go b/pkg/workflow/conditional.go
index c43798b..ab8d456 100644
--- a/pkg/workflow/conditional.go
+++ b/pkg/workflow/conditional.go
@@ -3,6 +3,7 @@ package workflow
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"strings"
"sync"
@@ -43,11 +44,11 @@ type ConditionalConfig struct {
// NewConditionalAgent 创建条件Agent
func NewConditionalAgent(config ConditionalConfig) (*ConditionalAgent, error) {
if config.Name == "" {
- return nil, fmt.Errorf("conditional agent name is required")
+ return nil, errors.New("conditional agent name is required")
}
if len(config.Conditions) == 0 {
- return nil, fmt.Errorf("at least one condition is required")
+ return nil, errors.New("at least one condition is required")
}
// 按优先级排序条件
@@ -55,7 +56,7 @@ func NewConditionalAgent(config ConditionalConfig) (*ConditionalAgent, error) {
copy(conditions, config.Conditions)
// 简单排序(优先级高的在前)
- for i := range len(conditions) {
+ for i := range conditions {
for j := i + 1; j < len(conditions); j++ {
if conditions[i].Priority < conditions[j].Priority {
conditions[i], conditions[j] = conditions[j], conditions[i]
@@ -93,7 +94,7 @@ func (c *ConditionalAgent) Execute(ctx context.Context, message string) *stream.
if selectedBranch == nil {
// 使用默认分支
if c.defaultBranch == nil {
- writer.Send(nil, fmt.Errorf("no condition matched and no default branch provided"))
+ writer.Send(nil, errors.New("no condition matched and no default branch provided"))
return
}
@@ -104,7 +105,7 @@ func (c *ConditionalAgent) Execute(ctx context.Context, message string) *stream.
Author: "system",
Content: types.Message{
Role: types.MessageRoleAssistant,
- Content: fmt.Sprintf("No condition matched, using default branch: %s", c.defaultBranch.ID),
+ Content: "No condition matched, using default branch: " + c.defaultBranch.ID,
},
Metadata: map[string]any{
"branch_type": "default",
@@ -231,11 +232,11 @@ const (
// NewParallelConditionalAgent 创建并行条件Agent
func NewParallelConditionalAgent(config ParallelConditionalConfig) (*ParallelConditionalAgent, error) {
if config.Name == "" {
- return nil, fmt.Errorf("parallel conditional agent name is required")
+ return nil, errors.New("parallel conditional agent name is required")
}
if len(config.Conditions) == 0 {
- return nil, fmt.Errorf("at least one condition is required")
+ return nil, errors.New("at least one condition is required")
}
maxParallel := config.MaxParallel
@@ -283,7 +284,7 @@ func (p *ParallelConditionalAgent) Execute(ctx context.Context, message string)
if len(results) == 0 {
// 没有匹配的条件,使用默认分支
if p.defaultAgent == nil {
- writer.Send(nil, fmt.Errorf("no condition matched and no default branch provided"))
+ writer.Send(nil, errors.New("no condition matched and no default branch provided"))
return
}
@@ -294,7 +295,7 @@ func (p *ParallelConditionalAgent) Execute(ctx context.Context, message string)
Author: "system",
Content: types.Message{
Role: types.MessageRoleAssistant,
- Content: fmt.Sprintf("No conditions matched, using default branch: %s", p.defaultAgent.ID),
+ Content: "No conditions matched, using default branch: " + p.defaultAgent.ID,
},
Metadata: map[string]any{
"branch_type": "default",
@@ -491,11 +492,11 @@ type SwitchConfig struct {
// NewSwitchAgent 创建Switch Agent
func NewSwitchAgent(config SwitchConfig) (*SwitchAgent, error) {
if config.Name == "" {
- return nil, fmt.Errorf("switch agent name is required")
+ return nil, errors.New("switch agent name is required")
}
if config.Variable == "" {
- return nil, fmt.Errorf("switch variable is required")
+ return nil, errors.New("switch variable is required")
}
return &SwitchAgent{
@@ -619,8 +620,8 @@ func (s *SwitchAgent) matchCaseValue(switchValue, caseValue string) bool {
// 范围匹配 (caseValue可以是 "value1,value2,value3")
if strings.Contains(caseValue, ",") {
- values := strings.Split(caseValue, ",")
- for _, v := range values {
+ values := strings.SplitSeq(caseValue, ",")
+ for v := range values {
if strings.EqualFold(strings.TrimSpace(v), switchValue) {
return true
}
@@ -696,11 +697,11 @@ type MultiLevelConditionalConfig struct {
// NewMultiLevelConditionalAgent 创建多级条件Agent
func NewMultiLevelConditionalAgent(config MultiLevelConditionalConfig) (*MultiLevelConditionalAgent, error) {
if config.Name == "" {
- return nil, fmt.Errorf("multi-level conditional agent name is required")
+ return nil, errors.New("multi-level conditional agent name is required")
}
if len(config.Levels) == 0 {
- return nil, fmt.Errorf("at least one level is required")
+ return nil, errors.New("at least one level is required")
}
maxDepth := config.MaxDepth
diff --git a/pkg/workflow/constraint.go b/pkg/workflow/constraint.go
index 82b9bcc..b5fa935 100644
--- a/pkg/workflow/constraint.go
+++ b/pkg/workflow/constraint.go
@@ -1,6 +1,7 @@
package workflow
import (
+ "errors"
"fmt"
"regexp"
"strconv"
@@ -10,7 +11,7 @@ import (
// Constraint 约束接口
type Constraint interface {
Name() string
- Validate(value interface{}) (bool, string) // 返回 (是否满足, 错误信息)
+ Validate(value any) (bool, string) // 返回 (是否满足, 错误信息)
}
// ConstraintSet 约束集合
@@ -26,19 +27,19 @@ func NewConstraintSet() *ConstraintSet {
func (cs *ConstraintSet) Add(constraint Constraint) error {
if constraint == nil {
- return fmt.Errorf("constraint cannot be nil")
+ return errors.New("constraint cannot be nil")
}
cs.constraints[constraint.Name()] = constraint
return nil
}
-func (cs *ConstraintSet) Validate(values map[string]interface{}) (bool, []string) {
+func (cs *ConstraintSet) Validate(values map[string]any) (bool, []string) {
violations := make([]string, 0)
for name, constraint := range cs.constraints {
value, exists := values[name]
if !exists {
- violations = append(violations, fmt.Sprintf("required value missing: %s", name))
+ violations = append(violations, "required value missing: "+name)
continue
}
@@ -77,9 +78,9 @@ func NewRangeConstraint(name string, min, max int64) *RangeConstraint {
func (c *RangeConstraint) Name() string { return c.name }
-func (c *RangeConstraint) Validate(value interface{}) (bool, string) {
+func (c *RangeConstraint) Validate(value any) (bool, string) {
if value == nil {
- return false, fmt.Sprintf("%s: value is nil", c.name)
+ return false, c.name + ": value is nil"
}
var num int64
@@ -97,7 +98,7 @@ func (c *RangeConstraint) Validate(value interface{}) (bool, string) {
var err error
num, err = strconv.ParseInt(v, 10, 64)
if err != nil {
- return false, fmt.Sprintf("%s: invalid number format", c.name)
+ return false, c.name + ": invalid number format"
}
default:
return false, fmt.Sprintf("%s: unsupported type %T", c.name, value)
@@ -128,14 +129,14 @@ func NewStringLengthConstraint(name string, minLen, maxLen int) *StringLengthCon
func (c *StringLengthConstraint) Name() string { return c.name }
-func (c *StringLengthConstraint) Validate(value interface{}) (bool, string) {
+func (c *StringLengthConstraint) Validate(value any) (bool, string) {
if value == nil {
- return false, fmt.Sprintf("%s: value is nil", c.name)
+ return false, c.name + ": value is nil"
}
str, ok := value.(string)
if !ok {
- return false, fmt.Sprintf("%s: value is not string", c.name)
+ return false, c.name + ": value is not string"
}
length := len(str)
@@ -169,14 +170,14 @@ func NewPatternConstraint(name string, pattern string) (*PatternConstraint, erro
func (c *PatternConstraint) Name() string { return c.name }
-func (c *PatternConstraint) Validate(value interface{}) (bool, string) {
+func (c *PatternConstraint) Validate(value any) (bool, string) {
if value == nil {
- return false, fmt.Sprintf("%s: value is nil", c.name)
+ return false, c.name + ": value is nil"
}
str, ok := value.(string)
if !ok {
- return false, fmt.Sprintf("%s: value is not string", c.name)
+ return false, c.name + ": value is not string"
}
if !c.regex.MatchString(str) {
@@ -208,14 +209,14 @@ func (c *ChoiceConstraint) WithCaseInsensitive() *ChoiceConstraint {
func (c *ChoiceConstraint) Name() string { return c.name }
-func (c *ChoiceConstraint) Validate(value interface{}) (bool, string) {
+func (c *ChoiceConstraint) Validate(value any) (bool, string) {
if value == nil {
- return false, fmt.Sprintf("%s: value is nil", c.name)
+ return false, c.name + ": value is nil"
}
str, ok := value.(string)
if !ok {
- return false, fmt.Sprintf("%s: value is not string", c.name)
+ return false, c.name + ": value is not string"
}
compareStr := str
@@ -249,19 +250,19 @@ func NewNotEmptyConstraint(name string) *NotEmptyConstraint {
func (c *NotEmptyConstraint) Name() string { return c.name }
-func (c *NotEmptyConstraint) Validate(value interface{}) (bool, string) {
+func (c *NotEmptyConstraint) Validate(value any) (bool, string) {
if value == nil {
- return false, fmt.Sprintf("%s: value is nil", c.name)
+ return false, c.name + ": value is nil"
}
switch v := value.(type) {
case string:
if len(strings.TrimSpace(v)) == 0 {
- return false, fmt.Sprintf("%s: value is empty", c.name)
+ return false, c.name + ": value is empty"
}
- case []interface{}:
+ case []any:
if len(v) == 0 {
- return false, fmt.Sprintf("%s: list is empty", c.name)
+ return false, c.name + ": list is empty"
}
}
@@ -272,10 +273,10 @@ func (c *NotEmptyConstraint) Validate(value interface{}) (bool, string) {
type CustomConstraint struct {
name string
- validateFn func(value interface{}) (bool, string)
+ validateFn func(value any) (bool, string)
}
-func NewCustomConstraint(name string, validateFn func(value interface{}) (bool, string)) *CustomConstraint {
+func NewCustomConstraint(name string, validateFn func(value any) (bool, string)) *CustomConstraint {
return &CustomConstraint{
name: name,
validateFn: validateFn,
@@ -284,9 +285,9 @@ func NewCustomConstraint(name string, validateFn func(value interface{}) (bool,
func (c *CustomConstraint) Name() string { return c.name }
-func (c *CustomConstraint) Validate(value interface{}) (bool, string) {
+func (c *CustomConstraint) Validate(value any) (bool, string) {
if c.validateFn == nil {
- return false, fmt.Sprintf("%s: validation function not defined", c.name)
+ return false, c.name + ": validation function not defined"
}
return c.validateFn(value)
}
diff --git a/pkg/workflow/dsl.go b/pkg/workflow/dsl.go
index e9adf2c..33bd564 100644
--- a/pkg/workflow/dsl.go
+++ b/pkg/workflow/dsl.go
@@ -3,6 +3,7 @@ package workflow
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"regexp"
"strings"
@@ -269,7 +270,7 @@ const (
StatusPaused WorkflowStatus = "paused" // 暂停
StatusCompleted WorkflowStatus = "completed" // 完成
StatusFailed WorkflowStatus = "failed" // 失败
- StatusCancelled WorkflowStatus = "cancelled" // 取消
+ StatusCancelled WorkflowStatus = "canceled" // 取消
StatusTimeout WorkflowStatus = "timeout" // 超时
)
@@ -491,7 +492,7 @@ func ParseFromJSON(data []byte) (*WorkflowDefinition, error) {
// ParseFromYAML 从YAML解析工作流定义
func ParseFromYAML(data []byte) (*WorkflowDefinition, error) {
// TODO: 实现YAML解析
- return nil, fmt.Errorf("YAML parsing not yet implemented")
+ return nil, errors.New("YAML parsing not yet implemented")
}
// ToJSON 转换为JSON
@@ -502,21 +503,21 @@ func (w *WorkflowDefinition) ToJSON() ([]byte, error) {
// ToYAML 转换为YAML
func (w *WorkflowDefinition) ToYAML() ([]byte, error) {
// TODO: 实现YAML转换
- return nil, fmt.Errorf("YAML conversion not yet implemented")
+ return nil, errors.New("YAML conversion not yet implemented")
}
// validateWorkflowDefinition 验证工作流定义
func validateWorkflowDefinition(def *WorkflowDefinition) error {
if def.ID == "" {
- return fmt.Errorf("workflow ID is required")
+ return errors.New("workflow ID is required")
}
if def.Name == "" {
- return fmt.Errorf("workflow name is required")
+ return errors.New("workflow name is required")
}
if len(def.Nodes) == 0 {
- return fmt.Errorf("workflow must have at least one node")
+ return errors.New("workflow must have at least one node")
}
// 检查是否有开始和结束节点
@@ -526,7 +527,7 @@ func validateWorkflowDefinition(def *WorkflowDefinition) error {
for _, node := range def.Nodes {
if node.ID == "" {
- return fmt.Errorf("node ID is required")
+ return errors.New("node ID is required")
}
if nodeIds[node.ID] {
@@ -543,17 +544,17 @@ func validateWorkflowDefinition(def *WorkflowDefinition) error {
}
if !hasStart {
- return fmt.Errorf("workflow must have a start node")
+ return errors.New("workflow must have a start node")
}
if !hasEnd {
- return fmt.Errorf("workflow must have an end node")
+ return errors.New("workflow must have an end node")
}
// 验证边的引用
for _, edge := range def.Edges {
if edge.From == "" || edge.To == "" {
- return fmt.Errorf("edge source and target are required")
+ return errors.New("edge source and target are required")
}
if !nodeIds[edge.From] {
@@ -589,8 +590,8 @@ func (e *ExpressionEvaluator) EvaluateBool(expression string) (bool, error) {
// 处理逻辑操作
if strings.Contains(expression, "&&") {
- parts := strings.Split(expression, "&&")
- for _, part := range parts {
+ parts := strings.SplitSeq(expression, "&&")
+ for part := range parts {
if result, err := e.EvaluateBool(strings.TrimSpace(part)); err != nil {
return false, err
} else if !result {
@@ -601,8 +602,8 @@ func (e *ExpressionEvaluator) EvaluateBool(expression string) (bool, error) {
}
if strings.Contains(expression, "||") {
- parts := strings.Split(expression, "||")
- for _, part := range parts {
+ parts := strings.SplitSeq(expression, "||")
+ for part := range parts {
if result, err := e.EvaluateBool(strings.TrimSpace(part)); err != nil {
return false, err
} else if result {
@@ -657,12 +658,12 @@ func (e *ExpressionEvaluator) evaluateComparison(expression string) (bool, error
func (e *ExpressionEvaluator) compareNumbers(aVal, bVal any, compare func(float64, float64) bool) (bool, error) {
a, err := e.toFloat64(aVal)
if err != nil {
- return false, fmt.Errorf("cannot convert left operand to number: %v", err)
+ return false, fmt.Errorf("cannot convert left operand to number: %w", err)
}
b, err := e.toFloat64(bVal)
if err != nil {
- return false, fmt.Errorf("cannot convert right operand to number: %v", err)
+ return false, fmt.Errorf("cannot convert right operand to number: %w", err)
}
return compare(a, b), nil
diff --git a/pkg/workflow/engine.go b/pkg/workflow/engine.go
index daa9d56..5641697 100644
--- a/pkg/workflow/engine.go
+++ b/pkg/workflow/engine.go
@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
+ "maps"
"strings"
"sync"
"time"
@@ -480,7 +481,7 @@ func (e *Engine) executeWorkflow(execution *WorkflowExecution) {
// 查找开始节点
startNodes := e.findStartNodes(execution.Definition)
if len(startNodes) == 0 {
- e.markExecutionFailed(execution, fmt.Errorf("no start node found"))
+ e.markExecutionFailed(execution, errors.New("no start node found"))
return
}
@@ -611,9 +612,7 @@ func (e *Engine) executeStartNode(execution *WorkflowExecution, node *NodeDef, r
// executeEndNode 执行结束节点
func (e *Engine) executeEndNode(execution *WorkflowExecution, node *NodeDef, result *NodeResult) error {
// 收集工作流输出
- for key, value := range execution.Context.Variables {
- execution.Context.Outputs[key] = value
- }
+ maps.Copy(execution.Context.Outputs, execution.Context.Variables)
result.Outputs["completed_at"] = time.Now()
result.Outputs["outputs"] = execution.Context.Outputs
@@ -623,7 +622,7 @@ func (e *Engine) executeEndNode(execution *WorkflowExecution, node *NodeDef, res
// executeTaskNode 执行任务节点
func (e *Engine) executeTaskNode(execution *WorkflowExecution, node *NodeDef, result *NodeResult) error {
if node.Agent == nil {
- return fmt.Errorf("task node requires agent configuration")
+ return errors.New("task node requires agent configuration")
}
// 创建Agent
@@ -669,7 +668,7 @@ func (e *Engine) executeTaskNode(execution *WorkflowExecution, node *NodeDef, re
// executeConditionNode 执行条件节点
func (e *Engine) executeConditionNode(execution *WorkflowExecution, node *NodeDef, result *NodeResult) error {
if node.Condition == nil {
- return fmt.Errorf("condition node requires condition configuration")
+ return errors.New("condition node requires condition configuration")
}
// 评估条件
@@ -685,7 +684,7 @@ func (e *Engine) executeConditionNode(execution *WorkflowExecution, node *NodeDe
// executeLoopNode 执行循环节点
func (e *Engine) executeLoopNode(execution *WorkflowExecution, node *NodeDef, result *NodeResult) error {
if node.Loop == nil {
- return fmt.Errorf("loop node requires loop configuration")
+ return errors.New("loop node requires loop configuration")
}
// TODO: 实现循环逻辑
@@ -696,7 +695,7 @@ func (e *Engine) executeLoopNode(execution *WorkflowExecution, node *NodeDef, re
// executeParallelNode 执行并行节点
func (e *Engine) executeParallelNode(execution *WorkflowExecution, node *NodeDef, result *NodeResult) error {
if node.Parallel == nil {
- return fmt.Errorf("parallel node requires parallel configuration")
+ return errors.New("parallel node requires parallel configuration")
}
// TODO: 实现并行逻辑
@@ -812,9 +811,7 @@ func (e *Engine) processAgentEvent(event *session.Event, outputs map[string]any)
}
if event.Metadata != nil {
- for key, value := range event.Metadata {
- outputs[key] = value
- }
+ maps.Copy(outputs, event.Metadata)
}
return outputs
diff --git a/pkg/workflow/gate.go b/pkg/workflow/gate.go
index 126e50c..b888b49 100644
--- a/pkg/workflow/gate.go
+++ b/pkg/workflow/gate.go
@@ -2,6 +2,7 @@ package workflow
import (
"context"
+ "errors"
"fmt"
"time"
)
@@ -43,8 +44,8 @@ type Gate interface {
type GateInput struct {
StepOutput *StepOutput
PreviousOutputs map[string]*StepOutput
- Project interface{} // 项目上下文
- Constraints interface{} // 约束条件
+ Project any // 项目上下文
+ Constraints any // 约束条件
SessionState map[string]any
Metadata map[string]any
}
@@ -445,7 +446,7 @@ func NewGateRegistry() *GateRegistry {
func (gr *GateRegistry) Register(gate Gate) error {
if gate == nil {
- return fmt.Errorf("gate cannot be nil")
+ return errors.New("gate cannot be nil")
}
gr.gates[gate.Name()] = gate
return nil
diff --git a/pkg/workflow/parallel.go b/pkg/workflow/parallel.go
index 10c22de..e3b35b4 100644
--- a/pkg/workflow/parallel.go
+++ b/pkg/workflow/parallel.go
@@ -247,7 +247,7 @@ func (p *ParallelWorkFlowAgent) mergeResults(results []*ParallelBranchResult) ma
// 合并输出
if result.Output != nil {
- merged[fmt.Sprintf("branch_%s", result.Branch.ID)] = result.Output
+ merged["branch_"+result.Branch.ID] = result.Output
}
}
@@ -305,7 +305,7 @@ const (
AsyncBranchStatusRunning AsyncBranchStatus = "running"
AsyncBranchStatusCompleted AsyncBranchStatus = "completed"
AsyncBranchStatusFailed AsyncBranchStatus = "failed"
- AsyncBranchStatusCancelled AsyncBranchStatus = "cancelled"
+ AsyncBranchStatusCancelled AsyncBranchStatus = "canceled"
)
// AsyncMetrics 异步指标
diff --git a/pkg/workflow/project_manager.go b/pkg/workflow/project_manager.go
index a544c60..36fa31c 100644
--- a/pkg/workflow/project_manager.go
+++ b/pkg/workflow/project_manager.go
@@ -2,6 +2,7 @@ package workflow
import (
"context"
+ "errors"
"fmt"
"time"
)
@@ -12,11 +13,11 @@ type ProjectManager interface {
CreateProject(ctx context.Context, spec *ProjectSpec) (string, error)
// 读取项目
- LoadProject(ctx context.Context, projectID string) (interface{}, error)
+ LoadProject(ctx context.Context, projectID string) (any, error)
GetProjectMetadata(ctx context.Context, projectID string) (*ProjectMetadata, error)
// 更新项目
- SaveProject(ctx context.Context, projectID string, data interface{}) error
+ SaveProject(ctx context.Context, projectID string, data any) error
UpdateProjectMetadata(ctx context.Context, projectID string, metadata *ProjectMetadata) error
// 删除项目
@@ -37,26 +38,26 @@ type ProjectManager interface {
// ProjectSpec 项目规范
type ProjectSpec struct {
- Name string `json:"name"`
- Description string `json:"description"`
- Type string `json:"type"`
- Metadata map[string]interface{} `json:"metadata,omitempty"`
- Tags []string `json:"tags,omitempty"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Type string `json:"type"`
+ Metadata map[string]any `json:"metadata,omitempty"`
+ Tags []string `json:"tags,omitempty"`
}
// ProjectMetadata 项目元数据
type ProjectMetadata struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Description string `json:"description"`
- Type string `json:"type"`
- Status string `json:"status"` // draft, in_progress, completed, archived
- CreatedAt int64 `json:"created_at"`
- UpdatedAt int64 `json:"updated_at"`
- CreatedBy string `json:"created_by,omitempty"`
- Tags []string `json:"tags,omitempty"`
- Metadata map[string]interface{} `json:"metadata,omitempty"`
- Size int64 `json:"size,omitempty"`
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Type string `json:"type"`
+ Status string `json:"status"` // draft, in_progress, completed, archived
+ CreatedAt int64 `json:"created_at"`
+ UpdatedAt int64 `json:"updated_at"`
+ CreatedBy string `json:"created_by,omitempty"`
+ Tags []string `json:"tags,omitempty"`
+ Metadata map[string]any `json:"metadata,omitempty"`
+ Size int64 `json:"size,omitempty"`
}
// ProjectFilter 项目过滤器
@@ -70,26 +71,26 @@ type ProjectFilter struct {
// ProjectSnapshot 项目快照
type ProjectSnapshot struct {
- ID string `json:"id"`
- ProjectID string `json:"project_id"`
- Description string `json:"description"`
- CreatedAt int64 `json:"created_at"`
- CreatedBy string `json:"created_by,omitempty"`
- Metadata interface{} `json:"metadata,omitempty"`
+ ID string `json:"id"`
+ ProjectID string `json:"project_id"`
+ Description string `json:"description"`
+ CreatedAt int64 `json:"created_at"`
+ CreatedBy string `json:"created_by,omitempty"`
+ Metadata any `json:"metadata,omitempty"`
}
// ===== 内存实现 =====
// InMemoryProjectManager 内存项目管理器实现
type InMemoryProjectManager struct {
- projects map[string]interface{}
+ projects map[string]any
metadata map[string]*ProjectMetadata
snapshots map[string][]*ProjectSnapshot
}
func NewInMemoryProjectManager() *InMemoryProjectManager {
return &InMemoryProjectManager{
- projects: make(map[string]interface{}),
+ projects: make(map[string]any),
metadata: make(map[string]*ProjectMetadata),
snapshots: make(map[string][]*ProjectSnapshot),
}
@@ -97,11 +98,11 @@ func NewInMemoryProjectManager() *InMemoryProjectManager {
func (pm *InMemoryProjectManager) CreateProject(ctx context.Context, spec *ProjectSpec) (string, error) {
if spec == nil {
- return "", fmt.Errorf("project spec cannot be nil")
+ return "", errors.New("project spec cannot be nil")
}
if spec.Name == "" {
- return "", fmt.Errorf("project name is required")
+ return "", errors.New("project name is required")
}
projectID := generateProjectID()
@@ -119,13 +120,13 @@ func (pm *InMemoryProjectManager) CreateProject(ctx context.Context, spec *Proje
}
pm.metadata[projectID] = metadata
- pm.projects[projectID] = make(map[string]interface{})
+ pm.projects[projectID] = make(map[string]any)
pm.snapshots[projectID] = make([]*ProjectSnapshot, 0)
return projectID, nil
}
-func (pm *InMemoryProjectManager) LoadProject(ctx context.Context, projectID string) (interface{}, error) {
+func (pm *InMemoryProjectManager) LoadProject(ctx context.Context, projectID string) (any, error) {
data, exists := pm.projects[projectID]
if !exists {
return nil, fmt.Errorf("project not found: %s", projectID)
@@ -141,7 +142,7 @@ func (pm *InMemoryProjectManager) GetProjectMetadata(ctx context.Context, projec
return metadata, nil
}
-func (pm *InMemoryProjectManager) SaveProject(ctx context.Context, projectID string, data interface{}) error {
+func (pm *InMemoryProjectManager) SaveProject(ctx context.Context, projectID string, data any) error {
if _, exists := pm.projects[projectID]; !exists {
return fmt.Errorf("project not found: %s", projectID)
}
@@ -283,7 +284,7 @@ func generateSnapshotID() string {
}
func getTimestamp() int64 {
- return int64(time.Now().Unix())
+ return time.Now().Unix()
}
func hasAnyTag(tags, filterTags []string) bool {
diff --git a/pkg/workflow/state_machine.go b/pkg/workflow/state_machine.go
index 91a9b2c..f43fa3d 100644
--- a/pkg/workflow/state_machine.go
+++ b/pkg/workflow/state_machine.go
@@ -2,7 +2,9 @@ package workflow
import (
"context"
+ "errors"
"fmt"
+ "maps"
"sync"
"time"
)
@@ -23,8 +25,8 @@ type StateMachine interface {
GetLastTransition() *StateTransition
// 持久化
- SaveState(ctx context.Context, stateData map[string]interface{}) error
- LoadState(ctx context.Context) (map[string]interface{}, error)
+ SaveState(ctx context.Context, stateData map[string]any) error
+ LoadState(ctx context.Context) (map[string]any, error)
}
// State 状态接口
@@ -79,7 +81,7 @@ type StateTransition struct {
To string
Timestamp time.Time
Duration float64
- Metadata map[string]interface{}
+ Metadata map[string]any
}
// StateMachineImpl 状态机实现
@@ -89,15 +91,15 @@ type StateMachineImpl struct {
transitions map[string][]*Transition
currentState string
history []*StateTransition
- stateData map[string]interface{}
+ stateData map[string]any
mu sync.RWMutex
persistentStore StatePersistentStore
}
// StatePersistentStore 状态持久化接口
type StatePersistentStore interface {
- Save(ctx context.Context, stateID string, data map[string]interface{}) error
- Load(ctx context.Context, stateID string) (map[string]interface{}, error)
+ Save(ctx context.Context, stateID string, data map[string]any) error
+ Load(ctx context.Context, stateID string) (map[string]any, error)
}
// NewStateMachine 创建状态机
@@ -108,7 +110,7 @@ func NewStateMachine(name string, initialState string, store StatePersistentStor
transitions: make(map[string][]*Transition),
currentState: initialState,
history: make([]*StateTransition, 0),
- stateData: make(map[string]interface{}),
+ stateData: make(map[string]any),
persistentStore: store,
}
}
@@ -238,7 +240,7 @@ func (sm *StateMachineImpl) performTransition(ctx context.Context, from, to stri
To: to,
Timestamp: startTime,
Duration: time.Since(startTime).Seconds(),
- Metadata: make(map[string]interface{}),
+ Metadata: make(map[string]any),
}
sm.history = append(sm.history, transition)
@@ -271,22 +273,18 @@ func (sm *StateMachineImpl) GetLastTransition() *StateTransition {
}
// SaveState 保存状态
-func (sm *StateMachineImpl) SaveState(ctx context.Context, stateData map[string]interface{}) error {
+func (sm *StateMachineImpl) SaveState(ctx context.Context, stateData map[string]any) error {
sm.mu.Lock()
defer sm.mu.Unlock()
if sm.persistentStore == nil {
- return fmt.Errorf("persistent store not configured")
+ return errors.New("persistent store not configured")
}
// 合并当前状态数据
- data := make(map[string]interface{})
- for k, v := range sm.stateData {
- data[k] = v
- }
- for k, v := range stateData {
- data[k] = v
- }
+ data := make(map[string]any)
+ maps.Copy(data, sm.stateData)
+ maps.Copy(data, stateData)
data["_current_state"] = sm.currentState
data["_state_machine_name"] = sm.name
@@ -295,12 +293,12 @@ func (sm *StateMachineImpl) SaveState(ctx context.Context, stateData map[string]
}
// LoadState 加载状态
-func (sm *StateMachineImpl) LoadState(ctx context.Context) (map[string]interface{}, error) {
+func (sm *StateMachineImpl) LoadState(ctx context.Context) (map[string]any, error) {
sm.mu.Lock()
defer sm.mu.Unlock()
if sm.persistentStore == nil {
- return nil, fmt.Errorf("persistent store not configured")
+ return nil, errors.New("persistent store not configured")
}
data, err := sm.persistentStore.Load(ctx, sm.name)
diff --git a/pkg/workflow/step.go b/pkg/workflow/step.go
index 95e3ee5..58371d0 100644
--- a/pkg/workflow/step.go
+++ b/pkg/workflow/step.go
@@ -411,7 +411,7 @@ func (s *LoopStep) Execute(ctx context.Context, input *StepInput) *stream.Reader
var iterations []*StepOutput
var lastOutput *StepOutput
- for i := 0; i < s.maxIterations; i++ {
+ for i := range s.maxIterations {
loopInput := &StepInput{
Input: input.Input,
PreviousStepOutputs: input.PreviousStepOutputs,
diff --git a/pkg/workflow/types.go b/pkg/workflow/types.go
index eb23126..b48cdcc 100644
--- a/pkg/workflow/types.go
+++ b/pkg/workflow/types.go
@@ -131,7 +131,7 @@ const (
RunStatusRunning RunStatus = "running"
RunStatusCompleted RunStatus = "completed"
RunStatusFailed RunStatus = "failed"
- RunStatusCancelled RunStatus = "cancelled"
+ RunStatusCancelled RunStatus = "canceled"
)
// WorkflowSession Workflow 会话
@@ -172,7 +172,7 @@ const (
EventStepSkipped WorkflowEventType = "step_skipped"
EventWorkflowCompleted WorkflowEventType = "workflow_completed"
EventWorkflowFailed WorkflowEventType = "workflow_failed"
- EventWorkflowCancelled WorkflowEventType = "workflow_cancelled"
+ EventWorkflowCancelled WorkflowEventType = "workflow_canceled"
)
// RunEvent Workflow 运行事件
diff --git a/pkg/workflow/visualization.go b/pkg/workflow/visualization.go
index 81cf07b..2416954 100644
--- a/pkg/workflow/visualization.go
+++ b/pkg/workflow/visualization.go
@@ -66,7 +66,7 @@ func (v *WorkflowVisualizer) GenerateMermaid() string {
for _, edge := range v.workflow.Edges {
label := ""
if edge.Label != "" {
- label = fmt.Sprintf(" | %s", edge.Label)
+ label = " | " + edge.Label
}
builder.WriteString(fmt.Sprintf(" %s --> %s%s\n",
edge.From, edge.To, label))
@@ -88,7 +88,7 @@ func (v *WorkflowVisualizer) GenerateASCII() string {
copy(sortedNodes, v.workflow.Nodes)
// 简单的Y坐标排序
- for i := 0; i < len(sortedNodes)-1; i++ {
+ for i := range len(sortedNodes) - 1 {
for j := i + 1; j < len(sortedNodes); j++ {
if sortedNodes[i].Position.Y > sortedNodes[j].Position.Y {
sortedNodes[i], sortedNodes[j] = sortedNodes[j], sortedNodes[i]
diff --git a/pkg/workflow/workflow.go b/pkg/workflow/workflow.go
index ac96fb9..774bcd2 100644
--- a/pkg/workflow/workflow.go
+++ b/pkg/workflow/workflow.go
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
+ "maps"
"time"
"github.com/astercloud/aster/pkg/store"
@@ -147,10 +148,10 @@ func (w *Workflow) WithSession(sessionID string) *Workflow {
// Validate 验证配置
func (w *Workflow) Validate() error {
if w.Name == "" {
- return fmt.Errorf("workflow name is required")
+ return errors.New("workflow name is required")
}
if len(w.Steps) == 0 {
- return fmt.Errorf("workflow must have at least one step")
+ return errors.New("workflow must have at least one step")
}
stepNames := make(map[string]bool)
@@ -205,7 +206,7 @@ func (w *Workflow) GetSession(sessionID string) (*WorkflowSession, error) {
}
if sessionID == "" {
- return nil, fmt.Errorf("no session_id provided")
+ return nil, errors.New("no session_id provided")
}
// 从缓存获取
@@ -232,9 +233,7 @@ func (w *Workflow) CreateSession(sessionID, userID string) *WorkflowSession {
// 初始化会话状态
if w.SessionState != nil {
- for k, v := range w.SessionState {
- session.State[k] = v
- }
+ maps.Copy(session.State, w.SessionState)
}
// 缓存会话
@@ -270,7 +269,7 @@ func (w *Workflow) GetOrCreateSession(sessionID, userID string) *WorkflowSession
// GetRun 获取运行记录
func (w *Workflow) GetRun(runID string) (*WorkflowRun, error) {
if w.workflowSession == nil {
- return nil, fmt.Errorf("no active session")
+ return nil, errors.New("no active session")
}
for _, run := range w.workflowSession.History {
@@ -285,11 +284,11 @@ func (w *Workflow) GetRun(runID string) (*WorkflowRun, error) {
// GetLastRun 获取最后一次运行
func (w *Workflow) GetLastRun() (*WorkflowRun, error) {
if w.workflowSession == nil {
- return nil, fmt.Errorf("no active session")
+ return nil, errors.New("no active session")
}
if len(w.workflowSession.History) == 0 {
- return nil, fmt.Errorf("no runs found")
+ return nil, errors.New("no runs found")
}
return w.workflowSession.History[len(w.workflowSession.History)-1], nil
@@ -358,14 +357,10 @@ func (w *Workflow) Execute(ctx context.Context, input *WorkflowInput) *stream.Re
// 合并会话状态
sessionState := make(map[string]any)
if session.State != nil {
- for k, v := range session.State {
- sessionState[k] = v
- }
+ maps.Copy(sessionState, session.State)
}
if input.SessionState != nil {
- for k, v := range input.SessionState {
- sessionState[k] = v
- }
+ maps.Copy(sessionState, input.SessionState)
}
stepOutputs := make(map[string]*StepOutput)
diff --git a/pkg/workflow/workflow_agent.go b/pkg/workflow/workflow_agent.go
index f8f31d9..68c4ea4 100644
--- a/pkg/workflow/workflow_agent.go
+++ b/pkg/workflow/workflow_agent.go
@@ -61,7 +61,7 @@ func (wa *WorkflowAgent) CreateWorkflowTool(
) WorkflowToolFunc {
return func(ctx context.Context, query string) (any, error) {
if wa.workflow == nil {
- return nil, fmt.Errorf("no workflow attached to agent")
+ return nil, errors.New("no workflow attached to agent")
}
workflowInput := &WorkflowInput{
@@ -170,7 +170,7 @@ func (wa *WorkflowAgent) GetWorkflowHistory() []WorkflowHistoryItem {
numRuns := min(len(runs), wa.NumHistoryRuns)
history := make([]WorkflowHistoryItem, numRuns)
- for i := 0; i < numRuns; i++ {
+ for i := range numRuns {
run := runs[len(runs)-numRuns+i]
history[i] = WorkflowHistoryItem{
RunID: run.RunID,
@@ -194,7 +194,7 @@ func (wa *WorkflowAgent) Run(ctx context.Context, input string) (string, error)
wa.mu.RUnlock()
if workflow == nil {
- return "", fmt.Errorf("no workflow attached to agent")
+ return "", errors.New("no workflow attached to agent")
}
workflowInput := &WorkflowInput{
diff --git a/scripts/fix-test-errcheck-simple.sh b/scripts/fix-test-errcheck-simple.sh
index 971ec43..09c6cb8 100755
--- a/scripts/fix-test-errcheck-simple.sh
+++ b/scripts/fix-test-errcheck-simple.sh
@@ -11,17 +11,17 @@ FILES=$(golangci-lint run 2>&1 | grep "errcheck" | grep "_test.go:" | cut -d: -f
count=0
for file in $FILES; do
echo "处理: $file"
-
+
# 备份文件
cp "$file" "$file.bak"
-
+
# 修复常见模式(macOS 兼容)
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS sed
# 1. defer xxx.Close() 不需要修复(已经是 defer)
# 2. 行首的方法调用添加 _ =
sed -i '' 's/^\([[:space:]]*\)\([a-zA-Z_][a-zA-Z0-9_]*\)\.\(Write\|Update\|Save\|Add\|Share\|Track\|Remove\|RemoveAll\|Setenv\|Unsetenv\|Join\|Broadcast\|Every\|Shutdown\|Terminate\|Read\|Decode\|Marshal\)(/\1_ = \2.\3(/g' "$file"
-
+
# 3. defer 后面的 Close/Shutdown/RemoveAll
sed -i '' 's/defer \([a-zA-Z_][a-zA-Z0-9_]*\)\.\(Close\|Shutdown\|RemoveAll\)(/defer func() { _ = \1.\2(/g' "$file"
sed -i '' 's/defer func() { _ = \([a-zA-Z_][a-zA-Z0-9_]*\)\.\(Close\|Shutdown\|RemoveAll\)(/defer func() { _ = \1.\2(/g' "$file"
@@ -31,7 +31,7 @@ for file in $FILES; do
sed -i 's/defer \([a-zA-Z_][a-zA-Z0-9_]*\)\.\(Close\|Shutdown\|RemoveAll\)(/defer func() { _ = \1.\2(/g' "$file"
sed -i 's/defer func() { _ = \([a-zA-Z_][a-zA-Z0-9_]*\)\.\(Close\|Shutdown\|RemoveAll\)(/defer func() { _ = \1.\2(/g' "$file"
fi
-
+
# 检查是否有改动
if ! diff -q "$file" "$file.bak" > /dev/null 2>&1; then
count=$((count + 1))
@@ -39,7 +39,7 @@ for file in $FILES; do
else
echo " - 无需修复"
fi
-
+
# 删除备份
rm "$file.bak"
done
diff --git a/server/auth/apikey.go b/server/auth/apikey.go
index b5eb1c0..3c88f1f 100644
--- a/server/auth/apikey.go
+++ b/server/auth/apikey.go
@@ -4,7 +4,7 @@ import (
"context"
"crypto/rand"
"encoding/hex"
- "fmt"
+ "errors"
"sync"
"time"
)
@@ -125,7 +125,7 @@ func (s *MemoryAPIKeyStore) Get(ctx context.Context, key string) (*APIKeyInfo, e
info, exists := s.keys[key]
if !exists {
- return nil, fmt.Errorf("api key not found")
+ return nil, errors.New("api key not found")
}
return info, nil
diff --git a/server/auth/jwt.go b/server/auth/jwt.go
index 4a855a1..0576114 100644
--- a/server/auth/jwt.go
+++ b/server/auth/jwt.go
@@ -10,11 +10,12 @@ import (
// JWTClaims JWT 声明
type JWTClaims struct {
+ jwt.RegisteredClaims
+
UserID string `json:"user_id"`
Username string `json:"username"`
Email string `json:"email"`
Roles []string `json:"roles"`
- jwt.RegisteredClaims
}
// JWTAuthenticator JWT 认证器
diff --git a/server/handlers/agent.go b/server/handlers/agent.go
index f93e56b..5975554 100644
--- a/server/handlers/agent.go
+++ b/server/handlers/agent.go
@@ -156,7 +156,7 @@ func (h *AgentHandler) Get(c *gin.Context) {
var agent AgentRecord
if err := (*h.store).Get(ctx, "agents", id, &agent); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -188,7 +188,7 @@ func (h *AgentHandler) Delete(c *gin.Context) {
id := c.Param("id")
if err := (*h.store).Delete(ctx, "agents", id); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -239,7 +239,7 @@ func (h *AgentHandler) Update(c *gin.Context) {
// 获取现有 Agent
var agentRecord AgentRecord
if err := (*h.store).Get(ctx, "agents", id, &agentRecord); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -340,7 +340,7 @@ func (h *AgentHandler) Run(c *gin.Context) {
// Get agent record
var agentRecord AgentRecord
if err := (*h.store).Get(ctx, "agents", id, &agentRecord); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -422,7 +422,7 @@ func (h *AgentHandler) Send(c *gin.Context) {
// Get agent record
var agentRecord AgentRecord
if err := (*h.store).Get(ctx, "agents", id, &agentRecord); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -488,7 +488,7 @@ func (h *AgentHandler) GetStatus(c *gin.Context) {
// Get agent record
var agentRecord AgentRecord
if err := (*h.store).Get(ctx, "agents", id, &agentRecord); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -525,7 +525,7 @@ func (h *AgentHandler) Resume(c *gin.Context) {
// Get agent record
var agentRecord AgentRecord
if err := (*h.store).Get(ctx, "agents", id, &agentRecord); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -778,11 +778,13 @@ func (h *AgentHandler) StreamChat(c *gin.Context) {
if event != nil {
// Extract text content from event
var textContent string
+ var textContentSb781 strings.Builder
for _, block := range event.Content.ContentBlocks {
if tb, ok := block.(*types.TextBlock); ok {
- textContent += tb.Text
+ textContentSb781.WriteString(tb.Text)
}
}
+ textContent += textContentSb781.String()
if textContent != "" {
c.SSEvent("message", gin.H{
diff --git a/server/handlers/dashboard.go b/server/handlers/dashboard.go
index b731b12..9167d0c 100644
--- a/server/handlers/dashboard.go
+++ b/server/handlers/dashboard.go
@@ -4,6 +4,7 @@ import (
"net/http"
"sort"
"strconv"
+ "strings"
"time"
"github.com/astercloud/aster/pkg/dashboard"
@@ -317,8 +318,8 @@ func (h *DashboardHandler) GetRecentEvents(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{
- "events": []gin.H{},
- "cursor": int64(0),
+ "events": []gin.H{},
+ "cursor": int64(0),
"message": "Registry not available, real-time events disabled",
},
})
@@ -509,15 +510,15 @@ func (h *DashboardHandler) UpdatePricing(c *gin.Context) {
// SessionSummary represents a session summary for dashboard
type SessionSummary struct {
- ID string `json:"id"`
- AgentID string `json:"agent_id,omitempty"`
- AgentName string `json:"agent_name,omitempty"`
- Status string `json:"status"`
- MessageCount int `json:"message_count"`
- TokenUsage TokenCount `json:"token_usage"`
- CreatedAt time.Time `json:"created_at"`
- UpdatedAt time.Time `json:"updated_at"`
- Metadata map[string]any `json:"metadata,omitempty"`
+ ID string `json:"id"`
+ AgentID string `json:"agent_id,omitempty"`
+ AgentName string `json:"agent_name,omitempty"`
+ Status string `json:"status"`
+ MessageCount int `json:"message_count"`
+ TokenUsage TokenCount `json:"token_usage"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+ Metadata map[string]any `json:"metadata,omitempty"`
}
// TokenCount represents token counts
@@ -633,6 +634,7 @@ func (h *DashboardHandler) ListSessions(c *gin.Context) {
// SessionDetail represents detailed session info
type SessionDetail struct {
SessionSummary
+
Messages []MessageSummary `json:"messages"`
}
@@ -670,11 +672,13 @@ func (h *DashboardHandler) GetSession(c *gin.Context) {
content := msg.Content
if content == "" && len(msg.ContentBlocks) > 0 {
// 从 ContentBlocks 提取文本
+ var contentSb673 strings.Builder
for _, block := range msg.ContentBlocks {
if textBlock, ok := block.(*types.TextBlock); ok {
- content += textBlock.Text
+ contentSb673.WriteString(textBlock.Text)
}
}
+ content += contentSb673.String()
}
// 使用消息索引作为时间戳的偏移
diff --git a/server/handlers/dashboard_events.go b/server/handlers/dashboard_events.go
index 69b1cca..db96b10 100644
--- a/server/handlers/dashboard_events.go
+++ b/server/handlers/dashboard_events.go
@@ -256,7 +256,7 @@ func (h *DashboardEventHandler) handleMessage(wsConn *DashboardEventConnection,
case "ping":
h.sendMessage(wsConn, "pong", nil)
default:
- h.sendError(wsConn, "unknown_action", fmt.Sprintf("Unknown action: %s", msg.Action))
+ h.sendError(wsConn, "unknown_action", "Unknown action: "+msg.Action)
}
}
@@ -723,7 +723,7 @@ func (c *DashboardEventConnection) extractEventInfo(agentID string, envelope typ
// sendMessage sends a message to the WebSocket client
func (h *DashboardEventHandler) sendMessage(wsConn *DashboardEventConnection, msgType string, payload any) {
- // Check if context is cancelled
+ // Check if context is canceled
if wsConn.ctx.Err() != nil {
return
}
diff --git a/server/handlers/eval.go b/server/handlers/eval.go
index 32d6d9b..fc05326 100644
--- a/server/handlers/eval.go
+++ b/server/handlers/eval.go
@@ -1,6 +1,7 @@
package handlers
import (
+ "errors"
"net/http"
"time"
@@ -337,7 +338,7 @@ func (h *EvalHandler) GetEval(c *gin.Context) {
var eval EvalRecord
if err := (*h.store).Get(ctx, "evals", id, &eval); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -369,7 +370,7 @@ func (h *EvalHandler) DeleteEval(c *gin.Context) {
id := c.Param("id")
if err := (*h.store).Delete(ctx, "evals", id); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -483,7 +484,7 @@ func (h *EvalHandler) GetBenchmark(c *gin.Context) {
var benchmark BenchmarkRecord
if err := (*h.store).Get(ctx, "benchmarks", id, &benchmark); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -515,7 +516,7 @@ func (h *EvalHandler) DeleteBenchmark(c *gin.Context) {
id := c.Param("id")
if err := (*h.store).Delete(ctx, "benchmarks", id); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -549,7 +550,7 @@ func (h *EvalHandler) RunBenchmark(c *gin.Context) {
var benchmark BenchmarkRecord
if err := (*h.store).Get(ctx, "benchmarks", id, &benchmark); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -572,7 +573,7 @@ func (h *EvalHandler) RunBenchmark(c *gin.Context) {
// TODO: Actually run the benchmark
// For now, generate sample results
results := make([]map[string]float64, benchmark.Runs)
- for i := 0; i < benchmark.Runs; i++ {
+ for i := range benchmark.Runs {
results[i] = map[string]float64{
"score": 0.90 + float64(i)*0.01,
"duration": 100 + float64(i)*10,
diff --git a/server/handlers/hitl.go b/server/handlers/hitl.go
index 02f015b..2c9df8f 100644
--- a/server/handlers/hitl.go
+++ b/server/handlers/hitl.go
@@ -86,7 +86,7 @@ func (m *HITLManager) CreateApprovalHandler(connID string) middleware.ApprovalHa
case <-ctx.Done():
decisions[i] = middleware.Decision{
Type: middleware.DecisionReject,
- Reason: "Context cancelled",
+ Reason: "Context canceled",
}
case <-time.After(5 * time.Minute): // 5分钟超时
decisions[i] = middleware.Decision{
diff --git a/server/handlers/mcp.go b/server/handlers/mcp.go
index 26c072f..27a5946 100644
--- a/server/handlers/mcp.go
+++ b/server/handlers/mcp.go
@@ -1,6 +1,7 @@
package handlers
import (
+ "errors"
"net/http"
"time"
@@ -141,7 +142,7 @@ func (h *MCPHandler) Get(c *gin.Context) {
var server MCPServerRecord
if err := (*h.store).Get(ctx, "mcp_servers", id, &server); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -193,7 +194,7 @@ func (h *MCPHandler) Update(c *gin.Context) {
var server MCPServerRecord
if err := (*h.store).Get(ctx, "mcp_servers", id, &server); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -258,7 +259,7 @@ func (h *MCPHandler) Delete(c *gin.Context) {
id := c.Param("id")
if err := (*h.store).Delete(ctx, "mcp_servers", id); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -292,7 +293,7 @@ func (h *MCPHandler) Connect(c *gin.Context) {
var server MCPServerRecord
if err := (*h.store).Get(ctx, "mcp_servers", id, &server); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -344,7 +345,7 @@ func (h *MCPHandler) Disconnect(c *gin.Context) {
var server MCPServerRecord
if err := (*h.store).Get(ctx, "mcp_servers", id, &server); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
diff --git a/server/handlers/memory.go b/server/handlers/memory.go
index bd4d196..5abddf7 100644
--- a/server/handlers/memory.go
+++ b/server/handlers/memory.go
@@ -1,6 +1,7 @@
package handlers
import (
+ "errors"
"net/http"
"time"
@@ -182,7 +183,7 @@ func (h *MemoryHandler) GetWorkingMemory(c *gin.Context) {
var memory WorkingMemoryRecord
if err := (*h.store).Get(ctx, "working_memory", id, &memory); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -244,7 +245,7 @@ func (h *MemoryHandler) UpdateWorkingMemory(c *gin.Context) {
var memory WorkingMemoryRecord
if err := (*h.store).Get(ctx, "working_memory", id, &memory); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -309,7 +310,7 @@ func (h *MemoryHandler) DeleteWorkingMemory(c *gin.Context) {
id := c.Param("id")
if err := (*h.store).Delete(ctx, "working_memory", id); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
diff --git a/server/handlers/middleware.go b/server/handlers/middleware.go
index ebfddde..204373f 100644
--- a/server/handlers/middleware.go
+++ b/server/handlers/middleware.go
@@ -1,6 +1,7 @@
package handlers
import (
+ "errors"
"time"
"github.com/astercloud/aster/pkg/logging"
@@ -100,7 +101,7 @@ func (h *MiddlewareHandler) Get(c *gin.Context) {
var mw MiddlewareRecord
if err := h.store.Get(ctx, "middlewares", id, &mw); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(404, gin.H{"success": false, "error": gin.H{"code": "not_found", "message": "middleware not found"}})
return
}
@@ -131,7 +132,7 @@ func (h *MiddlewareHandler) Update(c *gin.Context) {
var mw MiddlewareRecord
if err := h.store.Get(ctx, "middlewares", id, &mw); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(404, gin.H{"success": false, "error": gin.H{"code": "not_found", "message": "middleware not found"}})
return
}
@@ -173,7 +174,7 @@ func (h *MiddlewareHandler) Delete(c *gin.Context) {
id := c.Param("id")
if err := h.store.Delete(ctx, "middlewares", id); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(404, gin.H{"success": false, "error": gin.H{"code": "not_found", "message": "middleware not found"}})
return
}
@@ -192,7 +193,7 @@ func (h *MiddlewareHandler) Enable(c *gin.Context) {
var mw MiddlewareRecord
if err := h.store.Get(ctx, "middlewares", id, &mw); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(404, gin.H{"success": false, "error": gin.H{"code": "not_found", "message": "middleware not found"}})
return
}
@@ -219,7 +220,7 @@ func (h *MiddlewareHandler) Disable(c *gin.Context) {
var mw MiddlewareRecord
if err := h.store.Get(ctx, "middlewares", id, &mw); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(404, gin.H{"success": false, "error": gin.H{"code": "not_found", "message": "middleware not found"}})
return
}
diff --git a/server/handlers/remote_agent.go b/server/handlers/remote_agent.go
index bd28163..bcfe8e7 100644
--- a/server/handlers/remote_agent.go
+++ b/server/handlers/remote_agent.go
@@ -3,6 +3,7 @@ package handlers
import (
"context"
"encoding/json"
+ "errors"
"fmt"
"net/http"
"sync"
@@ -161,10 +162,10 @@ func (h *RemoteAgentHandler) handleRegister(ctx context.Context, conn *websocket
// 验证必填字段
if reg.AgentID == "" {
- return fmt.Errorf("agent_id is required")
+ return errors.New("agent_id is required")
}
if reg.TemplateID == "" {
- return fmt.Errorf("template_id is required")
+ return errors.New("template_id is required")
}
h.mu.Lock()
@@ -247,10 +248,10 @@ func (h *RemoteAgentHandler) handleRegisterSession(ctx context.Context, conn *we
// 验证必填字段
if reg.SessionID == "" {
- return fmt.Errorf("session_id is required")
+ return errors.New("session_id is required")
}
if reg.AgentID == "" {
- return fmt.Errorf("agent_id is required")
+ return errors.New("agent_id is required")
}
// 同步到 Store,使其在 /v1/sessions API 中可见
diff --git a/server/handlers/room.go b/server/handlers/room.go
index 6df25e1..281e75d 100644
--- a/server/handlers/room.go
+++ b/server/handlers/room.go
@@ -1,6 +1,7 @@
package handlers
import (
+ "errors"
"net/http"
"time"
@@ -135,7 +136,7 @@ func (h *RoomHandler) Get(c *gin.Context) {
var room RoomRecord
if err := (*h.store).Get(ctx, "rooms", id, &room); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -171,7 +172,7 @@ func (h *RoomHandler) Delete(c *gin.Context) {
// Delete from store
if err := (*h.store).Delete(ctx, "rooms", id); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
diff --git a/server/handlers/runtime_registry.go b/server/handlers/runtime_registry.go
index 2c23fdd..39b8125 100644
--- a/server/handlers/runtime_registry.go
+++ b/server/handlers/runtime_registry.go
@@ -162,7 +162,7 @@ func (r *RuntimeAgentRegistry) GetEventBuses() []*events.EventBus {
defer r.mu.RUnlock()
buses := make([]*events.EventBus, 0, len(r.agents)+len(r.remoteAgents))
-
+
// 本地 Agent 的 EventBus
for _, ag := range r.agents {
if ag != nil {
@@ -171,7 +171,7 @@ func (r *RuntimeAgentRegistry) GetEventBuses() []*events.EventBus {
}
}
}
-
+
// 远程 Agent 的 EventBus
for _, ra := range r.remoteAgents {
if ra != nil {
@@ -180,6 +180,6 @@ func (r *RuntimeAgentRegistry) GetEventBuses() []*events.EventBus {
}
}
}
-
+
return buses
}
diff --git a/server/handlers/session.go b/server/handlers/session.go
index a27d8d6..273e21b 100644
--- a/server/handlers/session.go
+++ b/server/handlers/session.go
@@ -1,6 +1,7 @@
package handlers
import (
+ "errors"
"net/http"
"time"
@@ -132,7 +133,7 @@ func (h *SessionHandler) Get(c *gin.Context) {
var session SessionRecord
if err := (*h.store).Get(ctx, "sessions", id, &session); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -182,7 +183,7 @@ func (h *SessionHandler) Update(c *gin.Context) {
var session SessionRecord
if err := (*h.store).Get(ctx, "sessions", id, &session); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -241,7 +242,7 @@ func (h *SessionHandler) Delete(c *gin.Context) {
id := c.Param("id")
if err := (*h.store).Delete(ctx, "sessions", id); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -275,7 +276,7 @@ func (h *SessionHandler) GetMessages(c *gin.Context) {
var session SessionRecord
if err := (*h.store).Get(ctx, "sessions", id, &session); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -342,7 +343,7 @@ func (h *SessionHandler) Resume(c *gin.Context) {
var session SessionRecord
if err := (*h.store).Get(ctx, "sessions", id, &session); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
diff --git a/server/handlers/system.go b/server/handlers/system.go
index 720fece..629ebec 100644
--- a/server/handlers/system.go
+++ b/server/handlers/system.go
@@ -1,6 +1,7 @@
package handlers
import (
+ "errors"
"runtime"
"time"
@@ -54,7 +55,7 @@ func (h *SystemHandler) GetConfig(c *gin.Context) {
var cfg SystemConfigRecord
if err := h.store.Get(ctx, "system_config", key, &cfg); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(404, gin.H{"success": false, "error": gin.H{"code": "not_found", "message": "config not found"}})
return
}
@@ -100,7 +101,7 @@ func (h *SystemHandler) DeleteConfig(c *gin.Context) {
key := c.Param("key")
if err := h.store.Delete(ctx, "system_config", key); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(404, gin.H{"success": false, "error": gin.H{"code": "not_found", "message": "config not found"}})
return
}
diff --git a/server/handlers/tool.go b/server/handlers/tool.go
index f36ece1..d132434 100644
--- a/server/handlers/tool.go
+++ b/server/handlers/tool.go
@@ -1,6 +1,7 @@
package handlers
import (
+ "errors"
"net/http"
"time"
@@ -139,7 +140,7 @@ func (h *ToolHandler) Get(c *gin.Context) {
var tool ToolRecord
if err := (*h.store).Get(ctx, "tools", id, &tool); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -193,7 +194,7 @@ func (h *ToolHandler) Update(c *gin.Context) {
var tool ToolRecord
if err := (*h.store).Get(ctx, "tools", id, &tool); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -264,7 +265,7 @@ func (h *ToolHandler) Delete(c *gin.Context) {
id := c.Param("id")
if err := (*h.store).Delete(ctx, "tools", id); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -314,7 +315,7 @@ func (h *ToolHandler) Execute(c *gin.Context) {
// Check if tool exists
var tool ToolRecord
if err := (*h.store).Get(ctx, "tools", id, &tool); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
diff --git a/server/handlers/websocket.go b/server/handlers/websocket.go
index cdfb51f..e1e5548 100644
--- a/server/handlers/websocket.go
+++ b/server/handlers/websocket.go
@@ -216,7 +216,7 @@ func (h *WebSocketHandler) handleMessage(wsConn *WebSocketConnection, msg *WebSo
case "permission_decision":
h.handlePermissionDecision(wsConn, msg.Payload)
default:
- h.sendError(wsConn, "unknown_message_type", fmt.Sprintf("Unknown message type: %s", msg.Type))
+ h.sendError(wsConn, "unknown_message_type", "Unknown message type: "+msg.Type)
}
}
@@ -392,11 +392,13 @@ func (h *WebSocketHandler) handleChat(wsConn *WebSocketConnection, payload map[s
// Extract text content from event
var textContent string
+ var textContentSb395 strings.Builder
for _, block := range event.Content.ContentBlocks {
if tb, ok := block.(*types.TextBlock); ok {
- textContent += tb.Text
+ textContentSb395.WriteString(tb.Text)
}
}
+ textContent += textContentSb395.String()
if textContent != "" {
logging.Info(wsConn.ctx, "websocket.stream.sending_text_delta", map[string]any{
diff --git a/server/handlers/workflow.go b/server/handlers/workflow.go
index 6a4e392..847eb2a 100644
--- a/server/handlers/workflow.go
+++ b/server/handlers/workflow.go
@@ -1,6 +1,7 @@
package handlers
import (
+ "errors"
"net/http"
"time"
@@ -30,7 +31,7 @@ type WorkflowTrigger struct {
type WorkflowExecution struct {
ID string `json:"id"`
WorkflowID string `json:"workflow_id"`
- Status string `json:"status"` // pending, running, completed, failed, cancelled
+ Status string `json:"status"` // pending, running, completed, failed, canceled
StartedAt time.Time `json:"started_at"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
Result map[string]any `json:"result,omitempty"`
@@ -181,7 +182,7 @@ func (h *WorkflowHandler) Get(c *gin.Context) {
var workflow WorkflowRecord
if err := (*h.store).Get(ctx, "workflows", id, &workflow); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -235,7 +236,7 @@ func (h *WorkflowHandler) Update(c *gin.Context) {
var workflow WorkflowRecord
if err := (*h.store).Get(ctx, "workflows", id, &workflow); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -314,7 +315,7 @@ func (h *WorkflowHandler) Delete(c *gin.Context) {
id := c.Param("id")
if err := (*h.store).Delete(ctx, "workflows", id); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -358,7 +359,7 @@ func (h *WorkflowHandler) Execute(c *gin.Context) {
// Check if workflow exists
var workflow WorkflowRecord
if err := (*h.store).Get(ctx, "workflows", id, &workflow); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -455,7 +456,7 @@ func (h *WorkflowHandler) GetExecutionDetails(c *gin.Context) {
var execution WorkflowExecution
if err := (*h.store).Get(ctx, "workflow_executions", executionID, &execution); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -488,7 +489,7 @@ func (h *WorkflowHandler) Suspend(c *gin.Context) {
var workflow WorkflowRecord
if err := (*h.store).Get(ctx, "workflows", id, &workflow); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
@@ -535,7 +536,7 @@ func (h *WorkflowHandler) Resume(c *gin.Context) {
var workflow WorkflowRecord
if err := (*h.store).Get(ctx, "workflows", id, &workflow); err != nil {
- if err == store.ErrNotFound {
+ if errors.Is(err, store.ErrNotFound) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"error": gin.H{
diff --git a/server/middleware.go b/server/middleware.go
index 201bcb0..e6b160d 100644
--- a/server/middleware.go
+++ b/server/middleware.go
@@ -70,7 +70,7 @@ func corsMiddleware(config CORSConfig) gin.HandlerFunc {
}
}
- if c.Request.Method == "OPTIONS" {
+ if c.Request.Method == http.MethodOptions {
c.AbortWithStatus(http.StatusNoContent)
return
}
diff --git a/server/ratelimit/middleware.go b/server/ratelimit/middleware.go
index 77ff30d..8ca9027 100644
--- a/server/ratelimit/middleware.go
+++ b/server/ratelimit/middleware.go
@@ -3,6 +3,7 @@ package ratelimit
import (
"fmt"
"net/http"
+ "strconv"
"time"
"github.com/gin-gonic/gin"
@@ -42,10 +43,10 @@ func Middleware(config Config, limiter Limiter) gin.HandlerFunc {
info := limiter.GetInfo(key)
// 设置速率限制响应头
- c.Header("X-RateLimit-Limit", fmt.Sprintf("%d", info.Limit))
+ c.Header("X-RateLimit-Limit", strconv.Itoa(info.Limit))
c.Header("X-RateLimit-Remaining", "0")
- c.Header("X-RateLimit-Reset", fmt.Sprintf("%d", info.ResetAt.Unix()))
- c.Header("Retry-After", fmt.Sprintf("%d", int(time.Until(info.ResetAt).Seconds())))
+ c.Header("X-RateLimit-Reset", strconv.FormatInt(info.ResetAt.Unix(), 10))
+ c.Header("Retry-After", strconv.Itoa(int(time.Until(info.ResetAt).Seconds())))
c.JSON(http.StatusTooManyRequests, gin.H{
"success": false,
@@ -61,9 +62,9 @@ func Middleware(config Config, limiter Limiter) gin.HandlerFunc {
// 设置速率限制响应头
info := limiter.GetInfo(key)
- c.Header("X-RateLimit-Limit", fmt.Sprintf("%d", info.Limit))
- c.Header("X-RateLimit-Remaining", fmt.Sprintf("%d", info.Remaining))
- c.Header("X-RateLimit-Reset", fmt.Sprintf("%d", info.ResetAt.Unix()))
+ c.Header("X-RateLimit-Limit", strconv.Itoa(info.Limit))
+ c.Header("X-RateLimit-Remaining", strconv.Itoa(info.Remaining))
+ c.Header("X-RateLimit-Reset", strconv.FormatInt(info.ResetAt.Unix(), 10))
c.Next()
}
diff --git a/server/server.go b/server/server.go
index e380cae..91e9d0d 100644
--- a/server/server.go
+++ b/server/server.go
@@ -2,6 +2,7 @@ package server
import (
"context"
+ "errors"
"fmt"
"net/http"
"time"
@@ -57,7 +58,7 @@ func New(config *Config, deps *Dependencies, opts ...Option) (*Server, error) {
}
if deps == nil {
- return nil, fmt.Errorf("dependencies cannot be nil")
+ return nil, errors.New("dependencies cannot be nil")
}
// Set Gin mode based on config
diff --git a/server/server_integration_test.go b/server/server_integration_test.go
index 0fc92da..d6b2b00 100644
--- a/server/server_integration_test.go
+++ b/server/server_integration_test.go
@@ -567,7 +567,7 @@ func TestConcurrentRequests(t *testing.T) {
const numRequests = 10
done := make(chan int, numRequests)
- for i := 0; i < numRequests; i++ {
+ for i := range numRequests {
go func(id int) {
defer func() {
if r := recover(); r != nil {
diff --git a/studio/public/vite.svg b/studio/public/vite.svg
index e7b8dfb..ee9fada 100644
--- a/studio/public/vite.svg
+++ b/studio/public/vite.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/studio/src/assets/react.svg b/studio/src/assets/react.svg
index 6c87de9..8e0e0f1 100644
--- a/studio/src/assets/react.svg
+++ b/studio/src/assets/react.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/ui/src/components/Thinking/AskUserQuestionCard.vue b/ui/src/components/Thinking/AskUserQuestionCard.vue
index d450f5c..0a81d63 100644
--- a/ui/src/components/Thinking/AskUserQuestionCard.vue
+++ b/ui/src/components/Thinking/AskUserQuestionCard.vue
@@ -2,7 +2,7 @@