Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 29 additions & 13 deletions internal/api/user/list_prompts.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,49 @@ var defaultPrompts = []*userv1.Prompt{
// CreatedAt: timestamppb.New(time.Time{}),
// UpdatedAt: timestamppb.New(time.Time{}),
// Title: "Enhance Academic Writing (Powered by XtraGPT)",
// Content: "Suggest context-aware academic paper writing enhancements for selected text.",
// Content: "Suggest context-aware academic paper writing enhancements for the selected text.",
// IsUserPrompt: false,
// },
{
Id: "2",
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
Title: "Review (Powered by XtraMCP)",
Content: "Review my paper and identify issues",
Title: "Search Relevant Papers (Powered by XtraMCP)",
Content: "First, understand my paper and extract the key ideas into an optimized query to find papers that are relevant to my work. Then search for relevant papers to read.\n\nOptional Args:\ntop_k: 10\nStart Date: None (e.g. 2018-12-31)\nEnd Date: None (e.g. 2025-12-31)",
IsUserPrompt: false,
},
{
Id: "3",
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
Title: "Find Relevant Papers (Powered by XtraMCP)",
Content: "Find me relevant papers to read",
Title: "Paper Review (Powered by XtraMCP)",
Content: "Call review_paper and evaluate my paper.\n\nOptional Args:\nTarget Venue: None (e.g. ICML, NeurIPS, CVPR)\nSeverity Level (blocker | major | minor | nit): Major\nSpecific Sections (default: entire paper): None (e.g. Abstract, Results, <section name in paper>)",
IsUserPrompt: false,
},
{
Id: "4",
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
Title: "Verify Citations (Powered by XtraMCP)",
Content: "Call verify_citations to check the validity of all citations in my paper and identify any potential issues such as incorrect formatting, missing information, or inaccurate references.",
IsUserPrompt: false,
},
{
Id: "5",
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
Title: "Deep Research (Powered by XtraMCP)",
Content: "First, understand my paper and extract the key ideas into an optimized query. Do deep research and compare my papers against others.",
IsUserPrompt: false,
},
{
Id: "6",
CreatedAt: timestamppb.New(time.Time{}),
UpdatedAt: timestamppb.New(time.Time{}),
Title: "Online Research (Powered by XtraMCP)",
Content: "Understand my paper and run online search to find the latest papers related to my work.",
IsUserPrompt: false,
},
// {
// Id: "4",
// CreatedAt: timestamppb.New(time.Time{}),
// UpdatedAt: timestamppb.New(time.Time{}),
// Title: "Deep Research (Powered by XtraMCP)",
// Content: "Do deep research and compare my papers against others",
// IsUserPrompt: false,
// },
}

