package main import ( "encoding/base64" "encoding/json" "net/http" "path/filepath" "testing" "github.com/stretchr/testify/require" ) func TestImageShowUsesStoredImageForTelegramAndTerminal(t *testing.T) { picture := imageTestPNG(t, 5, 4) store := &ImageStore{dir: filepath.Join(t.TempDir(), imageDirName)} name, err := store.Save(picture) require.NoError(t, err) telegram := newTelegramFake(t) bot, logs := telegramStartTestBot(t, telegram) box := &Toolbox{imageShow: &ImageShow{ store: store, bot: bot, }} args, err := json.Marshal(ToolImageShowArguments{ Image: name, Caption: "A red point.", }) require.NoError(t, err) output := box.run(t.Context(), "image_show", string(args)) require.Equal(t, imageText([]string{name}, "A red point.")+"\n"+filepath.Join(store.dir, name), output) calls := telegram.Calls() require.Len(t, calls, 1) require.Equal(t, "sendPhoto", calls[0].method) require.Equal(t, "4242", calls[0].params["chat_id"]) require.Equal(t, "A red point.", calls[0].params["caption"]) require.Equal(t, name, calls[0].params["photo_name"]) require.Equal(t, picture, calls[0].params["photo"]) require.Empty(t, logs.String()) } func TestImageShowRefusesUnknownNamesBeforeSending(t *testing.T) { telegram := newTelegramFake(t) bot, _ := telegramStartTestBot(t, telegram) show := &ImageShow{ store: &ImageStore{dir: t.TempDir()}, bot: bot, } for _, name := range []string{"../image.png", "unknown", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.png"} { _, err := show.Show(t.Context(), name, "Do not send this.") require.Error(t, err) } require.Empty(t, telegram.Calls()) box := &Toolbox{} require.Equal(t, `error: unknown tool "image_show"`, box.run(t.Context(), "image_show", `{"image":"unknown","caption":""}`)) } func TestImageShowFallsBackOnlyWhenTelegramRefusesPhoto(t *testing.T) { testCases := []struct { name string status int methods []string failure bool }{ { name: "refused photo sends document", status: http.StatusBadRequest, methods: []string{"sendPhoto", "sendDocument"}, }, { name: "uncertain server error does not send twice", status: http.StatusBadGateway, methods: []string{"sendPhoto"}, failure: true, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { telegram := newTelegramFake(t) telegram.fail = func(call TelegramFakeCall) (int, string) { if call.method == "sendPhoto" { return tc.status, "photo refused" } return 0, "" } store := &ImageStore{dir: t.TempDir()} picture := imageTestPNG(t, 3, 3) name, err := store.Save(picture) require.NoError(t, err) bot, _ := telegramStartTestBot(t, telegram) show := &ImageShow{ store: store, bot: bot, } _, err = show.Show(t.Context(), name, "Caption") if tc.failure { require.Error(t, err) } else { require.NoError(t, err) } var methods []string for _, call := range telegram.Calls() { methods = append(methods, call.method) if call.method == "sendDocument" { require.Equal(t, picture, call.params["document"]) require.Equal(t, name, call.params["document_name"]) require.Equal(t, "Caption", call.params["caption"]) } } require.Equal(t, tc.methods, methods) }) } } func TestAgentShowsImageInTerminalWithoutTelegram(t *testing.T) { server := newModelFake(t) test := newTestTurnLoop(t, server) name, err := test.agent.Images().Save(imageTestPNG(t, 2, 2)) require.NoError(t, err) output := test.loop.tools.run(t.Context(), "image_show", `{"image":"`+name+`","caption":"Here it is."}`) require.Equal(t, imageText([]string{name}, "Here it is.")+"\n"+filepath.Join(test.agent.Images().dir, name), output) require.Contains(t, terminalEventText(Event{ Kind: "echo", Text: output, }), filepath.Join(test.agent.Images().dir, name)) } func TestTellImageMarkerGivesTheChildPixels(t *testing.T) { server := newModelFake(t) test := newDaemonMessageTest(t, t.TempDir(), server) picture := imageTestPNG(t, 4, 2) name, err := test.agents[0].agent.Images().Save(picture) require.NoError(t, err) test.Start(t, 0, 1) arguments, err := json.Marshal(ToolTellArguments{ Name: "Garden", Message: imageText([]string{name}, "Review this plan."), }) require.NoError(t, err) require.Contains(t, test.loops[0].tools.run(t.Context(), "tell", string(arguments)), "Sent.") test.events[1].WaitFor(t, "idle", 1) var childCall *ModelFakeCall for i := range server.Calls() { call := server.Call(i) if call.body["model"] == "gpt-test-garden" { childCall = &call break } } require.NotNil(t, childCall) require.Contains(t, string(childCall.raw), "input_image") require.Contains(t, string(childCall.raw), base64.StdEncoding.EncodeToString(picture)) require.Contains(t, test.events[1].Last("user"), "[image "+name+"]") require.NotContains(t, promptToolNames(test.agents[1].agent.Tools()), "image_show") } func TestAgentShowsTelegramReceivedImageToItsOwner(t *testing.T) { picture := imageTestPNG(t, 6, 4) store := &ImageStore{dir: t.TempDir()} name, err := store.Save(picture) require.NoError(t, err) telegram := newTelegramFake(t) telegram.Image("owner-photo", picture) bot, logs := telegramStartTestBot(t, telegram) server := newModelFake(t) server.reply = func(w http.ResponseWriter, call ModelFakeCall) { if call.number == 0 { arguments, err := json.Marshal(ToolImageShowArguments{ Image: name, Caption: "Here is your photo.", }) require.NoError(t, err) modelWriteEvents(w, modelCompletedEvent([]any{modelFunctionCallItem("fc_1", "call_1", "image_show", string(arguments))}, 100, 0, 5)) return } modelWriteEvents(w, modelCompletedEvent([]any{modelMessageItem("Shown.")}, 100, 0, 5)) } socket := daemonStartTestWithBot(t, server, bot) terminal := terminalStartTestClient(t, socket) terminal.WaitFor(t, "[connected to OneChat]") message := telegramOwnerMessage("") message.Caption = "Show this photo." message.Photo = []TelegramPhoto{{ FileID: "owner-photo", Width: 6, Height: 4, }} telegram.Add(TelegramUpdate{ UpdateID: 1, Message: message, }) terminal.WaitFor(t, "Shown.\n[done]") require.Contains(t, terminal.Output(), "[result] [image "+name+"]") require.Contains(t, terminal.Output(), filepath.Join(imageDirName, name)) var photos []TelegramFakeCall for _, call := range telegram.Calls() { if call.method == "sendPhoto" { photos = append(photos, call) } } require.Len(t, photos, 1) require.Equal(t, picture, photos[0].params["photo"]) require.Equal(t, "4242", photos[0].params["chat_id"]) require.Equal(t, "Here is your photo.", photos[0].params["caption"]) require.NotContains(t, logs.String(), "Here is your photo.") require.NotContains(t, logs.String(), "Show this photo.") require.NotContains(t, logs.String(), telegramTestToken) }