// Props
import { ContentPipeline } from "smthrs";
type ContentPipelineProps = {
id?: string;
stages: ContentPipelineStage[];
skipIf?: boolean;
children: string | ReactNode; // initial prompt for stage[0]
};
type ContentPipelineStage = {
id: string;
agent: AgentLike;
output: OutputTarget;
label?: string;
};
export default smithers(() => (
<Workflow name="blog-pipeline">
<ContentPipeline
stages={[
{ id: "outline", agent: outliner, output: outputs.outline, label: "Create outline" },
{ id: "draft", agent: writer, output: outputs.draft, label: "Write draft" },
{ id: "edit", agent: editor, output: outputs.edited, label: "Edit and polish" },
]}
>
Write a blog post about building AI workflows with React components.
</ContentPipeline>
</Workflow>
));
Notes
- Each stage after the first depends on the previous via
needs. - Stage
idvalues must be unique within the workflow.
Source
The<ContentPipeline> implementation and the files it imports, straight from the package source. This section is generated; edit the source, not this block.
// @smithers-type-exports-begin
/** @typedef {import("./ContentPipelineProps.ts").ContentPipelineProps} ContentPipelineProps */
/** @typedef {import("./ContentPipelineStage.ts").ContentPipelineStage} ContentPipelineStage */
// @smithers-type-exports-end
import React from "react";
import { Sequence } from "./Sequence.js";
import { Task } from "./Task.js";
/**
* Progressive content refinement: outline -> draft -> edit -> publish.
*
* Composes Sequence and Task to create a typed waterfall where each
* stage is explicitly defined. Each Task uses `needs` to depend on
* the previous stage, passing output forward through the pipeline.
* @param {ContentPipelineProps} props
*/
export function ContentPipeline(props) {
if (props.skipIf) return null;
const { stages, children } = props;
const taskElements = stages.map((stage, index) => {
const taskProps = {
id: stage.id,
output: stage.output,
agent: stage.agent,
label: stage.label,
};
if (index === 0) {
// First stage receives the initial prompt.
return React.createElement(Task, taskProps, children);
}
// Subsequent stages depend on the previous stage. `deps` resolves the
// previous stage's output into this stage's prompt (`needs` alone is
// cache-context only and injects nothing), so the waterfall actually
// passes output forward.
const prevStage = stages[index - 1];
taskProps.needs = { previous: prevStage.id };
taskProps.deps = { previous: prevStage.output };
return React.createElement(
Task,
taskProps,
(d) =>
`Continue from the previous stage's output. Perform: ${stage.label ?? stage.id}\n\nPrevious stage output:\n${JSON.stringify(d.previous ?? "(no output)")}`,
);
});
return React.createElement(Sequence, null, ...taskElements);
}
import React from "react";
/** @typedef {import("./SequenceProps.ts").SequenceProps} SequenceProps */
/**
* @param {SequenceProps} props
*/
export function Sequence(props) {
if (props.skipIf) return null;
// Sequence carries only a display label; pass a sanitized bag (align with
// the sanitizing structural components) so control props don't leak through.
// `label` names the phase group in run views (graph, the Claude /workflows
// mirror) and is preserved in the persisted frame XML.
const next = {
...(props.label === undefined ? {} : { label: props.label }),
...(props.failurePolicy === undefined ? {} : { failurePolicy: props.failurePolicy }),
};
return React.createElement("smithers:sequence", next, props.children);
}
// @smithers-type-exports-begin
/**
* @template D
* @typedef {import("./InferDeps.ts").InferDeps<D>} InferDeps
*/
/** @typedef {import("./OutputTarget.ts").OutputTarget} OutputTarget */
// @smithers-type-exports-end
import { applyCliToolAllowlist } from "./cliToolAllowlist.js";
import { createTaskComponent } from "./taskCore.js";
export { renderPromptToText } from "./taskCore.js";
/**
* The Node/CLI-agent-aware `Task`. Every render-path behavior (deps
* resolution, agent-chain assembly, MDX prompt rendering, static/compute/agent
* branching) lives in `taskCore.js`; this file only supplies the CLI-agent
* tool-allowlist enforcement step (`applyCliToolAllowlist`, which statically
* imports `ClaudeCodeAgent`/`PiAgent`/`GeminiAgent`/`AntigravityAgent` — see
* `cliToolAllowlist.js`). `Task.browser.js` builds the same component with a
* no-op allowlist step instead, so it never pulls those Node-only
* (`node:child_process`-backed) classes into a browser bundle.
*/
export const Task = createTaskComponent({ applyCliToolAllowlist });
import type React from "react";
import type { ContentPipelineStage } from "./ContentPipelineStage.ts";
export type ContentPipelineProps = {
id?: string;
/** Pipeline stages executed in order. Each stage receives the previous stage's output. */
stages: ContentPipelineStage[];
/** Skip the entire pipeline. */
skipIf?: boolean;
/** Initial prompt/content for the first stage (string or ReactNode). */
children: string | React.ReactNode;
};
import type { AgentLike } from "@smthrs/agents/AgentLike";
import type { OutputTarget } from "./OutputTarget.ts";
export type ContentPipelineStage = {
/** Unique identifier for this stage. */
id: string;
/** Agent that performs this stage's work. */
agent: AgentLike;
/** Output schema for this stage. */
output: OutputTarget;
/** Human-readable label for the stage (used as task label). */
label?: string;
};