import apiClient from './apiClient' import { SseClient } from './sseClient' import type { AgentRun, AgentEvent, AgentTraceResponse, ApiAgentRun, OperationResponse, PageMeta, ToolDefinition, PermissionRequest } from '@/contracts' function toAgentRun(run: ApiAgentRun): AgentRun { // API 的 token_usage 是累计值,UI 模型预留了输入/输出拆分字段。 return { run_id: run.run_id, status: run.status, current_step: run.current_step, max_steps: run.max_steps, token_usage: { input_tokens: 0, output_tokens: 0, total_tokens: run.token_usage, }, started_at: run.created_at, completed_at: ['completed', 'failed', 'cancelled'].includes(run.status) ? run.updated_at : undefined, error: run.error_message ?? undefined, } } export async function listAgentRuns(params?: { limit?: number offset?: number }): Promise<{ items: AgentRun[]; total: number }> { const response = await apiClient.get<{ items: ApiAgentRun[]; page: PageMeta }>('/api/agent/runs', { params }) return { items: response.items.map(toAgentRun), total: response.page.total } } export async function getAgentRun(runId: string): Promise { return toAgentRun(await apiClient.get(`/api/agent/runs/${runId}`)) } export interface CreateAgentRunRequest { input: string provider_id: string model: string skill_id?: string allowed_tools?: string[] max_steps?: number tool_timeout_seconds?: number run_timeout_seconds?: number token_budget?: number allow_network?: boolean max_concurrent_tools?: number } export async function createAgentRun(request: CreateAgentRunRequest): Promise { return toAgentRun(await apiClient.post('/api/agent/runs', request)) } export async function cancelAgentRun(runId: string): Promise { return apiClient.post(`/api/agent/runs/${runId}/cancel`) } export async function getAgentTrace( runId: string, params?: { after_sequence?: number; limit?: number }, ): Promise { return apiClient.get(`/api/agent/runs/${runId}/trace`, { params }) } export async function listTools(): Promise { const response = await apiClient.get<{ items: ToolDefinition[] }>('/api/tools') return response.items } export function streamAgentEvents( runId: string, handlers: { onEvent?: (event: AgentEvent) => void onError?: (error: Error) => void onDone?: () => void onOpen?: () => void }, afterSequence = -1, ): SseClient { // 将通用 SSE 包装成领域事件,Store 无需了解传输层 envelope。 const client = new SseClient({ url: `/api/agent/runs/${runId}/events?after_sequence=${afterSequence}`, method: 'GET', lastEventId: afterSequence >= 0 ? String(afterSequence) : undefined, onEvent: (eventName, data) => { handlers.onEvent?.({ event: eventName as AgentEvent['event'], sequence: (data.sequence as number) || 0, run_id: (data.run_id as string) || runId, data: (data.data || data) as Record, timestamp: (data.timestamp as string) || new Date().toISOString(), }) }, onError: handlers.onError, onDone: handlers.onDone, onOpen: handlers.onOpen, }) client.connect().catch(() => {}) return client } export async function respondToPermission( runId: string, requestId: string, decision: 'allow_once' | 'allow_session' | 'deny' ): Promise { return apiClient.post(`/api/agent/runs/${runId}/permissions/${requestId}`, { decision, }) } export const mockTools: ToolDefinition[] = [ { name: 'notes.search', description: '搜索笔记,支持关键词和语义检索', parameters: { type: 'object', properties: { query: { type: 'string', description: '搜索关键词' }, limit: { type: 'number', description: '返回结果数量' }, }, required: ['query'], }, source: 'builtin', }, { name: 'notes.read', description: '读取指定笔记的完整内容', parameters: { type: 'object', properties: { note_id: { type: 'string' }, }, required: ['note_id'], }, source: 'builtin', }, { name: 'notes.create', description: '创建新笔记', parameters: { type: 'object', properties: { title: { type: 'string' }, content: { type: 'string' }, folder_path: { type: 'string' }, }, required: ['title', 'content'], }, source: 'builtin', }, { name: 'rag.search', description: '基于 RAG 的语义检索,返回相关知识片段', parameters: { type: 'object', properties: { query: { type: 'string' }, top_k: { type: 'number' }, }, required: ['query'], }, source: 'builtin', }, { name: 'tasks.create', description: '创建任务', parameters: { type: 'object', properties: { title: { type: 'string' }, description: { type: 'string' }, priority: { type: 'string', enum: ['low', 'medium', 'high'] }, }, required: ['title'], }, source: 'builtin', }, { name: 'system.echo', description: '回显输入内容(测试用)', parameters: { type: 'object', properties: { text: { type: 'string' }, }, required: ['text'], }, source: 'builtin', }, { name: 'math.add', description: '两数相加(测试用)', parameters: { type: 'object', properties: { a: { type: 'number' }, b: { type: 'number' }, }, required: ['a', 'b'], }, source: 'builtin', }, ] export const mockAgentRuns: AgentRun[] = [ { run_id: 'run-1', status: 'completed', current_step: 3, max_steps: 10, token_usage: { input_tokens: 2340, output_tokens: 890, total_tokens: 3230 }, started_at: '2026-08-25T11:00:00Z', completed_at: '2026-08-25T11:02:30Z', }, { run_id: 'run-2', status: 'running', current_step: 2, max_steps: 10, token_usage: { input_tokens: 1500, output_tokens: 420, total_tokens: 1920 }, started_at: '2026-08-26T09:30:00Z', }, ] export const mockAgentEvents: AgentEvent[] = [ { event: 'RunStarted', sequence: 1, run_id: 'run-1', data: { task: '帮我整理红黑树的核心知识点' }, timestamp: '2026-08-25T11:00:00Z', }, { event: 'ThinkingDelta', sequence: 2, run_id: 'run-1', data: { text: '我需要先搜索笔记中关于红黑树的内容...' }, timestamp: '2026-08-25T11:00:01Z', }, { event: 'ToolCall', sequence: 3, run_id: 'run-1', data: { tool_call_id: 'tc-1', name: 'notes.search', parameters: { query: '红黑树 插入 删除', limit: 5 }, status: 'running', }, timestamp: '2026-08-25T11:00:02Z', }, { event: 'ToolResult', sequence: 4, run_id: 'run-1', data: { tool_call_id: 'tc-1', name: 'notes.search', status: 'completed', result: '找到 5 条相关结果,包括红黑树性质、插入操作、删除操作等...', duration_ms: 320, }, timestamp: '2026-08-25T11:00:02Z', }, { event: 'Citation', sequence: 5, run_id: 'run-1', data: { note_id: 'n-rbt', block_id: 'b1', heading_path: '数据结构 / 红黑树 / 性质', }, timestamp: '2026-08-25T11:00:03Z', }, { event: 'ThinkingDelta', sequence: 6, run_id: 'run-1', data: { text: '搜索结果很全面,让我整理一下结构...' }, timestamp: '2026-08-25T11:00:03Z', }, { event: 'TextDelta', sequence: 7, run_id: 'run-1', data: { text: '## 红黑树核心知识点整理\n\n### 1. 基本性质\n红黑树是一种自平衡二叉搜索树,每个节点带有颜色属性...' }, timestamp: '2026-08-25T11:00:04Z', }, { event: 'Usage', sequence: 8, run_id: 'run-1', data: { input_tokens: 2340, output_tokens: 890, total_tokens: 3230 }, timestamp: '2026-08-25T11:02:30Z', }, { event: 'RunCompleted', sequence: 9, run_id: 'run-1', data: { message: 'Task completed successfully' }, timestamp: '2026-08-25T11:02:30Z', }, ] export const mockPermissionRequest: PermissionRequest = { request_id: 'perm-1', run_id: 'run-2', tool_name: 'notes.create', permission: 'notes.write', parameters: { title: '红黑树知识点总结', folder_path: '/数据结构' }, impact: '将在你的知识库中创建一篇新笔记', }