import type { Bindings, ExecutionGraph, ExecutionContext } from '../types'; import { ExecutionError } from '../types'; import { GraphExecutor } from '../graph-executor'; import { graphSchema } from '../lib/schemas'; import { createComponentLoader } from '../lib/component-loader'; import { recordTelemetry } from '../lib/telemetry'; type WebhookRecord = { graph: Record; description: string; created_at: string; }; export function generateToken(): string { const tokenBytes = crypto.getRandomValues(new Uint8Array(16)); return Array.from(tokenBytes).map(b => b.toString(16).padStart(2, '0')).join(''); } export async function validateAndParseWebhook(raw: string): Promise { try { return JSON.parse(raw) as WebhookRecord; } catch { return null; } } export async function executeWebhookGraph( env: Bindings, graph: Record, triggerContext: Record, token: string, apiKey?: string, ctx?: ExecutionContext, // 可選 — 用 waitUntil 把 telemetry 推到背景 userAgent?: string, // MCP / SDK client 帶過來 ): Promise<{ success: boolean; data?: unknown; error?: string; trace?: unknown; duration_ms: number }> { const parsed = graphSchema.safeParse(graph); if (!parsed.success) { return { success: false, error: '圖定義已失效', duration_ms: 0 }; } const loader = createComponentLoader(env); const executor = new GraphExecutor(loader, undefined, env, apiKey); const start = Date.now(); try { const result = await executor.execute( parsed.data as ExecutionGraph, { ...triggerContext, _webhook_token: token }, env.EXEC_CONTEXT, ); const duration_ms = Date.now() - start; // Implicit telemetry:成功 run(含 paused 也算「成功啟動」由 trigger_workflow 那層分類) recordTelemetry(env, apiKey, { event_type: 'run_success', workflow_name: token, duration_ms, agent_user_agent: userAgent, }, ctx); return { success: true, data: result.data, duration_ms }; } catch (err) { const duration_ms = Date.now() - start; const errMsg = err instanceof Error ? err.message : String(err); const isPaused = /workflow paused/i.test(errMsg); // Implicit telemetry:paused 算 run_success;真錯才 run_fail recordTelemetry(env, apiKey, { event_type: isPaused ? 'run_success' : 'run_fail', workflow_name: token, error_code: isPaused ? 'paused_awaiting_resume' : 'execution_error', duration_ms, agent_user_agent: userAgent, }, ctx); if (err instanceof ExecutionError) { const traceFormatted = err.trace.map(s => ({ node: s.nodeId, status: s.error ? 'failed' : 'success', ...(s.error ? { error: s.error } : {}), })); return { success: false, error: errMsg, trace: traceFormatted, duration_ms, }; } return { success: false, error: errMsg, duration_ms }; } }