chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,503 @@
|
||||
import type {
|
||||
ChildWorkflowContext,
|
||||
IterationContext,
|
||||
ParentIteration,
|
||||
} from '@/executor/execution/types'
|
||||
import type { BlockLog } from '@/executor/types'
|
||||
import type { SubflowType } from '@/stores/workflows/workflow/types'
|
||||
|
||||
export type ExecutionEventType =
|
||||
| 'execution:started'
|
||||
| 'execution:completed'
|
||||
| 'execution:paused'
|
||||
| 'execution:error'
|
||||
| 'execution:cancelled'
|
||||
| 'block:started'
|
||||
| 'block:completed'
|
||||
| 'block:error'
|
||||
| 'block:childWorkflowStarted'
|
||||
| 'stream:chunk'
|
||||
| 'stream:done'
|
||||
|
||||
/**
|
||||
* Base event structure for SSE
|
||||
*/
|
||||
interface BaseExecutionEvent {
|
||||
type: ExecutionEventType
|
||||
timestamp: string
|
||||
executionId: string
|
||||
eventId?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Execution started event
|
||||
*/
|
||||
interface ExecutionStartedEvent extends BaseExecutionEvent {
|
||||
type: 'execution:started'
|
||||
workflowId: string
|
||||
data: {
|
||||
startTime: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execution completed event
|
||||
*/
|
||||
interface ExecutionCompletedEvent extends BaseExecutionEvent {
|
||||
type: 'execution:completed'
|
||||
workflowId: string
|
||||
data: {
|
||||
success: boolean
|
||||
output: any
|
||||
duration: number
|
||||
startTime: string
|
||||
endTime: string
|
||||
/** Authoritative per-block terminal states from the server's blockLogs. */
|
||||
finalBlockLogs?: BlockLog[]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execution paused event (HITL block waiting for human input)
|
||||
*/
|
||||
interface ExecutionPausedEvent extends BaseExecutionEvent {
|
||||
type: 'execution:paused'
|
||||
workflowId: string
|
||||
data: {
|
||||
output: any
|
||||
duration: number
|
||||
startTime: string
|
||||
endTime: string
|
||||
/** Authoritative per-block terminal states from the server's blockLogs. */
|
||||
finalBlockLogs?: BlockLog[]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execution error event
|
||||
*/
|
||||
interface ExecutionErrorEvent extends BaseExecutionEvent {
|
||||
type: 'execution:error'
|
||||
workflowId: string
|
||||
data: {
|
||||
error: string
|
||||
duration: number
|
||||
/** Authoritative per-block terminal states from the server's blockLogs. */
|
||||
finalBlockLogs?: BlockLog[]
|
||||
}
|
||||
}
|
||||
|
||||
interface ExecutionCancelledEvent extends BaseExecutionEvent {
|
||||
type: 'execution:cancelled'
|
||||
workflowId: string
|
||||
data: {
|
||||
duration: number
|
||||
/** Authoritative per-block terminal states from the server's blockLogs. */
|
||||
finalBlockLogs?: BlockLog[]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Block started event
|
||||
*/
|
||||
interface BlockStartedEvent extends BaseExecutionEvent {
|
||||
type: 'block:started'
|
||||
workflowId: string
|
||||
data: {
|
||||
blockId: string
|
||||
blockName: string
|
||||
blockType: string
|
||||
executionOrder: number
|
||||
iterationCurrent?: number
|
||||
iterationTotal?: number
|
||||
iterationType?: SubflowType
|
||||
iterationContainerId?: string
|
||||
parentIterations?: ParentIteration[]
|
||||
childWorkflowBlockId?: string
|
||||
childWorkflowName?: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Block completed event
|
||||
*/
|
||||
interface BlockCompletedEvent extends BaseExecutionEvent {
|
||||
type: 'block:completed'
|
||||
workflowId: string
|
||||
data: {
|
||||
blockId: string
|
||||
blockName: string
|
||||
blockType: string
|
||||
input?: any
|
||||
output: any
|
||||
durationMs: number
|
||||
startedAt: string
|
||||
executionOrder: number
|
||||
endedAt: string
|
||||
iterationCurrent?: number
|
||||
iterationTotal?: number
|
||||
iterationType?: SubflowType
|
||||
iterationContainerId?: string
|
||||
parentIterations?: ParentIteration[]
|
||||
childWorkflowBlockId?: string
|
||||
childWorkflowName?: string
|
||||
/** Per-invocation unique ID for correlating child block events with this workflow block. */
|
||||
childWorkflowInstanceId?: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Block error event
|
||||
*/
|
||||
interface BlockErrorEvent extends BaseExecutionEvent {
|
||||
type: 'block:error'
|
||||
workflowId: string
|
||||
data: {
|
||||
blockId: string
|
||||
blockName: string
|
||||
blockType: string
|
||||
input?: any
|
||||
error: string
|
||||
durationMs: number
|
||||
startedAt: string
|
||||
executionOrder: number
|
||||
endedAt: string
|
||||
iterationCurrent?: number
|
||||
iterationTotal?: number
|
||||
iterationType?: SubflowType
|
||||
iterationContainerId?: string
|
||||
parentIterations?: ParentIteration[]
|
||||
childWorkflowBlockId?: string
|
||||
childWorkflowName?: string
|
||||
/** Per-invocation unique ID for correlating child block events with this workflow block. */
|
||||
childWorkflowInstanceId?: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Block child workflow started event — fires when a workflow block generates its instanceId,
|
||||
* before child execution begins. Allows clients to pre-associate the running entry with
|
||||
* the instanceId so child block events can be correlated in real-time.
|
||||
*/
|
||||
interface BlockChildWorkflowStartedEvent extends BaseExecutionEvent {
|
||||
type: 'block:childWorkflowStarted'
|
||||
workflowId: string
|
||||
data: {
|
||||
blockId: string
|
||||
childWorkflowInstanceId: string
|
||||
iterationCurrent?: number
|
||||
iterationTotal?: number
|
||||
iterationType?: SubflowType
|
||||
iterationContainerId?: string
|
||||
parentIterations?: ParentIteration[]
|
||||
childWorkflowBlockId?: string
|
||||
childWorkflowName?: string
|
||||
executionOrder?: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream chunk event (for agent blocks)
|
||||
*/
|
||||
interface StreamChunkEvent extends BaseExecutionEvent {
|
||||
type: 'stream:chunk'
|
||||
workflowId: string
|
||||
data: {
|
||||
blockId: string
|
||||
chunk: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream done event
|
||||
*/
|
||||
interface StreamDoneEvent extends BaseExecutionEvent {
|
||||
type: 'stream:done'
|
||||
workflowId: string
|
||||
data: {
|
||||
blockId: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Union type of all execution events
|
||||
*/
|
||||
export type ExecutionEvent =
|
||||
| ExecutionStartedEvent
|
||||
| ExecutionCompletedEvent
|
||||
| ExecutionPausedEvent
|
||||
| ExecutionErrorEvent
|
||||
| ExecutionCancelledEvent
|
||||
| BlockStartedEvent
|
||||
| BlockCompletedEvent
|
||||
| BlockErrorEvent
|
||||
| BlockChildWorkflowStartedEvent
|
||||
| StreamChunkEvent
|
||||
| StreamDoneEvent
|
||||
|
||||
export type ExecutionStartedData = ExecutionStartedEvent['data']
|
||||
export type ExecutionCompletedData = ExecutionCompletedEvent['data']
|
||||
export type ExecutionPausedData = ExecutionPausedEvent['data']
|
||||
export type ExecutionErrorData = ExecutionErrorEvent['data']
|
||||
export type ExecutionCancelledData = ExecutionCancelledEvent['data']
|
||||
export type BlockStartedData = BlockStartedEvent['data']
|
||||
export type BlockCompletedData = BlockCompletedEvent['data']
|
||||
export type BlockErrorData = BlockErrorEvent['data']
|
||||
export type BlockChildWorkflowStartedData = BlockChildWorkflowStartedEvent['data']
|
||||
export type StreamChunkData = StreamChunkEvent['data']
|
||||
export type StreamDoneData = StreamDoneEvent['data']
|
||||
|
||||
/**
|
||||
* Helper to create SSE formatted message
|
||||
*/
|
||||
export function formatSSEEvent(event: ExecutionEvent): string {
|
||||
return `data: ${JSON.stringify(event)}\n\n`
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to encode SSE event as Uint8Array
|
||||
*/
|
||||
export function encodeSSEEvent(event: ExecutionEvent): Uint8Array {
|
||||
return new TextEncoder().encode(formatSSEEvent(event))
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for creating SSE execution callbacks
|
||||
*/
|
||||
interface SSECallbackOptions {
|
||||
executionId: string
|
||||
workflowId: string
|
||||
controller: ReadableStreamDefaultController<Uint8Array>
|
||||
isStreamClosed: () => boolean
|
||||
setStreamClosed: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates execution callbacks using a provided event sink.
|
||||
*/
|
||||
export function createExecutionCallbacks(options: {
|
||||
executionId: string
|
||||
workflowId: string
|
||||
sendEvent: (event: ExecutionEvent) => void | Promise<void>
|
||||
}) {
|
||||
const { executionId, workflowId, sendEvent } = options
|
||||
|
||||
const sendBufferedEvent = async (event: ExecutionEvent) => {
|
||||
await sendEvent(event)
|
||||
}
|
||||
|
||||
const onBlockStart = async (
|
||||
blockId: string,
|
||||
blockName: string,
|
||||
blockType: string,
|
||||
executionOrder: number,
|
||||
iterationContext?: IterationContext,
|
||||
childWorkflowContext?: ChildWorkflowContext
|
||||
) => {
|
||||
await sendBufferedEvent({
|
||||
type: 'block:started',
|
||||
timestamp: new Date().toISOString(),
|
||||
executionId,
|
||||
workflowId,
|
||||
data: {
|
||||
blockId,
|
||||
blockName,
|
||||
blockType,
|
||||
executionOrder,
|
||||
...(iterationContext && {
|
||||
iterationCurrent: iterationContext.iterationCurrent,
|
||||
iterationTotal: iterationContext.iterationTotal,
|
||||
iterationType: iterationContext.iterationType,
|
||||
iterationContainerId: iterationContext.iterationContainerId,
|
||||
...(iterationContext.parentIterations?.length && {
|
||||
parentIterations: iterationContext.parentIterations,
|
||||
}),
|
||||
}),
|
||||
...(childWorkflowContext && {
|
||||
childWorkflowBlockId: childWorkflowContext.parentBlockId,
|
||||
childWorkflowName: childWorkflowContext.workflowName,
|
||||
}),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const onBlockComplete = async (
|
||||
blockId: string,
|
||||
blockName: string,
|
||||
blockType: string,
|
||||
callbackData: {
|
||||
input?: unknown
|
||||
output: any
|
||||
executionTime: number
|
||||
startedAt: string
|
||||
executionOrder: number
|
||||
endedAt: string
|
||||
childWorkflowInstanceId?: string
|
||||
},
|
||||
iterationContext?: IterationContext,
|
||||
childWorkflowContext?: ChildWorkflowContext
|
||||
) => {
|
||||
const hasError = callbackData.output?.error
|
||||
const iterationData = iterationContext
|
||||
? {
|
||||
iterationCurrent: iterationContext.iterationCurrent,
|
||||
iterationTotal: iterationContext.iterationTotal,
|
||||
iterationType: iterationContext.iterationType,
|
||||
iterationContainerId: iterationContext.iterationContainerId,
|
||||
...(iterationContext.parentIterations?.length && {
|
||||
parentIterations: iterationContext.parentIterations,
|
||||
}),
|
||||
}
|
||||
: {}
|
||||
const childWorkflowData = childWorkflowContext
|
||||
? {
|
||||
childWorkflowBlockId: childWorkflowContext.parentBlockId,
|
||||
childWorkflowName: childWorkflowContext.workflowName,
|
||||
}
|
||||
: {}
|
||||
|
||||
const instanceData = callbackData.childWorkflowInstanceId
|
||||
? { childWorkflowInstanceId: callbackData.childWorkflowInstanceId }
|
||||
: {}
|
||||
|
||||
if (hasError) {
|
||||
await sendBufferedEvent({
|
||||
type: 'block:error',
|
||||
timestamp: new Date().toISOString(),
|
||||
executionId,
|
||||
workflowId,
|
||||
data: {
|
||||
blockId,
|
||||
blockName,
|
||||
blockType,
|
||||
input: callbackData.input,
|
||||
error: callbackData.output.error,
|
||||
durationMs: callbackData.executionTime || 0,
|
||||
startedAt: callbackData.startedAt,
|
||||
executionOrder: callbackData.executionOrder,
|
||||
endedAt: callbackData.endedAt,
|
||||
...iterationData,
|
||||
...childWorkflowData,
|
||||
...instanceData,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
await sendBufferedEvent({
|
||||
type: 'block:completed',
|
||||
timestamp: new Date().toISOString(),
|
||||
executionId,
|
||||
workflowId,
|
||||
data: {
|
||||
blockId,
|
||||
blockName,
|
||||
blockType,
|
||||
input: callbackData.input,
|
||||
output: callbackData.output,
|
||||
durationMs: callbackData.executionTime || 0,
|
||||
startedAt: callbackData.startedAt,
|
||||
executionOrder: callbackData.executionOrder,
|
||||
endedAt: callbackData.endedAt,
|
||||
...iterationData,
|
||||
...childWorkflowData,
|
||||
...instanceData,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const onStream = async (streamingExecution: unknown) => {
|
||||
const streamingExec = streamingExecution as { stream: ReadableStream; execution: any }
|
||||
const blockId = streamingExec.execution?.blockId
|
||||
const reader = streamingExec.stream.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
const chunk = decoder.decode(value, { stream: true })
|
||||
await sendBufferedEvent({
|
||||
type: 'stream:chunk',
|
||||
timestamp: new Date().toISOString(),
|
||||
executionId,
|
||||
workflowId,
|
||||
data: { blockId, chunk },
|
||||
})
|
||||
}
|
||||
await sendBufferedEvent({
|
||||
type: 'stream:done',
|
||||
timestamp: new Date().toISOString(),
|
||||
executionId,
|
||||
workflowId,
|
||||
data: { blockId },
|
||||
})
|
||||
} finally {
|
||||
try {
|
||||
reader.releaseLock()
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
const onChildWorkflowInstanceReady = async (
|
||||
blockId: string,
|
||||
childWorkflowInstanceId: string,
|
||||
iterationContext?: IterationContext,
|
||||
executionOrder?: number,
|
||||
childWorkflowContext?: ChildWorkflowContext
|
||||
) => {
|
||||
await sendBufferedEvent({
|
||||
type: 'block:childWorkflowStarted',
|
||||
timestamp: new Date().toISOString(),
|
||||
executionId,
|
||||
workflowId,
|
||||
data: {
|
||||
blockId,
|
||||
childWorkflowInstanceId,
|
||||
...(iterationContext && {
|
||||
iterationCurrent: iterationContext.iterationCurrent,
|
||||
iterationTotal: iterationContext.iterationTotal,
|
||||
iterationType: iterationContext.iterationType,
|
||||
iterationContainerId: iterationContext.iterationContainerId,
|
||||
...(iterationContext.parentIterations?.length && {
|
||||
parentIterations: iterationContext.parentIterations,
|
||||
}),
|
||||
}),
|
||||
...(childWorkflowContext && {
|
||||
childWorkflowBlockId: childWorkflowContext.parentBlockId,
|
||||
childWorkflowName: childWorkflowContext.workflowName,
|
||||
}),
|
||||
...(executionOrder !== undefined && { executionOrder }),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
sendEvent: sendBufferedEvent,
|
||||
onBlockStart,
|
||||
onBlockComplete,
|
||||
onStream,
|
||||
onChildWorkflowInstanceReady,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates SSE callbacks for workflow execution streaming
|
||||
*/
|
||||
export function createSSECallbacks(options: SSECallbackOptions) {
|
||||
const { executionId, workflowId, controller, isStreamClosed, setStreamClosed } = options
|
||||
|
||||
const sendEvent = (event: ExecutionEvent) => {
|
||||
if (isStreamClosed()) return
|
||||
try {
|
||||
controller.enqueue(encodeSSEEvent(event))
|
||||
} catch {
|
||||
setStreamClosed()
|
||||
}
|
||||
}
|
||||
|
||||
return createExecutionCallbacks({
|
||||
executionId,
|
||||
workflowId,
|
||||
sendEvent,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user