// Package context 管理 Agent 的上下文构建和压缩. // // 核心职责: // - 构建完整的系统提示词(角色定义 + 工具指南 + 环境信息 + 用户追加) // - 检测当前环境信息(Git 状态,操作系统,shell 等) // - 管理上下文窗口(自动压缩,微压缩) // // 设计要点: // - 零 UI 依赖,纯逻辑 // - 环境检测通过 os/exec 调用系统命令,不引入外部库 // - 系统提示词是可组合的,每个模块独立定义 // - Bundle + SectionRegistry 支持静态/动态分离缓存(模块 15) package context import ( "context" "fmt" "os" "os/exec" "runtime" "strings" gitlib "git.flytoex.net/yuanwei/flyto-agent/internal/syslib/git" ) // Builder 构建发送给 API 的上下文(系统提示 + 用户上下文 + 工具描述). // // 将系统提示构建逻辑收敛到一个结构体中. // // 升华改进(ELEVATED): 在早期方案字符串拼接基础上增加 Bundle + SectionRegistry 支持-- // - BuildSystemPromptBlocks() 返回 []SystemPromptBlock(带缓存语义) // - BuildSystemPrompt() 向后兼容,内部调用 BuildSystemPromptBlocks 后 join 为字符串 // // 替代方案:<废弃 BuildSystemPrompt,只保留 BuildSystemPromptBlocks> // - 否决原因:其他调用方(测试,子模块)还依赖字符串形式,激进废弃代价高. type Builder struct { // cwd 是当前工作目录 cwd string // systemPrompt 是自定义系统提示(如果设置,覆盖默认提示词,完全绕过 Bundle) systemPrompt string // appendPrompt 追加到系统提示末尾的额外内容 appendPrompt string // maxTokens 模型上下文窗口大小 maxTokens int // toolDescriptions 是工具描述列表(动态注入到系统提示中) toolDescriptions []ToolDescription // evolveFragment 是进化能力提示(如果启用进化功能) evolveFragment string // modelID 是当前使用的模型 ID,注入到 env_info section modelID string // bundleRegistry Bundle 注册表(nil 时使用 NewDefaultBundleRegistry()) bundleRegistry *BundleRegistry // sectionRegistry Section 计算缓存(nil 时每次 build 都重新计算) sectionRegistry *SectionRegistry // bundleKey 指定使用哪个 Bundle(零值 = DefaultBundleKey) bundleKey BundleKey // enableCaching 是否启用分块缓存语义 enableCaching bool // mcpServerStatuses is a per-build snapshot of MCP server connection // state, consumed by the mcp_servers volatile section so the model // sees an up-to-date view every turn. // // mcpServerStatuses 是 per-build 的 MCP server 连接状态快照, // mcp_servers volatile section 消费它, 让模型每轮看到最新视图. mcpServerStatuses []MCPServerStatus } // ToolDescription 是工具的简要描述,用于注入到系统提示中. type ToolDescription struct { Name string // 工具名称 Description string // 工具描述 } // EnvInfo 是运行时环境信息. // 注入到系统提示词中,让模型了解当前工作环境. type EnvInfo struct { // Cwd 当前工作目录 Cwd string // Platform 操作系统平台(linux/darwin/windows) Platform string // Shell 用户 shell(bash/zsh/fish 等) Shell string // Hostname 主机名 Hostname string // GitBranch 当前 Git 分支(如果在 Git 仓库中) GitBranch string // GitStatus Git 工作区状态摘要 GitStatus string // IsGitRepo 是否在 Git 仓库中 IsGitRepo bool // OSVersion 操作系统版本信息 OSVersion string } // NewBuilder 创建上下文构建器. // 默认使用 DefaultBundleKey(claude+programming)和内置 DefaultBundle. func NewBuilder(cwd string, maxTokens int) *Builder { if maxTokens <= 0 { maxTokens = 200000 // 默认上下文窗口 } return &Builder{ cwd: cwd, maxTokens: maxTokens, bundleKey: DefaultBundleKey, bundleRegistry: NewDefaultBundleRegistry(), sectionRegistry: NewSectionRegistry(), } } // SetSystemPrompt 设置自定义系统提示(覆盖默认). func (b *Builder) SetSystemPrompt(prompt string) { b.systemPrompt = prompt } // SetAppendPrompt 设置追加到系统提示末尾的额外内容. func (b *Builder) SetAppendPrompt(prompt string) { b.appendPrompt = prompt } // SetToolDescriptions 设置工具描述列表. func (b *Builder) SetToolDescriptions(descs []ToolDescription) { b.toolDescriptions = descs } // SetEvolveFragment 设置进化能力提示片段. func (b *Builder) SetEvolveFragment(fragment string) { b.evolveFragment = fragment } // SetModelID 设置当前使用的模型 ID(注入到 env_info section). func (b *Builder) SetModelID(modelID string) { b.modelID = modelID } // SetBundleRegistry 设置 Bundle 注册表(替换默认注册表). // 通常不需要调用--消费层向引擎注册 Bundle,引擎把注册表传入 Builder. func (b *Builder) SetBundleRegistry(r *BundleRegistry) { b.bundleRegistry = r } // SetSectionRegistry 设置 Section 缓存(替换每次 build 都重新分配的默认行为). // Engine 实例应当把自己持有的 SectionRegistry 传入,使缓存跨 build 调用复用. func (b *Builder) SetSectionRegistry(r *SectionRegistry) { b.sectionRegistry = r } // SetBundleKey 设置使用的 Bundle 标识符. // 零值(BundleKey{})等价于 DefaultBundleKey(claude+programming). func (b *Builder) SetBundleKey(key BundleKey) { b.bundleKey = key } // SetEnableCaching 设置是否启用分块缓存语义(影响 BuildSystemPromptBlocks 返回的 CacheScope). func (b *Builder) SetEnableCaching(enable bool) { b.enableCaching = enable } // SetMCPServerStatuses injects the per-build MCP server connection snapshot // consumed by the mcp_servers volatile section. Pass an empty slice (or // leave unset) to indicate no MCP servers configured — the section then // yields an empty string and is dropped. // // SetMCPServerStatuses 注入 per-build 的 MCP server 连接快照, 由 // mcp_servers volatile section 消费. 传空 slice (或不设) 表示未配置任何 // MCP server -- section 返回空串被过滤. func (b *Builder) SetMCPServerStatuses(statuses []MCPServerStatus) { b.mcpServerStatuses = statuses } // BuildSystemPromptBlocks 组装完整的系统提示词,返回带缓存语义的 API 块列表. // // 静态 sections(角色定义,行为准则等)→ CacheScope="ephemeral"(全局可缓存) // 动态 sections(环境信息,FLYTO.md 等)→ CacheScope="ephemeral"(会话级缓存) // volatile sections(MCP 连接等)→ CacheScope=""(不缓存) // // 如果设置了自定义 systemPrompt(覆盖模式),返回单块(不走 Bundle). // ctx 用于 Section 的 Compute 函数读取注入值(cwd,modelID 等). func (b *Builder) BuildSystemPromptBlocks(ctx context.Context) []SystemPromptBlock { // 精妙之处(CLEVER): nil context 防护-- // Go 的 context.WithValue 在 nil ctx 时会 panic. // 允许调用方传 nil(兼容旧代码),内部自动替换为 Background. if ctx == nil { ctx = context.Background() } // 覆盖模式:用户自定义系统提示完全绕过 Bundle if b.systemPrompt != "" { return b.buildOverrideBlocks(ctx) } // 确保有 BundleRegistry 和 SectionRegistry reg := b.bundleRegistry if reg == nil { reg = NewDefaultBundleRegistry() } secReg := b.sectionRegistry if secReg == nil { secReg = NewSectionRegistry() } // 解析 Bundle key := b.bundleKey if key == (BundleKey{}) { key = DefaultBundleKey } bundle := reg.Resolve(key) if bundle == nil { // 没有任何 Bundle:回退到旧版字符串组装 return []SystemPromptBlock{{Text: b.buildLegacyString(ctx)}} } // 将运行时状态注入 ctx,供 dynamic sections 的 Compute 读取 ctx = WithCwd(ctx, b.cwd) ctx = WithModelID(ctx, b.modelID) ctx = WithToolDescriptions(ctx, b.toolDescriptions) ctx = WithEvolveFragment(ctx, b.evolveFragment) ctx = WithAppendPrompt(ctx, b.appendPrompt) ctx = WithMCPServerStatuses(ctx, b.mcpServerStatuses) return BuildPromptBlocks(ctx, bundle, secReg, b.enableCaching) } // BuildSystemPrompt 组装完整的系统提示词,返回字符串. // // 向后兼容方法:内部调用 BuildSystemPromptBlocks 后合并为字符串. // 新代码应优先使用 BuildSystemPromptBlocks 以获得分块缓存语义. func (b *Builder) BuildSystemPrompt() string { ctx := context.Background() blocks := b.BuildSystemPromptBlocks(ctx) return BlocksToString(blocks) } // buildOverrideBlocks 处理用户自定义系统提示的情况. // 自定义提示不走 Bundle,但仍然可以附加动态内容(工具描述,环境信息等). func (b *Builder) buildOverrideBlocks(ctx context.Context) []SystemPromptBlock { var parts []string parts = append(parts, b.systemPrompt) // 附加动态内容(沿用旧版逻辑) instructions := LoadInstructions(b.cwd) if s := formatInstructionsSection(instructions); s != "" { parts = append(parts, s) } if len(b.toolDescriptions) > 0 { parts = append(parts, b.buildToolSection()) } envInfo := CollectEnvInfo(b.cwd) parts = append(parts, b.buildEnvSection(envInfo)) if b.evolveFragment != "" { parts = append(parts, b.evolveFragment) } if b.appendPrompt != "" { parts = append(parts, b.appendPrompt) } text := strings.Join(parts, "\n\n") if !b.enableCaching { return []SystemPromptBlock{{Text: text}} } return []SystemPromptBlock{{Text: text, CacheScope: "ephemeral"}} } // buildLegacyString 是无 Bundle 时的字符串组装降级路径(向后兼容). func (b *Builder) buildLegacyString(_ context.Context) string { var parts []string parts = append(parts, BuildDefaultSystemPrompt()) instructions := LoadInstructions(b.cwd) if s := formatInstructionsSection(instructions); s != "" { parts = append(parts, s) } if len(b.toolDescriptions) > 0 { parts = append(parts, b.buildToolSection()) } envInfo := CollectEnvInfo(b.cwd) parts = append(parts, b.buildEnvSection(envInfo)) if b.evolveFragment != "" { parts = append(parts, b.evolveFragment) } if b.appendPrompt != "" { parts = append(parts, b.appendPrompt) } return strings.Join(parts, "\n\n") } // buildToolSection 构建工具描述段落. // 列出所有可用工具的名称和简要描述,让模型知道有哪些工具可用. func (b *Builder) buildToolSection() string { var sb strings.Builder sb.WriteString("# Available Tools\n\n") sb.WriteString("You have access to the following tools:\n\n") for _, td := range b.toolDescriptions { sb.WriteString(fmt.Sprintf("- **%s**: %s\n", td.Name, td.Description)) } return sb.String() } // buildEnvSection 构建环境信息段落. // 让模型了解当前运行环境,以便给出更精确的建议. func (b *Builder) buildEnvSection(info *EnvInfo) string { var sb strings.Builder sb.WriteString("# Environment\n\n") sb.WriteString(fmt.Sprintf("- Working directory: %s\n", info.Cwd)) sb.WriteString(fmt.Sprintf("- Platform: %s\n", info.Platform)) if info.Shell != "" { sb.WriteString(fmt.Sprintf("- Shell: %s\n", info.Shell)) } if info.Hostname != "" { sb.WriteString(fmt.Sprintf("- Hostname: %s\n", info.Hostname)) } if info.OSVersion != "" { sb.WriteString(fmt.Sprintf("- OS Version: %s\n", info.OSVersion)) } if info.IsGitRepo { sb.WriteString(fmt.Sprintf("- Git repo: yes\n")) if info.GitBranch != "" { sb.WriteString(fmt.Sprintf("- Git branch: %s\n", info.GitBranch)) } if info.GitStatus != "" { sb.WriteString(fmt.Sprintf("- Git status: %s\n", info.GitStatus)) } } else { sb.WriteString("- Git repo: no\n") } return sb.String() } // CollectEnvInfo 收集当前运行时环境信息. // 通过系统命令和 Go 标准库获取各种环境参数. func CollectEnvInfo(cwd string) *EnvInfo { info := &EnvInfo{ Cwd: cwd, Platform: runtime.GOOS, } // 检测 shell info.Shell = detectShell() // 检测主机名 if hostname, err := os.Hostname(); err == nil { info.Hostname = hostname } // 检测 OS 版本 info.OSVersion = detectOSVersion() // 检测 Git 状态 detectGitInfo(cwd, info) return info } // detectShell 检测用户使用的 shell. func detectShell() string { // 优先检查 SHELL 环境变量 if shell := os.Getenv("SHELL"); shell != "" { // 提取 shell 名称(如 /bin/zsh -> zsh) parts := strings.Split(shell, "/") return parts[len(parts)-1] } // 回退检查 COMSPEC(Windows) if comspec := os.Getenv("COMSPEC"); comspec != "" { parts := strings.Split(comspec, "\\") return parts[len(parts)-1] } return "" } // detectOSVersion 检测操作系统版本. func detectOSVersion() string { switch runtime.GOOS { case "linux": // 尝试 uname -r if out, err := exec.Command("uname", "-r").Output(); err == nil { version := strings.TrimSpace(string(out)) return "Linux " + version } case "darwin": // 尝试 sw_vers -productVersion if out, err := exec.Command("sw_vers", "-productVersion").Output(); err == nil { return "macOS " + strings.TrimSpace(string(out)) } } return runtime.GOOS + "/" + runtime.GOARCH } // detectGitInfo fills EnvInfo's git fields via the syslib/git primitive, // folding raw porcelain status into the "clean" / "N file(s) changed" // human summary expected by the prompt. // // detectGitInfo 调用 syslib/git 原语填充 EnvInfo 的 git 字段, 把 raw // porcelain status 折叠为 prompt 期望的 "clean" / "N file(s) changed" // 人类摘要形式. func detectGitInfo(cwd string, info *EnvInfo) { gi := gitlib.GetInfo(cwd) info.IsGitRepo = gi.IsRepo if !gi.IsRepo { return } info.GitBranch = gi.Branch if gi.Status == "" { info.GitStatus = "clean" } else { lines := strings.Split(gi.Status, "\n") info.GitStatus = fmt.Sprintf("%d file(s) changed", len(lines)) } } // Compressor 已移至 compact.go,包含 Policy 和 RestoreManager 的集成.