Skip to content

Commit b5dab52

Browse files
claudeericallam
authored andcommitted
docs: version input streams under 4.4.2 and add docs site pages
Move input streams documentation from rules/4.3.0 to rules/4.4.2, reverting 4.3.0 to its pre-input-streams state. Add comprehensive input streams documentation to the Mintlify docs site with pages for task-side usage, backend sending, and React hook patterns. - rules/4.4.2/realtime.md: input streams docs (moved from 4.3.0) - rules/manifest.json: add 4.4.2 version, revert 4.3.0 realtime - docs/tasks/input-streams.mdx: main input streams guide - docs/realtime/backend/input-streams.mdx: backend sending patterns - docs/realtime/react-hooks/input-streams.mdx: React UI patterns - docs/docs.json: add input streams to navigation - Cross-references from existing streams and realtime overview docs https://claude.ai/code/session_01SJHJts7r2yAxmoKLLz8vpc
1 parent 0b6ff3c commit b5dab52

9 files changed

Lines changed: 707 additions & 5 deletions

File tree

docs/docs.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"tags",
7575
"runs/metadata",
7676
"tasks/streams",
77+
"tasks/input-streams",
7778
"run-usage",
7879
"context",
7980
"runs/priority",
@@ -143,6 +144,7 @@
143144
"realtime/react-hooks/triggering",
144145
"realtime/react-hooks/subscribe",
145146
"realtime/react-hooks/streams",
147+
"realtime/react-hooks/input-streams",
146148
"realtime/react-hooks/swr",
147149
"realtime/react-hooks/use-wait-token"
148150
]
@@ -152,7 +154,8 @@
152154
"pages": [
153155
"realtime/backend/overview",
154156
"realtime/backend/subscribe",
155-
"realtime/backend/streams"
157+
"realtime/backend/streams",
158+
"realtime/backend/input-streams"
156159
]
157160
}
158161
]
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
---
2+
title: Input Streams
3+
sidebarTitle: Input Streams
4+
description: Send data into running tasks from your backend code
5+
---
6+
7+
The Input Streams API allows you to send data into running Trigger.dev tasks from your backend code. This enables bidirectional communication — while [output streams](/realtime/backend/streams) let you read data from tasks, input streams let you push data into them.
8+
9+
<Note>
10+
To learn how to receive input stream data inside your tasks, see our [Input Streams](/tasks/input-streams) documentation. For frontend applications using React, see our [React hooks input streams documentation](/realtime/react-hooks/input-streams).
11+
</Note>
12+
13+
## Sending data to a running task
14+
15+
### Using defined input streams (Recommended)
16+
17+
The recommended approach is to use [defined input streams](/tasks/input-streams#defining-input-streams) for full type safety:
18+
19+
```ts
20+
import { cancelSignal, approval } from "./trigger/streams";
21+
22+
// Cancel a running AI stream
23+
await cancelSignal.send(runId, { reason: "User clicked stop" });
24+
25+
// Approve a draft
26+
await approval.send(runId, { approved: true, reviewer: "alice@example.com" });
27+
```
28+
29+
The `.send()` method is fully typed — the data parameter must match the generic type you defined on the input stream.
30+
31+
## Practical examples
32+
33+
### Cancel from a Next.js API route
34+
35+
```ts app/api/cancel/route.ts
36+
import { cancelStream } from "@/trigger/streams";
37+
38+
export async function POST(req: Request) {
39+
const { runId } = await req.json();
40+
41+
await cancelStream.send(runId, { reason: "User clicked stop" });
42+
43+
return Response.json({ cancelled: true });
44+
}
45+
```
46+
47+
### Approval workflow API
48+
49+
```ts app/api/approve/route.ts
50+
import { approval } from "@/trigger/streams";
51+
52+
export async function POST(req: Request) {
53+
const { runId, approved, reviewer } = await req.json();
54+
55+
await approval.send(runId, {
56+
approved,
57+
reviewer,
58+
});
59+
60+
return Response.json({ success: true });
61+
}
62+
```
63+
64+
### Remix action handler
65+
66+
```ts app/routes/api.approve.ts
67+
import { json, type ActionFunctionArgs } from "@remix-run/node";
68+
import { approval } from "~/trigger/streams";
69+
70+
export async function action({ request }: ActionFunctionArgs) {
71+
const formData = await request.formData();
72+
const runId = formData.get("runId") as string;
73+
const approved = formData.get("approved") === "true";
74+
const reviewer = formData.get("reviewer") as string;
75+
76+
await approval.send(runId, { approved, reviewer });
77+
78+
return json({ success: true });
79+
}
80+
```
81+
82+
### Express handler
83+
84+
```ts
85+
import express from "express";
86+
import { cancelSignal } from "./trigger/streams";
87+
88+
const app = express();
89+
app.use(express.json());
90+
91+
app.post("/api/cancel", async (req, res) => {
92+
const { runId, reason } = req.body;
93+
94+
await cancelSignal.send(runId, { reason });
95+
96+
res.json({ cancelled: true });
97+
});
98+
```
99+
100+
### Sending from another task
101+
102+
You can send input stream data from one task to another running task:
103+
104+
```ts
105+
import { task } from "@trigger.dev/sdk";
106+
import { approval } from "./streams";
107+
108+
export const reviewerTask = task({
109+
id: "auto-reviewer",
110+
run: async (payload: { targetRunId: string }) => {
111+
// Perform automated review logic...
112+
const isApproved = await performReview();
113+
114+
// Send approval to the waiting task
115+
await approval.send(payload.targetRunId, {
116+
approved: isApproved,
117+
reviewer: "auto-reviewer",
118+
});
119+
},
120+
});
121+
```
122+
123+
## Error handling
124+
125+
The `.send()` method will throw if:
126+
127+
- The run has already completed, failed, or been canceled
128+
- The payload exceeds the 1MB size limit
129+
- The run ID is invalid
130+
131+
```ts
132+
import { cancelSignal } from "./trigger/streams";
133+
134+
try {
135+
await cancelSignal.send(runId, { reason: "User clicked stop" });
136+
} catch (error) {
137+
console.error("Failed to send:", error);
138+
// Handle the error — the run may have already completed
139+
}
140+
```
141+
142+
## Important notes
143+
144+
- Maximum payload size per `.send()` call is **1MB**
145+
- You cannot send data to a completed, failed, or canceled run
146+
- Data sent before a listener is registered inside the task is **buffered** and delivered when a listener attaches
147+
- Input streams require [Realtime Streams v2](/tasks/streams#enabling-streams-v2) (enabled by default in SDK 4.1.0+)

docs/realtime/backend/overview.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ There are three main categories of functionality:
1515
- **[Subscribe functions](/realtime/backend/subscribe)** - Subscribe to run updates using async iterators
1616
- **[Metadata](/realtime/backend/subscribe#subscribe-to-metadata-updates-from-your-tasks)** - Update and subscribe to run metadata in real-time
1717
- **[Streams](/realtime/backend/streams)** - Read and consume real-time streaming data from your tasks
18+
- **[Input Streams](/realtime/backend/input-streams)** - Send data into running tasks for cancellation, approvals, and interactive workflows
1819

1920
<Note>
20-
To learn how to emit streams from your tasks, see our [Realtime Streams](/tasks/streams) documentation.
21+
To learn how to emit streams from your tasks, see our [Realtime Streams](/tasks/streams) documentation. To learn about sending data into tasks, see our [Input Streams](/tasks/input-streams) documentation.
2122
</Note>
2223

2324
## Authentication

docs/realtime/overview.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Once subscribed, you'll receive the complete [run object](/realtime/run-object)
1919
- **Metadata updates** - Custom progress tracking, status updates, user data, etc. ([React hooks](/realtime/react-hooks/subscribe#using-metadata-to-show-progress-in-your-ui) | [backend](/realtime/backend/subscribe#subscribe-to-metadata-updates-from-your-tasks))
2020
- **Tag changes** - When [tags](/tags) are added or removed from the run
2121
- **Realtime Streams** - Stream real-time data from your tasks, perfect for AI/LLM outputs and progress updates. ([Learn more](/tasks/streams) | [React hooks](/realtime/react-hooks/streams) | [Backend](/realtime/backend/streams))
22+
- **Input Streams** - Send data into running tasks for cancellation, approvals, and interactive workflows. ([Learn more](/tasks/input-streams) | [React hooks](/realtime/react-hooks/input-streams) | [Backend](/realtime/backend/input-streams))
2223

2324
## Using Realtime in your applications
2425

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
---
2+
title: Input Streams hooks
3+
sidebarTitle: Input Streams
4+
description: Send data into running tasks from your React components
5+
---
6+
7+
These patterns allow you to send data into running tasks from your React frontend. This is useful for cancelling AI streams, approving workflows, or sending user input to interactive agents.
8+
9+
<Note>
10+
To learn how to define and receive input stream data inside your tasks, see our [Input Streams](/tasks/input-streams) documentation. For backend usage, see the [backend input streams documentation](/realtime/backend/input-streams).
11+
</Note>
12+
13+
## Sending data from React
14+
15+
Input streams don't have a dedicated React hook — instead, you call `.send()` directly from your component event handlers, typically through an API route for security.
16+
17+
### Via an API route (Recommended)
18+
19+
The recommended approach is to send input stream data through your backend API routes, which keeps your secret API key server-side:
20+
21+
```tsx
22+
"use client";
23+
24+
import { useRealtimeStream } from "@trigger.dev/react-hooks";
25+
import { aiOutput } from "@/trigger/streams";
26+
27+
export function AIChat({
28+
runId,
29+
accessToken,
30+
}: {
31+
runId: string;
32+
accessToken: string;
33+
}) {
34+
const { parts, error } = useRealtimeStream(aiOutput, runId, {
35+
accessToken,
36+
timeoutInSeconds: 300,
37+
});
38+
39+
const handleCancel = async () => {
40+
await fetch("/api/cancel", {
41+
method: "POST",
42+
headers: { "Content-Type": "application/json" },
43+
body: JSON.stringify({ runId }),
44+
});
45+
};
46+
47+
if (error) return <div>Error: {error.message}</div>;
48+
if (!parts) return <div>Loading...</div>;
49+
50+
return (
51+
<div>
52+
<div>{parts.join("")}</div>
53+
<button onClick={handleCancel}>Stop generating</button>
54+
</div>
55+
);
56+
}
57+
```
58+
59+
With the corresponding API route:
60+
61+
```ts app/api/cancel/route.ts
62+
import { cancelStream } from "@/trigger/streams";
63+
64+
export async function POST(req: Request) {
65+
const { runId } = await req.json();
66+
await cancelStream.send(runId, { reason: "User clicked stop" });
67+
return Response.json({ cancelled: true });
68+
}
69+
```
70+
71+
### Approval workflow UI
72+
73+
Here's a complete example of a human-in-the-loop approval interface:
74+
75+
```tsx
76+
"use client";
77+
78+
import { useRealtimeRun } from "@trigger.dev/react-hooks";
79+
import type { draftEmailTask } from "@/trigger/draft-email";
80+
81+
export function ApprovalUI({
82+
runId,
83+
accessToken,
84+
}: {
85+
runId: string;
86+
accessToken: string;
87+
}) {
88+
const { run, error } = useRealtimeRun<typeof draftEmailTask>(runId, {
89+
accessToken,
90+
});
91+
92+
const handleApprove = async (approved: boolean) => {
93+
await fetch("/api/approve", {
94+
method: "POST",
95+
headers: { "Content-Type": "application/json" },
96+
body: JSON.stringify({
97+
runId,
98+
approved,
99+
reviewer: "current-user@example.com",
100+
}),
101+
});
102+
};
103+
104+
if (error) return <div>Error: {error.message}</div>;
105+
if (!run) return <div>Loading...</div>;
106+
107+
return (
108+
<div>
109+
<div>Status: {run.status}</div>
110+
{run.status === "EXECUTING" && (
111+
<div>
112+
<p>Draft is ready for review. Please approve or reject:</p>
113+
<button onClick={() => handleApprove(true)}>Approve</button>
114+
<button onClick={() => handleApprove(false)}>Reject</button>
115+
</div>
116+
)}
117+
{run.status === "COMPLETED" && run.output && (
118+
<div>
119+
Result: {run.output.sent ? "Email sent!" : "Email rejected"}
120+
</div>
121+
)}
122+
</div>
123+
);
124+
}
125+
```
126+
127+
### Combining with output streams
128+
129+
A common pattern is to read output streams while sending input streams — for example, displaying an AI response while allowing the user to cancel:
130+
131+
```tsx
132+
"use client";
133+
134+
import { useRealtimeStream, useRealtimeRun } from "@trigger.dev/react-hooks";
135+
import { aiOutput } from "@/trigger/streams";
136+
import type { aiTask } from "@/trigger/ai-task";
137+
138+
export function InteractiveAI({
139+
runId,
140+
accessToken,
141+
}: {
142+
runId: string;
143+
accessToken: string;
144+
}) {
145+
const { run } = useRealtimeRun<typeof aiTask>(runId, { accessToken });
146+
const { parts } = useRealtimeStream(aiOutput, runId, {
147+
accessToken,
148+
timeoutInSeconds: 300,
149+
});
150+
151+
const handleCancel = async () => {
152+
await fetch("/api/cancel", {
153+
method: "POST",
154+
headers: { "Content-Type": "application/json" },
155+
body: JSON.stringify({ runId }),
156+
});
157+
};
158+
159+
const isRunning = run?.status === "EXECUTING";
160+
161+
return (
162+
<div>
163+
<div>{parts?.join("") ?? "Waiting for response..."}</div>
164+
{isRunning && (
165+
<button onClick={handleCancel}>Stop generating</button>
166+
)}
167+
</div>
168+
);
169+
}
170+
```
171+
172+
## Important notes
173+
174+
- Always send input stream data through your backend API routes to keep your API keys secure
175+
- Input streams require [Realtime Streams v2](/tasks/streams#enabling-streams-v2) (enabled by default in SDK 4.1.0+)
176+
- You cannot send data to a completed, failed, or canceled run
177+
- For the complete task-side API (`once()`, `on()`, `peek()`), see the [Input Streams guide](/tasks/input-streams)

0 commit comments

Comments
 (0)