feat: typing defaultAgent

This commit is contained in:
lin onetwo 2025-04-17 21:23:54 +08:00
parent 02e5be68cc
commit c11f2a0cd6
6 changed files with 589 additions and 94 deletions

View file

@ -88,6 +88,7 @@
"winston-daily-rotate-file": "5.0.0",
"winston-transport": "4.9.0",
"wouter": "^3.6.0",
"zod": "^3.24.3",
"zustand": "^5.0.3",
"zx": "8.3.1"
},

119
pnpm-lock.yaml generated
View file

@ -15,19 +15,19 @@ importers:
dependencies:
'@ai-sdk/anthropic':
specifier: ^1.2.6
version: 1.2.6(zod@3.23.8)
version: 1.2.6(zod@3.24.3)
'@ai-sdk/deepseek':
specifier: ^0.2.6
version: 0.2.6(zod@3.23.8)
version: 0.2.6(zod@3.24.3)
'@ai-sdk/openai':
specifier: ^1.3.7
version: 1.3.7(zod@3.23.8)
version: 1.3.7(zod@3.24.3)
'@ai-sdk/openai-compatible':
specifier: ^0.2.6
version: 0.2.6(zod@3.23.8)
version: 0.2.6(zod@3.24.3)
ai:
specifier: ^4.3.2
version: 4.3.2(react@19.0.0)(zod@3.23.8)
version: 4.3.2(react@19.0.0)(zod@3.24.3)
ansi-to-html:
specifier: ^0.7.2
version: 0.7.2
@ -126,7 +126,7 @@ importers:
version: 3.3.2
ollama-ai-provider:
specifier: ^1.2.0
version: 1.2.0(zod@3.23.8)
version: 1.2.0(zod@3.24.3)
reablocks:
specifier: ^9.0.1
version: 9.0.1(@emotion/is-prop-valid@1.2.2)(@types/react@19.0.8)(prop-types@15.8.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@ -184,6 +184,9 @@ importers:
wouter:
specifier: ^3.6.0
version: 3.6.0(react@19.0.0)
zod:
specifier: ^3.24.3
version: 3.24.3
zustand:
specifier: ^5.0.3
version: 5.0.3(@types/react@19.0.8)(immer@10.1.1)(react@19.0.0)(use-sync-external-store@1.5.0(react@19.0.0))
@ -7383,8 +7386,8 @@ packages:
peerDependencies:
zod: ^3.24.1
zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
zod@3.24.3:
resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==}
zustand@5.0.3:
resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
@ -7415,58 +7418,58 @@ snapshots:
'@aashutoshrathi/word-wrap@1.2.6': {}
'@ai-sdk/anthropic@1.2.6(zod@3.23.8)':
'@ai-sdk/anthropic@1.2.6(zod@3.24.3)':
dependencies:
'@ai-sdk/provider': 1.1.0
'@ai-sdk/provider-utils': 2.2.4(zod@3.23.8)
zod: 3.23.8
'@ai-sdk/provider-utils': 2.2.4(zod@3.24.3)
zod: 3.24.3
'@ai-sdk/deepseek@0.2.6(zod@3.23.8)':
'@ai-sdk/deepseek@0.2.6(zod@3.24.3)':
dependencies:
'@ai-sdk/openai-compatible': 0.2.6(zod@3.23.8)
'@ai-sdk/openai-compatible': 0.2.6(zod@3.24.3)
'@ai-sdk/provider': 1.1.0
'@ai-sdk/provider-utils': 2.2.4(zod@3.23.8)
zod: 3.23.8
'@ai-sdk/provider-utils': 2.2.4(zod@3.24.3)
zod: 3.24.3
'@ai-sdk/openai-compatible@0.2.6(zod@3.23.8)':
'@ai-sdk/openai-compatible@0.2.6(zod@3.24.3)':
dependencies:
'@ai-sdk/provider': 1.1.0
'@ai-sdk/provider-utils': 2.2.4(zod@3.23.8)
zod: 3.23.8
'@ai-sdk/provider-utils': 2.2.4(zod@3.24.3)
zod: 3.24.3
'@ai-sdk/openai@1.3.7(zod@3.23.8)':
'@ai-sdk/openai@1.3.7(zod@3.24.3)':
dependencies:
'@ai-sdk/provider': 1.1.0
'@ai-sdk/provider-utils': 2.2.4(zod@3.23.8)
zod: 3.23.8
'@ai-sdk/provider-utils': 2.2.4(zod@3.24.3)
zod: 3.24.3
'@ai-sdk/provider-utils@2.2.4(zod@3.23.8)':
'@ai-sdk/provider-utils@2.2.4(zod@3.24.3)':
dependencies:
'@ai-sdk/provider': 1.1.0
nanoid: 3.3.11
secure-json-parse: 2.7.0
zod: 3.23.8
zod: 3.24.3
'@ai-sdk/provider@1.1.0':
dependencies:
json-schema: 0.4.0
'@ai-sdk/react@1.2.6(react@19.0.0)(zod@3.23.8)':
'@ai-sdk/react@1.2.6(react@19.0.0)(zod@3.24.3)':
dependencies:
'@ai-sdk/provider-utils': 2.2.4(zod@3.23.8)
'@ai-sdk/ui-utils': 1.2.5(zod@3.23.8)
'@ai-sdk/provider-utils': 2.2.4(zod@3.24.3)
'@ai-sdk/ui-utils': 1.2.5(zod@3.24.3)
react: 19.0.0
swr: 2.3.3(react@19.0.0)
throttleit: 2.1.0
optionalDependencies:
zod: 3.23.8
zod: 3.24.3
'@ai-sdk/ui-utils@1.2.5(zod@3.23.8)':
'@ai-sdk/ui-utils@1.2.5(zod@3.24.3)':
dependencies:
'@ai-sdk/provider': 1.1.0
'@ai-sdk/provider-utils': 2.2.4(zod@3.23.8)
zod: 3.23.8
zod-to-json-schema: 3.24.5(zod@3.23.8)
'@ai-sdk/provider-utils': 2.2.4(zod@3.24.3)
zod: 3.24.3
zod-to-json-schema: 3.24.5(zod@3.24.3)
'@aws-crypto/sha256-browser@5.2.0':
dependencies:
@ -8525,7 +8528,7 @@ snapshots:
dependencies:
'@jimp/types': 1.6.0
'@jimp/utils': 1.6.0
zod: 3.23.8
zod: 3.24.3
'@jimp/plugin-blur@1.6.0':
dependencies:
@ -8535,7 +8538,7 @@ snapshots:
'@jimp/plugin-circle@1.6.0':
dependencies:
'@jimp/types': 1.6.0
zod: 3.23.8
zod: 3.24.3
'@jimp/plugin-color@1.6.0':
dependencies:
@ -8543,7 +8546,7 @@ snapshots:
'@jimp/types': 1.6.0
'@jimp/utils': 1.6.0
tinycolor2: 1.6.0
zod: 3.23.8
zod: 3.24.3
'@jimp/plugin-contain@1.6.0':
dependencies:
@ -8552,7 +8555,7 @@ snapshots:
'@jimp/plugin-resize': 1.6.0
'@jimp/types': 1.6.0
'@jimp/utils': 1.6.0
zod: 3.23.8
zod: 3.24.3
'@jimp/plugin-cover@1.6.0':
dependencies:
@ -8560,20 +8563,20 @@ snapshots:
'@jimp/plugin-crop': 1.6.0
'@jimp/plugin-resize': 1.6.0
'@jimp/types': 1.6.0
zod: 3.23.8
zod: 3.24.3
'@jimp/plugin-crop@1.6.0':
dependencies:
'@jimp/core': 1.6.0
'@jimp/types': 1.6.0
'@jimp/utils': 1.6.0
zod: 3.23.8
zod: 3.24.3
'@jimp/plugin-displace@1.6.0':
dependencies:
'@jimp/types': 1.6.0
'@jimp/utils': 1.6.0
zod: 3.23.8
zod: 3.24.3
'@jimp/plugin-dither@1.6.0':
dependencies:
@ -8583,12 +8586,12 @@ snapshots:
dependencies:
'@jimp/types': 1.6.0
'@jimp/utils': 1.6.0
zod: 3.23.8
zod: 3.24.3
'@jimp/plugin-flip@1.6.0':
dependencies:
'@jimp/types': 1.6.0
zod: 3.23.8
zod: 3.24.3
'@jimp/plugin-hash@1.6.0':
dependencies:
@ -8606,7 +8609,7 @@ snapshots:
'@jimp/plugin-mask@1.6.0':
dependencies:
'@jimp/types': 1.6.0
zod: 3.23.8
zod: 3.24.3
'@jimp/plugin-print@1.6.0':
dependencies:
@ -8619,18 +8622,18 @@ snapshots:
parse-bmfont-binary: 1.0.6
parse-bmfont-xml: 1.1.6
simple-xml-to-json: 1.2.3
zod: 3.23.8
zod: 3.24.3
'@jimp/plugin-quantize@1.6.0':
dependencies:
image-q: 4.0.0
zod: 3.23.8
zod: 3.24.3
'@jimp/plugin-resize@1.6.0':
dependencies:
'@jimp/core': 1.6.0
'@jimp/types': 1.6.0
zod: 3.23.8
zod: 3.24.3
'@jimp/plugin-rotate@1.6.0':
dependencies:
@ -8639,7 +8642,7 @@ snapshots:
'@jimp/plugin-resize': 1.6.0
'@jimp/types': 1.6.0
'@jimp/utils': 1.6.0
zod: 3.23.8
zod: 3.24.3
'@jimp/plugin-threshold@1.6.0':
dependencies:
@ -8648,11 +8651,11 @@ snapshots:
'@jimp/plugin-hash': 1.6.0
'@jimp/types': 1.6.0
'@jimp/utils': 1.6.0
zod: 3.23.8
zod: 3.24.3
'@jimp/types@1.6.0':
dependencies:
zod: 3.23.8
zod: 3.24.3
'@jimp/utils@1.6.0':
dependencies:
@ -9752,15 +9755,15 @@ snapshots:
clean-stack: 2.2.0
indent-string: 4.0.0
ai@4.3.2(react@19.0.0)(zod@3.23.8):
ai@4.3.2(react@19.0.0)(zod@3.24.3):
dependencies:
'@ai-sdk/provider': 1.1.0
'@ai-sdk/provider-utils': 2.2.4(zod@3.23.8)
'@ai-sdk/react': 1.2.6(react@19.0.0)(zod@3.23.8)
'@ai-sdk/ui-utils': 1.2.5(zod@3.23.8)
'@ai-sdk/provider-utils': 2.2.4(zod@3.24.3)
'@ai-sdk/react': 1.2.6(react@19.0.0)(zod@3.24.3)
'@ai-sdk/ui-utils': 1.2.5(zod@3.24.3)
'@opentelemetry/api': 1.9.0
jsondiffpatch: 0.6.0
zod: 3.23.8
zod: 3.24.3
optionalDependencies:
react: 19.0.0
@ -13291,13 +13294,13 @@ snapshots:
obuf@1.1.2: {}
ollama-ai-provider@1.2.0(zod@3.23.8):
ollama-ai-provider@1.2.0(zod@3.24.3):
dependencies:
'@ai-sdk/provider': 1.1.0
'@ai-sdk/provider-utils': 2.2.4(zod@3.23.8)
'@ai-sdk/provider-utils': 2.2.4(zod@3.24.3)
partial-json: 0.1.7
optionalDependencies:
zod: 3.23.8
zod: 3.24.3
omggif@1.0.10: {}
@ -15384,11 +15387,11 @@ snapshots:
toposort: 2.0.2
type-fest: 2.19.0
zod-to-json-schema@3.24.5(zod@3.23.8):
zod-to-json-schema@3.24.5(zod@3.24.3):
dependencies:
zod: 3.23.8
zod: 3.24.3
zod@3.23.8: {}
zod@3.24.3: {}
zustand@5.0.3(@types/react@19.0.8)(immer@10.1.1)(react@19.0.0)(use-sync-external-store@1.5.0(react@19.0.0)):
optionalDependencies:

