Merge branch 'sipeed:main' into main
This commit is contained in:
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@@ -3,7 +3,6 @@ name: build
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
5
.github/workflows/docker-build.yml
vendored
5
.github/workflows/docker-build.yml
vendored
@@ -1,9 +1,8 @@
|
|||||||
name: 🐳 Build & Push Docker Image
|
name: 🐳 Build & Push Docker Image
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
release:
|
||||||
branches: [main]
|
types: [published]
|
||||||
tags: ["v*"]
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
|
|||||||
52
.github/workflows/pr.yml
vendored
Normal file
52
.github/workflows/pr.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
name: pr-check
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
fmt-check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
- name: Check formatting
|
||||||
|
run: |
|
||||||
|
make fmt
|
||||||
|
git diff --exit-code || (echo "::error::Code is not formatted. Run 'make fmt' and commit the changes." && exit 1)
|
||||||
|
|
||||||
|
vet:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: fmt-check
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
- name: Run go vet
|
||||||
|
run: go vet ./...
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: fmt-check
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
|
||||||
|
- name: Run go test
|
||||||
|
run: go test ./...
|
||||||
|
|
||||||
@@ -170,8 +170,8 @@ func (cb *ContextBuilder) BuildMessages(history []providers.Message, summary str
|
|||||||
// Log system prompt summary for debugging (debug mode only)
|
// Log system prompt summary for debugging (debug mode only)
|
||||||
logger.DebugCF("agent", "System prompt built",
|
logger.DebugCF("agent", "System prompt built",
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"total_chars": len(systemPrompt),
|
"total_chars": len(systemPrompt),
|
||||||
"total_lines": strings.Count(systemPrompt, "\n") + 1,
|
"total_lines": strings.Count(systemPrompt, "\n") + 1,
|
||||||
"section_count": strings.Count(systemPrompt, "\n\n---\n\n") + 1,
|
"section_count": strings.Count(systemPrompt, "\n\n---\n\n") + 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -193,9 +193,9 @@ func (cb *ContextBuilder) BuildMessages(history []providers.Message, summary str
|
|||||||
// --- INICIO DEL FIX ---
|
// --- INICIO DEL FIX ---
|
||||||
//Diegox-17
|
//Diegox-17
|
||||||
for len(history) > 0 && (history[0].Role == "tool") {
|
for len(history) > 0 && (history[0].Role == "tool") {
|
||||||
logger.DebugCF("agent", "Removing orphaned tool message from history to prevent LLM error",
|
logger.DebugCF("agent", "Removing orphaned tool message from history to prevent LLM error",
|
||||||
map[string]interface{}{"role": history[0].Role})
|
map[string]interface{}{"role": history[0].Role})
|
||||||
history = history[1:]
|
history = history[1:]
|
||||||
}
|
}
|
||||||
//Diegox-17
|
//Diegox-17
|
||||||
// --- FIN DEL FIX ---
|
// --- FIN DEL FIX ---
|
||||||
|
|||||||
@@ -33,14 +33,14 @@ type AgentLoop struct {
|
|||||||
provider providers.LLMProvider
|
provider providers.LLMProvider
|
||||||
workspace string
|
workspace string
|
||||||
model string
|
model string
|
||||||
contextWindow int // Maximum context window size in tokens
|
contextWindow int // Maximum context window size in tokens
|
||||||
maxIterations int
|
maxIterations int
|
||||||
sessions *session.SessionManager
|
sessions *session.SessionManager
|
||||||
state *state.Manager
|
state *state.Manager
|
||||||
contextBuilder *ContextBuilder
|
contextBuilder *ContextBuilder
|
||||||
tools *tools.ToolRegistry
|
tools *tools.ToolRegistry
|
||||||
running atomic.Bool
|
running atomic.Bool
|
||||||
summarizing sync.Map // Tracks which sessions are currently being summarized
|
summarizing sync.Map // Tracks which sessions are currently being summarized
|
||||||
}
|
}
|
||||||
|
|
||||||
// processOptions configures how a message is processed
|
// processOptions configures how a message is processed
|
||||||
@@ -157,11 +157,22 @@ func (al *AgentLoop) Run(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if response != "" {
|
if response != "" {
|
||||||
al.bus.PublishOutbound(bus.OutboundMessage{
|
// Check if the message tool already sent a response during this round.
|
||||||
Channel: msg.Channel,
|
// If so, skip publishing to avoid duplicate messages to the user.
|
||||||
ChatID: msg.ChatID,
|
alreadySent := false
|
||||||
Content: response,
|
if tool, ok := al.tools.Get("message"); ok {
|
||||||
})
|
if mt, ok := tool.(*tools.MessageTool); ok {
|
||||||
|
alreadySent = mt.HasSentInRound()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !alreadySent {
|
||||||
|
al.bus.PublishOutbound(bus.OutboundMessage{
|
||||||
|
Channel: msg.Channel,
|
||||||
|
ChatID: msg.ChatID,
|
||||||
|
Content: response,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -285,9 +296,9 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe
|
|||||||
if constants.IsInternalChannel(originChannel) {
|
if constants.IsInternalChannel(originChannel) {
|
||||||
logger.InfoCF("agent", "Subagent completed (internal channel)",
|
logger.InfoCF("agent", "Subagent completed (internal channel)",
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"sender_id": msg.SenderID,
|
"sender_id": msg.SenderID,
|
||||||
"content_len": len(content),
|
"content_len": len(content),
|
||||||
"channel": originChannel,
|
"channel": originChannel,
|
||||||
})
|
})
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@@ -296,9 +307,9 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe
|
|||||||
// Don't forward result here, subagent should use message tool to communicate with user
|
// Don't forward result here, subagent should use message tool to communicate with user
|
||||||
logger.InfoCF("agent", "Subagent completed",
|
logger.InfoCF("agent", "Subagent completed",
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"sender_id": msg.SenderID,
|
"sender_id": msg.SenderID,
|
||||||
"channel": originChannel,
|
"channel": originChannel,
|
||||||
"content_len": len(content),
|
"content_len": len(content),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Agent only logs, does not respond to user
|
// Agent only logs, does not respond to user
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ type mockProvider struct{}
|
|||||||
|
|
||||||
func (m *mockProvider) Chat(ctx context.Context, messages []providers.Message, tools []providers.ToolDefinition, model string, opts map[string]interface{}) (*providers.LLMResponse, error) {
|
func (m *mockProvider) Chat(ctx context.Context, messages []providers.Message, tools []providers.ToolDefinition, model string, opts map[string]interface{}) (*providers.LLMResponse, error) {
|
||||||
return &providers.LLMResponse{
|
return &providers.LLMResponse{
|
||||||
Content: "Mock response",
|
Content: "Mock response",
|
||||||
ToolCalls: []providers.ToolCall{},
|
ToolCalls: []providers.ToolCall{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -364,7 +364,7 @@ type simpleMockProvider struct {
|
|||||||
|
|
||||||
func (m *simpleMockProvider) Chat(ctx context.Context, messages []providers.Message, tools []providers.ToolDefinition, model string, opts map[string]interface{}) (*providers.LLMResponse, error) {
|
func (m *simpleMockProvider) Chat(ctx context.Context, messages []providers.Message, tools []providers.ToolDefinition, model string, opts map[string]interface{}) (*providers.LLMResponse, error) {
|
||||||
return &providers.LLMResponse{
|
return &providers.LLMResponse{
|
||||||
Content: m.response,
|
Content: m.response,
|
||||||
ToolCalls: []providers.ToolCall{},
|
ToolCalls: []providers.ToolCall{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -475,7 +475,7 @@ func TestToolResult_SilentToolDoesNotSendUserMessage(t *testing.T) {
|
|||||||
SenderID: "user1",
|
SenderID: "user1",
|
||||||
ChatID: "chat1",
|
ChatID: "chat1",
|
||||||
Content: "read test.txt",
|
Content: "read test.txt",
|
||||||
SessionKey: "test-session",
|
SessionKey: "test-session",
|
||||||
}
|
}
|
||||||
|
|
||||||
response := helper.executeAndGetResponse(t, ctx, msg)
|
response := helper.executeAndGetResponse(t, ctx, msg)
|
||||||
@@ -517,7 +517,7 @@ func TestToolResult_UserFacingToolDoesSendMessage(t *testing.T) {
|
|||||||
SenderID: "user1",
|
SenderID: "user1",
|
||||||
ChatID: "chat1",
|
ChatID: "chat1",
|
||||||
Content: "run hello",
|
Content: "run hello",
|
||||||
SessionKey: "test-session",
|
SessionKey: "test-session",
|
||||||
}
|
}
|
||||||
|
|
||||||
response := helper.executeAndGetResponse(t, ctx, msg)
|
response := helper.executeAndGetResponse(t, ctx, msg)
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ func NewMemoryStore(workspace string) *MemoryStore {
|
|||||||
|
|
||||||
// getTodayFile returns the path to today's daily note file (memory/YYYYMM/YYYYMMDD.md).
|
// getTodayFile returns the path to today's daily note file (memory/YYYYMM/YYYYMMDD.md).
|
||||||
func (ms *MemoryStore) getTodayFile() string {
|
func (ms *MemoryStore) getTodayFile() string {
|
||||||
today := time.Now().Format("20060102") // YYYYMMDD
|
today := time.Now().Format("20060102") // YYYYMMDD
|
||||||
monthDir := today[:6] // YYYYMM
|
monthDir := today[:6] // YYYYMM
|
||||||
filePath := filepath.Join(ms.memoryDir, monthDir, today+".md")
|
filePath := filepath.Join(ms.memoryDir, monthDir, today+".md")
|
||||||
return filePath
|
return filePath
|
||||||
}
|
}
|
||||||
@@ -104,8 +104,8 @@ func (ms *MemoryStore) GetRecentDailyNotes(days int) string {
|
|||||||
|
|
||||||
for i := 0; i < days; i++ {
|
for i := 0; i < days; i++ {
|
||||||
date := time.Now().AddDate(0, 0, -i)
|
date := time.Now().AddDate(0, 0, -i)
|
||||||
dateStr := date.Format("20060102") // YYYYMMDD
|
dateStr := date.Format("20060102") // YYYYMMDD
|
||||||
monthDir := dateStr[:6] // YYYYMM
|
monthDir := dateStr[:6] // YYYYMM
|
||||||
filePath := filepath.Join(ms.memoryDir, monthDir, dateStr+".md")
|
filePath := filepath.Join(ms.memoryDir, monthDir, dateStr+".md")
|
||||||
|
|
||||||
if data, err := os.ReadFile(filePath); err == nil {
|
if data, err := os.ReadFile(filePath); err == nil {
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ import (
|
|||||||
// It uses WebSocket for receiving messages via stream mode and API for sending
|
// It uses WebSocket for receiving messages via stream mode and API for sending
|
||||||
type DingTalkChannel struct {
|
type DingTalkChannel struct {
|
||||||
*BaseChannel
|
*BaseChannel
|
||||||
config config.DingTalkConfig
|
config config.DingTalkConfig
|
||||||
clientID string
|
clientID string
|
||||||
clientSecret string
|
clientSecret string
|
||||||
streamClient *client.StreamClient
|
streamClient *client.StreamClient
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
// Map to store session webhooks for each chat
|
// Map to store session webhooks for each chat
|
||||||
sessionWebhooks sync.Map // chatID -> sessionWebhook
|
sessionWebhooks sync.Map // chatID -> sessionWebhook
|
||||||
}
|
}
|
||||||
@@ -109,8 +109,8 @@ func (c *DingTalkChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.DebugCF("dingtalk", "Sending message", map[string]interface{}{
|
logger.DebugCF("dingtalk", "Sending message", map[string]interface{}{
|
||||||
"chat_id": msg.ChatID,
|
"chat_id": msg.ChatID,
|
||||||
"preview": utils.Truncate(msg.Content, 100),
|
"preview": utils.Truncate(msg.Content, 100),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Use the session webhook to send the reply
|
// Use the session webhook to send the reply
|
||||||
|
|||||||
@@ -282,9 +282,9 @@ func (c *SlackChannel) handleMessageEvent(ev *slackevents.MessageEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.DebugCF("slack", "Received message", map[string]interface{}{
|
logger.DebugCF("slack", "Received message", map[string]interface{}{
|
||||||
"sender_id": senderID,
|
"sender_id": senderID,
|
||||||
"chat_id": chatID,
|
"chat_id": chatID,
|
||||||
"preview": utils.Truncate(content, 50),
|
"preview": utils.Truncate(content, 50),
|
||||||
"has_thread": threadTS != "",
|
"has_thread": threadTS != "",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -58,13 +58,13 @@ type AgentsConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AgentDefaults struct {
|
type AgentDefaults struct {
|
||||||
Workspace string `json:"workspace" env:"PICOCLAW_AGENTS_DEFAULTS_WORKSPACE"`
|
Workspace string `json:"workspace" env:"PICOCLAW_AGENTS_DEFAULTS_WORKSPACE"`
|
||||||
RestrictToWorkspace bool `json:"restrict_to_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE"`
|
RestrictToWorkspace bool `json:"restrict_to_workspace" env:"PICOCLAW_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE"`
|
||||||
Provider string `json:"provider" env:"PICOCLAW_AGENTS_DEFAULTS_PROVIDER"`
|
Provider string `json:"provider" env:"PICOCLAW_AGENTS_DEFAULTS_PROVIDER"`
|
||||||
Model string `json:"model" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"`
|
Model string `json:"model" env:"PICOCLAW_AGENTS_DEFAULTS_MODEL"`
|
||||||
MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"`
|
MaxTokens int `json:"max_tokens" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOKENS"`
|
||||||
Temperature float64 `json:"temperature" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"`
|
Temperature float64 `json:"temperature" env:"PICOCLAW_AGENTS_DEFAULTS_TEMPERATURE"`
|
||||||
MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"`
|
MaxToolIterations int `json:"max_tool_iterations" env:"PICOCLAW_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChannelsConfig struct {
|
type ChannelsConfig struct {
|
||||||
@@ -140,15 +140,16 @@ type HeartbeatConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProvidersConfig struct {
|
type ProvidersConfig struct {
|
||||||
Anthropic ProviderConfig `json:"anthropic"`
|
Anthropic ProviderConfig `json:"anthropic"`
|
||||||
OpenAI ProviderConfig `json:"openai"`
|
OpenAI ProviderConfig `json:"openai"`
|
||||||
OpenRouter ProviderConfig `json:"openrouter"`
|
OpenRouter ProviderConfig `json:"openrouter"`
|
||||||
Groq ProviderConfig `json:"groq"`
|
Groq ProviderConfig `json:"groq"`
|
||||||
Zhipu ProviderConfig `json:"zhipu"`
|
Zhipu ProviderConfig `json:"zhipu"`
|
||||||
VLLM ProviderConfig `json:"vllm"`
|
VLLM ProviderConfig `json:"vllm"`
|
||||||
Gemini ProviderConfig `json:"gemini"`
|
Gemini ProviderConfig `json:"gemini"`
|
||||||
Nvidia ProviderConfig `json:"nvidia"`
|
Nvidia ProviderConfig `json:"nvidia"`
|
||||||
Moonshot ProviderConfig `json:"moonshot"`
|
Moonshot ProviderConfig `json:"moonshot"`
|
||||||
|
ShengSuanYun ProviderConfig `json:"shengsuanyun"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderConfig struct {
|
type ProviderConfig struct {
|
||||||
@@ -180,13 +181,13 @@ func DefaultConfig() *Config {
|
|||||||
return &Config{
|
return &Config{
|
||||||
Agents: AgentsConfig{
|
Agents: AgentsConfig{
|
||||||
Defaults: AgentDefaults{
|
Defaults: AgentDefaults{
|
||||||
Workspace: "~/.picoclaw/workspace",
|
Workspace: "~/.picoclaw/workspace",
|
||||||
RestrictToWorkspace: true,
|
RestrictToWorkspace: true,
|
||||||
Provider: "",
|
Provider: "",
|
||||||
Model: "glm-4.7",
|
Model: "glm-4.7",
|
||||||
MaxTokens: 8192,
|
MaxTokens: 8192,
|
||||||
Temperature: 0.7,
|
Temperature: 0.7,
|
||||||
MaxToolIterations: 20,
|
MaxToolIterations: 20,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Channels: ChannelsConfig{
|
Channels: ChannelsConfig{
|
||||||
@@ -239,15 +240,16 @@ func DefaultConfig() *Config {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Providers: ProvidersConfig{
|
Providers: ProvidersConfig{
|
||||||
Anthropic: ProviderConfig{},
|
Anthropic: ProviderConfig{},
|
||||||
OpenAI: ProviderConfig{},
|
OpenAI: ProviderConfig{},
|
||||||
OpenRouter: ProviderConfig{},
|
OpenRouter: ProviderConfig{},
|
||||||
Groq: ProviderConfig{},
|
Groq: ProviderConfig{},
|
||||||
Zhipu: ProviderConfig{},
|
Zhipu: ProviderConfig{},
|
||||||
VLLM: ProviderConfig{},
|
VLLM: ProviderConfig{},
|
||||||
Gemini: ProviderConfig{},
|
Gemini: ProviderConfig{},
|
||||||
Nvidia: ProviderConfig{},
|
Nvidia: ProviderConfig{},
|
||||||
Moonshot: ProviderConfig{},
|
Moonshot: ProviderConfig{},
|
||||||
|
ShengSuanYun: ProviderConfig{},
|
||||||
},
|
},
|
||||||
Gateway: GatewayConfig{
|
Gateway: GatewayConfig{
|
||||||
Host: "0.0.0.0",
|
Host: "0.0.0.0",
|
||||||
@@ -337,6 +339,9 @@ func (c *Config) GetAPIKey() string {
|
|||||||
if c.Providers.VLLM.APIKey != "" {
|
if c.Providers.VLLM.APIKey != "" {
|
||||||
return c.Providers.VLLM.APIKey
|
return c.Providers.VLLM.APIKey
|
||||||
}
|
}
|
||||||
|
if c.Providers.ShengSuanYun.APIKey != "" {
|
||||||
|
return c.Providers.ShengSuanYun.APIKey
|
||||||
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ var supportedChannels = map[string]bool{
|
|||||||
"whatsapp": true,
|
"whatsapp": true,
|
||||||
"feishu": true,
|
"feishu": true,
|
||||||
"qq": true,
|
"qq": true,
|
||||||
"dingtalk": true,
|
"dingtalk": true,
|
||||||
"maixcam": true,
|
"maixcam": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ func TestConvertKeysToSnake(t *testing.T) {
|
|||||||
"apiKey": "test-key",
|
"apiKey": "test-key",
|
||||||
"apiBase": "https://example.com",
|
"apiBase": "https://example.com",
|
||||||
"nested": map[string]interface{}{
|
"nested": map[string]interface{}{
|
||||||
"maxTokens": float64(8192),
|
"maxTokens": float64(8192),
|
||||||
"allowFrom": []interface{}{"user1", "user2"},
|
"allowFrom": []interface{}{"user1", "user2"},
|
||||||
"deeperLevel": map[string]interface{}{
|
"deeperLevel": map[string]interface{}{
|
||||||
"clientId": "abc",
|
"clientId": "abc",
|
||||||
},
|
},
|
||||||
@@ -256,11 +256,11 @@ func TestConvertConfig(t *testing.T) {
|
|||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"agents": map[string]interface{}{
|
"agents": map[string]interface{}{
|
||||||
"defaults": map[string]interface{}{
|
"defaults": map[string]interface{}{
|
||||||
"model": "claude-3-opus",
|
"model": "claude-3-opus",
|
||||||
"max_tokens": float64(4096),
|
"max_tokens": float64(4096),
|
||||||
"temperature": 0.5,
|
"temperature": 0.5,
|
||||||
"max_tool_iterations": float64(10),
|
"max_tool_iterations": float64(10),
|
||||||
"workspace": "~/.openclaw/workspace",
|
"workspace": "~/.openclaw/workspace",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,22 +254,22 @@ func findMatchingBrace(text string, pos int) int {
|
|||||||
// claudeCliJSONResponse represents the JSON output from the claude CLI.
|
// claudeCliJSONResponse represents the JSON output from the claude CLI.
|
||||||
// Matches the real claude CLI v2.x output format.
|
// Matches the real claude CLI v2.x output format.
|
||||||
type claudeCliJSONResponse struct {
|
type claudeCliJSONResponse struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Subtype string `json:"subtype"`
|
Subtype string `json:"subtype"`
|
||||||
IsError bool `json:"is_error"`
|
IsError bool `json:"is_error"`
|
||||||
Result string `json:"result"`
|
Result string `json:"result"`
|
||||||
SessionID string `json:"session_id"`
|
SessionID string `json:"session_id"`
|
||||||
TotalCostUSD float64 `json:"total_cost_usd"`
|
TotalCostUSD float64 `json:"total_cost_usd"`
|
||||||
DurationMS int `json:"duration_ms"`
|
DurationMS int `json:"duration_ms"`
|
||||||
DurationAPI int `json:"duration_api_ms"`
|
DurationAPI int `json:"duration_api_ms"`
|
||||||
NumTurns int `json:"num_turns"`
|
NumTurns int `json:"num_turns"`
|
||||||
Usage claudeCliUsageInfo `json:"usage"`
|
Usage claudeCliUsageInfo `json:"usage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// claudeCliUsageInfo represents token usage from the claude CLI response.
|
// claudeCliUsageInfo represents token usage from the claude CLI response.
|
||||||
type claudeCliUsageInfo struct {
|
type claudeCliUsageInfo struct {
|
||||||
InputTokens int `json:"input_tokens"`
|
InputTokens int `json:"input_tokens"`
|
||||||
OutputTokens int `json:"output_tokens"`
|
OutputTokens int `json:"output_tokens"`
|
||||||
CacheCreationInputTokens int `json:"cache_creation_input_tokens"`
|
CacheCreationInputTokens int `json:"cache_creation_input_tokens"`
|
||||||
CacheReadInputTokens int `json:"cache_read_input_tokens"`
|
CacheReadInputTokens int `json:"cache_read_input_tokens"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -967,9 +967,9 @@ func TestFindMatchingBrace(t *testing.T) {
|
|||||||
{`{"a":1}`, 0, 7},
|
{`{"a":1}`, 0, 7},
|
||||||
{`{"a":{"b":2}}`, 0, 13},
|
{`{"a":{"b":2}}`, 0, 13},
|
||||||
{`text {"a":1} more`, 5, 12},
|
{`text {"a":1} more`, 5, 12},
|
||||||
{`{unclosed`, 0, 0}, // no match returns pos
|
{`{unclosed`, 0, 0}, // no match returns pos
|
||||||
{`{}`, 0, 2}, // empty object
|
{`{}`, 0, 2}, // empty object
|
||||||
{`{{{}}}`, 0, 6}, // deeply nested
|
{`{{{}}}`, 0, 6}, // deeply nested
|
||||||
{`{"a":"b{c}d"}`, 0, 13}, // braces in strings (simplified matcher)
|
{`{"a":"b{c}d"}`, 0, 13}, // braces in strings (simplified matcher)
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
@@ -289,6 +289,14 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) {
|
|||||||
apiKey = cfg.Providers.VLLM.APIKey
|
apiKey = cfg.Providers.VLLM.APIKey
|
||||||
apiBase = cfg.Providers.VLLM.APIBase
|
apiBase = cfg.Providers.VLLM.APIBase
|
||||||
}
|
}
|
||||||
|
case "shengsuanyun":
|
||||||
|
if cfg.Providers.ShengSuanYun.APIKey != "" {
|
||||||
|
apiKey = cfg.Providers.ShengSuanYun.APIKey
|
||||||
|
apiBase = cfg.Providers.ShengSuanYun.APIBase
|
||||||
|
if apiBase == "" {
|
||||||
|
apiBase = "https://router.shengsuanyun.com/api/v1"
|
||||||
|
}
|
||||||
|
}
|
||||||
case "claude-cli", "claudecode", "claude-code":
|
case "claude-cli", "claudecode", "claude-code":
|
||||||
workspace := cfg.Agents.Defaults.Workspace
|
workspace := cfg.Agents.Defaults.Workspace
|
||||||
if workspace == "" {
|
if workspace == "" {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package tools
|
package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -147,8 +147,8 @@ func (t *CronTool) addJob(args map[string]interface{}) *ToolResult {
|
|||||||
if hasAt {
|
if hasAt {
|
||||||
atMS := time.Now().UnixMilli() + int64(atSeconds)*1000
|
atMS := time.Now().UnixMilli() + int64(atSeconds)*1000
|
||||||
schedule = cron.CronSchedule{
|
schedule = cron.CronSchedule{
|
||||||
Kind: "at",
|
Kind: "at",
|
||||||
AtMS: &atMS,
|
AtMS: &atMS,
|
||||||
}
|
}
|
||||||
} else if hasEvery {
|
} else if hasEvery {
|
||||||
everyMS := int64(everySeconds) * 1000
|
everyMS := int64(everySeconds) * 1000
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type MessageTool struct {
|
|||||||
sendCallback SendCallback
|
sendCallback SendCallback
|
||||||
defaultChannel string
|
defaultChannel string
|
||||||
defaultChatID string
|
defaultChatID string
|
||||||
|
sentInRound bool // Tracks whether a message was sent in the current processing round
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMessageTool() *MessageTool {
|
func NewMessageTool() *MessageTool {
|
||||||
@@ -49,6 +50,12 @@ func (t *MessageTool) Parameters() map[string]interface{} {
|
|||||||
func (t *MessageTool) SetContext(channel, chatID string) {
|
func (t *MessageTool) SetContext(channel, chatID string) {
|
||||||
t.defaultChannel = channel
|
t.defaultChannel = channel
|
||||||
t.defaultChatID = chatID
|
t.defaultChatID = chatID
|
||||||
|
t.sentInRound = false // Reset send tracking for new processing round
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasSentInRound returns true if the message tool sent a message during the current round.
|
||||||
|
func (t *MessageTool) HasSentInRound() bool {
|
||||||
|
return t.sentInRound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MessageTool) SetSendCallback(callback SendCallback) {
|
func (t *MessageTool) SetSendCallback(callback SendCallback) {
|
||||||
@@ -87,9 +94,10 @@ func (t *MessageTool) Execute(ctx context.Context, args map[string]interface{})
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.sentInRound = true
|
||||||
// Silent: user already received the message directly
|
// Silent: user already received the message directly
|
||||||
return &ToolResult{
|
return &ToolResult{
|
||||||
ForLLM: fmt.Sprintf("Message sent to %s:%s", channel, chatID),
|
ForLLM: fmt.Sprintf("Message sent to %s:%s", channel, chatID),
|
||||||
Silent: true,
|
Silent: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type ExecTool struct {
|
type ExecTool struct {
|
||||||
workingDir string
|
workingDir string
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
|
|||||||
@@ -22,27 +22,27 @@ type SubagentTask struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SubagentManager struct {
|
type SubagentManager struct {
|
||||||
tasks map[string]*SubagentTask
|
tasks map[string]*SubagentTask
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
provider providers.LLMProvider
|
provider providers.LLMProvider
|
||||||
defaultModel string
|
defaultModel string
|
||||||
bus *bus.MessageBus
|
bus *bus.MessageBus
|
||||||
workspace string
|
workspace string
|
||||||
tools *ToolRegistry
|
tools *ToolRegistry
|
||||||
maxIterations int
|
maxIterations int
|
||||||
nextID int
|
nextID int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSubagentManager(provider providers.LLMProvider, defaultModel, workspace string, bus *bus.MessageBus) *SubagentManager {
|
func NewSubagentManager(provider providers.LLMProvider, defaultModel, workspace string, bus *bus.MessageBus) *SubagentManager {
|
||||||
return &SubagentManager{
|
return &SubagentManager{
|
||||||
tasks: make(map[string]*SubagentTask),
|
tasks: make(map[string]*SubagentTask),
|
||||||
provider: provider,
|
provider: provider,
|
||||||
defaultModel: defaultModel,
|
defaultModel: defaultModel,
|
||||||
bus: bus,
|
bus: bus,
|
||||||
workspace: workspace,
|
workspace: workspace,
|
||||||
tools: NewToolRegistry(),
|
tools: NewToolRegistry(),
|
||||||
maxIterations: 10,
|
maxIterations: 10,
|
||||||
nextID: 1,
|
nextID: 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user