Skip to content

Commit 43245b6

Browse files
authored
Merge pull request #26 from navapbc/fg/migrate-postgres-storage
feat: migrate memory and traces to postgresstore
2 parents 1d8efaa + 80fce6e commit 43245b6

File tree

9 files changed

+1865
-2222
lines changed

9 files changed

+1865
-2222
lines changed

mastra-test-app/package.json

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
"test": "echo \"Error: no test specified\" && exit 1",
77
"dev": "mastra dev",
88
"dev:pretty": "mastra dev --inspect | npx pino-pretty",
9-
"build": "mastra build && npm run build:prisma",
10-
"build:prisma": "cp -r prisma .mastra/output/ && cd .mastra/output && npm pkg set scripts.postinstall='prisma generate' && npm pkg set dependencies.prisma='6.12.0' && pnpm install",
9+
"build": "mastra build",
1110
"postinstall": "prisma generate",
1211
"start": "mastra start",
1312
"seed": "tsx src/seed.ts",
@@ -30,12 +29,12 @@
3029
"@ai-sdk/google": "^1.2.22",
3130
"@ai-sdk/openai": "^1.3.23",
3231
"@inquirer/prompts": "^7.7.1",
33-
"@mastra/auth": "^0.1.1",
34-
"@mastra/core": "^0.11.1",
35-
"@mastra/libsql": "^0.11.2",
36-
"@mastra/loggers": "^0.10.4",
37-
"@mastra/mcp": "^0.10.7",
38-
"@mastra/memory": "^0.11.5",
32+
"@mastra/core": "^0.13.2",
33+
"@mastra/libsql": "^0.13.2",
34+
"@mastra/loggers": "^0.10.6",
35+
"@mastra/mcp": "^0.10.11",
36+
"@mastra/memory": "^0.12.2",
37+
"@mastra/pg": "^0.13.3",
3938
"@prisma/client": "^6.12.0",
4039
"@types/jsonwebtoken": "^9.0.10",
4140
"csv-parse": "^6.1.0",

mastra-test-app/pnpm-lock.yaml

Lines changed: 1193 additions & 2046 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mastra-test-app/src/auth-utils.ts

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,13 @@ import jwt from 'jsonwebtoken';
33
const JWT_SECRET = process.env.MASTRA_JWT_SECRET!;
44
const APP_PASSWORD = process.env.MASTRA_APP_PASSWORD!;
55

6-
if (!JWT_SECRET) {
7-
throw new Error('MASTRA_JWT_SECRET environment variable is required');
8-
}
9-
10-
if (!APP_PASSWORD) {
11-
throw new Error('MASTRA_APP_PASSWORD environment variable is required');
12-
}
136

14-
/**
15-
* Validates the provided password against the configured app password
16-
*/
7+
//Validates the provided password against the configured app password
178
export function validatePassword(password: string): boolean {
189
return password === APP_PASSWORD;
1910
}
2011

