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
7 changes: 2 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,8 @@ OPENROUTER_API_KEY='your_openrouter_api_key_here'
# AI Ga
AI_GATEWAY_API_KEY='your_api_key_here'

# Langfuse Configuration (optional, for monitoring and logging)
# Get your keys from https://langfuse.com
LANGFUSE_SECRET_KEY="sk-lf-your_secret_key_here"
LANGFUSE_PUBLIC_KEY="pk-lf-your_public_key_here"
LANGFUSE_BASE_URL="https://cloud.langfuse.com"
# Lamina Configuration
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

Typo in comment: "Lamina" should be "Laminar". The correct product name is "Laminar", not "Lamina".

Suggested change
# Lamina Configuration
# Laminar Configuration

Copilot uses AI. Check for mistakes.
LMNR_PROJECT_API_KEY='your_project_api_key_here'
Comment on lines +26 to +27
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove quote characters from environment variable placeholder.

The migration from Langfuse to Lamina configuration looks correct. However, environment variable values in .env files should not include quote characters - they become part of the actual value when parsed.

Based on coding guidelines, environment variables should be stored without quotes. Apply this diff:

 # Lamina Configuration
-LMNR_PROJECT_API_KEY='your_project_api_key_here'
+LMNR_PROJECT_API_KEY=your_project_api_key_here

This aligns with the pattern used by other variables in this file (e.g., lines 4-5, 24).

📝 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.

Suggested change
# Lamina Configuration
LMNR_PROJECT_API_KEY='your_project_api_key_here'
# Lamina Configuration
LMNR_PROJECT_API_KEY=your_project_api_key_here
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 27-27: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)

🤖 Prompt for AI Agents
In .env.example around lines 26 to 27, the LMNR_PROJECT_API_KEY placeholder is
wrapped in single quotes which will become part of the value when parsed; remove
the quote characters so the line reads
LMNR_PROJECT_API_KEY=your_project_api_key_here to match other entries and avoid
embedding quotes in the environment value.


# Database (Postgres / PgVector) - Supabase Configuration
# Get from Supabase Dashboard > Project Settings > Database > Connection String (Session Pooler)
Expand Down
44 changes: 37 additions & 7 deletions app/api/chat/route.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { mastra } from "../../../src/mastra";
import { RequestContext } from '@mastra/core/request-context';
import { RequestContext } from "@mastra/core/request-context";
import { toAISdkStream } from "@mastra/ai-sdk";
import { createUIMessageStream, createUIMessageStreamResponse } from "ai";

export async function POST(req: Request) {
const { messages, data } = await req.json();
const myAgent = mastra.getAgent("weatherAgent");
const { messages, data, id } = await req.json();

const agentId: string =
(typeof data?.agentId === "string" && data.agentId.length > 0 ? data.agentId : undefined) ??
(typeof id === "string" && id.length > 0 ? id : undefined) ??
"weatherAgent";

const myAgent = mastra.getAgent(agentId);

const requestContext = new RequestContext();

Expand All @@ -13,9 +21,31 @@ export async function POST(req: Request) {
}
}

