@@ -20,8 +20,6 @@ import {
2020 requireBillingAttributionHeader ,
2121} from '@/lib/billing/core/billing-attribution'
2222import { admissionRejectedResponse , tryAdmit } from '@/lib/core/admission/gate'
23- import { getJobQueue , shouldExecuteInline } from '@/lib/core/async-jobs'
24- import { isAsyncJobEnqueueError } from '@/lib/core/async-jobs/types'
2523import {
2624 createTimeoutAbortController ,
2725 getTimeoutErrorMessage ,
@@ -69,6 +67,7 @@ import {
6967 hydrateUserFilesWithBase64 ,
7068} from '@/lib/uploads/utils/user-file-base64.server'
7169import { getCustomBlockRowsForWorkspace } from '@/lib/workflows/custom-blocks/operations'
70+ import { enqueueWorkflowExecution } from '@/lib/workflows/executor/enqueue-execution'
7271import { executeWorkflow } from '@/lib/workflows/executor/execute-workflow'
7372import { executeWorkflowCore } from '@/lib/workflows/executor/execution-core'
7473import {
@@ -105,7 +104,6 @@ import {
105104} from '@/lib/workflows/streaming/streaming'
106105import { createHttpResponseFromBlock , workflowHasResponseBlock } from '@/lib/workflows/utils'
107106import { getWorkspaceBillingSettings } from '@/lib/workspaces/utils'
108- import { executeWorkflowJob , type WorkflowExecutionPayload } from '@/background/workflow-execution'
109107import { withCustomBlockOverlay } from '@/blocks/custom/server-overlay'
110108import {
111109 PublicApiNotAllowedError ,
@@ -127,8 +125,6 @@ import { CORE_TRIGGER_TYPES, type CoreTriggerType } from '@/stores/logs/filters/
127125const logger = createLogger ( 'WorkflowExecuteAPI' )
128126const MAX_WORKFLOW_EXECUTE_BODY_BYTES = 10 * 1024 * 1024
129127const SERVER_EXECUTION_ID_CLAIM_ATTEMPTS = 3
130- const ASYNC_ENQUEUE_ATTEMPTS = 2
131- const WORKFLOW_EXECUTION_JOB_ID_PREFIX = 'workflow-execution:'
132128
133129export const runtime = 'nodejs'
134130export const dynamic = 'force-dynamic'
@@ -338,170 +334,38 @@ function requirePreprocessedExecutionContext(
338334}
339335
340336async function handleAsyncExecution ( params : AsyncExecutionParams ) : Promise < AsyncExecutionResult > {
341- const {
342- requestId,
343- workflowId,
344- userId,
345- billingAttribution,
346- workspaceId,
347- input,
348- triggerType,
349- executionId,
350- callChain,
351- } = params
352- const asyncLogger = logger . withMetadata ( {
353- requestId,
354- workflowId,
355- workspaceId,
356- userId,
357- executionId,
358- } )
359-
360- const correlation = {
361- executionId,
362- requestId,
363- source : 'workflow' as const ,
364- workflowId,
365- triggerType,
366- }
367-
368- const payload : WorkflowExecutionPayload = {
369- workflowId,
370- userId,
371- billingAttribution,
372- workspaceId,
373- input,
374- triggerType,
375- executionId,
376- requestId,
377- correlation,
378- callChain,
379- executionMode : 'async' ,
380- admissionCompleted : true ,
381- }
337+ const enqueue = await enqueueWorkflowExecution ( params )
382338
383- let jobQueue : Awaited < ReturnType < typeof getJobQueue > >
384- try {
385- jobQueue = await getJobQueue ( )
386- } catch ( error ) {
387- asyncLogger . error ( 'Failed to initialize async execution queue' , {
388- error : toError ( error ) . message ,
389- } )
390- await releaseExecutionSlot ( executionId )
339+ if ( enqueue . outcome === 'rejected' ) {
391340 return {
392341 response : NextResponse . json ( { error : 'Failed to queue async execution' } , { status : 500 } ) ,
393342 retainExecutionClaim : false ,
394343 }
395344 }
396345
397- const deterministicJobId = `${ WORKFLOW_EXECUTION_JOB_ID_PREFIX } ${ executionId } `
398- const enqueueOptions = {
399- jobId : deterministicJobId ,
400- metadata : { workflowId, workspaceId, userId, correlation } ,
401- }
402- let jobId : string | undefined
403- let enqueueError : unknown
404- let acceptanceCouldBeUnknown = false
405-
406- for ( let attempt = 1 ; attempt <= ASYNC_ENQUEUE_ATTEMPTS ; attempt ++ ) {
407- try {
408- jobId = await jobQueue . enqueue ( 'workflow-execution' , payload , enqueueOptions )
409- enqueueError = undefined
410- break
411- } catch ( error ) {
412- enqueueError = error
413- const classifiedError = isAsyncJobEnqueueError ( error ) ? error : undefined
414- const attemptAcceptance = classifiedError ?. acceptance ?? 'unknown'
415- acceptanceCouldBeUnknown ||= attemptAcceptance === 'unknown'
416- asyncLogger . warn ( 'Async workflow enqueue attempt failed' , {
417- acceptance : attemptAcceptance ,
418- attempt,
419- error : toError ( error ) . message ,
420- jobId : deterministicJobId ,
421- } )
422- if ( classifiedError ?. retryable === false || attempt === ASYNC_ENQUEUE_ATTEMPTS ) {
423- break
424- }
425- }
426- }
427-
428- if ( ! jobId ) {
429- const acceptance = acceptanceCouldBeUnknown
430- ? 'unknown'
431- : isAsyncJobEnqueueError ( enqueueError )
432- ? enqueueError . acceptance
433- : 'unknown'
434- asyncLogger . error ( 'Failed to queue async execution' , {
435- acceptance,
436- error : toError ( enqueueError ) . message ,
437- jobId : deterministicJobId ,
438- } )
439-
440- if ( acceptance === 'rejected' ) {
441- await releaseExecutionSlot ( executionId )
442- return {
443- response : NextResponse . json ( { error : 'Failed to queue async execution' } , { status : 500 } ) ,
444- retainExecutionClaim : false ,
445- }
446- }
447-
346+ if ( enqueue . outcome === 'ambiguous' ) {
448347 return {
449348 response : NextResponse . json (
450349 {
451350 error : 'Async execution queue acceptance could not be confirmed' ,
452351 code : 'ASYNC_ENQUEUE_AMBIGUOUS' ,
453- executionId,
352+ executionId : enqueue . executionId ,
454353 } ,
455- { status : 503 , headers : { [ WORKFLOW_EXECUTION_ID_HEADER ] : executionId } }
354+ { status : 503 , headers : { [ WORKFLOW_EXECUTION_ID_HEADER ] : enqueue . executionId } }
456355 ) ,
457356 retainExecutionClaim : true ,
458357 }
459358 }
460359
461- asyncLogger . info ( 'Queued async workflow execution' , { jobId } )
462-
463- if ( shouldExecuteInline ( ) ) {
464- void ( async ( ) => {
465- let workerOwnsReservation = false
466- try {
467- await jobQueue . startJob ( jobId )
468- workerOwnsReservation = true
469- const output = await executeWorkflowJob ( payload )
470- await jobQueue . completeJob ( jobId , output )
471- } catch ( error ) {
472- const errorMessage = toError ( error ) . message
473- asyncLogger . error ( 'Async workflow execution failed' , {
474- jobId,
475- error : errorMessage ,
476- } )
477- /**
478- * Before worker ownership transfers, no LoggingSession exists to
479- * release the route's reservation.
480- */
481- if ( ! workerOwnsReservation ) {
482- await releaseExecutionSlot ( executionId )
483- }
484- try {
485- await jobQueue . markJobFailed ( jobId , errorMessage )
486- } catch ( markFailedError ) {
487- asyncLogger . error ( 'Failed to mark job as failed' , {
488- jobId,
489- error : toError ( markFailedError ) . message ,
490- } )
491- }
492- }
493- } ) ( )
494- }
495-
496360 return {
497361 response : NextResponse . json (
498362 {
499363 success : true ,
500364 async : true ,
501- jobId,
502- executionId,
365+ jobId : enqueue . jobId ,
366+ executionId : enqueue . executionId ,
503367 message : 'Workflow execution queued' ,
504- statusUrl : `${ getBaseUrl ( ) } /api/jobs/${ jobId } ` ,
368+ statusUrl : `${ getBaseUrl ( ) } /api/jobs/${ enqueue . jobId } ` ,
505369 } ,
506370 { status : 202 }
507371 ) ,
0 commit comments