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,197 @@
|
||||
import type { SubBlockType } from '@sim/workflow-types/blocks'
|
||||
import type { WorkflowInputField } from '@/lib/workflows/input-format'
|
||||
import type { BlockConfig, BlockIcon, SubBlockConfig } from '@/blocks/types'
|
||||
|
||||
/**
|
||||
* The block-type prefix that identifies a custom (deploy-as-block) block. Shared
|
||||
* by the registry overlay, the executor handler dispatch, and access control.
|
||||
*/
|
||||
export const CUSTOM_BLOCK_TYPE_PREFIX = 'custom_block_'
|
||||
|
||||
/** Whether a block type is a published custom block. */
|
||||
export function isCustomBlockType(type: string | undefined | null): type is string {
|
||||
return typeof type === 'string' && type.startsWith(CUSTOM_BLOCK_TYPE_PREFIX)
|
||||
}
|
||||
|
||||
/** Tile background for custom-block icons (the uploaded image renders on top). */
|
||||
export const CUSTOM_BLOCK_TILE_COLOR = '#6F6F6F'
|
||||
|
||||
/** A curated output exposed on the block, mapped from a child block output. */
|
||||
export interface CustomBlockOutput {
|
||||
blockId: string
|
||||
path: string
|
||||
name: string
|
||||
}
|
||||
|
||||
/**
|
||||
* A curated input the admin chose to expose on the block, keyed by the source
|
||||
* Start field's stable `id`, with optional consumer-facing hints.
|
||||
*/
|
||||
export interface CustomBlockInput {
|
||||
id: string
|
||||
name: string
|
||||
type: string
|
||||
placeholder?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* The DB-backed identity + presentation of a custom block. `workflowId` is the
|
||||
* bound source workflow whose LATEST deployment this block always executes.
|
||||
*/
|
||||
export interface CustomBlockRow {
|
||||
type: string
|
||||
name: string
|
||||
description: string
|
||||
workflowId: string
|
||||
/** Curated exposed outputs; empty/absent exposes the child's whole `result`. */
|
||||
exposedOutputs?: CustomBlockOutput[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Params that carry the block's own wiring rather than a mapped Start input.
|
||||
* Everything else on the block is collected into the child `inputMapping`. Shared
|
||||
* with the serializer so "does this custom block declare input sub-blocks?" reads
|
||||
* from one source instead of re-listing the structural ids.
|
||||
*/
|
||||
export const RESERVED_PARAMS = new Set([
|
||||
'workflowId',
|
||||
'inputMapping',
|
||||
'triggerMode',
|
||||
'advancedMode',
|
||||
])
|
||||
|
||||
/** Map a Start input field type to the editor sub-block type used to collect it. */
|
||||
function subBlockTypeForField(fieldType: string): SubBlockType {
|
||||
switch (fieldType) {
|
||||
case 'boolean':
|
||||
return 'switch'
|
||||
case 'object':
|
||||
case 'array':
|
||||
return 'code'
|
||||
case 'file[]':
|
||||
return 'file-upload'
|
||||
default:
|
||||
return 'short-input'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a `BlockConfig` for a published custom block from its DB row and the
|
||||
* live-derived Start input fields. Shared by the client (real icon + per-field
|
||||
* editors) and the server (placeholder icon + `inputFields: []`, since the
|
||||
* `inputMapping` wiring is schema-agnostic).
|
||||
*
|
||||
* Execution reuses the `workflow_executor` tool: the bound `workflowId` and the
|
||||
* assembled `inputMapping` are hidden, baked sub-blocks; each Start input becomes
|
||||
* its own editable sub-block whose value is collected into `inputMapping`.
|
||||
* `<refs>` inside those values resolve at execution exactly like the
|
||||
* `workflow_input` block.
|
||||
*
|
||||
* The sub-block id is the field's stable id (`field.id`), NOT its display name, so
|
||||
* renaming a Start input in the source workflow and redeploying never orphans a
|
||||
* consumer's placed value. The name is shown as the sub-block title and is what
|
||||
* the child workflow ultimately receives — the id→name remap happens at execution
|
||||
* in `WorkflowBlockHandler` against the loaded child's current field names. Legacy
|
||||
* fields without an id fall back to keying on the name.
|
||||
*/
|
||||
export function buildCustomBlockConfig(
|
||||
row: CustomBlockRow,
|
||||
inputFields: WorkflowInputField[],
|
||||
opts: { icon: BlockIcon; bgColor?: string; hideFromToolbar?: boolean }
|
||||
): BlockConfig {
|
||||
const fieldSubBlocks: SubBlockConfig[] = inputFields.map((field) => {
|
||||
const type = subBlockTypeForField(field.type)
|
||||
const sub: SubBlockConfig = {
|
||||
id: field.id ?? field.name,
|
||||
title: field.name,
|
||||
type,
|
||||
description: field.description,
|
||||
placeholder: field.placeholder,
|
||||
// Serializer Loop-B (required subBlocks not covered by tool params) and the
|
||||
// editor asterisk both read this — same enforcement path as regular blocks.
|
||||
required: field.required === true,
|
||||
}
|
||||
if (field.type === 'object' || field.type === 'array') sub.language = 'json'
|
||||
if (field.type === 'file[]') sub.multiple = true
|
||||
return sub
|
||||
})
|
||||
|
||||
return {
|
||||
type: row.type,
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
category: 'tools',
|
||||
longDescription:
|
||||
'A published workflow packaged as a reusable, self-contained block. Fill its input ' +
|
||||
'fields; it runs the underlying workflow and returns the outputs below. The bound ' +
|
||||
'workflow is baked in — no workflow id or input mapping to configure.',
|
||||
bgColor: opts.bgColor ?? CUSTOM_BLOCK_TILE_COLOR,
|
||||
icon: opts.icon,
|
||||
// A disabled block stays resolvable (so a still-placed instance survives
|
||||
// serialization and fails loudly at run via `getCustomBlockAuthority`, instead
|
||||
// of silently vanishing from the graph) but is hidden from the palette so no
|
||||
// new instance can be placed.
|
||||
hideFromToolbar: opts.hideFromToolbar,
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'workflowId',
|
||||
type: 'short-input',
|
||||
hidden: true,
|
||||
value: () => row.workflowId,
|
||||
},
|
||||
{
|
||||
id: 'inputMapping',
|
||||
type: 'code',
|
||||
language: 'json',
|
||||
hidden: true,
|
||||
value: (params) => {
|
||||
const mapping: Record<string, unknown> = {}
|
||||
for (const [key, val] of Object.entries(params)) {
|
||||
if (RESERVED_PARAMS.has(key)) continue
|
||||
if (val === undefined || val === '') continue
|
||||
mapping[key] = val
|
||||
}
|
||||
return JSON.stringify(mapping)
|
||||
},
|
||||
},
|
||||
...fieldSubBlocks,
|
||||
],
|
||||
tools: {
|
||||
access: ['workflow_executor'],
|
||||
config: {
|
||||
tool: () => 'workflow_executor',
|
||||
params: (params) => ({
|
||||
workflowId: params.workflowId,
|
||||
inputMapping: params.inputMapping,
|
||||
}),
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
workflowId: { type: 'string', description: 'Bound source workflow id' },
|
||||
inputMapping: { type: 'json', description: 'Mapping of input fields to values' },
|
||||
},
|
||||
outputs: buildOutputs(row.exposedOutputs),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The block's declared outputs. Internal plumbing (child workflow id/name, trace
|
||||
* spans) is never exposed. With curated `exposedOutputs`, each becomes its own
|
||||
* named output; otherwise the whole child `result` is exposed.
|
||||
*/
|
||||
function buildOutputs(exposed: CustomBlockOutput[] | undefined): BlockConfig['outputs'] {
|
||||
const outputs: BlockConfig['outputs'] = {
|
||||
success: { type: 'boolean', description: 'Execution success status' },
|
||||
error: { type: 'string', description: 'Error message' },
|
||||
}
|
||||
if (exposed && exposed.length > 0) {
|
||||
for (const out of exposed) {
|
||||
outputs[out.name] = { type: 'json', description: `Output: ${out.path}` }
|
||||
}
|
||||
} else {
|
||||
outputs.result = { type: 'json', description: 'Workflow execution result' }
|
||||
}
|
||||
return outputs
|
||||
}
|
||||
Reference in New Issue
Block a user