From 28bb952ac62b07f3d820eca075e5c66691144942 Mon Sep 17 00:00:00 2001 From: lwmacct Date: Sat, 20 Dec 2025 05:36:00 +0800 Subject: [PATCH] refactor: code quality improvements and lint fixes Lint fixes: - Fix variable shadowing in generator.go (errors -> errs) - Fix else-if syntax error in runtime.go with composite literal - Add missing errors import in runtime.go - Reorder embedded field in jwt.go struct - Fix string concatenation in loop in semantic.go - Remove unnecessary int64 conversion in project_manager.go Config updates: - Optimize .golangci.yml to disable overly strict linters - Update .pre-commit-config.yaml to use local golangci-lint hook - Update go-ci.yml workflow configuration Documentation and examples updates included. --- .github/workflows/go-ci.yml | 19 +-- .golangci.yml | 119 ++++++++++++++---- .pre-commit-config.yaml | 19 +-- .../02.core-concepts/13.session-recovery.md | 38 +++--- .../02.core-concepts/14.session-sqlite.md | 6 +- docs/content/02.core-concepts/15.recipe.md | 6 +- .../content/04.memory/12.logic-memory-plan.md | 5 +- docs/content/04.memory/14.rules-system.md | 2 +- docs/content/04.memory/16.dialog-extractor.md | 2 +- docs/content/08.security/1.guardrails.md | 1 - docs/content/08.security/2.permission.md | 12 +- docs/content/09.deployment/7.desktop/index.md | 26 ++-- .../5.studio/remote-agents.md | 6 +- .../5.workflows/plan-explore-ui.md | 1 - docs/content/roadmap/desktop-roadmap.md | 24 ++-- docs/public/images/architecture-layers.svg | 38 +++--- docs/public/images/logo-banner.svg | 52 ++++---- docs/public/images/logo-icon.svg | 36 +++--- docs/public/images/logo.svg | 86 ++++++------- examples/a2a/main.go | 2 +- examples/actor/actor_test.go | 46 ++++--- examples/comprehensive/main.go | 3 +- examples/config-paths/main.go | 5 +- .../custom_claude_api/test_redis_store.go | 8 +- examples/desktop/README.md | 32 ++--- examples/guardrails/main.go | 13 +- examples/human-in-the-loop/main.go | 3 +- examples/logic-memory/README.md | 1 - examples/long-running-tools/main.go | 7 +- examples/memory-advanced/main.go | 7 +- examples/openrouter/agent_test.go | 43 ++++--- examples/openrouter/main.go | 3 +- examples/permission/README.md | 2 +- examples/permission/main.go | 5 +- examples/ptc/local-test/main.go | 2 +- examples/recipe/main.go | 7 +- examples/recipes/code-review.yaml | 16 +-- examples/recipes/writing-assistant.yaml | 10 +- examples/session-mysql/main.go | 3 +- examples/session-postgres/main.go | 3 +- examples/telemetry/main.go | 6 +- examples/tool-cache/main.go | 3 +- examples/workflow-semantic/main.go | 7 +- examples/workflow/main.go | 4 +- pkg/a2a/handler.go | 2 +- pkg/a2a/server.go | 20 +-- pkg/a2a/server_test.go | 4 +- pkg/a2a/store.go | 5 +- pkg/a2a/store_test.go | 14 +-- pkg/actor/actor_test.go | 13 +- pkg/actor/system.go | 3 +- pkg/actor/types.go | 5 +- pkg/agent/actor.go | 7 +- pkg/agent/agent.go | 94 ++++++-------- pkg/agent/execution_plan.go | 13 +- pkg/agent/model_fallback.go | 7 +- pkg/agent/model_fallback_test.go | 4 +- pkg/agent/plan_mode.go | 12 +- pkg/agent/processor.go | 40 +++--- pkg/agent/prompt_builder.go | 9 +- pkg/agent/prompt_compression_test.go | 4 +- pkg/agent/prompt_compressor_llm.go | 5 +- pkg/agent/prompt_modules.go | 36 +++--- pkg/agent/prompt_optimizer.go | 9 +- pkg/agent/remote_agent.go | 16 +-- pkg/agent/streaming.go | 16 +-- pkg/agent/subagent.go | 20 +-- pkg/agent/tool_manager.go | 13 +- pkg/agent/workflow/loop.go | 6 +- pkg/agent/workflow/parallel.go | 4 +- pkg/agent/workflow/sequential.go | 4 +- pkg/asteros/asteros.go | 2 +- pkg/asteros/interfaces/a2a.go | 1 + pkg/asteros/interfaces/agui.go | 4 +- pkg/asteros/interfaces/http.go | 1 + pkg/backends/state.go | 11 +- pkg/backends/store_backend.go | 8 +- pkg/backends/utils_test.go | 8 +- pkg/commands/executor.go | 5 +- pkg/commands/loader.go | 3 +- pkg/compression/service.go | 2 +- pkg/compression/service_test.go | 6 +- pkg/config/loader.go | 18 +-- pkg/config/loader_test.go | 7 -- pkg/context/compression_strategy.go | 2 +- pkg/context/compression_strategy_test.go | 11 +- pkg/context/token_counter_test.go | 8 +- pkg/context/window_manager.go | 3 +- pkg/context/window_manager_test.go | 6 +- pkg/core/pool.go | 5 +- pkg/core/pool_test.go | 10 +- pkg/core/room.go | 5 +- pkg/core/scheduler.go | 6 +- pkg/culture/culture.go | 16 ++- pkg/dashboard/aggregator.go | 26 ++-- pkg/dashboard/cost_calculator.go | 15 +-- pkg/dashboard/trace_builder.go | 6 +- pkg/dashboard/types.go | 95 +++++++------- pkg/desktop/desktop.go | 10 +- pkg/evals/batch.go | 5 +- pkg/evals/batch_test.go | 6 +- pkg/events/bus_test.go | 16 +-- pkg/executionplan/executor.go | 14 +-- pkg/executionplan/generator.go | 25 ++-- pkg/executionplan/types.go | 92 +++++++------- pkg/executionplan/types_test.go | 4 +- pkg/guardrails/openai_moderation.go | 3 +- pkg/guardrails/pii_detection.go | 9 +- pkg/knowledge/core/pipeline.go | 22 ++-- pkg/knowledge/manager.go | 52 ++++---- pkg/knowledge/rag.go | 20 +-- pkg/knowledge/simple/pipeline.go | 18 +-- pkg/mcpserver/docstools.go | 17 +-- pkg/mcpserver/server.go | 7 +- pkg/media/processor.go | 25 +--- pkg/memory/auto/types.go | 14 +-- pkg/memory/bridge.go | 16 +-- pkg/memory/consolidation_strategies.go | 7 +- pkg/memory/consolidation_test.go | 4 +- pkg/memory/dialog/extractor.go | 10 +- pkg/memory/lineage.go | 11 +- pkg/memory/logic/consolidation.go | 4 +- pkg/memory/logic/manager.go | 11 +- pkg/memory/logic/metrics.go | 17 +-- pkg/memory/logic/store_mysql.go | 10 +- pkg/memory/logic/store_postgres.go | 10 +- pkg/memory/logic/store_test.go | 2 +- pkg/memory/memory.go | 19 +-- pkg/memory/observation_compressor.go | 5 +- pkg/memory/observation_compressor_test.go | 12 +- pkg/memory/preference.go | 5 +- pkg/memory/preference_storage.go | 7 +- pkg/memory/provenance.go | 7 +- pkg/memory/provenance_test.go | 2 +- pkg/memory/quality_analyzer.go | 4 +- pkg/memory/quality_analyzer_test.go | 2 +- pkg/memory/quality_metrics.go | 2 +- pkg/memory/quality_metrics_test.go | 4 +- pkg/memory/reference_registry.go | 2 +- pkg/memory/reference_registry_test.go | 4 +- pkg/memory/rules/manager.go | 8 +- pkg/memory/rules/types.go | 7 +- pkg/memory/schema.go | 12 +- pkg/memory/semantic.go | 34 ++--- pkg/memory/session_compressor.go | 2 +- pkg/memory/session_compressor_test.go | 2 +- pkg/memory/session_manager.go | 15 +-- pkg/memory/session_summary.go | 29 +++-- pkg/memory/session_summary_test.go | 17 +-- pkg/memory/working.go | 17 +-- pkg/middleware/agent_memory.go | 6 +- pkg/middleware/filesystem.go | 5 +- pkg/middleware/filesystem_security_test.go | 6 +- pkg/middleware/hitl.go | 3 +- pkg/middleware/hitl_test.go | 5 +- pkg/middleware/integration_test.go | 6 +- pkg/middleware/logic_memory.go | 19 ++- pkg/middleware/logic_memory_test.go | 6 +- pkg/middleware/logic_memory_tools.go | 13 +- pkg/middleware/observation_compression.go | 1 + pkg/middleware/patch_tool_calls.go | 1 + pkg/middleware/patch_tool_calls_test.go | 2 +- pkg/middleware/phase4_bench_test.go | 24 ++-- pkg/middleware/reasoning.go | 6 +- pkg/middleware/registry.go | 13 +- pkg/middleware/simplicity_checker.go | 1 + pkg/middleware/structured_output.go | 4 +- pkg/middleware/subagent.go | 17 ++- pkg/middleware/subagent_async_manager.go | 8 +- pkg/middleware/subagent_async_test.go | 2 +- pkg/middleware/subagent_enhanced_test.go | 9 +- pkg/middleware/summarization.go | 18 +-- pkg/middleware/telemetry.go | 1 + pkg/middleware/telemetry_test.go | 4 +- pkg/middleware/todolist.go | 4 +- pkg/middleware/working_memory.go | 6 +- pkg/permission/inspector.go | 21 ++-- pkg/permission/permission.go | 20 +-- pkg/permission/permission_test.go | 8 +- pkg/provider/anthropic.go | 17 +-- pkg/provider/custom_claude.go | 37 +++--- pkg/provider/deepseek.go | 13 +- pkg/provider/doubao.go | 5 +- pkg/provider/gemini.go | 13 +- pkg/provider/glm.go | 13 +- pkg/provider/interface.go | 8 +- pkg/provider/openai.go | 9 +- pkg/provider/openai_compatible.go | 19 +-- pkg/provider/openai_compatible_test.go | 6 +- pkg/reasoning/engine.go | 4 +- pkg/reasoning/reasoning.go | 15 ++- pkg/reasoning/reasoning_test.go | 2 +- pkg/recipe/recipe.go | 21 ++-- pkg/router/router.go | 3 +- pkg/run/context.go | 17 +-- pkg/run/event.go | 5 + pkg/run/status.go | 2 +- pkg/sandbox/cloud/aliyun.go | 6 +- pkg/sandbox/cloud/mcp.go | 4 +- pkg/sandbox/cloud/volcengine.go | 6 +- pkg/sandbox/factory.go | 11 +- pkg/sandbox/local.go | 34 ++--- pkg/sandbox/local_test.go | 2 +- pkg/sandbox/mock.go | 2 +- pkg/sandbox/remote.go | 17 +-- pkg/security/access_control.go | 22 ++-- pkg/security/access_control_test.go | 9 +- pkg/security/audit.go | 15 ++- pkg/security/audit_test.go | 4 +- pkg/security/pii_detector.go | 4 +- pkg/security/pii_middleware.go | 2 + pkg/security/policy.go | 27 ++-- pkg/security/policy_test.go | 2 +- pkg/security/redaction.go | 2 +- pkg/security/redaction_strategies.go | 17 +-- pkg/server/workflow_demo.go | 1 + pkg/session/inmemory.go | 9 +- pkg/session/inmemory_test.go | 20 +-- pkg/session/mysql/service.go | 5 +- pkg/session/mysql/service_test.go | 30 ++--- pkg/session/postgres/service.go | 5 +- pkg/session/postgres/service_test.go | 18 +-- pkg/session/search.go | 9 +- pkg/session/sqlite/store.go | 10 +- pkg/session/sqlite/store_test.go | 9 +- pkg/session/summary.go | 3 +- pkg/skills/injector.go | 4 +- pkg/skills/loader.go | 5 +- pkg/skills/manager.go | 17 +-- pkg/skills/runtime.go | 11 +- pkg/store/factory.go | 5 +- pkg/store/json.go | 1 - pkg/store/mysql.go | 17 ++- pkg/store/redis.go | 17 +-- pkg/stream/stream_test.go | 8 +- pkg/structured/schema.go | 38 +++--- pkg/structured/typed.go | 9 +- pkg/telemetry/genai/attributes.go | 38 +++--- pkg/telemetry/integration_example.go | 3 +- pkg/telemetry/metrics.go | 5 +- pkg/telemetry/otel_adapter.go | 3 +- pkg/tools/bridge/http_server.go | 3 +- pkg/tools/bridge/runtime.go | 7 +- pkg/tools/builtin/ask_user.go | 7 +- pkg/tools/builtin/bash.go | 5 +- pkg/tools/builtin/bash_test.go | 4 +- pkg/tools/builtin/bashoutput.go | 8 +- pkg/tools/builtin/codeexecute.go | 7 +- pkg/tools/builtin/edit.go | 7 +- pkg/tools/builtin/exitplanmode.go | 31 ++--- pkg/tools/builtin/exitplanmode_test.go | 7 +- pkg/tools/builtin/git_safety.go | 8 +- pkg/tools/builtin/glob.go | 5 +- pkg/tools/builtin/glob_test.go | 8 +- pkg/tools/builtin/grep.go | 28 ++--- pkg/tools/builtin/grep_test.go | 8 +- pkg/tools/builtin/killshell.go | 7 +- pkg/tools/builtin/killshell_test.go | 11 +- pkg/tools/builtin/mcp_resources.go | 9 +- pkg/tools/builtin/rag_search.go | 5 +- pkg/tools/builtin/read_test.go | 4 +- pkg/tools/builtin/semanticmemory.go | 6 +- pkg/tools/builtin/skillcall.go | 10 +- pkg/tools/builtin/skillcall_test.go | 4 +- pkg/tools/builtin/storage_manager.go | 36 +++--- pkg/tools/builtin/subagent_manager.go | 42 ++++--- pkg/tools/builtin/task.go | 25 ++-- pkg/tools/builtin/task_executor.go | 7 +- pkg/tools/builtin/task_manager.go | 48 +++---- pkg/tools/builtin/task_test.go | 5 +- .../builtin/testdata/scripts/harmless.sh | 2 +- .../builtin/testdata/scripts/timeout_test.sh | 2 +- pkg/tools/builtin/testutils.go | 12 +- pkg/tools/builtin/todowrite.go | 27 ++-- pkg/tools/builtin/todowrite_test.go | 11 +- pkg/tools/builtin/toolsearch.go | 4 +- pkg/tools/builtin/webfetch.go | 2 +- pkg/tools/builtin/websearch.go | 5 +- pkg/tools/builtin/websearch_test.go | 2 +- pkg/tools/builtin/write.go | 5 +- pkg/tools/cache_test.go | 6 +- pkg/tools/constraints.go | 7 +- pkg/tools/knowledge/factory.go | 14 +-- pkg/tools/long_running.go | 17 ++- pkg/tools/mcp/server.go | 7 +- pkg/tools/search/bm25_test.go | 2 +- pkg/tools/search/tool_index.go | 7 +- pkg/types/config.go | 3 +- pkg/types/content.go | 6 +- pkg/types/events.go | 2 +- pkg/types/store.go | 18 +-- pkg/types/streaming.go | 6 +- pkg/util/json_test.go | 6 +- pkg/vector/factory/factory.go | 3 +- pkg/vector/memory_store.go | 9 +- pkg/vector/mock_embedder.go | 2 +- pkg/vector/openai_embedder.go | 3 +- pkg/vector/pgvector/store.go | 7 +- pkg/vector/weaviate/store.go | 12 +- pkg/workflow/actor_engine.go | 9 +- pkg/workflow/conditional.go | 31 ++--- pkg/workflow/constraint.go | 51 ++++---- pkg/workflow/dsl.go | 33 ++--- pkg/workflow/engine.go | 19 ++- pkg/workflow/gate.go | 7 +- pkg/workflow/parallel.go | 4 +- pkg/workflow/project_manager.go | 65 +++++----- pkg/workflow/state_machine.go | 36 +++--- pkg/workflow/step.go | 2 +- pkg/workflow/types.go | 4 +- pkg/workflow/visualization.go | 4 +- pkg/workflow/workflow.go | 25 ++-- pkg/workflow/workflow_agent.go | 6 +- scripts/fix-test-errcheck-simple.sh | 10 +- server/auth/apikey.go | 4 +- server/auth/jwt.go | 3 +- server/handlers/agent.go | 18 +-- server/handlers/dashboard.go | 28 +++-- server/handlers/dashboard_events.go | 4 +- server/handlers/eval.go | 13 +- server/handlers/hitl.go | 2 +- server/handlers/mcp.go | 11 +- server/handlers/memory.go | 7 +- server/handlers/middleware.go | 11 +- server/handlers/remote_agent.go | 9 +- server/handlers/room.go | 5 +- server/handlers/runtime_registry.go | 6 +- server/handlers/session.go | 11 +- server/handlers/system.go | 5 +- server/handlers/tool.go | 9 +- server/handlers/websocket.go | 6 +- server/handlers/workflow.go | 17 +-- server/middleware.go | 2 +- server/ratelimit/middleware.go | 13 +- server/server.go | 3 +- server/server_integration_test.go | 2 +- studio/public/vite.svg | 2 +- studio/src/assets/react.svg | 2 +- .../Thinking/AskUserQuestionCard.vue | 20 +-- ui/src/components/Thinking/ThinkingBlock.vue | 6 +- ui/src/components/Tools/EditorTool.vue | 2 +- 341 files changed, 2086 insertions(+), 2039 deletions(-) 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 @@
-
@@ -26,8 +26,8 @@
-
@@ -37,7 +37,7 @@
-

