feat: 实现流式输出功能 v0.1.0
- 创建 hxclaw 项目,基于 picoclaw 的 CLI 增强工具 - 实现流式输出,使用 fmt.Print + os.Stdout.Sync() 实时刷新 - 解决 onChunk 回调累积文本导致的重复输出问题 - 使用 strings.Builder 收集完整响应并保存到 session - 添加讨论记录和更新日志文档
This commit is contained in:
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
.DS_Store
|
||||
*.ini
|
||||
|
||||
hxclaw.exe
|
||||
|
||||
.idea/
|
||||
.fleet/
|
||||
.vs/
|
||||
.vscode/
|
||||
.zed/
|
||||
|
||||
# hxclaw specific
|
||||
bin/
|
||||
.hxclaw_history
|
||||
60
agents.md
Normal file
60
agents.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# hxclaw AI 行为指南
|
||||
|
||||
## 开发规范
|
||||
|
||||
### 设计原则
|
||||
|
||||
- **面向对象 + 设计模式**:所有功能拆分成模块,模块可复用
|
||||
- **模块化**:每个功能独立成包,包内高内聚,包间低耦合
|
||||
- **设计模式**:常用单例、工厂、策略模式
|
||||
|
||||
### 代码要求
|
||||
|
||||
- **注释**:全局使用中文注释,注释内容详细,说明意图和实现逻辑
|
||||
- **测试**:所有代码需编写单元测试和功能测试,通过后才可交付
|
||||
- **命名**:变量、函数、文件名使用英文,注释使用中文
|
||||
|
||||
### 交流规范
|
||||
|
||||
- **语言**:全程使用中文回答和思考
|
||||
- **问题处理**:一个问题若超过3次尝试仍无法解决,立即停止,告诉用户遇到的问题,询问用户接下来怎么办
|
||||
- **进度同步**:每次开始编写代码前,更新讨论记录和其他文档
|
||||
|
||||
---
|
||||
|
||||
## 项目背景
|
||||
|
||||
### 定位
|
||||
|
||||
- hxclaw 是 picoclaw 的 CLI 增强工具
|
||||
- 提供流式输出和 Markdown 终端渲染
|
||||
- 作为独立二进制,与 picoclaw 共存
|
||||
|
||||
### 技术栈
|
||||
|
||||
- 语言:Go 1.21+
|
||||
- 依赖:通过 go.mod replace 复用 picoclaw
|
||||
- 终端库:charmbracelet/lipgloss
|
||||
- 测试:Go 标准测试框架
|
||||
|
||||
---
|
||||
|
||||
## 当前任务
|
||||
|
||||
### v0.1.0 目标
|
||||
|
||||
实现流式输出功能:
|
||||
1. 创建 go.mod 配置依赖
|
||||
2. 实现 main.go 入口
|
||||
3. 实现流式 Provider 调用
|
||||
4. 实时打印 token
|
||||
5. 处理非流式 Provider 回退
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 不要修改 picoclaw 源码
|
||||
- 保持代码独立,便于后续版本同步
|
||||
- 优先实现核心功能,再考虑增强功能
|
||||
- 文档和代码同步更新
|
||||
242
changelog.md
Normal file
242
changelog.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# hxclaw 更新日志
|
||||
|
||||
## 版本记录
|
||||
|
||||
### v0.1.0 (规划中)
|
||||
|
||||
- 创建 hxclaw 项目
|
||||
- 实现流式输出功能
|
||||
- Markdown 渲染功能(待实现)
|
||||
- 代码高亮功能(待实现)
|
||||
|
||||
---
|
||||
|
||||
## 待实现功能
|
||||
|
||||
### v0.1.0 (当前)
|
||||
|
||||
- [x] 流式输出功能
|
||||
- [x] 导入 picoclaw 核心库
|
||||
- [x] 实现流式 Provider 调用
|
||||
- [x] 实时打印 token
|
||||
- [x] 处理非流式 Provider 回退
|
||||
|
||||
### v0.2.0 (计划)
|
||||
|
||||
- [ ] Markdown 渲染
|
||||
- [ ] Markdown 解析
|
||||
- [ ] 基础样式(粗体、斜体、链接)
|
||||
- [ ] 代码块渲染
|
||||
- [ ] 表格渲染
|
||||
- [ ] 列表渲染
|
||||
|
||||
### v0.3.0 (计划)
|
||||
|
||||
- [ ] 代码高亮
|
||||
- [ ] 集成 glow 或类似库
|
||||
- [ ] 支持常见语言语法高亮
|
||||
|
||||
---
|
||||
|
||||
## 目前进度
|
||||
|
||||
- [x] 创建项目目录结构
|
||||
- [x] 编写讨论记录(taolun.md)
|
||||
- [x] 编写更新日志(changelog.md)
|
||||
- [x] 编写 AI 行为指南(agents.md)
|
||||
- [x] 创建 go.mod
|
||||
- [x] 实现 main.go 入口
|
||||
- [x] 实现流式输出核心逻辑
|
||||
- [x] 编译成功,生成 hxclaw 二进制
|
||||
|
||||
---
|
||||
|
||||
## 认知纠正(踩坑记录)
|
||||
|
||||
### Go replace 机制不需要发布到 registry
|
||||
|
||||
**问题**:最初担心需要像 npm 那样发布到 registry 才能被其他项目引用
|
||||
|
||||
**纠正**:Go 的 replace 机制可以直接指向:
|
||||
- 本地路径(如 `../picoclaw`)
|
||||
- GitHub 仓库 + tag(如 `github.com/sipeed/picoclaw v0.2.4`)
|
||||
|
||||
**知识点**:Go 模块不需要发布到任何 registry,GitHub 就是事实上的 registry
|
||||
|
||||
---
|
||||
|
||||
### hxclaw 不需要实现全部 picoclaw 功能
|
||||
|
||||
**问题**:最初担心需要自己实现 onboard、tools、mcp 等全部功能
|
||||
|
||||
**纠正**:hxclaw 是 CLI 增强层,只替换交互逻辑。picoclaw 的核心功能(agent loop、tools、mcp、skills)通过导入其 pkg 即可复用
|
||||
|
||||
**知识点**:采用组合优于继承的设计,需要什么功能就导入对应的包
|
||||
|
||||
---
|
||||
|
||||
### 流式输出需要判断 Provider 是否支持
|
||||
|
||||
**问题**:不是所有 Provider 都支持流式输出
|
||||
|
||||
**纠正**:需要使用类型断言判断 Provider 是否实现 `providers.StreamingProvider` 接口:
|
||||
```go
|
||||
if sp, ok := provider.(providers.StreamingProvider); ok {
|
||||
// 使用 ChatStream
|
||||
} else {
|
||||
// 使用普通 Chat
|
||||
}
|
||||
```
|
||||
|
||||
**知识点**:picoclaw 的 Provider 设计使用了接口分离原则,流式是可选能力
|
||||
|
||||
---
|
||||
|
||||
### 终端渲染使用 charmbracelet 库
|
||||
|
||||
**问题**:如何实现 Markdown 终端渲染
|
||||
|
||||
**纠正**:使用 charmbracelet 家族:
|
||||
- lipgloss:样式定义
|
||||
- glow:代码高亮
|
||||
|
||||
**知识点**:charmbracelet 是 Go 终端UI 的事实标准,API 设计优雅
|
||||
|
||||
---
|
||||
|
||||
### 独立二进制部署方式
|
||||
|
||||
**问题**:hxclaw 和 picoclaw 的关系
|
||||
|
||||
**纠正**:hxclaw 作为独立二进制,用户可以同时保留两个命令:
|
||||
- `picoclaw agent` 使用原版
|
||||
- `hxclaw` 使用增强版
|
||||
|
||||
**知识点**:通过 go.mod replace 实现依赖绑定,用户无需安装 picoclaw 源码
|
||||
|
||||
---
|
||||
|
||||
### AgentRegistry 没有 BuildMessages 方法
|
||||
|
||||
**问题**:最初尝试调用 agentLoop.GetRegistry().BuildMessages() 构建消息
|
||||
|
||||
**纠正**:BuildMessages 属于 ContextBuilder,不是 AgentRegistry:
|
||||
```go
|
||||
// 正确方式
|
||||
agentInstance.ContextBuilder.BuildMessages(history, summary, input, media, channel, chatID, senderID, senderDisplayName)
|
||||
```
|
||||
|
||||
**知识点**:picoclaw 代码结构中,ContextBuilder 负责消息构建,AgentRegistry 负责 agent 管理
|
||||
|
||||
---
|
||||
|
||||
### ToolDefinitions 获取方式
|
||||
|
||||
**问题**:如何获取可用的工具定义列表
|
||||
|
||||
**纠正**:通过 ToolRegistry 的 ToProviderDefs 方法:
|
||||
```go
|
||||
toolDefs := agentInstance.Tools.ToProviderDefs()
|
||||
```
|
||||
|
||||
**知识点**:ToolRegistry 维护工具注册,ToProviderDefs 转换为 provider 可用的格式
|
||||
|
||||
---
|
||||
|
||||
### 流式输出实时刷新
|
||||
|
||||
**问题**:流式输出时字符不是实时显示,要等很久才一次性出现
|
||||
|
||||
**纠正**:在 onChunk 回调中添加 `os.Stdout.Sync()` 强制刷新 stdout:
|
||||
```go
|
||||
func(token string) {
|
||||
fmt.Print(token)
|
||||
os.Stdout.Sync() // 强制刷新
|
||||
}
|
||||
```
|
||||
|
||||
**知识点**:Go 的 `fmt.Print` 使用缓冲输出,需要手动刷新才能实时显示
|
||||
|
||||
---
|
||||
|
||||
### Session 历史消息获取
|
||||
|
||||
**问题**:如何获取会话历史用于流式调用
|
||||
|
||||
**纠正**:通过 `SessionStore` 接口:
|
||||
```go
|
||||
history := agentInstance.Sessions.GetHistory(sessionKey)
|
||||
summary := agentInstance.Sessions.GetSummary(sessionKey)
|
||||
```
|
||||
|
||||
**知识点**:`AgentInstance.Sessions` 实现了 `SessionStore` 接口,支持 `GetHistory` 和 `GetSummary` 方法
|
||||
|
||||
---
|
||||
|
||||
### 流式调用后的消息保存
|
||||
|
||||
**问题**:流式调用绕过了 agent loop,消息没有保存到 session
|
||||
|
||||
**纠正**:流式调用后手动保存消息:
|
||||
```go
|
||||
agentInstance.Sessions.AddMessage(sessionKey, "user", input)
|
||||
agentInstance.Sessions.AddMessage(sessionKey, "assistant", result)
|
||||
```
|
||||
|
||||
**知识点**:`SessionStore` 接口提供 `AddMessage` 方法,支持 "user" 和 "assistant" 角色
|
||||
|
||||
---
|
||||
|
||||
### onChunk 回调接收累积文本导致重复输出
|
||||
|
||||
**问题**:picoclaw 的 `StreamingProvider` 接口定义:
|
||||
```go
|
||||
onChunk func(accumulated string)
|
||||
```
|
||||
|
||||
注释说明:"onChunk receives the accumulated text so far (not individual deltas)"。每次回调时参数是累积的完整文本(如 "你好" → "你好!再次" → "你好!再次见到"),而不是增量。
|
||||
|
||||
**纠正**:使用 `printedLen` 跟踪已打印位置,只打印新增部分:
|
||||
```go
|
||||
var printedLen int
|
||||
func(accumulated string) {
|
||||
if len(accumulated) > printedLen {
|
||||
fmt.Print(accumulated[printedLen:])
|
||||
printedLen = len(accumulated)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**知识点**:picoclaw 故意设计为累积文本,这样可以在任意时刻获取完整内容用于调试
|
||||
|
||||
---
|
||||
|
||||
### 尝试 uilive 库但只显示最后一行
|
||||
|
||||
**问题**:为了实现同行流动效果,尝试使用 `github.com/gosuri/uilive` 库
|
||||
|
||||
**现象**:该库会覆盖每一行,只显示最后一行内容
|
||||
|
||||
**纠正**:移除 uilive,直接使用 `fmt.Print` + `os.Stdout.Sync()`,让终端自然处理换行
|
||||
|
||||
**知识点**:uilive 适用于进度条等场景,不适合长文本流式输出
|
||||
|
||||
---
|
||||
|
||||
### 流式输出期望同行流动但实际换行显示
|
||||
|
||||
**问题**:用户期望像 ollama 那样在同行逐字符流动
|
||||
|
||||
**最终方案**:
|
||||
```go
|
||||
fmt.Print(accumulated[printedLen:])
|
||||
os.Stdout.Sync()
|
||||
```
|
||||
|
||||
效果:
|
||||
- 字符串自然累积增长
|
||||
- 终端自动处理换行(满一行自动 wrap)
|
||||
- 保留所有历史输出
|
||||
- 每次刷新缓冲区确保立即显示
|
||||
|
||||
**知识点**:最简单的方案就是最有效的方案,不需要额外库
|
||||
120
cmd/hxclaw/internal/helpers.go
Normal file
120
cmd/hxclaw/internal/helpers.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ergochat/readline"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
)
|
||||
|
||||
// 错误定义
|
||||
var (
|
||||
ErrInterrupt = errors.New("interrupt")
|
||||
ErrEOF = errors.New("EOF")
|
||||
)
|
||||
|
||||
// Logo 是 hxclaw 的 Logo
|
||||
const Logo = "🦐"
|
||||
|
||||
// GetPicoclawHome 返回 picoclaw 的家目录
|
||||
// 优先级: $PICOCLAW_HOME > ~/.picoclaw
|
||||
func GetPicoclawHome() string {
|
||||
return config.GetHome()
|
||||
}
|
||||
|
||||
// LoadConfig 加载配置文件
|
||||
// 复用 picoclaw 的配置加载逻辑
|
||||
func LoadConfig() (*config.Config, error) {
|
||||
cfg, err := config.LoadConfig(GetConfigPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.SetLevelFromString(cfg.Gateway.LogLevel)
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// GetConfigPath 获取配置文件路径
|
||||
func GetConfigPath() string {
|
||||
if configPath := os.Getenv(config.EnvConfig); configPath != "" {
|
||||
return configPath
|
||||
}
|
||||
return filepath.Join(GetPicoclawHome(), "config.json")
|
||||
}
|
||||
|
||||
// Readline 实例包装
|
||||
type Readline struct {
|
||||
rl *readline.Instance
|
||||
}
|
||||
|
||||
// NewReadline 创建一个新的 Readline 实例
|
||||
func NewReadline(prompt string) (*Readline, error) {
|
||||
// 确保历史文件目录存在
|
||||
historyDir := filepath.Dir(filepath.Join(GetPicoclawHome(), ".hxclaw_history"))
|
||||
os.MkdirAll(historyDir, 0755)
|
||||
|
||||
rl, err := readline.NewEx(&readline.Config{
|
||||
Prompt: prompt,
|
||||
HistoryFile: filepath.Join(GetPicoclawHome(), ".hxclaw_history"),
|
||||
HistoryLimit: 100,
|
||||
InterruptPrompt: "^C",
|
||||
EOFPrompt: "exit",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Readline{rl: rl}, nil
|
||||
}
|
||||
|
||||
// Readline 读取一行输入
|
||||
func (r *Readline) Readline() (string, error) {
|
||||
line, err := r.rl.Readline()
|
||||
if err != nil {
|
||||
if err == readline.ErrInterrupt {
|
||||
return "", ErrInterrupt
|
||||
}
|
||||
if err == io.EOF {
|
||||
return "", ErrEOF
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return line, nil
|
||||
}
|
||||
|
||||
// Close 关闭 Readline 实例
|
||||
func (r *Readline) Close() error {
|
||||
return r.rl.Close()
|
||||
}
|
||||
|
||||
// SimpleReader 简单输入读取器(无历史记录)
|
||||
type SimpleReader struct {
|
||||
reader *bufio.Reader
|
||||
}
|
||||
|
||||
// NewSimpleReader 创建一个新的简单读取器
|
||||
func NewSimpleReader() *SimpleReader {
|
||||
return &SimpleReader{
|
||||
reader: bufio.NewReader(os.Stdin),
|
||||
}
|
||||
}
|
||||
|
||||
// ReadString 读取一行输入
|
||||
func (r *SimpleReader) ReadString() (string, error) {
|
||||
line, err := r.reader.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return "", ErrEOF
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
// 去掉换行符
|
||||
if len(line) > 0 && line[len(line)-1] == '\n' {
|
||||
line = line[:len(line)-1]
|
||||
}
|
||||
return line, nil
|
||||
}
|
||||
186
cmd/hxclaw/main.go
Normal file
186
cmd/hxclaw/main.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hxclaw/hxclaw/cmd/hxclaw/internal"
|
||||
"github.com/sipeed/picoclaw/pkg/agent"
|
||||
"github.com/sipeed/picoclaw/pkg/bus"
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
"github.com/sipeed/picoclaw/pkg/providers"
|
||||
)
|
||||
|
||||
const Logo = "🦐"
|
||||
|
||||
func main() {
|
||||
fmt.Printf("%s HxClaw - PicoClaw 增强版 CLI\n\n", Logo)
|
||||
|
||||
cfg, err := internal.LoadConfig()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "错误:加载配置失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logger.ConfigureFromEnv()
|
||||
|
||||
provider, modelID, err := providers.CreateProvider(cfg)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "错误:创建 Provider 失败: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if modelID != "" {
|
||||
cfg.Agents.Defaults.ModelName = modelID
|
||||
}
|
||||
|
||||
msgBus := bus.NewMessageBus()
|
||||
defer msgBus.Close()
|
||||
|
||||
agentLoop := agent.NewAgentLoop(cfg, msgBus, provider)
|
||||
defer agentLoop.Close()
|
||||
|
||||
startupInfo := agentLoop.GetStartupInfo()
|
||||
logger.InfoCF("hxclaw", "HxClaw 已初始化",
|
||||
map[string]any{
|
||||
"tools_count": startupInfo["tools"].(map[string]any)["count"],
|
||||
"skills_total": startupInfo["skills"].(map[string]any)["total"],
|
||||
"skills_available": startupInfo["skills"].(map[string]any)["available"],
|
||||
})
|
||||
|
||||
fmt.Printf("%s Interactive mode (Ctrl+C to exit)\n\n", Logo)
|
||||
interactiveMode(agentLoop, "cli:default")
|
||||
}
|
||||
|
||||
func interactiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
||||
prompt := fmt.Sprintf("%s You: ", Logo)
|
||||
|
||||
rl, err := internal.NewReadline(prompt)
|
||||
if err != nil {
|
||||
fmt.Printf("初始化 readline 失败: %v\n", err)
|
||||
fmt.Println("回退到简单输入模式...")
|
||||
simpleInteractiveMode(agentLoop, sessionKey)
|
||||
return
|
||||
}
|
||||
defer rl.Close()
|
||||
|
||||
for {
|
||||
line, err := rl.Readline()
|
||||
if err != nil {
|
||||
if err == internal.ErrInterrupt || err == internal.ErrEOF {
|
||||
fmt.Println("\n再见!")
|
||||
return
|
||||
}
|
||||
fmt.Printf("读取输入错误: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
input := line
|
||||
if input == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if input == "exit" || input == "quit" {
|
||||
fmt.Println("再见!")
|
||||
return
|
||||
}
|
||||
|
||||
runWithStreaming(agentLoop, input, sessionKey)
|
||||
}
|
||||
}
|
||||
|
||||
func simpleInteractiveMode(agentLoop *agent.AgentLoop, sessionKey string) {
|
||||
reader := internal.NewSimpleReader()
|
||||
for {
|
||||
fmt.Print(fmt.Sprintf("%s You: ", Logo))
|
||||
line, err := reader.ReadString()
|
||||
if err != nil {
|
||||
if err == internal.ErrEOF {
|
||||
fmt.Println("\n再见!")
|
||||
return
|
||||
}
|
||||
fmt.Printf("读取输入错误: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
input := line
|
||||
if input == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if input == "exit" || input == "quit" {
|
||||
fmt.Println("再见!")
|
||||
return
|
||||
}
|
||||
|
||||
runWithStreaming(agentLoop, input, sessionKey)
|
||||
}
|
||||
}
|
||||
|
||||
// runWithStreaming 尝试使用流式输出,如果 Provider 不支持则回退到普通模式
|
||||
func runWithStreaming(agentLoop *agent.AgentLoop, input, sessionKey string) {
|
||||
agentInstance := agentLoop.GetRegistry().GetDefaultAgent()
|
||||
if agentInstance == nil {
|
||||
fmt.Println("错误:无法获取 Agent 实例")
|
||||
return
|
||||
}
|
||||
|
||||
provider := agentInstance.Provider
|
||||
ctx := context.Background()
|
||||
|
||||
// 判断是否支持流式
|
||||
if sp, ok := provider.(providers.StreamingProvider); ok {
|
||||
// 从 session 中获取历史消息
|
||||
history := agentInstance.Sessions.GetHistory(sessionKey)
|
||||
summary := agentInstance.Sessions.GetSummary(sessionKey)
|
||||
|
||||
// 使用 ContextBuilder 构建消息,包含历史
|
||||
messages := agentInstance.ContextBuilder.BuildMessages(
|
||||
history,
|
||||
summary,
|
||||
input,
|
||||
nil, // media
|
||||
"cli", // channel
|
||||
sessionKey,
|
||||
"", // senderID
|
||||
"", // senderDisplayName
|
||||
)
|
||||
|
||||
// 获取工具定义
|
||||
toolDefs := agentInstance.Tools.ToProviderDefs()
|
||||
|
||||
fmt.Print("\n")
|
||||
var result strings.Builder
|
||||
var printedLen int
|
||||
_, err := sp.ChatStream(ctx, messages, toolDefs, agentInstance.Model, nil, func(accumulated string) {
|
||||
if len(accumulated) > printedLen {
|
||||
fmt.Print(accumulated[printedLen:])
|
||||
os.Stdout.Sync()
|
||||
result.WriteString(accumulated[printedLen:])
|
||||
printedLen = len(accumulated)
|
||||
}
|
||||
})
|
||||
fmt.Println()
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("流式调用错误: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 将用户消息和回复保存到 session
|
||||
if result.Len() > 0 {
|
||||
agentInstance.Sessions.AddMessage(sessionKey, "user", input)
|
||||
agentInstance.Sessions.AddMessage(sessionKey, "assistant", result.String())
|
||||
}
|
||||
} else {
|
||||
// 回退到普通模式
|
||||
response, err := agentLoop.ProcessDirect(ctx, input, sessionKey)
|
||||
if err != nil {
|
||||
fmt.Printf("错误: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("\n%s %s\n\n", Logo, response)
|
||||
}
|
||||
}
|
||||
77
go.mod
Normal file
77
go.mod
Normal file
@@ -0,0 +1,77 @@
|
||||
module github.com/hxclaw/hxclaw
|
||||
|
||||
go 1.25.9
|
||||
|
||||
require (
|
||||
github.com/ergochat/readline v0.1.3
|
||||
github.com/sipeed/picoclaw v0.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/adhocore/gronx v1.19.6 // indirect
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 // indirect
|
||||
github.com/aws/smithy-go v1.24.2 // indirect
|
||||
github.com/caarlos0/env/v11 v11.4.0 // indirect
|
||||
github.com/creack/pty v1.1.24 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/github/copilot-sdk/go v0.2.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab // indirect
|
||||
github.com/google/jsonschema-go v0.4.2 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gosuri/uilive v0.0.4 // indirect
|
||||
github.com/h2non/filetype v1.1.3 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modelcontextprotocol/go-sdk v1.5.0 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/openai/openai-go/v3 v3.22.0 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/rtp v1.10.1 // indirect
|
||||
github.com/pion/webrtc/v3 v3.3.6 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rs/zerolog v1.35.0 // indirect
|
||||
github.com/segmentio/asm v1.1.3 // indirect
|
||||
github.com/segmentio/encoding v0.5.4 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.2.0 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
golang.org/x/crypto v0.49.0 // indirect
|
||||
golang.org/x/net v0.52.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/term v0.41.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.70.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/sqlite v1.48.2 // indirect
|
||||
)
|
||||
|
||||
// 开发时指向本地 picoclaw
|
||||
replace github.com/sipeed/picoclaw => /Users/titor/picoclaw
|
||||
184
go.sum
Normal file
184
go.sum
Normal file
@@ -0,0 +1,184 @@
|
||||
github.com/adhocore/gronx v1.19.6 h1:5KNVcoR9ACgL9HhEqCm5QXsab/gI4QDIybTAWcXDKDc=
|
||||
github.com/adhocore/gronx v1.19.6/go.mod h1:7oUY1WAU8rEJWmAxXR2DN0JaO4gi9khSgKjiRypqteg=
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAfT7CoSYSac11PY=
|
||||
github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.14 h1:opVIRo/ZbbI8OIqSOKmpFaY7IwfFUOCCXBsUpJOwDdI=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.14/go.mod h1:U4/V0uKxh0Tl5sxmCBZ3AecYny4UNlVmObYjKuuaiOo=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.14 h1:n+UcGWAIZHkXzYt87uMFBv/l8THYELoX6gVcUvgl6fI=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.14/go.mod h1:cJKuyWB59Mqi0jM3nFYQRmnHVQIcgoxjEMAbLkpr62w=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 h1:NUS3K4BTDArQqNu2ih7yeDLaS3bmHD0YndtA6UP884g=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21/go.mod h1:YWNWJQNjKigKY1RHVJCuupeWDrrHjRqHm0N9rdrWzYI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
|
||||
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.4 h1:W6tKfa/s37faUnwJ71pGqsBO7/wfUX1L7tVprupQGo4=
|
||||
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.4/go.mod h1:BZ+9thH0QOTDUwE8KAv/ZwUzsNC7CSMJXj/wtnZMs5k=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 h1:QKZH0S178gCmFEgst8hN0mCX1KxLgHBKKY/CLqwP8lg=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9/go.mod h1:7yuQJoT+OoH8aqIxw9vwF+8KpvLZ8AWmvmUWHsGQZvI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.15 h1:lFd1+ZSEYJZYvv9d6kXzhkZu07si3f+GQ1AaYwa2LUM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.15/go.mod h1:WSvS1NLr7JaPunCXqpJnWk1Bjo7IxzZXrZi1QQCkuqM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 h1:dzztQ1YmfPrxdrOiuZRMF6fuOwWlWpD2StNLTceKpys=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19/go.mod h1:YO8TrYtFdl5w/4vmjL8zaBSsiNp3w0L1FfKVKenZT7w=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 h1:p8ogvvLugcR/zLBXTXrTkj0RYBUdErbMnAFFp12Lm/U=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10/go.mod h1:60dv0eZJfeVXfbT1tFJinbHrDfSJ2GZl4Q//OSSNAVw=
|
||||
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
||||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/caarlos0/env/v11 v11.4.0 h1:Kcb6t5kIIr4XkoQC9AF2j+8E1Jsrl3Wz/hhm1LtoGAc=
|
||||
github.com/caarlos0/env/v11 v11.4.0/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/ergochat/readline v0.1.3 h1:/DytGTmwdUJcLAe3k3VJgowh5vNnsdifYT6uVaf4pSo=
|
||||
github.com/ergochat/readline v0.1.3/go.mod h1:o3ux9QLHLm77bq7hDB21UTm6HlV2++IPDMfIfKDuOgY=
|
||||
github.com/github/copilot-sdk/go v0.2.0 h1:RnrIIirmtp4wGgqSQFJ2k9phbeveIxOtYZqDogoNEa0=
|
||||
github.com/github/copilot-sdk/go v0.2.0/go.mod h1:uGWkjVYcp2DV9DgtqYihh5tEoJjNqxIFaUNnrwY4FxM=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab h1:VYNivV7P8IRHUam2swVUNkhIdp0LRRFKe4hXNnoZKTc=
|
||||
github.com/gomarkdown/markdown v0.0.0-20260217112301-37c66b85d6ab/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
|
||||
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gosuri/uilive v0.0.4 h1:hUEBpQDj8D8jXgtCdBu7sWsy5sbW/5GhuO8KBwJ2jyY=
|
||||
github.com/gosuri/uilive v0.0.4/go.mod h1:V/epo5LjjlDE5RJUcqx8dbw+zc93y5Ya3yg8tfZ74VI=
|
||||
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
||||
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modelcontextprotocol/go-sdk v1.5.0 h1:CHU0FIX9kpueNkxuYtfYQn1Z0slhFzBZuq+x6IiblIU=
|
||||
github.com/modelcontextprotocol/go-sdk v1.5.0/go.mod h1:gggDIhoemhWs3BGkGwd1umzEXCEMMvAnhTrnbXJKKKA=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/openai/openai-go/v3 v3.22.0 h1:6MEoNoV8sbjOVmXdvhmuX3BjVbVdcExbVyGixiyJ8ys=
|
||||
github.com/openai/openai-go/v3 v3.22.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtp v1.10.1 h1:xP1prZcCTUuhO2c83XtxyOHJteISg6o8iPsE2acaMtA=
|
||||
github.com/pion/rtp v1.10.1/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
|
||||
github.com/pion/webrtc/v3 v3.3.6 h1:7XAh4RPtlY1Vul6/GmZrv7z+NnxKA6If0KStXBI2ZLE=
|
||||
github.com/pion/webrtc/v3 v3.3.6/go.mod h1:zyN7th4mZpV27eXybfR/cnUf3J2DRy8zw/mdjD9JTNM=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI=
|
||||
github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
|
||||
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
|
||||
github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0=
|
||||
github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw=
|
||||
modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
|
||||
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.48.2 h1:5CnW4uP8joZtA0LedVqLbZV5GD7F/0x91AXeSyjoh5c=
|
||||
modernc.org/sqlite v1.48.2/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
141
taolun.md
Normal file
141
taolun.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# hxclaw 讨论记录
|
||||
|
||||
## 知识点汇总
|
||||
|
||||
### 1. hxclaw 是什么?
|
||||
|
||||
- hxclaw 是基于 picoclaw 的 CLI 增强工具
|
||||
- 提供流式输出(替代原有的批量输出)
|
||||
- 提供 Markdown 终端渲染(提升阅读体验)
|
||||
- 定位: picoclaw 的变体,面向需要更好终端交互体验的用户
|
||||
|
||||
### 2. 为什么采用独立二进制而非插件?
|
||||
|
||||
- picoclaw 目前没有正式的插件系统
|
||||
- 工具扩展通过 ToolRegistry 注册(如 spi.go, i2c.go)
|
||||
- 技能通过 SKILL.md 文件加载
|
||||
- MCP 通过配置连接外部服务器
|
||||
- 最小侵入方式:独立二进制 + 复用核心库
|
||||
|
||||
### 3. 如何复用 picoclaw 功能?
|
||||
|
||||
- 使用 Go 的 replace 机制在 go.mod 中声明依赖
|
||||
- 开发时 replace 指向本地 picoclaw 目录
|
||||
- 发布时 replace 指向 GitHub 具体版本(如 v0.2.4)
|
||||
- hxclaw 只需导入:pkg/agent, pkg/providers, pkg/config, pkg/bus
|
||||
|
||||
### 4. hxclaw 架构设计
|
||||
|
||||
```
|
||||
hxclaw/
|
||||
├── cmd/hxclaw/ # CLI 入口(自己实现)
|
||||
├── go.mod # 依赖配置
|
||||
└── pkg/ # 空目录,全量复用 picoclaw
|
||||
```
|
||||
|
||||
- 不依赖:pkg/channels(不需要消息通道)、pkg/gateway(不需要 HTTP 服务)、web/(不需要网页)
|
||||
- 依赖:pkg/agent(核心逻辑)、pkg/providers(LLM 调用)、pkg/config(配置加载)
|
||||
|
||||
### 5. 流式输出原理
|
||||
|
||||
- picoclaw 的 providers 已支持 StreamingProvider 接口
|
||||
- 接口定义:ChatStream(ctx, messages, tools, model, options, onChunk)
|
||||
- onChunk 是回调函数,每个 token 生成时调用
|
||||
- CLI 层需要判断 provider 是否实现 StreamingProvider,然后选择调用
|
||||
|
||||
### 6. Markdown 终端渲染
|
||||
|
||||
- 使用 charmbracelet 家族库
|
||||
- lipgloss:终端样式
|
||||
- glow:代码高亮
|
||||
- 流程:Markdown → ANSI 转义序列 → 终端显示
|
||||
|
||||
### 7. 部署方式
|
||||
|
||||
- 独立二进制 hxclaw,与 picoclaw 二进制共存于同一目录
|
||||
- 用户使用 `hxclaw` 命令调用增强版 CLI
|
||||
- 配置文件复用 picoclaw 的 config.json(位于 ~/.picoclaw/config.json)
|
||||
|
||||
### 8. 版本同步策略
|
||||
|
||||
- 关键版本跟进(功能大版本更新时)
|
||||
- 不需要每次 picoclaw 升级都同步
|
||||
- 依赖版本在 go.mod 中声明,更新时修改 replace 目标版本即可
|
||||
|
||||
### 9. AgentRegistry 的正确使用方式
|
||||
|
||||
- `AgentRegistry` 负责管理多个 Agent 实例
|
||||
- `GetDefaultAgent()` 获取默认的 Agent 实例
|
||||
- `AgentInstance` 包含:
|
||||
- `Provider` - LLM 提供者
|
||||
- `ContextBuilder` - 消息构建器
|
||||
- `Tools` - 工具注册表
|
||||
- `Model` - 模型名称
|
||||
- 注意:`AgentRegistry` 没有 `BuildMessages` 方法,该方法属于 `ContextBuilder`
|
||||
|
||||
### 10. ToolDefinitions 的获取方式
|
||||
|
||||
- 通过 `agentInstance.Tools.ToProviderDefs()` 获取
|
||||
- 返回 `[]providers.ToolDefinition` 格式
|
||||
- 该方法将工具注册表中的工具转换为 provider 可用的格式
|
||||
|
||||
### 11. 流式输出的实现问题与解决方案
|
||||
|
||||
#### 问题 1:onChunk 回调接收累积文本
|
||||
|
||||
picoclaw 的 `StreamingProvider` 接口定义:
|
||||
```go
|
||||
onChunk func(accumulated string)
|
||||
```
|
||||
|
||||
注释明确说明:"onChunk receives the accumulated text so far (not individual deltas)"。
|
||||
|
||||
这意味着每次回调时参数是完整的累积文本(如 "你好" → "你好!再次" → "你好!再次见到"),而不是增量。
|
||||
|
||||
#### 问题 2:直接打印导致重复输出
|
||||
|
||||
如果直接打印 token:
|
||||
```go
|
||||
func(token string) {
|
||||
fmt.Print(token) // 打印累积文本!
|
||||
}
|
||||
```
|
||||
会导致输出:
|
||||
```
|
||||
你好
|
||||
你好!再次
|
||||
你好!再次见到
|
||||
...
|
||||
```
|
||||
|
||||
#### 解决方案 1:跟踪已打印长度
|
||||
|
||||
使用 `printedLen` 跟踪已打印的字符位置,只打印新增部分:
|
||||
```go
|
||||
var printedLen int
|
||||
func(accumulated string) {
|
||||
if len(accumulated) > printedLen {
|
||||
fmt.Print(accumulated[printedLen:])
|
||||
printedLen = len(accumulated)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 问题 3:尝试使用 uilive 库
|
||||
|
||||
尝试使用 `github.com/gosuri/uilive` 库实现同行流动效果,但发现该库会覆盖每一行,只显示最后一行内容,不符合需求。
|
||||
|
||||
#### 最终解决方案:直接 Print + Sync
|
||||
|
||||
```go
|
||||
fmt.Print(accumulated[printedLen:])
|
||||
os.Stdout.Sync()
|
||||
```
|
||||
|
||||
这样:
|
||||
1. 字符串自然累积增长
|
||||
2. 终端自动处理换行(满一行自动 wrap)
|
||||
3. 保留所有历史输出
|
||||
4. 每次刷新缓冲区确保立即显示
|
||||
|
||||
这正是 ollama 等工具的流式输出效果。
|
||||
Reference in New Issue
Block a user