Merge branch 'main' into feat/skills

This commit is contained in:
Acbox
2026-01-31 20:21:35 +08:00
18 changed files with 2913 additions and 24 deletions
+84 -22
View File
@@ -1,27 +1,34 @@
import { generateText, ModelMessage, stepCountIs, streamText, TextStreamPart, ToolSet } from 'ai'
import { createChatGateway } from './gateway'
import { AgentSkill, ClientType, Schedule } from './types'
import { AgentSkill, BaseModelConfig, Schedule } from './types'
import { system, schedule } from './prompts'
import { AuthFetcher } from './index'
import { getScheduleTools } from './tools/schedule'
import { getWebTools } from './tools/web'
import { subagentSystem } from './prompts/subagent'
import { getSubagentTools } from './tools/subagent'
import { getSkillTools } from './tools/skill'
export interface AgentParams {
apiKey: string
baseUrl: string
model: string
clientType: ClientType
export enum AgentAction {
WebSearch = 'web_search',
Message = 'message',
Subagent = 'subagent',
Schedule = 'schedule',
Skill = 'skill',
}
export interface AgentParams extends BaseModelConfig {
locale?: Intl.LocalesArgument
language?: string
maxSteps?: number
maxContextLoadTime: number
maxContextLoadTime?: number
platforms?: string[]
currentPlatform?: string
braveApiKey?: string
braveBaseUrl?: string
skills?: AgentSkill[]
useSkills?: string[]
allowed?: AgentAction[]
}
export interface AgentInput {
@@ -45,26 +52,33 @@ export const createAgent = (
...params.useSkills?.map((name) => params.skills?.find((s) => s.name === name)
).filter((s) => s !== undefined) ?? [])
const allowedActions = params.allowed
?? Object.values(AgentAction)
const maxSteps = params.maxSteps ?? 50
const getTools = () => {
const scheduleTools = getScheduleTools({ fetch: fetcher })
const skillTools = getSkillTools({
skills: params.skills ?? [],
useSkill: (skill) => {
if (enabledSkills.some((s) => s.name === skill.name)) {
return
const tools: ToolSet = {}
if (allowedActions.includes(AgentAction.Skill)) {
const skillTools = getSkillTools({
skills: params.skills ?? [],
useSkill: (skill) => {
if (enabledSkills.some((s) => s.name === skill.name)) {
return
}
enabledSkills.push(skill)
}
enabledSkills.push(skill)
}
})
const tools: ToolSet = {
...scheduleTools,
...skillTools,
})
Object.assign(tools, skillTools)
}
// Add web search tools if Brave API key is provided
if (params.braveApiKey) {
if (allowedActions.includes(AgentAction.Schedule)) {
const scheduleTools = getScheduleTools({ fetch: fetcher })
Object.assign(tools, scheduleTools)
}
if (params.braveApiKey && allowedActions.includes(AgentAction.WebSearch)) {
const webTools = getWebTools({
braveApiKey: params.braveApiKey,
braveBaseUrl: params.braveBaseUrl,
@@ -72,6 +86,19 @@ export const createAgent = (
Object.assign(tools, webTools)
}
if (allowedActions.includes(AgentAction.Subagent)) {
const subagentTools = getSubagentTools({
fetch: fetcher,
apiKey: params.apiKey,
baseUrl: params.baseUrl,
model: params.model,
clientType: params.clientType,
braveApiKey: params.braveApiKey,
braveBaseUrl: params.braveBaseUrl,
})
Object.assign(tools, subagentTools)
}
return tools
}
@@ -80,7 +107,7 @@ export const createAgent = (
date: new Date(),
locale: params.locale,
language: params.language,
maxContextLoadTime: params.maxContextLoadTime,
maxContextLoadTime: params.maxContextLoadTime ?? 1550,
platforms: params.platforms ?? [],
currentPlatform: params.currentPlatform,
skills: params.skills ?? [],
@@ -116,6 +143,40 @@ export const createAgent = (
}
}
const askAsSubagent = async (
input: AgentInput,
options: {
name: string
description?: string
}
): Promise<AgentResult> => {
messages.push(...input.messages)
const user: ModelMessage = {
role: 'user',
content: input.query,
}
messages.push(user)
const { response } = await generateText({
model: gateway({
apiKey: params.apiKey,
baseURL: params.baseUrl,
})(params.model),
system: subagentSystem({ date: new Date(), name: options.name, description: options.description }),
stopWhen: stepCountIs(maxSteps),
messages,
prepareStep: () => {
return {
system: subagentSystem({ date: new Date(), name: options.name, description: options.description }),
}
},
tools: getTools(),
})
return {
messages: [user, ...response.messages],
skills: enabledSkills.map((s) => s.name),
}
}
async function* stream(input: AgentInput): AsyncGenerator<TextStreamPart<ToolSet>, AgentResult> {
messages.push(...input.messages)
const user: ModelMessage = {
@@ -186,5 +247,6 @@ export const createAgent = (
ask,
stream,
triggerSchedule,
askAsSubagent,
}
}
+21
View File
@@ -0,0 +1,21 @@
import { time } from './shared'
export interface SubagentParams {
date: Date
name: string
description?: string
}
export const subagentSystem = ({ date, name, description }: SubagentParams) => {
return `
---
${time({ date })}
name: ${name}
description: ${description}
---
You are a subagent, which is a specialized assistant for a specific task.
Your task is communicated with the master agent to complete a task.
`
}
+11
View File
@@ -59,6 +59,17 @@ Your abilities:
+ 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.
**Subagent**
When a task is large, you can create a Subagent to help you complete some tasks in order to save your own context.
- You can use ${quote('create_subagent')} to create a new subagent.
- You can use ${quote('list_subagents')} to list subagents you have created.
- You can use ${quote('delete_subagent')} to delete a subagent by id.
- You can use ${quote('query_subagent')} to ask a subagent to complete a task.
+ The ${quote('name')} is the name of the subagent to ask.
+ The ${quote('query')} is the prompt to ask the subagent to complete the task.
Before asking a subagent, you should first create a subagent if it does not exist.
**Skills**
There are ${skills.length} skills available, you can use ${quote('use_skill')} to use a skill.
+101
View File
@@ -0,0 +1,101 @@
import { tool } from 'ai'
import { z } from 'zod'
import { AgentAction, createAgent } from '../agent'
import { BaseModelConfig } from '../types'
import { AuthFetcher } from '..'
export interface SubagentToolParams extends BaseModelConfig {
fetch: AuthFetcher
braveApiKey?: string
braveBaseUrl?: string
}
export const getSubagentTools = ({ fetch, apiKey, baseUrl, model, clientType, braveApiKey, braveBaseUrl }: SubagentToolParams) => {
const listSubagents = tool({
description: 'List subagents for current user',
inputSchema: z.object({}),
execute: async () => {
const response = await fetch('/subagents', { method: 'GET' })
return response.json()
},
})
const createSubagent = tool({
description: 'Create a new subagent',
inputSchema: z.object({
name: z.string(),
description: z.string(),
}),
execute: async ({ name, description }) => {
const response = await fetch('/subagents', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, description }),
})
return response.json()
},
})
const deleteSubagent = tool({
description: 'Delete a subagent by id',
inputSchema: z.object({
id: z.string().describe('Subagent ID'),
}),
execute: async ({ id }) => {
const response = await fetch(`/subagents/${id}`, { method: 'DELETE' })
return response.status === 204 ? { success: true } : response.json()
},
})
const querySubagent = tool({
description: 'Query a subagent',
inputSchema: z.object({
name: z.string(),
query: z.string().describe('The prompt to ask the subagent to do.'),
}),
execute: async ({ name, query }) => {
const listResponse = await fetch('/subagents', { method: 'GET' })
const listPayload = await listResponse.json()
const items = Array.isArray(listPayload?.items) ? listPayload.items : []
const target = items.find((item: { name?: string }) => item?.name === name)
if (!target?.id) {
throw new Error(`subagent not found: ${name}`)
}
const contextResponse = await fetch(`/subagents/${target.id}/context`, { method: 'GET' })
const contextPayload = await contextResponse.json()
const contextMessages = Array.isArray(contextPayload?.messages) ? contextPayload.messages : []
const { askAsSubagent } = createAgent({
apiKey,
baseUrl,
model,
clientType,
braveApiKey,
braveBaseUrl,
allowed: [
AgentAction.WebSearch,
]
})
const result = await askAsSubagent({
messages: contextMessages,
query,
}, { name, description: target.description })
const updatedMessages = [...contextMessages, ...result.messages]
await fetch(`/subagents/${target.id}/context`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages: updatedMessages }),
})
return {
success: true,
result: result.messages[result.messages.length - 1].content,
}
},
})
return {
'list_subagents': listSubagents,
'create_subagent': createSubagent,
'delete_subagent': deleteSubagent,
'query_subagent': querySubagent,
}
}
+7
View File
@@ -4,6 +4,13 @@ export enum ClientType {
GOOGLE = 'google',
}
export interface BaseModelConfig {
apiKey: string
baseUrl: string
model: string
clientType: ClientType
}
export interface Schedule {
id: string
name: string