mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
fix: web, skills tools
This commit is contained in:
+154
-98
@@ -1,5 +1,20 @@
|
||||
import { generateText, ImagePart, LanguageModelUsage, ModelMessage, stepCountIs, streamText, UserModelMessage } from 'ai'
|
||||
import { AgentInput, AgentParams, AgentSkill, allActions, Schedule } from './types'
|
||||
import {
|
||||
generateText,
|
||||
ImagePart,
|
||||
LanguageModelUsage,
|
||||
ModelMessage,
|
||||
stepCountIs,
|
||||
streamText,
|
||||
ToolSet,
|
||||
UserModelMessage,
|
||||
} from 'ai'
|
||||
import {
|
||||
AgentInput,
|
||||
AgentParams,
|
||||
AgentSkill,
|
||||
allActions,
|
||||
Schedule,
|
||||
} from './types'
|
||||
import { system, schedule, user, subagentSystem } from './prompts'
|
||||
import { AuthFetcher } from './index'
|
||||
import { createModel } from './model'
|
||||
@@ -12,36 +27,40 @@ import {
|
||||
} from './utils/attachments'
|
||||
import type { ContainerFileAttachment } from './types/attachment'
|
||||
import { getMCPTools } from './tools/mcp'
|
||||
import { getTools } from './tools'
|
||||
|
||||
export const createAgent = ({
|
||||
model: modelConfig,
|
||||
activeContextTime = 24 * 60,
|
||||
brave,
|
||||
language = 'Same as the user input',
|
||||
allowedActions = allActions,
|
||||
channels = [],
|
||||
skills = [],
|
||||
currentChannel = 'Unknown Channel',
|
||||
identity = {
|
||||
botId: '',
|
||||
containerId: '',
|
||||
channelIdentityId: '',
|
||||
displayName: '',
|
||||
},
|
||||
auth,
|
||||
}: AgentParams, fetch: AuthFetcher) => {
|
||||
export const createAgent = (
|
||||
{
|
||||
model: modelConfig,
|
||||
activeContextTime = 24 * 60,
|
||||
brave,
|
||||
language = 'Same as the user input',
|
||||
allowedActions = allActions,
|
||||
channels = [],
|
||||
skills = [],
|
||||
currentChannel = 'Unknown Channel',
|
||||
identity = {
|
||||
botId: '',
|
||||
containerId: '',
|
||||
channelIdentityId: '',
|
||||
displayName: '',
|
||||
},
|
||||
auth,
|
||||
}: AgentParams,
|
||||
fetch: AuthFetcher,
|
||||
) => {
|
||||
const model = createModel(modelConfig)
|
||||
const enabledSkills: AgentSkill[] = []
|
||||
|
||||
const enableSkill = (skill: string) => {
|
||||
const agentSkill = skills.find(s => s.name === skill)
|
||||
const agentSkill = skills.find((s) => s.name === skill)
|
||||
if (agentSkill) {
|
||||
enabledSkills.push(agentSkill)
|
||||
}
|
||||
}
|
||||
|
||||
const getEnabledSkills = () => {
|
||||
return enabledSkills.map(skill => skill.name)
|
||||
return enabledSkills.map((skill) => skill.name)
|
||||
}
|
||||
|
||||
const loadSystemFiles = async () => {
|
||||
@@ -56,8 +75,8 @@ export const createAgent = ({
|
||||
const url = `${auth.baseUrl.replace(/\/$/, '')}/bots/${identity.botId}/tools`
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json, text/event-stream',
|
||||
'Authorization': `Bearer ${auth.bearer}`,
|
||||
Accept: 'application/json, text/event-stream',
|
||||
Authorization: `Bearer ${auth.bearer}`,
|
||||
}
|
||||
if (identity.channelIdentityId) {
|
||||
headers['X-Memoh-Channel-Identity-Id'] = identity.channelIdentityId
|
||||
@@ -70,8 +89,9 @@ export const createAgent = ({
|
||||
})
|
||||
const response = await fetch(url, { method: 'POST', headers, body })
|
||||
if (!response.ok) return ''
|
||||
const data = await response.json().catch(() => ({} as any))
|
||||
const structured = data?.result?.structuredContent ?? data?.result?.content?.[0]?.text
|
||||
const data = await response.json().catch(() => ({}))
|
||||
const structured =
|
||||
data?.result?.structuredContent ?? data?.result?.content?.[0]?.text
|
||||
if (typeof structured === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(structured)
|
||||
@@ -98,7 +118,8 @@ export const createAgent = ({
|
||||
}
|
||||
|
||||
const generateSystemPrompt = async () => {
|
||||
const { identityContent, soulContent, toolsContent } = await loadSystemFiles()
|
||||
const { identityContent, soulContent, toolsContent } =
|
||||
await loadSystemFiles()
|
||||
return system({
|
||||
date: new Date(),
|
||||
language,
|
||||
@@ -123,7 +144,7 @@ export const createAgent = ({
|
||||
}
|
||||
}
|
||||
const headers: Record<string, string> = {
|
||||
'Authorization': `Bearer ${auth.bearer}`,
|
||||
Authorization: `Bearer ${auth.bearer}`,
|
||||
}
|
||||
if (identity.channelIdentityId) {
|
||||
headers['X-Memoh-Channel-Identity-Id'] = identity.channelIdentityId
|
||||
@@ -137,16 +158,24 @@ export const createAgent = ({
|
||||
if (identity.replyTarget) {
|
||||
headers['X-Memoh-Reply-Target'] = identity.replyTarget
|
||||
}
|
||||
const { tools: mcpTools, close: closeMCP } = await getMCPTools(`${baseUrl}/bots/${botId}/tools`, headers)
|
||||
const { tools: mcpTools, close: closeMCP } = await getMCPTools(
|
||||
`${baseUrl}/bots/${botId}/tools`,
|
||||
headers,
|
||||
)
|
||||
const tools = getTools(allowedActions, { fetch, model: modelConfig, brave, identity, auth, enableSkill })
|
||||
return {
|
||||
tools: mcpTools,
|
||||
tools: { ...mcpTools, ...tools } as ToolSet,
|
||||
close: closeMCP,
|
||||
}
|
||||
}
|
||||
|
||||
const generateUserPrompt = (input: AgentInput) => {
|
||||
const images = input.attachments.filter(attachment => attachment.type === 'image')
|
||||
const files = input.attachments.filter((a): a is ContainerFileAttachment => a.type === 'file')
|
||||
const images = input.attachments.filter(
|
||||
(attachment) => attachment.type === 'image',
|
||||
)
|
||||
const files = input.attachments.filter(
|
||||
(a): a is ContainerFileAttachment => a.type === 'file',
|
||||
)
|
||||
const text = user(input.query, {
|
||||
channelIdentityId: identity.channelIdentityId || identity.contactId || '',
|
||||
displayName: identity.displayName || identity.contactName || 'User',
|
||||
@@ -158,8 +187,10 @@ export const createAgent = ({
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text },
|
||||
...images.map(image => ({ type: 'image', image: image.base64 }) as ImagePart),
|
||||
]
|
||||
...images.map(
|
||||
(image) => ({ type: 'image', image: image.base64 }) as ImagePart,
|
||||
),
|
||||
],
|
||||
}
|
||||
return userMessage
|
||||
}
|
||||
@@ -167,7 +198,7 @@ export const createAgent = ({
|
||||
const ask = async (input: AgentInput) => {
|
||||
const userPrompt = generateUserPrompt(input)
|
||||
const messages = [...input.messages, userPrompt]
|
||||
input.skills.forEach(skill => enableSkill(skill))
|
||||
input.skills.forEach((skill) => enableSkill(skill))
|
||||
const systemPrompt = await generateSystemPrompt()
|
||||
const { tools, close } = await getAgentTools()
|
||||
const { response, reasoning, text, usage } = await generateText({
|
||||
@@ -185,12 +216,17 @@ export const createAgent = ({
|
||||
},
|
||||
tools,
|
||||
})
|
||||
const { cleanedText, attachments: textAttachments } = extractAttachmentsFromText(text)
|
||||
const { messages: strippedMessages, attachments: messageAttachments } = stripAttachmentsFromMessages(response.messages)
|
||||
const allAttachments = dedupeAttachments([...textAttachments, ...messageAttachments])
|
||||
const { cleanedText, attachments: textAttachments } =
|
||||
extractAttachmentsFromText(text)
|
||||
const { messages: strippedMessages, attachments: messageAttachments } =
|
||||
stripAttachmentsFromMessages(response.messages)
|
||||
const allAttachments = dedupeAttachments([
|
||||
...textAttachments,
|
||||
...messageAttachments,
|
||||
])
|
||||
return {
|
||||
messages: strippedMessages,
|
||||
reasoning: reasoning.map(part => part.text),
|
||||
reasoning: reasoning.map((part) => part.text),
|
||||
usage,
|
||||
text: cleanedText,
|
||||
attachments: allAttachments,
|
||||
@@ -199,16 +235,14 @@ export const createAgent = ({
|
||||
}
|
||||
|
||||
const askAsSubagent = async (params: {
|
||||
input: string
|
||||
name: string
|
||||
description: string
|
||||
messages: ModelMessage[]
|
||||
input: string;
|
||||
name: string;
|
||||
description: string;
|
||||
messages: ModelMessage[];
|
||||
}) => {
|
||||
const userPrompt: UserModelMessage = {
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: params.input },
|
||||
]
|
||||
content: [{ type: 'text', text: params.input }],
|
||||
}
|
||||
const generateSubagentSystemPrompt = () => {
|
||||
return subagentSystem({
|
||||
@@ -236,7 +270,7 @@ export const createAgent = ({
|
||||
})
|
||||
return {
|
||||
messages: [userPrompt, ...response.messages],
|
||||
reasoning: reasoning.map(part => part.text),
|
||||
reasoning: reasoning.map((part) => part.text),
|
||||
usage,
|
||||
text,
|
||||
skills: getEnabledSkills(),
|
||||
@@ -244,18 +278,21 @@ export const createAgent = ({
|
||||
}
|
||||
|
||||
const triggerSchedule = async (params: {
|
||||
schedule: Schedule
|
||||
messages: ModelMessage[]
|
||||
skills: string[]
|
||||
schedule: Schedule;
|
||||
messages: ModelMessage[];
|
||||
skills: string[];
|
||||
}) => {
|
||||
const scheduleMessage: UserModelMessage = {
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: schedule({ schedule: params.schedule, date: new Date() }) },
|
||||
]
|
||||
{
|
||||
type: 'text',
|
||||
text: schedule({ schedule: params.schedule, date: new Date() }),
|
||||
},
|
||||
],
|
||||
}
|
||||
const messages = [...params.messages, scheduleMessage]
|
||||
params.skills.forEach(skill => enableSkill(skill))
|
||||
params.skills.forEach((skill) => enableSkill(skill))
|
||||
const { tools, close } = await getAgentTools()
|
||||
const { response, reasoning, text, usage } = await generateText({
|
||||
model,
|
||||
@@ -269,7 +306,7 @@ export const createAgent = ({
|
||||
})
|
||||
return {
|
||||
messages: [scheduleMessage, ...response.messages],
|
||||
reasoning: reasoning.map(part => part.text),
|
||||
reasoning: reasoning.map((part) => part.text),
|
||||
usage,
|
||||
text,
|
||||
skills: getEnabledSkills(),
|
||||
@@ -301,17 +338,17 @@ export const createAgent = ({
|
||||
async function* stream(input: AgentInput): AsyncGenerator<AgentAction> {
|
||||
const userPrompt = generateUserPrompt(input)
|
||||
const messages = [...input.messages, userPrompt]
|
||||
input.skills.forEach(skill => enableSkill(skill))
|
||||
input.skills.forEach((skill) => enableSkill(skill))
|
||||
const systemPrompt = await generateSystemPrompt()
|
||||
const attachmentsExtractor = new AttachmentsStreamExtractor()
|
||||
const result: {
|
||||
messages: ModelMessage[]
|
||||
reasoning: string[]
|
||||
usage: LanguageModelUsage | null
|
||||
messages: ModelMessage[];
|
||||
reasoning: string[];
|
||||
usage: LanguageModelUsage | null;
|
||||
} = {
|
||||
messages: [],
|
||||
reasoning: [],
|
||||
usage: null
|
||||
usage: null,
|
||||
}
|
||||
const { tools, close } = await getAgentTools()
|
||||
const { fullStream } = streamText({
|
||||
@@ -328,9 +365,9 @@ export const createAgent = ({
|
||||
onFinish: async ({ usage, reasoning, response }) => {
|
||||
await close()
|
||||
result.usage = usage as never
|
||||
result.reasoning = reasoning.map(part => part.text)
|
||||
result.reasoning = reasoning.map((part) => part.text)
|
||||
result.messages = response.messages
|
||||
}
|
||||
},
|
||||
})
|
||||
yield {
|
||||
type: 'agent_start',
|
||||
@@ -338,26 +375,38 @@ export const createAgent = ({
|
||||
}
|
||||
for await (const chunk of fullStream) {
|
||||
if (chunk.type === 'error') {
|
||||
throw new Error(resolveStreamErrorMessage((chunk as { error?: unknown }).error))
|
||||
throw new Error(
|
||||
resolveStreamErrorMessage((chunk as { error?: unknown }).error),
|
||||
)
|
||||
}
|
||||
switch (chunk.type) {
|
||||
case 'reasoning-start': yield {
|
||||
type: 'reasoning_start',
|
||||
metadata: chunk
|
||||
}; break
|
||||
case 'reasoning-delta': yield {
|
||||
type: 'reasoning_delta',
|
||||
delta: chunk.text
|
||||
}; break
|
||||
case 'reasoning-end': yield {
|
||||
type: 'reasoning_end',
|
||||
metadata: chunk
|
||||
}; break
|
||||
case 'text-start': yield {
|
||||
type: 'text_start',
|
||||
}; break
|
||||
case 'reasoning-start':
|
||||
yield {
|
||||
type: 'reasoning_start',
|
||||
metadata: chunk,
|
||||
}
|
||||
break
|
||||
case 'reasoning-delta':
|
||||
yield {
|
||||
type: 'reasoning_delta',
|
||||
delta: chunk.text,
|
||||
}
|
||||
break
|
||||
case 'reasoning-end':
|
||||
yield {
|
||||
type: 'reasoning_end',
|
||||
metadata: chunk,
|
||||
}
|
||||
break
|
||||
case 'text-start':
|
||||
yield {
|
||||
type: 'text_start',
|
||||
}
|
||||
break
|
||||
case 'text-delta': {
|
||||
const { visibleText, attachments } = attachmentsExtractor.push(chunk.text)
|
||||
const { visibleText, attachments } = attachmentsExtractor.push(
|
||||
chunk.text,
|
||||
)
|
||||
if (visibleText) {
|
||||
yield {
|
||||
type: 'text_delta',
|
||||
@@ -393,30 +442,37 @@ export const createAgent = ({
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'tool-call': yield {
|
||||
type: 'tool_call_start',
|
||||
toolName: chunk.toolName,
|
||||
toolCallId: chunk.toolCallId,
|
||||
input: chunk.input,
|
||||
metadata: chunk
|
||||
}; break
|
||||
case 'tool-result': yield {
|
||||
type: 'tool_call_end',
|
||||
toolName: chunk.toolName,
|
||||
toolCallId: chunk.toolCallId,
|
||||
input: chunk.input,
|
||||
result: chunk.output,
|
||||
metadata: chunk
|
||||
}; break
|
||||
case 'file': yield {
|
||||
type: 'image_delta',
|
||||
image: chunk.file.base64,
|
||||
metadata: chunk
|
||||
}
|
||||
case 'tool-call':
|
||||
yield {
|
||||
type: 'tool_call_start',
|
||||
toolName: chunk.toolName,
|
||||
toolCallId: chunk.toolCallId,
|
||||
input: chunk.input,
|
||||
metadata: chunk,
|
||||
}
|
||||
break
|
||||
case 'tool-result':
|
||||
yield {
|
||||
type: 'tool_call_end',
|
||||
toolName: chunk.toolName,
|
||||
toolCallId: chunk.toolCallId,
|
||||
input: chunk.input,
|
||||
result: chunk.output,
|
||||
metadata: chunk,
|
||||
}
|
||||
break
|
||||
case 'file':
|
||||
yield {
|
||||
type: 'image_delta',
|
||||
image: chunk.file.base64,
|
||||
metadata: chunk,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { messages: strippedMessages } = stripAttachmentsFromMessages(result.messages)
|
||||
const { messages: strippedMessages } = stripAttachmentsFromMessages(
|
||||
result.messages,
|
||||
)
|
||||
yield {
|
||||
type: 'agent_end',
|
||||
messages: strippedMessages,
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
import { tool } from 'ai'
|
||||
import { z } from 'zod'
|
||||
import { AuthFetcher } from '..'
|
||||
import type { IdentityContext } from '../types'
|
||||
|
||||
export type ContactToolParams = {
|
||||
fetch: AuthFetcher
|
||||
identity: IdentityContext
|
||||
}
|
||||
|
||||
export const getContactTools = ({ fetch, identity }: ContactToolParams) => {
|
||||
const botId = identity.botId.trim()
|
||||
|
||||
const listMyIdentities = async () => {
|
||||
const response = await fetch('/users/me/identities')
|
||||
return response.json()
|
||||
}
|
||||
|
||||
const contactSearch = tool({
|
||||
description: 'Search identity cards by platform, external id, or display name',
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe('The query to search identities').optional().default(''),
|
||||
}),
|
||||
execute: async ({ query }) => {
|
||||
const payload = await listMyIdentities()
|
||||
const keyword = query.trim().toLowerCase()
|
||||
const items = Array.isArray(payload?.items) ? payload.items : []
|
||||
const filtered = keyword
|
||||
? items.filter((item: { platform?: string; external_id?: string; display_name?: string }) => {
|
||||
const platform = String(item?.platform ?? '').toLowerCase()
|
||||
const externalID = String(item?.external_id ?? '').toLowerCase()
|
||||
const displayName = String(item?.display_name ?? '').toLowerCase()
|
||||
return platform.includes(keyword) || externalID.includes(keyword) || displayName.includes(keyword)
|
||||
})
|
||||
: items
|
||||
return {
|
||||
canonical_channel_identity_id: payload?.canonical_channel_identity_id ?? '',
|
||||
total: filtered.length,
|
||||
items: filtered,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const contactCardMe = tool({
|
||||
description: 'Get my canonical identity card and all linked channel identities',
|
||||
inputSchema: z.object({}),
|
||||
execute: async () => {
|
||||
return listMyIdentities()
|
||||
},
|
||||
})
|
||||
|
||||
const contactIssueBindCode = tool({
|
||||
description: 'Issue a bind code for linking current channel identity to this account',
|
||||
inputSchema: z.object({
|
||||
ttl_seconds: z.number().int().positive().optional().describe('Bind code ttl in seconds'),
|
||||
}),
|
||||
execute: async ({ ttl_seconds }) => {
|
||||
if (!botId) {
|
||||
throw new Error('bot_id is required')
|
||||
}
|
||||
const response = await fetch(`/bots/${botId}/bind_codes`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ttl_seconds }),
|
||||
})
|
||||
return response.json()
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
'contact_search': contactSearch,
|
||||
'contact_card_me': contactCardMe,
|
||||
'contact_issue_bind_code': contactIssueBindCode,
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,7 @@ import { AuthFetcher } from '..'
|
||||
import { AgentAction, AgentAuthContext, BraveConfig, IdentityContext, ModelConfig } from '../types'
|
||||
import { ToolSet } from 'ai'
|
||||
import { getWebTools } from './web'
|
||||
import { getScheduleTools } from './schedule'
|
||||
import { getMemoryTools } from './memory'
|
||||
import { getSubagentTools } from './subagent'
|
||||
import { getContactTools } from './contact'
|
||||
import { getMessageTools } from './message'
|
||||
import { getSkillTools } from './skill'
|
||||
|
||||
export interface ToolsParams {
|
||||
@@ -27,26 +23,10 @@ export const getTools = (
|
||||
const webTools = getWebTools({ brave })
|
||||
Object.assign(tools, webTools)
|
||||
}
|
||||
if (actions.includes(AgentAction.Schedule)) {
|
||||
const scheduleTools = getScheduleTools({ fetch, identity })
|
||||
Object.assign(tools, scheduleTools)
|
||||
}
|
||||
if (actions.includes(AgentAction.Memory)) {
|
||||
const memoryTools = getMemoryTools({ fetch, identity })
|
||||
Object.assign(tools, memoryTools)
|
||||
}
|
||||
if (actions.includes(AgentAction.Subagent)) {
|
||||
const subagentTools = getSubagentTools({ fetch, model, brave, identity, auth })
|
||||
Object.assign(tools, subagentTools)
|
||||
}
|
||||
if (actions.includes(AgentAction.Contact)) {
|
||||
const contactTools = getContactTools({ fetch, identity })
|
||||
Object.assign(tools, contactTools)
|
||||
}
|
||||
if (actions.includes(AgentAction.Message)) {
|
||||
const messageTools = getMessageTools({ fetch, identity })
|
||||
Object.assign(tools, messageTools)
|
||||
}
|
||||
if (actions.includes(AgentAction.Skill)) {
|
||||
const skillTools = getSkillTools({ useSkill: enableSkill })
|
||||
Object.assign(tools, skillTools)
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import { tool } from 'ai'
|
||||
import { AuthFetcher } from '..'
|
||||
import type { IdentityContext } from '../types'
|
||||
import { z } from 'zod'
|
||||
|
||||
export type MemoryToolParams = {
|
||||
fetch: AuthFetcher
|
||||
identity: IdentityContext
|
||||
}
|
||||
|
||||
type MemorySearchItem = {
|
||||
id?: string
|
||||
memory?: string
|
||||
score?: number
|
||||
createdAt?: string
|
||||
metadata?: {
|
||||
source?: string
|
||||
}
|
||||
}
|
||||
|
||||
export const getMemoryTools = ({ fetch, identity }: MemoryToolParams) => {
|
||||
const searchMemory = tool({
|
||||
description: 'Search for memories',
|
||||
inputSchema: z.object({
|
||||
query: z.string().describe('The query to search for memories'),
|
||||
limit: z.number().int().positive().max(50).optional(),
|
||||
}),
|
||||
execute: async ({ query, limit }) => {
|
||||
const botId = identity.botId.trim()
|
||||
if (!botId) {
|
||||
throw new Error('botId is required to search memory')
|
||||
}
|
||||
const response = await fetch(`/bots/${botId}/memory/search`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
limit,
|
||||
}),
|
||||
})
|
||||
const data = await response.json()
|
||||
const results = Array.isArray(data?.results)
|
||||
? (data.results as MemorySearchItem[])
|
||||
: []
|
||||
const simplified = results.map((item) => ({
|
||||
id: item?.id,
|
||||
memory: item?.memory,
|
||||
score: item?.score,
|
||||
}))
|
||||
return {
|
||||
query,
|
||||
total: simplified.length,
|
||||
results: simplified,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
'search_memory': searchMemory,
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import { tool } from 'ai'
|
||||
import { z } from 'zod'
|
||||
import { AuthFetcher } from '..'
|
||||
import type { IdentityContext } from '../types'
|
||||
|
||||
export type MessageToolParams = {
|
||||
fetch: AuthFetcher
|
||||
identity: IdentityContext
|
||||
}
|
||||
|
||||
const SendMessageSchema = z.object({
|
||||
bot_id: z.string().optional(),
|
||||
platform: z.string().optional(),
|
||||
target: z.string().optional(),
|
||||
channel_identity_id: z.string().optional(),
|
||||
to_user_id: z.string().optional(),
|
||||
message: z.string(),
|
||||
})
|
||||
|
||||
export const getMessageTools = ({ fetch, identity }: MessageToolParams) => {
|
||||
const sendMessage = tool({
|
||||
description: 'Send a message to a channel or session',
|
||||
inputSchema: SendMessageSchema,
|
||||
execute: async (payload) => {
|
||||
const botId = (payload.bot_id ?? identity.botId ?? '').trim()
|
||||
const platform = (payload.platform ?? identity.currentPlatform ?? '').trim()
|
||||
const replyTarget = (identity.replyTarget ?? '').trim()
|
||||
const target = (payload.target ?? replyTarget).trim()
|
||||
const channelIdentityID = (payload.channel_identity_id ?? payload.to_user_id ?? '').trim()
|
||||
if (!botId) {
|
||||
throw new Error('bot_id is required')
|
||||
}
|
||||
if (!platform) {
|
||||
throw new Error('platform is required')
|
||||
}
|
||||
// Prefer chat token when there is no explicit target identity.
|
||||
const useSessionToken = !!identity.sessionToken && !channelIdentityID
|
||||
if (!target && !channelIdentityID && !useSessionToken) {
|
||||
throw new Error('target or channel_identity_id is required')
|
||||
}
|
||||
console.log('[Tool] send_message', {
|
||||
botId,
|
||||
platform,
|
||||
target: target || undefined,
|
||||
channelIdentityID: channelIdentityID || undefined,
|
||||
replyTarget,
|
||||
useSessionToken,
|
||||
})
|
||||
const body: Record<string, unknown> = {
|
||||
message: {
|
||||
text: payload.message,
|
||||
},
|
||||
}
|
||||
if (target) {
|
||||
body.target = target
|
||||
}
|
||||
if (channelIdentityID) {
|
||||
body.channel_identity_id = channelIdentityID
|
||||
}
|
||||
const url = useSessionToken
|
||||
? `/bots/${botId}/channel/${platform}/send_chat`
|
||||
: `/bots/${botId}/channel/${platform}/send`
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
|
||||
if (useSessionToken && identity.sessionToken) {
|
||||
headers.Authorization = `Bearer ${identity.sessionToken}`
|
||||
}
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
const result = await response.json()
|
||||
return {
|
||||
...result,
|
||||
instruction: 'Message delivered successfully. You have completed your response. Please STOP now and do not call any more tools.',
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
'send_message': sendMessage,
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
import { tool } from 'ai'
|
||||
import { z } from 'zod'
|
||||
import { AuthFetcher } from '..'
|
||||
import type { IdentityContext } from '../types'
|
||||
|
||||
export type ScheduleToolParams = {
|
||||
fetch: AuthFetcher
|
||||
identity: IdentityContext
|
||||
}
|
||||
|
||||
const ScheduleSchema = z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
pattern: z.string(),
|
||||
max_calls: z.number().nullable().optional(),
|
||||
enabled: z.boolean(),
|
||||
command: z.string(),
|
||||
})
|
||||
|
||||
export const getScheduleTools = ({ fetch, identity }: ScheduleToolParams) => {
|
||||
const botId = identity.botId.trim()
|
||||
const base = `/bots/${botId}/schedule`
|
||||
|
||||
const listSchedules = tool({
|
||||
description: 'List schedules for current user',
|
||||
inputSchema: z.object({}),
|
||||
execute: async () => {
|
||||
const response = await fetch(base, { method: 'GET' })
|
||||
return response.json()
|
||||
},
|
||||
})
|
||||
|
||||
const getSchedule = tool({
|
||||
description: 'Get a schedule by id',
|
||||
inputSchema: z.object({
|
||||
id: z.string().describe('Schedule ID'),
|
||||
}),
|
||||
execute: async ({ id }) => {
|
||||
const response = await fetch(`${base}/${id}`, { method: 'GET' })
|
||||
return response.json()
|
||||
},
|
||||
})
|
||||
|
||||
const createSchedule = tool({
|
||||
description: 'Create a new schedule',
|
||||
inputSchema: z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
pattern: z.string(),
|
||||
max_calls: z.number().nullable().optional().default(null).describe('Max calls (optional, empty for unlimited)'),
|
||||
enabled: z.boolean().optional(),
|
||||
command: z.string(),
|
||||
}),
|
||||
execute: async (payload) => {
|
||||
const response = await fetch(base, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
return response.json()
|
||||
},
|
||||
})
|
||||
|
||||
const updateSchedule = tool({
|
||||
description: 'Update an existing schedule',
|
||||
inputSchema: ScheduleSchema.partial().extend({
|
||||
id: z.string(),
|
||||
}),
|
||||
execute: async (payload) => {
|
||||
const { id, ...body } = payload
|
||||
const response = await fetch(`${base}/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
return response.json()
|
||||
},
|
||||
})
|
||||
|
||||
const deleteSchedule = tool({
|
||||
description: 'Delete a schedule',
|
||||
inputSchema: z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
execute: async ({ id }) => {
|
||||
const response = await fetch(`${base}/${id}`, { method: 'DELETE' })
|
||||
return response.status === 204 ? { success: true } : response.json()
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
'schedule_list': listSchedules,
|
||||
'schedule_get': getSchedule,
|
||||
'schedule_create': createSchedule,
|
||||
'schedule_update': updateSchedule,
|
||||
'schedule_delete': deleteSchedule,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user