From 5eeec638168d239b255a229bf9b919eafb51982c Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Wed, 12 Aug 2026 00:28:52 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20agent-invoke=20=E6=B5=81=E5=BC=8F?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 复用 agentMessages 表 + 新增来源字段 (source/a2aTaskId/callerAgentId/callerSessionId) - sendTask 从 generateText 改为 streamText + streamBuffer - 新增 A2A SSE 端点供前端侧边面板订阅 - 新增 AgentInvokePanel 侧边面板组件,只读流式展示 - 扩展 ToolContext/executeAgentTool 传递 sessionId Co-authored-by: CodeFree --- .codegraph/daemon.pid | 4 +- .../2026-08-12-agent-invoke-streaming-design.md | 575 ++++ packages/common/config/index.ts | 3 + packages/drizzle-pkg/db.sqlite | Bin 585728 -> 647168 bytes packages/drizzle-pkg/lib/schema/a2a.ts | 45 + .../drizzle-pkg/migrations/0004_public_dracula.sql | 21 + .../drizzle-pkg/migrations/meta/0004_snapshot.json | 2790 ++++++++++++++++++++ packages/drizzle-pkg/migrations/meta/_journal.json | 7 + server/api/agents/[agentSlug]/a2a.post.ts | 131 + server/api/agents/[agentSlug]/agent-card.get.ts | 23 + server/service/a2a/client.ts | 103 + server/service/a2a/converter.ts | 105 + server/service/a2a/index.ts | 4 + server/service/a2a/service.ts | 341 +++ server/service/a2a/types.ts | 206 ++ .../executors/agent-invoke/agent-invoke.ts | 4 +- server/service/agent/collaboration.ts | 117 +- 17 files changed, 4370 insertions(+), 109 deletions(-) create mode 100644 docs/superpowers/specs/2026-08-12-agent-invoke-streaming-design.md create mode 100644 packages/drizzle-pkg/lib/schema/a2a.ts create mode 100644 packages/drizzle-pkg/migrations/0004_public_dracula.sql create mode 100644 packages/drizzle-pkg/migrations/meta/0004_snapshot.json create mode 100644 server/api/agents/[agentSlug]/a2a.post.ts create mode 100644 server/api/agents/[agentSlug]/agent-card.get.ts create mode 100644 server/service/a2a/client.ts create mode 100644 server/service/a2a/converter.ts create mode 100644 server/service/a2a/index.ts create mode 100644 server/service/a2a/service.ts create mode 100644 server/service/a2a/types.ts diff --git a/.codegraph/daemon.pid b/.codegraph/daemon.pid index e214e67..8fa9340 100644 --- a/.codegraph/daemon.pid +++ b/.codegraph/daemon.pid @@ -1,6 +1,6 @@ { - "pid": 2002, + "pid": 2264, "version": "0.9.7", "socketPath": "/home/dash/code/nuxt-app/.codegraph/daemon.sock", - "startedAt": 1786289048581 + "startedAt": 1786457265175 } diff --git a/docs/superpowers/specs/2026-08-12-agent-invoke-streaming-design.md b/docs/superpowers/specs/2026-08-12-agent-invoke-streaming-design.md new file mode 100644 index 0000000..2056087 --- /dev/null +++ b/docs/superpowers/specs/2026-08-12-agent-invoke-streaming-design.md @@ -0,0 +1,575 @@ +# Agent-Invoke 流式会话设计 + +**日期**: 2026-08-12 +**状态**: Draft +**关联模块**: `server/service/a2a/`, `server/service/agent-tool/executors/agent-invoke/`, `app/components/agent/`, `app/composables/useAgentChat.ts` + +## 1. 背景与目标 + +### 1.1 当前状态 + +`agent-invoke` 工具允许一个 agent 通过 A2A 协议调用另一个 agent 处理子任务。当前实现存在以下问题: + +- `sendTask`(`server/service/a2a/service.ts:128`)使用 `generateText` 同步调用 LLM,**无流式输出** +- A2A 调用产生的对话消息**未保存**到 `agentMessages` 表,调用结束后无痕迹 +- 前端无法感知 A2A 调用过程,只在 tool call 完成后看到最终结果文本 +- `a2aTaskSessions` 表仅记录 task 元数据(callerContext、state),不记录对话消息 + +### 1.2 目标 + +1. **流式展示**:A2A 调用过程中,被调用 agent 的回复实时流式展示在前端侧边面板 +2. **数据持久化**:A2A 调用产生的 user/assistant 消息保存到 `agentMessages` 表,记录调用来源信息 +3. **会话隔离**:A2A 产生的会话不在前端会话列表展示(仅用于审计/日志) +4. **只读面板**:侧边面板只读展示,用户不能在面板中继续对话 + +### 1.3 非目标 + +- 不支持用户在侧边面板中与被调用 agent 继续对话 +- 不修改 A2A 协议的 JSON-RPC 接口(仅改内部 service 层) +- 不实现 A2A 调用的 streaming SSE 公开端点(仅内部订阅) + +## 2. 架构设计 + +### 2.1 数据流总览 + +``` +调用方前端 ← SSE ← 调用方 streamText (chat-engine) + └─ tool-call: agent-invoke (args: {agentSlug, input}) + └─ agentInvokeExecutor.execute() + └─ invokeAgentViaA2A() + └─ createTask() → 创建 session + a2aTaskSessions 记录 + ↓ 返回 taskId + └─ sendTask() → streamText (被调用 agent) + └─ onChunk → appendChunk(taskId, chunk) + └─ onFinish → saveMessage (source='a2a') + └─ 返回 { output, ok, taskId } + └─ 返回 ToolResult (metadata.taskId) + └─ 调用方 streamText 继续,tool-output-available + +前端侧: + tool-call (agent-invoke) 开始 + → useAgentChat 检测到 toolName=agent-invoke + → 自动打开 AgentInvokePanel + → AgentInvokePanel 通过 EventSource 订阅 /api/agents/a2a/stream?taskId=xxx + ← SSE chunks → 实时展示被调用 agent 回复 + tool-output-available + → 面板可关闭,展示完成状态 +``` + +### 2.2 关键设计决策 + +#### 决策 1:streamBuffer 以 taskId 为 key + +现有 `stream-buffer.ts` 以 `sessionId` 为 key。A2A 场景下,被调用 agent 的 session 对前端不可见,但 taskId 是调用方已知的(通过 tool call metadata 传递)。 + +**方案**:streamBuffer 支持以 `taskId` 为 key 创建独立 buffer,与 session buffer 隔离。新增 `createStreamBufferByKey(key, meta)` / `appendChunkByKey(key, chunk)` / `subscribeToBufferByKey(key, ...)` 等函数,或直接复用现有函数但传入 `a2a_${taskId}` 作为 key。 + +#### 决策 2:taskId 传递给前端 + +`agentInvokeExecutor.execute()` 是同步等待 `invokeAgentViaA2A` 返回的。但前端需要在 tool 执行**期间**就订阅 SSE。 + +**方案**:`invokeAgentViaA2A` 在 `createTask` 完成后、`sendTask` 开始前,通过调用方 streamBuffer 推送一个自定义 SSE 事件 `a2a-task-started`,包含 `{ taskId, calleeAgentSlug, calleeAgentName }`。前端在收到 `tool-call` chunk(toolName=agent-invoke)后,等待后续的 `a2a-task-started` 事件获取 taskId,然后打开面板订阅。 + +**替代方案(更简单)**:executor 在 execute 开始时立即创建 task(先 createTask 拿到 taskId),将 taskId 作为 tool call 的 `args` 的一部分通过 streamText 的 `tool-input-available` 事件传递给前端。但这需要改 executor 接口。 + +**最终选择**:通过调用方 streamBuffer 推送 `a2a-task-started` 事件。原因:不改 executor 接口,复用现有 streamBuffer 机制。 + +#### 决策 3:消息保存与来源记录 + +在 `agentMessages` 表新增字段记录来源: + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `source` | text | `'user'` | 消息来源:`'user'`(用户直接对话)\| `'a2a'`(A2A 调用) | +| `a2aTaskId` | text | null | A2A task ID,仅 a2a 消息有值 | +| `callerAgentId` | integer | null | 调用方 agent ID,references agents.id | +| `callerSessionId` | text | null | 调用方会话 ID | + +**会话列表过滤**:`listSessions` 查询时,排除只含 `source='a2a'` 消息的 session。实现方式:在 `a2aTaskSessions` 表新增 `visibleInSidebar` 字段(integer, default 0),`listSessions` 查询时 LEFT JOIN `a2aTaskSessions` 并过滤 `visibleInSidebar = 0` 的 session。 + +## 3. 详细设计 + +### 3.1 数据层变更 + +#### 3.1.1 Schema 变更 + +**文件**: `packages/drizzle-pkg/lib/schema/agent.ts` — `agentMessages` 表新增字段: + +```typescript +source: text("source", { length: 10 }).notNull().default("user"), +a2aTaskId: text("a2a_task_id", { length: 64 }), +callerAgentId: integer("caller_agent_id").references(() => agents.id, { onDelete: "set null" }), +callerSessionId: text("caller_session_id"), +``` + +新增索引:`idx_agent_messages_source` ON (source) + +**文件**: `packages/drizzle-pkg/lib/schema/a2a.ts` — `a2aTaskSessions` 表新增字段: + +```typescript +visibleInSidebar: integer("visible_in_sidebar").notNull().default(0), +``` + +#### 3.1.2 Migration + +使用 drizzle-kit 生成 migration: + +```bash +bun run db:generate +bun run db:migrate +``` + +#### 3.1.3 查询层变更 + +**`server/service/agent/session.ts` — `listSessions`**: + +查询条件增加:排除 `a2aTaskSessions.visibleInSidebar = 0` 的 session。 + +```sql +-- 原查询 +SELECT * FROM agent_sessions WHERE user_id = ? AND deleted_at IS NULL + +-- 新查询 +SELECT s.* FROM agent_sessions s +LEFT JOIN a2a_task_sessions a ON a.session_id = s.id +WHERE s.user_id = ? AND s.deleted_at IS NULL + AND (a.visible_in_sidebar = 1 OR a.id IS NULL) +``` + +### 3.2 后端流式改造 + +#### 3.2.1 `invokeAgentViaA2A` 改造 + +**文件**: `server/service/a2a/client.ts` + +在 `createTask` 完成后、`sendTask` 开始前,通过调用方 streamBuffer 推送 `a2a-task-started` 事件。 + +需要新增参数 `callerSessionId`(调用方会话 ID),用于找到调用方的 streamBuffer 并推送事件。 + +```typescript +export async function invokeAgentViaA2A( + invocation: AgentInvocation, + options?: { + maxRecursionDepth?: number; + timeoutMs?: number; + maxOutputTokens?: number; + callerSessionId?: string; // 新增 + }, +): Promise { + // ... createTask 后 + const task = await createTask(...); + + // 推送 a2a-task-started 事件到调用方 streamBuffer + if (options?.callerSessionId) { + const event = `data: ${JSON.stringify({ + type: "a2a-task-started", + taskId: task.id, + calleeAgentSlug: agentSlug, + calleeAgentName: agent.name, + })}\n\n`; + appendChunk(options.callerSessionId, new TextEncoder().encode(event)); + } + + // sendTask 改为流式 + const completedTask = await sendTask(task.id, a2aContext); + // ... +} +``` + +返回结果新增 `taskId`: + +```typescript +return { agentSlug, output, ok: true, taskId: task.id }; +``` + +#### 3.2.2 `sendTask` 改造 + +**文件**: `server/service/a2a/service.ts` + +从 `generateText` 改为 `streamText`,流式过程中: +1. 创建以 `a2a_${taskId}` 为 key 的 streamBuffer +2. `onChunk` 将文本增量编码为 SSE 格式写入 buffer +3. `onFinish` 保存 user/assistant 消息到 `agentMessages`(带来源字段) + +```typescript +export async function sendTask(taskId, context): Promise { + // ... 前置校验同原逻辑 + + // 创建 A2A streamBuffer + const a2aBufferKey = `a2a_${taskId}`; + createStreamBuffer(a2aBufferKey, { modelId: resolved.dbId }); + + // 保存 user 消息(source='a2a') + const userSortOrder = (await getMaxSortOrder(taskRow.sessionId)) + 1; + await saveMessage({ + sessionId: taskRow.sessionId, + role: "user", + content: inputText, + sortOrder: userSortOrder, + // 新增字段 + source: "a2a", + a2aTaskId: taskId, + callerAgentId: context.callerAgentId, + callerSessionId: context.callerSessionId, // 需在 A2AInvocationContext 新增 + }); + + const result = streamText({ + model: languageModel, + system: agent.systemPrompt, + prompt: inputText, + tools, + stopWhen: stepCountIs(maxSteps), + maxOutputTokens, + abortSignal: combinedSignal, + onChunk: ({ chunk }) => { + if (chunk.type === "text-delta") { + const sseChunk = `data: ${JSON.stringify({ + type: "text-delta", + textDelta: chunk.text, + })}\n\n`; + appendChunk(a2aBufferKey, new TextEncoder().encode(sseChunk)); + } + }, + onFinish: async ({ text, usage }) => { + // 保存 assistant 消息 + const assistantSortOrder = (await getMaxSortOrder(taskRow.sessionId)) + 1; + await saveMessage({ + sessionId: taskRow.sessionId, + role: "assistant", + content: text ?? "", + modelId: resolved.dbId, + inputTokens: usage?.inputTokens ?? null, + outputTokens: usage?.outputTokens ?? null, + sortOrder: assistantSortOrder, + source: "a2a", + a2aTaskId: taskId, + callerAgentId: context.callerAgentId, + callerSessionId: context.callerSessionId, + }); + markBufferDone(a2aBufferKey); + await updateTaskState(taskId, "completed"); + }, + }); + + // 等待流式完成 + await result.text; + + const updatedRow = await getTaskRow(taskId); + return rowToTask(updatedRow!, textToA2AMessage(output, "agent")); +} +``` + +#### 3.2.3 `A2AInvocationContext` 类型扩展 + +**文件**: `server/service/a2a/types.ts` + +```typescript +export interface A2AInvocationContext { + // ... 现有字段 + callerSessionId?: string; // 新增:调用方会话 ID +} +``` + +#### 3.2.4 `agentInvokeExecutor` 改造 + +**文件**: `server/service/agent-tool/executors/agent-invoke/agent-invoke.ts` + +`execute` 方法传入 `callerSessionId`: + +```typescript +async execute( + input: unknown, + config: AgentInvokeToolConfig, + ctx: { userId: number | null; agentId?: number | null; recursionDepth?: number; sessionId?: string }, +): Promise { + // ... 现有逻辑 + const result = await invokeAgentViaA2A( + { + agentSlug: data.agentSlug, + input: data.input, + context: { + userId: ctx.userId, + callerAgentId: ctx.agentId ?? null, + recursionDepth: currentDepth + 1, + }, + }, + { + maxRecursionDepth: config.maxRecursionDepth, + timeoutMs: config.timeoutMs, + maxOutputTokens: config.maxOutputTokens, + callerSessionId: ctx.sessionId, // 新增 + }, + ); + + return { + success: true, + data: output, + metadata: { + durationMs: Date.now() - start, + agentSlug: data.agentSlug, + recursionDepth: currentDepth + 1, + taskId: result.taskId, // 新增 + }, + }; +} +``` + +**已确认**:executor 的 `ctx`(`ToolContext`)当前**不包含** `sessionId`。需要扩展以下接口: + +1. `ToolContext`(`server/service/agent-tool/registry.ts:11`)新增 `sessionId?: string` +2. `executeAgentTool`(`server/service/agent-tool/index.ts:302`)新增 `sessionId?: string` 参数,传入 ctx +3. `getAgentToolsForChatByAgentId`(`server/service/agent-tool/index.ts:546`)新增 `sessionId?: string` 参数,传递给内部 `executeAgentTool` 调用 +4. `chat-engine.ts` 调用 `getAgentToolsForChatByAgentId` 时传入 `sessionId`(`chat-engine.ts` 已有 `sessionId` 变量,line 191) + +#### 3.2.5 新增 SSE 端点 + +**文件**: `server/api/agents/a2a/stream.get.ts` + +```typescript +export default defineEventHandler(async (event) => { + const query = getQuery(event); + const taskId = query.taskId as string; + + if (!taskId) { + throw createError({ statusCode: 400, statusMessage: "缺少 taskId" }); + } + + // 鉴权:验证调用方有权访问该 task + // (通过 a2aTaskSessions 查询 callerAgentId / callerSessionId 匹配当前用户) + + const a2aBufferKey = `a2a_${taskId}`; + const buf = getStreamBuffer(a2aBufferKey); + + if (!buf) { + return R.success({ active: false, reason: "no-buffer" }); + } + + // 复用 stream.get.ts 的 ReadableStream 模式 + const stream = new ReadableStream({ + start(controller) { + for (const chunk of buf.chunks) { + controller.enqueue(chunk); + } + if (buf.done) { + controller.close(); + return; + } + const unsubscribe = subscribeToBuffer(a2aBufferKey, ...); + event.node.req.on("close", () => { unsubscribe(); }); + }, + }); + + return new Response(stream, { + headers: { + "Content-Type": "text/event-stream; charset=utf-8", + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "X-Stream-Resume": "true", + }, + }); +}); +``` + +### 3.3 前端变更 + +#### 3.3.1 新增 `AgentInvokePanel` 组件 + +**文件**: `app/components/agent/AgentInvokePanel.vue` + +```vue + + + + +``` + +#### 3.3.2 `useAgentChat.ts` 改造 + +在处理 `tool-call` chunk 时,检测 `toolName === 'agent-invoke'`,自动打开面板。 + +新增响应式状态: + +```typescript +const invokePanelState = ref<{ + visible: boolean; + taskId: string; + calleeAgentSlug: string; + calleeAgentName: string; +} | null>(null); +``` + +在 chunk 处理逻辑中: + +```typescript +case "tool-input-available": { + // ... 现有逻辑 + + // 检测 agent-invoke 工具调用 + if (chunk.toolName === "agent-invoke") { + // 解析 args 获取 calleeAgentSlug + const args = typeof chunk.args === "string" ? JSON.parse(chunk.args) : chunk.args; + // 先展示面板(taskId 暂空,等待 a2a-task-started 事件) + invokePanelState.value = { + visible: true, + taskId: "", + calleeAgentSlug: args?.agentSlug ?? "", + calleeAgentName: args?.agentSlug ?? "", + }; + } + break; +} + +case "a2a-task-started": { + // 更新面板的 taskId,开始 SSE 订阅 + if (invokePanelState.value) { + invokePanelState.value.taskId = chunk.taskId; + invokePanelState.value.calleeAgentName = chunk.calleeAgentName; + } + break; +} + +case "tool-output-available": { + // ... 现有逻辑 + // agent-invoke 完成后,面板标记为完成(但不自动关闭,用户手动关闭) +} +``` + +#### 3.3.3 `AgentChatArea.vue` 改造 + +挂载 `AgentInvokePanel`: + +```vue + +``` + +### 3.4 错误处理 + +| 场景 | 处理 | +|------|------| +| A2A 调用超时 | streamBuffer 推送 error 事件,面板展示错误,executor 返回失败 | +| 被调用 agent 不存在/不可调用 | executor 返回失败(现有逻辑),面板不打开 | +| SSE 连接中断 | 面板展示"连接中断",用户可手动关闭 | +| 前端刷新 | streamBuffer 支持 resume(复用现有 stream.get.ts 模式),但 A2A 场景下 taskId 丢失,面板不恢复(可接受) | +| 递归深度超限 | executor 返回失败(现有逻辑),面板不打开 | + +### 3.5 测试策略 + +#### 3.5.1 单元测试 + +- `sendTask` 流式输出:mock streamText,验证 onChunk 写入 buffer、onFinish 保存消息 +- `invokeAgentViaA2A` 推送 `a2a-task-started` 事件:验证 appendChunk 被调用 +- `listSessions` 过滤 A2A 会话:插入 A2A 消息,验证会话列表不包含 + +#### 3.5.2 E2E 测试 + +- 配置两个 agent(通用助手 + 编程助手),通用助手配置 agent-invoke 工具 +- 发送消息触发 agent-invoke,验证侧边面板自动打开并展示流式回复 +- 验证 A2A 消息保存到数据库(source='a2a') +- 验证会话列表不展示 A2A 会话 + +## 4. 影响范围 + +### 4.1 修改文件清单 + +| 文件 | 变更类型 | 说明 | +|------|----------|------| +| `packages/drizzle-pkg/lib/schema/agent.ts` | 修改 | agentMessages 新增 source/a2aTaskId/callerAgentId/callerSessionId | +| `packages/drizzle-pkg/lib/schema/a2a.ts` | 修改 | a2aTaskSessions 新增 visibleInSidebar | +| `server/service/agent-tool/registry.ts` | 修改 | ToolContext 新增 sessionId 字段 | +| `server/service/agent-tool/index.ts` | 修改 | executeAgentTool + getAgentToolsForChatByAgentId 新增 sessionId 参数 | +| `server/service/a2a/types.ts` | 修改 | A2AInvocationContext 新增 callerSessionId | +| `server/service/a2a/client.ts` | 修改 | invokeAgentViaA2A 推送 a2a-task-started 事件,返回 taskId | +| `server/service/a2a/service.ts` | 修改 | sendTask 改为 streamText,保存消息 | +| `server/service/agent-tool/executors/agent-invoke/agent-invoke.ts` | 修改 | execute 传入 callerSessionId,metadata 返回 taskId | +| `server/service/agent/session.ts` | 修改 | listSessions 过滤 A2A 会话 | +| `server/service/agent/chat-engine.ts` | 修改 | 调用 getAgentToolsForChatByAgentId 时传入 sessionId | +| `server/api/agents/a2a/stream.get.ts` | 新增 | A2A SSE 端点 | +| `app/components/agent/AgentInvokePanel.vue` | 新增 | 侧边面板组件 | +| `app/composables/useAgentChat.ts` | 修改 | 检测 agent-invoke tool call,管理面板状态 | +| `app/components/agent/AgentChatArea.vue` | 修改 | 挂载 AgentInvokePanel | + +### 4.2 需要确认的依赖 + +- ~~executor 的 `ctx` 是否已包含 `sessionId`?~~ **已确认不包含**,需扩展 `ToolContext`、`executeAgentTool`、`getAgentToolsForChatByAgentId` 接口(见 3.2.4) +- `AgentInvocationResult` 类型需要新增 `taskId` 字段 + +## 5. 开放问题 + +1. ~~**executor ctx.sessionId**~~:**已解决** — 需扩展 `ToolContext`、`executeAgentTool`、`getAgentToolsForChatByAgentId` 接口。 +2. **streamBuffer key 命名**:使用 `a2a_${taskId}` 作为 key,与现有 sessionId 格式 `as_xxx` 不冲突。 +3. **并发 A2A 调用**:同一调用方同时发起多个 agent-invoke(多步 tool call),面板如何处理?建议只展示最后一个,或支持多个面板堆叠(后续迭代)。 diff --git a/packages/common/config/index.ts b/packages/common/config/index.ts index 5924f71..32b637a 100644 --- a/packages/common/config/index.ts +++ b/packages/common/config/index.ts @@ -52,6 +52,9 @@ export const API_ALLOWLIST: RouteRule[] = [ { path: "/api/agents/:agentSlug/chat/cleanup-approvals", methods: ["POST"] }, { path: "/api/agents/:agentSlug/feedback", methods: ["POST"] }, { path: "/api/agents/:agentSlug/migrate", methods: ["POST"] }, + /** A2A 协议 — Agent Card 公开(规范要求),JSON-RPC 端点走身份解析 */ + { path: "/api/agents/:agentSlug/agent-card", methods: ["GET"] }, + { path: "/api/agents/:agentSlug/a2a", methods: ["POST"] }, { path: "/api/agent-tools", methods: ["GET"] }, { path: "/api/llm/chat/tools", methods: ["GET"] }, ]; diff --git a/packages/drizzle-pkg/db.sqlite b/packages/drizzle-pkg/db.sqlite index 30cd77ab66f9ac4dfbd67cf4bf5c428ca855a244..3a1735b9ad3ddb90b0df011725de79e1a1ec6d0c 100644 GIT binary patch delta 26101 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 qR9z$ay;NO)u_P43Ve*&ZP agentSessions.id, { onDelete: "cascade" }), + callerAgentId: integer("caller_agent_id").references(() => agents.id, { onDelete: "set null" }), + calleeAgentId: integer("callee_agent_id") + .notNull() + .references(() => agents.id, { onDelete: "cascade" }), + state: text("state", { length: 20 }).notNull().default("submitted"), + callerContext: text("caller_context"), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }) + .defaultNow() + .$onUpdate(() => new Date()) + .notNull(), + completedAt: integer("completed_at", { mode: "timestamp_ms" }), + }, + (table) => [ + uniqueIndex("a2a_task_sessions_task_id_uniq").on(table.taskId), + index("a2a_task_sessions_session_idx").on(table.sessionId), + index("a2a_task_sessions_callee_idx").on(table.calleeAgentId), + index("a2a_task_sessions_state_idx").on(table.state), + ], +); + +export type A2aTaskSessionRow = typeof a2aTaskSessions.$inferSelect; diff --git a/packages/drizzle-pkg/migrations/0004_public_dracula.sql b/packages/drizzle-pkg/migrations/0004_public_dracula.sql new file mode 100644 index 0000000..fad79be --- /dev/null +++ b/packages/drizzle-pkg/migrations/0004_public_dracula.sql @@ -0,0 +1,21 @@ +CREATE TABLE `a2a_task_sessions` ( + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `task_id` text(64) NOT NULL, + `session_id` text NOT NULL, + `caller_agent_id` integer, + `callee_agent_id` integer NOT NULL, + `state` text(20) DEFAULT 'submitted' NOT NULL, + `caller_context` text, + `created_at` integer DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer)) NOT NULL, + `updated_at` integer DEFAULT (cast((julianday('now') - 2440587.5)*86400000 as integer)) NOT NULL, + `completed_at` integer, + FOREIGN KEY (`session_id`) REFERENCES `agent_sessions`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`caller_agent_id`) REFERENCES `agents`(`id`) ON UPDATE no action ON DELETE set null, + FOREIGN KEY (`callee_agent_id`) REFERENCES `agents`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE UNIQUE INDEX `a2a_task_sessions_task_id_unique` ON `a2a_task_sessions` (`task_id`);--> statement-breakpoint +CREATE UNIQUE INDEX `a2a_task_sessions_task_id_uniq` ON `a2a_task_sessions` (`task_id`);--> statement-breakpoint +CREATE INDEX `a2a_task_sessions_session_idx` ON `a2a_task_sessions` (`session_id`);--> statement-breakpoint +CREATE INDEX `a2a_task_sessions_callee_idx` ON `a2a_task_sessions` (`callee_agent_id`);--> statement-breakpoint +CREATE INDEX `a2a_task_sessions_state_idx` ON `a2a_task_sessions` (`state`); \ No newline at end of file diff --git a/packages/drizzle-pkg/migrations/meta/0004_snapshot.json b/packages/drizzle-pkg/migrations/meta/0004_snapshot.json new file mode 100644 index 0000000..4166413 --- /dev/null +++ b/packages/drizzle-pkg/migrations/meta/0004_snapshot.json @@ -0,0 +1,2790 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "e05dd29e-eac2-4fb9-99bc-cd76a5bb3ca2", + "prevId": "4a75c138-554b-44d5-a2b7-30d0800b2522", + "tables": { + "a2a_task_sessions": { + "name": "a2a_task_sessions", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "task_id": { + "name": "task_id", + "type": "text(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "caller_agent_id": { + "name": "caller_agent_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "callee_agent_id": { + "name": "callee_agent_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text(20)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'submitted'" + }, + "caller_context": { + "name": "caller_context", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "completed_at": { + "name": "completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "a2a_task_sessions_task_id_unique": { + "name": "a2a_task_sessions_task_id_unique", + "columns": [ + "task_id" + ], + "isUnique": true + }, + "a2a_task_sessions_task_id_uniq": { + "name": "a2a_task_sessions_task_id_uniq", + "columns": [ + "task_id" + ], + "isUnique": true + }, + "a2a_task_sessions_session_idx": { + "name": "a2a_task_sessions_session_idx", + "columns": [ + "session_id" + ], + "isUnique": false + }, + "a2a_task_sessions_callee_idx": { + "name": "a2a_task_sessions_callee_idx", + "columns": [ + "callee_agent_id" + ], + "isUnique": false + }, + "a2a_task_sessions_state_idx": { + "name": "a2a_task_sessions_state_idx", + "columns": [ + "state" + ], + "isUnique": false + } + }, + "foreignKeys": { + "a2a_task_sessions_session_id_agent_sessions_id_fk": { + "name": "a2a_task_sessions_session_id_agent_sessions_id_fk", + "tableFrom": "a2a_task_sessions", + "tableTo": "agent_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "a2a_task_sessions_caller_agent_id_agents_id_fk": { + "name": "a2a_task_sessions_caller_agent_id_agents_id_fk", + "tableFrom": "a2a_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "caller_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "a2a_task_sessions_callee_agent_id_agents_id_fk": { + "name": "a2a_task_sessions_callee_agent_id_agents_id_fk", + "tableFrom": "a2a_task_sessions", + "tableTo": "agents", + "columnsFrom": [ + "callee_agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_documents": { + "name": "agent_documents", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "agent_id": { + "name": "agent_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text(20)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'document'" + }, + "title": { + "name": "title", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_agent_documents_user_id": { + "name": "idx_agent_documents_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_agent_documents_user_type": { + "name": "idx_agent_documents_user_type", + "columns": [ + "user_id", + "type" + ], + "isUnique": false + }, + "idx_agent_documents_user_session": { + "name": "idx_agent_documents_user_session", + "columns": [ + "user_id", + "session_id" + ], + "isUnique": false + }, + "idx_agent_documents_user_agent": { + "name": "idx_agent_documents_user_agent", + "columns": [ + "user_id", + "agent_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_tool_logs": { + "name": "agent_tool_logs", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "tool_id": { + "name": "tool_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tool_slug": { + "name": "tool_slug", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "input": { + "name": "input", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "output": { + "name": "output", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text(20)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "agent_tool_logs_tool_id_idx": { + "name": "agent_tool_logs_tool_id_idx", + "columns": [ + "tool_id" + ], + "isUnique": false + }, + "agent_tool_logs_created_at_idx": { + "name": "agent_tool_logs_created_at_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_tools": { + "name": "agent_tools", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "needs_approval": { + "name": "needs_approval", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "admin_only": { + "name": "admin_only", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "agent_tools_slug_idx": { + "name": "agent_tools_slug_idx", + "columns": [ + "slug" + ], + "isUnique": true + }, + "agent_tools_enabled_idx": { + "name": "agent_tools_enabled_idx", + "columns": [ + "enabled" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_message_feedback": { + "name": "agent_message_feedback", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "feedback": { + "name": "feedback", + "type": "text(10)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_agent_feedback_message_user": { + "name": "idx_agent_feedback_message_user", + "columns": [ + "message_id", + "user_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "agent_message_feedback_message_id_agent_messages_id_fk": { + "name": "agent_message_feedback_message_id_agent_messages_id_fk", + "tableFrom": "agent_message_feedback", + "tableTo": "agent_messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_message_feedback_user_id_users_id_fk": { + "name": "agent_message_feedback_user_id_users_id_fk", + "tableFrom": "agent_message_feedback", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_messages": { + "name": "agent_messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text(20)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "parts": { + "name": "parts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_agent_messages_session_sort": { + "name": "idx_agent_messages_session_sort", + "columns": [ + "session_id", + "sort_order" + ], + "isUnique": false + } + }, + "foreignKeys": { + "agent_messages_session_id_agent_sessions_id_fk": { + "name": "agent_messages_session_id_agent_sessions_id_fk", + "tableFrom": "agent_messages", + "tableTo": "agent_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_sessions": { + "name": "agent_sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "agent_id": { + "name": "agent_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "temp_token": { + "name": "temp_token", + "type": "text(64)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'新对话'" + }, + "model_id": { + "name": "model_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_thinking": { + "name": "enable_thinking", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_tools": { + "name": "enable_tools", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "system_prompt": { + "name": "system_prompt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_agent_sessions_user_deleted": { + "name": "idx_agent_sessions_user_deleted", + "columns": [ + "user_id", + "deleted_at" + ], + "isUnique": false + }, + "idx_agent_sessions_temp_token": { + "name": "idx_agent_sessions_temp_token", + "columns": [ + "temp_token" + ], + "isUnique": false + }, + "idx_agent_sessions_agent": { + "name": "idx_agent_sessions_agent", + "columns": [ + "agent_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "agent_sessions_agent_id_agents_id_fk": { + "name": "agent_sessions_agent_id_agents_id_fk", + "tableFrom": "agent_sessions", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "agent_sessions_user_id_users_id_fk": { + "name": "agent_sessions_user_id_users_id_fk", + "tableFrom": "agent_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_tool_associations": { + "name": "agent_tool_associations", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "agent_id": { + "name": "agent_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tool_id": { + "name": "tool_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "needs_approval": { + "name": "needs_approval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "agent_tool_assoc_uniq": { + "name": "agent_tool_assoc_uniq", + "columns": [ + "agent_id", + "tool_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "agent_tool_associations_agent_id_agents_id_fk": { + "name": "agent_tool_associations_agent_id_agents_id_fk", + "tableFrom": "agent_tool_associations", + "tableTo": "agents", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_tool_associations_tool_id_agent_tools_id_fk": { + "name": "agent_tool_associations_tool_id_agent_tools_id_fk", + "tableFrom": "agent_tool_associations", + "tableTo": "agent_tools", + "columnsFrom": [ + "tool_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agents": { + "name": "agents", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "slug": { + "name": "slug", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "system_prompt": { + "name": "system_prompt", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "default_model_id": { + "name": "default_model_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title_model_id": { + "name": "title_model_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title_strategy": { + "name": "title_strategy", + "type": "text(20)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'llm'" + }, + "enable_thinking": { + "name": "enable_thinking", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "enable_tools": { + "name": "enable_tools", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "max_step_count": { + "name": "max_step_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 6 + }, + "is_default": { + "name": "is_default", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_callable": { + "name": "is_callable", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "agents_slug_unique": { + "name": "agents_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "agents_enabled_idx": { + "name": "agents_enabled_idx", + "columns": [ + "enabled" + ], + "isUnique": false + }, + "agents_sort_order_idx": { + "name": "agents_sort_order_idx", + "columns": [ + "sort_order" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_accounts": { + "name": "oauth_accounts", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "sessions_user_id_idx": { + "name": "sessions_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "nickname": { + "name": "nickname", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "avatar": { + "name": "avatar", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'user'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "users_username_unique": { + "name": "users_username_unique", + "columns": [ + "username" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "app_configs": { + "name": "app_configs", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value_type": { + "name": "value_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_configs": { + "name": "user_configs", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value_type": { + "name": "value_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "user_configs_user_id_idx": { + "name": "user_configs_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_configs_user_id_users_id_fk": { + "name": "user_configs_user_id_users_id_fk", + "tableFrom": "user_configs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_configs_user_id_key_pk": { + "columns": [ + "user_id", + "key" + ], + "name": "user_configs_user_id_key_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "article_cards": { + "name": "article_cards", + "columns": { + "article_id": { + "name": "article_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "card_id": { + "name": "card_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_article_card_article": { + "name": "idx_article_card_article", + "columns": [ + "article_id" + ], + "isUnique": false + }, + "idx_article_card_card": { + "name": "idx_article_card_card", + "columns": [ + "card_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "article_cards_article_id_articles_id_fk": { + "name": "article_cards_article_id_articles_id_fk", + "tableFrom": "article_cards", + "tableTo": "articles", + "columnsFrom": [ + "article_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "article_cards_card_id_cards_id_fk": { + "name": "article_cards_card_id_cards_id_fk", + "tableFrom": "article_cards", + "tableTo": "cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "article_card_pk": { + "columns": [ + "article_id", + "card_id" + ], + "name": "article_card_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "articles": { + "name": "articles", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "title": { + "name": "title", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cover": { + "name": "cover", + "type": "text(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_article_status": { + "name": "idx_article_status", + "columns": [ + "status" + ], + "isUnique": false + }, + "idx_article_created": { + "name": "idx_article_created", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "card_images": { + "name": "card_images", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "card_id": { + "name": "card_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text(500)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_card_image_card": { + "name": "idx_card_image_card", + "columns": [ + "card_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "card_images_card_id_cards_id_fk": { + "name": "card_images_card_id_cards_id_fk", + "tableFrom": "card_images", + "tableTo": "cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "card_tags": { + "name": "card_tags", + "columns": { + "card_id": { + "name": "card_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tag_id": { + "name": "tag_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_card_tag_card": { + "name": "idx_card_tag_card", + "columns": [ + "card_id" + ], + "isUnique": false + }, + "idx_card_tag_tag": { + "name": "idx_card_tag_tag", + "columns": [ + "tag_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "card_tags_card_id_cards_id_fk": { + "name": "card_tags_card_id_cards_id_fk", + "tableFrom": "card_tags", + "tableTo": "cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "card_tags_tag_id_tags_id_fk": { + "name": "card_tags_tag_id_tags_id_fk", + "tableFrom": "card_tags", + "tableTo": "tags", + "columnsFrom": [ + "tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "card_tag_pk": { + "columns": [ + "card_id", + "tag_id" + ], + "name": "card_tag_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "cards": { + "name": "cards", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "aspect_ratio": { + "name": "aspect_ratio", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_card_category": { + "name": "idx_card_category", + "columns": [ + "category_id" + ], + "isUnique": false + }, + "idx_card_type": { + "name": "idx_card_type", + "columns": [ + "type" + ], + "isUnique": false + }, + "idx_card_created": { + "name": "idx_card_created", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "cards_category_id_categories_id_fk": { + "name": "cards_category_id_categories_id_fk", + "tableFrom": "cards", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "categories": { + "name": "categories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_category_slug": { + "name": "idx_category_slug", + "columns": [ + "slug" + ], + "isUnique": true + }, + "idx_category_parent": { + "name": "idx_category_parent", + "columns": [ + "parent_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "chat_messages": { + "name": "chat_messages", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "nickname": { + "name": "nickname", + "type": "text(20)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_chat_msg_created": { + "name": "idx_chat_msg_created", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "favorites": { + "name": "favorites", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "card_id": { + "name": "card_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_favorite_user": { + "name": "idx_favorite_user", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_favorite_card": { + "name": "idx_favorite_card", + "columns": [ + "card_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "favorites_user_id_users_id_fk": { + "name": "favorites_user_id_users_id_fk", + "tableFrom": "favorites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "favorites_card_id_cards_id_fk": { + "name": "favorites_card_id_cards_id_fk", + "tableFrom": "favorites", + "tableTo": "cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "favorite_pk": { + "columns": [ + "user_id", + "card_id" + ], + "name": "favorite_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ideas": { + "name": "ideas", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "author": { + "name": "author", + "type": "text(30)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform": { + "name": "platform", + "type": "text(30)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text(30)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_idea_created": { + "name": "idx_idea_created", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text(1000)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_project_created": { + "name": "idx_project_created", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tags": { + "name": "tags", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_tag_slug": { + "name": "idx_tag_slug", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tools": { + "name": "tools", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text(50)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "llm_models": { + "name": "llm_models", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(200)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text(200)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'text'" + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "supports_tools": { + "name": "supports_tools", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "max_tokens": { + "name": "max_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_llm_model_provider": { + "name": "idx_llm_model_provider", + "columns": [ + "provider_id" + ], + "isUnique": false + }, + "idx_llm_model_type": { + "name": "idx_llm_model_type", + "columns": [ + "type" + ], + "isUnique": false + } + }, + "foreignKeys": { + "llm_models_provider_id_llm_providers_id_fk": { + "name": "llm_models_provider_id_llm_providers_id_fk", + "tableFrom": "llm_models", + "tableTo": "llm_providers", + "columnsFrom": [ + "provider_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "llm_providers": { + "name": "llm_providers", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "base_url": { + "name": "base_url", + "type": "text(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parse_mode": { + "name": "parse_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'openai'" + }, + "api_key": { + "name": "api_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": { + "idx_llm_provider_user_slug": { + "name": "idx_llm_provider_user_slug", + "columns": [ + "user_id", + "slug" + ], + "isUnique": true + }, + "idx_llm_provider_user": { + "name": "idx_llm_provider_user", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_llm_provider_status": { + "name": "idx_llm_provider_status", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": { + "llm_providers_user_id_users_id_fk": { + "name": "llm_providers_user_id_users_id_fk", + "tableFrom": "llm_providers", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "scheduled_tasks": { + "name": "scheduled_tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "function_name": { + "name": "function_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "function_payload": { + "name": "function_payload", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "http_method": { + "name": "http_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "http_url": { + "name": "http_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "http_headers": { + "name": "http_headers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "http_body": { + "name": "http_body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "catch_up": { + "name": "catch_up", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "max_retries": { + "name": "max_retries", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "retry_delay_seconds": { + "name": "retry_delay_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 60 + }, + "timeout_seconds": { + "name": "timeout_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 300 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task_execution_logs": { + "name": "task_execution_logs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))" + }, + "finished_at": { + "name": "finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "task_execution_logs_task_id_scheduled_tasks_id_fk": { + "name": "task_execution_logs_task_id_scheduled_tasks_id_fk", + "tableFrom": "task_execution_logs", + "tableTo": "scheduled_tasks", + "columnsFrom": [ + "task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/packages/drizzle-pkg/migrations/meta/_journal.json b/packages/drizzle-pkg/migrations/meta/_journal.json index e204c5d..0c29ce9 100644 --- a/packages/drizzle-pkg/migrations/meta/_journal.json +++ b/packages/drizzle-pkg/migrations/meta/_journal.json @@ -29,6 +29,13 @@ "when": 1786287044322, "tag": "0003_adorable_dragon_lord", "breakpoints": true + }, + { + "idx": 4, + "version": "6", + "when": 1786459321606, + "tag": "0004_public_dracula", + "breakpoints": true } ] } \ No newline at end of file diff --git a/server/api/agents/[agentSlug]/a2a.post.ts b/server/api/agents/[agentSlug]/a2a.post.ts new file mode 100644 index 0000000..8f39262 --- /dev/null +++ b/server/api/agents/[agentSlug]/a2a.post.ts @@ -0,0 +1,131 @@ +import { defineWrappedResponseHandler } from "#server/utils/handler"; +import { R } from "#server/utils/response"; +import { getAgentBySlug } from "#server/service/agent/agent"; +import { resolveAgentIdentity } from "#server/service/agent/identity"; +import { createTask, sendTask, getTask, cancelTask } from "#server/service/a2a/service"; +import { DEFAULT_A2A_CONFIG, JSONRPC_ERROR_CODES, A2A_METHODS } from "#server/service/a2a/types"; +import type { + JSONRPCRequest, + JSONRPCResponse, + JSONRPCError, + A2ATaskSendParams, + A2ATaskGetParams, + A2ATaskCancelParams, + A2AInvocationContext, + A2AMessage, +} from "#server/service/a2a/types"; + +function rpcError(id: string | number | null, code: number, message: string, data?: unknown): JSONRPCResponse { + const error: JSONRPCError = { code, message }; + if (data !== undefined) error.data = data; + return { jsonrpc: "2.0", id, error }; +} + +function rpcResult(id: string | number | null, result: T): JSONRPCResponse { + return { jsonrpc: "2.0", id, result }; +} + +export default defineWrappedResponseHandler(async (event) => { + const agentSlug = getRouterParam(event, "agentSlug"); + if (!agentSlug) { + return rpcError(null, JSONRPC_ERROR_CODES.INVALID_PARAMS, "missing agentSlug"); + } + + const agent = await getAgentBySlug(agentSlug); + if (!agent) { + return rpcError(null, JSONRPC_ERROR_CODES.AGENT_NOT_CALLABLE, `agent not found: ${agentSlug}`); + } + + const body = await readBody(event); + if (!body || body.jsonrpc !== "2.0" || !body.method) { + return rpcError(body?.id ?? null, JSONRPC_ERROR_CODES.INVALID_REQUEST, "invalid JSON-RPC 2.0 request"); + } + + const isInternal = event.node.req.headers["x-a2a-internal"] === "true"; + const internalCallerAgentId = event.node.req.headers["x-a2a-caller-agent-id"]; + const internalRecursionDepth = event.node.req.headers["x-a2a-recursion-depth"]; + + let userId: number | null; + let tempToken: string | null; + if (isInternal) { + userId = null; + tempToken = null; + } else { + const identity = await resolveAgentIdentity(event); + userId = identity.userId; + tempToken = identity.tempToken; + } + + const callerAgentId = isInternal + ? (internalCallerAgentId ? Number(internalCallerAgentId) : null) + : null; + const recursionDepth = isInternal + ? (internalRecursionDepth ? Number(internalRecursionDepth) : 0) + : 0; + + const { id } = body; + + switch (body.method) { + case A2A_METHODS.TASKS_SEND: { + const params = body.params as A2ATaskSendParams; + if (!params?.message || !params?.id) { + return rpcError(id, JSONRPC_ERROR_CODES.INVALID_PARAMS, "missing required params: id, message"); + } + + const { task } = await createTask({ + calleeAgentSlug: agentSlug, + message: params.message as A2AMessage, + callerAgentId, + userId, + tempToken, + existingSessionId: params.sessionId, + }); + + const ctx: A2AInvocationContext = { + callerAgentId, + calleeAgentId: agent.id, + calleeAgentSlug: agentSlug, + userId, + sessionId: task.sessionId, + recursionDepth, + maxRecursionDepth: DEFAULT_A2A_CONFIG.maxRecursionDepth, + timeoutMs: DEFAULT_A2A_CONFIG.timeoutMs, + maxOutputTokens: DEFAULT_A2A_CONFIG.maxOutputTokens, + }; + + const completedTask = await sendTask(task.id, ctx); + return rpcResult(id, completedTask); + } + + case A2A_METHODS.TASKS_GET: { + const params = body.params as A2ATaskGetParams; + if (!params?.id) { + return rpcError(id, JSONRPC_ERROR_CODES.INVALID_PARAMS, "missing required param: id"); + } + const task = await getTask(params.id); + if (!task) { + return rpcError(id, JSONRPC_ERROR_CODES.TASK_NOT_FOUND, `task not found: ${params.id}`); + } + return rpcResult(id, task); + } + + case A2A_METHODS.TASKS_CANCEL: { + const params = body.params as A2ATaskCancelParams; + if (!params?.id) { + return rpcError(id, JSONRPC_ERROR_CODES.INVALID_PARAMS, "missing required param: id"); + } + const canceled = await cancelTask(params.id); + if (!canceled) { + return rpcError(id, JSONRPC_ERROR_CODES.TASK_NOT_CANCELABLE, `task not cancelable: ${params.id}`); + } + return rpcResult(id, { id: params.id, canceled: true }); + } + + case A2A_METHODS.TASKS_SEND_SUBSCRIBE: + case A2A_METHODS.TASKS_SUBSCRIBE: + return rpcError(id, JSONRPC_ERROR_CODES.NOT_IMPLEMENTED, "streaming not implemented yet"); + + default: + return rpcError(id, JSONRPC_ERROR_CODES.METHOD_NOT_FOUND, `method not found: ${body.method}`); + } +}); diff --git a/server/api/agents/[agentSlug]/agent-card.get.ts b/server/api/agents/[agentSlug]/agent-card.get.ts new file mode 100644 index 0000000..e93daa5 --- /dev/null +++ b/server/api/agents/[agentSlug]/agent-card.get.ts @@ -0,0 +1,23 @@ +import { defineWrappedResponseHandler } from "#server/utils/handler"; +import { R } from "#server/utils/response"; +import { getAgentBySlug } from "#server/service/agent/agent"; +import { agentRowToCard } from "#server/service/a2a/converter"; + +export default defineWrappedResponseHandler(async (event) => { + const agentSlug = getRouterParam(event, "agentSlug"); + if (!agentSlug) { + return R.error("参数无效", null); + } + + const agent = await getAgentBySlug(agentSlug); + if (!agent) { + return R.error("agent 不存在", null); + } + + const protocol = event.node.req.headers["x-forwarded-proto"] ?? "http"; + const host = event.node.req.headers.host ?? "localhost"; + const baseUrl = `${protocol}://${host}`; + + const card = agentRowToCard(agent, baseUrl); + return card; +}); diff --git a/server/service/a2a/client.ts b/server/service/a2a/client.ts new file mode 100644 index 0000000..0659525 --- /dev/null +++ b/server/service/a2a/client.ts @@ -0,0 +1,103 @@ +/** + * A2A 内部客户端 + * + * 替代 collaboration.ts 的 invokeAgent(),提供与原接口兼容的 + * agent 间调用能力,底层走 A2A Service 层。 + * + * 设计原则: + * - 接口签名与 invokeAgent 完全兼容,便于无缝替换 + * - 递归深度、超时、自调用阻断通过 A2AInvocationContext 传递 + * - 未来可切换为真实 HTTP 调用本机 A2A 端点(接口不变) + */ + +import type { AgentInvocation, AgentInvocationResult } from "../agent/types"; +import { getAgentBySlug } from "../agent/agent"; +import { createTask, sendTask } from "./service"; +import { textToA2AMessage, a2aMessageToText } from "./converter"; +import { DEFAULT_A2A_CONFIG, JSONRPC_ERROR_CODES } from "./types"; +import type { A2AInvocationContext } from "./types"; +import log4js from "logger"; + +const logger = log4js.getLogger("APP"); + +export async function invokeAgentViaA2A( + invocation: AgentInvocation, + options?: { + maxRecursionDepth?: number; + timeoutMs?: number; + maxOutputTokens?: number; + }, +): Promise { + const { agentSlug, input, context } = invocation; + const maxRecursionDepth = options?.maxRecursionDepth ?? DEFAULT_A2A_CONFIG.maxRecursionDepth; + const timeoutMs = options?.timeoutMs ?? DEFAULT_A2A_CONFIG.timeoutMs; + const maxOutputTokens = options?.maxOutputTokens ?? DEFAULT_A2A_CONFIG.maxOutputTokens; + + const userId = context?.userId ?? null; + const callerAgentId = context?.callerAgentId ?? null; + const recursionDepth = context?.recursionDepth ?? 0; + + const agent = await getAgentBySlug(agentSlug); + if (!agent) { + return { agentSlug, output: "", ok: false, error: `agent not found: ${agentSlug}` }; + } + + if (!agent.isCallable) { + return { agentSlug, output: "", ok: false, error: `agent not callable: ${agentSlug}` }; + } + + if (callerAgentId !== null && callerAgentId === agent.id) { + return { agentSlug, output: "", ok: false, error: `self-invocation blocked: agent ${agentSlug} cannot invoke itself` }; + } + + if (recursionDepth >= maxRecursionDepth) { + return { + agentSlug, + output: "", + ok: false, + error: `max recursion depth (${maxRecursionDepth}) reached`, + }; + } + + const message = textToA2AMessage(input, "user"); + + try { + const { task } = await createTask({ + calleeAgentSlug: agentSlug, + message, + callerAgentId, + userId, + }); + + const a2aContext: A2AInvocationContext = { + callerAgentId, + calleeAgentId: agent.id, + calleeAgentSlug: agentSlug, + userId, + sessionId: task.sessionId, + recursionDepth, + maxRecursionDepth, + timeoutMs, + maxOutputTokens, + }; + + const completedTask = await sendTask(task.id, a2aContext); + + const output = completedTask.message + ? a2aMessageToText(completedTask.message) + : ""; + + logger.info( + "[A2A-CLIENT] invokeAgentViaA2A done agentSlug=%s taskId=%s outputLen=%d", + agentSlug, + task.id, + output.length, + ); + + return { agentSlug, output, ok: true }; + } catch (err) { + const errorMsg = err instanceof Error ? err.message : String(err); + logger.error("[A2A-CLIENT] invokeAgentViaA2A error agentSlug=%s error=%s", agentSlug, errorMsg); + return { agentSlug, output: "", ok: false, error: errorMsg }; + } +} diff --git a/server/service/a2a/converter.ts b/server/service/a2a/converter.ts new file mode 100644 index 0000000..7c9b962 --- /dev/null +++ b/server/service/a2a/converter.ts @@ -0,0 +1,105 @@ +/** + * A2A 消息格式转换层 + * + * 负责 A2A 协议类型与内部业务类型之间的双向转换: + * - Agent 数据行 ↔ Agent Card + * - A2A Message ↔ 纯文本输入(用于 generateText) + * - 纯文本输出 ↔ A2A Message + * - A2A Part ↔ StoredPart(预留,当前仅 TextPart) + */ + +import type { AgentRow } from "../agent/types"; +import type { StoredPart } from "../agent/stored-part"; +import type { + A2AAgentCard, + A2ASkill, + A2AMessage, + A2APart, + A2ATextPart, + A2ADataPart, + A2AFilePart, +} from "./types"; +import { A2A_PROTOCOL_VERSION } from "./types"; + +// ============ Agent Row → Agent Card ============ + +export function agentRowToCard(agent: AgentRow, baseUrl: string): A2AAgentCard { + const url = `${baseUrl}/api/agents/${agent.slug}/a2a`; + + const skills: A2ASkill[] = [ + { + id: agent.slug, + name: agent.name, + description: agent.description ?? agent.systemPrompt.slice(0, 200), + tags: agent.isCallable ? ["callable", "agent"] : ["agent"], + inputModes: ["text/plain"], + outputModes: ["text/plain"], + }, + ]; + + return { + name: agent.name, + description: agent.description ?? "", + url, + version: "1.0.0", + protocolVersion: A2A_PROTOCOL_VERSION, + capabilities: { + streaming: true, + pushNotifications: false, + stateTransition: true, + }, + defaultInputModes: ["text/plain"], + defaultOutputModes: ["text/plain"], + skills, + }; +} + +// ============ A2A Message ↔ 纯文本 ============ + +export function a2aMessageToText(message: A2AMessage): string { + const textParts = message.parts.filter((p): p is A2ATextPart => p.type === "text"); + if (textParts.length === 0) return ""; + return textParts.map((p) => p.text).join("\n"); +} + +export function textToA2AMessage(text: string, role: "user" | "agent" = "agent"): A2AMessage { + return { + role, + parts: [{ type: "text", text }], + }; +} + +// ============ A2A Part ↔ StoredPart(预留) ============ + +export function a2aPartsToStoredParts(parts: A2APart[]): StoredPart[] { + return parts + .filter((p): p is A2ATextPart => p.type === "text") + .map((p, i) => ({ + id: `a2a-part-${i}`, + type: "text", + text: p.text, + })); +} + +export function storedPartsToA2AParts(parts: StoredPart[]): A2APart[] { + return parts + .filter((p) => p.type === "text" && p.text !== undefined) + .map((p) => ({ + type: "text" as const, + text: p.text!, + })); +} + +// ============ Part 类型守卫 ============ + +export function isTextPart(part: A2APart): part is A2ATextPart { + return part.type === "text"; +} + +export function isDataPart(part: A2APart): part is A2ADataPart { + return part.type === "data"; +} + +export function isFilePart(part: A2APart): part is A2AFilePart { + return part.type === "file"; +} diff --git a/server/service/a2a/index.ts b/server/service/a2a/index.ts new file mode 100644 index 0000000..e190ccf --- /dev/null +++ b/server/service/a2a/index.ts @@ -0,0 +1,4 @@ +export * from "./types"; +export * from "./converter"; +export * from "./service"; +export * from "./client"; diff --git a/server/service/a2a/service.ts b/server/service/a2a/service.ts new file mode 100644 index 0000000..10536c3 --- /dev/null +++ b/server/service/a2a/service.ts @@ -0,0 +1,341 @@ +/** + * A2A Service 核心层 + * + * 负责 A2A task 的生命周期管理,与 chat-engine 解耦: + * - createTask: 创建 task(创建 session + 映射记录) + * - sendTask: 同步执行 task(调用 generateText) + * - getTask: 查询 task 状态 + * - cancelTask: 取消 task(对接 abort-manager) + * - subscribeTask: streaming(预留) + * + * 复用现有模块: + * - agent/agent.ts: getAgentBySlug, getAgentById + * - agent/session.ts: createSession, getSessionById + * - agent-tool/index.ts: getAgentToolsForChatByAgentId + * - agent/abort-manager.ts: registerAbortController, unregisterAbortController + * - llm/model-resolver: resolveModelForUser, resolveModelAny, toLanguageModel + * - utils/context: getConfigGlobal + */ + +import { generateText, stepCountIs } from "ai"; +import { dbGlobal } from "drizzle-pkg/lib/db"; +import { a2aTaskSessions } from "drizzle-pkg/lib/schema/a2a"; +import { eq, and } from "drizzle-orm"; +import log4js from "logger"; + +import { getAgentBySlug, getAgentById } from "../agent/agent"; +import { createSession, getSessionById } from "../agent/session"; +import { registerAbortController, unregisterAbortController, abortSessionStream } from "../agent/abort-manager"; +import { getAgentToolsForChatByAgentId } from "#server/service/agent-tool"; +import { resolveModelForUser, resolveModelAny, toLanguageModel } from "#server/service/llm/model-resolver"; +import { getConfigGlobal } from "#server/utils/context"; + +import { a2aMessageToText, textToA2AMessage } from "./converter"; +import { + DEFAULT_A2A_CONFIG, + A2A_PROTOCOL_VERSION, + JSONRPC_ERROR_CODES, +} from "./types"; +import type { + A2ATask, + A2ATaskState, + A2AMessage, + A2AInvocationContext, + A2AServiceConfig, +} from "./types"; + +const logger = log4js.getLogger("APP"); + +// ============ 辅助:生成 Task ID ============ + +function generateTaskId(): string { + return `a2a_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`; +} + +// ============ 辅助:row → A2ATask ============ + +function rowToTask(row: typeof a2aTaskSessions.$inferSelect, message?: A2AMessage): A2ATask { + return { + id: row.taskId, + sessionId: row.sessionId, + state: row.state as A2ATaskState, + message, + createdAt: row.createdAt.getTime(), + updatedAt: row.updatedAt.getTime(), + }; +} + +// ============ createTask ============ + +export async function createTask(params: { + calleeAgentSlug: string; + message: A2AMessage; + callerAgentId?: number | null; + userId?: number | null; + tempToken?: string | null; + existingSessionId?: string; +}): Promise<{ task: A2ATask; agent: NonNullable>> }> { + const { calleeAgentSlug, message, callerAgentId = null, userId = null, tempToken = null } = params; + + const agent = await getAgentBySlug(calleeAgentSlug); + if (!agent) { + throw createA2AError(JSONRPC_ERROR_CODES.AGENT_NOT_CALLABLE, `agent not found: ${calleeAgentSlug}`); + } + + let sessionId: string; + if (params.existingSessionId) { + const existing = await getSessionById(params.existingSessionId); + if (!existing) { + throw createA2AError(JSONRPC_ERROR_CODES.INTERNAL_ERROR, `session not found: ${params.existingSessionId}`); + } + sessionId = existing.id; + } else { + const session = await createSession({ + userId, + tempToken, + agentId: agent.id, + modelId: agent.defaultModelId, + enableThinking: agent.enableThinking, + enableTools: agent.enableTools, + systemPrompt: agent.systemPrompt, + }); + sessionId = session.id; + } + + const inputText = a2aMessageToText(message); + const taskId = generateTaskId(); + const now = new Date(); + const [row] = await dbGlobal + .insert(a2aTaskSessions) + .values({ + taskId, + sessionId, + callerAgentId, + calleeAgentId: agent.id, + state: "submitted", + callerContext: inputText, + createdAt: now, + updatedAt: now, + }) + .returning(); + + const task = rowToTask(row!, message); + return { task, agent }; +} + +// ============ sendTask ============ + +export async function sendTask( + taskId: string, + context: A2AInvocationContext, +): Promise { + const { calleeAgentId, calleeAgentSlug, userId, recursionDepth, maxRecursionDepth, timeoutMs, maxOutputTokens } = context; + + const taskRow = await getTaskRow(taskId); + if (!taskRow) { + throw createA2AError(JSONRPC_ERROR_CODES.TASK_NOT_FOUND, `task not found: ${taskId}`); + } + + if (calleeAgentId === null) { + throw createA2AError(JSONRPC_ERROR_CODES.AGENT_NOT_CALLABLE, `calleeAgentId is null for task ${taskId}`); + } + + const agent = await getAgentById(calleeAgentId); + if (!agent) { + throw createA2AError(JSONRPC_ERROR_CODES.AGENT_NOT_CALLABLE, `agent not found by id: ${calleeAgentId}`); + } + + if (!agent.isCallable) { + await updateTaskState(taskId, "failed"); + throw createA2AError(JSONRPC_ERROR_CODES.AGENT_NOT_CALLABLE, `agent not callable: ${calleeAgentSlug}`); + } + + if (context.callerAgentId !== null && context.callerAgentId === calleeAgentId) { + await updateTaskState(taskId, "failed"); + throw createA2AError(JSONRPC_ERROR_CODES.SELF_INVOCATION_BLOCKED, `self-invocation blocked: agent ${calleeAgentSlug} cannot invoke itself`); + } + + if (recursionDepth >= maxRecursionDepth) { + await updateTaskState(taskId, "failed"); + throw createA2AError(JSONRPC_ERROR_CODES.MAX_RECURSION_REACHED, `max recursion depth (${maxRecursionDepth}) reached`); + } + + const inputText = taskRow.callerContext ?? ""; + if (!inputText) { + throw createA2AError(JSONRPC_ERROR_CODES.INVALID_PARAMS, `task ${taskId} has no input message`); + } + + await updateTaskState(taskId, "working"); + + const globalDefaultModelId = (await getConfigGlobal("agentDefaultModelId")) ?? null; + const targetModelId = agent.defaultModelId ?? globalDefaultModelId; + if (!targetModelId) { + await updateTaskState(taskId, "failed"); + throw createA2AError(JSONRPC_ERROR_CODES.INTERNAL_ERROR, `agent ${calleeAgentSlug} has no defaultModelId and no global default model configured`); + } + + const resolved = userId + ? await resolveModelForUser(targetModelId, userId) + : await resolveModelAny(targetModelId); + if (!resolved) { + await updateTaskState(taskId, "failed"); + throw createA2AError(JSONRPC_ERROR_CODES.INTERNAL_ERROR, `model ${targetModelId} not resolvable for agent ${calleeAgentSlug}`); + } + + const languageModel = toLanguageModel(resolved); + + const enableTools = agent.enableTools === 1; + const { tools } = await getAgentToolsForChatByAgentId({ + agentId: agent.id, + userId, + userRole: null, + enableTools, + recursionDepth, + }); + + const maxSteps = agent.maxStepCount ?? 8; + + const abortController = new AbortController(); + const timeoutSignal = AbortSignal.timeout(timeoutMs); + const combinedSignal = anySignal([abortController.signal, timeoutSignal]); + registerAbortController(taskRow.sessionId, abortController); + + logger.info( + "[A2A] sendTask taskId=%s agentSlug=%s agentId=%d callerAgentId=%s recursionDepth=%d tools=%d maxSteps=%d", + taskId, + calleeAgentSlug, + agent.id, + context.callerAgentId, + recursionDepth, + Object.keys(tools).length, + maxSteps, + ); + + try { + const result = await generateText({ + model: languageModel, + system: agent.systemPrompt, + prompt: inputText, + ...(Object.keys(tools).length > 0 + ? { + tools, + stopWhen: stepCountIs(maxSteps), + } + : {}), + maxOutputTokens, + abortSignal: combinedSignal, + }); + + const output = result.text ?? ""; + const responseMessage = textToA2AMessage(output, "agent"); + + await updateTaskState(taskId, "completed"); + + logger.info( + "[A2A] sendTask done taskId=%s agentSlug=%s outputLen=%d usage=%j", + taskId, + calleeAgentSlug, + output.length, + result.usage, + ); + + const updatedRow = await getTaskRow(taskId); + return rowToTask(updatedRow!, responseMessage); + } catch (err) { + const errorMsg = err instanceof Error ? err.message : String(err); + logger.error("[A2A] sendTask error taskId=%s agentSlug=%s error=%s", taskId, calleeAgentSlug, errorMsg); + + const isTimeout = errorMsg.includes("timeout") || errorMsg.includes("abort"); + await updateTaskState(taskId, isTimeout ? "failed" : "failed"); + + throw createA2AError( + isTimeout ? JSONRPC_ERROR_CODES.TIMEOUT : JSONRPC_ERROR_CODES.INTERNAL_ERROR, + errorMsg, + ); + } finally { + unregisterAbortController(taskRow.sessionId); + } +} + +// ============ getTask ============ + +export async function getTask(taskId: string): Promise { + const row = await getTaskRow(taskId); + if (!row) return null; + return rowToTask(row); +} + +// ============ cancelTask ============ + +export async function cancelTask(taskId: string): Promise { + const row = await getTaskRow(taskId); + if (!row) return false; + + const cancelableStates: A2ATaskState[] = ["submitted", "working", "input-required"]; + if (!cancelableStates.includes(row.state as A2ATaskState)) { + return false; + } + + const aborted = abortSessionStream(row.sessionId); + await updateTaskState(taskId, "canceled"); + return aborted; +} + +// ============ subscribeTask(预留) ============ + +export async function subscribeTask( + _taskId: string, + _onChunk: (chunk: string) => void, + _onDone: (task: A2ATask) => void, + _onError: (error: Error) => void, +): Promise { + throw createA2AError(JSONRPC_ERROR_CODES.NOT_IMPLEMENTED, "subscribeTask not implemented yet"); +} + +// ============ 内部辅助函数 ============ + +async function getTaskRow(taskId: string) { + const [row] = await dbGlobal + .select() + .from(a2aTaskSessions) + .where(eq(a2aTaskSessions.taskId, taskId)) + .limit(1); + return row ?? null; +} + +async function updateTaskState(taskId: string, state: A2ATaskState): Promise { + const updateData: Record = { + state, + updatedAt: new Date(), + }; + if (state === "completed" || state === "failed" || state === "canceled") { + updateData.completedAt = new Date(); + } + await dbGlobal + .update(a2aTaskSessions) + .set(updateData) + .where(eq(a2aTaskSessions.taskId, taskId)); +} + +function createA2AError(code: number, message: string): Error { + const err = new Error(message); + (err as any).code = code; + return err; +} + +function anySignal(signals: AbortSignal[]): AbortSignal { + const controller = new AbortController(); + for (const signal of signals) { + if (signal.aborted) { + controller.abort(); + break; + } + signal.addEventListener("abort", () => controller.abort(), { once: true }); + } + return controller.signal; +} + +// ============ 导出配置 ============ + +export { DEFAULT_A2A_CONFIG, A2A_PROTOCOL_VERSION }; +export type { A2AServiceConfig }; diff --git a/server/service/a2a/types.ts b/server/service/a2a/types.ts new file mode 100644 index 0000000..16a939d --- /dev/null +++ b/server/service/a2a/types.ts @@ -0,0 +1,206 @@ +/** + * A2A (Agent-to-Agent) 协议类型定义 + * + * 遵循 A2A 协议规范,定义 Agent Card、Task、Message、Part 等 + * 核心数据结构,以及 JSON-RPC 2.0 请求/响应类型。 + * + * 设计原则: + * - 协议类型与业务逻辑解耦,纯类型定义 + * - 预留扩展点(streaming、push notification、外部 agent) + * - 内部调用与外部调用共享同一套类型 + */ + +// ============ Agent Card ============ + +export interface A2AAgentCard { + name: string; + description: string; + url: string; + version: string; + protocolVersion: string; + capabilities: A2ACapabilities; + defaultInputModes: string[]; + defaultOutputModes: string[]; + skills: A2ASkill[]; +} + +export interface A2ACapabilities { + streaming: boolean; + pushNotifications: boolean; + stateTransition: boolean; +} + +export interface A2ASkill { + id: string; + name: string; + description: string; + tags: string[]; + inputModes?: string[]; + outputModes?: string[]; +} + +// ============ Task ============ + +export type A2ATaskState = + | "submitted" + | "working" + | "input-required" + | "completed" + | "canceled" + | "failed"; + +export interface A2ATask { + id: string; + sessionId: string; + state: A2ATaskState; + message?: A2AMessage; + artifacts?: A2AArtifact[]; + createdAt: number; + updatedAt: number; +} + +export interface A2AArtifact { + id: string; + name?: string; + description?: string; + parts: A2APart[]; +} + +// ============ Message & Part ============ + +export interface A2AMessage { + role: "user" | "agent"; + parts: A2APart[]; + taskId?: string; + contextId?: string; + messageId?: string; +} + +export type A2APart = A2ATextPart | A2ADataPart | A2AFilePart; + +export interface A2ATextPart { + type: "text"; + text: string; + metadata?: Record; +} + +export interface A2ADataPart { + type: "data"; + data: Record; + metadata?: Record; +} + +export interface A2AFilePart { + type: "file"; + file: { + name?: string; + mimeType?: string; + bytes?: string; + uri?: string; + }; + metadata?: Record; +} + +// ============ JSON-RPC 2.0 ============ + +export interface JSONRPCRequest { + jsonrpc: "2.0"; + id: string | number | null; + method: string; + params?: T; +} + +export interface JSONRPCResponse { + jsonrpc: "2.0"; + id: string | number | null; + result?: T; + error?: JSONRPCError; +} + +export interface JSONRPCError { + code: number; + message: string; + data?: unknown; +} + +// JSON-RPC 标准错误码 +export const JSONRPC_ERROR_CODES = { + PARSE_ERROR: -32700, + INVALID_REQUEST: -32600, + METHOD_NOT_FOUND: -32601, + INVALID_PARAMS: -32602, + INTERNAL_ERROR: -32603, + // A2A 应用层错误码(-32000 ~ -32099) + TASK_NOT_FOUND: -32001, + TASK_NOT_CANCELABLE: -32002, + AGENT_NOT_CALLABLE: -32003, + MAX_RECURSION_REACHED: -32004, + SELF_INVOCATION_BLOCKED: -32005, + TIMEOUT: -32006, + NOT_IMPLEMENTED: -32099, +} as const; + +// ============ A2A 方法名 ============ + +export const A2A_METHODS = { + TASKS_SEND: "tasks/send", + TASKS_SEND_SUBSCRIBE: "tasks/sendSubscribe", + TASKS_GET: "tasks/get", + TASKS_CANCEL: "tasks/cancel", + TASKS_SUBSCRIBE: "tasks/subscribe", +} as const; + +// ============ tasks/send 请求参数 ============ + +export interface A2ATaskSendParams { + id: string; + message: A2AMessage; + sessionId?: string; + metadata?: Record; +} + +// ============ tasks/get 请求参数 ============ + +export interface A2ATaskGetParams { + id: string; + historyLength?: number; +} + +// ============ tasks/cancel 请求参数 ============ + +export interface A2ATaskCancelParams { + id: string; + metadata?: Record; +} + +// ============ 内部调用上下文 ============ + +export interface A2AInvocationContext { + callerAgentId: number | null; + calleeAgentId: number | null; + calleeAgentSlug: string; + userId: number | null; + sessionId?: string; + recursionDepth: number; + maxRecursionDepth: number; + timeoutMs: number; + maxOutputTokens: number; +} + +// ============ A2A Service 配置 ============ + +export interface A2AServiceConfig { + maxRecursionDepth: number; + timeoutMs: number; + maxOutputTokens: number; +} + +export const DEFAULT_A2A_CONFIG: A2AServiceConfig = { + maxRecursionDepth: 3, + timeoutMs: 60_000, + maxOutputTokens: 8192, +}; + +// ============ A2A 协议版本 ============ + +export const A2A_PROTOCOL_VERSION = "0.3.0"; diff --git a/server/service/agent-tool/executors/agent-invoke/agent-invoke.ts b/server/service/agent-tool/executors/agent-invoke/agent-invoke.ts index 15339f7..d25071f 100644 --- a/server/service/agent-tool/executors/agent-invoke/agent-invoke.ts +++ b/server/service/agent-tool/executors/agent-invoke/agent-invoke.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import type { JSONSchema7 } from "json-schema"; import type { ToolExecutor, ToolResult } from "../../registry"; import type { AgentInvokeToolConfig } from "./config"; -import { invokeAgent } from "../../../agent/collaboration"; +import { invokeAgentViaA2A } from "../../../a2a/client"; import { listAgents } from "../../../agent/agent"; export const agentInvokeInputSchema = z.object({ @@ -78,7 +78,7 @@ export const agentInvokeExecutor: ToolExecutor = { const data: AgentInvokeInput = parsed.data; const currentDepth = ctx.recursionDepth ?? 0; - const result = await invokeAgent( + const result = await invokeAgentViaA2A( { agentSlug: data.agentSlug, input: data.input, diff --git a/server/service/agent/collaboration.ts b/server/service/agent/collaboration.ts index 77ca39b..940406d 100644 --- a/server/service/agent/collaboration.ts +++ b/server/service/agent/collaboration.ts @@ -1,17 +1,16 @@ -import { generateText, stepCountIs } from "ai"; +/** + * @deprecated 此模块已废弃,请使用 server/service/a2a/client.ts 中的 invokeAgentViaA2A()。 + * + * 本文件保留为薄包装,仅用于向后兼容。底层已切换到 A2A Service 层。 + * 新代码请直接 import { invokeAgentViaA2A } from "#server/service/a2a/client"。 + */ + import type { AgentInvocation, AgentInvocationResult } from "./types"; -import { getAgentBySlug } from "./agent"; -import { getAgentToolsForChatByAgentId } from "#server/service/agent-tool"; -import { resolveModelForUser, resolveModelAny, toLanguageModel } from "#server/service/llm/model-resolver"; -import { getConfigGlobal } from "#server/utils/context"; +import { invokeAgentViaA2A } from "../a2a/client"; import log4js from "logger"; const logger = log4js.getLogger("APP"); -const DEFAULT_MAX_RECURSION_DEPTH = 3; -const DEFAULT_TIMEOUT_MS = 60_000; -const DEFAULT_MAX_OUTPUT_TOKENS = 8192; - export async function invokeAgent( invocation: AgentInvocation, options?: { @@ -20,101 +19,9 @@ export async function invokeAgent( maxOutputTokens?: number; }, ): Promise { - const { agentSlug, input, context } = invocation; - const maxRecursionDepth = options?.maxRecursionDepth ?? DEFAULT_MAX_RECURSION_DEPTH; - const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS; - const maxOutputTokens = options?.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS; - - const userId = context?.userId ?? null; - const callerAgentId = context?.callerAgentId ?? null; - const recursionDepth = context?.recursionDepth ?? 0; - - const agent = await getAgentBySlug(agentSlug); - if (!agent) { - return { agentSlug, output: "", ok: false, error: `agent not found: ${agentSlug}` }; - } - - if (!agent.isCallable) { - return { agentSlug, output: "", ok: false, error: `agent not callable: ${agentSlug}` }; - } - - if (callerAgentId !== null && callerAgentId === agent.id) { - return { agentSlug, output: "", ok: false, error: `self-invocation blocked: agent ${agentSlug} cannot invoke itself` }; - } - - if (recursionDepth >= maxRecursionDepth) { - return { - agentSlug, - output: "", - ok: false, - error: `max recursion depth (${maxRecursionDepth}) reached`, - }; - } - - const globalDefaultModelId = (await getConfigGlobal("agentDefaultModelId")) ?? null; - const targetModelId = agent.defaultModelId ?? globalDefaultModelId; - if (!targetModelId) { - return { agentSlug, output: "", ok: false, error: `agent ${agentSlug} has no defaultModelId and no global default model configured` }; - } - - const resolved = userId - ? await resolveModelForUser(targetModelId, userId) - : await resolveModelAny(targetModelId); - if (!resolved) { - return { agentSlug, output: "", ok: false, error: `model ${targetModelId} not resolvable for agent ${agentSlug}` }; - } - - const languageModel = toLanguageModel(resolved); - - const enableTools = agent.enableTools === 1; - const { tools } = await getAgentToolsForChatByAgentId({ - agentId: agent.id, - userId, - userRole: null, - enableTools, - recursionDepth, - }); - - const maxSteps = agent.maxStepCount ?? 8; - - logger.info( - "[COLLAB] invokeAgent agentSlug=%s agentId=%d callerAgentId=%s recursionDepth=%d tools=%d maxSteps=%d", - agentSlug, - agent.id, - callerAgentId, - recursionDepth, - Object.keys(tools).length, - maxSteps, + logger.warn( + "[COLLAB] invokeAgent is deprecated, use invokeAgentViaA2A from a2a/client instead. agentSlug=%s", + invocation.agentSlug, ); - - try { - const result = await generateText({ - model: languageModel, - system: agent.systemPrompt, - prompt: input, - ...(Object.keys(tools).length > 0 - ? { - tools, - stopWhen: stepCountIs(maxSteps), - } - : {}), - maxOutputTokens, - abortSignal: AbortSignal.timeout(timeoutMs), - }); - - const output = result.text ?? ""; - - logger.info( - "[COLLAB] invokeAgent done agentSlug=%s outputLen=%d usage=%j", - agentSlug, - output.length, - result.usage, - ); - - return { agentSlug, output, ok: true }; - } catch (err) { - const errorMsg = err instanceof Error ? err.message : String(err); - logger.error("[COLLAB] invokeAgent error agentSlug=%s error=%s", agentSlug, errorMsg); - return { agentSlug, output: "", ok: false, error: errorMsg }; - } + return invokeAgentViaA2A(invocation, options); }