|
|
@@ -1,16 +1,18 @@
|
|
|
package agent
|
|
|
|
|
|
import (
|
|
|
- "encoding/json"
|
|
|
- "fmt"
|
|
|
+ "os"
|
|
|
+ "path/filepath"
|
|
|
"strings"
|
|
|
"testing"
|
|
|
|
|
|
"github.com/charmbracelet/crush/internal/agent/tools"
|
|
|
"github.com/charmbracelet/crush/internal/message"
|
|
|
+ "github.com/charmbracelet/crush/internal/shell"
|
|
|
"github.com/charmbracelet/fantasy/ai"
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
"github.com/stretchr/testify/require"
|
|
|
+ "gopkg.in/dnaeon/go-vcr.v4/pkg/recorder"
|
|
|
|
|
|
_ "github.com/joho/godotenv/autoload"
|
|
|
)
|
|
|
@@ -21,8 +23,7 @@ var modelPairs = []modelPair{
|
|
|
{"openrouter-kimi-k2", openRouterBuilder("moonshotai/kimi-k2-0905"), openRouterBuilder("qwen/qwen3-next-80b-a3b-instruct")},
|
|
|
}
|
|
|
|
|
|
-func getModels(t *testing.T, pair modelPair) (ai.LanguageModel, ai.LanguageModel) {
|
|
|
- r := newRecorder(t)
|
|
|
+func getModels(t *testing.T, r *recorder.Recorder, pair modelPair) (ai.LanguageModel, ai.LanguageModel) {
|
|
|
large, err := pair.largeModel(t, r)
|
|
|
require.NoError(t, err)
|
|
|
small, err := pair.smallModel(t, r)
|
|
|
@@ -31,11 +32,13 @@ func getModels(t *testing.T, pair modelPair) (ai.LanguageModel, ai.LanguageModel
|
|
|
}
|
|
|
|
|
|
func setupAgent(t *testing.T, pair modelPair) (SessionAgent, env) {
|
|
|
- large, small := getModels(t, pair)
|
|
|
+ r := newRecorder(t)
|
|
|
+ large, small := getModels(t, r, pair)
|
|
|
env := testEnv(t)
|
|
|
|
|
|
createSimpleGoProject(t, env.workingDir)
|
|
|
- agent, err := coderAgent(env, large, small)
|
|
|
+ agent, err := coderAgent(r, env, large, small)
|
|
|
+ shell.Reset(env.workingDir)
|
|
|
require.NoError(t, err)
|
|
|
return agent, env
|
|
|
}
|
|
|
@@ -82,8 +85,6 @@ func TestCoderAgent(t *testing.T) {
|
|
|
var tcID string
|
|
|
out:
|
|
|
for _, msg := range msgs {
|
|
|
- data, _ := json.Marshal(msg)
|
|
|
- fmt.Println(string(data))
|
|
|
if msg.Role == message.Assistant {
|
|
|
for _, tc := range msg.ToolCalls() {
|
|
|
if tc.Name == tools.ViewToolName {
|
|
|
@@ -104,6 +105,509 @@ func TestCoderAgent(t *testing.T) {
|
|
|
}
|
|
|
require.True(t, foundFile)
|
|
|
})
|
|
|
+ t.Run("update a file", func(t *testing.T) {
|
|
|
+ agent, env := setupAgent(t, pair)
|
|
|
+
|
|
|
+ session, err := env.sessions.Create(t.Context(), "New Session")
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ res, err := agent.Run(t.Context(), SessionAgentCall{
|
|
|
+ Prompt: "update the main.go file by changing the print to say hello from crush",
|
|
|
+ SessionID: session.ID,
|
|
|
+ MaxOutputTokens: 10000,
|
|
|
+ })
|
|
|
+ require.NoError(t, err)
|
|
|
+ assert.NotNil(t, res)
|
|
|
+
|
|
|
+ msgs, err := env.messages.List(t.Context(), session.ID)
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ foundRead := false
|
|
|
+ foundWrite := false
|
|
|
+ var readTCID, writeTCID string
|
|
|
+
|
|
|
+ for _, msg := range msgs {
|
|
|
+ if msg.Role == message.Assistant {
|
|
|
+ for _, tc := range msg.ToolCalls() {
|
|
|
+ if tc.Name == tools.ViewToolName {
|
|
|
+ readTCID = tc.ID
|
|
|
+ }
|
|
|
+ if tc.Name == tools.EditToolName || tc.Name == tools.WriteToolName {
|
|
|
+ writeTCID = tc.ID
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if msg.Role == message.Tool {
|
|
|
+ for _, tr := range msg.ToolResults() {
|
|
|
+ if tr.ToolCallID == readTCID {
|
|
|
+ foundRead = true
|
|
|
+ }
|
|
|
+ if tr.ToolCallID == writeTCID {
|
|
|
+ foundWrite = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ require.True(t, foundRead, "Expected to find a read operation")
|
|
|
+ require.True(t, foundWrite, "Expected to find a write operation")
|
|
|
+
|
|
|
+ mainGoPath := filepath.Join(env.workingDir, "main.go")
|
|
|
+ content, err := os.ReadFile(mainGoPath)
|
|
|
+ require.NoError(t, err)
|
|
|
+ require.Contains(t, strings.ToLower(string(content)), "hello from crush")
|
|
|
+ })
|
|
|
+ t.Run("bash tool", func(t *testing.T) {
|
|
|
+ agent, env := setupAgent(t, pair)
|
|
|
+
|
|
|
+ session, err := env.sessions.Create(t.Context(), "New Session")
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ res, err := agent.Run(t.Context(), SessionAgentCall{
|
|
|
+ Prompt: "use bash to create a file named test.txt with content 'hello bash'",
|
|
|
+ SessionID: session.ID,
|
|
|
+ MaxOutputTokens: 10000,
|
|
|
+ })
|
|
|
+ require.NoError(t, err)
|
|
|
+ assert.NotNil(t, res)
|
|
|
+
|
|
|
+ msgs, err := env.messages.List(t.Context(), session.ID)
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ foundBash := false
|
|
|
+ var bashTCID string
|
|
|
+
|
|
|
+ for _, msg := range msgs {
|
|
|
+ if msg.Role == message.Assistant {
|
|
|
+ for _, tc := range msg.ToolCalls() {
|
|
|
+ if tc.Name == tools.BashToolName {
|
|
|
+ bashTCID = tc.ID
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if msg.Role == message.Tool {
|
|
|
+ for _, tr := range msg.ToolResults() {
|
|
|
+ if tr.ToolCallID == bashTCID {
|
|
|
+ foundBash = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ require.True(t, foundBash, "Expected to find a bash operation")
|
|
|
+
|
|
|
+ testFilePath := filepath.Join(env.workingDir, "test.txt")
|
|
|
+ content, err := os.ReadFile(testFilePath)
|
|
|
+ require.NoError(t, err)
|
|
|
+ require.Contains(t, string(content), "hello bash")
|
|
|
+ })
|
|
|
+ t.Run("download tool", func(t *testing.T) {
|
|
|
+ agent, env := setupAgent(t, pair)
|
|
|
+
|
|
|
+ session, err := env.sessions.Create(t.Context(), "New Session")
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ res, err := agent.Run(t.Context(), SessionAgentCall{
|
|
|
+ Prompt: "download the file from https://httpbin.org/robots.txt and save it as robots.txt",
|
|
|
+ SessionID: session.ID,
|
|
|
+ MaxOutputTokens: 10000,
|
|
|
+ })
|
|
|
+ require.NoError(t, err)
|
|
|
+ assert.NotNil(t, res)
|
|
|
+
|
|
|
+ msgs, err := env.messages.List(t.Context(), session.ID)
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ foundDownload := false
|
|
|
+ var downloadTCID string
|
|
|
+
|
|
|
+ for _, msg := range msgs {
|
|
|
+ if msg.Role == message.Assistant {
|
|
|
+ for _, tc := range msg.ToolCalls() {
|
|
|
+ if tc.Name == tools.DownloadToolName {
|
|
|
+ downloadTCID = tc.ID
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if msg.Role == message.Tool {
|
|
|
+ for _, tr := range msg.ToolResults() {
|
|
|
+ if tr.ToolCallID == downloadTCID {
|
|
|
+ foundDownload = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ require.True(t, foundDownload, "Expected to find a download operation")
|
|
|
+
|
|
|
+ robotsPath := filepath.Join(env.workingDir, "robots.txt")
|
|
|
+ _, err = os.Stat(robotsPath)
|
|
|
+ require.NoError(t, err, "Expected robots.txt file to exist")
|
|
|
+ })
|
|
|
+ t.Run("fetch tool", func(t *testing.T) {
|
|
|
+ agent, env := setupAgent(t, pair)
|
|
|
+
|
|
|
+ session, err := env.sessions.Create(t.Context(), "New Session")
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ res, err := agent.Run(t.Context(), SessionAgentCall{
|
|
|
+ Prompt: "fetch the content from https://httpbin.org/html and tell me if it contains the word 'Herman'",
|
|
|
+ SessionID: session.ID,
|
|
|
+ MaxOutputTokens: 10000,
|
|
|
+ })
|
|
|
+ require.NoError(t, err)
|
|
|
+ assert.NotNil(t, res)
|
|
|
+
|
|
|
+ msgs, err := env.messages.List(t.Context(), session.ID)
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ foundFetch := false
|
|
|
+ var fetchTCID string
|
|
|
+
|
|
|
+ for _, msg := range msgs {
|
|
|
+ if msg.Role == message.Assistant {
|
|
|
+ for _, tc := range msg.ToolCalls() {
|
|
|
+ if tc.Name == tools.FetchToolName {
|
|
|
+ fetchTCID = tc.ID
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if msg.Role == message.Tool {
|
|
|
+ for _, tr := range msg.ToolResults() {
|
|
|
+ if tr.ToolCallID == fetchTCID {
|
|
|
+ foundFetch = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ require.True(t, foundFetch, "Expected to find a fetch operation")
|
|
|
+ })
|
|
|
+ t.Run("glob tool", func(t *testing.T) {
|
|
|
+ agent, env := setupAgent(t, pair)
|
|
|
+
|
|
|
+ session, err := env.sessions.Create(t.Context(), "New Session")
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ res, err := agent.Run(t.Context(), SessionAgentCall{
|
|
|
+ Prompt: "use glob to find all .go files in the current directory",
|
|
|
+ SessionID: session.ID,
|
|
|
+ MaxOutputTokens: 10000,
|
|
|
+ })
|
|
|
+ require.NoError(t, err)
|
|
|
+ assert.NotNil(t, res)
|
|
|
+
|
|
|
+ msgs, err := env.messages.List(t.Context(), session.ID)
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ foundGlob := false
|
|
|
+ var globTCID string
|
|
|
+
|
|
|
+ for _, msg := range msgs {
|
|
|
+ if msg.Role == message.Assistant {
|
|
|
+ for _, tc := range msg.ToolCalls() {
|
|
|
+ if tc.Name == tools.GlobToolName {
|
|
|
+ globTCID = tc.ID
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if msg.Role == message.Tool {
|
|
|
+ for _, tr := range msg.ToolResults() {
|
|
|
+ if tr.ToolCallID == globTCID {
|
|
|
+ foundGlob = true
|
|
|
+ require.Contains(t, tr.Content, "main.go", "Expected glob to find main.go")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ require.True(t, foundGlob, "Expected to find a glob operation")
|
|
|
+ })
|
|
|
+ t.Run("grep tool", func(t *testing.T) {
|
|
|
+ agent, env := setupAgent(t, pair)
|
|
|
+
|
|
|
+ session, err := env.sessions.Create(t.Context(), "New Session")
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ res, err := agent.Run(t.Context(), SessionAgentCall{
|
|
|
+ Prompt: "use grep to search for the word 'package' in go files",
|
|
|
+ SessionID: session.ID,
|
|
|
+ MaxOutputTokens: 10000,
|
|
|
+ })
|
|
|
+ require.NoError(t, err)
|
|
|
+ assert.NotNil(t, res)
|
|
|
+
|
|
|
+ msgs, err := env.messages.List(t.Context(), session.ID)
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ foundGrep := false
|
|
|
+ var grepTCID string
|
|
|
+
|
|
|
+ for _, msg := range msgs {
|
|
|
+ if msg.Role == message.Assistant {
|
|
|
+ for _, tc := range msg.ToolCalls() {
|
|
|
+ if tc.Name == tools.GrepToolName {
|
|
|
+ grepTCID = tc.ID
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if msg.Role == message.Tool {
|
|
|
+ for _, tr := range msg.ToolResults() {
|
|
|
+ if tr.ToolCallID == grepTCID {
|
|
|
+ foundGrep = true
|
|
|
+ require.Contains(t, tr.Content, "main.go", "Expected grep to find main.go")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ require.True(t, foundGrep, "Expected to find a grep operation")
|
|
|
+ })
|
|
|
+ t.Run("ls tool", func(t *testing.T) {
|
|
|
+ agent, env := setupAgent(t, pair)
|
|
|
+
|
|
|
+ session, err := env.sessions.Create(t.Context(), "New Session")
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ res, err := agent.Run(t.Context(), SessionAgentCall{
|
|
|
+ Prompt: "use ls to list the files in the current directory",
|
|
|
+ SessionID: session.ID,
|
|
|
+ MaxOutputTokens: 10000,
|
|
|
+ })
|
|
|
+ require.NoError(t, err)
|
|
|
+ assert.NotNil(t, res)
|
|
|
+
|
|
|
+ msgs, err := env.messages.List(t.Context(), session.ID)
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ foundLS := false
|
|
|
+ var lsTCID string
|
|
|
+
|
|
|
+ for _, msg := range msgs {
|
|
|
+ if msg.Role == message.Assistant {
|
|
|
+ for _, tc := range msg.ToolCalls() {
|
|
|
+ if tc.Name == tools.LSToolName {
|
|
|
+ lsTCID = tc.ID
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if msg.Role == message.Tool {
|
|
|
+ for _, tr := range msg.ToolResults() {
|
|
|
+ if tr.ToolCallID == lsTCID {
|
|
|
+ foundLS = true
|
|
|
+ require.Contains(t, tr.Content, "main.go", "Expected ls to list main.go")
|
|
|
+ require.Contains(t, tr.Content, "go.mod", "Expected ls to list go.mod")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ require.True(t, foundLS, "Expected to find an ls operation")
|
|
|
+ })
|
|
|
+ t.Run("multiedit tool", func(t *testing.T) {
|
|
|
+ agent, env := setupAgent(t, pair)
|
|
|
+
|
|
|
+ session, err := env.sessions.Create(t.Context(), "New Session")
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ res, err := agent.Run(t.Context(), SessionAgentCall{
|
|
|
+ Prompt: "use multiedit to change 'Hello, World!' to 'Hello, Crush!' and add a comment '// Greeting' above the fmt.Println line in main.go",
|
|
|
+ SessionID: session.ID,
|
|
|
+ MaxOutputTokens: 10000,
|
|
|
+ })
|
|
|
+ require.NoError(t, err)
|
|
|
+ assert.NotNil(t, res)
|
|
|
+
|
|
|
+ msgs, err := env.messages.List(t.Context(), session.ID)
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ foundMultiEdit := false
|
|
|
+ var multiEditTCID string
|
|
|
+
|
|
|
+ for _, msg := range msgs {
|
|
|
+ if msg.Role == message.Assistant {
|
|
|
+ for _, tc := range msg.ToolCalls() {
|
|
|
+ if tc.Name == tools.MultiEditToolName {
|
|
|
+ multiEditTCID = tc.ID
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if msg.Role == message.Tool {
|
|
|
+ for _, tr := range msg.ToolResults() {
|
|
|
+ if tr.ToolCallID == multiEditTCID {
|
|
|
+ foundMultiEdit = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ require.True(t, foundMultiEdit, "Expected to find a multiedit operation")
|
|
|
+
|
|
|
+ mainGoPath := filepath.Join(env.workingDir, "main.go")
|
|
|
+ content, err := os.ReadFile(mainGoPath)
|
|
|
+ require.NoError(t, err)
|
|
|
+ require.Contains(t, string(content), "Hello, Crush!", "Expected file to contain 'Hello, Crush!'")
|
|
|
+ })
|
|
|
+ t.Run("sourcegraph tool", func(t *testing.T) {
|
|
|
+ agent, env := setupAgent(t, pair)
|
|
|
+
|
|
|
+ session, err := env.sessions.Create(t.Context(), "New Session")
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ res, err := agent.Run(t.Context(), SessionAgentCall{
|
|
|
+ Prompt: "use sourcegraph to search for 'func main' in Go repositories",
|
|
|
+ SessionID: session.ID,
|
|
|
+ MaxOutputTokens: 10000,
|
|
|
+ })
|
|
|
+ require.NoError(t, err)
|
|
|
+ assert.NotNil(t, res)
|
|
|
+
|
|
|
+ msgs, err := env.messages.List(t.Context(), session.ID)
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ foundSourcegraph := false
|
|
|
+ var sourcegraphTCID string
|
|
|
+
|
|
|
+ for _, msg := range msgs {
|
|
|
+ if msg.Role == message.Assistant {
|
|
|
+ for _, tc := range msg.ToolCalls() {
|
|
|
+ if tc.Name == tools.SourcegraphToolName {
|
|
|
+ sourcegraphTCID = tc.ID
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if msg.Role == message.Tool {
|
|
|
+ for _, tr := range msg.ToolResults() {
|
|
|
+ if tr.ToolCallID == sourcegraphTCID {
|
|
|
+ foundSourcegraph = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ require.True(t, foundSourcegraph, "Expected to find a sourcegraph operation")
|
|
|
+ })
|
|
|
+ t.Run("write tool", func(t *testing.T) {
|
|
|
+ agent, env := setupAgent(t, pair)
|
|
|
+
|
|
|
+ session, err := env.sessions.Create(t.Context(), "New Session")
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ res, err := agent.Run(t.Context(), SessionAgentCall{
|
|
|
+ Prompt: "use write to create a new file called config.json with content '{\"name\": \"test\", \"version\": \"1.0.0\"}'",
|
|
|
+ SessionID: session.ID,
|
|
|
+ MaxOutputTokens: 10000,
|
|
|
+ })
|
|
|
+ require.NoError(t, err)
|
|
|
+ assert.NotNil(t, res)
|
|
|
+
|
|
|
+ msgs, err := env.messages.List(t.Context(), session.ID)
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ foundWrite := false
|
|
|
+ var writeTCID string
|
|
|
+
|
|
|
+ for _, msg := range msgs {
|
|
|
+ if msg.Role == message.Assistant {
|
|
|
+ for _, tc := range msg.ToolCalls() {
|
|
|
+ if tc.Name == tools.WriteToolName {
|
|
|
+ writeTCID = tc.ID
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if msg.Role == message.Tool {
|
|
|
+ for _, tr := range msg.ToolResults() {
|
|
|
+ if tr.ToolCallID == writeTCID {
|
|
|
+ foundWrite = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ require.True(t, foundWrite, "Expected to find a write operation")
|
|
|
+
|
|
|
+ configPath := filepath.Join(env.workingDir, "config.json")
|
|
|
+ content, err := os.ReadFile(configPath)
|
|
|
+ require.NoError(t, err)
|
|
|
+ require.Contains(t, string(content), "test", "Expected config.json to contain 'test'")
|
|
|
+ require.Contains(t, string(content), "1.0.0", "Expected config.json to contain '1.0.0'")
|
|
|
+ })
|
|
|
+ t.Run("parallel tool calls", func(t *testing.T) {
|
|
|
+ agent, env := setupAgent(t, pair)
|
|
|
+
|
|
|
+ session, err := env.sessions.Create(t.Context(), "New Session")
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ res, err := agent.Run(t.Context(), SessionAgentCall{
|
|
|
+ Prompt: "use glob to find all .go files and use ls to list the current directory, it is very important that you run both tool calls in parallel",
|
|
|
+ SessionID: session.ID,
|
|
|
+ MaxOutputTokens: 10000,
|
|
|
+ })
|
|
|
+ require.NoError(t, err)
|
|
|
+ assert.NotNil(t, res)
|
|
|
+
|
|
|
+ msgs, err := env.messages.List(t.Context(), session.ID)
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ var assistantMsg *message.Message
|
|
|
+ var toolMsgs []message.Message
|
|
|
+
|
|
|
+ for _, msg := range msgs {
|
|
|
+ if msg.Role == message.Assistant && len(msg.ToolCalls()) > 0 {
|
|
|
+ assistantMsg = &msg
|
|
|
+ }
|
|
|
+ if msg.Role == message.Tool {
|
|
|
+ toolMsgs = append(toolMsgs, msg)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ require.NotNil(t, assistantMsg, "Expected to find an assistant message with tool calls")
|
|
|
+ require.NotNil(t, toolMsgs, "Expected to find a tool message")
|
|
|
+
|
|
|
+ toolCalls := assistantMsg.ToolCalls()
|
|
|
+ require.GreaterOrEqual(t, len(toolCalls), 2, "Expected at least 2 tool calls in parallel")
|
|
|
+
|
|
|
+ foundGlob := false
|
|
|
+ foundLS := false
|
|
|
+ var globTCID, lsTCID string
|
|
|
+
|
|
|
+ for _, tc := range toolCalls {
|
|
|
+ if tc.Name == tools.GlobToolName {
|
|
|
+ foundGlob = true
|
|
|
+ globTCID = tc.ID
|
|
|
+ }
|
|
|
+ if tc.Name == tools.LSToolName {
|
|
|
+ foundLS = true
|
|
|
+ lsTCID = tc.ID
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ require.True(t, foundGlob, "Expected to find a glob tool call")
|
|
|
+ require.True(t, foundLS, "Expected to find an ls tool call")
|
|
|
+
|
|
|
+ require.GreaterOrEqual(t, len(toolMsgs), 2, "Expected at least 2 tool results in the same message")
|
|
|
+
|
|
|
+ foundGlobResult := false
|
|
|
+ foundLSResult := false
|
|
|
+
|
|
|
+ for _, msg := range toolMsgs {
|
|
|
+ for _, tr := range msg.ToolResults() {
|
|
|
+ if tr.ToolCallID == globTCID {
|
|
|
+ foundGlobResult = true
|
|
|
+ require.Contains(t, tr.Content, "main.go", "Expected glob result to contain main.go")
|
|
|
+ require.False(t, tr.IsError, "Expected glob result to not be an error")
|
|
|
+ }
|
|
|
+ if tr.ToolCallID == lsTCID {
|
|
|
+ foundLSResult = true
|
|
|
+ require.Contains(t, tr.Content, "main.go", "Expected ls result to contain main.go")
|
|
|
+ require.False(t, tr.IsError, "Expected ls result to not be an error")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ require.True(t, foundGlobResult, "Expected to find glob tool result")
|
|
|
+ require.True(t, foundLSResult, "Expected to find ls tool result")
|
|
|
+ })
|
|
|
})
|
|
|
}
|
|
|
}
|