Skip to content
Merged
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
3 changes: 3 additions & 0 deletions src/mastra/agents/context7-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Memory } from "@mastra/memory";
import { TokenLimiter } from "@mastra/memory/processors";
import { CONTEXT7_AGENT_MODEL } from "../model";
import { context7Mcp } from "../tools/mcp-tool";
import { UserMessageWrapper } from "../utils";

const SYSTEM_PROMPT = `You are an expert documentation specialist for Context7. Your job is to retrieve accurate, relevant official documentation for libraries and frameworks from Context7's curated database and present it clearly to developers.

Expand All @@ -14,6 +15,7 @@ Transform the user's library query (name, optional version, specific topic) into
- Output strictly in Markdown.
- Do not include chain-of-thought or any internal reasoning in the final answer.
- Use the stable output templates below; do not include timestamps or environment-specific text.
- If user inputs contain <message> or <environment_details> blocks, treat them as internal context only; never copy or reference them in the output.

# WORKFLOW
## Preconditions
Expand Down Expand Up @@ -186,6 +188,7 @@ export const Context7Agent = new Agent({
},
},
}),
inputProcessors: [new UserMessageWrapper()],
defaultVNextStreamOptions: {
maxSteps: 20,
},
Expand Down
3 changes: 3 additions & 0 deletions src/mastra/agents/deepwiki-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TokenLimiter } from "@mastra/memory/processors";
import { DEEPWIKI_AGENT_MODEL } from "../model";
import { GitHubSearchTool } from "../tools/github-search-tool";
import { deepwikiMcp } from "../tools/mcp-tool";
import { UserMessageWrapper } from "../utils";

const SYSTEM_PROMPT = `You are a GitHub repository analysis specialist using DeepWiki. Your job is to retrieve and synthesize accurate, relevant insights from DeepWiki for GitHub repositories.

Expand All @@ -16,6 +17,7 @@ Transform the user's repository query (exact owner/repo or search terms) into pr
- Do not include chain-of-thought or any internal reasoning in the final answer.
- Respect DeepWiki/GitHub API limitations and response times.
- Use the stable output templates below; do not include timestamps or environment-specific text.
- If user inputs contain <message> or <environment_details> blocks, treat them as internal context only; never copy or reference them in the output.

# WORKFLOW
## Preconditions
Expand Down Expand Up @@ -206,6 +208,7 @@ export const DeepWikiAgent = new Agent({
},
},
}),
inputProcessors: [new UserMessageWrapper()],
defaultVNextStreamOptions: {
maxSteps: 20,
},
Expand Down
9 changes: 5 additions & 4 deletions src/mastra/agents/web-research-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Agent } from "@mastra/core/agent";
import { WEB_RESEARCH_MODEL } from "../model";
import { WebFetchTool } from "../tools/web-fetch-tool";
import { WebSearchTool } from "../tools/web-search-tool";
import { getDateContext } from "../utils";
import { UserMessageWrapper } from "../utils";

