Skip to content
Development
Skill

/output-dev-step-function

Create step functions in steps.ts for Output SDK workflows. Use when implementing I/O operations, error handling, HTTP requests, or LLM calls.

From plugin
output
43052 skills11 agents1 command
Install
$ npx -y skills add growthxai/output --skill output-dev-step-function --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/output-dev-step-function

Context preview

The summary Claude sees to decide when to auto-load this skill.

Create step functions in steps.ts for Output SDK workflows. Use when implementing I/O operations, error handling, HTTP requests, or LLM calls.

SKILL.md

output-dev-step-function.SKILL.md
name: output-dev-step-function
description: Create step functions in steps.ts for Output SDK workflows. Use when implementing I/O operations, error handling, HTTP requests, or LLM calls.
allowed-tools: [Read, Write, Edit]

Creating Step Functions

Overview

This skill documents how to create step functions in `steps.ts` for Output SDK workflows. Steps are where all I/O operations happen - HTTP requests, LLM calls, database operations, file system access, etc.

When to Use This Skill

  • Implementing I/O operations for a workflow
  • Adding HTTP client integrations
  • Implementing LLM-powered steps
  • Handling errors with FatalError and ValidationError
  • Creating reusable step components

File Organization

Option 1: Flat File (Default)

For smaller workflows, use a single `steps.ts` file:

src/workflows/{workflow-name}/
├── workflow.ts
├── steps.ts         # All steps in one file
├── types.ts
└── ...

Option 2: Folder-Based (Large workflows)

For larger workflows with many steps, use a `steps/` folder:

src/workflows/{workflow-name}/
├── workflow.ts
├── steps/           # Steps split into individual files
│   ├── fetch_data.ts
│   ├── process.ts
│   └── validate.ts
├── types.ts
└── ...

Component Location Rules

**Important**: `step()` calls MUST be in files containing 'steps' in the path:

  • `src/workflows/my_workflow/steps.ts` ✓
  • `src/workflows/my_workflow/steps/fetch_data.ts` ✓
  • `src/shared/steps/common_steps.ts` ✓
  • `src/workflows/my_workflow/helpers.ts` ✗ (cannot contain step() calls)

Activity Isolation Constraints

Steps are Temporal activities with strict import rules to ensure deterministic replay.

Steps CAN import from:

  • Local workflow files: `./utils.js`, `./types.js`, `./helpers.js`
  • Local subdirectories: `./clients/pokeapi.js`, `./lib/helpers.js`
  • Shared utilities: `../../shared/utils/*.js`
  • Shared clients: `../../shared/clients/*.js`
  • Shared services: `../../shared/services/*.js`

Steps CANNOT import:

  • Other step files (even shared steps - workflows import those)
  • Evaluator files
  • Workflow files

**Example of WRONG imports:**

// WRONG - steps cannot import other steps
import { otherStep } from '../../shared/steps/other.js'; // ✗
import { anotherStep } from './other_steps.js'; // ✗

Critical Import Patterns

Core Imports

// CORRECT - Import from @outputai/core
import { step, z, FatalError, ValidationError } from '@outputai/core';

// WRONG - Never import z from zod
import { z } from 'zod';

HTTP Client Import

// CORRECT - Use @outputai/http wrapper
import { createKyClient } from '@outputai/http';

// WRONG - Never use axios directly
import axios from 'axios';

**Related Skill**: `output-error-http-client`

LLM Client Import

// CORRECT - Use @outputai/llm wrapper
import { generateText, Output } from '@outputai/llm';

// WRONG - Never call LLM providers directly
import OpenAI from 'openai';

ES Module Imports

All imports MUST use `.js` extension:

// CORRECT
import { InputSchema, OutputSchema } from './types.js';
import { GeminiService } from '../../shared/clients/gemini_client.js';

// WRONG - Missing .js extension
import { InputSchema, OutputSchema } from './types';

Basic Structure

import { step, z, FatalError, ValidationError } from '@outputai/core';
import { createKyClient } from '@outputai/http';
import { generateText, Output } from '@outputai/llm';

import { StepInputSchema, StepOutputSchema } from './types.js';

export const myStep = step( {
  name: 'myStep',
  description: 'Description of what this step does',
  inputSchema: StepInputSchema,
  outputSchema: StepOutputSchema,
  fn: async input => {
    // Implementation with I/O operations
    return { /* output matching outputSchema */ };
  }
} );

Required Properties

name (string)

Unique identifier for the step. Use camelCase.

name: 'generateImageIdeas'

description (string)

Human-readable description of the step's purpose.

description: 'Generate creative infographic prompt ideas using Claude'

inputSchema (Zod schema)

Schema for validating step input. Define in `types.ts` and import.

inputSchema: z.object( {
  content: z.string(),
  numberOfIdeas: z.number()
} )

outputSchema (Zod schema)

Schema for validating step output. Define in `types.ts` and import.

outputSchema: z.array( z.string() )

fn (async function)

The step execution function. This is where I/O operations happen.

fn: async input => {
  const result = await someExternalService( input );
  return result;
}

HTTP Client Usage

Creating an HTTP Client

import { createKyClient } from '@outputai/http';
import { FatalError, ValidationError } from '@outputai/core';

const RETRY_STATUS_CODES = [ 408, 429, 500, 502, 503, 504 ];
const FATAL_STATUS_CODES = [ 401, 403, 404 ];

const client = createKyClient( {
  timeout: 30000,
  retry: {
    limit: 3,
    statusCodes: RETRY_STATUS_CODES
  },
  hooks: {
    beforeError: [
      ( { error } ) => {
        const status = error.response?.status;
        const message = error.message;

        if ( status && FATAL_STATUS_CODES.includes( status ) ) {
          throw new FatalError(
            `HTTP ${status} error: ${message}. This is a permanent error.`
          );
        }

        throw new ValidationError(
          `HTTP request failed: ${message}`
        );
      }
    ]
  }
} );

Making HTTP Requests

// GET request
const response = await client.get( 'https://api.example.com/data' );
const data = await response.json();

// POST request with JSON body
const response = await client.post( 'https://api.example.com/submit', {
  json: { field: 'value' }
} );

// HEAD request (check URL accessibility)
const response = await client.head( url );
const contentType = re
Read more
Ships withoutput

The open-source TypeScript framework for building AI workflows and agents. Designed for Claude Code — describe what you want, Claude builds it, with all the best practices already in place. One framework.

Get the whole plugin