From c239489fcd7efb16942668f4deab6dc8465f4f9a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:44:34 +0000 Subject: [PATCH 1/6] Initial plan From f4c26f0ea4e1b4afab065ba5110a1c34b58e20a8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:51:26 +0000 Subject: [PATCH 2/6] Implement anthropic tool lifecycle and streaming parity fixes Co-authored-by: sawka <2722291+sawka@users.noreply.github.com> --- pkg/aiusechat/anthropic/anthropic-backend.go | 79 ++++++++++-- .../anthropic/anthropic-backend_test.go | 95 +++++++++++++++ .../anthropic/anthropic-convertmessage.go | 113 +++++++++++++++++- pkg/aiusechat/usechat-backend.go | 9 +- 4 files changed, 279 insertions(+), 17 deletions(-) diff --git a/pkg/aiusechat/anthropic/anthropic-backend.go b/pkg/aiusechat/anthropic/anthropic-backend.go index b52b4a6797..131f1231af 100644 --- a/pkg/aiusechat/anthropic/anthropic-backend.go +++ b/pkg/aiusechat/anthropic/anthropic-backend.go @@ -10,8 +10,8 @@ import ( "errors" "fmt" "io" - "log" "net/http" + "net/url" "strings" "time" @@ -20,6 +20,7 @@ import ( "github.com/wavetermdev/waveterm/pkg/aiusechat/aiutil" "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore" "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" + "github.com/wavetermdev/waveterm/pkg/util/logutil" "github.com/wavetermdev/waveterm/pkg/util/utilfn" "github.com/wavetermdev/waveterm/pkg/web/sse" ) @@ -95,8 +96,9 @@ type anthropicMessageContentBlock struct { Name string `json:"name,omitempty"` Input interface{} `json:"input,omitempty"` - ToolUseDisplayName string `json:"toolusedisplayname,omitempty"` // internal field (cannot marshal to API, must be stripped) - ToolUseShortDescription string `json:"tooluseshortdescription,omitempty"` // internal field (cannot marshal to API, must be stripped) + ToolUseDisplayName string `json:"toolusedisplayname,omitempty"` // internal field (cannot marshal to API, must be stripped) + ToolUseShortDescription string `json:"tooluseshortdescription,omitempty"` // internal field (cannot marshal to API, must be stripped) + ToolUseData *uctypes.UIMessageDataToolUse `json:"toolusedata,omitempty"` // internal field (cannot marshal to API, must be stripped) // Tool result content ToolUseID string `json:"tool_use_id,omitempty"` @@ -154,6 +156,7 @@ func (b *anthropicMessageContentBlock) Clean() *anthropicMessageContentBlock { rtn.SourcePreviewUrl = "" rtn.ToolUseDisplayName = "" rtn.ToolUseShortDescription = "" + rtn.ToolUseData = nil if rtn.Source != nil { rtn.Source = rtn.Source.Clean() } @@ -298,6 +301,7 @@ type streamingState struct { stepStarted bool rtnMessage *anthropicChatMessage usage *anthropicUsageType + chatOpts uctypes.WaveChatOpts } func (p *partialJSON) Write(s string) { @@ -330,6 +334,20 @@ func (p *partialJSON) FinalObject() (json.RawMessage, error) { } } +// sanitizeHostnameInError removes the Wave cloud hostname from error messages +func sanitizeHostnameInError(err error) error { + if err == nil { + return nil + } + errStr := err.Error() + parsedURL, parseErr := url.Parse(uctypes.DefaultAIEndpoint) + if parseErr == nil && parsedURL.Host != "" && strings.Contains(errStr, parsedURL.Host) { + errStr = strings.ReplaceAll(errStr, uctypes.DefaultAIEndpoint, "AI service") + errStr = strings.ReplaceAll(errStr, parsedURL.Host, "host") + } + return fmt.Errorf("%s", errStr) +} + // makeThinkingOpts creates thinking options based on level and max tokens func makeThinkingOpts(thinkingLevel string, maxTokens int) *anthropicThinkingOpts { if thinkingLevel != uctypes.ThinkingLevelMedium && thinkingLevel != uctypes.ThinkingLevelHigh { @@ -373,13 +391,13 @@ func parseAnthropicHTTPError(resp *http.Response) error { // Try to parse as Anthropic error format first var eresp anthropicHTTPErrorResponse if err := json.Unmarshal(slurp, &eresp); err == nil && eresp.Error.Message != "" { - return fmt.Errorf("anthropic %s: %s", resp.Status, eresp.Error.Message) + return sanitizeHostnameInError(fmt.Errorf("anthropic %s: %s", resp.Status, eresp.Error.Message)) } // Try to parse as proxy error format var proxyErr uctypes.ProxyErrorResponse if err := json.Unmarshal(slurp, &proxyErr); err == nil && !proxyErr.Success && proxyErr.Error != "" { - return fmt.Errorf("anthropic %s: %s", resp.Status, proxyErr.Error) + return sanitizeHostnameInError(fmt.Errorf("anthropic %s: %s", resp.Status, proxyErr.Error)) } // Fall back to truncated raw response @@ -387,7 +405,7 @@ func parseAnthropicHTTPError(resp *http.Response) error { if msg == "" { msg = "unknown error" } - return fmt.Errorf("anthropic %s: %s", resp.Status, msg) + return sanitizeHostnameInError(fmt.Errorf("anthropic %s: %s", resp.Status, msg)) } func RunAnthropicChatStep( @@ -426,7 +444,7 @@ func RunAnthropicChatStep( // Validate continuation if provided if cont != nil { - if chatOpts.Config.Model != cont.Model { + if !uctypes.AreModelsCompatible(chat.APIType, chatOpts.Config.Model, cont.Model) { return nil, nil, nil, fmt.Errorf("cannot continue with a different model, model:%q, cont-model:%q", chatOpts.Config.Model, cont.Model) } } @@ -461,7 +479,7 @@ func RunAnthropicChatStep( resp, err := httpClient.Do(req) if err != nil { - return nil, nil, nil, err + return nil, nil, nil, sanitizeHostnameInError(err) } defer resp.Body.Close() @@ -499,7 +517,7 @@ func RunAnthropicChatStep( // Use eventsource decoder for proper SSE parsing decoder := eventsource.NewDecoder(resp.Body) - stopReason, rtnMessage := handleAnthropicStreamingResp(ctx, sse, decoder, cont) + stopReason, rtnMessage := handleAnthropicStreamingResp(ctx, sse, decoder, cont, chatOpts) return stopReason, rtnMessage, rateLimitInfo, nil } @@ -509,6 +527,7 @@ func handleAnthropicStreamingResp( sse *sse.SSEHandlerCh, decoder *eventsource.Decoder, cont *uctypes.WaveContinueResponse, + chatOpts uctypes.WaveChatOpts, ) (*uctypes.WaveStopReason, *anthropicChatMessage) { // Per-response state state := &streamingState{ @@ -518,6 +537,7 @@ func handleAnthropicStreamingResp( Role: "assistant", Content: []anthropicMessageContentBlock{}, }, + chatOpts: chatOpts, } var rtnStopReason *uctypes.WaveStopReason @@ -558,6 +578,13 @@ func handleAnthropicStreamingResp( // Normal end of stream break } + if sse.Err() != nil { + return &uctypes.WaveStopReason{ + Kind: uctypes.StopKindCanceled, + ErrorType: "client_disconnect", + ErrorText: "client disconnected", + }, extractPartialTextFromState(state) + } // transport error mid-stream _ = sse.AiMsgError(err.Error()) return &uctypes.WaveStopReason{ @@ -587,6 +614,28 @@ func handleAnthropicStreamingResp( return rtnStopReason, state.rtnMessage } +func extractPartialTextFromState(state *streamingState) *anthropicChatMessage { + var content []anthropicMessageContentBlock + for _, block := range state.rtnMessage.Content { + if block.Type == "text" && block.Text != "" { + content = append(content, block) + } + } + for _, st := range state.blockMap { + if st.kind == blockText && st.contentBlock != nil && st.contentBlock.Text != "" { + content = append(content, *st.contentBlock) + } + } + if len(content) == 0 { + return nil + } + return &anthropicChatMessage{ + MessageId: uuid.New().String(), + Role: "assistant", + Content: content, + } +} + // handleAnthropicEvent processes one SSE event block. It may emit SSE parts // and/or return a StopReason when the stream is complete. // @@ -601,6 +650,13 @@ func handleAnthropicEvent( state *streamingState, cont *uctypes.WaveContinueResponse, ) (stopFromDelta *string, final *uctypes.WaveStopReason) { + if err := sse.Err(); err != nil { + return nil, &uctypes.WaveStopReason{ + Kind: uctypes.StopKindCanceled, + ErrorType: "client_disconnect", + ErrorText: "client disconnected", + } + } eventName := event.Event() data := event.Data() switch eventName { @@ -732,6 +788,7 @@ func handleAnthropicEvent( if st.kind == blockToolUse { st.accumJSON.Write(ev.Delta.PartialJSON) _ = sse.AiMsgToolInputDelta(st.toolCallID, ev.Delta.PartialJSON) + aiutil.SendToolProgress(st.toolCallID, st.toolName, st.accumJSON.Bytes(), state.chatOpts, sse, true) } case "signature_delta": // Accumulate signature for thinking blocks @@ -784,6 +841,7 @@ func handleAnthropicEvent( } } _ = sse.AiMsgToolInputAvailable(st.toolCallID, st.toolName, raw) + aiutil.SendToolProgress(st.toolCallID, st.toolName, raw, state.chatOpts, sse, false) state.toolCalls = append(state.toolCalls, uctypes.WaveToolCall{ ID: st.toolCallID, Name: st.toolName, @@ -798,6 +856,7 @@ func handleAnthropicEvent( } state.rtnMessage.Content = append(state.rtnMessage.Content, toolUseBlock) } + delete(state.blockMap, *ev.Index) return nil, nil case "message_delta": @@ -868,7 +927,7 @@ func handleAnthropicEvent( } default: - log.Printf("unknown anthropic event type: %s", eventName) + logutil.DevPrintf("unknown anthropic event type: %s", eventName) return nil, nil } } diff --git a/pkg/aiusechat/anthropic/anthropic-backend_test.go b/pkg/aiusechat/anthropic/anthropic-backend_test.go index 8d9acb78e2..71e89bfb2f 100644 --- a/pkg/aiusechat/anthropic/anthropic-backend_test.go +++ b/pkg/aiusechat/anthropic/anthropic-backend_test.go @@ -6,6 +6,7 @@ package anthropic import ( "testing" + "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore" "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" ) @@ -69,3 +70,97 @@ func TestConvertPartsToAnthropicBlocks_SkipsUnknownTypes(t *testing.T) { t.Errorf("expected second text 'Another valid text', got %v", block2.Text) } } + +func TestGetFunctionCallInputByToolCallId(t *testing.T) { + toolData := &uctypes.UIMessageDataToolUse{ToolCallId: "call-1", ToolName: "read_file", Status: uctypes.ToolUseStatusPending} + chat := uctypes.AIChat{ + NativeMessages: []uctypes.GenAIMessage{ + &anthropicChatMessage{ + MessageId: "m1", + Role: "assistant", + Content: []anthropicMessageContentBlock{ + {Type: "tool_use", ID: "call-1", Name: "read_file", Input: map[string]interface{}{"path": "/tmp/a"}, ToolUseData: toolData}, + }, + }, + }, + } + fnCall := GetFunctionCallInputByToolCallId(chat, "call-1") + if fnCall == nil { + t.Fatalf("expected function call input") + } + if fnCall.CallId != "call-1" || fnCall.Name != "read_file" { + t.Fatalf("unexpected function call input: %#v", fnCall) + } + if fnCall.Arguments != "{\"path\":\"/tmp/a\"}" { + t.Fatalf("unexpected arguments: %s", fnCall.Arguments) + } + if fnCall.ToolUseData == nil || fnCall.ToolUseData.ToolCallId != "call-1" { + t.Fatalf("expected tool use data") + } +} + +func TestUpdateAndRemoveToolUseCall(t *testing.T) { + chatID := "anthropic-test-tooluse" + chatstore.DefaultChatStore.Delete(chatID) + defer chatstore.DefaultChatStore.Delete(chatID) + + aiOpts := &uctypes.AIOptsType{ + APIType: uctypes.APIType_AnthropicMessages, + Model: "claude-sonnet-4-5", + APIVersion: AnthropicDefaultAPIVersion, + } + msg := &anthropicChatMessage{ + MessageId: "m1", + Role: "assistant", + Content: []anthropicMessageContentBlock{ + {Type: "text", Text: "start"}, + {Type: "tool_use", ID: "call-1", Name: "read_file", Input: map[string]interface{}{"path": "/tmp/a"}}, + }, + } + if err := chatstore.DefaultChatStore.PostMessage(chatID, aiOpts, msg); err != nil { + t.Fatalf("failed to seed chat: %v", err) + } + + newData := uctypes.UIMessageDataToolUse{ToolCallId: "call-1", ToolName: "read_file", Status: uctypes.ToolUseStatusCompleted} + if err := UpdateToolUseData(chatID, "call-1", newData); err != nil { + t.Fatalf("update failed: %v", err) + } + + chat := chatstore.DefaultChatStore.Get(chatID) + updated := chat.NativeMessages[0].(*anthropicChatMessage) + if updated.Content[1].ToolUseData == nil || updated.Content[1].ToolUseData.Status != uctypes.ToolUseStatusCompleted { + t.Fatalf("tool use data not updated") + } + + if err := RemoveToolUseCall(chatID, "call-1"); err != nil { + t.Fatalf("remove failed: %v", err) + } + chat = chatstore.DefaultChatStore.Get(chatID) + updated = chat.NativeMessages[0].(*anthropicChatMessage) + if len(updated.Content) != 1 || updated.Content[0].Type != "text" { + t.Fatalf("expected tool_use block removed, got %#v", updated.Content) + } +} + +func TestConvertToUIMessageIncludesToolUseData(t *testing.T) { + msg := &anthropicChatMessage{ + MessageId: "m1", + Role: "assistant", + Content: []anthropicMessageContentBlock{ + { + Type: "tool_use", + ID: "call-1", + Name: "read_file", + Input: map[string]interface{}{"path": "/tmp/a"}, + ToolUseData: &uctypes.UIMessageDataToolUse{ToolCallId: "call-1", ToolName: "read_file", Status: uctypes.ToolUseStatusPending}, + }, + }, + } + ui := msg.ConvertToUIMessage() + if ui == nil || len(ui.Parts) != 2 { + t.Fatalf("expected tool and data-tooluse parts, got %#v", ui) + } + if ui.Parts[0].Type != "tool-read_file" || ui.Parts[1].Type != "data-tooluse" { + t.Fatalf("unexpected part types: %#v", ui.Parts) + } +} diff --git a/pkg/aiusechat/anthropic/anthropic-convertmessage.go b/pkg/aiusechat/anthropic/anthropic-convertmessage.go index 7fec54b1ab..10e900a6fa 100644 --- a/pkg/aiusechat/anthropic/anthropic-convertmessage.go +++ b/pkg/aiusechat/anthropic/anthropic-convertmessage.go @@ -13,10 +13,13 @@ import ( "log" "net/http" "regexp" + "slices" "strings" "github.com/google/uuid" + "github.com/wavetermdev/waveterm/pkg/aiusechat/chatstore" "github.com/wavetermdev/waveterm/pkg/aiusechat/uctypes" + "github.com/wavetermdev/waveterm/pkg/util/logutil" "github.com/wavetermdev/waveterm/pkg/util/utilfn" "github.com/wavetermdev/waveterm/pkg/wavebase" ) @@ -136,7 +139,7 @@ func buildAnthropicHTTPRequest(ctx context.Context, msgs []anthropicInputMessage // pretty print json of anthropicMsgs if jsonStr, err := utilfn.MarshalIndentNoHTMLString(convertedMsgs, "", " "); err == nil { - log.Printf("system-prompt: %v\n", chatOpts.SystemPrompt) + logutil.DevPrintf("system-prompt: %v\n", chatOpts.SystemPrompt) var toolNames []string for _, tool := range chatOpts.Tools { toolNames = append(toolNames, tool.Name) @@ -144,9 +147,9 @@ func buildAnthropicHTTPRequest(ctx context.Context, msgs []anthropicInputMessage for _, tool := range chatOpts.TabTools { toolNames = append(toolNames, tool.Name) } - log.Printf("tools: %s\n", strings.Join(toolNames, ", ")) - log.Printf("anthropicMsgs JSON:\n%s", jsonStr) - log.Printf("has-api-key: %v\n", opts.APIToken != "") + logutil.DevPrintf("tools: %s\n", strings.Join(toolNames, ", ")) + logutil.DevPrintf("anthropicMsgs JSON:\n%s", jsonStr) + logutil.DevPrintf("has-api-key: %v\n", opts.APIToken != "") } var buf bytes.Buffer @@ -698,6 +701,13 @@ func (m *anthropicChatMessage) ConvertToUIMessage() *uctypes.UIMessage { ToolCallID: block.ID, Input: block.Input, }) + if block.ToolUseData != nil { + parts = append(parts, uctypes.UIMessagePart{ + Type: "data-tooluse", + ID: block.ID, + Data: *block.ToolUseData, + }) + } } default: // For now, skip all other types (will implement later) @@ -827,3 +837,98 @@ func ConvertAIChatToUIChat(aiChat uctypes.AIChat) (*uctypes.UIChat, error) { Messages: uiMessages, }, nil } + +func GetFunctionCallInputByToolCallId(aiChat uctypes.AIChat, toolCallId string) *uctypes.AIFunctionCallInput { + for _, genMsg := range aiChat.NativeMessages { + chatMsg, ok := genMsg.(*anthropicChatMessage) + if !ok { + continue + } + for _, block := range chatMsg.Content { + if block.Type != "tool_use" || block.ID != toolCallId { + continue + } + argsBytes, err := json.Marshal(block.Input) + if err != nil { + continue + } + return &uctypes.AIFunctionCallInput{ + CallId: block.ID, + Name: block.Name, + Arguments: string(argsBytes), + ToolUseData: block.ToolUseData, + } + } + } + return nil +} + +func UpdateToolUseData(chatId string, toolCallId string, toolUseData uctypes.UIMessageDataToolUse) error { + chat := chatstore.DefaultChatStore.Get(chatId) + if chat == nil { + return fmt.Errorf("chat not found: %s", chatId) + } + for _, genMsg := range chat.NativeMessages { + chatMsg, ok := genMsg.(*anthropicChatMessage) + if !ok { + continue + } + for i, block := range chatMsg.Content { + if block.Type != "tool_use" || block.ID != toolCallId { + continue + } + updatedMsg := &anthropicChatMessage{ + MessageId: chatMsg.MessageId, + Usage: chatMsg.Usage, + Role: chatMsg.Role, + Content: slices.Clone(chatMsg.Content), + } + updatedMsg.Content[i].ToolUseData = &toolUseData + aiOpts := &uctypes.AIOptsType{ + APIType: chat.APIType, + Model: chat.Model, + APIVersion: chat.APIVersion, + } + return chatstore.DefaultChatStore.PostMessage(chatId, aiOpts, updatedMsg) + } + } + return fmt.Errorf("tool call with ID %s not found in chat %s", toolCallId, chatId) +} + +func RemoveToolUseCall(chatId string, toolCallId string) error { + chat := chatstore.DefaultChatStore.Get(chatId) + if chat == nil { + return fmt.Errorf("chat not found: %s", chatId) + } + for _, genMsg := range chat.NativeMessages { + chatMsg, ok := genMsg.(*anthropicChatMessage) + if !ok { + continue + } + for i, block := range chatMsg.Content { + if block.Type != "tool_use" || block.ID != toolCallId { + continue + } + updatedMsg := &anthropicChatMessage{ + MessageId: chatMsg.MessageId, + Usage: chatMsg.Usage, + Role: chatMsg.Role, + Content: slices.Delete(slices.Clone(chatMsg.Content), i, i+1), + } + if len(updatedMsg.Content) == 0 { + chatstore.DefaultChatStore.RemoveMessage(chatId, chatMsg.MessageId) + } else { + aiOpts := &uctypes.AIOptsType{ + APIType: chat.APIType, + Model: chat.Model, + APIVersion: chat.APIVersion, + } + if err := chatstore.DefaultChatStore.PostMessage(chatId, aiOpts, updatedMsg); err != nil { + return err + } + } + return nil + } + } + return nil +} diff --git a/pkg/aiusechat/usechat-backend.go b/pkg/aiusechat/usechat-backend.go index cb380a457c..37e2f432ec 100644 --- a/pkg/aiusechat/usechat-backend.go +++ b/pkg/aiusechat/usechat-backend.go @@ -186,15 +186,18 @@ func (b *anthropicBackend) RunChatStep( cont *uctypes.WaveContinueResponse, ) (*uctypes.WaveStopReason, []uctypes.GenAIMessage, *uctypes.RateLimitInfo, error) { stopReason, msg, rateLimitInfo, err := anthropic.RunAnthropicChatStep(ctx, sseHandler, chatOpts, cont) + if msg == nil { + return stopReason, nil, rateLimitInfo, err + } return stopReason, []uctypes.GenAIMessage{msg}, rateLimitInfo, err } func (b *anthropicBackend) UpdateToolUseData(chatId string, toolCallId string, toolUseData uctypes.UIMessageDataToolUse) error { - return fmt.Errorf("UpdateToolUseData not implemented for anthropic backend") + return anthropic.UpdateToolUseData(chatId, toolCallId, toolUseData) } func (b *anthropicBackend) RemoveToolUseCall(chatId string, toolCallId string) error { - return fmt.Errorf("RemoveToolUseCall not implemented for anthropic backend") + return anthropic.RemoveToolUseCall(chatId, toolCallId) } func (b *anthropicBackend) ConvertToolResultsToNativeChatMessage(toolResults []uctypes.AIToolResult) ([]uctypes.GenAIMessage, error) { @@ -210,7 +213,7 @@ func (b *anthropicBackend) ConvertAIMessageToNativeChatMessage(message uctypes.A } func (b *anthropicBackend) GetFunctionCallInputByToolCallId(aiChat uctypes.AIChat, toolCallId string) *uctypes.AIFunctionCallInput { - return nil + return anthropic.GetFunctionCallInputByToolCallId(aiChat, toolCallId) } func (b *anthropicBackend) ConvertAIChatToUIChat(aiChat uctypes.AIChat) (*uctypes.UIChat, error) { From 96467627e8b39aaa5dadb4a3c21c617be7a4c847 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:54:51 +0000 Subject: [PATCH 3/6] Finalize anthropic backend parity updates and tests Co-authored-by: sawka <2722291+sawka@users.noreply.github.com> --- pkg/aiusechat/anthropic/anthropic-backend.go | 16 ++++++++++++++-- tsunami/demo/cpuchart/go.mod | 7 +------ tsunami/demo/cpuchart/go.sum | 12 ------------ tsunami/demo/githubaction/go.mod | 7 ------- tsunami/demo/githubaction/go.sum | 4 ---- tsunami/demo/modaltest/go.mod | 7 ------- tsunami/demo/modaltest/go.sum | 4 ---- tsunami/demo/pomodoro/go.mod | 7 ------- tsunami/demo/pomodoro/go.sum | 4 ---- tsunami/demo/recharts/go.mod | 7 ------- tsunami/demo/recharts/go.sum | 4 ---- tsunami/demo/tabletest/go.mod | 7 ------- tsunami/demo/tabletest/go.sum | 4 ---- tsunami/demo/todo/go.mod | 7 ------- tsunami/demo/todo/go.sum | 4 ---- tsunami/demo/tsunamiconfig/go.mod | 7 ------- tsunami/demo/tsunamiconfig/go.sum | 4 ---- 17 files changed, 15 insertions(+), 97 deletions(-) diff --git a/pkg/aiusechat/anthropic/anthropic-backend.go b/pkg/aiusechat/anthropic/anthropic-backend.go index 131f1231af..9d2415f7ed 100644 --- a/pkg/aiusechat/anthropic/anthropic-backend.go +++ b/pkg/aiusechat/anthropic/anthropic-backend.go @@ -12,6 +12,7 @@ import ( "io" "net/http" "net/url" + "sort" "strings" "time" @@ -621,7 +622,15 @@ func extractPartialTextFromState(state *streamingState) *anthropicChatMessage { content = append(content, block) } } - for _, st := range state.blockMap { + var partialIdx []int + for idx, st := range state.blockMap { + if st.kind == blockText && st.contentBlock != nil && st.contentBlock.Text != "" { + partialIdx = append(partialIdx, idx) + } + } + sort.Ints(partialIdx) + for _, idx := range partialIdx { + st := state.blockMap[idx] if st.kind == blockText && st.contentBlock != nil && st.contentBlock.Text != "" { content = append(content, *st.contentBlock) } @@ -630,9 +639,10 @@ func extractPartialTextFromState(state *streamingState) *anthropicChatMessage { return nil } return &anthropicChatMessage{ - MessageId: uuid.New().String(), + MessageId: state.rtnMessage.MessageId, Role: "assistant", Content: content, + Usage: state.rtnMessage.Usage, } } @@ -856,6 +866,8 @@ func handleAnthropicEvent( } state.rtnMessage.Content = append(state.rtnMessage.Content, toolUseBlock) } + // extractPartialTextFromState reads blockMap for still-in-flight content, so remove completed blocks + // once they have been appended to rtnMessage.Content to avoid duplicate text on disconnect. delete(state.blockMap, *ev.Index) return nil, nil diff --git a/tsunami/demo/cpuchart/go.mod b/tsunami/demo/cpuchart/go.mod index a828a4479b..72d2f4ddcc 100644 --- a/tsunami/demo/cpuchart/go.mod +++ b/tsunami/demo/cpuchart/go.mod @@ -2,17 +2,12 @@ module tsunami/app/cpuchart go 1.25.6 -require ( - github.com/shirou/gopsutil/v4 v4.25.8 - github.com/wavetermdev/waveterm/tsunami v0.0.0 -) +require github.com/shirou/gopsutil/v4 v4.25.8 require ( github.com/ebitengine/purego v0.8.4 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect - github.com/outrigdev/goid v0.3.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect diff --git a/tsunami/demo/cpuchart/go.sum b/tsunami/demo/cpuchart/go.sum index 4d3c872cfc..965d0ed1e9 100644 --- a/tsunami/demo/cpuchart/go.sum +++ b/tsunami/demo/cpuchart/go.sum @@ -1,26 +1,16 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970= github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= @@ -32,5 +22,3 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tsunami/demo/githubaction/go.mod b/tsunami/demo/githubaction/go.mod index e93f7a5b90..8af0676a3c 100644 --- a/tsunami/demo/githubaction/go.mod +++ b/tsunami/demo/githubaction/go.mod @@ -2,11 +2,4 @@ module tsunami/app/githubaction go 1.25.6 -require github.com/wavetermdev/waveterm/tsunami v0.0.0 - -require ( - github.com/google/uuid v1.6.0 // indirect - github.com/outrigdev/goid v0.3.0 // indirect -) - replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/githubaction/go.sum b/tsunami/demo/githubaction/go.sum index 4c44991dfc..e69de29bb2 100644 --- a/tsunami/demo/githubaction/go.sum +++ b/tsunami/demo/githubaction/go.sum @@ -1,4 +0,0 @@ -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/modaltest/go.mod b/tsunami/demo/modaltest/go.mod index 7a4ec22e75..cd56206d7d 100644 --- a/tsunami/demo/modaltest/go.mod +++ b/tsunami/demo/modaltest/go.mod @@ -2,11 +2,4 @@ module github.com/wavetermdev/waveterm/tsunami/demo/modaltest go 1.25.6 -require github.com/wavetermdev/waveterm/tsunami v0.0.0-00010101000000-000000000000 - -require ( - github.com/google/uuid v1.6.0 // indirect - github.com/outrigdev/goid v0.3.0 // indirect -) - replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/modaltest/go.sum b/tsunami/demo/modaltest/go.sum index 4c44991dfc..e69de29bb2 100644 --- a/tsunami/demo/modaltest/go.sum +++ b/tsunami/demo/modaltest/go.sum @@ -1,4 +0,0 @@ -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/pomodoro/go.mod b/tsunami/demo/pomodoro/go.mod index 777fdb392e..9c6becc31f 100644 --- a/tsunami/demo/pomodoro/go.mod +++ b/tsunami/demo/pomodoro/go.mod @@ -2,11 +2,4 @@ module tsunami/app/pomodoro go 1.25.6 -require github.com/wavetermdev/waveterm/tsunami v0.0.0 - -require ( - github.com/google/uuid v1.6.0 // indirect - github.com/outrigdev/goid v0.3.0 // indirect -) - replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/pomodoro/go.sum b/tsunami/demo/pomodoro/go.sum index 4c44991dfc..e69de29bb2 100644 --- a/tsunami/demo/pomodoro/go.sum +++ b/tsunami/demo/pomodoro/go.sum @@ -1,4 +0,0 @@ -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/recharts/go.mod b/tsunami/demo/recharts/go.mod index 1369113900..4067012a94 100644 --- a/tsunami/demo/recharts/go.mod +++ b/tsunami/demo/recharts/go.mod @@ -2,11 +2,4 @@ module tsunami/app/recharts go 1.25.6 -require github.com/wavetermdev/waveterm/tsunami v0.0.0 - -require ( - github.com/google/uuid v1.6.0 // indirect - github.com/outrigdev/goid v0.3.0 // indirect -) - replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/recharts/go.sum b/tsunami/demo/recharts/go.sum index 4c44991dfc..e69de29bb2 100644 --- a/tsunami/demo/recharts/go.sum +++ b/tsunami/demo/recharts/go.sum @@ -1,4 +0,0 @@ -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/tabletest/go.mod b/tsunami/demo/tabletest/go.mod index 77550fcfbd..fdb23ddd6e 100644 --- a/tsunami/demo/tabletest/go.mod +++ b/tsunami/demo/tabletest/go.mod @@ -2,11 +2,4 @@ module tsunami/app/tabletest go 1.25.6 -require github.com/wavetermdev/waveterm/tsunami v0.0.0 - -require ( - github.com/google/uuid v1.6.0 // indirect - github.com/outrigdev/goid v0.3.0 // indirect -) - replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/tabletest/go.sum b/tsunami/demo/tabletest/go.sum index 4c44991dfc..e69de29bb2 100644 --- a/tsunami/demo/tabletest/go.sum +++ b/tsunami/demo/tabletest/go.sum @@ -1,4 +0,0 @@ -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/todo/go.mod b/tsunami/demo/todo/go.mod index c70affff32..1c17fba3c5 100644 --- a/tsunami/demo/todo/go.mod +++ b/tsunami/demo/todo/go.mod @@ -2,11 +2,4 @@ module tsunami/app/todo go 1.25.6 -require github.com/wavetermdev/waveterm/tsunami v0.0.0 - -require ( - github.com/google/uuid v1.6.0 // indirect - github.com/outrigdev/goid v0.3.0 // indirect -) - replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/todo/go.sum b/tsunami/demo/todo/go.sum index 4c44991dfc..e69de29bb2 100644 --- a/tsunami/demo/todo/go.sum +++ b/tsunami/demo/todo/go.sum @@ -1,4 +0,0 @@ -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/tsunamiconfig/go.mod b/tsunami/demo/tsunamiconfig/go.mod index bc485e2ace..a3118a9ca7 100644 --- a/tsunami/demo/tsunamiconfig/go.mod +++ b/tsunami/demo/tsunamiconfig/go.mod @@ -2,11 +2,4 @@ module tsunami/app/tsunamiconfig go 1.25.6 -require github.com/wavetermdev/waveterm/tsunami v0.0.0 - -require ( - github.com/google/uuid v1.6.0 // indirect - github.com/outrigdev/goid v0.3.0 // indirect -) - replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/tsunamiconfig/go.sum b/tsunami/demo/tsunamiconfig/go.sum index 4c44991dfc..e69de29bb2 100644 --- a/tsunami/demo/tsunamiconfig/go.sum +++ b/tsunami/demo/tsunamiconfig/go.sum @@ -1,4 +0,0 @@ -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= -github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= From 6100556b1fe3c23f9bf8028664d144f6d6e6edc4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:55:07 +0000 Subject: [PATCH 4/6] Revert unintended tsunami demo dependency changes Co-authored-by: sawka <2722291+sawka@users.noreply.github.com> --- tsunami/demo/cpuchart/go.mod | 7 ++++++- tsunami/demo/cpuchart/go.sum | 12 ++++++++++++ tsunami/demo/githubaction/go.mod | 7 +++++++ tsunami/demo/githubaction/go.sum | 4 ++++ tsunami/demo/modaltest/go.mod | 7 +++++++ tsunami/demo/modaltest/go.sum | 4 ++++ tsunami/demo/pomodoro/go.mod | 7 +++++++ tsunami/demo/pomodoro/go.sum | 4 ++++ tsunami/demo/recharts/go.mod | 7 +++++++ tsunami/demo/recharts/go.sum | 4 ++++ tsunami/demo/tabletest/go.mod | 7 +++++++ tsunami/demo/tabletest/go.sum | 4 ++++ tsunami/demo/todo/go.mod | 7 +++++++ tsunami/demo/todo/go.sum | 4 ++++ tsunami/demo/tsunamiconfig/go.mod | 7 +++++++ tsunami/demo/tsunamiconfig/go.sum | 4 ++++ 16 files changed, 95 insertions(+), 1 deletion(-) diff --git a/tsunami/demo/cpuchart/go.mod b/tsunami/demo/cpuchart/go.mod index 72d2f4ddcc..a828a4479b 100644 --- a/tsunami/demo/cpuchart/go.mod +++ b/tsunami/demo/cpuchart/go.mod @@ -2,12 +2,17 @@ module tsunami/app/cpuchart go 1.25.6 -require github.com/shirou/gopsutil/v4 v4.25.8 +require ( + github.com/shirou/gopsutil/v4 v4.25.8 + github.com/wavetermdev/waveterm/tsunami v0.0.0 +) require ( github.com/ebitengine/purego v0.8.4 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/outrigdev/goid v0.3.0 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect diff --git a/tsunami/demo/cpuchart/go.sum b/tsunami/demo/cpuchart/go.sum index 965d0ed1e9..4d3c872cfc 100644 --- a/tsunami/demo/cpuchart/go.sum +++ b/tsunami/demo/cpuchart/go.sum @@ -1,16 +1,26 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/shirou/gopsutil/v4 v4.25.8 h1:NnAsw9lN7587WHxjJA9ryDnqhJpFH6A+wagYWTOH970= github.com/shirou/gopsutil/v4 v4.25.8/go.mod h1:q9QdMmfAOVIw7a+eF86P7ISEU6ka+NLgkUxlopV4RwI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= @@ -22,3 +32,5 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tsunami/demo/githubaction/go.mod b/tsunami/demo/githubaction/go.mod index 8af0676a3c..e93f7a5b90 100644 --- a/tsunami/demo/githubaction/go.mod +++ b/tsunami/demo/githubaction/go.mod @@ -2,4 +2,11 @@ module tsunami/app/githubaction go 1.25.6 +require github.com/wavetermdev/waveterm/tsunami v0.0.0 + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/outrigdev/goid v0.3.0 // indirect +) + replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/githubaction/go.sum b/tsunami/demo/githubaction/go.sum index e69de29bb2..4c44991dfc 100644 --- a/tsunami/demo/githubaction/go.sum +++ b/tsunami/demo/githubaction/go.sum @@ -0,0 +1,4 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/modaltest/go.mod b/tsunami/demo/modaltest/go.mod index cd56206d7d..7a4ec22e75 100644 --- a/tsunami/demo/modaltest/go.mod +++ b/tsunami/demo/modaltest/go.mod @@ -2,4 +2,11 @@ module github.com/wavetermdev/waveterm/tsunami/demo/modaltest go 1.25.6 +require github.com/wavetermdev/waveterm/tsunami v0.0.0-00010101000000-000000000000 + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/outrigdev/goid v0.3.0 // indirect +) + replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/modaltest/go.sum b/tsunami/demo/modaltest/go.sum index e69de29bb2..4c44991dfc 100644 --- a/tsunami/demo/modaltest/go.sum +++ b/tsunami/demo/modaltest/go.sum @@ -0,0 +1,4 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/pomodoro/go.mod b/tsunami/demo/pomodoro/go.mod index 9c6becc31f..777fdb392e 100644 --- a/tsunami/demo/pomodoro/go.mod +++ b/tsunami/demo/pomodoro/go.mod @@ -2,4 +2,11 @@ module tsunami/app/pomodoro go 1.25.6 +require github.com/wavetermdev/waveterm/tsunami v0.0.0 + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/outrigdev/goid v0.3.0 // indirect +) + replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/pomodoro/go.sum b/tsunami/demo/pomodoro/go.sum index e69de29bb2..4c44991dfc 100644 --- a/tsunami/demo/pomodoro/go.sum +++ b/tsunami/demo/pomodoro/go.sum @@ -0,0 +1,4 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/recharts/go.mod b/tsunami/demo/recharts/go.mod index 4067012a94..1369113900 100644 --- a/tsunami/demo/recharts/go.mod +++ b/tsunami/demo/recharts/go.mod @@ -2,4 +2,11 @@ module tsunami/app/recharts go 1.25.6 +require github.com/wavetermdev/waveterm/tsunami v0.0.0 + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/outrigdev/goid v0.3.0 // indirect +) + replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/recharts/go.sum b/tsunami/demo/recharts/go.sum index e69de29bb2..4c44991dfc 100644 --- a/tsunami/demo/recharts/go.sum +++ b/tsunami/demo/recharts/go.sum @@ -0,0 +1,4 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/tabletest/go.mod b/tsunami/demo/tabletest/go.mod index fdb23ddd6e..77550fcfbd 100644 --- a/tsunami/demo/tabletest/go.mod +++ b/tsunami/demo/tabletest/go.mod @@ -2,4 +2,11 @@ module tsunami/app/tabletest go 1.25.6 +require github.com/wavetermdev/waveterm/tsunami v0.0.0 + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/outrigdev/goid v0.3.0 // indirect +) + replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/tabletest/go.sum b/tsunami/demo/tabletest/go.sum index e69de29bb2..4c44991dfc 100644 --- a/tsunami/demo/tabletest/go.sum +++ b/tsunami/demo/tabletest/go.sum @@ -0,0 +1,4 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/todo/go.mod b/tsunami/demo/todo/go.mod index 1c17fba3c5..c70affff32 100644 --- a/tsunami/demo/todo/go.mod +++ b/tsunami/demo/todo/go.mod @@ -2,4 +2,11 @@ module tsunami/app/todo go 1.25.6 +require github.com/wavetermdev/waveterm/tsunami v0.0.0 + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/outrigdev/goid v0.3.0 // indirect +) + replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/todo/go.sum b/tsunami/demo/todo/go.sum index e69de29bb2..4c44991dfc 100644 --- a/tsunami/demo/todo/go.sum +++ b/tsunami/demo/todo/go.sum @@ -0,0 +1,4 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= diff --git a/tsunami/demo/tsunamiconfig/go.mod b/tsunami/demo/tsunamiconfig/go.mod index a3118a9ca7..bc485e2ace 100644 --- a/tsunami/demo/tsunamiconfig/go.mod +++ b/tsunami/demo/tsunamiconfig/go.mod @@ -2,4 +2,11 @@ module tsunami/app/tsunamiconfig go 1.25.6 +require github.com/wavetermdev/waveterm/tsunami v0.0.0 + +require ( + github.com/google/uuid v1.6.0 // indirect + github.com/outrigdev/goid v0.3.0 // indirect +) + replace github.com/wavetermdev/waveterm/tsunami => /Users/mike/work/waveterm/tsunami diff --git a/tsunami/demo/tsunamiconfig/go.sum b/tsunami/demo/tsunamiconfig/go.sum index e69de29bb2..4c44991dfc 100644 --- a/tsunami/demo/tsunamiconfig/go.sum +++ b/tsunami/demo/tsunamiconfig/go.sum @@ -0,0 +1,4 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/outrigdev/goid v0.3.0 h1:t/otQD3EXc45cLtQVPUnNgEyRaTQA4cPeu3qVcrsIws= +github.com/outrigdev/goid v0.3.0/go.mod h1:hEH7f27ypN/GHWt/7gvkRoFYR0LZizfUBIAbak4neVE= From 0ae41658b687fcff62b21c423ca1902d9fe8edc9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:57:30 +0000 Subject: [PATCH 5/6] Handle nil anthropic tool-call input serialization Co-authored-by: sawka <2722291+sawka@users.noreply.github.com> --- pkg/aiusechat/anthropic/anthropic-convertmessage.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/aiusechat/anthropic/anthropic-convertmessage.go b/pkg/aiusechat/anthropic/anthropic-convertmessage.go index 10e900a6fa..bd6383b2d6 100644 --- a/pkg/aiusechat/anthropic/anthropic-convertmessage.go +++ b/pkg/aiusechat/anthropic/anthropic-convertmessage.go @@ -848,7 +848,11 @@ func GetFunctionCallInputByToolCallId(aiChat uctypes.AIChat, toolCallId string) if block.Type != "tool_use" || block.ID != toolCallId { continue } - argsBytes, err := json.Marshal(block.Input) + argsInput := block.Input + if argsInput == nil { + argsInput = map[string]interface{}{} + } + argsBytes, err := json.Marshal(argsInput) if err != nil { continue } From d629e4a51598607b89d54a09fe7e0c9a3a1872b7 Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 3 Mar 2026 17:30:55 -0800 Subject: [PATCH 6/6] add websearch tool for anthropic --- frontend/app/aipanel/aipanel.tsx | 2 +- package-lock.json | 4 +- pkg/aiusechat/anthropic/anthropic-backend.go | 48 ++++++++++++------- .../anthropic/anthropic-convertmessage.go | 18 +++---- 4 files changed, 44 insertions(+), 28 deletions(-) diff --git a/frontend/app/aipanel/aipanel.tsx b/frontend/app/aipanel/aipanel.tsx index 46780455c2..dded015f85 100644 --- a/frontend/app/aipanel/aipanel.tsx +++ b/frontend/app/aipanel/aipanel.tsx @@ -306,7 +306,7 @@ const AIPanelComponentInner = memo(() => { }; useEffect(() => { - globalStore.set(model.isAIStreaming, status == "streaming"); + globalStore.set(model.isAIStreaming, status === "streaming" || status === "submitted"); }, [status]); useEffect(() => { diff --git a/package-lock.json b/package-lock.json index 269181d32a..f11d429435 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "waveterm", - "version": "0.14.1-beta.1", + "version": "0.14.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "waveterm", - "version": "0.14.1-beta.1", + "version": "0.14.1", "hasInstallScript": true, "license": "Apache-2.0", "workspaces": [ diff --git a/pkg/aiusechat/anthropic/anthropic-backend.go b/pkg/aiusechat/anthropic/anthropic-backend.go index 9d2415f7ed..02070b1bf8 100644 --- a/pkg/aiusechat/anthropic/anthropic-backend.go +++ b/pkg/aiusechat/anthropic/anthropic-backend.go @@ -58,10 +58,11 @@ func (m *anthropicChatMessage) GetUsage() *uctypes.AIUsage { } return &uctypes.AIUsage{ - APIType: uctypes.APIType_AnthropicMessages, - Model: m.Usage.Model, - InputTokens: m.Usage.InputTokens, - OutputTokens: m.Usage.OutputTokens, + APIType: uctypes.APIType_AnthropicMessages, + Model: m.Usage.Model, + InputTokens: m.Usage.InputTokens, + OutputTokens: m.Usage.OutputTokens, + NativeWebSearchCount: m.Usage.NativeWebSearchCount, } } @@ -181,10 +182,15 @@ type anthropicStreamRequest struct { Stream bool `json:"stream"` System []anthropicMessageContentBlock `json:"system,omitempty"` ToolChoice any `json:"tool_choice,omitempty"` - Tools []uctypes.ToolDefinition `json:"tools,omitempty"` + Tools []any `json:"tools,omitempty"` // *uctypes.ToolDefinition or *anthropicWebSearchTool Thinking *anthropicThinkingOpts `json:"thinking,omitempty"` } +type anthropicWebSearchTool struct { + Type string `json:"type"` // "web_search_20250305" + Name string `json:"name"` // "web_search" +} + type anthropicCacheControl struct { Type string `json:"type"` // "ephemeral" TTL string `json:"ttl"` // "5m" or "1h" @@ -232,8 +238,9 @@ type anthropicUsageType struct { CacheCreationInputTokens int `json:"cache_creation_input_tokens,omitempty"` CacheReadInputTokens int `json:"cache_read_input_tokens,omitempty"` - // internal field for Wave use (not sent to API) - Model string `json:"model,omitempty"` + // internal fields for Wave use (not sent to API) + Model string `json:"model,omitempty"` + NativeWebSearchCount int `json:"nativewebsearchcount,omitempty"` // for reference, but we dont keep thsese up to date or track them CacheCreation *anthropicCacheCreationType `json:"cache_creation,omitempty"` // breakdown of cached tokens by TTL @@ -294,15 +301,16 @@ type partialJSON struct { } type streamingState struct { - blockMap map[int]*blockState - toolCalls []uctypes.WaveToolCall - stopFromDelta string - msgID string - model string - stepStarted bool - rtnMessage *anthropicChatMessage - usage *anthropicUsageType - chatOpts uctypes.WaveChatOpts + blockMap map[int]*blockState + toolCalls []uctypes.WaveToolCall + stopFromDelta string + msgID string + model string + stepStarted bool + rtnMessage *anthropicChatMessage + usage *anthropicUsageType + chatOpts uctypes.WaveChatOpts + webSearchCount int } func (p *partialJSON) Write(s string) { @@ -547,8 +555,10 @@ func handleAnthropicStreamingResp( defer func() { // Set usage in the returned message if state.usage != nil { - // Set model in usage for internal use state.usage.Model = state.model + if state.webSearchCount > 0 { + state.usage.NativeWebSearchCount = state.webSearchCount + } state.rtnMessage.Usage = state.usage } @@ -759,6 +769,10 @@ func handleAnthropicEvent( } state.blockMap[idx] = st _ = sse.AiMsgToolInputStart(tcID, tName) + case "server_tool_use": + if ev.ContentBlock.Name == "web_search" { + state.webSearchCount++ + } default: // ignore other block types gracefully per Anthropic guidance :contentReference[oaicite:18]{index=18} } diff --git a/pkg/aiusechat/anthropic/anthropic-convertmessage.go b/pkg/aiusechat/anthropic/anthropic-convertmessage.go index bd6383b2d6..552cc8080c 100644 --- a/pkg/aiusechat/anthropic/anthropic-convertmessage.go +++ b/pkg/aiusechat/anthropic/anthropic-convertmessage.go @@ -122,24 +122,23 @@ func buildAnthropicHTTPRequest(ctx context.Context, msgs []anthropicInputMessage reqBody.System = systemBlocks } - if len(chatOpts.Tools) > 0 { - cleanedTools := make([]uctypes.ToolDefinition, len(chatOpts.Tools)) - for i, tool := range chatOpts.Tools { - cleanedTools[i] = *tool.Clean() - } - reqBody.Tools = cleanedTools + for _, tool := range chatOpts.Tools { + cleanedTool := tool.Clean() + reqBody.Tools = append(reqBody.Tools, cleanedTool) } for _, tool := range chatOpts.TabTools { - cleanedTool := *tool.Clean() + cleanedTool := tool.Clean() reqBody.Tools = append(reqBody.Tools, cleanedTool) } + if chatOpts.AllowNativeWebSearch { + reqBody.Tools = append(reqBody.Tools, &anthropicWebSearchTool{Type: "web_search_20250305", Name: "web_search"}) + } // Enable extended thinking based on level reqBody.Thinking = makeThinkingOpts(opts.ThinkingLevel, maxTokens) // pretty print json of anthropicMsgs if jsonStr, err := utilfn.MarshalIndentNoHTMLString(convertedMsgs, "", " "); err == nil { - logutil.DevPrintf("system-prompt: %v\n", chatOpts.SystemPrompt) var toolNames []string for _, tool := range chatOpts.Tools { toolNames = append(toolNames, tool.Name) @@ -147,6 +146,9 @@ func buildAnthropicHTTPRequest(ctx context.Context, msgs []anthropicInputMessage for _, tool := range chatOpts.TabTools { toolNames = append(toolNames, tool.Name) } + if chatOpts.AllowNativeWebSearch { + toolNames = append(toolNames, "web_search[server]") + } logutil.DevPrintf("tools: %s\n", strings.Join(toolNames, ", ")) logutil.DevPrintf("anthropicMsgs JSON:\n%s", jsonStr) logutil.DevPrintf("has-api-key: %v\n", opts.APIToken != "")