diff --git a/packages/agent/src/agent.ts b/packages/agent/src/agent.ts index cd01485e..efe1291b 100644 --- a/packages/agent/src/agent.ts +++ b/packages/agent/src/agent.ts @@ -1,7 +1,7 @@ import { streamText, generateText, ModelMessage, stepCountIs, UserModelMessage } from 'ai' import { AgentParams } from './types' import { system, schedule as schedulePrompt } from './prompts' -import { getMemoryTools, getScheduleTools } from './tools' +import { getMemoryTools, getScheduleTools, getMessageTools } from './tools' import { createChatGateway } from '@memohome/ai-gateway' import { Schedule } from '@memohome/shared' @@ -10,8 +10,12 @@ export const createAgent = (params: AgentParams) => { const gateway = createChatGateway(params.model) - const maxContextLoadTime = params.maxContextLoadTime ?? 60 + const maxContextLoadTime = params.maxContextLoadTime ?? 24 * 60 // 24 hours const language = params.language ?? 'Same as user input' + const platforms = params.platforms ?? [] + const currentPlatform = params.platforms + ? platforms.find(p => p.name === params.currentPlatform)?.name ?? 'Unknown Platform' + : 'client' const getTools = async () => { return { @@ -23,6 +27,10 @@ export const createAgent = (params: AgentParams) => { onRemoveSchedule: params.onRemoveSchedule ?? (() => Promise.resolve()), onSchedule: params.onSchedule ?? (() => Promise.resolve()), }), + ...getMessageTools( + platforms, + params.onSendMessage ?? (() => Promise.resolve()) + ), } } @@ -40,6 +48,8 @@ export const createAgent = (params: AgentParams) => { language, locale: params.locale, maxContextLoadTime, + platforms, + currentPlatform, }) } diff --git a/packages/agent/src/prompts/system.ts b/packages/agent/src/prompts/system.ts index 6423245f..08e7956f 100644 --- a/packages/agent/src/prompts/system.ts +++ b/packages/agent/src/prompts/system.ts @@ -1,3 +1,4 @@ +import { Platform } from '@memohome/shared' import { time } from './shared' import { quote } from './utils' @@ -6,13 +7,18 @@ export interface SystemParams { locale?: Intl.LocalesArgument language: string maxContextLoadTime: number + platforms: Platform[] + currentPlatform: string } -export const system = ({ date, locale, language, maxContextLoadTime }: SystemParams) => { +export const system = ({ date, locale, language, maxContextLoadTime, platforms, currentPlatform }: SystemParams) => { return ` --- ${time({ date, locale })} language: ${language} +available-platforms: +${platforms.map(platform => ` - ${platform.name}`).join('\n')} +current-platform: ${currentPlatform} --- You are a personal housekeeper assistant, which able to manage the master's daily affairs. @@ -33,5 +39,13 @@ Your abilities: + The ${quote('pattern')} is the pattern of the schedule with **Cron Syntax**. + The ${quote('command')} is the natural language command to execute, will send to you when the schedule is triggered, which means the command will be executed by presence of you. + The ${quote('maxCalls')} is the maximum number of calls to the schedule, If you want to run the task only once, set it to 1. +- The ${quote('command')} should include the method (e.g. ${quote('send-message')}) for returning the task result. If the user does not specify otherwise, the user should be asked how they would like to be notified. + +**Message** +- You can use ${quote('send-message')} to send a message to the master. + + The ${quote('platform')} is the platform to send the message to, it must be one of the ${quote('available-platforms')}. + + The ${quote('message')} is the message to send. + + IF: the problem is initiated by a user, regardless of the platform the user is using, the content should be directly output in the content. + + IF: the issue is initiated by a non-user (such as a scheduled task reminder), then it should be sent using the appropriate tools on the platform specified in the requirements. `.trim() } \ No newline at end of file diff --git a/packages/agent/src/tools/index.ts b/packages/agent/src/tools/index.ts index f758e659..b28ef4cb 100644 --- a/packages/agent/src/tools/index.ts +++ b/packages/agent/src/tools/index.ts @@ -1,2 +1,3 @@ export * from './memory' -export * from './schedule' \ No newline at end of file +export * from './schedule' +export * from './message' \ No newline at end of file diff --git a/packages/agent/src/tools/message.ts b/packages/agent/src/tools/message.ts new file mode 100644 index 00000000..4b86b696 --- /dev/null +++ b/packages/agent/src/tools/message.ts @@ -0,0 +1,24 @@ +import { Platform } from '@memohome/shared' +import { SendMessageOptions } from '../types' +import { tool } from 'ai' +import z from 'zod' + +export const getMessageTools = ( + platforms: Platform[], + onSendMessage: (platform: string, options: SendMessageOptions) => Promise, +) => { + const sendMessageTool = tool({ + description: 'Send a message to a platform', + inputSchema: z.object({ + platform: z.enum(platforms.map(platform => platform.name)), + message: z.string(), + }), + execute: async ({ platform, message }) => { + await onSendMessage(platform, { message }) + }, + }) + + return { + 'send-message': sendMessageTool, + } +} \ No newline at end of file diff --git a/packages/agent/src/types.ts b/packages/agent/src/types.ts index bf81177e..91a08245 100644 --- a/packages/agent/src/types.ts +++ b/packages/agent/src/types.ts @@ -1,7 +1,11 @@ import type { MemoryUnit } from '@memohome/memory' -import { ChatModel, Schedule } from '@memohome/shared' +import { ChatModel, Platform, Schedule } from '@memohome/shared' import { ModelMessage } from 'ai' +export interface SendMessageOptions { + message: string +} + export interface AgentParams { model: ChatModel @@ -18,6 +22,12 @@ export interface AgentParams { */ language?: string + platforms?: Platform[] + + currentPlatform?: string + + onSendMessage?: (platform: string, options: SendMessageOptions) => Promise + onReadMemory?: (from: Date, to: Date) => Promise onSearchMemory?: (query: string) => Promise diff --git a/packages/api/memory.db b/packages/api/memory.db index 1742b31e..5049f49b 100644 Binary files a/packages/api/memory.db and b/packages/api/memory.db differ diff --git a/packages/api/src/modules/agent/model.ts b/packages/api/src/modules/agent/model.ts index 1b73885f..0d5caee2 100644 --- a/packages/api/src/modules/agent/model.ts +++ b/packages/api/src/modules/agent/model.ts @@ -6,6 +6,7 @@ export const AgentStreamModel = { // Optional overrides - if not provided, will use settings maxContextLoadTime: z.number().int().min(1).max(1440).optional(), language: z.string().optional(), + platform: z.string().optional(), }), } diff --git a/packages/api/src/modules/agent/service.ts b/packages/api/src/modules/agent/service.ts index 7b1d4f36..106e0313 100644 --- a/packages/api/src/modules/agent/service.ts +++ b/packages/api/src/modules/agent/service.ts @@ -1,7 +1,8 @@ import { createAgent as createAgentService } from '@memohome/agent' import { createMemory, filterByTimestamp, MemoryUnit } from '@memohome/memory' -import { ChatModel, EmbeddingModel, Schedule } from '@memohome/shared' +import { ChatModel, EmbeddingModel, Platform, Schedule } from '@memohome/shared' import { createSchedule, deleteSchedule, getActiveSchedules } from '../schedule/service' +import { getActivePlatforms, sendMessageToPlatform } from '../platform/service' // Type for messages passed to onFinish callback type MessageType = Record @@ -13,6 +14,7 @@ export interface CreateAgentStreamParams { summaryModel: ChatModel maxContextLoadTime?: number language?: string + platform?: string onFinish?: (messages: MessageType[]) => Promise } @@ -24,6 +26,7 @@ export async function createAgent(params: CreateAgentStreamParams) { summaryModel, maxContextLoadTime, language, + platform, onFinish, } = params @@ -33,11 +36,21 @@ export async function createAgent(params: CreateAgentStreamParams) { embeddingModel, }) + const platforms = await getActivePlatforms() + // Create agent const agent = createAgentService({ model: chatModel, maxContextLoadTime, language: language || 'Same as user input', + platforms: platforms as Platform[], + currentPlatform: platform, + onSendMessage: async (platform: string, options) => { + await sendMessageToPlatform(platform, { + message: options.message, + userId, + }) + }, onReadMemory: async (from: Date, to: Date) => { return await filterByTimestamp(from, to, userId) }, diff --git a/packages/platform-telegram/src/index.ts b/packages/platform-telegram/src/index.ts index 13077550..6ab6329c 100644 --- a/packages/platform-telegram/src/index.ts +++ b/packages/platform-telegram/src/index.ts @@ -24,7 +24,7 @@ export class TelegramPlatform extends BasePlatform { redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379') // private storage?: TelegramRedisStorage - async start(config: Record): Promise { + override async start(config: z.infer): Promise { const botToken = config.botToken as string if (!botToken) { throw new Error('Bot token is required') diff --git a/packages/platform/src/index.ts b/packages/platform/src/index.ts index 2b38549d..bd14691e 100644 --- a/packages/platform/src/index.ts +++ b/packages/platform/src/index.ts @@ -13,7 +13,7 @@ export class BasePlatform { started: boolean = false port: number = 7003 - config = z.object() + config = z.record(z.string(), z.unknown()) // eslint-disable-next-line @typescript-eslint/no-unused-vars async start(config: z.infer): Promise {}