@@ -46,7 +45,6 @@ export const BridgeCompletedCard = ({ result }: { result: any }) => {
);
}
-
// The result is expected to be a JSON string within the 'text' field
// if it comes from certain tool structures (like MCP). Handle potential variations.
if (typeof result === "string") {
@@ -60,7 +58,7 @@ export const BridgeCompletedCard = ({ result }: { result: any }) => {
// Assume the result object itself contains the data if not a string or standard structure
parsedResult = result;
} else {
- console.error("Unexpected result format for getSwapBridgeData:", result);
+ console.error("Unexpected result format for swap_or_bridge:", result);
parsedResult = {}; // Fallback to avoid errors
}
@@ -73,7 +71,6 @@ export const BridgeCompletedCard = ({ result }: { result: any }) => {
toAmountUSD,
message, // Use the message from the result if available
} = parsedResult || {};
-
const displayAmount = toAmount
? parseFloat(toAmount).toLocaleString(undefined, {
maximumFractionDigits: 5,
@@ -85,7 +82,6 @@ export const BridgeCompletedCard = ({ result }: { result: any }) => {
currency: "USD",
})
: "N/A";
-
return (
@@ -134,7 +130,6 @@ export const BridgeCompletedCard = ({ result }: { result: any }) => {
);
}
};
-
export const SwapBridgeWidget = ({
toolCallId,
fromToken,
@@ -154,21 +149,17 @@ export const SwapBridgeWidget = ({
const widgetEvents = useWidgetEvents();
const hasSentResult = useRef(false);
const { addToolResult } = useChat();
-
const handleToolResult = useCallback(
(resultData: any) => {
const resultText = JSON.stringify(resultData);
-
addToolResult({
toolCallId,
result: resultText,
});
-
hasSentResult.current = true;
},
[addToolResult, toolCallId]
);
-
const onRouteExecutionCompleted = useCallback(
(route: Route) => {
console.log("🚀 ~ onRouteExecutionCompleted ~ route:", route);
@@ -181,7 +172,6 @@ export const SwapBridgeWidget = ({
BigInt(route.toAmount),
route.toToken.decimals
);
-
handleToolResult({
message: toChain === fromChain ? "Swap Completed" : "Bridge Completed",
destinationTxLink,
@@ -193,7 +183,6 @@ export const SwapBridgeWidget = ({
},
[handleToolResult, toChain, fromChain]
);
-
const onRouteExecutionFailed = useCallback(
(update: RouteExecutionUpdate) => {
console.log("🚀 ~ onRouteExecutionFailed ~ update:", update);
@@ -204,14 +193,12 @@ export const SwapBridgeWidget = ({
},
[handleToolResult]
);
-
useEffect(() => {
widgetEvents.on(
WidgetEvent.RouteExecutionCompleted,
onRouteExecutionCompleted
);
widgetEvents.on(WidgetEvent.RouteExecutionFailed, onRouteExecutionFailed);
-
// Cleanup function
return () => {
widgetEvents.off(
@@ -224,7 +211,6 @@ export const SwapBridgeWidget = ({
);
};
}, [widgetEvents, onRouteExecutionCompleted, onRouteExecutionFailed]);
-
return (
;
} else {
diff --git a/src/components/shared/tool-header-info.tsx b/src/components/shared/tool-header-info.tsx
index 389b6124..6eeb49ea 100644
--- a/src/components/shared/tool-header-info.tsx
+++ b/src/components/shared/tool-header-info.tsx
@@ -20,7 +20,7 @@ export function ToolHeaderInfo({
const [toTokenSymbol, setToTokenSymbol] = useState("");
useEffect(() => {
- if (toolName === "getSwapBridgeData") {
+ if (toolName === "swap_or_bridge") {
setFromTokenSymbol("");
setToTokenSymbol("");
diff --git a/src/constants/tools.ts b/src/constants/tools.ts
index bba28d49..57daa477 100644
--- a/src/constants/tools.ts
+++ b/src/constants/tools.ts
@@ -89,7 +89,7 @@ export const TOOL_INFO = {
description: "Create a perps order",
icon: LineChart,
},
- getSwapBridgeData: {
+ swap_or_bridge: {
label: "Cross-Chain Token Swap",
description: "Exchange between assets and/or chains",
icon: ArrowRightLeft,
diff --git a/src/lib/messageUtils.ts b/src/lib/messageUtils.ts
new file mode 100644
index 00000000..012f1f49
--- /dev/null
+++ b/src/lib/messageUtils.ts
@@ -0,0 +1,65 @@
+import { CoreMessage } from "ai";
+
+import { UIMessage } from "@/app/api/chat/tools/types";
+
+export async function filterAndSimplifyHistoryForLLM(
+ messages: UIMessage[]
+): Promise {
+ const simplifiedHistory: CoreMessage[] = [];
+ console.log(
+ `>>> Simplifying history for LLM input. Original count: ${messages.length}`
+ );
+
+ for (const message of messages) {
+ if (message.role === "system") {
+ if (
+ typeof message.content === "string" &&
+ message.content.trim().length > 0
+ ) {
+ simplifiedHistory.push({ role: "system", content: message.content });
+ }
+ } else if (message.role === "user") {
+ let userText = "";
+ if (typeof message.content === "string") {
+ userText = message.content;
+ } else if (Array.isArray(message.parts)) {
+ userText = message.parts
+ .filter(part => part.type === "text")
+ .map(part => (part as { type: "text"; text: string }).text)
+ .join("\n");
+ }
+ if (userText.trim().length > 0) {
+ simplifiedHistory.push({ role: "user", content: userText.trim() });
+ }
+ } else if (message.role === "assistant") {
+ let assistantText = "";
+ if (typeof message.content === "string") {
+ assistantText = message.content;
+ } else if (Array.isArray(message.parts)) {
+ assistantText = message.parts
+ .filter(part => part.type === "text")
+ .map(part => (part as { type: "text"; text: string }).text)
+ .join("");
+ }
+ if (assistantText.trim().length > 0) {
+ simplifiedHistory.push({
+ role: "assistant",
+ content: assistantText.trim(),
+ });
+ }
+ }
+ }
+
+ console.log(
+ `<<< Simplified history complete. New count: ${simplifiedHistory.length}`
+ );
+ return simplifiedHistory;
+}
+export async function filterToolCallIdsForModel(
+ messages: UIMessage[]
+): Promise {
+ console.warn(
+ "filterToolCallIdsForModel is deprecated for LLM history preparation."
+ );
+ return messages;
+}
diff --git a/src/lib/morpheusSearch.ts b/src/lib/morpheusSearch.ts
index 24e0a11b..0b64f92b 100644
--- a/src/lib/morpheusSearch.ts
+++ b/src/lib/morpheusSearch.ts
@@ -1,5 +1,8 @@
+// src/lib/morpheusSearch.ts
import { google } from "@ai-sdk/google";
import {
+ CoreMessage,
+ // Import CoreMessage for the simplified history type
experimental_createMCPClient as createMCPClient,
generateText,
streamText,
@@ -8,16 +11,22 @@ import {
import { z } from "zod";
import { morpheusSystemPrompt } from "@/app/api/chat/morpheusSystemPrompt";
+// Adjust path if your types file is located differently
+import { UIMessage } from "@/app/api/chat/tools/types";
-import { UIMessage } from "../app/api/chat/tools/types";
+// Import the NEW filter function from your utility file
+import { filterAndSimplifyHistoryForLLM } from "./messageUtils";
async function getMorpheusSearchRawStream(
- messages: UIMessage[]
+ // Receive the original, full messages from route.ts
+ originalMessages: UIMessage[]
): Promise {
+ const requestId = `morpheus-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
console.log(
- `Morpheus-Search: Setting up streamText with context. Message count: ${messages.length}`
+ `[${requestId}] Morpheus-Search: Setting up streamText. Original message count: ${originalMessages.length}`
);
+ // Initialize MCP Client early
const mcpClient = await createMCPClient({
transport: {
type: "sse",
@@ -26,6 +35,21 @@ async function getMorpheusSearchRawStream(
});
try {
+ // --- Filter and Simplify History for Morpheus LLM ---
+ // This removes role:tool messages and strips tool parts from assistant messages
+ console.log(
+ `[${requestId}] Morpheus: Original messages before simplifying for model:`,
+ JSON.stringify(originalMessages, null, 2)
+ );
+ const messagesForMorpheusModel: CoreMessage[] =
+ await filterAndSimplifyHistoryForLLM(originalMessages);
+ console.log(
+ `[${requestId}] Morpheus: Messages after simplifying (sent to model):`,
+ JSON.stringify(messagesForMorpheusModel, null, 2)
+ );
+ // --- End Filter ---
+
+ // Define Google models
const model = google("gemini-2.5-flash-preview-04-17", {
useSearchGrounding: false,
});
@@ -34,7 +58,7 @@ async function getMorpheusSearchRawStream(
useSearchGrounding: true,
});
- // only use subset of tools for Morpheus Search
+ // Load specific Morpheus tools via MCP
const tools = await mcpClient.tools({
schemas: {
get_token_info: {
@@ -62,23 +86,25 @@ async function getMorpheusSearchRawStream(
),
}),
},
+ // Add other MCP tool schemas specific to Morpheus if needed
},
});
- console.log("🚀 ~ Morpheus tools:", Object.keys(tools));
+ console.log(
+ `[${requestId}] Morpheus MCP tools loaded:`,
+ Object.keys(tools)
+ );
+ // Configure and execute the stream with the simplified history
const streamResult = streamText({
model: model,
- messages: messages,
+ // Use the SIMPLIFIED messages array
+ messages: messagesForMorpheusModel,
system: morpheusSystemPrompt,
- // providerOptions: {
- // google: {
- // thinking: { type: 'enabled', budgetTokens: 12000 },
- // }
- // },
tools: {
- // MCP Tools
+ // MCP Tools loaded above
...tools,
+ // NeoSearch Tool (defined locally)
NeoSearch: tool({
description:
"Search the web for current information, news, or context about a topic. Use this for general information needs.",
@@ -88,12 +114,17 @@ async function getMorpheusSearchRawStream(
.describe("The query to search for on the web"),
}),
execute: async ({ searchQuery }) => {
+ const neoSearchRequestId = `${requestId}-neosearch`;
console.log(
- "🔍 NeoSearch execute FUNCTION IS BEING CALLED! (Using generateText internally)"
+ `[${neoSearchRequestId}] 🔍 NeoSearch execute FUNCTION IS BEING CALLED! (Using generateText internally)`
+ );
+ console.log(
+ `[${neoSearchRequestId}] NeoSearch - Search query:`,
+ searchQuery
);
- console.log("NeoSearch - Search query:", searchQuery);
try {
+ // Use the separate search-enabled model for this tool
const searchResponse = await generateText({
model: searchEnabledModel,
prompt: searchQuery,
@@ -103,7 +134,10 @@ async function getMorpheusSearchRawStream(
const metadata = searchResponse.providerMetadata;
const googleMetadata = metadata?.google;
- console.log("NeoSearch successful for query:", searchQuery);
+ console.log(
+ `[${neoSearchRequestId}] NeoSearch successful for query:`,
+ searchQuery
+ );
return {
searchResults: text,
sources: googleMetadata?.sources || [],
@@ -114,7 +148,7 @@ async function getMorpheusSearchRawStream(
};
} catch (error: unknown) {
console.error(
- "Error in web search execution (using generateText):",
+ `[${neoSearchRequestId}] Error in web search execution (using generateText):`,
error
);
throw new Error(
@@ -123,31 +157,50 @@ async function getMorpheusSearchRawStream(
}
},
}),
+ // Add other locally defined tools specific to Morpheus if needed
},
temperature: 0.2,
- maxSteps: 25,
- onFinish: async () => {
+ maxSteps: 25, // Adjust as needed
+ onFinish: async (finishArgs: { finishReason: string; usage: object }) => {
+ // Added type annotation
+ // Ensure MCP client is closed when the stream finishes
await mcpClient.close();
- console.log("Morpheus-Search: stream finished, MCP client closed.");
+ console.log(
+ `[${requestId}] Morpheus-Search: stream finished, MCP client closed. Reason: ${finishArgs.finishReason}`
+ );
},
});
- const rawStream = streamResult.toDataStreamResponse().body;
+ // Get the underlying ReadableStream
+ const rawStream = streamResult.toDataStream();
if (!rawStream) {
- throw new Error("streamText did not return a ReadableStream body.");
+ // This case should ideally not happen if streamText succeeds
+ throw new Error(
+ "[${requestId}] streamText did not return a ReadableStream body."
+ );
}
console.log(
- "Morpheus-Search: Successfully obtained raw stream with context enabled."
+ `[${requestId}] Morpheus-Search: Successfully obtained raw stream.`
);
return rawStream;
} catch (error: unknown) {
- await mcpClient.close();
+ // Ensure MCP client is closed in case of an error during setup or execution
console.error(
- "Morpheus-Search: Error during raw stream generation:",
+ `[${requestId}] Morpheus-Search: Error during raw stream generation:`,
error
);
+ // Attempt to close MCP client, catching potential errors during close
+ await mcpClient
+ .close()
+ .catch(closeErr =>
+ console.error(
+ `[${requestId}] Error closing MCP client during error handling:`,
+ closeErr
+ )
+ );
+ // Re-throw the original error to be handled by the calling function (route.ts)
throw error instanceof Error ? error : new Error(String(error));
}
}