const stream = await myAgent.stream(messages, {
requestContext,
format: "aisdk",
const stream = await myAgent.stream(messages, { requestContext });

const uiStream = createUIMessageStream({
originalMessages: messages,
execute: async ({ writer }) => {
const aiStream = toAISdkStream(stream, { from: "agent" }) as any;

if (typeof aiStream[Symbol.asyncIterator] === "function") {
for await (const part of aiStream as AsyncIterable<any>) {
await writer.write(part);
}
} else if (typeof aiStream.getReader === "function") {
const reader = aiStream.getReader();
try {
while (true) {
const { value, done } = await reader.read();
if (done) {break;}
await writer.write(value);
}
} finally {
reader.releaseLock?.();
}
}
},
});
return stream.toUIMessageStreamResponse();

return createUIMessageStreamResponse({ stream: uiStream });
}
95 changes: 72 additions & 23 deletions app/chat/components/agent-tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,49 +10,85 @@ import {
import type { ToolInvocationState } from "../providers/chat-context"
import type { DynamicToolUIPart } from "ai"
import { cn } from "@/lib/utils"
import { useMemo } from "react"

export interface AgentToolsProps {
tools: Array<ToolInvocationState | DynamicToolUIPart>
className?: string
}

const toolDisplayNames: Record<string, string> = {
weatherTool: "Weather",
webScraperTool: "Web Scraper",
googleNewsTool: "Google News",
googleScholarTool: "Google Scholar",
arxivTool: "arXiv Search",
polygonStockQuotesTool: "Stock Quotes",
finnhubAnalysisTool: "Analyst Ratings",
pdfToMarkdownTool: "PDF Parser",
function getProgressMessage(tool: ToolInvocationState | DynamicToolUIPart): string | null {
const { input } = tool
if (input !== null && typeof input === "object" && Object.prototype.hasOwnProperty.call(input, "message")) {
const msg = (input as { message?: unknown }).message
if (typeof msg === "string" && msg.trim().length > 0) {
return msg.trim()
}
}
return null
}

function formatToolName(toolName: string): string {
if (toolDisplayNames[toolName]) {return toolDisplayNames[toolName]}
return toolName
const normalized = toolName
.replace(/Tool$/, "")
.replace(/([A-Z])/g, " $1")
.replace(/^./, (s) => s.toUpperCase())
.replace(/[:/_-]+/g, " ")
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
.trim()

return normalized.length > 0
? normalized.replace(/^./, (s) => s.toUpperCase())
: "Tool"
}

export function AgentTools({ tools, className }: AgentToolsProps) {
if (!tools || tools.length === 0) {return null}
if (tools.length === 0) {return null}

const groups = useMemo(() => {
const grouped = new Map<string, Array<ToolInvocationState | DynamicToolUIPart>>()
const order: string[] = []

for (const tool of tools) {
const id = tool.toolCallId
if (!grouped.has(id)) {
grouped.set(id, [])
order.push(id)
}
grouped.get(id)?.push(tool)
}

return order.map((id) => ({ id, items: grouped.get(id) ?? [] }))
}, [tools])

return (
<div className={cn("space-y-2 mt-2", className)}>
{tools.map((tool) => {
const toolName = tool.toolName ?? "unknown"
{groups.map(({ id, items }, groupIdx) => {
if (items.length === 0) {return null}
const latest = items[items.length - 1]

const toolNameFromDynamic = (latest as { toolName?: unknown }).toolName
const toolName =
(typeof toolNameFromDynamic === "string" && toolNameFromDynamic.trim().length > 0
? toolNameFromDynamic
: undefined) ??
(typeof (latest as { type?: unknown }).type === "string" &&
((latest as { type: string }).type).startsWith("tool-")
? (latest as { type: string }).type.slice("tool-".length)
: "unknown")
const displayName = formatToolName(toolName)
const toolState = tool.state
const toolState = latest.state
const hasOutput = toolState === "output-available" || toolState === "output-error"
const errorText = toolState === "output-error"
? (tool as unknown as { errorText?: string }).errorText
: undefined
const errorText =
toolState === "output-error"
? (latest as unknown as { errorText?: string }).errorText
: undefined

const progressMessages = items
.map(getProgressMessage)
.filter((m): m is string => typeof m === "string" && m.length > 0)

return (
<Tool
key={tool.toolCallId}
key={`${id}-${toolName}-${toolState}-${groupIdx}`}
defaultOpen={toolState === "output-error"}
>
<ToolHeader
Expand All @@ -61,10 +97,23 @@ export function AgentTools({ tools, className }: AgentToolsProps) {
state={toolState}
/>
<ToolContent>
<ToolInput input={tool.input} />
{progressMessages.length > 0 && (
<div className="rounded-md border bg-muted/30 px-3 py-2 text-xs text-muted-foreground">
<div className="font-medium text-foreground/80">Progress</div>
<ul className="mt-1 space-y-1">
{progressMessages.slice(-6).map((msg, idx) => (
<li key={`${id}-progress-${idx}`} className="truncate">
{msg}
</li>
))}
</ul>
</div>
)}

<ToolInput input={latest.input} />
{hasOutput && (
<ToolOutput
output={toolState === "output-available" ? tool.output : undefined}
output={toolState === "output-available" ? latest.output : undefined}
errorText={errorText}
/>
)}
Expand Down
2 changes: 1 addition & 1 deletion app/chat/components/agent-web-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ type PreviewStatus = "idle" | "running" | "success" | "error"
interface AgentWebPreviewProps {
preview: WebPreviewData
onClose?: () => void
onCodeChange?: (code: string) => void
onCodeChange?: (_code: string) => void
defaultTab?: "preview" | "code"
height?: string | number
showConsole?: boolean
Expand Down
2 changes: 1 addition & 1 deletion app/chat/components/chat-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function ChatInput() {

const handleSubmit = async (message: { text: string; files: unknown[] }) => {
if (message.text.trim()) {
await sendMessage(message.text, message.files as File[])
sendMessage(message.text, message.files as File[])
}
}

Expand Down
Loading
Loading