feat: AutoZero 通用 Agent 框架(从 AutoGo 五阶段拆分完成)

llm(三协议 Provider)/ session(JSONL 持久化)/ tools(Registry、围栏、
builtin、computer)/ cdp(browser)/ skills / mcp / agent(循环、两级压缩、
编排、审批门控)+ 底座 jsonl/textx/execx/diag/scratch/spill/imagecache。

产品装配代码(config 桥接、schedule 工具、memory/plan/retro/prompt)
留在 AutoGo,宿主接入方式见 MIGRATION.md。
This commit is contained in:
Sakurasan committed 2026-10-26 00:54:46 +08:00
commit d5dac5a6fc
140 files changed
+26497

No files matched your search

+152
View File
@@ -0,0 +1,152 @@
package llm
import (
"fmt"
"strings"
)
// APIFormat 是自定义供应商的 API 协议格式。
type APIFormat = string
const (
// FormatAnthropic 走 Anthropic Messages API(/v1/messages)。
FormatAnthropic = "anthropic"
// FormatChatCompletions 走 OpenAI Chat Completions(/chat/completions)。
FormatChatCompletions = "chat_completions"
// FormatResponses 走 OpenAI Responses API(/responses)。
FormatResponses = "responses"
)
// NormalizeAPIFormat 归一化用户输入的格式别名;未知格式返回空串。
func NormalizeAPIFormat(s string) APIFormat {
switch strings.ToLower(strings.TrimSpace(s)) {
case "anthropic", "anthropic_messages", "messages":
return FormatAnthropic
case "chat_completions", "chatcompletions", "openai", "chat":
return FormatChatCompletions
case "responses", "response":
return FormatResponses
}
return ""
}
// ProviderSpec 描述一个模型供应商的接入参数。
type ProviderSpec struct {
Name string // 展示名
BaseURL string
APIKey string
APIFormat APIFormat
Model string // 默认模型
AllowedModels []string // /models 白名单;空 = 不筛选
MaxTokens int // anthropic 单次回复上限
}
// NewProvider 按 spec 构造对应协议的 Provider。
func NewProvider(spec ProviderSpec) (Provider, error) {
format := spec.APIFormat
if format == "" {
format = guessFormat(spec.BaseURL)
}
switch format {
case FormatAnthropic:
return &AnthropicProvider{BaseURL: spec.BaseURL, APIKey: spec.APIKey, Model: spec.Model, MaxTokens: spec.MaxTokens}, nil
case FormatResponses:
return &ResponsesProvider{BaseURL: spec.BaseURL, APIKey: spec.APIKey, Model: spec.Model}, nil
case FormatChatCompletions:
return &OpenAIProvider{BaseURL: spec.BaseURL, APIKey: spec.APIKey, Model: spec.Model}, nil
}
return nil, fmt.Errorf("llm: unknown api_format %q (want anthropic | chat_completions | responses)", spec.APIFormat)
}
// guessFormat 依据 BaseURL 猜协议:含 "anthropic" 视为 anthropic,其余默认 chat_completions。
func guessFormat(baseURL string) APIFormat {
if strings.Contains(strings.ToLower(baseURL), "anthropic") {
return FormatAnthropic
}
return FormatChatCompletions
}
// DedupeModels 去空白、大小写不敏感去重(保留首次出现的原始写法与顺序)。
func DedupeModels(in []string) []string {
seen := make(map[string]bool, len(in))
out := make([]string, 0, len(in))
for _, s := range in {
s = strings.TrimSpace(s)
if s == "" || seen[strings.ToLower(s)] {
continue
}
seen[strings.ToLower(s)] = true
out = append(out, s)
}
if len(out) == 0 {
return nil
}
return out
}
// FilterAllowed 按白名单筛选模型列表;白名单为空返回原列表。
// 支持通配前缀("glm-*")与精确名;大小写不敏感。
func FilterAllowed(models []string, allowed []string) []string {
if len(allowed) == 0 {
return models
}
out := make([]string, 0, len(models))
for _, m := range models {
for _, a := range allowed {
if matchesAllowed(m, a) {
out = append(out, m)
break
}
}
}
return out
}
// matchesAllowed 报告单个模型是否匹配一条条目:精确名(EqualFold)或
// 前缀通配(尾部 *)。FilterAllowed(条目=白名单、模型=候选)与
// MissingRegisteredModels(条目=登记模型、模型=远端目录)共用同一匹配语义,
// 两处口径漂移会让"下架判定"和"白名单筛选"打架。
func matchesAllowed(model, pattern string) bool {
pattern = strings.ToLower(strings.TrimSpace(pattern))
if pattern == "" {
return false
}
if strings.HasSuffix(pattern, "*") {
return strings.HasPrefix(strings.ToLower(model), strings.TrimSuffix(pattern, "*"))
}
return strings.EqualFold(model, pattern)
}
// MissingRegisteredModels 返回登记模型里在 fetched(远端 /models 目录)中
// 已不存在的条目——「已下架」的判定源,TUI 多选表格与 WebUI 供应商页共用。
// 保序、保留原文写法、大小写不敏感去重;只做精确名比对(EqualFold),
// 通配条目(含 *)**不参与下架判定**:模式没法对具体 ID 目录作存在性结论,
// 按"目录里仍有前缀匹配"算存活会掩盖真下架,按字面比对又必然误报。
//
// fetched 为空(拉取失败 / anthropic 无枚举端点)返回 nil:没有目录就没有
// 资格谈下架,误标比漏标危害大。
func MissingRegisteredModels(registered, fetched []string) []string {
if len(registered) == 0 || len(fetched) == 0 {
return nil
}
seen := map[string]bool{}
var out []string
for _, r := range registered {
r = strings.TrimSpace(r)
if r == "" || seen[strings.ToLower(r)] || strings.HasSuffix(r, "*") {
continue // 通配条目跳过判定(见上),不代表存活也不标下架
}
seen[strings.ToLower(r)] = true
alive := false
for _, m := range fetched {
if strings.EqualFold(m, r) {
alive = true
break
}
}
if !alive {
out = append(out, r)
}
}
return out
}