v0.50.1 a637010

anthropic_tool_names_test.go RawHistoryFolder

1package provider
2
3import (
4	"context"
5	"encoding/json"
6	"fmt"
7	"net/http"
8	"net/http/httptest"
9	"testing"
10)
11
12func TestAnthropicMCPResourceToolNames(t *testing.T) {
13	for _, helper := range []struct{ local, wire string }{
14		{"mcp_list_resources", "ListMcpResourcesTool"},
15		{"mcp_read_resource", "ReadMcpResourceTool"},
16	} {
17		for _, oauth := range []bool{false, true} {
18			t.Run(fmt.Sprintf("%s/oauth=%t", helper.local, oauth), func(t *testing.T) {
19				wireName := helper.local
20				if oauth {
21					wireName = helper.wire
22				}
23				tools := []Tool{
24					{Name: helper.local, Schema: json.RawMessage(`{"type":"object","properties":{}}`)},
25					{Name: "mcp__example__lookup", Schema: json.RawMessage(`{"type":"object","properties":{}}`)},
26				}
27				req := Request{
28					Model: "claude-sonnet-5-5",
29					Tools: tools,
30					Messages: []Message{
31						{Role: RoleUser, Content: []Content{TextBlock{Text: "List resources."}}},
32						{Role: RoleAssistant, Content: []Content{ToolCallBlock{ID: "prior-call", Name: helper.local, Arguments: json.RawMessage(`{}`)}}},
33						{Role: RoleTool, Content: []Content{ToolResultBlock{CallID: "prior-call", Content: []Content{TextBlock{Text: "No resources."}}}}},
34					},
35				}
36				srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
37					var body struct {
38						Tools    []anthTool `json:"tools"`
39						Messages []struct {
40							Content []struct {
41								Type string `json:"type"`
42								Name string `json:"name"`
43							} `json:"content"`
44						} `json:"messages"`
45					}
46					if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
47						t.Error(err)
48						w.WriteHeader(http.StatusBadRequest)
49						return
50					}
51					if len(body.Tools) != 2 || body.Tools[0].Name != wireName || body.Tools[1].Name != "mcp__example__lookup" {
52						t.Errorf("wire tools = %+v, want %q and unchanged server tool", body.Tools, wireName)
53					}
54					foundCall := false
55					for _, message := range body.Messages {
56						for _, block := range message.Content {
57							if block.Type == "tool_use" {
58								foundCall = true
59								if block.Name != wireName {
60									t.Errorf("replayed tool name = %q, want %q", block.Name, wireName)
61								}
62							}
63						}
64					}
65					if !foundCall {
66						t.Error("request lost replayed tool call")
67					}
68					w.Header().Set("Content-Type", "text/event-stream")
69					fmt.Fprintf(w, "event: content_block_start\ndata: {\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"next-call\",\"name\":%q,\"input\":{}}}\n\n", wireName)
70					fmt.Fprint(w, "event: content_block_delta\ndata: {\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{}\"}}\n\n")
71					fmt.Fprint(w, "event: content_block_stop\ndata: {\"index\":0}\n\nevent: message_delta\ndata: {\"delta\":{\"stop_reason\":\"tool_use\"}}\n\nevent: message_stop\ndata: {}\n\n")
72				}))
73				defer srv.Close()
74				client := NewAnthropic("synthetic-key", srv.URL)
75				if oauth {
76					client = NewAnthropicOAuth("synthetic-token", srv.URL)
77				}
78				events, err := client.Stream(context.Background(), req)
79				if err != nil {
80					t.Fatal(err)
81				}
82				started, completed := false, false
83				for event := range events {
84					switch e := event.(type) {
85					case EventToolStart:
86						started = true
87						if e.Name != helper.local {
88							t.Errorf("streamed tool name = %q, want %q", e.Name, helper.local)
89						}
90					case EventDone:
91						completed = true
92						if e.Err != nil || e.Stop != StopToolUse || len(e.Message.Content) != 1 {
93							t.Errorf("unexpected completion: %+v", e)
94							continue
95						}
96						call, ok := e.Message.Content[0].(ToolCallBlock)
97						if !ok || call.Name != helper.local || call.ID != "next-call" || string(call.Arguments) != "{}" {
98							t.Errorf("completed content = %+v, want local helper call", e.Message.Content)
99						}
100					}
101				}
102				if !started || !completed {
103					t.Fatalf("missing events: started=%t completed=%t", started, completed)
104				}
105				if tools[0].Name != helper.local {
106					t.Error("request mutated caller's tool definition")
107				}
108			})
109		}
110	}
111}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 477

↑↓ moveenter openesc close