import { z } from "zod"; import type { AgentRunStatus } from "./agent.ts"; import { Id, JsonObject, NonNegInt, Timestamp } from "./common.ts"; import type { TypedEvent } from "./events.ts"; // Agent 运行事件(REQ §10.6):packages/agent 产生,server 先写 agent_run_event(带 seq)再推 SSE。 // 这里是唯一定义;agent 包的 onEvent 回调直接收 AgentRunEvent。 export const AgentRunEventType = z.enum([ "run_started", "text_delta", "thinking_delta", "tool_call_start", "tool_call_end", "tool_call_rejected", "compaction", "run_completed", "run_failed", "subagent_spawned", "subagent_event", "subagent_completed", "plan_updated", "options_proposed", "options_resolved", "approval_requested", "approval_resolved", ]); export type AgentRunEventType = z.infer; /** 一次运行累计用量(数值都齐全;写 agent_run.usage 时可直接用,是 AgentUsage 的子集形状) */ export const AgentUsageTotals = z.object({ inputTokens: NonNegInt, outputTokens: NonNegInt, cacheReadTokens: NonNegInt, cacheWriteTokens: NonNegInt, totalTokens: NonNegInt, costUsd: z.number().nonnegative(), }); export type AgentUsageTotals = z.infer; export const AgentFailureReason = z.enum(["budget", "aborted", "error"]); export type AgentFailureReason = z.infer; export const AgentBudgetKind = z.enum(["tool_calls", "duration", "tokens", "cost"]); export type AgentBudgetKind = z.infer; const RunStarted = z.object({ type: z.literal("run_started"), goal: z.string(), resumed: z.boolean() }); const TextDelta = z.object({ type: z.literal("text_delta"), delta: z.string() }); const ThinkingDelta = z.object({ type: z.literal("thinking_delta"), delta: z.string() }); const ToolCallStart = z.object({ type: z.literal("tool_call_start"), toolCallId: z.string(), capabilityId: z.string(), /** 已脱敏、截断的输入摘要 */ inputSummary: z.string(), /** 崩溃恢复后重放/补记的调用 */ recovery: z.boolean(), }); const ToolCallEnd = z.object({ type: z.literal("tool_call_end"), toolCallId: z.string(), capabilityId: z.string(), isError: z.boolean(), /** 已脱敏、截断的输出摘要 */ outputSummary: z.string(), recovery: z.boolean(), /** 工具执行耗时(毫秒,单调时钟;没真正执行的调用没有) */ durationMs: z.number().int().nonnegative().optional(), }); const ToolCallRejected = z.object({ type: z.literal("tool_call_rejected"), toolCallId: z.string(), toolName: z.string(), capabilityId: z.string().optional(), reason: z.string(), }); const Compaction = z.object({ type: z.literal("compaction"), phase: z.enum(["start", "end"]), reason: z.enum(["manual", "threshold", "overflow"]), status: z.enum(["completed", "declined", "aborted", "failed"]).optional(), }); const RunCompleted = z.object({ type: z.literal("run_completed"), finalText: z.string(), usage: AgentUsageTotals }); const RunFailed = z.object({ type: z.literal("run_failed"), reason: AgentFailureReason, message: z.string(), usage: AgentUsageTotals, budget: AgentBudgetKind.optional(), }); // 子代理(agent.spawn):子会话的事件镜像进父会话事件流,由父会话的运行进程按父 seq 写入(单写者,seq 不冲突)。 const SubagentSpawned = z.object({ type: z.literal("subagent_spawned"), childRunId: z.string(), goal: z.string(), mode: z.enum(["execute", "plan_only"]), depth: NonNegInt, capabilities: z.array(z.string()), /** 从父会话划拨给子会话的预算 */ budget: z.record(z.string(), z.number()), }); /** 子会话的一条事件(工具调用 / 开始 / 结束);childSeq 是子会话内的 seq,正文已脱敏截断 */ const SubagentEvent = z.object({ type: z.literal("subagent_event"), childRunId: z.string(), childSeq: NonNegInt, childType: z.string(), capabilityId: z.string().optional(), summary: z.string(), }); const SubagentCompleted = z.object({ type: z.literal("subagent_completed"), childRunId: z.string(), status: z.string(), /** 子会话汇报摘要(业务核查结论 + 未经核查的模型文本),已脱敏截断 */ summary: z.string(), usage: AgentUsageTotals, toolCalls: NonNegInt, }); // 自动化优先的会话交互(AG-2):模型用 agent.plan 维护步骤清单,用 agent.propose_options 让用户在方案里选; // 需审批的能力在对应步骤处出审批卡。这些事件都由会话的运行进程写入(单写者):用户在挂起期间作的选择 / 审批, // 在会话恢复、重放挂起的调用时才记为 *_resolved。 export const AgentPlanStepStatus = z.enum(["pending", "running", "done", "skipped", "failed"]); export type AgentPlanStepStatus = z.infer; /** 步骤清单的一步;id 由模型给,同一会话内稳定(更新时按 id 合并) */ export const AgentPlanStep = z.object({ id: z.string().trim().min(1).max(40), title: z.string().trim().min(1).max(200), status: AgentPlanStepStatus, note: z.string().max(500).nullable().default(null), }); export type AgentPlanStep = z.infer; /** * 方案绑定的待审批动作(AGUI-3):模型只给能力与计划 ID;服务端在提出方案时按计划当前的 payload hash + revision * 绑定,并用计划本身生成动作要点(对象、大小)。管理员会话选定这个方案 = 批准这一份计划,执行时服务端再核对绑定。 */ export const AgentOptionActionCapability = z.enum(["download.submit", "organize.execute", "policy.apply"]); export const AgentOptionAction = z.object({ capabilityId: AgentOptionActionCapability, /** download.submit / organize.execute 的 planId;policy.apply 的 policyRevisionId */ planId: Id, }); export type AgentOptionAction = z.infer; /** 给用户选的一个方案 */ export const AgentOptionItem = z.object({ id: z.string().trim().min(1).max(40), title: z.string().trim().min(1).max(200), summary: z.string().trim().min(1).max(1000), risks: z.array(z.string().max(300)).max(8).default([]), /** 估算代价(体积 / 时长 / 费用),模型给出的文本,未经核查 */ estimatedCost: z.string().max(200).nullable().default(null), /** 选定后要执行的需审批动作(可选) */ action: AgentOptionAction.nullable().optional(), }); export type AgentOptionItem = z.infer; /** 事件里的方案动作:服务端从计划生成的要点;approves = 选定即批准(删除、覆盖类动作为 false,仍单独确认) */ export const AgentOptionActionView = AgentOptionAction.extend({ /** 动作名,如「提交下载」 */ label: z.string(), /** 动作要点:对象与大小 / 数量,如「《沙丘:第二部》· 12.0 GB」 */ detail: z.string().nullable(), approves: z.boolean(), }); export type AgentOptionActionView = z.infer; export const AgentOptionView = AgentOptionItem.extend({ action: AgentOptionActionView.nullable().optional() }); export type AgentOptionView = z.infer; /** 超时未选时的处理:recommended = 自动选推荐项;stop = 不选,模型收尾 */ export const AgentOptionsTimeoutAction = z.enum(["recommended", "stop"]); export type AgentOptionsTimeoutAction = z.infer; const PlanUpdated = z.object({ type: z.literal("plan_updated"), steps: z.array(AgentPlanStep), /** 本会话第几版计划(从 1 起) */ revision: z.number().int().min(1), }); const OptionsProposed = z.object({ type: z.literal("options_proposed"), /** 挂起的工具调用(恢复时重放) */ invocationId: z.string(), /** 对应的待处理事项(用户也可以在待处理中心选) */ decisionId: Id, question: z.string(), options: z.array(AgentOptionView), recommendedId: z.string().nullable(), /** 出现在哪一步(提出时正在进行的步骤) */ stepId: z.string().nullable(), expiresAt: Timestamp, timeoutAction: AgentOptionsTimeoutAction, }); const OptionsResolved = z.object({ type: z.literal("options_resolved"), invocationId: z.string(), decisionId: Id, /** chosen = 选了 choice;rejected = 都不要(reason 是用户说明);stop = 超时按设置停止 */ outcome: z.enum(["chosen", "rejected", "stop"]), choice: z.string().nullable(), reason: z.string().nullable(), by: z.enum(["user", "timeout"]), }); const ApprovalRequested = z.object({ type: z.literal("approval_requested"), approvalId: Id, decisionId: Id.nullable(), invocationId: z.string(), capabilityId: z.string(), planType: z.string(), planId: Id, payloadHash: z.string(), expectedRevision: z.number().int().min(0), riskLevel: z.enum(["low", "medium", "high"]), /** 计划摘要(已脱敏截断) */ summary: z.string(), stepId: z.string().nullable(), expiresAt: Timestamp, }); const ApprovalResolved = z.object({ type: z.literal("approval_resolved"), approvalId: Id, status: z.enum(["approved", "rejected", "expired", "revoked", "invalid"]), reason: z.string().nullable(), decidedBy: z.string().nullable(), }); /** 事件主体(不含 runId/seq/at) */ export const AgentRunEventBody = z.discriminatedUnion("type", [ RunStarted, TextDelta, ThinkingDelta, ToolCallStart, ToolCallEnd, ToolCallRejected, Compaction, RunCompleted, RunFailed, SubagentSpawned, SubagentEvent, SubagentCompleted, PlanUpdated, OptionsProposed, OptionsResolved, ApprovalRequested, ApprovalResolved, ]); export type AgentRunEventBody = z.infer; const EventMeta = { /** = agent_run.id */ runId: z.string().min(1), /** 该 run 内从 0 起单调递增;续跑时从 agent_run.last_event_seq + 1 继续 */ seq: NonNegInt, at: Timestamp, }; /** 完整事件:主体 + runId/seq/at */ export const AgentRunEvent = z.discriminatedUnion("type", [ RunStarted.extend(EventMeta), TextDelta.extend(EventMeta), ThinkingDelta.extend(EventMeta), ToolCallStart.extend(EventMeta), ToolCallEnd.extend(EventMeta), ToolCallRejected.extend(EventMeta), Compaction.extend(EventMeta), RunCompleted.extend(EventMeta), RunFailed.extend(EventMeta), SubagentSpawned.extend(EventMeta), SubagentEvent.extend(EventMeta), SubagentCompleted.extend(EventMeta), PlanUpdated.extend(EventMeta), OptionsProposed.extend(EventMeta), OptionsResolved.extend(EventMeta), ApprovalRequested.extend(EventMeta), ApprovalResolved.extend(EventMeta), ]); export type AgentRunEvent = AgentRunEventBody & { runId: string; seq: number; at: string }; /** agent_run_event 行:(run_id, seq) 唯一;payload = 事件主体去掉 type */ export const AgentRunEventRow = z.object({ runId: Id, seq: NonNegInt, type: AgentRunEventType, at: Timestamp, payload: JsonObject, }); export type AgentRunEventRow = z.infer; export function agentRunEventToRow(e: AgentRunEvent): AgentRunEventRow { const { runId, seq, at, type, ...payload } = e; return { runId, seq, at, type, payload }; } /** 行 → 事件;payload 不合法时抛错(表里只应有 agentRunEventToRow 写入的内容) */ export function agentRunEventFromRow(row: AgentRunEventRow): AgentRunEvent { return AgentRunEvent.parse({ ...row.payload, type: row.type, runId: row.runId, seq: row.seq, at: row.at }); } /** 工具调用事件 → SSE agent.tool_call 的 phase */ export function toolCallPhase(e: AgentRunEventBody): "started" | "succeeded" | "failed" | "denied" | null { switch (e.type) { case "tool_call_start": return "started"; case "tool_call_end": return e.isError ? "failed" : "succeeded"; case "tool_call_rejected": return "denied"; default: return null; } } /** run_completed / run_failed / run_started → agent_run.status */ export function agentRunStatusOf(e: AgentRunEventBody): AgentRunStatus | null { switch (e.type) { case "run_started": return "running"; case "run_completed": return "succeeded"; case "run_failed": return e.reason === "budget" ? "budget_exceeded" : e.reason === "aborted" ? "cancelled" : "failed"; default: return null; } } /** * Agent 运行事件 → 全局 SSE 事件(agent.run_status / agent.text_delta / agent.tool_call)。 * thinking_delta 与 compaction 不上全局总线(只在 run 详情里按 agent_run_event 回放),返回 null。 */ export function agentRunEventToBusEvent(e: AgentRunEvent): TypedEvent | null { const phase = toolCallPhase(e); if (phase) { let capabilityId = ""; let summary = ""; if (e.type === "tool_call_start") [capabilityId, summary] = [e.capabilityId, e.inputSummary]; else if (e.type === "tool_call_end") [capabilityId, summary] = [e.capabilityId, e.outputSummary]; else if (e.type === "tool_call_rejected") [capabilityId, summary] = [e.capabilityId ?? e.toolName, e.reason]; return { type: "agent.tool_call", payload: { runId: e.runId, runSeq: e.seq, capabilityId, phase, summary } }; } // 子会话事件:借 agent.tool_call(能力 agent.spawn)提示父会话页"有新事件",页面按 seq 补读 if (e.type === "subagent_spawned" || e.type === "subagent_event" || e.type === "subagent_completed") { const phase = e.type === "subagent_completed" ? (e.status === "succeeded" ? "succeeded" : "failed") : ("started" as const); const summary = e.type === "subagent_spawned" ? `派生子会话 ${e.childRunId}:${e.goal}` : e.type === "subagent_event" ? `子会话 ${e.childRunId}:${e.childType} ${e.capabilityId ?? ""} ${e.summary}`.trim() : `子会话 ${e.childRunId} ${e.status}:${e.summary}`; return { type: "agent.tool_call", payload: { runId: e.runId, runSeq: e.seq, capabilityId: "agent.spawn", phase, summary: summary.slice(0, 600) }, }; } // 计划 / 方案 / 审批:借 agent.tool_call 提示会话页"有新事件"(页面按 seq 补读),摘要给全局通知用 const hint = interactionHint(e); if (hint) { return { type: "agent.tool_call", payload: { runId: e.runId, runSeq: e.seq, capabilityId: hint.capabilityId, phase: hint.phase, summary: hint.summary, }, }; } if (e.type === "text_delta") { return { type: "agent.text_delta", payload: { runId: e.runId, runSeq: e.seq, delta: e.delta } }; } const status = agentRunStatusOf(e); if (status) { const summary = e.type === "run_completed" ? e.finalText : e.type === "run_failed" ? e.message : null; return { type: "agent.run_status", payload: { runId: e.runId, status, summary } }; } return null; } function interactionHint( e: AgentRunEventBody, ): { capabilityId: string; phase: "started" | "succeeded" | "failed" | "denied"; summary: string } | null { switch (e.type) { case "plan_updated": { const done = e.steps.filter((s) => s.status === "done").length; return { capabilityId: "agent.plan", phase: "succeeded", summary: `步骤 ${done}/${e.steps.length}` }; } case "options_proposed": return { capabilityId: "agent.propose_options", phase: "started", summary: e.question.slice(0, 600) }; case "options_resolved": return { capabilityId: "agent.propose_options", phase: e.outcome === "chosen" ? "succeeded" : "failed", summary: e.outcome === "chosen" ? `已选 ${e.choice}` : e.outcome === "rejected" ? "都不要" : "超时未选", }; case "approval_requested": return { capabilityId: e.capabilityId, phase: "started", summary: `等待审批:${e.summary}`.slice(0, 600) }; case "approval_resolved": return { capabilityId: "approval", phase: e.status === "approved" ? "succeeded" : "denied", summary: `审批${e.status}${e.reason ? `:${e.reason}` : ""}`.slice(0, 600), }; default: return null; } }