From 8687e162048279232e68f5bfb3be8acde8ab3ab0 Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Thu, 18 Jun 2026 15:57:00 -0400 Subject: [PATCH 1/2] Add Go low-level tool-definition E2E test Related to issue #1682 but does not fix #1682. Align low_level_tool_definition coverage with PR #1721 snapshot behavior by only defining tools exercised by the shared snapshot. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- go/internal/e2e/tools_e2e_test.go | 74 +++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/go/internal/e2e/tools_e2e_test.go b/go/internal/e2e/tools_e2e_test.go index 621f7758d..cd5b8d6f9 100644 --- a/go/internal/e2e/tools_e2e_test.go +++ b/go/internal/e2e/tools_e2e_test.go @@ -84,6 +84,80 @@ func TestToolsE2E(t *testing.T) { } }) + t.Run("low_level_tool_definition", func(t *testing.T) { + ctx.ConfigureForTest(t) + + type PhaseArgs struct { + Phase string `json:"phase" jsonschema:"Current phase,enum=searching,enum=analyzing,enum=done"` + } + type SearchArgs struct { + Keyword string `json:"keyword" jsonschema:"Search keyword"` + } + + currentPhase := "" + + setCurrentPhaseTool := copilot.DefineTool("set_current_phase", "Sets the current phase of the agent", + func(params PhaseArgs, inv copilot.ToolInvocation) (string, error) { + currentPhase = params.Phase + return "Phase set to " + params.Phase, nil + }) + + searchItemsTool := copilot.DefineTool("search_items", "Search for items by keyword", + func(params SearchArgs, inv copilot.ToolInvocation) (string, error) { + if params.Keyword != "copilot" { + t.Fatalf("Expected keyword to be 'copilot', got %q", params.Keyword) + } + return "Found: item_alpha, item_beta", nil + }) + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + AvailableTools: copilot.NewToolSet().AddCustom("*").AddBuiltIn("web_fetch").ToSlice(), + Tools: []copilot.Tool{ + setCurrentPhaseTool, + searchItemsTool, + }, + }) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + _, err = session.Send(t.Context(), copilot.MessageOptions{ + Prompt: "First, set the current phase to 'analyzing'. Then search for items with keyword 'copilot'. Report the phase and search results.", + }) + if err != nil { + t.Fatalf("Failed to send message: %v", err) + } + + answer, err := testharness.GetFinalAssistantMessage(t.Context(), session) + if err != nil { + t.Fatalf("Failed to get assistant message: %v", err) + } + + if answer == nil { + t.Fatalf("Expected non-nil assistant message") + } + ad, ok := answer.Data.(*copilot.AssistantMessageData) + if !ok { + t.Fatalf("Expected AssistantMessageData") + } + + content := ad.Content + if content == "" { + t.Fatalf("Expected non-empty response") + } + lower := strings.ToLower(content) + if !strings.Contains(lower, "analyzing") { + t.Errorf("Expected response to contain 'analyzing', got %q", content) + } + if !strings.Contains(lower, "item_alpha") && !strings.Contains(lower, "item_beta") { + t.Errorf("Expected response to contain 'item_alpha' or 'item_beta', got %q", content) + } + if currentPhase != "analyzing" { + t.Errorf("Expected currentPhase to be 'analyzing', got %q", currentPhase) + } + }) + t.Run("handles tool calling errors", func(t *testing.T) { ctx.ConfigureForTest(t) From aebc8ac98923f1c361aca15b23531a36f25b12fa Mon Sep 17 00:00:00 2001 From: Ed Burns Date: Thu, 18 Jun 2026 16:21:17 -0400 Subject: [PATCH 2/2] Address Go PR review suggestions for low-level tool test Synchronize handler-updated state with a mutex and move keyword assertion to the main test goroutine to avoid calling t.Fatalf from a tool handler goroutine. Related to issue #1682 but does not fix #1682. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- go/internal/e2e/tools_e2e_test.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/go/internal/e2e/tools_e2e_test.go b/go/internal/e2e/tools_e2e_test.go index cd5b8d6f9..1600eb163 100644 --- a/go/internal/e2e/tools_e2e_test.go +++ b/go/internal/e2e/tools_e2e_test.go @@ -94,19 +94,23 @@ func TestToolsE2E(t *testing.T) { Keyword string `json:"keyword" jsonschema:"Search keyword"` } + var mu sync.Mutex currentPhase := "" + searchKeyword := "" setCurrentPhaseTool := copilot.DefineTool("set_current_phase", "Sets the current phase of the agent", func(params PhaseArgs, inv copilot.ToolInvocation) (string, error) { + mu.Lock() currentPhase = params.Phase + mu.Unlock() return "Phase set to " + params.Phase, nil }) searchItemsTool := copilot.DefineTool("search_items", "Search for items by keyword", func(params SearchArgs, inv copilot.ToolInvocation) (string, error) { - if params.Keyword != "copilot" { - t.Fatalf("Expected keyword to be 'copilot', got %q", params.Keyword) - } + mu.Lock() + searchKeyword = params.Keyword + mu.Unlock() return "Found: item_alpha, item_beta", nil }) @@ -153,8 +157,15 @@ func TestToolsE2E(t *testing.T) { if !strings.Contains(lower, "item_alpha") && !strings.Contains(lower, "item_beta") { t.Errorf("Expected response to contain 'item_alpha' or 'item_beta', got %q", content) } - if currentPhase != "analyzing" { - t.Errorf("Expected currentPhase to be 'analyzing', got %q", currentPhase) + mu.Lock() + gotPhase := currentPhase + gotKeyword := searchKeyword + mu.Unlock() + if gotKeyword != "copilot" { + t.Errorf("Expected search keyword to be 'copilot', got %q", gotKeyword) + } + if gotPhase != "analyzing" { + t.Errorf("Expected currentPhase to be 'analyzing', got %q", gotPhase) } })