import { Hono } from 'hono'; import type { Bindings } from '../types'; import { generateToken, validateAndParseWebhook, executeWebhookGraph } from '../actions/webhook-handlers'; import { resolveWebhookGraph } from '../actions/webhook-graph-resolver'; import { writeExecutionVerdict } from '../actions/execution-logger'; export const webhooksRouter = new Hono<{ Bindings: Bindings }>(); type WebhookRecord = { graph: Record; description: string; created_at: string; }; // POST /webhooks — 接受 graph、triplets 或直接 nodes/edges webhooksRouter.post('/webhooks', async (c) => { const body = await c.req.json().catch(() => null); if (!body) return c.json({ error: 'invalid json' }, 400); const description = typeof body.description === 'string' ? body.description : ''; const resolved = await resolveWebhookGraph(body as Record, description, c.env); if (resolved.error) { return c.json({ error: resolved.error }, 400); } const token = generateToken(); const record: WebhookRecord = { graph: resolved.resolvedGraph, description, created_at: new Date().toISOString(), }; await c.env.WEBHOOKS.put(token, JSON.stringify(record)); const baseUrl = new URL(c.req.url).origin; return c.json({ token, webhook_url: `${baseUrl}/webhooks/${token}/trigger`, description: record.description, created_at: record.created_at, }, 201); }); // POST /webhooks/:token/trigger — 觸發執行 webhooksRouter.post('/webhooks/:token/trigger', async (c) => { const token = c.req.param('token'); if (!token || token.length < 16) { return c.json({ error: 'invalid token' }, 400); } const raw = await c.env.WEBHOOKS.get(token, 'text'); if (!raw) return c.json({ error: 'webhook not found' }, 404); const record = await validateAndParseWebhook(raw); if (!record) return c.json({ error: 'webhook 定義損毀' }, 500); let triggerContext: Record = {}; try { const body = await c.req.json().catch(() => null); if (body && typeof body === 'object') { triggerContext = body as Record; } } catch { // 無 body 時使用空 context } const apiKey = c.req.header('X-Arcrun-API-Key') ?? undefined; const result = await executeWebhookGraph(c.env, record.graph, triggerContext, token, apiKey); // fire-and-forget analytics(不阻擋回應) const graph = record.graph as { id?: string; nodes?: unknown[] }; const workflowId = graph.id ?? token; const nodes = Array.isArray(graph.nodes) ? (graph.nodes as import('../types').GraphNode[]) : []; c.executionCtx.waitUntil( writeExecutionVerdict(c.env, workflowId, nodes, result.success ? 'success' : 'failed', result.duration_ms, result.error ?? '', triggerContext, apiKey), ); return c.json(result, result.success ? 200 : 500); });