first commit

This commit is contained in:
2026-08-14 23:41:57 +08:00
commit 086803a8dd
471 changed files with 91938 additions and 0 deletions
+258
View File
@@ -0,0 +1,258 @@
package agent
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"sync"
)
// 支持的接口类型。
const (
ProviderOpenAI = "openai"
ProviderAnthropic = "anthropic"
)
// 协作 worker 管理模式。
const (
// CoopModeDocker 通过 Docker 容器管理 worker(需本机/远程 Docker daemon)。
CoopModeDocker = "docker"
// CoopModeLocal 通过本地子进程管理 worker(无需 Docker,托管沙箱等场景)。
CoopModeLocal = "local"
)
// LLM 配置环境变量名。环境变量优先级最高,覆盖 api-config.json 用户配置与内置默认值。
// 适用于托管沙箱等场景:平台注入环境变量,Agent 启动即生效,无需通过 Web 设置页配置。
const (
EnvLLMAPIKey = "LLM_API_KEY"
EnvLLMBaseURL = "LLM_BASE_URL"
EnvLLMModel = "LLM_MODEL"
EnvLLMProvider = "LLM_PROVIDER"
EnvLLMEngine = "LLM_ENGINE"
EnvCoopMode = "COOP_MODE"
)
// APIConfig 是用户可配置的 LLM API 连接信息。
type APIConfig struct {
// APIKey 是模型服务商 API Key。
APIKey string `json:"api_key,omitempty"`
// BaseURL 是模型接口地址。
BaseURL string `json:"base_url,omitempty"`
// Model 是模型名称。
Model string `json:"model,omitempty"`
// Provider 是接口类型:openaiOpenAI 兼容)或 anthropicAnthropic Messages API)。
Provider string `json:"provider,omitempty"`
// Engine 是默认协作引擎:pi(默认)/ pigo / claude。
// 仅影响 run_coop 未显式指定 engine 时的默认值,见 tools_coop.go。
Engine string `json:"engine,omitempty"`
// CoopMode 是协作 worker 管理模式:local(默认,本地子进程)/ docker(Docker 容器)。
// local 模式无需 Docker,适用于托管沙箱等无 Docker 环境;docker 模式需本机/远程 Docker daemon。
CoopMode string `json:"coop_mode,omitempty"`
}
// APIConfigStore 持久化用户的 LLM API 配置,未配置的字段回退到默认值(环境变量)。
type APIConfigStore struct {
mu sync.RWMutex
path string
config APIConfig
defaults APIConfig
}
func NewAPIConfigStore(dataDir string, defaults APIConfig) (*APIConfigStore, error) {
store := &APIConfigStore{
path: filepath.Join(dataDir, "api-config.json"),
defaults: defaults,
}
store.load()
return store, nil
}
func (s *APIConfigStore) load() {
data, err := os.ReadFile(s.path)
if err != nil {
return
}
if len(data) == 0 {
return
}
var config APIConfig
if err := json.Unmarshal(data, &config); err != nil {
return
}
s.config = config
}
func (s *APIConfigStore) persist() error {
data, err := json.MarshalIndent(s.config, "", " ")
if err != nil {
return err
}
tmp := s.path + ".tmp"
if err := os.WriteFile(tmp, data, 0o600); err != nil {
return err
}
return os.Rename(tmp, s.path)
}
// APIKey 返回生效的 API Key。
// 优先级:环境变量 LLM_API_KEY > api-config.json > 内置默认值。
func (s *APIConfigStore) APIKey() string {
if v := strings.TrimSpace(os.Getenv(EnvLLMAPIKey)); v != "" {
return v
}
s.mu.RLock()
defer s.mu.RUnlock()
if s.config.APIKey != "" {
return s.config.APIKey
}
return s.defaults.APIKey
}
// BaseURL 返回生效的接口地址。
// 优先级:环境变量 LLM_BASE_URL > api-config.json > 内置默认值。
func (s *APIConfigStore) BaseURL() string {
if v := strings.TrimSpace(os.Getenv(EnvLLMBaseURL)); v != "" {
return v
}
s.mu.RLock()
defer s.mu.RUnlock()
if s.config.BaseURL != "" {
return s.config.BaseURL
}
return s.defaults.BaseURL
}
// Model 返回生效的模型名称。
// 优先级:环境变量 LLM_MODEL > api-config.json > 内置默认值。
func (s *APIConfigStore) Model() string {
if v := strings.TrimSpace(os.Getenv(EnvLLMModel)); v != "" {
return v
}
s.mu.RLock()
defer s.mu.RUnlock()
if s.config.Model != "" {
return s.config.Model
}
return s.defaults.Model
}
// IsAPIKeyConfigured 报告是否已配置 API Key(含环境变量、用户配置与内置默认值)。
// LLM 客户端实际会按 APIKey() 的优先级(环境变量 > api-config.json > 默认值)取用,
// 因此只要最终能拿到非空 Key 就视为已配置,避免误判导致无法对话。
func (s *APIConfigStore) IsAPIKeyConfigured() bool {
if v := strings.TrimSpace(os.Getenv(EnvLLMAPIKey)); v != "" {
return true
}
s.mu.RLock()
defer s.mu.RUnlock()
return s.config.APIKey != "" || s.defaults.APIKey != ""
}
// Provider 返回生效的接口类型。
// 优先级:环境变量 LLM_PROVIDER > api-config.json > 内置默认值 > openai。
func (s *APIConfigStore) Provider() string {
if v := strings.TrimSpace(os.Getenv(EnvLLMProvider)); v != "" {
if v == ProviderOpenAI || v == ProviderAnthropic {
return v
}
}
s.mu.RLock()
defer s.mu.RUnlock()
if s.config.Provider != "" {
return s.config.Provider
}
if s.defaults.Provider != "" {
return s.defaults.Provider
}
return ProviderOpenAI
}
// Engine 返回生效的默认协作引擎:pi(默认)/ pigo / claude。
// 优先级:环境变量 LLM_ENGINE > api-config.json > 内置默认值 > pi。
// 仅在 run_coop 未显式指定 engine 时使用。
func (s *APIConfigStore) Engine() string {
if v := strings.TrimSpace(os.Getenv(EnvLLMEngine)); v != "" {
switch v {
case "pi", "pigo", "claude":
return v
}
}
s.mu.RLock()
defer s.mu.RUnlock()
switch s.config.Engine {
case "pi", "pigo", "claude":
return s.config.Engine
}
if s.defaults.Engine != "" {
return s.defaults.Engine
}
return "pi"
}
// CoopMode 返回生效的协作 worker 管理模式:local(默认)/ docker。
// 优先级:环境变量 COOP_MODE > api-config.json > 内置默认值 > local。
// local 模式通过本地子进程运行 worker(无需 Docker),docker 模式通过 Docker 容器运行。
func (s *APIConfigStore) CoopMode() string {
if v := strings.TrimSpace(os.Getenv(EnvCoopMode)); v != "" {
switch v {
case CoopModeDocker, CoopModeLocal:
return v
}
}
s.mu.RLock()
defer s.mu.RUnlock()
switch s.config.CoopMode {
case CoopModeDocker, CoopModeLocal:
return s.config.CoopMode
}
if s.defaults.CoopMode != "" {
return s.defaults.CoopMode
}
return CoopModeLocal
}
// Update 更新用户配置。每个参数为 nil 表示不修改该字段;
// 非 nil(含空字符串)表示设置或清除该字段(空串=清除,回退默认)。
func (s *APIConfigStore) Update(apiKey, baseURL, model, provider, engine, coopMode *string) error {
s.mu.Lock()
defer s.mu.Unlock()
if apiKey != nil {
s.config.APIKey = *apiKey
}
if baseURL != nil {
s.config.BaseURL = *baseURL
}
if model != nil {
s.config.Model = *model
}
if provider != nil {
value := strings.TrimSpace(*provider)
if value != ProviderOpenAI && value != ProviderAnthropic {
value = ""
}
s.config.Provider = value
}
if engine != nil {
value := strings.TrimSpace(*engine)
switch value {
case "pi", "pigo", "claude":
// 合法值
default:
value = ""
}
s.config.Engine = value
}
if coopMode != nil {
value := strings.TrimSpace(*coopMode)
switch value {
case CoopModeDocker, CoopModeLocal:
// 合法值
default:
value = ""
}
s.config.CoopMode = value
}
return s.persist()
}