Files
simstudioai--sim/apps/sim/lib/api/contracts/workflows.ts
T
wehub-resource-sync d25d482dc2
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
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

921 lines
28 KiB
TypeScript

import { z } from 'zod'
import { workspaceIdSchema } from '@/lib/api/contracts/primitives'
import { defineRouteContract } from '@/lib/api/contracts/types'
const subBlockValuesSchema = z.record(z.string(), z.record(z.string(), z.unknown()))
const executionIdSchema = z
.string()
.min(1, 'Invalid execution ID')
.max(128, 'Execution ID too long')
.regex(
/^[A-Za-z0-9._:-]+$/,
'Execution ID can only contain letters, numbers, dots, underscores, colons, and hyphens'
)
const workflowPositionSchema = z.object({
x: z.number(),
y: z.number(),
})
const workflowBlockDataSchema = z.object({
parentId: z.string().optional(),
extent: z.literal('parent').optional(),
width: z.number().optional(),
height: z.number().optional(),
collection: z.unknown().optional(),
count: z.number().optional(),
loopType: z.enum(['for', 'forEach', 'while', 'doWhile']).optional(),
whileCondition: z.string().optional(),
doWhileCondition: z.string().optional(),
parallelType: z.enum(['collection', 'count']).optional(),
batchSize: z.number().optional(),
type: z.string().optional(),
canonicalModes: z.record(z.string(), z.enum(['basic', 'advanced'])).optional(),
})
const workflowSubBlockStateSchema = z.object({
id: z.string(),
type: z.string(),
value: z.unknown(),
})
const workflowBlockOutputSchema = z.unknown()
const workflowEdgeHandleSchema = z
.string()
.nullish()
.transform((value) => value ?? undefined)
const workflowBlockStateSchema = z.object({
id: z.string(),
type: z.string(),
name: z.string(),
position: workflowPositionSchema,
subBlocks: z.record(z.string(), workflowSubBlockStateSchema),
outputs: z.record(z.string(), workflowBlockOutputSchema),
enabled: z.boolean(),
horizontalHandles: z.boolean().optional(),
height: z.number().optional(),
advancedMode: z.boolean().optional(),
triggerMode: z.boolean().optional(),
data: workflowBlockDataSchema.optional(),
locked: z.boolean().optional(),
})
const workflowEdgeSchema = z.object({
id: z.string(),
source: z.string(),
target: z.string(),
sourceHandle: workflowEdgeHandleSchema,
targetHandle: workflowEdgeHandleSchema,
type: z.string().optional(),
animated: z.boolean().optional(),
style: z.record(z.string(), z.unknown()).optional(),
data: z.record(z.string(), z.unknown()).optional(),
label: z.string().optional(),
labelStyle: z.record(z.string(), z.unknown()).optional(),
labelShowBg: z.boolean().optional(),
labelBgStyle: z.record(z.string(), z.unknown()).optional(),
labelBgPadding: z.tuple([z.number(), z.number()]).optional(),
labelBgBorderRadius: z.number().optional(),
markerStart: z.string().optional(),
markerEnd: z.string().optional(),
})
const workflowLoopSchema = z.object({
id: z.string(),
nodes: z.array(z.string()),
iterations: z.number(),
loopType: z.enum(['for', 'forEach', 'while', 'doWhile']),
forEachItems: z
.union([z.array(z.unknown()), z.record(z.string(), z.unknown()), z.string()])
.optional(),
whileCondition: z.string().optional(),
doWhileCondition: z.string().optional(),
enabled: z.boolean().optional(),
locked: z.boolean().optional(),
})
const workflowParallelSchema = z.object({
id: z.string(),
nodes: z.array(z.string()),
distribution: z
.union([z.array(z.unknown()), z.record(z.string(), z.unknown()), z.string()])
.optional(),
count: z.number().optional(),
parallelType: z.enum(['count', 'collection']).optional(),
batchSize: z.number().optional(),
enabled: z.boolean().optional(),
locked: z.boolean().optional(),
})
/**
* Write/input wire shape for a workflow variable.
*
* Intentionally omits `workflowId`: a variable is workflow-scoped and the
* route's `[id]` path parameter is the single source of truth on writes.
* Persisting `workflowId` per-variable would be redundant.
*
* `value` is `unknown` to match the canonical client-side `Variable` type
* from `@sim/workflow-types/workflow`. Variables are free-form on the
* editor (the user enters a string that may parse as any of the declared
* types) and validation is done per-`type` at use-time by
* `validateVariable` in `apps/sim/stores/variables/store.ts`.
*/
export const workflowVariableWriteSchema = z.object({
id: z.string(),
name: z.string(),
type: z.enum(['string', 'number', 'boolean', 'object', 'array', 'plain']),
value: z.unknown(),
validationError: z.string().optional(),
})
/**
* Read/response wire shape for a workflow variable.
*
* Adds the server-stamped `workflowId` that GET handlers attach for the
* client-side variables store (`apps/sim/stores/variables/types.ts`),
* which keeps a `workflowId` field for cross-workflow filtering inside a
* single global store. Without this on the read schema, the field is
* stripped during `requestJson` Zod parsing and the store filter breaks.
*
* Routes stamping this field:
* - `apps/sim/app/api/workflows/[id]/route.ts` (GET) on `data.variables`
* - `apps/sim/app/api/workflows/[id]/state/route.ts` (GET) on `variables`
* - `apps/sim/app/api/workflows/[id]/variables/route.ts` (GET) on `data`
*/
export const workflowVariableReadSchema = workflowVariableWriteSchema.extend({
workflowId: z.string(),
})
export const workflowStateSchema = z.object({
blocks: z.record(z.string(), workflowBlockStateSchema),
edges: z.array(workflowEdgeSchema),
loops: z.record(z.string(), workflowLoopSchema).optional(),
parallels: z.record(z.string(), workflowParallelSchema).optional(),
lastSaved: z.number().optional(),
isDeployed: z.boolean().optional(),
deployedAt: z.coerce.date().nullable().optional(),
variables: z.record(z.string(), workflowVariableWriteSchema).optional(),
/**
* Display metadata stamped onto the workflow state by the GET
* `/api/workflows/[id]` route, so callers consuming the wire payload
* (export, copilot tool result handling, etc.) can show the workflow's
* name/description without a second request. Not persisted on disk —
* the route reads it from the workflow row and stamps it on read.
*/
metadata: z
.object({
name: z.string().optional(),
description: z.string().nullable().optional(),
})
.optional(),
})
export type WorkflowStateContractInput = z.input<typeof workflowStateSchema>
export type WorkflowStateContractOutput = z.output<typeof workflowStateSchema>
export type WorkflowStateWirePayload = WorkflowStateContractOutput
/**
* Loose subset of {@link workflowStateSchema} emitted by the copilot
* checkpoint revert route. The route forwards the persisted JSONB blob
* verbatim without strict schema validation, so `blocks`/`edges`/`loops`/
* `parallels` are typed as opaque records/arrays here. Always carries
* `lastSaved` (set by the route to `Date.now()`) and `isDeployed`; carries
* `deployedAt` only when the persisted value is a valid date.
*/
export const cleanedWorkflowStateSchema = z.object({
blocks: z.record(z.string(), z.unknown()),
edges: z.array(z.unknown()),
loops: z.record(z.string(), z.unknown()),
parallels: z.record(z.string(), z.unknown()),
isDeployed: z.boolean(),
lastSaved: z.number(),
deployedAt: z.coerce.date().optional(),
})
export type CleanedWorkflowState = z.output<typeof cleanedWorkflowStateSchema>
export const workflowScopeSchema = z.enum(['active', 'archived', 'all'])
export const workflowIdParamsSchema = z.object({
id: z.string().min(1, 'Invalid workflow ID'),
})
export const workflowListQuerySchema = z.object({
workspaceId: z.string().min(1).optional(),
scope: workflowScopeSchema.default('active'),
})
export type WorkflowListItem = z.output<typeof workflowListItemSchema>
export const workflowListItemSchema = z.object({
id: z.string(),
name: z.string(),
description: z.string().nullable(),
workspaceId: z.string().nullable(),
folderId: z.string().nullable(),
sortOrder: z.number(),
createdAt: z.string(),
updatedAt: z.string(),
archivedAt: z.string().nullable(),
locked: z.boolean(),
isDeployed: z.boolean().optional(),
})
export const createWorkflowBodySchema = z.object({
id: z.string().uuid().optional(),
name: z.string().min(1, 'Name is required'),
description: z.string().optional().default(''),
workspaceId: z.string().optional(),
folderId: z.string().nullable().optional(),
sortOrder: z.number().int().optional(),
deduplicate: z.boolean().optional(),
})
export const createWorkflowResponseSchema = z.object({
id: z.string(),
name: z.string(),
description: z.string(),
workspaceId: z.string(),
folderId: z.string().nullable().optional(),
sortOrder: z.number(),
createdAt: z.string(),
updatedAt: z.string(),
startBlockId: z.string(),
subBlockValues: subBlockValuesSchema,
})
export type CreateWorkflowBody = z.input<typeof createWorkflowBodySchema>
export type CreateWorkflowResponse = z.output<typeof createWorkflowResponseSchema>
export const duplicateWorkflowBodySchema = z.object({
name: z.string().min(1, 'Name is required'),
description: z.string().optional(),
workspaceId: z.string().optional(),
folderId: z.string().nullable().optional(),
newId: z.string().uuid().optional(),
})
export const duplicateWorkflowResponseSchema = z.object({
id: z.string(),
name: z.string(),
description: z.string().nullable(),
workspaceId: z.string(),
folderId: z.string().nullable(),
sortOrder: z.number(),
locked: z.boolean(),
blocksCount: z.number(),
edgesCount: z.number(),
subflowsCount: z.number(),
})
export type DuplicateWorkflowBody = z.input<typeof duplicateWorkflowBodySchema>
export type DuplicateWorkflowResponse = z.output<typeof duplicateWorkflowResponseSchema>
export const updateWorkflowBodySchema = z.object({
name: z.string().min(1, 'Name is required').optional(),
description: z.string().optional(),
folderId: z.string().nullable().optional(),
sortOrder: z.number().int().min(0).optional(),
locked: z.boolean().optional(),
})
export type UpdateWorkflowBody = z.input<typeof updateWorkflowBodySchema>
export const reorderWorkflowsBodySchema = z.object({
workspaceId: z.string(),
updates: z.array(
z.object({
id: z.string(),
sortOrder: z.number().int().min(0),
folderId: z.string().nullable().optional(),
})
),
})
export type ReorderWorkflowsBody = z.input<typeof reorderWorkflowsBodySchema>
export const executeWorkflowRunFromBlockSchema = z.object({
startBlockId: z.string().min(1, 'Start block ID is required'),
sourceSnapshot: z
.object({
blockStates: z.record(z.string(), z.any()),
executedBlocks: z.array(z.string()),
blockLogs: z.array(z.any()),
decisions: z.object({
router: z.record(z.string(), z.string()),
condition: z.record(z.string(), z.string()),
}),
completedLoops: z.array(z.string()),
loopExecutions: z.record(z.string(), z.any()).optional(),
parallelExecutions: z.record(z.string(), z.any()).optional(),
parallelBlockMapping: z.record(z.string(), z.any()).optional(),
activeExecutionPath: z.array(z.string()),
})
.optional(),
executionId: z.string().optional(),
})
export const executeWorkflowTriggerTypeSchema = z.enum([
'manual',
'api',
'schedule',
'chat',
'webhook',
'mcp',
'copilot',
'mothership',
'workflow',
])
export const executeWorkflowBodySchema = z.object({
selectedOutputs: z.array(z.string()).optional().default([]),
triggerType: executeWorkflowTriggerTypeSchema.optional(),
stream: z.boolean().optional(),
useDraftState: z.boolean().optional(),
input: z.any().optional(),
isClientSession: z.boolean().optional(),
includeFileBase64: z.boolean().optional().default(true),
base64MaxBytes: z.number().int().positive().optional(),
workflowStateOverride: workflowStateSchema.optional(),
executionId: executionIdSchema.optional(),
triggerBlockId: z.string().optional(),
startBlockId: z.string().optional(),
stopAfterBlockId: z.string().optional(),
runFromBlock: executeWorkflowRunFromBlockSchema.optional(),
/**
* Workspace of the parent execution when this call is a workflow-in-workflow
* invocation (e.g. the agent `workflow_executor` tool). When present, the
* route rejects execution of a workflow that lives in a different workspace.
* Direct API callers omit it and are unaffected.
*/
parentWorkspaceId: workspaceIdSchema.optional(),
})
export type ExecuteWorkflowBody = z.input<typeof executeWorkflowBodySchema>
export const workflowVariablesBodySchema = z.object({
variables: z.record(z.string(), workflowVariableWriteSchema),
})
export type WorkflowVariablesBody = z.input<typeof workflowVariablesBodySchema>
export const workflowExecutionParamsSchema = z.object({
id: z.string().min(1, 'Invalid workflow ID'),
executionId: z.string().min(1, 'Invalid execution ID'),
})
export const resumeExecutionParamsSchema = z.object({
workflowId: z.string().min(1),
executionId: z.string().min(1),
})
export const resumeExecutionContextParamsSchema = z.object({
workflowId: z.string().min(1),
executionId: z.string().min(1),
contextId: z.string().min(1),
})
export const workflowExecutionStreamQuerySchema = z.object({
from: z.preprocess((value) => {
if (typeof value !== 'string') return 0
const parsed = Number.parseInt(value, 10)
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 0
}, z.number().int().min(0)),
})
export const pausedWorkflowExecutionsQuerySchema = z.object({
status: z.string().optional(),
})
export const workflowAutoLayoutBodySchema = z.object({
spacing: z
.object({
horizontal: z.number().min(100).max(1000).optional(),
vertical: z.number().min(50).max(500).optional(),
})
.optional()
.default({}),
alignment: z.enum(['start', 'center', 'end']).optional().default('center'),
padding: z
.object({
x: z.number().min(50).max(500).optional(),
y: z.number().min(50).max(500).optional(),
})
.optional()
.default({}),
gridSize: z.number().min(0).max(50).optional(),
blocks: z.record(z.string(), z.any()).optional(),
edges: z.array(z.any()).optional(),
loops: z.record(z.string(), z.any()).optional(),
parallels: z.record(z.string(), z.any()).optional(),
})
export type WorkflowAutoLayoutBody = z.input<typeof workflowAutoLayoutBodySchema>
export const workflowDeploymentVersionParamSchema = z.union([
z.literal('active'),
z.coerce.number().refine(Number.isFinite, 'Invalid version'),
])
export type WorkflowDeploymentVersionParam = z.output<typeof workflowDeploymentVersionParamSchema>
export const workflowLogResultSchema = z.object({
success: z.boolean(),
error: z.string().optional(),
output: z.any(),
metadata: z
.object({
source: z.string().optional(),
duration: z.number().optional(),
})
.optional(),
})
export const workflowLogBodySchema = z.object({
logs: z.array(z.any()).optional(),
executionId: z.string().min(1, 'Execution ID is required').optional(),
result: workflowLogResultSchema.optional(),
})
export type WorkflowLogBody = z.input<typeof workflowLogBodySchema>
export const importWorkflowAsSuperuserBodySchema = z.object({
workflowId: z.string().min(1, 'Workflow ID is required'),
targetWorkspaceId: z.string().min(1, 'Target workspace ID is required'),
})
export type ImportWorkflowAsSuperuserBody = z.input<typeof importWorkflowAsSuperuserBodySchema>
export const importWorkflowAsSuperuserPermissiveBodySchema = z
.object({
workflowId: z.string().optional(),
targetWorkspaceId: z.string().optional(),
})
.passthrough()
export const importWorkflowAsSuperuserResponseSchema = z.object({
success: z.literal(true),
newWorkflowId: z.string(),
copilotChatsImported: z.number(),
})
export type ImportWorkflowAsSuperuserResponse = z.output<
typeof importWorkflowAsSuperuserResponseSchema
>
const successResponseSchema = z.object({
success: z.literal(true),
})
const workflowStatusResponseSchema = z.object({
isDeployed: z.boolean(),
deployedAt: z.coerce.date().nullable().optional(),
isPublished: z.boolean().optional(),
needsRedeployment: z.boolean(),
})
const workflowAutoLayoutResponseSchema = z.object({
success: z.literal(true),
message: z.string(),
data: z.object({
blockCount: z.number(),
elapsed: z.string(),
layoutedBlocks: z.record(z.string(), workflowBlockStateSchema),
}),
})
const workflowLogResponseSchema = z.object({
message: z.string(),
})
const workflowVariablesResponseSchema = z.object({
success: z.literal(true),
})
const pausedWorkflowExecutionSummarySchema = z
.object({
id: z.string(),
workflowId: z.string(),
executionId: z.string(),
status: z.string(),
totalPauseCount: z.number(),
resumedCount: z.number(),
pausedAt: z.string().nullable(),
updatedAt: z.string().nullable(),
expiresAt: z.string().nullable(),
metadata: z.record(z.string(), z.unknown()).nullable(),
triggerIds: z.array(z.string()),
pausePoints: z.array(z.record(z.string(), z.unknown())),
})
.passthrough()
const pausedWorkflowExecutionsResponseSchema = z.object({
pausedExecutions: z.array(pausedWorkflowExecutionSummarySchema),
})
const pausedWorkflowExecutionDetailSchema = pausedWorkflowExecutionSummarySchema.extend({
executionSnapshot: z.unknown(),
queue: z.array(z.record(z.string(), z.unknown())),
})
const workflowExecutionStatusEnum = z.enum([
'pending',
'running',
'paused',
'completed',
'failed',
'cancelled',
])
const workflowExecutionPausedDetailSchema = z.object({
pausedAt: z.string(),
resumeAt: z.string().nullable(),
pauseKind: z.enum(['time', 'human']).nullable(),
blockedOnBlockId: z.string().nullable(),
pausedExecutionId: z.string(),
pausePointCount: z.number(),
resumedCount: z.number(),
})
const workflowExecutionStatusResponseSchema = z.object({
executionId: z.string(),
workflowId: z.string(),
status: workflowExecutionStatusEnum,
trigger: z.string(),
level: z.string(),
startedAt: z.string(),
endedAt: z.string().nullable(),
totalDurationMs: z.number().nullable(),
paused: workflowExecutionPausedDetailSchema.nullable(),
cost: z.object({ total: z.number() }).nullable(),
error: z.string().nullable(),
finalOutput: z.unknown().nullable(),
blockOutputs: z.record(z.string(), z.unknown()).nullable(),
})
export type WorkflowExecutionStatusResponse = z.output<typeof workflowExecutionStatusResponseSchema>
const workflowExecutionStatusQuerySchema = z.object({
includeOutput: z
.enum(['true', 'false'])
.optional()
.transform((value) => value === 'true'),
selectedOutputs: z
.string()
.optional()
.transform((value) =>
value
? value
.split(',')
.map((s) => s.trim())
.filter(Boolean)
: []
),
})
const cancelWorkflowExecutionResponseSchema = z.object({
success: z.boolean(),
executionId: z.string(),
redisAvailable: z.boolean(),
durablyRecorded: z.boolean(),
locallyAborted: z.boolean(),
pausedCancelled: z.boolean(),
reason: z.string().optional(),
})
const resumeWorkflowExecutionContextResponseSchema = z
.object({
status: z.enum(['queued', 'started']).optional(),
success: z.boolean().optional(),
async: z.boolean().optional(),
executionId: z.string().optional(),
queuePosition: z.number().optional(),
jobId: z.string().optional(),
output: z.unknown().optional(),
error: z.string().optional(),
metadata: z
.object({
duration: z.number().optional(),
startTime: z.string().optional(),
endTime: z.string().optional(),
})
.optional(),
message: z.string().optional(),
statusUrl: z.string().optional(),
})
.passthrough()
export const listWorkflowsContract = defineRouteContract({
method: 'GET',
path: '/api/workflows',
query: workflowListQuerySchema,
response: {
mode: 'json',
schema: z.object({
data: z.array(workflowListItemSchema),
}),
},
})
/**
* Wire shape returned by GET `/api/workflows/[id]` for the `data` payload.
* Mirrors the workflow row spread by the route handler plus the stamped `state`
* (built from normalized tables) and stamped per-variable `workflowId`. Keep
* this aligned with `apps/sim/app/api/workflows/[id]/route.ts` whenever the
* row spread or the state assembly changes — it replaces a previous
* `.passthrough()` slot that forced clients to cast row fields like
* `workspaceId` / `isDeployed` / `deployedAt` out as `unknown`.
*/
export const getWorkflowResponseDataSchema = z.object({
id: z.string(),
userId: z.string(),
workspaceId: z.string().nullable(),
folderId: z.string().nullable(),
sortOrder: z.number(),
name: z.string(),
description: z.string().nullable(),
lastSynced: z.coerce.date(),
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
isDeployed: z.boolean(),
deployedAt: z.coerce.date().nullable(),
isPublicApi: z.boolean(),
locked: z.boolean(),
runCount: z.number(),
lastRunAt: z.coerce.date().nullable(),
archivedAt: z.coerce.date().nullable(),
state: workflowStateSchema,
variables: z.record(z.string(), workflowVariableReadSchema).optional(),
})
export type GetWorkflowResponseData = z.output<typeof getWorkflowResponseDataSchema>
export const getWorkflowStateContract = defineRouteContract({
method: 'GET',
path: '/api/workflows/[id]',
params: workflowIdParamsSchema,
response: {
mode: 'json',
schema: z.object({
data: getWorkflowResponseDataSchema,
}),
},
})
export const workflowStateResponseSchema = z.object({
blocks: z.record(z.string(), workflowBlockStateSchema),
edges: z.array(workflowEdgeSchema),
loops: z.record(z.string(), workflowLoopSchema),
parallels: z.record(z.string(), workflowParallelSchema),
variables: z.record(z.string(), workflowVariableReadSchema),
})
export const getWorkflowNormalizedStateContract = defineRouteContract({
method: 'GET',
path: '/api/workflows/[id]/state',
params: workflowIdParamsSchema,
response: {
mode: 'json',
schema: workflowStateResponseSchema,
},
})
export const putWorkflowNormalizedStateContract = defineRouteContract({
method: 'PUT',
path: '/api/workflows/[id]/state',
params: workflowIdParamsSchema,
body: workflowStateSchema,
response: {
mode: 'json',
schema: z.object({
success: z.literal(true),
warnings: z.array(z.string()).default([]),
}),
},
})
export const createWorkflowContract = defineRouteContract({
method: 'POST',
path: '/api/workflows',
body: createWorkflowBodySchema,
response: {
mode: 'json',
schema: createWorkflowResponseSchema,
},
})
export const duplicateWorkflowContract = defineRouteContract({
method: 'POST',
path: '/api/workflows/[id]/duplicate',
params: workflowIdParamsSchema,
body: duplicateWorkflowBodySchema,
response: {
mode: 'json',
schema: duplicateWorkflowResponseSchema,
},
})
export const updateWorkflowContract = defineRouteContract({
method: 'PUT',
path: '/api/workflows/[id]',
params: workflowIdParamsSchema,
body: updateWorkflowBodySchema,
response: {
mode: 'json',
schema: z.object({
workflow: workflowListItemSchema,
}),
},
})
export const deleteWorkflowContract = defineRouteContract({
method: 'DELETE',
path: '/api/workflows/[id]',
params: workflowIdParamsSchema,
response: {
mode: 'json',
schema: successResponseSchema,
},
})
export const reorderWorkflowsContract = defineRouteContract({
method: 'PUT',
path: '/api/workflows/reorder',
body: reorderWorkflowsBodySchema,
response: {
mode: 'json',
schema: successResponseSchema.extend({
updated: z.number(),
}),
},
})
export const restoreWorkflowContract = defineRouteContract({
method: 'POST',
path: '/api/workflows/[id]/restore',
params: workflowIdParamsSchema,
response: {
mode: 'json',
schema: successResponseSchema,
},
})
export const importWorkflowAsSuperuserContract = defineRouteContract({
method: 'POST',
path: '/api/superuser/import-workflow',
body: importWorkflowAsSuperuserBodySchema,
response: {
mode: 'json',
schema: importWorkflowAsSuperuserResponseSchema,
},
})
export const getWorkflowStatusContract = defineRouteContract({
method: 'GET',
path: '/api/workflows/[id]/status',
params: workflowIdParamsSchema,
response: {
mode: 'json',
schema: workflowStatusResponseSchema,
},
})
export const workflowAutoLayoutContract = defineRouteContract({
method: 'POST',
path: '/api/workflows/[id]/autolayout',
params: workflowIdParamsSchema,
body: workflowAutoLayoutBodySchema,
response: {
mode: 'json',
schema: workflowAutoLayoutResponseSchema,
},
})
export const workflowLogContract = defineRouteContract({
method: 'POST',
path: '/api/workflows/[id]/log',
params: workflowIdParamsSchema,
body: workflowLogBodySchema,
response: {
mode: 'json',
schema: workflowLogResponseSchema,
},
})
export const workflowVariablesContract = defineRouteContract({
method: 'POST',
path: '/api/workflows/[id]/variables',
params: workflowIdParamsSchema,
body: workflowVariablesBodySchema,
response: {
mode: 'json',
schema: workflowVariablesResponseSchema,
},
})
export const getWorkflowVariablesContract = defineRouteContract({
method: 'GET',
path: '/api/workflows/[id]/variables',
params: workflowIdParamsSchema,
response: {
mode: 'json',
schema: z.object({
data: z.record(z.string(), workflowVariableReadSchema),
}),
},
})
export const pausedWorkflowExecutionsContract = defineRouteContract({
method: 'GET',
path: '/api/workflows/[id]/paused',
params: workflowIdParamsSchema,
query: pausedWorkflowExecutionsQuerySchema,
response: {
mode: 'json',
schema: pausedWorkflowExecutionsResponseSchema,
},
})
export const pausedWorkflowExecutionByIdContract = defineRouteContract({
method: 'GET',
path: '/api/workflows/[id]/paused/[executionId]',
params: workflowExecutionParamsSchema,
response: {
mode: 'json',
schema: pausedWorkflowExecutionDetailSchema,
},
})
export const cancelWorkflowExecutionContract = defineRouteContract({
method: 'POST',
path: '/api/workflows/[id]/executions/[executionId]/cancel',
params: workflowExecutionParamsSchema,
response: {
mode: 'json',
schema: cancelWorkflowExecutionResponseSchema,
},
})
export const getWorkflowExecutionContract = defineRouteContract({
method: 'GET',
path: '/api/workflows/[id]/executions/[executionId]',
params: workflowExecutionParamsSchema,
query: workflowExecutionStatusQuerySchema,
response: {
mode: 'json',
schema: workflowExecutionStatusResponseSchema,
},
})
export const streamWorkflowExecutionContract = defineRouteContract({
method: 'GET',
path: '/api/workflows/[id]/executions/[executionId]/stream',
params: workflowExecutionParamsSchema,
query: workflowExecutionStreamQuerySchema,
response: {
mode: 'stream',
},
})
export const resumeWorkflowExecutionContract = defineRouteContract({
method: 'GET',
path: '/api/resume/[workflowId]/[executionId]',
params: resumeExecutionParamsSchema,
response: {
mode: 'json',
schema: pausedWorkflowExecutionDetailSchema,
},
})
export const resumeWorkflowExecutionContextContract = defineRouteContract({
method: 'POST',
path: '/api/resume/[workflowId]/[executionId]/[contextId]',
params: resumeExecutionContextParamsSchema,
response: {
mode: 'json',
schema: resumeWorkflowExecutionContextResponseSchema,
},
})
/**
* Detail for a single pause context. The `pausePoint`/`queue`/`activeResumeEntry`
* shapes are modeled loosely (the pause point's `response.data` is block- and
* user-defined) — consistent with how the parent execution detail contract
* (`pausedWorkflowExecutionDetailSchema`) keeps its pause points as records.
*/
const pauseContextDetailResponseSchema = z
.object({
execution: pausedWorkflowExecutionSummarySchema,
pausePoint: z.record(z.string(), z.unknown()),
queue: z.array(z.record(z.string(), z.unknown())),
activeResumeEntry: z.record(z.string(), z.unknown()).nullable().optional(),
})
.passthrough()
export const getPauseContextDetailContract = defineRouteContract({
method: 'GET',
path: '/api/resume/[workflowId]/[executionId]/[contextId]',
params: resumeExecutionContextParamsSchema,
response: {
mode: 'json',
schema: pauseContextDetailResponseSchema,
},
})