View file

@ -0,0 +1,265 @@
import { z } from 'zod';
/**
*
*/
export interface IPromptPart {
id: string;
text?: string;
tags?: string[];
caption?: string;
content?: string;
name?: string;
children?: IPromptPart[];
}
/**
* Schema for a prompt part in the agent configuration
*/
// 使用前置声明解决递归类型引用问题,并使用具体的接口而不是 any
const PromptPartSchema: z.ZodType<IPromptPart> = z.lazy(() =>
z.object({
id: z.string().describe('唯一标识符'),
text: z.string().optional().describe('提示词文本内容'),
tags: z.array(z.string()).optional().describe('标签列表,用于分类和引用'),
caption: z.string().optional().describe('提示词的简短描述'),
content: z.string().optional().describe('提示词内容,通常用于动态修改的情况'),
name: z.string().optional().describe('名称,用于特定场景的引用'),
children: z.array(PromptPartSchema).optional().describe('子提示词列表')
}).describe('表示提示词的一部分,可以是文本或嵌套结构')
);
/**
*
*/
export const PromptSchema = z.object({
id: z.string().describe('提示词配置的唯一标识符'),
caption: z.string().describe('简短描述'),
enabled: z.boolean().optional().default(true).describe('是否启用'),
promptType: z.enum(['system', 'user']).optional().describe('提示词类型'),
tags: z.array(z.string()).optional().describe('标签列表'),
text: z.string().optional().describe('提示词内容'),
children: z.array(PromptPartSchema).optional().describe('子提示词列表'),
}).describe('完整的提示词配置,包含类型和内容');
/**
*
*/
export const ModelParametersSchema = z.object({
temperature: z.number().min(0).max(2).optional().describe('控制输出随机性,越高越随机'),
frequency_penalty: z.number().optional().describe('频率惩罚,避免重复'),
presence_penalty: z.number().optional().describe('存在惩罚,鼓励多样性'),
top_p: z.number().optional().describe('Top-p 采样'),
top_k: z.number().optional().describe('Top-k 采样'),
top_a: z.number().optional().describe('Top-a 采样'),
min_p: z.number().optional().describe('Min-p 采样'),
repetition_penalty: z.number().optional().describe('重复惩罚'),
max_context: z.number().optional().describe('最大上下文长度'),
max_tokens: z.number().optional().describe('最大生成 token 数'),
stream: z.boolean().optional().default(true).describe('是否使用流式输出'),
function_calling: z.boolean().optional().describe('是否支持函数调用'),
show_thoughts: z.boolean().optional().describe('是否展示思考过程'),
reasoning_effort: z.enum(['low', 'medium', 'high']).optional().describe('推理努力程度'),
seed: z.number().optional().describe('随机种子,-1 表示随机'),
}).describe('模型生成参数配置');
/**
* Wiki
*/
const WikiParameterSchema = z.object({
workspaceName: z.string().describe('工作区名称'),
filter: z.string().describe('筛选条件'),
}).describe('Wiki 参数配置');
/**
*
*/
const TriggerSchema = z.object({
search: z.string().optional().describe('搜索关键词'),
randomChance: z.number().min(0).max(1).optional().describe('随机触发概率'),
filter: z.string().optional().describe('筛选条件'),
model: z.object({
preset: z.string().optional().describe('预设模型'),
system: z.string().optional().describe('系统提示词'),
user: z.string().optional().describe('用户提示词'),
}).optional().describe('基于模型判断的触发条件'),
}).describe('触发条件配置');
/**
*
*/
const PositionParameterSchema = z.object({
position: z.enum(['relative', 'absolute', 'before', 'after']).describe('位置类型'),
targetId: z.string().describe('目标元素ID'),
bottom: z.number().optional().describe('底部偏移'),
}).describe('位置参数配置');
/**
*
*/
const FullReplacementParameterSchema = z.object({
targetId: z.string().describe('目标元素ID'),
sourceType: z.enum(['historyOfSession', 'llmResponse']).describe('源类型'),
}).describe('完全替换参数配置');
/**
*
*/
const DynamicPositionParameterSchema = PositionParameterSchema.extend({}).describe('动态位置参数配置');
/**
*
*/
const RetrievalAugmentedGenerationParameterSchema = PositionParameterSchema.extend({
sourceType: z.enum(['wiki']).describe('源类型'),
wikiParam: WikiParameterSchema.optional().describe('Wiki 参数'),
trigger: TriggerSchema.optional().describe('触发条件'),
removal: z.object({
expireAfterChatRound: z.number().optional().describe('多少轮对话后过期'),
coolDownChatRoundAfterLastShown: z.number().optional().describe('上次展示后冷却轮数'),
}).optional().describe('移除条件'),
}).describe('检索增强生成参数配置');
/**
*
*/
const FunctionParameterSchema = PositionParameterSchema.extend({
functionId: z.string().describe('函数ID'),
timeoutSecond: z.number().optional().describe('超时时间(秒)'),
timeoutMessage: z.string().optional().describe('超时消息'),
trigger: TriggerSchema.optional().describe('触发条件'),
}).describe('函数参数配置');
/**
* JavaScript
*/
const JavascriptToolParameterSchema = PositionParameterSchema.extend({
uri: z.string().describe('JavaScript 工具 URI'),
}).describe('JavaScript 工具参数配置');
/**
*
*/
const ModelContextProtocolParameterSchema = PositionParameterSchema.extend({
id: z.string().describe('MCP 服务器 ID'),
timeoutSecond: z.number().optional().describe('超时时间(秒)'),
timeoutMessage: z.string().optional().describe('超时消息'),
responseProcessing: z.object({
id: z.array(z.string()).describe('响应处理器 ID'),
}).optional().describe('响应处理配置'),
trigger: TriggerSchema.optional().describe('触发条件'),
}).describe('模型上下文协议参数配置');
/**
*
*/
const ToolCallingParameterSchema = z.object({
targetId: z.string().describe('目标元素ID'),
match: z.string().describe('匹配模式'),
}).describe('工具调用参数配置');
/**
*
*/
const AutoRerollParameterSchema = z.object({
targetId: z.string().describe('目标元素ID'),
search: z.string().describe('搜索关键词'),
maxRetry: z.number().describe('最大重试次数'),
}).describe('自动重新生成参数配置');
/**
*
*/
const AutoReplyParameterSchema = z.object({
targetId: z.string().describe('目标元素ID'),
text: z.string().describe('回复文本'),
trigger: TriggerSchema.describe('触发条件'),
maxAutoReply: z.number().describe('最大自动回复次数'),
}).describe('自动回复参数配置');
/**
*
*/
export const PromptDynamicModificationSchema = z.object({
id: z.string().describe('唯一标识符'),
caption: z.string().describe('简短描述'),
description: z.string().optional().describe('详细描述'),
content: z.string().optional().describe('内容'),
forbidOverrides: z.boolean().optional().default(false).describe('是否禁止覆盖'),
// 动态修改类型
dynamicModificationType: z.enum([
'fullReplacement',
'dynamicPosition',
'retrievalAugmentedGeneration',
'function',
'javascriptTool',
'modelContextProtocol',
]).describe('动态修改类型'),
// 各种参数配置
fullReplacementParam: FullReplacementParameterSchema.optional().describe('完全替换参数'),
dynamicPositionParam: DynamicPositionParameterSchema.optional().describe('动态位置参数'),
retrievalAugmentedGenerationParam: RetrievalAugmentedGenerationParameterSchema.optional().describe('检索增强生成参数'),
functionParam: FunctionParameterSchema.optional().describe('函数参数'),
javascriptToolParam: JavascriptToolParameterSchema.optional().describe('JavaScript 工具参数'),
modelContextProtocolParam: ModelContextProtocolParameterSchema.optional().describe('模型上下文协议参数'),
}).describe('提示词动态修改配置');
/**
*
*/
export const ResponseDynamicModificationSchema = z.object({
id: z.string().describe('唯一标识符'),
caption: z.string().optional().describe('简短描述'),
dynamicModificationType: z.enum(['fullReplacement']).optional().describe('动态修改类型'),
fullReplacementParam: FullReplacementParameterSchema.optional().describe('完全替换参数'),
forbidOverrides: z.boolean().optional().default(false).describe('是否禁止覆盖'),
// 响应处理类型
responseProcessingType: z.enum(['toolCalling', 'autoReroll']).optional().describe('响应处理类型'),
toolCallingParam: ToolCallingParameterSchema.optional().describe('工具调用参数'),
autoRerollParam: AutoRerollParameterSchema.optional().describe('自动重新生成参数'),
// 响应类型
responseType: z.enum(['autoReply']).optional().describe('响应类型'),
autoReplyParam: AutoReplyParameterSchema.optional().describe('自动回复参数'),
}).describe('响应动态修改配置');
/**
*
*/
export const ResponseSchema = z.object({
id: z.string().describe('唯一标识符'),
caption: z.string().describe('简短描述'),
}).describe('响应配置');
/**
*
*/
export const AgentSchema = z.object({
id: z.string().describe('代理唯一标识符'),
provider: z.string().describe('提供商'),
model: z.string().describe('使用的模型'),
modelParameters: ModelParametersSchema.describe('模型参数配置'),
prompts: z.array(PromptSchema).describe('提示词配置列表'),
promptDynamicModification: z.array(PromptDynamicModificationSchema).describe('提示词动态修改配置列表'),
response: z.array(ResponseSchema).describe('响应配置列表'),
responseDynamicModification: z.array(ResponseDynamicModificationSchema).describe('响应动态修改配置列表'),
}).describe('代理配置');
/**
*
*/
export const DefaultAgentsSchema = z.array(AgentSchema).describe('默认代理配置列表');
/**
*
*/
export type DefaultAgents = z.infer<typeof DefaultAgentsSchema>;
export type AgentPromptDescription = z.infer<typeof AgentSchema>;
export type ModelParameters = z.infer<typeof ModelParametersSchema>;
export type Prompt = z.infer<typeof PromptSchema>;
export type PromptDynamicModification = z.infer<typeof PromptDynamicModificationSchema>;
export type Response = z.infer<typeof ResponseSchema>;
export type ResponseDynamicModification = z.infer<typeof ResponseDynamicModificationSchema>;

