fix ci
This commit is contained in:
parent
cea0fa35db
commit
a188e7463c
8 changed files with 49 additions and 146 deletions
|
|
@ -3,6 +3,7 @@ package auth
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
@ -19,6 +20,19 @@ import (
|
||||||
"github.com/sipeed/picoclaw/pkg/config"
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func newIPv4TestServer(t *testing.T, handler http.Handler) *httptest.Server {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
server := httptest.NewUnstartedServer(handler)
|
||||||
|
listener, err := net.Listen("tcp4", "127.0.0.1:0")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
server.Listener = listener
|
||||||
|
server.Start()
|
||||||
|
t.Cleanup(server.Close)
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewWeComCommand(t *testing.T) {
|
func TestNewWeComCommand(t *testing.T) {
|
||||||
cmd := newWeComCommand()
|
cmd := newWeComCommand()
|
||||||
|
|
||||||
|
|
@ -53,7 +67,7 @@ func TestBuildWeComQRCodePageURL(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFetchWeComQRCode(t *testing.T) {
|
func TestFetchWeComQRCode(t *testing.T) {
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := newIPv4TestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
assert.Equal(t, "/generate", r.URL.Path)
|
assert.Equal(t, "/generate", r.URL.Path)
|
||||||
assert.Equal(t, wecomQRSourceID, r.URL.Query().Get("source"))
|
assert.Equal(t, wecomQRSourceID, r.URL.Query().Get("source"))
|
||||||
assert.Equal(t, wecomQRSourceID, r.URL.Query().Get("sourceID"))
|
assert.Equal(t, wecomQRSourceID, r.URL.Query().Get("sourceID"))
|
||||||
|
|
@ -61,7 +75,6 @@ func TestFetchWeComQRCode(t *testing.T) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_, _ = w.Write([]byte(`{"data":{"scode":"scode-1","auth_url":"https://example.com/qr"}}`))
|
_, _ = w.Write([]byte(`{"data":{"scode":"scode-1","auth_url":"https://example.com/qr"}}`))
|
||||||
}))
|
}))
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
opts := normalizeWeComQRFlowOptions(wecomQRFlowOptions{
|
opts := normalizeWeComQRFlowOptions(wecomQRFlowOptions{
|
||||||
HTTPClient: server.Client(),
|
HTTPClient: server.Client(),
|
||||||
|
|
@ -78,7 +91,7 @@ func TestFetchWeComQRCode(t *testing.T) {
|
||||||
func TestPollWeComQRCodeResult(t *testing.T) {
|
func TestPollWeComQRCodeResult(t *testing.T) {
|
||||||
var calls atomic.Int32
|
var calls atomic.Int32
|
||||||
|
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := newIPv4TestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
call := calls.Add(1)
|
call := calls.Add(1)
|
||||||
assert.Equal(t, "/query", r.URL.Path)
|
assert.Equal(t, "/query", r.URL.Path)
|
||||||
assert.Equal(t, "scode-1", r.URL.Query().Get("scode"))
|
assert.Equal(t, "scode-1", r.URL.Query().Get("scode"))
|
||||||
|
|
@ -92,7 +105,6 @@ func TestPollWeComQRCodeResult(t *testing.T) {
|
||||||
_, _ = w.Write([]byte(`{"data":{"status":"success","bot_info":{"botid":"bot-1","secret":"secret-1"}}}`))
|
_, _ = w.Write([]byte(`{"data":{"status":"success","bot_info":{"botid":"bot-1","secret":"secret-1"}}}`))
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
var output bytes.Buffer
|
var output bytes.Buffer
|
||||||
opts := normalizeWeComQRFlowOptions(wecomQRFlowOptions{
|
opts := normalizeWeComQRFlowOptions(wecomQRFlowOptions{
|
||||||
|
|
|
||||||
|
|
@ -1794,43 +1794,6 @@ func (m *toolFeedbackReasoningProvider) GetDefaultModel() string {
|
||||||
return "tool-feedback-reasoning-model"
|
return "tool-feedback-reasoning-model"
|
||||||
}
|
}
|
||||||
|
|
||||||
type toolFeedbackExtraContentProvider struct {
|
|
||||||
filePath string
|
|
||||||
calls int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *toolFeedbackExtraContentProvider) Chat(
|
|
||||||
ctx context.Context,
|
|
||||||
messages []providers.Message,
|
|
||||||
tools []providers.ToolDefinition,
|
|
||||||
model string,
|
|
||||||
opts map[string]any,
|
|
||||||
) (*providers.LLMResponse, error) {
|
|
||||||
m.calls++
|
|
||||||
if m.calls == 1 {
|
|
||||||
return &providers.LLMResponse{
|
|
||||||
ToolCalls: []providers.ToolCall{{
|
|
||||||
ID: "call_explicit_read_file",
|
|
||||||
Type: "function",
|
|
||||||
Name: "read_file",
|
|
||||||
Arguments: map[string]any{"path": m.filePath},
|
|
||||||
ExtraContent: &providers.ExtraContent{
|
|
||||||
ToolFeedbackExplanation: "Read README.md first to confirm the current project structure.",
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &providers.LLMResponse{
|
|
||||||
Content: "DONE",
|
|
||||||
ToolCalls: []providers.ToolCall{},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *toolFeedbackExtraContentProvider) GetDefaultModel() string {
|
|
||||||
return "tool-feedback-extra-content-model"
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestToolFeedbackExplanationFromResponse_UsesCurrentContentFirst(t *testing.T) {
|
func TestToolFeedbackExplanationFromResponse_UsesCurrentContentFirst(t *testing.T) {
|
||||||
response := &providers.LLMResponse{
|
response := &providers.LLMResponse{
|
||||||
Content: "Read README.md first",
|
Content: "Read README.md first",
|
||||||
|
|
@ -3934,8 +3897,14 @@ func TestProcessMessage_DoesNotLeakReasoningContentInToolFeedback(t *testing.T)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessMessage_DoesNotPublishToolFeedbackForDiscordWhenDisabled(t *testing.T) {
|
func TestProcessMessage_DoesNotPublishToolFeedbackForDiscordWhenDisabled(t *testing.T) {
|
||||||
|
assertToolFeedbackNotPublishedWhenDisabled(t, "discord")
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertToolFeedbackNotPublishedWhenDisabled(t *testing.T, channel string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
tmpDir := t.TempDir()
|
tmpDir := t.TempDir()
|
||||||
heartbeatFile := filepath.Join(tmpDir, "tool-feedback-discord.txt")
|
heartbeatFile := filepath.Join(tmpDir, "tool-feedback-"+channel+".txt")
|
||||||
if err := os.WriteFile(heartbeatFile, []byte("tool feedback task"), 0o644); err != nil {
|
if err := os.WriteFile(heartbeatFile, []byte("tool feedback task"), 0o644); err != nil {
|
||||||
t.Fatalf("WriteFile() error = %v", err)
|
t.Fatalf("WriteFile() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -3961,7 +3930,7 @@ func TestProcessMessage_DoesNotPublishToolFeedbackForDiscordWhenDisabled(t *test
|
||||||
al := NewAgentLoop(cfg, msgBus, provider)
|
al := NewAgentLoop(cfg, msgBus, provider)
|
||||||
|
|
||||||
response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{
|
response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{
|
||||||
Channel: "discord",
|
Channel: channel,
|
||||||
SenderID: "user-1",
|
SenderID: "user-1",
|
||||||
ChatID: "chat-1",
|
ChatID: "chat-1",
|
||||||
Content: "check tool feedback",
|
Content: "check tool feedback",
|
||||||
|
|
@ -3975,103 +3944,17 @@ func TestProcessMessage_DoesNotPublishToolFeedbackForDiscordWhenDisabled(t *test
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case outbound := <-msgBus.OutboundChan():
|
case outbound := <-msgBus.OutboundChan():
|
||||||
t.Fatalf("expected no outbound tool feedback for discord when disabled, got %+v", outbound)
|
t.Fatalf("expected no outbound tool feedback for %s when disabled, got %+v", channel, outbound)
|
||||||
case <-time.After(200 * time.Millisecond):
|
case <-time.After(200 * time.Millisecond):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessMessage_DoesNotPublishToolFeedbackForTelegramWhenDisabled(t *testing.T) {
|
func TestProcessMessage_DoesNotPublishToolFeedbackForTelegramWhenDisabled(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
assertToolFeedbackNotPublishedWhenDisabled(t, "telegram")
|
||||||
heartbeatFile := filepath.Join(tmpDir, "tool-feedback-telegram-default.txt")
|
|
||||||
if err := os.WriteFile(heartbeatFile, []byte("tool feedback task"), 0o644); err != nil {
|
|
||||||
t.Fatalf("WriteFile() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &config.Config{
|
|
||||||
Agents: config.AgentsConfig{
|
|
||||||
Defaults: config.AgentDefaults{
|
|
||||||
Workspace: tmpDir,
|
|
||||||
ModelName: "test-model",
|
|
||||||
MaxTokens: 4096,
|
|
||||||
MaxToolIterations: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Tools: config.ToolsConfig{
|
|
||||||
ReadFile: config.ReadFileToolConfig{
|
|
||||||
Enabled: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
msgBus := bus.NewMessageBus()
|
|
||||||
provider := &toolFeedbackProvider{filePath: heartbeatFile}
|
|
||||||
al := NewAgentLoop(cfg, msgBus, provider)
|
|
||||||
|
|
||||||
response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{
|
|
||||||
Channel: "telegram",
|
|
||||||
SenderID: "user-1",
|
|
||||||
ChatID: "chat-1",
|
|
||||||
Content: "check tool feedback",
|
|
||||||
}))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("processMessage() error = %v", err)
|
|
||||||
}
|
|
||||||
if response != "HEARTBEAT_OK" {
|
|
||||||
t.Fatalf("processMessage() response = %q, want %q", response, "HEARTBEAT_OK")
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case outbound := <-msgBus.OutboundChan():
|
|
||||||
t.Fatalf("expected no outbound tool feedback for telegram when disabled, got %+v", outbound)
|
|
||||||
case <-time.After(200 * time.Millisecond):
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessMessage_DoesNotPublishToolFeedbackForFeishuWhenDisabled(t *testing.T) {
|
func TestProcessMessage_DoesNotPublishToolFeedbackForFeishuWhenDisabled(t *testing.T) {
|
||||||
tmpDir := t.TempDir()
|
assertToolFeedbackNotPublishedWhenDisabled(t, "feishu")
|
||||||
heartbeatFile := filepath.Join(tmpDir, "tool-feedback-feishu-default.txt")
|
|
||||||
if err := os.WriteFile(heartbeatFile, []byte("tool feedback task"), 0o644); err != nil {
|
|
||||||
t.Fatalf("WriteFile() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &config.Config{
|
|
||||||
Agents: config.AgentsConfig{
|
|
||||||
Defaults: config.AgentDefaults{
|
|
||||||
Workspace: tmpDir,
|
|
||||||
ModelName: "test-model",
|
|
||||||
MaxTokens: 4096,
|
|
||||||
MaxToolIterations: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Tools: config.ToolsConfig{
|
|
||||||
ReadFile: config.ReadFileToolConfig{
|
|
||||||
Enabled: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
msgBus := bus.NewMessageBus()
|
|
||||||
provider := &toolFeedbackProvider{filePath: heartbeatFile}
|
|
||||||
al := NewAgentLoop(cfg, msgBus, provider)
|
|
||||||
|
|
||||||
response, err := al.processMessage(context.Background(), testInboundMessage(bus.InboundMessage{
|
|
||||||
Channel: "feishu",
|
|
||||||
SenderID: "user-1",
|
|
||||||
ChatID: "chat-1",
|
|
||||||
Content: "check tool feedback",
|
|
||||||
}))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("processMessage() error = %v", err)
|
|
||||||
}
|
|
||||||
if response != "HEARTBEAT_OK" {
|
|
||||||
t.Fatalf("processMessage() response = %q, want %q", response, "HEARTBEAT_OK")
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case outbound := <-msgBus.OutboundChan():
|
|
||||||
t.Fatalf("expected no outbound tool feedback for feishu when disabled, got %+v", outbound)
|
|
||||||
case <-time.After(200 * time.Millisecond):
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessMessage_MessageToolPublishesOutboundWithTurnMetadata(t *testing.T) {
|
func TestProcessMessage_MessageToolPublishesOutboundWithTurnMetadata(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/audio/tts"
|
"github.com/sipeed/picoclaw/pkg/audio/tts"
|
||||||
"github.com/sipeed/picoclaw/pkg/bus"
|
"github.com/sipeed/picoclaw/pkg/bus"
|
||||||
"github.com/sipeed/picoclaw/pkg/channels"
|
"github.com/sipeed/picoclaw/pkg/channels"
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/channels"
|
|
||||||
|
|
||||||
larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
|
larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
|
||||||
|
|
||||||
|
"github.com/sipeed/picoclaw/pkg/channels"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExtractContent(t *testing.T) {
|
func TestExtractContent(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -932,7 +932,10 @@ func TestPreSendMedia_LeavesTrackedMessageForChannelSend(t *testing.T) {
|
||||||
}, ch)
|
}, ch)
|
||||||
|
|
||||||
if ch.dismissedChatID != "" {
|
if ch.dismissedChatID != "" {
|
||||||
t.Fatalf("expected tracked tool feedback cleanup to be deferred to channel media send, got %q", ch.dismissedChatID)
|
t.Fatalf(
|
||||||
|
"expected tracked tool feedback cleanup to be deferred to channel media send, got %q",
|
||||||
|
ch.dismissedChatID,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,13 +109,6 @@ func successResponseWithMessageID(t *testing.T, messageID int) *ta.Response {
|
||||||
return &ta.Response{Ok: true, Result: b}
|
return &ta.Response{Ok: true, Result: b}
|
||||||
}
|
}
|
||||||
|
|
||||||
func successBoolResponse(t *testing.T) *ta.Response {
|
|
||||||
t.Helper()
|
|
||||||
b, err := json.Marshal(true)
|
|
||||||
require.NoError(t, err)
|
|
||||||
return &ta.Response{Ok: true, Result: b}
|
|
||||||
}
|
|
||||||
|
|
||||||
func successUserResponse(t *testing.T, user *telego.User) *ta.Response {
|
func successUserResponse(t *testing.T, user *telego.User) *ta.Response {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
b, err := json.Marshal(user)
|
b, err := json.Marshal(user)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@ package utils
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestFormatToolFeedbackMessage(t *testing.T) {
|
func TestFormatToolFeedbackMessage(t *testing.T) {
|
||||||
got := FormatToolFeedbackMessage("read_file", "I will read README.md first to confirm the current project structure.")
|
got := FormatToolFeedbackMessage(
|
||||||
|
"read_file",
|
||||||
|
"I will read README.md first to confirm the current project structure.",
|
||||||
|
)
|
||||||
want := "\U0001f527 `read_file`\nI will read README.md first to confirm the current project structure."
|
want := "\U0001f527 `read_file`\nI will read README.md first to confirm the current project structure."
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Fatalf("FormatToolFeedbackMessage() = %q, want %q", got, want)
|
t.Fatalf("FormatToolFeedbackMessage() = %q, want %q", got, want)
|
||||||
|
|
@ -27,7 +30,10 @@ func TestFormatToolFeedbackMessage_EmptyToolNameOmitsToolLine(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFitToolFeedbackMessage_TruncatesBodyWithinSingleMessage(t *testing.T) {
|
func TestFitToolFeedbackMessage_TruncatesBodyWithinSingleMessage(t *testing.T) {
|
||||||
got := FitToolFeedbackMessage("\U0001f527 `read_file`\nRead README.md first to confirm the current project structure.", 40)
|
got := FitToolFeedbackMessage(
|
||||||
|
"\U0001f527 `read_file`\nRead README.md first to confirm the current project structure.",
|
||||||
|
40,
|
||||||
|
)
|
||||||
want := "\U0001f527 `read_file`\nRead README.md first to..."
|
want := "\U0001f527 `read_file`\nRead README.md first to..."
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Fatalf("FitToolFeedbackMessage() = %q, want %q", got, want)
|
t.Fatalf("FitToolFeedbackMessage() = %q, want %q", got, want)
|
||||||
|
|
|
||||||
|
|
@ -700,7 +700,8 @@ func TestHandleGetSession_DoesNotExposeLegacyToolArgumentsWhenExplanationMissing
|
||||||
t.Fatalf("LoadConfig() error = %v", err)
|
t.Fatalf("LoadConfig() error = %v", err)
|
||||||
}
|
}
|
||||||
cfg.Agents.Defaults.ToolFeedback.MaxArgsLength = 20
|
cfg.Agents.Defaults.ToolFeedback.MaxArgsLength = 20
|
||||||
if err := config.SaveConfig(configPath, cfg); err != nil {
|
err = config.SaveConfig(configPath, cfg)
|
||||||
|
if err != nil {
|
||||||
t.Fatalf("SaveConfig() error = %v", err)
|
t.Fatalf("SaveConfig() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -712,7 +713,11 @@ func TestHandleGetSession_DoesNotExposeLegacyToolArgumentsWhenExplanationMissing
|
||||||
|
|
||||||
argsJSON := `{"path":"README.md","start_line":1,"end_line":10,"extra":"abcdefghijklmnopqrstuvwxyz"}`
|
argsJSON := `{"path":"README.md","start_line":1,"end_line":10,"extra":"abcdefghijklmnopqrstuvwxyz"}`
|
||||||
sessionKey := picoSessionPrefix + "detail-tool-summary-legacy-args"
|
sessionKey := picoSessionPrefix + "detail-tool-summary-legacy-args"
|
||||||
if err := store.AddFullMessage(nil, sessionKey, providers.Message{Role: "user", Content: "check file"}); err != nil {
|
if err := store.AddFullMessage(
|
||||||
|
nil,
|
||||||
|
sessionKey,
|
||||||
|
providers.Message{Role: "user", Content: "check file"},
|
||||||
|
); err != nil {
|
||||||
t.Fatalf("AddFullMessage(user) error = %v", err)
|
t.Fatalf("AddFullMessage(user) error = %v", err)
|
||||||
}
|
}
|
||||||
if err := store.AddFullMessage(nil, sessionKey, providers.Message{
|
if err := store.AddFullMessage(nil, sessionKey, providers.Message{
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue