Conversation
…ssages Removed static temporal context from agent system prompts and implemented dynamic environment details in user messages via UserMessageWrapper processor. This improves prompt freshness by injecting current datetime per interaction rather than embedding fixed datetime in system context.
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds a UserMessageWrapper input processor and new date/context utilities, removes the legacy getDateContext helper, and updates three agents to use the processor and static instructions; re-exports new utilities from utils index. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Agent as Agent (Web/DeepWiki/Context7)
participant Proc as UserMessageWrapper
participant LLM as LLM
User->>Agent: send messages
Agent->>Proc: processInput(messages)
Note right of Proc: For each user text part:\n- if not wrapped → wrapMessage(text)\n- append <environment_details>
Proc-->>Agent: processed messages (mutated)
Agent->>LLM: submit SYSTEM_PROMPT + processed messages
LLM-->>Agent: response
Agent-->>User: return response
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
Comment |
There was a problem hiding this comment.
Pull Request Overview
This refactoring modernizes temporal context handling in AI agents by removing static datetime information from system prompts and implementing dynamic environment details that are injected per-interaction through user message processing.
- Introduces a new
UserMessageWrapperprocessor that dynamically injects current datetime and timezone information into user messages - Replaces static date context generation with on-demand environment details wrapping
- Updates all agent configurations to use the new input processor pattern
Reviewed Changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/mastra/utils/processor.ts | Implements UserMessageWrapper class that processes user messages to add environment context |
| src/mastra/utils/date.ts | Provides date/time formatting utilities for generating current timestamps with timezone |
| src/mastra/utils/context.ts | Contains functions for generating environment details and wrapping messages with context |
| src/mastra/utils/index.ts | Updates exports to include new utilities and remove deprecated ones |
| src/mastra/agents/*.ts | Updates agent configurations to use UserMessageWrapper instead of static date context |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
src/mastra/utils/context.ts
Outdated
| export function getEnvironmentDetails(): string { | ||
| const localTime = getCurrentDateTimeZone(); | ||
|
|
||
| return `<environment_details>\n# Current Time\n${localTime}\n</environment_details>`; |
There was a problem hiding this comment.
[nitpick] The hardcoded XML-like tag name environment_details should be extracted as a constant to improve maintainability and consistency across the codebase.
| * @returns A combined string containing the message and environment details. | ||
| */ | ||
| export function wrapMessage(message: string): string { | ||
| const query = `<message>\n${message}\n</message>`; |
There was a problem hiding this comment.
[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.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (7)
src/mastra/utils/date.ts (2)
2-6: MakegetCurrentDateTimeZonetestable and resilient to Intl failuresAllow an injectable Date for determinism in tests and guard Intl access. This also avoids repeatedly constructing new Dates in callers.
-export function getCurrentDateTimeZone(): string { - const now = new Date(); - const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC"; - return `${formatDateToISOString(now)} (${timeZone})`; -} +export function getCurrentDateTimeZone(now: Date = new Date()): string { + let timeZone = "UTC"; + try { + timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC"; + } catch { + // Fallback: keep UTC label + } + return `${formatDateToISOString(now)} (${timeZone})`; +}
8-22: Naming nit: function emits RFC3339 with offset, not canonicaltoISOStringThe output includes a numeric offset (valid), whereas JS
toISOStringuses Zulu. Consider a clearer name (e.g.,formatDateTimeWithOffset) to avoid confusion. No functional change required.src/mastra/agents/deepwiki-agent.ts (1)
14-21: Shield model from echoing injected environment blocksSince user inputs are wrapped with
<message>and<environment_details>, make the constraint explicit so the model never reproduces them.# CONSTRAINTS - Operate autonomously with minimal user interaction. - Output strictly in Markdown. - 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.src/mastra/agents/context7-agent.ts (1)
13-18: Prevent accidental leakage of environment blocksMirror the DeepWiki constraint to avoid echoing the injected context.
# CONSTRAINTS - Operate autonomously with minimal user interaction. - 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.src/mastra/agents/web-research-agent.ts (1)
22-29: Add a no‑echo rule for injected environment contextMake explicit that any
<message>/<environment_details>in user inputs must not appear in outputs.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**:src/mastra/utils/context.ts (1)
12-16: Avoid Markdown inside XML-like tags; support deterministic time injectionDrop the
#header inside the XML-ish block to reduce echo risk and allow an optional Date param for testability.-export function getEnvironmentDetails(): string { - const localTime = getCurrentDateTimeZone(); - - return `<environment_details>\n# Current Time\n${localTime}\n</environment_details>`; -} +export function getEnvironmentDetails(now: Date = new Date()): string { + const localTime = getCurrentDateTimeZone(now); + return `<environment_details>\n<current_time>${localTime}</current_time>\n</environment_details>`; +}src/mastra/utils/processor.ts (1)
17-19: Minor: consider a more specific processor nameIf names are surfaced in logs/metrics,
user-message-wrapperis more explicit.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
src/mastra/agents/context7-agent.ts(2 hunks)src/mastra/agents/deepwiki-agent.ts(2 hunks)src/mastra/agents/web-research-agent.ts(2 hunks)src/mastra/utils/context.ts(1 hunks)src/mastra/utils/date-context.ts(0 hunks)src/mastra/utils/date.ts(1 hunks)src/mastra/utils/index.ts(1 hunks)src/mastra/utils/processor.ts(1 hunks)
💤 Files with no reviewable changes (1)
- src/mastra/utils/date-context.ts
🧰 Additional context used
🧬 Code graph analysis (6)
src/mastra/utils/context.ts (1)
src/mastra/utils/date.ts (1)
getCurrentDateTimeZone(2-6)
src/mastra/agents/deepwiki-agent.ts (1)
src/mastra/utils/processor.ts (1)
UserMessageWrapper(17-43)
src/mastra/utils/date.ts (1)
src/mastra/utils/index.ts (2)
getCurrentDateTimeZone(2-2)formatDateToISOString(2-2)
src/mastra/agents/context7-agent.ts (1)
src/mastra/utils/processor.ts (1)
UserMessageWrapper(17-43)
src/mastra/utils/processor.ts (2)
src/mastra/utils/index.ts (2)
UserMessageWrapper(3-3)wrapMessage(1-1)src/mastra/utils/context.ts (1)
wrapMessage(28-32)
src/mastra/agents/web-research-agent.ts (2)
src/mastra/utils/index.ts (1)
UserMessageWrapper(3-3)src/mastra/utils/processor.ts (1)
UserMessageWrapper(17-43)
🔇 Additional comments (6)
src/mastra/agents/context7-agent.ts (1)
190-190: Wrapper wiring LGTMConsistent with other agents; no issues spotted.
src/mastra/agents/web-research-agent.ts (1)
79-79: Wrapper wiring LGTMConsistent with the refactor intent.
src/mastra/utils/index.ts (1)
1-3: Re-exports look goodPublic surface is cohesive and minimal.
src/mastra/utils/processor.ts (2)
5-16: Processor purpose and docs are clearNice, concise JSDoc and correct intent.
31-42: Add a quick regression check — ensure we don't double-wrap user messages; only wrap the latest user turnprocessInput currently wraps every user message (src/mastra/utils/processor.ts — processInput). Your grep found <environment_details> in src/mastra/utils/context.ts, but that doesn't confirm/deny double-wrapping. Run these checks and add a regression test:
- Inspect code: rg -nP --type=ts -C3 'processInput(|wrapMessage('
- Search prompts/snapshots/logs for repeated wrappers: rg -nP '<environment_details>|wrapMessage' -C2 path/to/prompt_snapshots || true
- Add a unit/regression: feed a history with an already-wrapped earlier user message and assert only the newest user message is wrapped (or make wrapMessage idempotent).
src/mastra/agents/deepwiki-agent.ts (1)
210-210: Confirm inputProcessors run before prompt assembly and only once per turnUserMessageWrapper is implemented at src/mastra/utils/processor.ts and is configured in src/mastra/agents/deepwiki-agent.ts, src/mastra/agents/web-research-agent.ts, and src/mastra/agents/context7-agent.ts. No local Agent runner was found in the repo (likely handled by @mastra/core) — verify the Agent invokes inputProcessors prior to prompt assembly/tool execution and that each processor is executed exactly once per user turn.
src/mastra/utils/context.ts
Outdated
| export function wrapMessage(message: string): string { | ||
| const query = `<message>\n${message}\n</message>`; | ||
| const environmentDetails = getEnvironmentDetails(); | ||
| return `${query}\n\n${environmentDetails}`; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Make wrapMessage idempotent to prevent prompt bloat
If the processor runs more than once or processes history, don’t re-wrap already wrapped text.
-export function wrapMessage(message: string): string {
- const query = `<message>\n${message}\n</message>`;
- const environmentDetails = getEnvironmentDetails();
- return `${query}\n\n${environmentDetails}`;
-}
+const WRAPPED_MARKER = "<environment_details>";
+
+export function wrapMessage(message: string): string {
+ // Idempotency: if already wrapped, return as-is
+ if (typeof message === "string" && message.includes(WRAPPED_MARKER)) {
+ return message;
+ }
+ const query = `<message>\n${message}\n</message>`;
+ const environmentDetails = getEnvironmentDetails();
+ return `${query}\n\n${environmentDetails}`;
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function wrapMessage(message: string): string { | |
| const query = `<message>\n${message}\n</message>`; | |
| const environmentDetails = getEnvironmentDetails(); | |
| return `${query}\n\n${environmentDetails}`; | |
| } | |
| const WRAPPED_MARKER = "<environment_details>"; | |
| export function wrapMessage(message: string): string { | |
| // Idempotency: if already wrapped, return as-is | |
| if (typeof message === "string" && message.includes(WRAPPED_MARKER)) { | |
| return message; | |
| } | |
| const query = `<message>\n${message}\n</message>`; | |
| const environmentDetails = getEnvironmentDetails(); | |
| return `${query}\n\n${environmentDetails}`; | |
| } |
🤖 Prompt for AI Agents
In src/mastra/utils/context.ts around lines 28-32, wrapMessage currently blindly
wraps input with <message> tags causing nested wrappers and prompt bloat when
re-run; change it to be idempotent by detecting if the input is already wrapped
(e.g. /^\s*<message>[\s\S]*<\/message>\s*$/) and, if so, extract and reuse the
inner content instead of adding another wrapper, and only append
environmentDetails if it is not already present at the end; return a single
properly wrapped message with environment details appended exactly once.
| processInput({ messages }: { messages: MastraMessageV2[] }): MastraMessageV2[] { | ||
| messages.forEach((msg) => { | ||
| if (msg.role !== "user") return; | ||
| msg.content.parts.forEach((part) => { | ||
| if (part.type === "text") { | ||
| part.text = wrapMessage(part.text); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| return messages; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Prevent re-wrapping history; add basic guards
Current logic wraps every user message each run, which can re-wrap history and inflate tokens. Wrap only the latest user message, skip if already wrapped, and guard against missing content.parts.
-export class UserMessageWrapper implements Processor {
+export class UserMessageWrapper implements Processor {
readonly name = "message-wrapper";
@@
- processInput({ messages }: { messages: MastraMessageV2[] }): MastraMessageV2[] {
- messages.forEach((msg) => {
- if (msg.role !== "user") return;
- msg.content.parts.forEach((part) => {
- if (part.type === "text") {
- part.text = wrapMessage(part.text);
- }
- });
- });
-
- return messages;
- }
+ processInput({ messages }: { messages: MastraMessageV2[] }): MastraMessageV2[] {
+ const WRAPPED_MARKER = "<environment_details>";
+ // Find most recent user message
+ for (let i = messages.length - 1; i >= 0; i--) {
+ const msg = messages[i];
+ if (msg.role !== "user") continue;
+ const parts = (msg as any)?.content?.parts;
+ if (!Array.isArray(parts)) break;
+ parts.forEach((part: any) => {
+ if (part?.type === "text" && typeof part.text === "string" && !part.text.includes(WRAPPED_MARKER)) {
+ part.text = wrapMessage(part.text);
+ }
+ });
+ break; // only the latest user message
+ }
+ return messages;
+ }
}🤖 Prompt for AI Agents
In src/mastra/utils/processor.ts around lines 31 to 42, the current loop wraps
every user message each run which can re-wrap history and inflate tokens; change
the logic to find only the latest user message (e.g. iterate messages from the
end and pick the first with role === "user"), ensure message.content &&
Array.isArray(message.content.parts) before touching parts, and for each part
only wrap when part.type === "text" AND the text is not already wrapped (detect
existing wrapper via a simple check, e.g. a prefix/suffix or a marker that
wrapMessage would add). Update the function to modify just that single latest
user message and return messages.
Add safety instructions to prevent potential information leakage in agent outputs. Improve date handling functions with timezone fallback and injectable now parameter for better consistency. Enhance user message processor with input validation and double-wrap prevention.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/mastra/utils/date.ts (1)
2-10: Avoid UTC label/local-offset mismatch when Intl is unavailable; also guard empty timeZone.If
Intlis missing (or returns a falsy/empty timeZone), we currently emit something like "…-07:00 (UTC)". Format in UTC in that case and treat empty strings as missing.Apply this diff to the function:
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})`; + let detectedTZ: string | undefined; + try { + detectedTZ = Intl.DateTimeFormat().resolvedOptions().timeZone; + } catch { + // ignore + } + const timeZone = detectedTZ && detectedTZ.length ? detectedTZ : "UTC"; + const formatted = detectedTZ ? formatDateTimeWithOffset(now) : formatDateTimeUTC(now); + return `${formatted} (${timeZone})`; }Add this helper (outside the changed block):
function formatDateTimeUTC(date: Date): string { const year = date.getUTCFullYear(); const month = String(date.getUTCMonth() + 1).padStart(2, "0"); const day = String(date.getUTCDate()).padStart(2, "0"); const hour = String(date.getUTCHours()).padStart(2, "0"); const minute = String(date.getUTCMinutes()).padStart(2, "0"); const second = String(date.getUTCSeconds()).padStart(2, "0"); return `${year}-${month}-${day}T${hour}:${minute}:${second}+00:00`; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/mastra/agents/context7-agent.ts(3 hunks)src/mastra/agents/deepwiki-agent.ts(3 hunks)src/mastra/agents/web-research-agent.ts(3 hunks)src/mastra/utils/context.ts(1 hunks)src/mastra/utils/date.ts(1 hunks)src/mastra/utils/index.ts(1 hunks)src/mastra/utils/processor.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- src/mastra/agents/deepwiki-agent.ts
- src/mastra/agents/web-research-agent.ts
- src/mastra/utils/context.ts
- src/mastra/utils/index.ts
- src/mastra/agents/context7-agent.ts
- src/mastra/utils/processor.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/mastra/utils/date.ts (1)
src/mastra/utils/index.ts (2)
getCurrentDateTimeZone(2-2)formatDateTimeWithOffset(2-2)
🔇 Additional comments (1)
src/mastra/utils/date.ts (1)
13-26: Formatting logic looks solid.Local offset sign, hours, and minutes are correct. Consider adding a DST-boundary test to lock behavior.
Overview
This refactoring removes static temporal context from agent system prompts and implements dynamic environment details that are injected into user messages via a new
UserMessageWrapperprocessor.Changes
inputProcessors: [new UserMessageWrapper()]to all agent definitionssrc/mastra/utils/context.ts: Functions to generate and wrap environment details with current datetimesrc/mastra/utils/date.ts: Date/time formatting utilitiessrc/mastra/utils/processor.ts:UserMessageWrapperclass that processes input messagessrc/mastra/utils/date-context.tsBenefits
Technical Details
The
UserMessageWrapperimplements theProcessorinterface and wraps user message text parts with environment details including current date/time and timezone information.Summary by CodeRabbit
New Features
Refactor
Chores