import { Hono } from 'hono'; import type { Bindings, ExecutionGraph } from '../types'; import { ExecutionError } from '../types'; import { GraphExecutor } from '../graph-executor'; import { executeSchema } from '../lib/schemas'; import { createComponentLoader } from '../lib/component-loader'; import { writeExecutionVerdict } from '../actions/execution-logger'; export const executeRouter = new Hono<{ Bindings: Bindings }>(); // POST /execute — 執行一個完整的圖 executeRouter.post('/execute', async (c) => { const body = await c.req.json(); const parsed = executeSchema.safeParse(body); if (!parsed.success) { return c.json({ error: '圖定義驗證失敗', details: parsed.error.issues }, 400); } const { graph, context } = parsed.data; const apiKey = c.req.header('x-arcrun-api-key') ?? undefined; const loader = createComponentLoader(c.env); const executor = new GraphExecutor(loader, undefined, c.env, apiKey); const start = Date.now(); try { // BUILD-006:傳入 KV namespace(若不存在則 fallback 到記憶體 merge) const result = await executor.execute(graph as ExecutionGraph, context, c.env.EXEC_CONTEXT); const duration_ms = Date.now() - start; c.executionCtx.waitUntil( writeExecutionVerdict(c.env, graph.id, graph.nodes, 'success', duration_ms, '執行完成') ); return c.json({ success: true, data: result.data, trace: result.trace, duration_ms }); } catch (err) { const duration_ms = Date.now() - start; const errMsg = err instanceof Error ? err.message : String(err); c.executionCtx.waitUntil( writeExecutionVerdict(c.env, graph.id, graph.nodes, 'failed', duration_ms, errMsg.slice(0, 100)) ); if (err instanceof ExecutionError) { const traceFormatted = err.trace.map(s => ({ node: s.nodeId, status: s.error ? 'failed' : 'success', ...(s.error ? { error: s.error } : {}), })); return c.json({ success: false, error: errMsg, failed_node: err.failed_node, failed_input: err.failed_input, trace: traceFormatted, duration_ms, }, 500); } return c.json({ success: false, error: errMsg, failed_node: null, trace: [], duration_ms }, 500); } });