package core import ( "encoding/json" "strings" ) // 智能体画像(4.7):核心定制机制,全部数据化、可导入导出。 type AgentProfile struct { ID string `json:"id"` Owner string `json:"owner,omitempty"` // 租户;空 = 共享(builtin) Name string `json:"name"` Description string `json:"description,omitempty"` Builtin bool `json:"builtin,omitempty"` Source string `json:"source,omitempty"` // builtin | plugin | user SystemPrompt string `json:"systemPrompt"` // PromptModules 引用提示词库模块 ID(有序;空 = 组装默认基座全集)。 // SystemPrompt 兼容保留:非空时作为附加段追加在模块组装结果之后。 PromptModules []string `json:"promptModules,omitempty"` Model ModelRoute `json:"model"` Tools ToolPolicy `json:"tools"` Context ContextPolicy `json:"context"` HITL HITLPolicy `json:"hitl"` Budget RunBudget `json:"budget"` } // ModelRoute 按用途选择模型(4.7 kind 路由):空串回落到 chat 默认。 type ModelRoute struct { Chat string `json:"chat,omitempty"` Lightweight string `json:"lightweight,omitempty"` Embedding string `json:"embedding,omitempty"` Rerank string `json:"rerank,omitempty"` } // ToolPolicy 三清单:allow/deny 决定可见工具,confirm 强制执行前 HITL 确认。 // 匹配语义:精确名或前缀通配("http.*" 表示 http 前缀);deny 优先于 allow。 type ToolPolicy struct { Allow []string `json:"allow"` // ["*"] 全部 Deny []string `json:"deny,omitempty"` Confirm []string `json:"confirm,omitempty"` } // ContextPolicy(4.10):ctxasm 的输入;逐版本增加策略项不动组装器接口。 type ContextPolicy struct { MaxHistoryMessages int `json:"maxHistoryMessages"` // 0 = 默认 40 SummarizeBeyond bool `json:"summarizeBeyond,omitempty"` // 远期;先存标志 MemoryBudgetPct int `json:"memoryBudgetPct,omitempty"` // 注入槽份额上限 KnowledgeBudgetPct int `json:"knowledgeBudgetPct,omitempty"` MaxToolResultChars int `json:"maxToolResultChars,omitempty"` // 0 = 默认 20000 } type HITLPolicy struct { AskOnAmbiguity bool `json:"askOnAmbiguity"` TimeoutSec *int `json:"timeoutSec,omitempty"` } type RunBudget struct { MaxTokens *int `json:"maxTokens,omitempty"` MaxToolCalls *int `json:"maxToolCalls,omitempty"` MaxTurns *int `json:"maxTurns,omitempty"` // LLM 循环轮上限 } // ToolAllows 判定:deny 优先;allow 含 "*" 或精确/前缀命中。 func (p ToolPolicy) ToolAllowed(name string) bool { if matchAny(p.Deny, name) { return false } if len(p.Allow) == 0 { return true } return matchAny(p.Allow, name) } func (p ToolPolicy) NeedsConfirm(name string) bool { return matchAny(p.Confirm, name) } // matchAny 语义:pattern 精确等于 name,或以 "xxx." 开头表示前缀族(http. → http.*)。 func matchAny(patterns []string, name string) bool { for _, p := range patterns { if p == "*" || p == name { return true } if n := len(p); n > 1 && p[n-1] == '.' { if len(name) > n-1 && name[:n-1] == p[:n-1] { return true } } } return false } // ---- 设置(4.9)---- type SettingType string const ( SettingString SettingType = "string" SettingNumber SettingType = "number" SettingBool SettingType = "boolean" SettingEnum SettingType = "enum" SettingSecret SettingType = "secret" ) type SettingEffect string const ( EffectHot SettingEffect = "hot" // 立即生效 EffectNewRun SettingEffect = "new-run" // 仅影响新 run EffectRestart SettingEffect = "restart" // 需重启 ) // SettingScope 声明设置项的写入归属(提案 2026-10-13-rdm-settings-members-duties-perms-increment P2)。 // 空串 = 默认:user 与 project 层可写(向后兼容既有 schema)。 type SettingScope string const ( // SettingScopeSystem 受管项:仅系统管理面(admin)可写;user/project 写入被拒, // 生效链忽略这两层的存量覆盖值。 SettingScopeSystem SettingScope = "system" ) // SettingDesc 描述一个设置项;设置页按此渲染并标注生效时机(schema 驱动)。 type SettingDesc struct { Key string `json:"key"` Group string `json:"group"` // 分区:models | profiles | tools | knowledge | ui | plugin: Label string `json:"label"` Description string `json:"description,omitempty"` Type SettingType `json:"type"` Default any `json:"default,omitempty"` Options []string `json:"options,omitempty"` // enum Effect SettingEffect `json:"effect"` Secret bool `json:"secret,omitempty"` Scope SettingScope `json:"scope,omitempty"` // 空 = user/project 可写;system = 受管 } // ManagedPaths 返回 schema 中 scope=system 的键路径(点路径形式)。 func ManagedPaths(descs []SettingDesc) []string { out := make([]string, 0, len(descs)) for _, d := range descs { if d.Scope == SettingScopeSystem { out = append(out, d.Key) } } return out } // FlattenSettingLeafPaths 列出 values 的全部叶子点路径。子树为标量或数组时该子树 // 根即叶子。用于受管触碰检测:深合并语义下叶子决定最终覆盖面。 func FlattenSettingLeafPaths(values map[string]any) []string { var out []string var walk func(prefix string, m map[string]any) walk = func(prefix string, m map[string]any) { for k, v := range m { path := k if prefix != "" { path = prefix + "." + k } if vm, ok := v.(map[string]any); ok && len(vm) > 0 { walk(path, vm) continue } out = append(out, path) } } walk("", values) return out } // pathTouchesManaged 判断 path 是否触碰 managed:等于受管路径、位于受管子树内 // (受管路径是 path 的前缀),或整体覆盖受管子树(path 是受管路径的前缀—— // 标量覆盖整棵受管 map 同样破坏受管值)。 func pathTouchesManaged(path string, managed []string) bool { for _, mp := range managed { if path == mp || strings.HasPrefix(path, mp+".") || strings.HasPrefix(mp, path+".") { return true } } return false } // TouchesManagedSettings 返回 values 中触碰受管路径的叶子;空切片 = 无触碰。 // 调用方须传入已展开点路径的嵌套 map(normalize 后)。 func TouchesManagedSettings(values map[string]any, managed []string) []string { var hits []string for _, p := range FlattenSettingLeafPaths(values) { if pathTouchesManaged(p, managed) { hits = append(hits, p) } } return hits } // StripManagedSettings 返回删除受管路径后的浅拷贝(不改入参)。受管路径的祖先 // 若因此只剩空 map 一并删除。入参须为嵌套形式(点路径已展开)。 func StripManagedSettings(values map[string]any, managed []string) map[string]any { if len(values) == 0 || len(managed) == 0 { return values } // 叶子命中或祖先整体命中都删;逐叶子重建最简单可靠。 leaves := FlattenSettingLeafPaths(values) keep := map[string]any{} for _, leaf := range leaves { if pathTouchesManaged(leaf, managed) { continue } setPath(keep, leaf, lookupAny(values, leaf)) } return keep } func setPath(m map[string]any, path string, val any) { parts := strings.Split(path, ".") cur := m for i, p := range parts { if i == len(parts)-1 { cur[p] = val return } next, ok := cur[p].(map[string]any) if !ok { next = map[string]any{} cur[p] = next } cur = next } } func lookupAny(m map[string]any, path string) any { parts := strings.Split(path, ".") var cur any = m for _, p := range parts { cm, ok := cur.(map[string]any) if !ok { return nil } cur, ok = cm[p] if !ok { return nil } } return cur } // EffectiveSettings 是覆盖链(system → user → project → conversation)深合并结果。 type EffectiveSettings map[string]any // MergeSettings 深合并:后者覆盖前者;map 递归合并,其它类型直接覆盖(4.9 数组显式覆盖)。 func MergeSettings(base, override map[string]any) map[string]any { out := make(map[string]any, len(base)+len(override)) for k, v := range base { out[k] = v } for k, v := range override { if bm, ok := base[k].(map[string]any); ok { if vm, ok2 := v.(map[string]any); ok2 { out[k] = MergeSettings(bm, vm) continue } } out[k] = v } return out } // GetString/GetBool/GetNumber 读取点路径("models.chat");缺省返回 def。 func (s EffectiveSettings) GetString(path, def string) string { v, ok := lookupPath(s, path) if !ok { return def } if str, ok2 := v.(string); ok2 { return str } return def } func (s EffectiveSettings) GetBool(path string, def bool) bool { v, ok := lookupPath(s, path) if !ok { return def } if b, ok2 := v.(bool); ok2 { return b } return def } func (s EffectiveSettings) GetNumber(path string, def float64) float64 { v, ok := lookupPath(s, path) if !ok { return def } if f, ok2 := v.(float64); ok2 { return f } return def } func lookupPath(m map[string]any, path string) (any, bool) { parts := splitPath(path) var cur any = m for _, p := range parts { cm, ok := cur.(map[string]any) if !ok { return nil, false } cur, ok = cm[p] if !ok { return nil, false } } return cur, true } func splitPath(path string) []string { var out []string cur := "" for i := 0; i < len(path); i++ { switch path[i] { case '.': out = append(out, cur) cur = "" default: cur += string(path[i]) } } return append(out, cur) } var _ = json.Marshal // 保持 encoding/json 引用(RawMessage 一致性)