@@ -57,13 +57,13 @@ :disabled="isQuestionAnswered(idx) || isSubmitted" @click="handleOptionSelect(idx, option.label)" > -
- @@ -191,7 +191,7 @@ const isCompleted = computed(() => { const handleOptionSelect = (questionIdx: number, value: string) => { if (isSubmitted.value) return if (answers.value[questionIdx]) return - + answers.value = { ...answers.value, [questionIdx]: value } checkAndSubmit() } @@ -200,7 +200,7 @@ const handleTextSubmit = (questionIdx: number) => { if (isSubmitted.value) return const text = textInputs.value[questionIdx]?.trim() if (!text || answers.value[questionIdx]) return - + answers.value = { ...answers.value, [questionIdx]: text } checkAndSubmit() } diff --git a/ui/src/components/Thinking/ThinkingBlock.vue b/ui/src/components/Thinking/ThinkingBlock.vue index e027bce..b7f36fc 100644 --- a/ui/src/components/Thinking/ThinkingBlock.vue +++ b/ui/src/components/Thinking/ThinkingBlock.vue @@ -168,19 +168,19 @@ watch( ([newPendingApproval, newIsFinished], oldValue) => { const oldPendingApproval = oldValue?.[0] const oldIsFinished = oldValue?.[1] - + // 核心规则:正在运行时必须展开 if (!newIsFinished) { isExpanded.value = true return } - + // 有新的审批请求时展开 if (newPendingApproval && !oldPendingApproval) { isExpanded.value = true return } - + // 刚刚完成时,延迟折叠 if (newIsFinished && !oldIsFinished && !newPendingApproval) { setTimeout(() => { diff --git a/ui/src/components/Tools/EditorTool.vue b/ui/src/components/Tools/EditorTool.vue index 31db32b..5f904ee 100644 --- a/ui/src/components/Tools/EditorTool.vue +++ b/ui/src/components/Tools/EditorTool.vue @@ -1494,4 +1494,4 @@ watch([findQuery, findOptions], () => { .animate-spin { @apply animate-spin; } - \ No newline at end of file +