View file

@ -82,9 +82,6 @@ export async function* echoHandler(context: TaskContext) {
{ text: `You said: ${userText}` },
];
// DEBUG: console response.errorDetail
console.log(`response.errorDetail`, response.errorDetail);
// If we have structured error details, add them as an error part
if (response.errorDetail) {
parts.push({

View file

@ -0,0 +1,218 @@
import { container } from '@services/container';
import { IExternalAPIService } from '@services/externalAPI/interface';
import serviceIdentifier from '@services/serviceIdentifier';
import * as fs from 'fs/promises';
import path from 'path';
import { TaskContext, TaskYieldUpdate } from '../server';
import * as schema from '../server/schema';
import { AgentPromptDescription, DefaultAgentsSchema } from './defaultAgentsSchema';
/**
*
* defaultAgents.json
*
* @param context -
*/
export async function* exampleAgentHandler(context: TaskContext) {
// 发送工作中状态
yield {
state: 'working',
message: {
role: 'agent',
parts: [{ text: '正在处理您的消息...' }],
},
} as TaskYieldUpdate;
// 获取 AI API 服务
const externalAPIService = container.get<IExternalAPIService>(serviceIdentifier.ExternalAPI);
// 检查是否被取消
if (context.isCancelled()) {
yield { state: 'canceled' } as TaskYieldUpdate;
return;
}
try {
// 读取默认代理配置文件
const agentsConfigPath = path.join(__dirname, '../defaultAgents.json');
const agentsConfigContent = await fs.readFile(agentsConfigPath, 'utf-8');
// 解析配置文件
const agentsConfig = DefaultAgentsSchema.parse(JSON.parse(agentsConfigContent));
// 查找示例代理配置
const exampleAgent = agentsConfig.find(agent => agent.id === 'example-agent');
if (!exampleAgent) {
throw new Error('找不到示例代理配置');
}
// 获取用户消息文本
const userText = (context.userMessage.parts as schema.TextPart[])
.filter((part) => part.text)
.map((part) => part.text)
.join(' ');
// 提供初步反馈
yield {
state: 'working',
message: {
role: 'agent',
parts: [{ text: `您说: ${userText}\n\n正在使用 ${exampleAgent.provider}${exampleAgent.model} 模型生成回复...` }],
},
} as TaskYieldUpdate;
// 构建提示词
const systemPrompt = generateSystemPrompt(exampleAgent);
// 获取 AI 配置
const aiConfig = await externalAPIService.getAIConfig({
provider: exampleAgent.provider,
model: exampleAgent.model,
modelParameters: {
temperature: exampleAgent.modelParameters.temperature,
maxTokens: exampleAgent.modelParameters.max_tokens,
topP: exampleAgent.modelParameters.top_p,
},
});
// 跟踪当前请求 ID 用于可能的取消
let currentRequestId: string | null = null;
try {
// 使用 AI 服务生成回复
for await (
const response of externalAPIService.generateFromAI(
[
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userText },
],
aiConfig,
)
) {
// 存储请求 ID 用于潜在的取消
if (!currentRequestId && response.requestId) {
currentRequestId = response.requestId;
}
// 检查取消
if (context.isCancelled()) {
if (currentRequestId) {
await externalAPIService.cancelAIRequest(currentRequestId);
}
yield { state: 'canceled' } as TaskYieldUpdate;
return;
}
// 处理不同的响应状态
if (response.status === 'update' || response.status === 'done') {
yield {
state: response.status === 'done' ? 'completed' : 'working',
message: {
role: 'agent',
parts: [{ text: response.content }],
},
} as TaskYieldUpdate;
} else if (response.status === 'error') {
// 处理错误响应
const parts: schema.Part[] = [
{ text: `遇到错误: ${response.errorDetail?.message || '未知错误'}` },
];
// 如果有结构化错误详情,添加它们
if (response.errorDetail) {
parts.push({
type: 'error',
error: {
name: response.errorDetail.name,
code: response.errorDetail.code,
provider: response.errorDetail.provider,
},
});
}
yield {
state: 'failed',
message: {
role: 'agent',
parts,
},
} as TaskYieldUpdate;
return;
}
}
} catch (error) {
// 处理未预期的错误
const errorMessage = error instanceof Error ? error.message : String(error);
yield {
state: 'failed',
message: {
role: 'agent',
parts: [{ text: `处理时遇到意外错误: ${errorMessage}` }],
},
} as TaskYieldUpdate;
} finally {
// 确保在需要时取消请求
if (context.isCancelled() && currentRequestId) {
await externalAPIService.cancelAIRequest(currentRequestId);
}
}
} catch (error) {
// 处理配置文件处理错误
const errorMessage = error instanceof Error ? error.message : String(error);
yield {
state: 'failed',
message: {
role: 'agent',
parts: [{ text: `配置处理错误: ${errorMessage}` }],
},
} as TaskYieldUpdate;
}
}
/**
*
*
* @param agent -
* @returns
*/
function generateSystemPrompt(agent: AgentPromptDescription): string {
// 查找系统提示词
const systemPrompt = agent.prompts.find(prompt => prompt.id === 'system');
if (!systemPrompt || !systemPrompt.children) {
return '你是一个AI助手请回答用户的问题。';
}
// 构建提示词
let finalPrompt = '';
// 添加主要提示词
const mainPrompt = systemPrompt.children.find(child => child.id === 'default-main');
if (mainPrompt && mainPrompt.text) {
finalPrompt = `${mainPrompt.text}\n\n`;
}
// 添加其他标记为 SystemPrompt 的提示词
systemPrompt.children
.filter(child => child.tags?.includes('SystemPrompt') && child.id !== 'default-main')
.forEach(prompt => {
if (prompt.text) {
finalPrompt = `${finalPrompt}${prompt.text}\n\n`;
}
});
// 添加工具提示词,如果存在
const toolsPrompt = systemPrompt.children.find(child => child.id === 'default-tools');
if (toolsPrompt && toolsPrompt.children) {
const beforeTool = toolsPrompt.children.find(child => child.id === 'default-before-tool');
const postTool = toolsPrompt.children.find(child => child.id === 'default-post-tool');
if (beforeTool && beforeTool.text && postTool && postTool.text) {
finalPrompt = `${finalPrompt}${beforeTool.text}\n\n${postTool.text}\n\n`;
}
}
return finalPrompt.trim();
}

View file

@ -1,56 +1,67 @@
import * as schema from './schema';
import { TaskStore } from './store';
// Import TaskStore
/**
* Update yielded by a session handler.
* Context object provided to the TaskHandler.
*/
export type SessionYieldUpdate =
| Omit<schema.TaskStatus, 'timestamp'>
| schema.Artifact;
// 保持与 A2A 协议兼容
export type TaskYieldUpdate = SessionYieldUpdate;
/**
* Context provided to session handlers for processing messages
*/
export interface SessionContext {
export interface TaskContext {
/**
* The current session object (kept as 'task' for handler API compatibility)
* The current state of the task when the handler is invoked or resumed.
* Note: This is a snapshot. For the absolute latest state during async operations,
* the handler might need to reload the task via the store.
*/
task: schema.Task;
/**
* The most recent message from the user
* The specific user message that triggered this handler invocation or resumption.
*/
userMessage: schema.Message;
/**
* Full history of the conversation
* Function to check if cancellation has been requested for this task.
* Handlers should ideally check this periodically during long-running operations.
* @returns {boolean} True if cancellation has been requested, false otherwise.
*/
history: schema.Message[];
isCancelled(): boolean;
/**
* Check if the session has been cancelled
* The message history associated with the task up to the point the handler is invoked.
* Optional, as history might not always be available or relevant.
*/
isCancelled: () => boolean;
history?: schema.Message[];
/**
* Optional session store for persistence (rarely needed by handlers)
*/
taskStore?: TaskStore;
// taskStore is removed as the server now handles loading/saving directly.
// If a handler specifically needs history, it would need to be passed differently
// or the handler pattern might need adjustment based on use case.
// Potential future additions:
// - logger instance
// - AbortSignal linked to cancellation
}
// Keep TaskContext type for backward compatibility
export type TaskContext = SessionContext;
/**
* Represents the possible types of updates a TaskHandler can yield.
* It's either a partial TaskStatus (without the server-managed timestamp)
* or a complete Artifact object.
*/
export type TaskYieldUpdate =
| Omit<schema.TaskStatus, 'timestamp'>
| schema.Artifact;
/**
* Session handler function type
* Takes a session context and returns an async generator of updates
* Defines the signature for a task handler function.
*
* Handlers are implemented as async generators. They receive context about the
* task and the triggering message. They can perform work and `yield` status
* or artifact updates (`TaskYieldUpdate`). The server consumes these yields,
* updates the task state in the store, and streams events if applicable.
*
* @param context - The TaskContext object containing task details, cancellation status, and store access.
* @yields {TaskYieldUpdate} - Updates to the task's status or artifacts.
* @returns {Promise<schema.Task | void>} - Optionally returns the final complete Task object
* (needed for non-streaming 'tasks/send'). If void is returned, the server uses the
* last known state from the store after processing all yields.
*/
export type SessionHandler = (
context: SessionContext
) => AsyncGenerator<SessionYieldUpdate>;
// Keep TaskHandler type for backward compatibility with A2A protocol
export type TaskHandler = SessionHandler;
export type TaskHandler = (
context: TaskContext,
) => AsyncGenerator<TaskYieldUpdate, schema.Task | void, unknown>;