21-
/**
22-
* Generates a JWT token for authenticated sessions
23-
*/
12+
//Generates a JWT token for authenticated sessions
2413
export function generateAuthToken(): string {
2514
return jwt.sign(
2615
{
@@ -35,9 +24,7 @@ export function generateAuthToken(): string {
3524
);
3625
}
3726

38-
/**
39-
* Verifies a JWT token
40-
*/
27+
//Verifies a JWT token
4128
export function verifyAuthToken(token: string): boolean {
4229
try {
4330
jwt.verify(token, JWT_SECRET);
@@ -47,19 +34,15 @@ export function verifyAuthToken(token: string): boolean {
4734
}
4835
}
4936

50-
/**
51-
* Extracts token from Authorization header
52-
*/
37+
//Extracts token from Authorization header
5338
export function extractTokenFromHeader(authHeader: string | undefined): string | null {
5439
if (!authHeader || !authHeader.startsWith('Bearer ')) {
5540
return null;
5641
}
5742
return authHeader.substring(7); // Remove 'Bearer ' prefix
5843
}
5944

60-
/**
61-
* Creates a simple HTML login page
62-
*/
45+
//Creates a simple HTML login page
6346
export function createLoginPage(errorMessage?: string): string {
6447
return `
6548
<!DOCTYPE html>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Agent } from '@mastra/core/agent';
2+
import { openai } from '@ai-sdk/openai';
3+
import { anthropic } from '@ai-sdk/anthropic';
4+
import { google } from '@ai-sdk/google';
5+
import { databaseTools } from '../tools/database-tools';
6+
import { storageTools } from '../tools/storage-tools';
7+
8+
export const dataOpsAgent = new Agent({
9+
name: 'Data Ops Agent',
10+
description: 'Agent specialized in database and Mastra storage queries (participants, threads, messages, traces).',
11+
instructions: `
12+
You are a concise data operations assistant. Use the provided tools to:
13+
- Query and manage participants/household records
14+
- Inspect Mastra threads, messages, and traces
15+
- Return small, readable result sets
16+
`,
17+
// model: google('gemini-2.5-pro'),
18+
model: anthropic('claude-sonnet-4-20250514'),
19+
tools: {
20+
...Object.fromEntries(databaseTools.map(t => [t.id, t])),
21+
...Object.fromEntries(storageTools.map(t => [t.id, t])),
22+
},
23+
24+
defaultStreamOptions: {
25+
maxSteps: 50,
26+
maxRetries: 3,
27+
temperature: 0.1,
28+
telemetry: {
29+
isEnabled: true,
30+
functionId: 'dataOpsAgent.stream',
31+
recordInputs: true,
32+
recordOutputs: true,
33+
metadata: {
34+
agentId: 'dataOpsAgent',
35+
agentName: 'Data Ops Agent',
36+
},
37+
},
38+
},
39+
defaultGenerateOptions: {
40+
maxSteps: 50,
41+
maxRetries: 3,
42+
temperature: 0.1,
43+
telemetry: {
44+
isEnabled: true,
45+
functionId: 'dataOpsAgent.generate',
46+
recordInputs: true,
47+
recordOutputs: true,
48+
metadata: {
49+
agentId: 'dataOpsAgent',
50+
agentName: 'Data Ops Agent',
51+
},
52+
},
53+
},
54+
});

mastra-test-app/src/mastra/agents/web-automation-agent.ts

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { LibSQLStore, LibSQLVector } from '@mastra/libsql';
1+
import { postgresStore, pgVector } from '../storage';
22
import { exaMCP, playwrightMCP } from '../mcp';
33

44
import { Agent } from '@mastra/core/agent';
@@ -8,16 +8,9 @@ import { databaseTools } from '../tools/database-tools';
88
import { google } from '@ai-sdk/google';
99
import { openai } from '@ai-sdk/openai';
1010

11-
// Path is relative to .mastra/output/ when bundled
12-
const storage = new LibSQLStore({
13-
url: "file:../mastra.db",
14-
});
11+
const storage = postgresStore;
1512

16-
// Initialize vector store for semantic search
17-
const vectorStore = new LibSQLVector({
18-
connectionUrl: "file:../mastra.db",
19-
});
20-
// Create a memory instance with workingMemory enabled
13+
const vectorStore = pgVector;
2114
const memory = new Memory({
2215
storage: storage,
2316
vector: vectorStore,
@@ -155,11 +148,10 @@ export const webAutomationAgent = new Agent({
155148
)
156149
},
157150
memory: memory,
158-
// Set default options to handle step limits better
159151
defaultStreamOptions: {
160-
maxSteps: 50, // Increased from default 5
152+
maxSteps: 50,
161153
maxRetries: 3,
162-
temperature: 0.1, // Lower temperature for more focused responses
154+
temperature: 0.1,
163155
telemetry: {
164156
isEnabled: true,
165157
functionId: 'webAutomationAgent.stream',
@@ -172,7 +164,7 @@ export const webAutomationAgent = new Agent({
172164
},
173165
},
174166
defaultGenerateOptions: {
175-
maxSteps: 50, // Increased from default 5
167+
maxSteps: 50,
176168
maxRetries: 3,
177169
temperature: 0.1,
178170
telemetry: {
Lines changed: 13 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,23 @@
1-
import { LibSQLStore } from '@mastra/libsql';
1+
import { postgresStore } from './storage';
22
import { Mastra } from '@mastra/core/mastra';
33
import { PinoLogger } from '@mastra/loggers';
4-
import memoryAgent from './agents/memory-agent';
5-
import { weatherAgent } from './agents/weather-agent';
6-
import { weatherWorkflow } from './workflows/weather-workflow';
74
import { webAutomationAgent } from './agents/web-automation-agent';
85
import { webAutomationWorkflow } from './workflows/web-automation-workflow';
9-
import {
10-
validatePassword,
11-
generateAuthToken,
12-
verifyAuthToken,
13-
extractTokenFromHeader,
14-
createLoginPage
15-
} from '../auth-utils';
6+
import { dataOpsAgent } from './agents/data-ops-agent';
7+
import { serverMiddleware } from './middleware';
168

179
export const mastra = new Mastra({
1810
workflows: {
19-
weatherWorkflow,
2011
webAutomationWorkflow
2112
},
2213
agents: {
23-
weatherAgent,
2414
webAutomationAgent,
25-
memoryAgent,
15+
dataOpsAgent,
2616
},
27-
storage: new LibSQLStore({
28-
url: "file:../mastra.db",
29-
}),
17+
storage: postgresStore,
3018
logger: new PinoLogger({
3119
name: 'Mastra',
32-
level: 'info', // Changed from 'info' to 'debug' to capture more error details
20+
level: 'debug', // Change from 'info' to 'debug' to capture more error details
3321
}),
3422

3523
telemetry: {
@@ -46,124 +34,17 @@ export const mastra = new Mastra({
4634
server: {
4735
host: '0.0.0.0', // Allow external connections
4836
port: 4111,
37+
cors: {
38+
origin: ['http://localhost:4111', 'http://0.0.0.0:4111', '*'],
39+
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
40+
allowHeaders: ['Content-Type', 'Authorization', 'x-mastra-dev-playground'],
41+
credentials: true,
42+
},
4943
build: {
5044
swaggerUI: true, // Enable Swagger UI in production
5145
openAPIDocs: true, // Enable OpenAPI docs in production
5246
},
53-
// Disabled JWT auth since we're using session-based auth for the entire playground
54-
// experimental_auth: new MastraJwtAuth({
55-
// secret: process.env.MASTRA_JWT_SECRET!
56-
// }),
57-
middleware: [
58-
// Login route - handles password authentication
59-
{
60-
handler: async (c, next) => {
61-
const url = new URL(c.req.url);
62-
63-
// Handle login page GET request
64-
if (url.pathname === '/auth/login' && c.req.method === 'GET') {
65-
return new Response(createLoginPage(), {
66-
headers: { 'Content-Type': 'text/html' }
67-
});
68-
}
69-
70-
// Handle login form POST request
71-
if (url.pathname === '/auth/login' && c.req.method === 'POST') {
72-
try {
73-
const formData = await c.req.formData();
74-
const password = formData.get('password') as string;
75-
76-
if (validatePassword(password)) {
77-
const token = generateAuthToken();
78-
79-
// Set cookie and redirect to playground
80-
return new Response(null, {
81-
status: 302,
82-
headers: {
83-
'Location': '/agents/webAutomationAgent/chat/',
84-
'Set-Cookie': `mastra_token=${token}; Path=/; HttpOnly; Max-Age=86400; SameSite=Strict`
85-
}
86-
});
87-
} else {
88-
return new Response(createLoginPage('Invalid password. Please try again.'), {
89-
headers: { 'Content-Type': 'text/html' }
90-
});
91-
}
92-
} catch (error) {
93-
return new Response(createLoginPage('An error occurred. Please try again.'), {
94-
headers: { 'Content-Type': 'text/html' }
95-
});
96-
}
97-
}
98-
99-
await next();
100-
},
101-
path: '/auth/*',
102-
},
103-
// Protection middleware for playground and API routes
104-
{
105-
handler: async (c, next) => {
106-
const url = new URL(c.req.url);
107-
108-
// Allow Playground system requests to pass (telemetry/memory/etc.)
109-
// The Playground adds this header on its internal API calls
110-
const isDevPlayground = c.req.header('x-mastra-dev-playground') === 'true';
111-
if (isDevPlayground) {
112-
await next();
113-
return;
114-
}
11547

116-
// Skip auth for login routes
117-
if (url.pathname.startsWith('/auth/')) {
118-
await next();
119-
return;
120-
}
121-
122-
// Check for authentication token in cookie or header
123-
let token: string | null = null;
124-
125-
// First try cookie
126-
const cookies = c.req.header('Cookie');
127-
if (cookies) {
128-
const tokenMatch = cookies.match(/mastra_token=([^;]+)/);
129-
if (tokenMatch) {
130-
token = tokenMatch[1];
131-
}
132-
}
133-
134-
// Fallback to Authorization header
135-
if (!token) {
136-
token = extractTokenFromHeader(c.req.header('Authorization'));
137-
}
138-
139-
// Verify token
140-
if (!token || !verifyAuthToken(token)) {
141-
return new Response(null, {
142-
status: 302,
143-
headers: { 'Location': '/auth/login' }
144-
});
145-
}
146-
147-
await next();
148-
},
149-
path: '/*', // Protect all routes except auth
150-
},
151-
// Root redirect middleware
152-
{
153-
handler: async (c, next) => {
154-
const url = new URL(c.req.url);
155-
156-
if (url.pathname === '/') {
157-
return new Response(null, {
158-
status: 302,
159-
headers: { 'Location': '/auth/login' }
160-
});
161-
}
162-
163-
await next();
164-
},
165-
path: '/',
166-
}
167-
],
48+
middleware: serverMiddleware,
16849
},
16950
});

0 commit comments

Comments
 (0)