gui/pkg/mcp/tools_chat.go
Snider 32d2600258
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run
Add chat MCP tools
2026-04-15 21:13:17 +01:00

762 lines
28 KiB
Go

// pkg/mcp/tools_chat.go
package mcp
import (
"context"
"time"
core "dappco.re/go/core"
coreerr "dappco.re/go/core/log"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
type ChatMessage struct {
ID string `json:"id"`
Role string `json:"role"`
Content string `json:"content"`
CreatedAt time.Time `json:"created_at"`
Model string `json:"model,omitempty"`
Attachments []ChatImageAttachment `json:"attachments,omitempty"`
Thinking *ChatThinkingState `json:"thinking,omitempty"`
ToolCalls []ChatToolCall `json:"tool_calls,omitempty"`
ToolResults []ChatToolResult `json:"tool_results,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
FinishReason string `json:"finish_reason,omitempty"`
}
type ChatModelEntry struct {
Name string `json:"name"`
Architecture string `json:"architecture"`
QuantBits int `json:"quant_bits"`
SizeBytes int64 `json:"size_bytes"`
Loaded bool `json:"loaded"`
Backend string `json:"backend"`
SupportsVision bool `json:"supports_vision"`
}
type ChatSettings struct {
Temperature float32 `json:"temperature"`
TopP float32 `json:"top_p"`
TopK int `json:"top_k"`
MaxTokens int `json:"max_tokens"`
ContextWindow int `json:"context_window"`
SystemPrompt string `json:"system_prompt"`
DefaultModel string `json:"default_model"`
}
type ChatConversation struct {
ID string `json:"id"`
Title string `json:"title"`
Model string `json:"model"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Messages []ChatMessage `json:"messages"`
Settings *ChatSettings `json:"settings,omitempty"`
}
type ChatConversationSummary struct {
ID string `json:"id"`
Title string `json:"title"`
Model string `json:"model"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
MessageCount int `json:"message_count"`
}
type ChatThinkingState struct {
Active bool `json:"active"`
Content string `json:"content"`
StartedAt time.Time `json:"started_at,omitempty"`
EndedAt time.Time `json:"ended_at,omitempty"`
DurationMS int64 `json:"duration_ms,omitempty"`
}
type ChatToolCall struct {
ID string `json:"id"`
Name string `json:"name"`
Arguments map[string]any `json:"arguments"`
}
type ChatToolResult struct {
ToolCallID string `json:"tool_call_id"`
Content string `json:"content"`
}
type ChatImageAttachment struct {
Filename string `json:"filename"`
MimeType string `json:"mime_type"`
Data string `json:"data"`
Width int `json:"width"`
Height int `json:"height"`
}
// --- chat_send ---
type ChatSendInput struct {
ConversationID string `json:"conversation_id,omitempty"`
Content string `json:"content"`
}
type ChatSendOutput struct {
Conversation ChatConversation `json:"conversation"`
}
func (s *Subsystem) chatSend(_ context.Context, _ *mcp.CallToolRequest, input ChatSendInput) (*mcp.CallToolResult, ChatSendOutput, error) {
result := s.core.Action("gui.chat.send").Run(context.Background(), core.NewOptions(
core.Option{Key: "conversation_id", Value: input.ConversationID},
core.Option{Key: "content", Value: input.Content},
))
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatSendOutput{}, err
}
return nil, ChatSendOutput{}, nil
}
conversation, err := decodeChatValue[ChatConversation](result.Value)
if err != nil {
return nil, ChatSendOutput{}, err
}
return nil, ChatSendOutput{Conversation: conversation}, nil
}
// --- chat_clear ---
type ChatClearInput struct {
ConversationID string `json:"conversation_id,omitempty"`
ID string `json:"id,omitempty"`
}
type ChatClearOutput struct {
Conversation ChatConversation `json:"conversation"`
}
func (s *Subsystem) chatClear(_ context.Context, _ *mcp.CallToolRequest, input ChatClearInput) (*mcp.CallToolResult, ChatClearOutput, error) {
result := s.core.Action("gui.chat.clear").Run(context.Background(), core.NewOptions(
core.Option{Key: "conversation_id", Value: input.ConversationID},
core.Option{Key: "id", Value: input.ID},
))
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatClearOutput{}, err
}
return nil, ChatClearOutput{}, nil
}
conversation, err := decodeChatValue[ChatConversation](result.Value)
if err != nil {
return nil, ChatClearOutput{}, err
}
return nil, ChatClearOutput{Conversation: conversation}, nil
}
// --- chat_history ---
type ChatHistoryInput struct {
ConversationID string `json:"conversation_id,omitempty"`
ID string `json:"id,omitempty"`
}
type ChatHistoryOutput struct {
Conversation ChatConversation `json:"conversation"`
}
func (s *Subsystem) chatHistory(_ context.Context, _ *mcp.CallToolRequest, input ChatHistoryInput) (*mcp.CallToolResult, ChatHistoryOutput, error) {
result := s.core.Action("gui.chat.history").Run(context.Background(), core.NewOptions(
core.Option{Key: "conversation_id", Value: input.ConversationID},
core.Option{Key: "id", Value: input.ID},
))
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatHistoryOutput{}, err
}
return nil, ChatHistoryOutput{}, nil
}
conversation, err := decodeChatValue[ChatConversation](result.Value)
if err != nil {
return nil, ChatHistoryOutput{}, err
}
return nil, ChatHistoryOutput{Conversation: conversation}, nil
}
// --- chat_models ---
type ChatModelsInput struct{}
type ChatModelsOutput struct {
Models []ChatModelEntry `json:"models"`
}
func (s *Subsystem) chatModels(_ context.Context, _ *mcp.CallToolRequest, _ ChatModelsInput) (*mcp.CallToolResult, ChatModelsOutput, error) {
result := s.core.Action("gui.chat.models").Run(context.Background(), core.NewOptions())
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatModelsOutput{}, err
}
return nil, ChatModelsOutput{}, nil
}
models, err := decodeChatValue[[]ChatModelEntry](result.Value)
if err != nil {
return nil, ChatModelsOutput{}, err
}
return nil, ChatModelsOutput{Models: models}, nil
}
// --- chat_select_model ---
type ChatSelectModelInput struct {
Model string `json:"model"`
ConversationID string `json:"conversation_id,omitempty"`
ID string `json:"id,omitempty"`
}
type ChatSelectModelOutput struct {
Settings ChatSettings `json:"settings"`
}
func (s *Subsystem) chatSelectModel(_ context.Context, _ *mcp.CallToolRequest, input ChatSelectModelInput) (*mcp.CallToolResult, ChatSelectModelOutput, error) {
result := s.core.Action("gui.chat.selectModel").Run(context.Background(), core.NewOptions(
core.Option{Key: "model", Value: input.Model},
core.Option{Key: "conversation_id", Value: input.ConversationID},
core.Option{Key: "id", Value: input.ID},
))
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatSelectModelOutput{}, err
}
return nil, ChatSelectModelOutput{}, nil
}
settings, err := decodeChatValue[ChatSettings](result.Value)
if err != nil {
return nil, ChatSelectModelOutput{}, err
}
return nil, ChatSelectModelOutput{Settings: settings}, nil
}
// --- chat_settings_save ---
type ChatSettingsSaveInput ChatSettings
type ChatSettingsSaveOutput struct {
Settings ChatSettings `json:"settings"`
}
func (s *Subsystem) chatSettingsSave(_ context.Context, _ *mcp.CallToolRequest, input ChatSettingsSaveInput) (*mcp.CallToolResult, ChatSettingsSaveOutput, error) {
result := s.core.Action("gui.chat.settings.save").Run(context.Background(), core.NewOptions(
core.Option{Key: "temperature", Value: input.Temperature},
core.Option{Key: "top_p", Value: input.TopP},
core.Option{Key: "top_k", Value: input.TopK},
core.Option{Key: "max_tokens", Value: input.MaxTokens},
core.Option{Key: "context_window", Value: input.ContextWindow},
core.Option{Key: "system_prompt", Value: input.SystemPrompt},
core.Option{Key: "default_model", Value: input.DefaultModel},
))
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatSettingsSaveOutput{}, err
}
return nil, ChatSettingsSaveOutput{}, nil
}
settings, err := decodeChatValue[ChatSettings](result.Value)
if err != nil {
return nil, ChatSettingsSaveOutput{}, err
}
return nil, ChatSettingsSaveOutput{Settings: settings}, nil
}
// --- chat_settings_load ---
type ChatSettingsLoadInput struct{}
type ChatSettingsLoadOutput struct {
Settings ChatSettings `json:"settings"`
}
func (s *Subsystem) chatSettingsLoad(_ context.Context, _ *mcp.CallToolRequest, _ ChatSettingsLoadInput) (*mcp.CallToolResult, ChatSettingsLoadOutput, error) {
result := s.core.Action("gui.chat.settings.load").Run(context.Background(), core.NewOptions())
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatSettingsLoadOutput{}, err
}
return nil, ChatSettingsLoadOutput{}, nil
}
settings, err := decodeChatValue[ChatSettings](result.Value)
if err != nil {
return nil, ChatSettingsLoadOutput{}, err
}
return nil, ChatSettingsLoadOutput{Settings: settings}, nil
}
// --- chat_settings_defaults ---
type ChatSettingsDefaultsInput struct{}
type ChatSettingsDefaultsOutput struct {
Settings ChatSettings `json:"settings"`
}
func (s *Subsystem) chatSettingsDefaults(_ context.Context, _ *mcp.CallToolRequest, _ ChatSettingsDefaultsInput) (*mcp.CallToolResult, ChatSettingsDefaultsOutput, error) {
result := s.core.Action("gui.chat.settings.defaults").Run(context.Background(), core.NewOptions())
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatSettingsDefaultsOutput{}, err
}
return nil, ChatSettingsDefaultsOutput{}, nil
}
settings, err := decodeChatValue[ChatSettings](result.Value)
if err != nil {
return nil, ChatSettingsDefaultsOutput{}, err
}
return nil, ChatSettingsDefaultsOutput{Settings: settings}, nil
}
// --- chat_settings_reset ---
type ChatSettingsResetInput struct{}
type ChatSettingsResetOutput struct {
Settings ChatSettings `json:"settings"`
}
func (s *Subsystem) chatSettingsReset(_ context.Context, _ *mcp.CallToolRequest, _ ChatSettingsResetInput) (*mcp.CallToolResult, ChatSettingsResetOutput, error) {
result := s.core.Action("gui.chat.settings.reset").Run(context.Background(), core.NewOptions())
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatSettingsResetOutput{}, err
}
return nil, ChatSettingsResetOutput{}, nil
}
settings, err := decodeChatValue[ChatSettings](result.Value)
if err != nil {
return nil, ChatSettingsResetOutput{}, err
}
return nil, ChatSettingsResetOutput{Settings: settings}, nil
}
// --- chat_conversations_list ---
type ChatConversationsListInput struct{}
type ChatConversationsListOutput struct {
Conversations []ChatConversationSummary `json:"conversations"`
}
func (s *Subsystem) chatConversationsList(_ context.Context, _ *mcp.CallToolRequest, _ ChatConversationsListInput) (*mcp.CallToolResult, ChatConversationsListOutput, error) {
result := s.core.Action("gui.chat.conversations.list").Run(context.Background(), core.NewOptions())
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatConversationsListOutput{}, err
}
return nil, ChatConversationsListOutput{}, nil
}
conversations, err := decodeChatValue[[]ChatConversationSummary](result.Value)
if err != nil {
return nil, ChatConversationsListOutput{}, err
}
return nil, ChatConversationsListOutput{Conversations: conversations}, nil
}
// --- chat_conversations_get ---
type ChatConversationsGetInput struct {
ConversationID string `json:"conversation_id,omitempty"`
ID string `json:"id,omitempty"`
}
type ChatConversationsGetOutput struct {
Conversation ChatConversation `json:"conversation"`
}
func (s *Subsystem) chatConversationsGet(_ context.Context, _ *mcp.CallToolRequest, input ChatConversationsGetInput) (*mcp.CallToolResult, ChatConversationsGetOutput, error) {
result := s.core.Action("gui.chat.conversations.get").Run(context.Background(), core.NewOptions(
core.Option{Key: "conversation_id", Value: input.ConversationID},
core.Option{Key: "id", Value: input.ID},
))
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatConversationsGetOutput{}, err
}
return nil, ChatConversationsGetOutput{}, nil
}
conversation, err := decodeChatValue[ChatConversation](result.Value)
if err != nil {
return nil, ChatConversationsGetOutput{}, err
}
return nil, ChatConversationsGetOutput{Conversation: conversation}, nil
}
// --- chat_conversations_delete ---
type ChatConversationsDeleteInput struct {
ConversationID string `json:"conversation_id,omitempty"`
ID string `json:"id,omitempty"`
}
type ChatConversationsDeleteOutput struct {
Success bool `json:"success"`
}
func (s *Subsystem) chatConversationsDelete(_ context.Context, _ *mcp.CallToolRequest, input ChatConversationsDeleteInput) (*mcp.CallToolResult, ChatConversationsDeleteOutput, error) {
result := s.core.Action("gui.chat.conversations.delete").Run(context.Background(), core.NewOptions(
core.Option{Key: "conversation_id", Value: input.ConversationID},
core.Option{Key: "id", Value: input.ID},
))
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatConversationsDeleteOutput{}, err
}
return nil, ChatConversationsDeleteOutput{}, nil
}
return nil, ChatConversationsDeleteOutput{Success: true}, nil
}
// --- chat_conversations_search ---
type ChatConversationsSearchInput struct {
Query string `json:"q"`
}
type ChatConversationsSearchOutput struct {
Conversations []ChatConversationSummary `json:"conversations"`
}
func (s *Subsystem) chatConversationsSearch(_ context.Context, _ *mcp.CallToolRequest, input ChatConversationsSearchInput) (*mcp.CallToolResult, ChatConversationsSearchOutput, error) {
result := s.core.Action("gui.chat.conversations.search").Run(context.Background(), core.NewOptions(
core.Option{Key: "q", Value: input.Query},
))
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatConversationsSearchOutput{}, err
}
return nil, ChatConversationsSearchOutput{}, nil
}
conversations, err := decodeChatValue[[]ChatConversationSummary](result.Value)
if err != nil {
return nil, ChatConversationsSearchOutput{}, err
}
return nil, ChatConversationsSearchOutput{Conversations: conversations}, nil
}
// --- chat_conversations_new ---
type ChatConversationsNewInput struct{}
type ChatConversationsNewOutput struct {
Conversation ChatConversation `json:"conversation"`
}
func (s *Subsystem) chatConversationsNew(_ context.Context, _ *mcp.CallToolRequest, _ ChatConversationsNewInput) (*mcp.CallToolResult, ChatConversationsNewOutput, error) {
result := s.core.Action("gui.chat.conversations.new").Run(context.Background(), core.NewOptions())
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatConversationsNewOutput{}, err
}
return nil, ChatConversationsNewOutput{}, nil
}
conversation, err := decodeChatValue[ChatConversation](result.Value)
if err != nil {
return nil, ChatConversationsNewOutput{}, err
}
return nil, ChatConversationsNewOutput{Conversation: conversation}, nil
}
// --- chat_conversation_save ---
type ChatConversationSaveInput ChatConversation
type ChatConversationSaveOutput struct {
Conversation ChatConversation `json:"conversation"`
}
func (s *Subsystem) chatConversationSave(_ context.Context, _ *mcp.CallToolRequest, input ChatConversationSaveInput) (*mcp.CallToolResult, ChatConversationSaveOutput, error) {
result := s.core.Action("gui.chat.conversation.save").Run(context.Background(), core.NewOptions(
core.Option{Key: "id", Value: input.ID},
core.Option{Key: "title", Value: input.Title},
core.Option{Key: "model", Value: input.Model},
core.Option{Key: "created_at", Value: input.CreatedAt},
core.Option{Key: "updated_at", Value: input.UpdatedAt},
core.Option{Key: "messages", Value: input.Messages},
core.Option{Key: "settings", Value: input.Settings},
))
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatConversationSaveOutput{}, err
}
return nil, ChatConversationSaveOutput{}, nil
}
conversation, err := decodeChatValue[ChatConversation](result.Value)
if err != nil {
return nil, ChatConversationSaveOutput{}, err
}
return nil, ChatConversationSaveOutput{Conversation: conversation}, nil
}
// --- chat_conversations_rename ---
type ChatConversationsRenameInput struct {
ID string `json:"id"`
Title string `json:"title"`
}
type ChatConversationsRenameOutput struct {
Conversation ChatConversation `json:"conversation"`
}
func (s *Subsystem) chatConversationsRename(_ context.Context, _ *mcp.CallToolRequest, input ChatConversationsRenameInput) (*mcp.CallToolResult, ChatConversationsRenameOutput, error) {
result := s.core.Action("gui.chat.conversations.rename").Run(context.Background(), core.NewOptions(
core.Option{Key: "id", Value: input.ID},
core.Option{Key: "title", Value: input.Title},
))
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatConversationsRenameOutput{}, err
}
return nil, ChatConversationsRenameOutput{}, nil
}
conversation, err := decodeChatValue[ChatConversation](result.Value)
if err != nil {
return nil, ChatConversationsRenameOutput{}, err
}
return nil, ChatConversationsRenameOutput{Conversation: conversation}, nil
}
// --- chat_conversations_export ---
type ChatConversationsExportInput struct {
ConversationID string `json:"conversation_id,omitempty"`
ID string `json:"id,omitempty"`
}
type ChatConversationsExportOutput struct {
Markdown string `json:"markdown"`
}
func (s *Subsystem) chatConversationsExport(_ context.Context, _ *mcp.CallToolRequest, input ChatConversationsExportInput) (*mcp.CallToolResult, ChatConversationsExportOutput, error) {
result := s.core.Action("gui.chat.conversations.export").Run(context.Background(), core.NewOptions(
core.Option{Key: "conversation_id", Value: input.ConversationID},
core.Option{Key: "id", Value: input.ID},
))
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatConversationsExportOutput{}, err
}
return nil, ChatConversationsExportOutput{}, nil
}
markdown, ok := result.Value.(string)
if !ok {
return nil, ChatConversationsExportOutput{}, coreerr.E("mcp.chatConversationsExport", "unexpected result type", nil)
}
return nil, ChatConversationsExportOutput{Markdown: markdown}, nil
}
// --- chat_attach_image ---
type ChatAttachImageInput struct {
ConversationID string `json:"conversation_id,omitempty"`
Filename string `json:"filename"`
MimeType string `json:"mime_type"`
Data string `json:"data"`
Width int `json:"width"`
Height int `json:"height"`
}
type ChatAttachImageOutput struct {
Attachment ChatImageAttachment `json:"attachment"`
}
func (s *Subsystem) chatAttachImage(_ context.Context, _ *mcp.CallToolRequest, input ChatAttachImageInput) (*mcp.CallToolResult, ChatAttachImageOutput, error) {
result := s.core.Action("gui.chat.attachImage").Run(context.Background(), core.NewOptions(
core.Option{Key: "conversation_id", Value: input.ConversationID},
core.Option{Key: "filename", Value: input.Filename},
core.Option{Key: "mime_type", Value: input.MimeType},
core.Option{Key: "data", Value: input.Data},
core.Option{Key: "width", Value: input.Width},
core.Option{Key: "height", Value: input.Height},
))
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatAttachImageOutput{}, err
}
return nil, ChatAttachImageOutput{}, nil
}
attachment, err := decodeChatValue[ChatImageAttachment](result.Value)
if err != nil {
return nil, ChatAttachImageOutput{}, err
}
return nil, ChatAttachImageOutput{Attachment: attachment}, nil
}
// --- chat_remove_image ---
type ChatRemoveImageInput struct {
ConversationID string `json:"conversation_id,omitempty"`
Index int `json:"index"`
}
type ChatRemoveImageOutput struct {
Attachment ChatImageAttachment `json:"attachment"`
}
func (s *Subsystem) chatRemoveImage(_ context.Context, _ *mcp.CallToolRequest, input ChatRemoveImageInput) (*mcp.CallToolResult, ChatRemoveImageOutput, error) {
result := s.core.Action("gui.chat.removeImage").Run(context.Background(), core.NewOptions(
core.Option{Key: "conversation_id", Value: input.ConversationID},
core.Option{Key: "index", Value: input.Index},
))
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatRemoveImageOutput{}, err
}
return nil, ChatRemoveImageOutput{}, nil
}
attachment, err := decodeChatValue[ChatImageAttachment](result.Value)
if err != nil {
return nil, ChatRemoveImageOutput{}, err
}
return nil, ChatRemoveImageOutput{Attachment: attachment}, nil
}
// --- chat_thinking_start ---
type ChatThinkingStartInput struct {
ConversationID string `json:"conversation_id"`
MessageID string `json:"message_id,omitempty"`
StartedAt time.Time `json:"started_at,omitempty"`
}
type ChatThinkingStartOutput struct {
ConversationID string `json:"conversation_id"`
MessageID string `json:"message_id,omitempty"`
Content string `json:"content,omitempty"`
StartedAt time.Time `json:"started_at"`
DurationMS int64 `json:"duration_ms,omitempty"`
}
func (s *Subsystem) chatThinkingStart(_ context.Context, _ *mcp.CallToolRequest, input ChatThinkingStartInput) (*mcp.CallToolResult, ChatThinkingStartOutput, error) {
result := s.core.Action("gui.chat.thinking.start").Run(context.Background(), core.NewOptions(
core.Option{Key: "conversation_id", Value: input.ConversationID},
core.Option{Key: "message_id", Value: input.MessageID},
core.Option{Key: "started_at", Value: input.StartedAt},
))
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatThinkingStartOutput{}, err
}
return nil, ChatThinkingStartOutput{}, nil
}
output, err := decodeChatValue[ChatThinkingStartOutput](result.Value)
if err != nil {
return nil, ChatThinkingStartOutput{}, err
}
return nil, output, nil
}
// --- chat_thinking_append ---
type ChatThinkingAppendInput struct {
ConversationID string `json:"conversation_id"`
MessageID string `json:"message_id,omitempty"`
Content string `json:"content"`
}
type ChatThinkingAppendOutput struct {
Content string `json:"content"`
}
func (s *Subsystem) chatThinkingAppend(_ context.Context, _ *mcp.CallToolRequest, input ChatThinkingAppendInput) (*mcp.CallToolResult, ChatThinkingAppendOutput, error) {
result := s.core.Action("gui.chat.thinking.append").Run(context.Background(), core.NewOptions(
core.Option{Key: "conversation_id", Value: input.ConversationID},
core.Option{Key: "message_id", Value: input.MessageID},
core.Option{Key: "content", Value: input.Content},
))
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatThinkingAppendOutput{}, err
}
return nil, ChatThinkingAppendOutput{}, nil
}
content, ok := result.Value.(string)
if !ok {
return nil, ChatThinkingAppendOutput{}, coreerr.E("mcp.chatThinkingAppend", "unexpected result type", nil)
}
return nil, ChatThinkingAppendOutput{Content: content}, nil
}
// --- chat_thinking_end ---
type ChatThinkingEndInput struct {
ConversationID string `json:"conversation_id"`
MessageID string `json:"message_id,omitempty"`
DurationMS int64 `json:"duration_ms,omitempty"`
StartedAt time.Time `json:"started_at,omitempty"`
}
type ChatThinkingEndOutput struct {
DurationMS int64 `json:"duration_ms"`
}
func (s *Subsystem) chatThinkingEnd(_ context.Context, _ *mcp.CallToolRequest, input ChatThinkingEndInput) (*mcp.CallToolResult, ChatThinkingEndOutput, error) {
result := s.core.Action("gui.chat.thinking.end").Run(context.Background(), core.NewOptions(
core.Option{Key: "conversation_id", Value: input.ConversationID},
core.Option{Key: "message_id", Value: input.MessageID},
core.Option{Key: "duration_ms", Value: input.DurationMS},
core.Option{Key: "started_at", Value: input.StartedAt},
))
if !result.OK {
if err, ok := result.Value.(error); ok {
return nil, ChatThinkingEndOutput{}, err
}
return nil, ChatThinkingEndOutput{}, nil
}
durationMS, ok := result.Value.(int64)
if !ok {
return nil, ChatThinkingEndOutput{}, coreerr.E("mcp.chatThinkingEnd", "unexpected result type", nil)
}
return nil, ChatThinkingEndOutput{DurationMS: durationMS}, nil
}
func decodeChatValue[T any](value any) (T, error) {
var output T
result := core.JSONUnmarshalString(core.JSONMarshalString(value), &output)
if !result.OK {
if err, ok := result.Value.(error); ok {
return output, err
}
return output, coreerr.E("mcp.decodeChatValue", "failed to decode chat value", nil)
}
return output, nil
}
func (s *Subsystem) registerChatTools(server *mcp.Server) {
addTool(s, server, &mcp.Tool{
Name: "chat_send",
Description: `Send a chat message and stream a reply through the chat service. Example: {"conversation_id":"conv-1","content":"Hello"}`,
}, s.chatSend)
addTool(s, server, &mcp.Tool{
Name: "chat_clear",
Description: `Clear a conversation's message history. Example: {"id":"conv-1"}`,
}, s.chatClear)
addTool(s, server, &mcp.Tool{
Name: "chat_history",
Description: `Get a conversation and its message history. Example: {"id":"conv-1"}`,
}, s.chatHistory)
addTool(s, server, &mcp.Tool{Name: "chat_models", Description: "List available chat models"}, s.chatModels)
addTool(s, server, &mcp.Tool{
Name: "chat_select_model",
Description: `Select the active chat model for the current settings or conversation. Example: {"model":"lemer","conversation_id":"conv-1"}`,
}, s.chatSelectModel)
addTool(s, server, &mcp.Tool{Name: "chat_settings_save", Description: "Persist chat settings"}, s.chatSettingsSave)
addTool(s, server, &mcp.Tool{Name: "chat_settings_load", Description: "Load persisted chat settings"}, s.chatSettingsLoad)
addTool(s, server, &mcp.Tool{Name: "chat_settings_defaults", Description: "Return the default chat settings"}, s.chatSettingsDefaults)
addTool(s, server, &mcp.Tool{Name: "chat_settings_reset", Description: "Reset chat settings to defaults"}, s.chatSettingsReset)
addTool(s, server, &mcp.Tool{Name: "chat_conversations_list", Description: "List stored conversations"}, s.chatConversationsList)
addTool(s, server, &mcp.Tool{Name: "chat_conversations_get", Description: "Get a stored conversation by id"}, s.chatConversationsGet)
addTool(s, server, &mcp.Tool{Name: "chat_conversations_delete", Description: "Delete a stored conversation"}, s.chatConversationsDelete)
addTool(s, server, &mcp.Tool{Name: "chat_conversations_search", Description: "Search stored conversations by text"}, s.chatConversationsSearch)
addTool(s, server, &mcp.Tool{Name: "chat_conversations_new", Description: "Create a new conversation"}, s.chatConversationsNew)
addTool(s, server, &mcp.Tool{Name: "chat_conversation_save", Description: "Save a conversation object"}, s.chatConversationSave)
addTool(s, server, &mcp.Tool{Name: "chat_conversations_rename", Description: "Rename a conversation"}, s.chatConversationsRename)
addTool(s, server, &mcp.Tool{Name: "chat_conversations_export", Description: "Export a conversation as Markdown"}, s.chatConversationsExport)
addTool(s, server, &mcp.Tool{Name: "chat_attach_image", Description: "Queue an image attachment for the next chat message"}, s.chatAttachImage)
addTool(s, server, &mcp.Tool{Name: "chat_remove_image", Description: "Remove a queued image attachment"}, s.chatRemoveImage)
addTool(s, server, &mcp.Tool{Name: "chat_thinking_start", Description: "Begin a thinking state for a conversation"}, s.chatThinkingStart)
addTool(s, server, &mcp.Tool{Name: "chat_thinking_append", Description: "Append thinking content for a conversation"}, s.chatThinkingAppend)
addTool(s, server, &mcp.Tool{Name: "chat_thinking_end", Description: "End a thinking state for a conversation"}, s.chatThinkingEnd)
}