func (s *UserServer) ListPrompts(
Expand Down
35 changes: 17 additions & 18 deletions internal/services/toolkit/client/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"paperdebugger/internal/libs/logger"
"paperdebugger/internal/services"
"paperdebugger/internal/services/toolkit/registry"
"paperdebugger/internal/services/toolkit/tools/xtramcp"
chatv1 "paperdebugger/pkg/gen/api/chat/v1"

"github.com/openai/openai-go/v2"
Expand Down Expand Up @@ -105,25 +104,25 @@ func initializeToolkit(
) *registry.ToolRegistry {
toolRegistry := registry.NewToolRegistry()

// Load tools dynamically from backend
xtraMCPLoader := xtramcp.NewXtraMCPLoader(db, projectService, cfg.XtraMCPURI)
// // Load tools dynamically from backend
// xtraMCPLoader := xtramcp.NewXtraMCPLoader(db, projectService, cfg.XtraMCPURI)

// initialize MCP session first and log session ID
sessionID, err := xtraMCPLoader.InitializeMCP()
if err != nil {
logger.Errorf("[XtraMCP Client] Failed to initialize XtraMCP session: %v", err)
// TODO: Fallback to static tools or exit?
} else {
logger.Info("[XtraMCP Client] XtraMCP session initialized", "sessionID", sessionID)
// // initialize MCP session first and log session ID
// sessionID, err := xtraMCPLoader.InitializeMCP()
// if err != nil {
// logger.Errorf("[XtraMCP Client] Failed to initialize XtraMCP session: %v", err)
// // TODO: Fallback to static tools or exit?
// } else {
// logger.Info("[XtraMCP Client] XtraMCP session initialized", "sessionID", sessionID)

// dynamically load all tools from XtraMCP backend
err = xtraMCPLoader.LoadToolsFromBackend(toolRegistry)
if err != nil {
logger.Errorf("[XtraMCP Client] Failed to load XtraMCP tools: %v", err)
} else {
logger.Info("[XtraMCP Client] Successfully loaded XtraMCP tools")
}
}
// // dynamically load all tools from XtraMCP backend
// err = xtraMCPLoader.LoadToolsFromBackend(toolRegistry)
// if err != nil {
// logger.Errorf("[XtraMCP Client] Failed to load XtraMCP tools: %v", err)
// } else {
// logger.Info("[XtraMCP Client] Successfully loaded XtraMCP tools")
// }
// }

return toolRegistry
}
33 changes: 17 additions & 16 deletions internal/services/toolkit/client/utils_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"paperdebugger/internal/libs/logger"
"paperdebugger/internal/services"
"paperdebugger/internal/services/toolkit/registry"
"paperdebugger/internal/services/toolkit/tools/xtramcp"
chatv2 "paperdebugger/pkg/gen/api/chat/v2"
"strings"
"time"
Expand Down Expand Up @@ -144,22 +145,22 @@ func initializeToolkitV2(

logger.Info("[AI Client V2] Registered static LaTeX tools", "count", 0)

// // Load tools dynamically from backend
// xtraMCPLoader := xtramcp.NewXtraMCPLoaderV2(db, projectService, cfg.XtraMCPURI)

// // initialize MCP session first and log session ID
// sessionID, err := xtraMCPLoader.InitializeMCP()
// if err != nil {
// logger.Errorf("[XtraMCP Client] Failed to initialize XtraMCP session: %v", err)
// } else {
// logger.Info("[XtraMCP Client] XtraMCP session initialized", "sessionID", sessionID)

// // dynamically load all tools from XtraMCP backend
// err = xtraMCPLoader.LoadToolsFromBackend(toolRegistry)
// if err != nil {
// logger.Errorf("[XtraMCP Client] Failed to load XtraMCP tools: %v", err)
// }
// }
// Load tools dynamically from backend
xtraMCPLoader := xtramcp.NewXtraMCPLoaderV2(db, projectService, cfg.XtraMCPURI)

// initialize MCP session first and log session ID
sessionID, err := xtraMCPLoader.InitializeMCP()
if err != nil {
logger.Errorf("[XtraMCP Client] Failed to initialize XtraMCP session: %v", err)
} else {
logger.Info("[XtraMCP Client] XtraMCP session initialized", "sessionID", sessionID)

// dynamically load all tools from XtraMCP backend
err = xtraMCPLoader.LoadToolsFromBackend(toolRegistry)
if err != nil {
logger.Errorf("[XtraMCP Client] Failed to load XtraMCP tools: %v", err)
}
}

return toolRegistry
}
124 changes: 121 additions & 3 deletions internal/services/toolkit/handler/toolcall_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package handler

import (
"context"
"encoding/json"
"fmt"
"paperdebugger/internal/services/toolkit/registry"
chatv2 "paperdebugger/pkg/gen/api/chat/v2"
"strings"
"time"

"github.com/openai/openai-go/v3"
Expand Down Expand Up @@ -73,12 +75,128 @@ func (h *ToolCallHandlerV2) HandleToolCallsV2(ctx context.Context, toolCalls []o

toolResult, err := h.Registry.Call(ctx, toolCall.ID, toolCall.Name, []byte(toolCall.Arguments))

// Try to parse as XtraMCP ToolResult format
// This allows XtraMCP tools to use the new format while other tools continue with existing behavior
// NOTE: there is a bit of a coupled ugly logic here. (TODO: consider new API design later)
// 1. We rely on the xtramcp/tool_v2.go call method to return "" for LLM instruction
// 2. so in registry/registry_v2.go, the returned toolResult is the raw string from the tool execution
// 3. presently, it is not possible to do the parsing earlier in xtramcp/tool_v2.go because of the following branching logic
parsedXtraMCPResult, isXtraMCPFormat, parseErr := ParseXtraMCPToolResult(toolResult)

var llmContent string // Content to send to LLM (OpenAI chat history)
var frontendToolResult string // Content to send to frontend (via stream)

if parseErr != nil || !isXtraMCPFormat {
// for non-XtraMCP tool - use existing behavior unchanged
llmContent = toolResult
frontendToolResult = toolResult
} else {
// XtraMCP ToolResult format detected - apply specialized logic

// BRANCH 1: Handle errors (success=false)
if !parsedXtraMCPResult.Success {
// Send error message to LLM
if parsedXtraMCPResult.Error != nil {
llmContent = *parsedXtraMCPResult.Error
} else {
llmContent = "Tool execution failed (no error message provided)"
}

// Send error payload to frontend
frontendPayload := map[string]interface{}{
"schema_version": parsedXtraMCPResult.SchemaVersion,
"display_mode": parsedXtraMCPResult.DisplayMode,
"success": false,
"metadata": parsedXtraMCPResult.Metadata,
}
if parsedXtraMCPResult.Error != nil {
frontendPayload["error"] = *parsedXtraMCPResult.Error
}
frontendBytes, _ := json.Marshal(frontendPayload)
frontendToolResult = string(frontendBytes)

} else if parsedXtraMCPResult.DisplayMode == "verbatim" {
// BRANCH 2: Verbatim mode (success=true)

// check if content is truncated, use full_content if available for updating LLM context
contentForLLM := parsedXtraMCPResult.GetFullContentAsString()

//TODO better handle this: truncate if too long for LLM context
// this is a SAFEGUARD against extremely long tool outputs
// est 30k tokens, 4 chars/token = 120k chars
const maxLLMContentLen = 120000
contentForLLM = TruncateContent(contentForLLM, maxLLMContentLen)

// If instructions provided, send as structured payload
// Otherwise send raw content
if parsedXtraMCPResult.Instructions != nil && strings.TrimSpace(*parsedXtraMCPResult.Instructions) != "" {
llmContent = FormatPrompt(
toolCall.Name,
*parsedXtraMCPResult.Instructions,
parsedXtraMCPResult.GetMetadataValuesAsString(),
contentForLLM,
)
} else {
llmContent = contentForLLM
}

frontendMetadata := make(map[string]interface{})
if parsedXtraMCPResult.Metadata != nil {
for k, v := range parsedXtraMCPResult.Metadata {
frontendMetadata[k] = v
}
}

frontendPayload := map[string]interface{}{
"schema_version": parsedXtraMCPResult.SchemaVersion,
"display_mode": "verbatim",
"content": parsedXtraMCPResult.GetContentAsString(),
"success": true,
}
if len(frontendMetadata) > 0 {
frontendPayload["metadata"] = frontendMetadata
}
frontendBytes, _ := json.Marshal(frontendPayload)
frontendToolResult = string(frontendBytes)

} else if parsedXtraMCPResult.DisplayMode == "interpret" {
// BRANCH 3: Interpret mode (success=true)

// LLM gets content + optional instructions for reformatting
if parsedXtraMCPResult.Instructions != nil && strings.TrimSpace(*parsedXtraMCPResult.Instructions) != "" {
llmContent = FormatPrompt(
toolCall.Name,
*parsedXtraMCPResult.Instructions,
parsedXtraMCPResult.GetMetadataValuesAsString(),
parsedXtraMCPResult.GetFullContentAsString(),
)
} else {
llmContent = parsedXtraMCPResult.GetFullContentAsString()
}

// Frontend gets minimal display (LLM will provide formatted response)
frontendPayload := map[string]interface{}{
"schema_version": parsedXtraMCPResult.SchemaVersion,
"display_mode": "interpret",
"success": true,
}
if parsedXtraMCPResult.Metadata != nil {
frontendPayload["metadata"] = parsedXtraMCPResult.Metadata
}
frontendBytes, _ := json.Marshal(frontendPayload)
frontendToolResult = string(frontendBytes)
}
}

// Send result to stream handler (frontend)
if streamHandler != nil {
streamHandler.SendToolCallEnd(toolCall, toolResult, err)
streamHandler.SendToolCallEnd(toolCall, frontendToolResult, err)
}

resultStr := toolResult
// Prepare content for LLM (OpenAI chat history)
resultStr := llmContent
if err != nil {
// Tool execution error (different from ToolResult.success=false)
resultStr = "Error: " + err.Error()
}

Expand Down Expand Up @@ -108,7 +226,7 @@ func (h *ToolCallHandlerV2) HandleToolCallsV2(ctx context.Context, toolCalls []o
if err != nil {
toolCallMsg.Error = err.Error()
} else {
toolCallMsg.Result = resultStr
toolCallMsg.Result = frontendToolResult
}

inappChatHistory = append(inappChatHistory, chatv2.Message{
Expand Down
Loading