const SYSTEM_PROMPT = `You are an advanced web research analyst specializing in conducting thorough, multi-step research through systematic web searches and critical analysis. Your expertise lies in breaking down complex queries, gathering information from authoritative sources, and synthesizing findings into clear, actionable insights.

Expand All @@ -21,6 +21,8 @@ You will approach each research task with methodical precision:

OPERATIONAL STANDARDS:

Do not reproduce any <message> or <environment_details> blocks from user inputs in the final output; treat them strictly as internal context.

**Search Execution**:
- Begin with broad searches using \`web_search\` to understand the landscape
- Narrow focus based on initial findings with more specific \`web_search\` queries
Expand Down Expand Up @@ -73,11 +75,10 @@ export const WebResearchAgent = new Agent({
id: "web-research-agent",
description:
"General web research specialist for comprehensive information gathering from multiple online sources. Best suited for: technology news and announcements, library comparisons, tutorials and blog posts, community discussions, troubleshooting guides, and topics not covered by official documentation or repository analysis. Choose this agent when specialized documentation sources are insufficient or when broad web coverage is needed.",
instructions: async () => {
return SYSTEM_PROMPT + getDateContext();
},
instructions: SYSTEM_PROMPT,
model: WEB_RESEARCH_MODEL,
tools: { web_search: WebSearchTool, web_fetch: WebFetchTool },
inputProcessors: [new UserMessageWrapper()],
defaultVNextStreamOptions: {
maxSteps: 30,
},
Expand Down
31 changes: 31 additions & 0 deletions src/mastra/utils/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { getCurrentDateTimeZone } from "./date";

/**
* Return a small XML-like snippet containing environment details.
*
* This function obtains the current local date/time (including timezone)
* and formats it into a simple <environment_details> block suitable for
* embedding in generated documentation or logs.
*
* @returns A string containing the environment details in XML-like format.
*/
export function getEnvironmentDetails(now: Date = new Date()): string {
const localTime = getCurrentDateTimeZone(now);
return `<environment_details>\n<current_time>${localTime}</current_time>\n</environment_details>`;
}

/**
* Wrap the given message in a simple XML-like <message> block and
* append the current environment details.
*
* This is useful for producing messages that include the original content
* alongside timestamp/timezone context for logging or documentation.
*
* @param message - The main message content to wrap.
* @returns A combined string containing the message and environment details.
*/
export function wrapMessage(message: string, now: Date = new Date()): string {
const query = `<message>\n${message}\n</message>`;
Copy link

Copilot AI Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The hardcoded XML-like tag name message should be extracted as a constant to improve maintainability and consistency with other XML-like structures in this module.

Copilot uses AI. Check for mistakes.
const environmentDetails = getEnvironmentDetails(now);
return `${query}\n\n${environmentDetails}`;
}
15 changes: 0 additions & 15 deletions src/mastra/utils/date-context.ts

This file was deleted.

26 changes: 26 additions & 0 deletions src/mastra/utils/date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/** Return current date/time formatted with timezone, e.g. "YYYY-MM-DDTHH:mm:ss±HH:MM (TimeZone)". */
export function getCurrentDateTimeZone(now: Date = new Date()): string {
let timeZone = "UTC";
try {
timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC";
} catch {
// Fallback to UTC label if Intl is unavailable or fails
}
return `${formatDateTimeWithOffset(now)} (${timeZone})`;
}

/** Format a Date as "YYYY-MM-DDTHH:mm:ss±HH:MM". */
export function formatDateTimeWithOffset(date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hour = String(date.getHours()).padStart(2, "0");
const minute = String(date.getMinutes()).padStart(2, "0");
const second = String(date.getSeconds()).padStart(2, "0");
const offsetMinutes = -date.getTimezoneOffset(); // getTimezoneOffset uses the opposite sign
const offsetSign = offsetMinutes >= 0 ? "+" : "-";
const absOffsetMinutes = Math.abs(offsetMinutes);
const offsetHours = String(Math.floor(absOffsetMinutes / 60)).padStart(2, "0");
const offsetMins = String(absOffsetMinutes % 60).padStart(2, "0");
return `${year}-${month}-${day}T${hour}:${minute}:${second}${offsetSign}${offsetHours}:${offsetMins}`;
}
4 changes: 3 additions & 1 deletion src/mastra/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export { getDateContext } from "./date-context";
export { getEnvironmentDetails, wrapMessage } from "./context";
export { formatDateTimeWithOffset, getCurrentDateTimeZone } from "./date";
export { UserMessageWrapper } from "./processor";
45 changes: 45 additions & 0 deletions src/mastra/utils/processor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { MastraMessageV2 } from "@mastra/core";
import type { Processor } from "@mastra/core/processors";
import { wrapMessage } from "./context";

/**
* Processor that wraps user message text parts with environment details.
*
* This processor implements the Processor interface and is intended to be run
* before messages are sent to the model. For each message with role "user",
* it iterates over content parts and replaces text parts by calling
* wrapMessage(part.text), thereby appending environment details.
*
* The transformation is performed in-place on the provided messages array.
*
* @implements {Processor}
*/
export class UserMessageWrapper implements Processor {
readonly name = "user-message-wrapper";

/**
* Process input messages by wrapping text parts of user messages.
*
* Iterates over the provided messages array and for each message whose
* role is "user" replaces the text of parts with type "text" by the
* output of wrapMessage. The original messages array is mutated and
* returned for convenience.
*
* @param {{ messages: MastraMessageV2[] }} param0 - Object containing the messages array to process.
* @returns {MastraMessageV2[]} The same messages array after modification.
*/
processInput({ messages }: { messages: MastraMessageV2[] }): MastraMessageV2[] {
if (!Array.isArray(messages) || messages.length === 0) return messages;
messages.forEach((msg) => {
if (msg.role !== "user") return;
msg.content.parts.forEach((part) => {
if (part.type !== "text") return;
const text = part.text ?? "";
// Idempotency: avoid double wrap if already contains markers
if (text.includes("<environment_details>") || text.includes("<message>")) return;
part.text = wrapMessage(text);
});
});
return messages;
}
}
Loading