feat: new mastra

This commit is contained in:
Nevo David 2026-04-03 18:39:33 +07:00
parent 507a006b9f
commit 5d46a6cd34
17 changed files with 1197 additions and 937 deletions

View file

@ -20,7 +20,7 @@ import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/s
import { MastraAgent } from '@ag-ui/mastra';
import { MastraService } from '@gitroom/nestjs-libraries/chat/mastra.service';
import { Request, Response } from 'express';
import { RuntimeContext } from '@mastra/core/di';
import { RequestContext } from '@mastra/core/di';
import { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability';
import { AuthorizationActions, Sections } from '@gitroom/backend/services/auth/permissions/permission.exception.class';
@ -72,20 +72,19 @@ export class CopilotController {
return;
}
const mastra = await this._mastraService.mastra();
const runtimeContext = new RuntimeContext<ChannelsContext>();
runtimeContext.set(
const requestContext = new RequestContext<ChannelsContext>();
requestContext.set(
'integrations',
req?.body?.variables?.properties?.integrations || []
);
runtimeContext.set('organization', JSON.stringify(organization));
runtimeContext.set('ui', 'true');
requestContext.set('organization', JSON.stringify(organization));
requestContext.set('ui', 'true');
const agents = MastraAgent.getLocalAgents({
resourceId: organization.id,
mastra,
// @ts-ignore
runtimeContext,
requestContext: requestContext as any,
});
const runtime = new CopilotRuntime({
@ -124,7 +123,7 @@ export class CopilotController {
const mastra = await this._mastraService.mastra();
const memory = await mastra.getAgent('postiz').getMemory();
try {
return await memory.query({
return await memory.recall({
resourceId: organization.id,
threadId,
});
@ -137,14 +136,12 @@ export class CopilotController {
@CheckPolicies([AuthorizationActions.Create, Sections.AI])
async getList(@GetOrgFromRequest() organization: Organization) {
const mastra = await this._mastraService.mastra();
// @ts-ignore
const memory = await mastra.getAgent('postiz').getMemory();
const list = await memory.getThreadsByResourceIdPaginated({
resourceId: organization.id,
const list = await memory.listThreads({
filter: { resourceId: organization.id },
perPage: 100000,
page: 0,
orderBy: 'createdAt',
sortDirection: 'DESC',
orderBy: { field: 'createdAt', direction: 'DESC' },
});
return {

View file

@ -22,6 +22,7 @@ import { startMcp } from '@gitroom/nestjs-libraries/chat/start.mcp';
async function start() {
const app = await NestFactory.create(AppModule, {
rawBody: true,
bodyParser: false,
cors: {
...(!process.env.NOT_SECURED ? { credentials: true } : {}),
allowedHeaders: [
@ -52,8 +53,12 @@ async function start() {
})
);
app.use(['/copilot/*', '/posts'], (req: any, res: any, next: any) => {
json({ limit: '50mb' })(req, res, next);
app.use((req: any, res: any, next: any) => {
if (req.path.startsWith('/mcp') || req.path.startsWith('/sse/') || req.path.startsWith('/message/')) {
return next();
}
const limit = ['/copilot/', '/posts'].some((p) => req.path.startsWith(p)) ? '50mb' : '100kb';
json({ limit })(req, res, next);
});
app.use(cookieParser());

View file

@ -1,16 +1,6 @@
import type { ZodLikeSchema } from '@mastra/core/dist/types/zod-compat';
import type {
ToolExecutionContext,
} from '@mastra/core/dist/tools/types';
import { Tool } from '@mastra/core/dist/tools/tool';
import type { ToolAction } from '@mastra/core/tools';
export type ToolReturn = Tool<
ZodLikeSchema,
ZodLikeSchema,
ZodLikeSchema,
ZodLikeSchema,
ToolExecutionContext<ZodLikeSchema, ZodLikeSchema, ZodLikeSchema>
>;
export type ToolReturn = ToolAction<any, any, any, any, any, any>;
export interface AgentToolInterface {
name: string;

View file

@ -1,20 +1,16 @@
import { ToolAction } from '@mastra/core/dist/tools/types';
import { getAuth } from '@gitroom/nestjs-libraries/chat/async.storage';
export const checkAuth: ToolAction['execute'] = async (
{ runtimeContext },
options
export const checkAuth = (
inputData: any,
context: any
) => {
const auth = getAuth();
// @ts-ignore
if (options?.extra?.authInfo || auth) {
runtimeContext.set(
// @ts-ignore
const authInfo = context?.mcp?.extra?.authInfo || auth;
if (authInfo && context?.requestContext) {
(context.requestContext as any).set(
'organization',
// @ts-ignore
JSON.stringify(options?.extra?.authInfo || auth)
JSON.stringify(authInfo)
);
// @ts-ignore
runtimeContext.set('ui', 'false');
(context.requestContext as any).set('ui', 'false');
}
};

View file

@ -43,10 +43,11 @@ export class LoadToolsService {
async agent() {
const tools = await this.loadTools();
return new Agent({
id: 'postiz',
name: 'postiz',
description: 'Agent that helps manage and schedule social media posts for users',
instructions: ({ runtimeContext }) => {
const ui: string = runtimeContext.get('ui' as never);
instructions: ({ requestContext }) => {
const ui: string = requestContext.get('ui' as never);
return `
Global information:
- Date (UTC): ${dayjs().format('YYYY-MM-DD HH:mm:ss')}
@ -90,9 +91,7 @@ export class LoadToolsService {
memory: new Memory({
storage: pStore,
options: {
threads: {
generateTitle: true,
},
generateTitle: true,
workingMemory: {
enabled: true,
schema: AgentState,

View file

@ -1,5 +1,6 @@
import { PostgresStore, PgVector } from '@mastra/pg';
import { PostgresStore } from '@mastra/pg';
export const pStore = new PostgresStore({
connectionString: process.env.DATABASE_URL,
id: 'postiz-store',
connectionString: process.env.DATABASE_URL!,
});

View file

@ -6,6 +6,7 @@ import { randomUUID } from 'crypto';
import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';
import { OAuthService } from '@gitroom/nestjs-libraries/database/prisma/oauth/oauth.service';
import { runWithContext } from './async.storage';
export const startMcp = async (app: INestApplication) => {
const mastraService = app.get(MastraService, { strict: false });
const organizationService = app.get(OrganizationService, { strict: false });
@ -22,7 +23,7 @@ export const startMcp = async (app: INestApplication) => {
const mastra = await mastraService.mastra();
const agent = mastra.getAgent('postiz');
const tools = await agent.getTools();
const tools = await agent.listTools();
const serverConfig = {
name: 'Postiz MCP',
@ -76,6 +77,7 @@ export const startMcp = async (app: INestApplication) => {
sessionIdGenerator: () => {
return randomUUID();
},
enableJsonResponse: true,
},
req,
res,
@ -119,6 +121,7 @@ export const startMcp = async (app: INestApplication) => {
sessionIdGenerator: () => {
return randomUUID();
},
enableJsonResponse: true,
},
req,
res,

View file

@ -27,13 +27,11 @@ export class GenerateImageTool implements AgentToolInterface {
id: z.string(),
path: z.string(),
}),
execute: async (args, options) => {
const { context, runtimeContext } = args;
checkAuth(args, options);
// @ts-ignore
const org = JSON.parse(runtimeContext.get('organization') as string);
execute: async (inputData, context) => {
checkAuth(inputData, context);
const org = JSON.parse((context?.requestContext as any)?.get('organization') as string);
const image = await this._mediaService.generateImage(
context.prompt,
inputData.prompt,
org
);

View file

@ -1,6 +1,5 @@
import {
AgentToolInterface,
ToolReturn,
} from '@gitroom/nestjs-libraries/chat/agent.tool.interface';
import { createTool } from '@mastra/core/tools';
import { Injectable } from '@nestjs/common';
@ -33,9 +32,8 @@ export class GenerateVideoOptionsTool implements AgentToolInterface {
})
),
}),
execute: async (args, options) => {
const { context, runtimeContext } = args;
checkAuth(args, options);
execute: async (inputData, context) => {
checkAuth(inputData, context);
const videos = this._videoManagerService.getAllVideos();
console.log(
JSON.stringify(

View file

@ -48,20 +48,18 @@ export class GenerateVideoTool implements AgentToolInterface {
outputSchema: z.object({
url: z.string(),
}),
execute: async (args, options) => {
const { context, runtimeContext } = args;
checkAuth(args, options);
// @ts-ignore
const org = JSON.parse(runtimeContext.get('organization') as string);
execute: async (inputData, context) => {
checkAuth(inputData, context);
const org = JSON.parse((context?.requestContext as any)?.get('organization') as string);
const value = await this._mediaService.generateVideo(org, {
type: context.identifier,
output: context.output,
customParams: context.customParams.reduce(
(all, current) => ({
type: inputData.identifier,
output: inputData.output,
customParams: inputData.customParams.reduce(
(all: Record<string, any>, current: { key: string; value: any }) => ({
...all,
[current.key]: current.value,
}),
{}
{} as Record<string, any>
),
});

View file

@ -1,13 +1,11 @@
import {
AgentToolInterface,
ToolReturn,
} from '@gitroom/nestjs-libraries/chat/agent.tool.interface';
import { createTool } from '@mastra/core/tools';
import { Injectable } from '@nestjs/common';
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
import z from 'zod';
import { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context';
import { getAuth } from '@gitroom/nestjs-libraries/chat/async.storage';
@Injectable()
export class IntegrationListTool implements AgentToolInterface {
@ -28,14 +26,10 @@ export class IntegrationListTool implements AgentToolInterface {
})
),
}),
execute: async (args, options) => {
console.log(getAuth());
console.log(options);
const { context, runtimeContext } = args;
checkAuth(args, options);
execute: async (inputData, context) => {
checkAuth(inputData, context);
const organizationId = JSON.parse(
// @ts-ignore
runtimeContext.get('organization') as string
(context?.requestContext as any)?.get('organization') as string
).id;
return {

View file

@ -114,17 +114,15 @@ If the tools return errors, you would need to rerun it with the right parameters
)
.or(z.object({ errors: z.string() })),
}),
execute: async (args, options) => {
const { context, runtimeContext } = args;
checkAuth(args, options);
execute: async (inputData, context) => {
checkAuth(inputData, context);
const organizationId = JSON.parse(
// @ts-ignore
runtimeContext.get('organization') as string
(context?.requestContext as any)?.get('organization') as string
).id;
const finalOutput = [];
const integrations = {} as Record<string, Integration>;
for (const platform of context.socialPost) {
for (const platform of inputData.socialPost) {
integrations[platform.integrationId] =
await this._integrationService.getIntegrationById(
organizationId,
@ -142,7 +140,7 @@ If the tools return errors, you would need to rerun it with the right parameters
const obj = Object.assign(
newDTO,
platform.settings.reduce(
(acc, s) => ({
(acc: AllProvidersSettings, s: { key: string; value: any }) => ({
...acc,
[s.key]: s.value,
}),
@ -180,7 +178,7 @@ If the tools return errors, you would need to rerun it with the right parameters
}
}
for (const post of context.socialPost) {
for (const post of inputData.socialPost) {
const integration = integrations[post.integrationId];
if (!integration) {
@ -197,7 +195,7 @@ If the tools return errors, you would need to rerun it with the right parameters
integration,
group: makeId(10),
settings: post.settings.reduce(
(acc, s) => ({
(acc: AllProvidersSettings, s: { key: string; value: any }) => ({
...acc,
[s.key]: s.value,
}),
@ -205,11 +203,11 @@ If the tools return errors, you would need to rerun it with the right parameters
__type: integration.providerIdentifier,
} as AllProvidersSettings
),
value: post.postsAndComments.map((p) => ({
value: post.postsAndComments.map((p: any) => ({
content: p.content,
id: makeId(10),
delay: 0,
image: p.attachments.map((p) => ({
image: p.attachments.map((p: any) => ({
id: makeId(10),
path: p,
})),

View file

@ -43,19 +43,17 @@ export class IntegrationTriggerTool implements AgentToolInterface {
outputSchema: z.object({
output: z.array(z.record(z.string(), z.any())),
}),
execute: async (args, options) => {
const { context, runtimeContext } = args;
checkAuth(args, options);
console.log('triggerTool', context);
execute: async (inputData, context) => {
checkAuth(inputData, context);
console.log('triggerTool', inputData);
const organizationId = JSON.parse(
// @ts-ignore
runtimeContext.get('organization') as string
(context?.requestContext as any)?.get('organization') as string
).id;
const getIntegration =
await this._integrationService.getIntegrationById(
organizationId,
context.integrationId
inputData.integrationId
);
if (!getIntegration) {
@ -78,10 +76,10 @@ export class IntegrationTriggerTool implements AgentToolInterface {
if (
// @ts-ignore
!tools[integrationProvider.identifier].some(
(p) => p.methodName === context.methodName
(p) => p.methodName === inputData.methodName
) ||
// @ts-ignore
!integrationProvider[context.methodName]
!integrationProvider[inputData.methodName]
) {
return { output: 'tool not found' };
}
@ -89,14 +87,14 @@ export class IntegrationTriggerTool implements AgentToolInterface {
while (true) {
try {
// @ts-ignore
const load = await integrationProvider[context.methodName](
const load = await integrationProvider[inputData.methodName](
getIntegration.token,
context.dataSchema.reduce(
(all, current) => ({
inputData.dataSchema.reduce(
(all: Record<string, string>, current: { key: string; value: string }) => ({
...all,
[current.key]: current.value,
}),
{}
{} as Record<string, string>
),
getIntegration.internalId,
getIntegration

View file

@ -72,11 +72,10 @@ export class IntegrationValidationTool implements AgentToolInterface {
),
}),
}),
execute: async (args, options) => {
const { context, runtimeContext } = args;
checkAuth(args, options);
execute: async (inputData, context) => {
checkAuth(inputData, context);
const integration = socialIntegrationList.find(
(p) => p.identifier === context.platform
(p) => p.identifier === inputData.platform
)!;
if (!integration) {
@ -85,7 +84,7 @@ export class IntegrationValidationTool implements AgentToolInterface {
};
}
const maxLength = integration.maxLength(context.isPremium);
const maxLength = integration.maxLength(inputData.isPremium);
const schemas = !integration.dto
? false
: getValidationSchemas()[integration.dto.name];

View file

@ -22,14 +22,13 @@ export class VideoFunctionTool implements AgentToolInterface {
identifier: z.string(),
functionName: z.string(),
}),
execute: async (args, options) => {
const { context, runtimeContext } = args;
checkAuth(args, options);
execute: async (inputData, context) => {
checkAuth(inputData, context);
const videos = this._videoManagerService.getAllVideos();
const findVideo = videos.find(
(p) =>
p.identifier === context.identifier &&
p.tools.some((p) => p.functionName === context.functionName)
p.identifier === inputData.identifier &&
p.tools.some((p) => p.functionName === inputData.functionName)
);
if (!findVideo) {
@ -39,7 +38,7 @@ export class VideoFunctionTool implements AgentToolInterface {
const func = await this._moduleRef
// @ts-ignore
.get(findVideo.target, { strict: false })
[context.functionName]();
[inputData.functionName]();
return func;
},
});

View file

@ -42,7 +42,7 @@
"test": "jest --coverage --detectOpenHandles --reporters=default --reporters=jest-junit"
},
"dependencies": {
"@ag-ui/mastra": "0.2.0",
"@ag-ui/mastra": "^1.0.1",
"@ai-sdk/openai": "^2.0.52",
"@atproto/api": "^0.15.15",
"@aws-sdk/client-s3": "^3.787.0",
@ -63,9 +63,10 @@
"@mantine/dates": "^5.10.5",
"@mantine/hooks": "^5.10.5",
"@mantine/modals": "^5.10.5",
"@mastra/core": "^0.20.2",
"@mastra/memory": "^0.15.6",
"@mastra/pg": "^0.17.2",
"@mastra/core": "^1.21.0",
"@mastra/mcp": "^1.4.1",
"@mastra/memory": "^1.13.0",
"@mastra/pg": "^1.8.5",
"@modelcontextprotocol/sdk": "^1.22.0",
"@nest-lab/throttler-storage-redis": "^1.2.0",
"@nestjs/cli": "10.0.2",
@ -178,7 +179,7 @@
"json-to-graphql-query": "^2.2.5",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"mastra": "^0.13.2",
"mastra": "^1.3.19",
"md5": "^2.3.0",
"mime": "^3.0.0",
"mime-types": "^2.1.35",

1928
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff