# Workflow Nodes

Node builders create current workflow graph nodes with correct ports, defaults, and action IDs.

## Graph Basics

```typescript
const graph = tela.buildGraph(nodes, [
  [a, b],
  [map, child, 'subgraph'],
  [condition, approved, { branch: 0 }],
  [condition, rejected, { branch: 'default' }],
])
```

Connections reference node objects. Edge IDs follow Tela frontend convention.

Port selectors:

| Selector | Use |
|---|---|
| omitted | default output -> default input |
| `'subgraph'` | map subgraph output |
| `{ branch: 0 }` | condition case branch |
| `{ branch: 'default' }` | condition default branch |

## Spec Builder

Graph is source of truth. `workflowSpec.steps` stays empty.

```typescript
const spec = tela.buildWorkflowSpec({
  inputs: [tela.createWorkflowInput({ id: 'input-topic', name: 'topic', type: 'text' })],
  outputs: {},
})
```

## `llmNode`

Runs an LLM completion.

```typescript
const node = tela.llmNode({
  name: 'Extract Data',
  prompt: 'Extract data from {{input://input-document}}',
  model: 'gpt-5',
  temperature: 0,
  schema: {
    invoiceNumber: { type: 'string' },
    total: { type: 'number' },
  },
})
```

Pass a properties map to `schema`, not a full JSON Schema object.

## `agentNode`

Runs a multi-round agent.

```typescript
const node = tela.agentNode({
  name: 'Research',
  prompt: 'Research {{input://input-topic}} and return a summary.',
  model: 'claude-opus-4-5',
  maxRounds: 20,
})
```

## `codeExecutionNode`

Runs sandboxed JavaScript. Code must declare `function execute(input) { ... }`.

```typescript
const node = tela.codeExecutionNode({
  name: 'Aggregate',
  code: `function execute(input) {
    return { count: input["Extract Data"].items.length }
  }`,
})
```

Use `codeFile` for complex code to avoid template-string escaping bugs.

## `conditionNode`

Branches by selector expressions or code expressions.

```typescript
const route = tela.conditionNode({
  name: 'Route',
  cases: [
    { name: 'High Value', condition: '{{step://extract?path=total}}|greater_than|1000' },
  ],
})
```

Selector operators include `contains`, `equals`, `greater_than`, `less_than`, `is_true`, and negated variants. Use code mode for JavaScript expressions:

```typescript
{ name: 'Valid', condition: 'input["Extract"].valid === true', type: 'code' }
```

## `mapNode`

Iterates over an array and runs a subgraph once per item.

```typescript
const split = tela.documentSplitterNode({ name: 'Split', file: '{{input://input-document}}' })
const map = tela.mapNode({ name: 'Each Page', over: tela.stepRef(split), mapVariable: 'page' })
const extract = tela.llmNode({ name: 'Extract Page', prompt: 'Extract from {{page}}' })

const graph = tela.buildGraph([split, map, extract], [
  [split, map],
  [map, extract, 'subgraph'],
])
```

## `templateNode`

Executes a workflow template.

```typescript
const node = tela.templateNode({
  name: 'Run Template',
  templateId: 'template-uuid',
  variables: {
    customer: '{{input://input-customer}}',
    findings: '{{step://analysis}}',
  },
})
```

## `canvasNode`

Executes a Tela canvas or workflow by prompt/canvas ID.

```typescript
const node = tela.canvasNode({
  name: 'Summarize with Canvas',
  canvasId: 'canvas-uuid',
  variables: { document: '{{step://extract}}' },
})
```

Referenced canvases should have a promoted version with a message, variables, and configuration.

## `documentSplitterNode`

Splits PDF/CSV files into chunks.

```typescript
const node = tela.documentSplitterNode({
  name: 'Split Document',
  file: '{{input://input-document}}',
  chunkSize: 1,
  type: 'pdf',
})
```

Output is an array of file metadata objects, not parsed text.

## `documentCropperNode`

Extracts pages from PDF. Indexes are 1-based.

```typescript
const crop = tela.documentCropperNode({
  name: 'Crop Pages',
  file: '{{input://input-document}}',
  indexes: [1, 2, 3],
})

const split = tela.documentSplitterNode({
  name: 'Split Cropped',
  file: tela.stepRef(crop, { path: 'file' }),
})
```

## `documentTemplaterNode`

Fills DOCX templates and can create output documents/permalinks.

```typescript
const fill = tela.documentTemplaterNode({
  name: 'Fill Contract',
  vaultUrl: 'vault://template.docx',
  templateFilename: 'template.docx',
  placeholders: [
    { key: 'clientName', reference: '{{input://input-client}}' },
  ],
})
```

`templateFilename` must end with `.docx`.

## `stopNode`

Stops a branch intentionally. Use only for condition branches; normal workflows end at leaf nodes.

```typescript
const stop = tela.stopNode({ name: 'Stop Default' })
```

## End-to-End Example

```typescript
const documentInput = tela.createWorkflowInput({
  id: 'input-document',
  name: 'document',
  type: 'file',
  required: true,
  multimodal: false,
})

const split = tela.documentSplitterNode({ name: 'Split PDF', file: tela.inputRef(documentInput), chunkSize: 1 })
const mapPages = tela.mapNode({ name: 'Process Pages', over: tela.stepRef(split), mapVariable: 'page' })
const extract = tela.llmNode({
  name: 'Extract Page',
  prompt: 'Extract invoice data from {{page}}',
  schema: { amount: { type: 'number' }, date: { type: 'string' } },
})
const aggregate = tela.codeExecutionNode({
  name: 'Aggregate',
  code: `function execute(input) {
    return { pages: input["Extract Page"] }
  }`,
})

const nodes = [split, mapPages, extract, aggregate]
const graph = tela.buildGraph(nodes, [
  [split, mapPages],
  [mapPages, extract, 'subgraph'],
  [mapPages, aggregate],
])
const spec = tela.buildWorkflowSpec({ inputs: [documentInput] })

const version = await tela.createWorkflowVersion({
  promptId: 'prompt-uuid',
  title: 'PDF extraction workflow',
  workflowSpec: spec,
  graph,
  configuration: { model: 'gpt-5', type: 'chat', temperature: 0 },
})
```
