Update realtime docs for streaming and a couple tweaks to the SDK (#1486)
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@trigger.dev/react-hooks": patch
|
||||
"@trigger.dev/sdk": patch
|
||||
---
|
||||
|
||||
React hooks now all accept accessToken and baseURL options so the use of the Provider is no longer necessary
|
||||
|
||||
+20
-93
@@ -4,31 +4,16 @@ sidebarTitle: Overview & Auth
|
||||
description: Using the Trigger.dev SDK from your frontend application.
|
||||
---
|
||||
|
||||
You can use certain SDK functions in your frontend application to interact with the Trigger.dev API. This guide will show you how to authenticate your requests and use the SDK in your frontend application.
|
||||
You can use our [React hooks](/frontend/react-hooks) in your frontend application to interact with the Trigger.dev API. This guide will show you how to generate Public Access Tokens that can be used to authenticate your requests.
|
||||
|
||||
## Authentication
|
||||
|
||||
You must authenticate your requests using a "Public Access Token" when using the SDK in your frontend application. To create a Public Access Token, you can use the `auth.createPublicToken` function in your backend code:
|
||||
To create a Public Access Token, you can use the `auth.createPublicToken` function in your **backend** code:
|
||||
|
||||
```tsx
|
||||
const publicToken = await auth.createPublicToken();
|
||||
```
|
||||
|
||||
To use a Public Access Token in your frontend application, you can call the `auth.configure` function or the `auth.withAuth` function:
|
||||
|
||||
```ts
|
||||
import { auth } from "@trigger.dev/sdk/v3";
|
||||
|
||||
auth.configure({
|
||||
accessToken: publicToken,
|
||||
});
|
||||
|
||||
// or
|
||||
await auth.withAuth({ accessToken: publicToken }, async () => {
|
||||
// Your code here will use the public token
|
||||
});
|
||||
```
|
||||
|
||||
### Scopes
|
||||
|
||||
By default a Public Access Token has limited permissions. You can specify the scopes you need when creating a Public Access Token:
|
||||
@@ -104,6 +89,22 @@ const publicToken = await auth.createPublicToken({
|
||||
});
|
||||
```
|
||||
|
||||
### Write scopes
|
||||
|
||||
You can also specify write scopes, which is required for triggering tasks from your frontend application:
|
||||
|
||||
```ts
|
||||
const publicToken = await auth.createPublicToken({
|
||||
scopes: {
|
||||
write: {
|
||||
tasks: ["my-task-1", "my-task-2"],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
This will allow the token to trigger the specified tasks. `tasks` is the only write scope available at the moment.
|
||||
|
||||
### Expiration
|
||||
|
||||
By default, Public Access Token's expire after 15 minutes. You can specify a different expiration time when creating a Public Access Token:
|
||||
@@ -163,80 +164,6 @@ const handle = await tasks.batchTrigger("my-task", [
|
||||
console.log(handle.publicAccessToken);
|
||||
```
|
||||
|
||||
## Available SDK functions
|
||||
## Usage
|
||||
|
||||
Currently the following functions are available in the frontend SDK:
|
||||
|
||||
### runs.retrieve
|
||||
|
||||
The `runs.retrieve` function allows you to retrieve a run by its ID.
|
||||
|
||||
```ts
|
||||
import { runs, auth } from "@trigger.dev/sdk/v3";
|
||||
|
||||
// Somewhere in your backend code
|
||||
const handle = await tasks.trigger("my-task", { some: "data" });
|
||||
|
||||
// In your frontend code
|
||||
auth.configure({
|
||||
accessToken: handle.publicAccessToken,
|
||||
});
|
||||
|
||||
const run = await runs.retrieve(handle.id);
|
||||
```
|
||||
|
||||
Learn more about the `runs.retrieve` function in the [runs.retrieve doc](/management/runs/retrieve).
|
||||
|
||||
### runs.subscribeToRun
|
||||
|
||||
The `runs.subscribeToRun` function allows you to subscribe to a run by its ID, and receive updates in real-time when the run changes.
|
||||
|
||||
```ts
|
||||
import { runs, auth } from "@trigger.dev/sdk/v3";
|
||||
|
||||
// Somewhere in your backend code
|
||||
const handle = await tasks.trigger("my-task", { some: "data" });
|
||||
|
||||
// In your frontend code
|
||||
auth.configure({
|
||||
accessToken: handle.publicAccessToken,
|
||||
});
|
||||
|
||||
for await (const run of runs.subscribeToRun(handle.id)) {
|
||||
// This will log the run every time it changes
|
||||
console.log(run);
|
||||
}
|
||||
```
|
||||
|
||||
See the [Realtime doc](/realtime) for more information.
|
||||
|
||||
### runs.subscribeToRunsWithTag
|
||||
|
||||
The `runs.subscribeToRunsWithTag` function allows you to subscribe to runs with a specific tag, and receive updates in real-time when the runs change.
|
||||
|
||||
```ts
|
||||
import { runs, auth } from "@trigger.dev/sdk/v3";
|
||||
|
||||
// Somewhere in your backend code
|
||||
const handle = await tasks.trigger("my-task", { some: "data" }, { tags: ["my-tag"] });
|
||||
|
||||
// In your frontend code
|
||||
auth.configure({
|
||||
accessToken: handle.publicAccessToken,
|
||||
});
|
||||
|
||||
for await (const run of runs.subscribeToRunsWithTag("my-tag")) {
|
||||
// This will log the run every time it changes
|
||||
console.log(run);
|
||||
}
|
||||
```
|
||||
|
||||
See the [Realtime doc](/realtime) for more information.
|
||||
|
||||
## React hooks
|
||||
|
||||
We also provide React hooks to make it easier to use the SDK in your React application. See our [React hooks](/frontend/react-hooks) documentation for more information.
|
||||
|
||||
## Triggering tasks
|
||||
|
||||
We don't currently support triggering tasks from the frontend SDK. If this is something you need, please let us know by [upvoting the feature](https://feedback.trigger.dev/p/ability-to-trigger-tasks-from-frontend).
|
||||
To learn how to use these Public Access Tokens, see our [React hooks](/frontend/react-hooks) guide.
|
||||
|
||||
+435
-84
@@ -28,14 +28,35 @@ yarn install @trigger.dev/react-hooks
|
||||
|
||||
## Authentication
|
||||
|
||||
Before you can use the hooks, you need to provide a public access token to the `TriggerAuthContext` provider. Learn more about [authentication in the frontend guide](/frontend/overview).
|
||||
All hooks accept an optional last argument `options` that accepts an `accessToken` param, which should be a valid Public Access Token. Learn more about [generating tokens in the frontend guide](/frontend/overview).
|
||||
|
||||
```tsx
|
||||
import { useRealtimeRun } from "@trigger.dev/react-hooks";
|
||||
|
||||
export function MyComponent({
|
||||
runId,
|
||||
publicAccessToken,
|
||||
}: {
|
||||
runId: string;
|
||||
publicAccessToken: string;
|
||||
}) {
|
||||
const { run, error } = useRealtimeRun(runId, {
|
||||
accessToken: publicAccessToken, // This is required
|
||||
baseURL: "https://your-trigger-dev-instance.com", // optional, only needed if you are self-hosting Trigger.dev
|
||||
});
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, you can use our `TriggerAuthContext` provider
|
||||
|
||||
```tsx
|
||||
import { TriggerAuthContext } from "@trigger.dev/react-hooks";
|
||||
|
||||
export function SetupTrigger() {
|
||||
export function SetupTrigger({ publicAccessToken }: { publicAccessToken: string }) {
|
||||
return (
|
||||
<TriggerAuthContext.Provider value={{ accessToken: "your-access-token" }}>
|
||||
<TriggerAuthContext.Provider value={{ accessToken: publicAccessToken }}>
|
||||
<MyComponent />
|
||||
</TriggerAuthContext.Provider>
|
||||
);
|
||||
@@ -47,11 +68,11 @@ Now children components can use the hooks to interact with the Trigger.dev API.
|
||||
```tsx
|
||||
import { TriggerAuthContext } from "@trigger.dev/react-hooks";
|
||||
|
||||
export function SetupTrigger() {
|
||||
export function SetupTrigger({ publicAccessToken }: { publicAccessToken: string }) {
|
||||
return (
|
||||
<TriggerAuthContext.Provider
|
||||
value={{
|
||||
accessToken: "your-access-token",
|
||||
accessToken: publicAccessToken,
|
||||
baseURL: "https://your-trigger-dev-instance.com",
|
||||
}}
|
||||
>
|
||||
@@ -217,9 +238,7 @@ export async function generatePublicAccessToken(runId: string) {
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Usage
|
||||
|
||||
### SWR vs Realtime hooks
|
||||
## SWR vs Realtime hooks
|
||||
|
||||
We offer two "styles" of hooks: SWR and Realtime. The SWR hooks use the [swr](https://swr.vercel.app/) library to fetch data once and cache it. The Realtime hooks use [Trigger.dev realtime](/realtime) to subscribe to updates in real-time.
|
||||
|
||||
@@ -231,77 +250,7 @@ We offer two "styles" of hooks: SWR and Realtime. The SWR hooks use the [swr](ht
|
||||
|
||||
All hooks named `useRealtime*` are Realtime hooks, and all hooks named `use*` are SWR hooks.
|
||||
|
||||
#### Common SWR hook options
|
||||
|
||||
You can pass the following options to the all SWR hooks:
|
||||
|
||||
<ParamField path="revalidateOnFocus" type="boolean">
|
||||
Revalidate the data when the window regains focus.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="revalidateOnReconnect" type="boolean">
|
||||
Revalidate the data when the browser regains a network connection.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="refreshInterval" type="number">
|
||||
Poll for updates at the specified interval (in milliseconds). Polling is not recommended for most
|
||||
use-cases. Use the Realtime hooks instead.
|
||||
</ParamField>
|
||||
|
||||
#### Common SWR hook return values
|
||||
|
||||
<ResponseField name="error" type="Error">
|
||||
An error object if an error occurred while fetching the data.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="isLoading" type="boolean">
|
||||
A boolean indicating if the data is currently being fetched.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="isValidating" type="boolean">
|
||||
A boolean indicating if the data is currently being revalidated.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="isError" type="boolean">
|
||||
A boolean indicating if an error occurred while fetching the data.
|
||||
</ResponseField>
|
||||
|
||||
### useRun
|
||||
|
||||
The `useRun` hook allows you to fetch a run by its ID.
|
||||
|
||||
```tsx
|
||||
"use client"; // This is needed for Next.js App Router or other RSC frameworks
|
||||
|
||||
import { useRun } from "@trigger.dev/react-hooks";
|
||||
|
||||
export function MyComponent({ runId }: { runId: string }) {
|
||||
const { run, error, isLoading } = useRun(runId);
|
||||
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
return <div>Run: {run.id}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
The `run` object returned is the same as the [run object](/management/runs/retrieve) returned by the Trigger.dev API. To correctly type the run's payload and output, you can provide the type of your task to the `useRun` hook:
|
||||
|
||||
```tsx
|
||||
import { useRun } from "@trigger.dev/react-hooks";
|
||||
import type { myTask } from "@/trigger/myTask";
|
||||
|
||||
export function MyComponent({ runId }: { runId: string }) {
|
||||
const { run, error, isLoading } = useRun<typeof myTask>(runId);
|
||||
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
// Now run.payload and run.output are correctly typed
|
||||
|
||||
return <div>Run: {run.id}</div>;
|
||||
}
|
||||
```
|
||||
## Realtime hooks
|
||||
|
||||
### useRealtimeRun
|
||||
|
||||
@@ -312,8 +261,16 @@ The `useRealtimeRun` hook allows you to subscribe to a run by its ID.
|
||||
|
||||
import { useRealtimeRun } from "@trigger.dev/react-hooks";
|
||||
|
||||
export function MyComponent({ runId }: { runId: string }) {
|
||||
const { run, error } = useRealtimeRun(runId);
|
||||
export function MyComponent({
|
||||
runId,
|
||||
publicAccessToken,
|
||||
}: {
|
||||
runId: string;
|
||||
publicAccessToken: string;
|
||||
}) {
|
||||
const { run, error } = useRealtimeRun(runId, {
|
||||
accessToken: publicAccessToken,
|
||||
});
|
||||
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
@@ -327,8 +284,16 @@ To correctly type the run's payload and output, you can provide the type of your
|
||||
import { useRealtimeRun } from "@trigger.dev/react-hooks";
|
||||
import type { myTask } from "@/trigger/myTask";
|
||||
|
||||
export function MyComponent({ runId }: { runId: string }) {
|
||||
const { run, error } = useRealtimeRun<typeof myTask>(runId);
|
||||
export function MyComponent({
|
||||
runId,
|
||||
publicAccessToken,
|
||||
}: {
|
||||
runId: string;
|
||||
publicAccessToken: string;
|
||||
}) {
|
||||
const { run, error } = useRealtimeRun<typeof myTask>(runId, {
|
||||
accessToken: publicAccessToken,
|
||||
});
|
||||
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
@@ -338,7 +303,7 @@ export function MyComponent({ runId }: { runId: string }) {
|
||||
}
|
||||
```
|
||||
|
||||
See our [Realtime documentation](/realtime) for more information.
|
||||
See our [Realtime documentation](/realtime) for more information about the type of the run object and more.
|
||||
|
||||
### useRealtimeRunsWithTag
|
||||
|
||||
@@ -444,3 +409,389 @@ export function MyComponent({ batchId }: { batchId: string }) {
|
||||
```
|
||||
|
||||
See our [Realtime documentation](/realtime) for more information.
|
||||
|
||||
### useRealtimeRunWithStreams
|
||||
|
||||
The `useRealtimeRunWithStreams` hook allows you to subscribe to a run by its ID and also receive any streams that are emitted by the task. See our [Realtime documentation](/realtime#streams) for more information about emitting streams from a task.
|
||||
|
||||
```tsx
|
||||
"use client"; // This is needed for Next.js App Router or other RSC frameworks
|
||||
|
||||
import { useRealtimeRunWithStreams } from "@trigger.dev/react-hooks";
|
||||
|
||||
export function MyComponent({
|
||||
runId,
|
||||
publicAccessToken,
|
||||
}: {
|
||||
runId: string;
|
||||
publicAccessToken: string;
|
||||
}) {
|
||||
const { run, streams, error } = useRealtimeRunWithStreams(runId, {
|
||||
accessToken: publicAccessToken,
|
||||
});
|
||||
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>Run: {run.id}</div>
|
||||
<div>
|
||||
{Object.keys(streams).map((stream) => (
|
||||
<div key={stream}>Stream: {stream}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
You can provide the type of the streams to the `useRealtimeRunWithStreams` hook:
|
||||
|
||||
```tsx
|
||||
import { useRealtimeRunWithStreams } from "@trigger.dev/react-hooks";
|
||||
import type { myTask } from "@/trigger/myTask";
|
||||
|
||||
type STREAMS = {
|
||||
openai: string; // this is the type of each "part" of the stream
|
||||
};
|
||||
|
||||
export function MyComponent({
|
||||
runId,
|
||||
publicAccessToken,
|
||||
}: {
|
||||
runId: string;
|
||||
publicAccessToken: string;
|
||||
}) {
|
||||
const { run, streams, error } = useRealtimeRunWithStreams<typeof myTask, STREAMS>(runId, {
|
||||
accessToken: publicAccessToken,
|
||||
});
|
||||
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
const text = streams.openai?.map((part) => part).join("");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>Run: {run.id}</div>
|
||||
<div>{text}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
As you can see above, each stream is an array of the type you provided, keyed by the stream name. If instead of a pure text stream you have a stream of objects, you can provide the type of the object:
|
||||
|
||||
```tsx
|
||||
import type { TextStreamPart } from "ai";
|
||||
import type { myTask } from "@/trigger/myTask";
|
||||
|
||||
type STREAMS = { openai: TextStreamPart<{}> };
|
||||
|
||||
export function MyComponent({
|
||||
runId,
|
||||
publicAccessToken,
|
||||
}: {
|
||||
runId: string;
|
||||
publicAccessToken: string;
|
||||
}) {
|
||||
const { run, streams, error } = useRealtimeRunWithStreams<typeof myTask, STREAMS>(runId, {
|
||||
accessToken: publicAccessToken,
|
||||
});
|
||||
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
const text = streams.openai
|
||||
?.filter((stream) => stream.type === "text-delta")
|
||||
?.map((part) => part.text)
|
||||
.join("");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>Run: {run.id}</div>
|
||||
<div>{text}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Common options
|
||||
|
||||
#### enabled
|
||||
|
||||
You can pass the `enabled` option to the Realtime hooks to enable or disable the subscription.
|
||||
|
||||
```tsx
|
||||
import { useRealtimeRun } from "@trigger.dev/react-hooks";
|
||||
|
||||
export function MyComponent({
|
||||
runId,
|
||||
publicAccessToken,
|
||||
enabled,
|
||||
}: {
|
||||
runId: string;
|
||||
publicAccessToken: string;
|
||||
enabled: boolean;
|
||||
}) {
|
||||
const { run, error } = useRealtimeRun(runId, {
|
||||
accessToken: publicAccessToken,
|
||||
enabled,
|
||||
});
|
||||
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
return <div>Run: {run.id}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
This allows you to conditionally disable using the hook based on some state.
|
||||
|
||||
#### id
|
||||
|
||||
You can pass the `id` option to the Realtime hooks to change the ID of the subscription.
|
||||
|
||||
```tsx
|
||||
import { useRealtimeRun } from "@trigger.dev/react-hooks";
|
||||
|
||||
export function MyComponent({
|
||||
id,
|
||||
runId,
|
||||
publicAccessToken,
|
||||
enabled,
|
||||
}: {
|
||||
id: string;
|
||||
runId: string;
|
||||
publicAccessToken: string;
|
||||
enabled: boolean;
|
||||
}) {
|
||||
const { run, error } = useRealtimeRun(runId, {
|
||||
accessToken: publicAccessToken,
|
||||
enabled,
|
||||
id,
|
||||
});
|
||||
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
return <div>Run: {run.id}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
This allows you to change the ID of the subscription based on some state. Passing in a different ID will unsubscribe from the current subscription and subscribe to the new one (and remove any cached data).
|
||||
|
||||
#### experimental_throttleInMs
|
||||
|
||||
The `*withStreams` variants of the Realtime hooks accept an `experimental_throttleInMs` option to throttle the updates from the server. This can be useful if you are getting too many updates and want to reduce the number of updates.
|
||||
|
||||
```tsx
|
||||
import { useRealtimeRunsWithStreams } from "@trigger.dev/react-hooks";
|
||||
|
||||
export function MyComponent({
|
||||
runId,
|
||||
publicAccessToken,
|
||||
}: {
|
||||
runId: string;
|
||||
publicAccessToken: string;
|
||||
}) {
|
||||
const { runs, error } = useRealtimeRunsWithStreams(tag, {
|
||||
accessToken: publicAccessToken,
|
||||
experimental_throttleInMs: 1000, // Throttle updates to once per second
|
||||
});
|
||||
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{runs.map((run) => (
|
||||
<div key={run.id}>Run: {run.id}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## SWR Hooks
|
||||
|
||||
### useRun
|
||||
|
||||
The `useRun` hook allows you to fetch a run by its ID.
|
||||
|
||||
```tsx
|
||||
"use client"; // This is needed for Next.js App Router or other RSC frameworks
|
||||
|
||||
import { useRun } from "@trigger.dev/react-hooks";
|
||||
|
||||
export function MyComponent({ runId }: { runId: string }) {
|
||||
const { run, error, isLoading } = useRun(runId);
|
||||
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
return <div>Run: {run.id}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
The `run` object returned is the same as the [run object](/management/runs/retrieve) returned by the Trigger.dev API. To correctly type the run's payload and output, you can provide the type of your task to the `useRun` hook:
|
||||
|
||||
```tsx
|
||||
import { useRun } from "@trigger.dev/react-hooks";
|
||||
import type { myTask } from "@/trigger/myTask";
|
||||
|
||||
export function MyComponent({ runId }: { runId: string }) {
|
||||
const { run, error, isLoading } = useRun<typeof myTask>(runId, {
|
||||
refreshInterval: 0, // Disable polling
|
||||
});
|
||||
|
||||
if (isLoading) return <div>Loading...</div>;
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
// Now run.payload and run.output are correctly typed
|
||||
|
||||
return <div>Run: {run.id}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### Common options
|
||||
|
||||
You can pass the following options to the all SWR hooks:
|
||||
|
||||
<ParamField path="revalidateOnFocus" type="boolean">
|
||||
Revalidate the data when the window regains focus.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="revalidateOnReconnect" type="boolean">
|
||||
Revalidate the data when the browser regains a network connection.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="refreshInterval" type="number">
|
||||
Poll for updates at the specified interval (in milliseconds). Polling is not recommended for most
|
||||
use-cases. Use the Realtime hooks instead.
|
||||
</ParamField>
|
||||
|
||||
### Common return values
|
||||
|
||||
<ResponseField name="error" type="Error">
|
||||
An error object if an error occurred while fetching the data.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="isLoading" type="boolean">
|
||||
A boolean indicating if the data is currently being fetched.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="isValidating" type="boolean">
|
||||
A boolean indicating if the data is currently being revalidated.
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="isError" type="boolean">
|
||||
A boolean indicating if an error occurred while fetching the data.
|
||||
</ResponseField>
|
||||
|
||||
## Trigger Hooks
|
||||
|
||||
We provide a set of hooks that can be used to trigger tasks from your frontend application. You'll need to generate a Public Access Token with `write` permissions to use these hooks. See our [frontend guide](/frontend/overview#write-scopes) for more information.
|
||||
|
||||
### useTaskTrigger
|
||||
|
||||
The `useTaskTrigger` hook allows you to trigger a task from your frontend application.
|
||||
|
||||
```tsx
|
||||
"use client"; // This is needed for Next.js App Router or other RSC frameworks
|
||||
|
||||
import { useTaskTrigger } from "@trigger.dev/react-hooks";
|
||||
import type { myTask } from "@/trigger/myTask";
|
||||
|
||||
export function MyComponent({ publicAccessToken }: { publicAccessToken: string }) {
|
||||
const { submit, handle, error, isLoading } = useTaskTrigger<typeof myTask>("my-task", {
|
||||
accessToken: publicAccessToken,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return <div>Error: {error.message}</div>;
|
||||
}
|
||||
|
||||
if (handle) {
|
||||
return <div>Run ID: {handle.id}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>
|
||||
{isLoading ? "Loading..." : "Trigger Task"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### useRealtimeTaskTrigger
|
||||
|
||||
The `useRealtimeTaskTrigger` hook allows you to trigger a task from your frontend application and then subscribe to the run in using Realtime:
|
||||
|
||||
```tsx
|
||||
"use client"; // This is needed for Next.js App Router or other RSC frameworks
|
||||
|
||||
import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
|
||||
import type { myTask } from "@/trigger/myTask";
|
||||
|
||||
export function MyComponent({ publicAccessToken }: { publicAccessToken: string }) {
|
||||
const { submit, run, error, isLoading } = useRealtimeTaskTrigger<typeof myTask>("my-task", {
|
||||
accessToken: publicAccessToken,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return <div>Error: {error.message}</div>;
|
||||
}
|
||||
|
||||
// This is the realtime run object, which will automatically update when the run changes
|
||||
if (run) {
|
||||
return <div>Run ID: {run.id}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>
|
||||
{isLoading ? "Loading..." : "Trigger Task"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### useRealtimeTaskTriggerWithStreams
|
||||
|
||||
The `useRealtimeTaskTriggerWithStreams` hook allows you to trigger a task from your frontend application and then subscribe to the run in using Realtime, and also receive any streams that are emitted by the task.
|
||||
|
||||
```tsx
|
||||
"use client"; // This is needed for Next.js App Router or other RSC frameworks
|
||||
|
||||
import { useRealtimeTaskTriggerWithStreams } from "@trigger.dev/react-hooks";
|
||||
import type { myTask } from "@/trigger/myTask";
|
||||
|
||||
type STREAMS = {
|
||||
openai: string; // this is the type of each "part" of the stream
|
||||
};
|
||||
|
||||
export function MyComponent({ publicAccessToken }: { publicAccessToken: string }) {
|
||||
const { submit, run, streams, error, isLoading } = useRealtimeTaskTriggerWithStreams<
|
||||
typeof myTask,
|
||||
STREAMS
|
||||
>("my-task", {
|
||||
accessToken: publicAccessToken,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return <div>Error: {error.message}</div>;
|
||||
}
|
||||
|
||||
if (streams && run) {
|
||||
const text = streams.openai?.map((part) => part).join("");
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>Run ID: {run.id}</div>
|
||||
<div>{text}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>
|
||||
{isLoading ? "Loading..." : "Trigger Task"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
+11
-1
@@ -12,13 +12,23 @@ Trigger.dev v3 makes it easy to write reliable long-running tasks without timeou
|
||||
- We provide an SDK and CLI for writing tasks in your existing codebase, inside [/trigger folders](/config/config-file).
|
||||
- We provide different types of tasks: [regular](/tasks-regular) and [scheduled](/tasks/scheduled).
|
||||
- We provide a dashboard for monitoring, debugging, and managing your tasks.
|
||||
- We provide a [Realtime API](/realtime) for monitoring tasks in real-time, along with [React hooks](/frontend/react-hooks#realtime-hooks) for building custom dashboards.
|
||||
|
||||
We're [open source](https://github.com/triggerdotdev/trigger.dev) and you can choose to use the [Trigger.dev Cloud](https://cloud.trigger.dev) or [Self-host Trigger.dev](/open-source-self-hosting) on your own infrastructure.
|
||||
|
||||
## Getting started
|
||||
|
||||
<div className="w-full h-full aspect-video mb-3">
|
||||
<iframe width="100%" height="100%" src="https://www.youtube.com/embed/YH_4c0K7fGM?si=5JzZmZseuqI5aciM" title="Trigger.dev walkthrough" frameborder="0" allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen/>
|
||||
<iframe
|
||||
width="100%"
|
||||
height="100%"
|
||||
src="https://www.youtube.com/embed/YH_4c0K7fGM?si=5JzZmZseuqI5aciM"
|
||||
title="Trigger.dev walkthrough"
|
||||
frameborder="0"
|
||||
allow="accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allowfullscreen
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CardGroup>
|
||||
|
||||
+56
-17
@@ -1,7 +1,10 @@
|
||||
{
|
||||
"$schema": "https://mintlify.com/schema.json",
|
||||
"name": "Trigger.dev",
|
||||
"openapi": ["/openapi.yml", "/v3-openapi.yaml"],
|
||||
"openapi": [
|
||||
"/openapi.yml",
|
||||
"/v3-openapi.yaml"
|
||||
],
|
||||
"api": {
|
||||
"playground": {
|
||||
"mode": "simple"
|
||||
@@ -133,20 +136,30 @@
|
||||
"pages": [
|
||||
{
|
||||
"group": "Tasks",
|
||||
"pages": ["tasks/overview", "tasks/schemaTask", "tasks/scheduled"]
|
||||
"pages": [
|
||||
"tasks/overview",
|
||||
"tasks/schemaTask",
|
||||
"tasks/scheduled"
|
||||
]
|
||||
},
|
||||
"triggering",
|
||||
"runs",
|
||||
"apikeys",
|
||||
{
|
||||
"group": "Configuration",
|
||||
"pages": ["config/config-file", "config/extensions/overview"]
|
||||
"pages": [
|
||||
"config/config-file",
|
||||
"config/extensions/overview"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Development",
|
||||
"pages": ["cli-dev", "run-tests"]
|
||||
"pages": [
|
||||
"cli-dev",
|
||||
"run-tests"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Deployment",
|
||||
@@ -156,7 +169,9 @@
|
||||
"github-actions",
|
||||
{
|
||||
"group": "Deployment integrations",
|
||||
"pages": ["vercel-integration"]
|
||||
"pages": [
|
||||
"vercel-integration"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -168,7 +183,13 @@
|
||||
"errors-retrying",
|
||||
{
|
||||
"group": "Wait",
|
||||
"pages": ["wait", "wait-for", "wait-until", "wait-for-event", "wait-for-request"]
|
||||
"pages": [
|
||||
"wait",
|
||||
"wait-for",
|
||||
"wait-until",
|
||||
"wait-for-event",
|
||||
"wait-for-request"
|
||||
]
|
||||
},
|
||||
"queue-concurrency",
|
||||
"versioning",
|
||||
@@ -184,16 +205,19 @@
|
||||
},
|
||||
{
|
||||
"group": "Frontend usage",
|
||||
"pages": ["frontend/overview", "frontend/react-hooks"]
|
||||
"pages": [
|
||||
"frontend/overview",
|
||||
"frontend/react-hooks"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Realtime API",
|
||||
"pages": [
|
||||
"realtime/overview",
|
||||
"realtime/streams",
|
||||
"realtime/react-hooks",
|
||||
"realtime/subscribe-to-run",
|
||||
"realtime/subscribe-to-runs-with-tag",
|
||||
"realtime/use-realtime-run",
|
||||
"realtime/use-realtime-runs-with-tag"
|
||||
"realtime/subscribe-to-runs-with-tag"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -202,7 +226,10 @@
|
||||
"management/overview",
|
||||
{
|
||||
"group": "Tasks API",
|
||||
"pages": ["management/tasks/trigger", "management/tasks/batch-trigger"]
|
||||
"pages": [
|
||||
"management/tasks/trigger",
|
||||
"management/tasks/batch-trigger"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Runs API",
|
||||
@@ -241,7 +268,9 @@
|
||||
},
|
||||
{
|
||||
"group": "Projects API",
|
||||
"pages": ["management/projects/runs"]
|
||||
"pages": [
|
||||
"management/projects/runs"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -287,11 +316,17 @@
|
||||
},
|
||||
{
|
||||
"group": "Help",
|
||||
"pages": ["community", "help-slack", "help-email"]
|
||||
"pages": [
|
||||
"community",
|
||||
"help-slack",
|
||||
"help-email"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "",
|
||||
"pages": ["guides/introduction"]
|
||||
"pages": [
|
||||
"guides/introduction"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Frameworks",
|
||||
@@ -357,11 +392,15 @@
|
||||
},
|
||||
{
|
||||
"group": "Dashboard",
|
||||
"pages": ["guides/dashboard/creating-a-project"]
|
||||
"pages": [
|
||||
"guides/dashboard/creating-a-project"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Migrations",
|
||||
"pages": ["guides/use-cases/upgrading-from-v2"]
|
||||
"pages": [
|
||||
"guides/use-cases/upgrading-from-v2"
|
||||
]
|
||||
}
|
||||
],
|
||||
"footerSocials": {
|
||||
@@ -369,4 +408,4 @@
|
||||
"github": "https://github.com/triggerdotdev",
|
||||
"linkedin": "https://www.linkedin.com/company/triggerdotdev"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,6 +258,10 @@ We suggest combining run metadata with the realtime API and our [React hooks](/f
|
||||
|
||||
We have a full demo app repo available [here](https://github.com/triggerdotdev/nextjs-realtime-simple-demo)
|
||||
|
||||
## Realtime streams
|
||||
|
||||
See our dedicated [Realtime streams](/realtime/streams) documentation for more information on how to use the Realtime streams API.
|
||||
|
||||
## Limits
|
||||
|
||||
The Realtime API in the Trigger.dev Cloud limits the number of concurrent subscriptions, depending on your plan. If you exceed the limit, you will receive an error when trying to subscribe to a run. For more information, see our [pricing page](https://trigger.dev/pricing).
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: Realtime React hooks
|
||||
sidebarTitle: React hooks
|
||||
description: Subscribes to all changes to a run in a React component.
|
||||
---
|
||||
|
||||
See our [React hooks](/frontend/react-hooks) for more information about how to use the Realtime API from your frontend application.
|
||||
@@ -0,0 +1,452 @@
|
||||
---
|
||||
title: Realtime streams
|
||||
sidebarTitle: Streams
|
||||
description: Stream data in realtime from inside your tasks
|
||||
---
|
||||
|
||||
The world is going realtime, and so should your tasks. With the Streams API, you can stream data from your tasks to the outside world in realtime. This is useful for a variety of use cases, including AI.
|
||||
|
||||
## How it works
|
||||
|
||||
The Streams API is a simple API that allows you to send data from your tasks to the outside world in realtime using the [metadata](/runs/metadata) system. You can send any kind of data that is streamed in realtime, but the most common use case is to send streaming output from streaming LLM providers, like OpenAI.
|
||||
|
||||
## Usage
|
||||
|
||||
To use the Streams API, you need to register a stream with a specific key using `metadata.stream`. The following example uses the OpenAI SDK with `stream: true` to stream the output of the LLM model in realtime:
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
import OpenAI from "openai";
|
||||
|
||||
const openai = new OpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
});
|
||||
|
||||
export type STREAMS = {
|
||||
openai: OpenAI.ChatCompletionChunk; // The type of the chunk is determined by the provider
|
||||
};
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { prompt: string }) => {
|
||||
const completion = await openai.chat.completions.create({
|
||||
messages: [{ role: "user", content: payload.prompt }],
|
||||
model: "gpt-3.5-turbo",
|
||||
stream: true,
|
||||
});
|
||||
|
||||
// Register the stream with the key "openai"
|
||||
// This will "tee" the stream and send it to the metadata system
|
||||
const stream = await metadata.stream("openai", completion);
|
||||
|
||||
let text = "";
|
||||
|
||||
// You can read the returned stream as an async iterator
|
||||
for await (const chunk of stream) {
|
||||
logger.log("Received chunk", { chunk });
|
||||
|
||||
// The type of the chunk is determined by the provider
|
||||
text += chunk.choices.map((choice) => choice.delta?.content).join("");
|
||||
}
|
||||
|
||||
return { text };
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
You can then subscribe to the stream using the `runs.subscribeToRun` method:
|
||||
|
||||
<Note>
|
||||
`runs.subscribeToRun` should be used from your backend or another task. To subscribe to a run from
|
||||
your frontend, you can use our [React hooks](/frontend/react-hooks).
|
||||
</Note>
|
||||
|
||||
```ts
|
||||
import { runs } from "@trigger.dev/sdk/v3";
|
||||
import type { myTask, STREAMS } from "./trigger/my-task";
|
||||
|
||||
// Somewhere in your backend
|
||||
async function subscribeToStream(runId: string) {
|
||||
// Use a for-await loop to subscribe to the stream
|
||||
for await (const part of runs.subscribeToRun<typeof myTask>(runId).withStreams<STREAMS>()) {
|
||||
switch (part.type) {
|
||||
case "run": {
|
||||
console.log("Received run", part.run);
|
||||
break;
|
||||
}
|
||||
case "openai": {
|
||||
// part.chunk is of type OpenAI.ChatCompletionChunk
|
||||
console.log("Received OpenAI chunk", part.chunk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can register and subscribe to multiple streams in the same task. Let's add a stream from the response body of a fetch request:
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
import OpenAI from "openai";
|
||||
|
||||
const openai = new OpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
});
|
||||
|
||||
export type STREAMS = {
|
||||
openai: OpenAI.ChatCompletionChunk; // The type of the chunk is determined by the provider
|
||||
fetch: string; // The response body will be an array of strings
|
||||
};
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { prompt: string }) => {
|
||||
const completion = await openai.chat.completions.create({
|
||||
messages: [{ role: "user", content: payload.prompt }],
|
||||
model: "gpt-3.5-turbo",
|
||||
stream: true,
|
||||
});
|
||||
|
||||
// Register the stream with the key "openai"
|
||||
await metadata.stream("openai", completion);
|
||||
|
||||
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
|
||||
|
||||
if (!response.body) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register the stream with the key "fetch"
|
||||
// Pipe the response.body through a TextDecoderStream to convert it to a string
|
||||
await metadata.stream("fetch", response.body.pipeThrough(new TextDecoderStream()));
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
You may notice above that we aren't consuming either of the streams in the task. In the
|
||||
background, we'll wait until all streams are consumed before the task is considered complete (with
|
||||
a max timeout of 60 seconds). If you have a longer running stream, make sure to consume it in the
|
||||
task.
|
||||
</Note>
|
||||
|
||||
And then subscribing to the streams:
|
||||
|
||||
```ts
|
||||
import { runs } from "@trigger.dev/sdk/v3";
|
||||
import type { myTask, STREAMS } from "./trigger/my-task";
|
||||
|
||||
// Somewhere in your backend
|
||||
async function subscribeToStream(runId: string) {
|
||||
// Use a for-await loop to subscribe to the stream
|
||||
for await (const part of runs.subscribeToRun<typeof myTask>(runId).withStreams<STREAMS>()) {
|
||||
switch (part.type) {
|
||||
case "run": {
|
||||
console.log("Received run", part.run);
|
||||
break;
|
||||
}
|
||||
case "openai": {
|
||||
// part.chunk is of type OpenAI.ChatCompletionChunk
|
||||
console.log("Received OpenAI chunk", part.chunk);
|
||||
break;
|
||||
}
|
||||
case "fetch": {
|
||||
// part.chunk is a string
|
||||
console.log("Received fetch chunk", part.chunk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## React hooks
|
||||
|
||||
If you're building a frontend application, you can use our React hooks to subscribe to streams. Here's an example of how you can use the `useRealtimeRunWithStreams` hook to subscribe to a stream:
|
||||
|
||||
```tsx
|
||||
import { useRealtimeRunWithStreams } from "@trigger.dev/sdk/v3";
|
||||
import type { myTask, STREAMS } from "./trigger/my-task";
|
||||
|
||||
// Somewhere in your React component
|
||||
function MyComponent({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) {
|
||||
const { run, streams } = useRealtimeRunWithStreams<typeof myTask, STREAMS>(runId, {
|
||||
accessToken: publicAccessToken,
|
||||
});
|
||||
|
||||
if (!run) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Run ID: {run.id}</h1>
|
||||
<h2>Streams:</h2>
|
||||
<ul>
|
||||
{Object.entries(streams).map(([key, value]) => (
|
||||
<li key={key}>
|
||||
<strong>{key}</strong>: {JSON.stringify(value)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Read more about using the React hooks in the [React hooks](/frontend/react-hooks) documentation.
|
||||
|
||||
## Usage with the `ai` SDK
|
||||
|
||||
The [ai SDK](https://sdk.vercel.ai/docs/introduction) provides a higher-level API for working with AI models. You can use the `ai` SDK with the Streams API by using the `streamText` method:
|
||||
|
||||
```ts
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { logger, metadata, runs, schemaTask } from "@trigger.dev/sdk/v3";
|
||||
import { streamText } from "ai";
|
||||
import { z } from "zod";
|
||||
|
||||
export type STREAMS = {
|
||||
openai: string;
|
||||
};
|
||||
|
||||
export const aiStreaming = schemaTask({
|
||||
id: "ai-streaming",
|
||||
description: "Stream data from the AI sdk",
|
||||
schema: z.object({
|
||||
model: z.string().default("o1-preview"),
|
||||
prompt: z.string().default("Hello, how are you?"),
|
||||
}),
|
||||
run: async ({ model, prompt }) => {
|
||||
logger.info("Running OpenAI model", { model, prompt });
|
||||
|
||||
const result = streamText({
|
||||
model: openai(model),
|
||||
prompt,
|
||||
});
|
||||
|
||||
// pass the textStream to the metadata system
|
||||
const stream = await metadata.stream("openai", result.textStream);
|
||||
|
||||
let text = "";
|
||||
|
||||
for await (const chunk of stream) {
|
||||
logger.log("Received chunk", { chunk });
|
||||
|
||||
text += chunk; // chunk is a string
|
||||
}
|
||||
|
||||
return { text };
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
And then render the stream in your frontend:
|
||||
|
||||
```tsx
|
||||
import { useRealtimeRunWithStreams } from "@trigger.dev/sdk/v3";
|
||||
import type { aiStreaming, STREAMS } from "./trigger/ai-streaming";
|
||||
|
||||
function MyComponent({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) {
|
||||
const { streams } = useRealtimeRunWithStreams<typeof aiStreaming, STREAMS>(runId, {
|
||||
accessToken: publicAccessToken,
|
||||
});
|
||||
|
||||
if (!streams.openai) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
const text = streams.openai.join(""); // `streams.openai` is an array of strings
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>OpenAI response:</h2>
|
||||
<p>{text}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Using tools and `fullStream`
|
||||
|
||||
When calling `streamText`, you can provide a `tools` object that allows the LLM to use additional tools. You can then access the tool call and results using the `fullStream` method:
|
||||
|
||||
```ts
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { logger, metadata, runs, schemaTask } from "@trigger.dev/sdk/v3";
|
||||
import { streamText, tool, type TextStreamPart } from "ai";
|
||||
import { z } from "zod";
|
||||
|
||||
const tools = {
|
||||
getWeather: tool({
|
||||
description: "Get the weather in a location",
|
||||
parameters: z.object({
|
||||
location: z.string().describe("The location to get the weather for"),
|
||||
}),
|
||||
execute: async ({ location }) => ({
|
||||
location,
|
||||
temperature: 72 + Math.floor(Math.random() * 21) - 10,
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
export type STREAMS = {
|
||||
// Give the stream a type of TextStreamPart along with the tools
|
||||
openai: TextStreamPart<{ getWeather: typeof tools.getWeather }>;
|
||||
};
|
||||
|
||||
export const aiStreamingWithTools = schemaTask({
|
||||
id: "ai-streaming-with-tools",
|
||||
description: "Stream data from the AI SDK and use tools",
|
||||
schema: z.object({
|
||||
model: z.string().default("gpt-4o-mini"),
|
||||
prompt: z
|
||||
.string()
|
||||
.default(
|
||||
"Based on the temperature, will I need to wear extra clothes today in San Fransico? Please be detailed."
|
||||
),
|
||||
}),
|
||||
run: async ({ model, prompt }) => {
|
||||
logger.info("Running OpenAI model", { model, prompt });
|
||||
|
||||
const result = streamText({
|
||||
model: openai(model),
|
||||
prompt,
|
||||
tools, // Pass in the tools to use
|
||||
maxSteps: 5, // Allow streamText to repeatedly call the model
|
||||
});
|
||||
|
||||
// pass the fullStream to the metadata system
|
||||
const stream = await metadata.stream("openai", result.fullStream);
|
||||
|
||||
let text = "";
|
||||
|
||||
for await (const chunk of stream) {
|
||||
logger.log("Received chunk", { chunk });
|
||||
|
||||
// chunk is a TextStreamPart
|
||||
if (chunk.type === "text-delta") {
|
||||
text += chunk.textDelta;
|
||||
}
|
||||
}
|
||||
|
||||
return { text };
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Now you can get access to the tool call and results in your frontend:
|
||||
|
||||
```tsx
|
||||
import { useRealtimeRunWithStreams } from "@trigger.dev/sdk/v3";
|
||||
import { useRealtimeRunWithStreams } from "@trigger.dev/sdk/v3";
|
||||
import type { aiStreamingWithTools, STREAMS } from "./trigger/ai-streaming";
|
||||
|
||||
function MyComponent({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) {
|
||||
const { streams } = useRealtimeRunWithStreams<typeof aiStreamingWithTools, STREAMS>(runId, {
|
||||
accessToken: publicAccessToken,
|
||||
});
|
||||
|
||||
if (!streams.openai) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
// streams.openai is an array of TextStreamPart
|
||||
const toolCall = streams.openai.find(
|
||||
(stream) => stream.type === "tool-call" && stream.toolName === "getWeather"
|
||||
);
|
||||
const toolResult = streams.openai.find((stream) => stream.type === "tool-result");
|
||||
const textDeltas = streams.openai.filter((stream) => stream.type === "text-delta");
|
||||
|
||||
const text = textDeltas.map((delta) => delta.textDelta).join("");
|
||||
const weatherLocation = toolCall ? toolCall.args.location : undefined;
|
||||
const weather = toolResult ? toolResult.result.temperature : undefined;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>OpenAI response:</h2>
|
||||
<p>{text}</p>
|
||||
<h2>Weather:</h2>
|
||||
<p>
|
||||
{weatherLocation
|
||||
? `The weather in ${weatherLocation} is ${weather} degrees.`
|
||||
: "No weather data"}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Using `toolTask`
|
||||
|
||||
As you can see above, we defined a tool which will be used in the `aiStreamingWithTools` task. You can also define a Trigger.dev task that can be used as a tool, and will automatically be invoked with `triggerAndWait` when the tool is called. This is done using the `toolTask` function:
|
||||
|
||||
```ts
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { logger, metadata, runs, schemaTask, toolTask } from "@trigger.dev/sdk/v3";
|
||||
import { streamText, tool, type TextStreamPart } from "ai";
|
||||
import { z } from "zod";
|
||||
|
||||
export const getWeather = toolTask({
|
||||
id: "get-weather",
|
||||
description: "Get the weather for a location",
|
||||
// Define the parameters for the tool, which becomes the task payload
|
||||
parameters: z.object({
|
||||
location: z.string(),
|
||||
}),
|
||||
run: async ({ location }) => {
|
||||
// return mock data
|
||||
return {
|
||||
location,
|
||||
temperature: 72 + Math.floor(Math.random() * 21) - 10,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export type STREAMS = {
|
||||
// Give the stream a type of TextStreamPart along with the tools
|
||||
openai: TextStreamPart<{ getWeather: typeof getWeather.tool }>;
|
||||
};
|
||||
|
||||
export const aiStreamingWithTools = schemaTask({
|
||||
id: "ai-streaming-with-tools",
|
||||
description: "Stream data from the AI SDK and use tools",
|
||||
schema: z.object({
|
||||
model: z.string().default("gpt-4o-mini"),
|
||||
prompt: z
|
||||
.string()
|
||||
.default(
|
||||
"Based on the temperature, will I need to wear extra clothes today in San Fransico? Please be detailed."
|
||||
),
|
||||
}),
|
||||
run: async ({ model, prompt }) => {
|
||||
logger.info("Running OpenAI model", { model, prompt });
|
||||
|
||||
const result = streamText({
|
||||
model: openai(model),
|
||||
prompt,
|
||||
tools: {
|
||||
getWeather: getWeather.tool, // pass weatherTask.tool as a tool
|
||||
},
|
||||
maxSteps: 5, // Allow streamText to repeatedly call the model
|
||||
});
|
||||
|
||||
// pass the fullStream to the metadata system
|
||||
const stream = await metadata.stream("openai", result.fullStream);
|
||||
|
||||
let text = "";
|
||||
|
||||
for await (const chunk of stream) {
|
||||
logger.log("Received chunk", { chunk });
|
||||
|
||||
// chunk is a TextStreamPart
|
||||
if (chunk.type === "text-delta") {
|
||||
text += chunk.textDelta;
|
||||
}
|
||||
}
|
||||
|
||||
return { text };
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
title: useRealtimeRun
|
||||
sidebarTitle: useRealtimeRun
|
||||
description: Subscribes to all changes to a run in a React component.
|
||||
---
|
||||
|
||||
import RunObject from "/snippets/realtime/run-object.mdx";
|
||||
|
||||
<RequestExample>
|
||||
|
||||
```ts Example
|
||||
import { useRealtimeRun } from "@trigger.dev/react-hooks";
|
||||
import type { myTask } from "./trigger/tasks";
|
||||
|
||||
export function MyComponent({ runId }: { runId: string }) {
|
||||
const { run, error } = useRealtimeRun<typeof myTask>(runId);
|
||||
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
return <div>Run: {run.id}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
</RequestExample>
|
||||
|
||||
This react hook subscribes to all changes to a run. See the [React hooks doc](/frontend/react-hooks) for more information on how to use this hook.
|
||||
|
||||
### Response
|
||||
|
||||
The react hook returns an object with the following properties:
|
||||
|
||||
<ParamField path="run" type="object" required>
|
||||
The run object. See the [Run object doc](realtime/overview#run-object) for more information.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="error" type="Error">
|
||||
An error object if an error occurred while subscribing to a run.
|
||||
</ParamField>
|
||||
@@ -1,45 +0,0 @@
|
||||
---
|
||||
title: useRealtimeRunsWithTag
|
||||
sidebarTitle: useRealtimeRunsWithTag
|
||||
description: Subscribes to all changes to runs with a specific tag in a React component.
|
||||
---
|
||||
|
||||
import RunObject from "/snippets/realtime/run-object.mdx";
|
||||
|
||||
<RequestExample>
|
||||
|
||||
```ts Example
|
||||
import { useRealtimeRunsWithTag } from "@trigger.dev/react-hooks";
|
||||
import type { myTask, myOtherTask } from "./trigger/tasks";
|
||||
|
||||
export function MyComponent({ tag }: { tag: string }) {
|
||||
const { runs, error } = useRealtimeRunsWithTag<typeof myTask | typeof myOtherTask>(tag);
|
||||
|
||||
if (error) return <div>Error: {error.message}</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{runs.map((run) => (
|
||||
<div key={run.id}>Run: {run.id}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
</RequestExample>
|
||||
|
||||
This react hook subscribes to all changes to runs with a specific tag. See the [React hooks doc](/frontend/react-hooks) for more information on how to use this hook.
|
||||
|
||||
### Response
|
||||
|
||||
The react hook returns an object with the following properties:
|
||||
|
||||
<ParamField path="runs" type="object[]" required>
|
||||
An array of run objects. See the [Run object doc](/realtime/overview#run-object) for more
|
||||
information.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="error" type="Error">
|
||||
An error object if an error occurred while subscribing.
|
||||
</ParamField>
|
||||
+281
-63
@@ -17,42 +17,7 @@ const handle = await myTask.trigger(
|
||||
);
|
||||
```
|
||||
|
||||
Then inside your run function, you can access the metadata like this:
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { message: string }) => {
|
||||
const user = metadata.get("user");
|
||||
console.log(user.name); // "Eric"
|
||||
console.log(user.id); // "user_1234"
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
You can also update the metadata during the run:
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { message: string }) => {
|
||||
// Do some work
|
||||
await metadata.set("progress", 0.1);
|
||||
|
||||
// Do some more work
|
||||
await metadata.set("progress", 0.5);
|
||||
|
||||
// Do even more work
|
||||
await metadata.set("progress", 1.0);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
You can get the current metadata at any time by calling `metadata.get()` or `metadata.current()` (again, only inside a run):
|
||||
You can get the current metadata at any time by calling `metadata.get()` or `metadata.current()` (only inside a run):
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
@@ -71,26 +36,6 @@ export const myTask = task({
|
||||
});
|
||||
```
|
||||
|
||||
You can update metadata inside a run using `metadata.set()`, `metadata.save()`, or `metadata.del()`:
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { message: string }) => {
|
||||
// Set a key
|
||||
await metadata.set("progress", 0.5);
|
||||
|
||||
// Update the entire metadata object
|
||||
await metadata.save({ progress: 0.6 });
|
||||
|
||||
// Delete a key
|
||||
await metadata.del("progress");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Any of these methods can be called anywhere "inside" the run function, or a function called from the run function:
|
||||
|
||||
```ts
|
||||
@@ -99,12 +44,12 @@ import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { message: string }) => {
|
||||
await doSomeWork();
|
||||
doSomeWork();
|
||||
},
|
||||
});
|
||||
|
||||
async function doSomeWork() {
|
||||
await metadata.set("progress", 0.5);
|
||||
metadata.set("progress", 0.5);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -114,8 +59,8 @@ If you call any of the metadata methods outside of the run function, they will h
|
||||
import { metadata } from "@trigger.dev/sdk/v3";
|
||||
|
||||
// Somewhere outside of the run function
|
||||
async function doSomeWork() {
|
||||
await metadata.set("progress", 0.5); // This will do nothing
|
||||
function doSomeWork() {
|
||||
metadata.set("progress", 0.5); // This will do nothing
|
||||
}
|
||||
```
|
||||
|
||||
@@ -139,10 +84,10 @@ export const myTask = task({
|
||||
// Your run function work here
|
||||
},
|
||||
onStart: async () => {
|
||||
await metadata.set("progress", 0.5);
|
||||
metadata.set("progress", 0.5);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
await metadata.set("progress", 1.0);
|
||||
metadata.set("progress", 1.0);
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -153,13 +98,286 @@ import { defineConfig, metadata } from "@trigger.dev/sdk/v3";
|
||||
export default defineConfig({
|
||||
project: "proj_1234",
|
||||
onStart: async () => {
|
||||
await metadata.set("progress", 0.5);
|
||||
metadata.set("progress", 0.5);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Updates API
|
||||
|
||||
One of the more powerful features of metadata is the ability to update it as the run progresses. This is useful for tracking the progress of a run, storing intermediate results, or storing any other information that changes over time. (Combining metadata with [Realtime](/realtime) can give you a live view of the progress of your runs.)
|
||||
|
||||
All metadata update methods (accept for `flush` and `stream`) are synchronous and will not block the run function. We periodically flush metadata to the database in the background, so you can safely update the metadata inside a run as often as you need to, without worrying about impacting the run's performance.
|
||||
|
||||
### set
|
||||
|
||||
Set the value of a key in the metadata object:
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { message: string }) => {
|
||||
// Do some work
|
||||
metadata.set("progress", 0.1);
|
||||
|
||||
// Do some more work
|
||||
metadata.set("progress", 0.5);
|
||||
|
||||
// Do even more work
|
||||
metadata.set("progress", 1.0);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### del
|
||||
|
||||
Delete a key from the metadata object:
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { message: string }) => {
|
||||
// Do some work
|
||||
metadata.set("progress", 0.1);
|
||||
|
||||
// Do some more work
|
||||
metadata.set("progress", 0.5);
|
||||
|
||||
// Remove the progress key
|
||||
metadata.del("progress");
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### replace
|
||||
|
||||
Replace the entire metadata object with a new object:
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { message: string }) => {
|
||||
// Do some work
|
||||
metadata.set("progress", 0.1);
|
||||
|
||||
// Replace the metadata object
|
||||
metadata.replace({ user: { name: "Eric", id: "user_1234" } });
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### append
|
||||
|
||||
Append a value to an array in the metadata object:
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { message: string }) => {
|
||||
// Do some work
|
||||
metadata.set("progress", 0.1);
|
||||
|
||||
// Append a value to an array
|
||||
metadata.append("logs", "Step 1 complete");
|
||||
|
||||
console.log(metadata.get("logs")); // ["Step 1 complete"]
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### remove
|
||||
|
||||
Remove a value from an array in the metadata object:
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { message: string }) => {
|
||||
// Do some work
|
||||
metadata.set("progress", 0.1);
|
||||
|
||||
// Append a value to an array
|
||||
metadata.append("logs", "Step 1 complete");
|
||||
|
||||
// Remove a value from the array
|
||||
metadata.remove("logs", "Step 1 complete");
|
||||
|
||||
console.log(metadata.get("logs")); // []
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### increment
|
||||
|
||||
Increment a numeric value in the metadata object:
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { message: string }) => {
|
||||
// Do some work
|
||||
metadata.set("progress", 0.1);
|
||||
|
||||
// Increment a value
|
||||
metadata.increment("progress", 0.4);
|
||||
|
||||
console.log(metadata.get("progress")); // 0.5
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### decrement
|
||||
|
||||
Decrement a numeric value in the metadata object:
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { message: string }) => {
|
||||
// Do some work
|
||||
metadata.set("progress", 0.5);
|
||||
|
||||
// Decrement a value
|
||||
metadata.decrement("progress", 0.4);
|
||||
|
||||
console.log(metadata.get("progress")); // 0.1
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### stream
|
||||
|
||||
Capture a stream of values and make the stream available when using Realtime. See our [Realtime streams](/realtime/streams) documentation for more information.
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { message: string }) => {
|
||||
const readableStream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue("Step 1 complete");
|
||||
controller.enqueue("Step 2 complete");
|
||||
controller.enqueue("Step 3 complete");
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
|
||||
// IMPORTANT: you must await the stream method
|
||||
const stream = await metadata.stream("logs", readableStream);
|
||||
|
||||
// You can read from the returned stream locally
|
||||
for await (const value of stream) {
|
||||
console.log(value);
|
||||
}
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
`metadata.stream` accepts any `AsyncIterable` or `ReadableStream` object. The stream will be captured and made available in the Realtime API. So for example, you could pass the body of a fetch response to `metadata.stream` to capture the response body and make it available in Realtime:
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { url: string }) => {
|
||||
logger.info("Streaming response", { url });
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.body) {
|
||||
throw new Error("Response body is not readable");
|
||||
}
|
||||
|
||||
const stream = await metadata.stream(
|
||||
"fetch",
|
||||
response.body.pipeThrough(new TextDecoderStream())
|
||||
);
|
||||
|
||||
let text = "";
|
||||
|
||||
for await (const chunk of stream) {
|
||||
logger.log("Received chunk", { chunk });
|
||||
|
||||
text += chunk;
|
||||
}
|
||||
|
||||
return { text };
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Or the results of a streaming call to the OpenAI SDK:
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
import OpenAI from "openai";
|
||||
|
||||
const openai = new OpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
});
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { prompt: string }) => {
|
||||
const completion = await openai.chat.completions.create({
|
||||
messages: [{ role: "user", content: payload.prompt }],
|
||||
model: "gpt-3.5-turbo",
|
||||
stream: true,
|
||||
});
|
||||
|
||||
const stream = await metadata.stream("openai", completion);
|
||||
|
||||
let text = "";
|
||||
|
||||
for await (const chunk of stream) {
|
||||
logger.log("Received chunk", { chunk });
|
||||
|
||||
text += chunk.choices.map((choice) => choice.delta?.content).join("");
|
||||
}
|
||||
|
||||
return { text };
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### flush
|
||||
|
||||
Flush the metadata to the database. The SDK will automatically flush the metadata periodically, so you don't need to call this method unless you need to ensure that the metadata is persisted immediately.
|
||||
|
||||
```ts
|
||||
import { task, metadata } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export const myTask = task({
|
||||
id: "my-task",
|
||||
run: async (payload: { message: string }) => {
|
||||
// Do some work
|
||||
metadata.set("progress", 0.1);
|
||||
|
||||
// Flush the metadata to the database
|
||||
await metadata.flush();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Metadata propagation
|
||||
|
||||
Metadata is NOT propagated to child tasks. If you want to pass metadata to a child task, you must do so explicitly:
|
||||
|
||||
@@ -27,7 +27,7 @@ export function useRun<TTask extends AnyTask>(
|
||||
isValidating: boolean;
|
||||
isError: boolean;
|
||||
} {
|
||||
const apiClient = useApiClient();
|
||||
const apiClient = useApiClient(options);
|
||||
const {
|
||||
data: run,
|
||||
error,
|
||||
|
||||
@@ -1,11 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import { ApiRequestOptions } from "@trigger.dev/core/v3";
|
||||
|
||||
// eslint-disable-next-line import/export
|
||||
export * from "swr";
|
||||
// eslint-disable-next-line import/export
|
||||
export { default as useSWR, SWRConfig } from "swr";
|
||||
|
||||
export type CommonTriggerHookOptions = {
|
||||
/**
|
||||
* Poll for updates at the specified interval (in milliseconds). Polling is not recommended for most use-cases. Use the Realtime hooks instead.
|
||||
*/
|
||||
refreshInterval?: number;
|
||||
/** Revalidate the data when the browser regains a network connection. */
|
||||
revalidateOnReconnect?: boolean;
|
||||
/** Revalidate the data when the window regains focus. */
|
||||
revalidateOnFocus?: boolean;
|
||||
|
||||
/** Optional access token for authentication */
|
||||
accessToken?: string;
|
||||
/** Optional base URL for the API endpoints */
|
||||
baseURL?: string;
|
||||
/** Optional additional request configuration */
|
||||
requestOptions?: ApiRequestOptions;
|
||||
};
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
"@types/slug": "^5.0.3",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@types/ws": "^8.5.3",
|
||||
"ai": "^3.4.33",
|
||||
"ai": "^4.0.1",
|
||||
"encoding": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"tshy": "^3.0.2",
|
||||
@@ -109,4 +109,4 @@
|
||||
"main": "./dist/commonjs/index.js",
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"module": "./dist/esm/index.js"
|
||||
}
|
||||
}
|
||||
Generated
+76
-4
@@ -1531,8 +1531,8 @@ importers:
|
||||
specifier: ^8.5.3
|
||||
version: 8.5.4
|
||||
ai:
|
||||
specifier: ^3.4.33
|
||||
version: 3.4.33(react@18.3.1)(svelte@4.2.19)(vue@3.4.38)(zod@3.23.8)
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.2(react@18.3.1)(zod@3.23.8)
|
||||
encoding:
|
||||
specifier: ^0.1.13
|
||||
version: 0.1.13
|
||||
@@ -1961,6 +1961,22 @@ packages:
|
||||
zod: 3.23.8
|
||||
dev: false
|
||||
|
||||
/@ai-sdk/provider-utils@2.0.1(zod@3.23.8):
|
||||
resolution: {integrity: sha512-TNg7rPhRtETB2Z9F0JpOvpGii9Fs8EWM8nYy1jEkvSXkrPJ6b/9zVnDdaJsmLFDyrMbOsPJlkblYtmYEQou36w==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.0.0
|
||||
peerDependenciesMeta:
|
||||
zod:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 1.0.0
|
||||
eventsource-parser: 3.0.0
|
||||
nanoid: 3.3.7
|
||||
secure-json-parse: 2.7.0
|
||||
zod: 3.23.8
|
||||
dev: true
|
||||
|
||||
/@ai-sdk/provider@0.0.22:
|
||||
resolution: {integrity: sha512-smZ1/2jL/JSKnbhC6ama/PxI2D/psj+YAe0c0qpd5ComQCNFltg72VFf0rpUSFMmFuj1pCCNoBOCrvyl8HTZHQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -1980,7 +1996,6 @@ packages:
|
||||
engines: {node: '>=18'}
|
||||
dependencies:
|
||||
json-schema: 0.4.0
|
||||
dev: false
|
||||
|
||||
/@ai-sdk/react@0.0.53(react@19.0.0-rc.0)(zod@3.23.8):
|
||||
resolution: {integrity: sha512-sIsmTFoR/QHvUUkltmHwP4bPjwy2vko6j/Nj8ayxLhEHs04Ug+dwXQyfA7MwgimEE3BcDQpWL8ikVj0m3ZILWQ==}
|
||||
@@ -2041,6 +2056,26 @@ packages:
|
||||
zod: 3.23.8
|
||||
dev: false
|
||||
|
||||
/@ai-sdk/react@1.0.1(react@18.3.1)(zod@3.23.8):
|
||||
resolution: {integrity: sha512-vonKc5bcUQDkzWhqP/bBagT1Cam81gHuCAWPy52PHv1372OUrLmO2s9ZJPvfORE9ns8H84zT78MWaCf5pb/yiQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
react: ^18 || ^19 || ^19.0.0-rc
|
||||
zod: ^3.0.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
optional: true
|
||||
zod:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@ai-sdk/provider-utils': 2.0.1(zod@3.23.8)
|
||||
'@ai-sdk/ui-utils': 1.0.1(zod@3.23.8)
|
||||
react: 18.3.1
|
||||
swr: 2.2.5(react@18.3.1)
|
||||
throttleit: 2.1.0
|
||||
zod: 3.23.8
|
||||
dev: true
|
||||
|
||||
/@ai-sdk/solid@0.0.43(zod@3.23.8):
|
||||
resolution: {integrity: sha512-7PlPLaeMAu97oOY2gjywvKZMYHF+GDfUxYNcuJ4AZ3/MRBatzs/U2r4ClT1iH8uMOcMg02RX6UKzP5SgnUBjVw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -2154,6 +2189,21 @@ packages:
|
||||
zod-to-json-schema: 3.23.5(zod@3.23.8)
|
||||
dev: false
|
||||
|
||||
/@ai-sdk/ui-utils@1.0.1(zod@3.23.8):
|
||||
resolution: {integrity: sha512-zK7yNixtCve8ng/8+9jUFyLvI+1dPzSHuyIM56p3EeXwJECRt6e8xyk9AZJNskhmDN6jP+qucP2rWlkX3ZQ2gA==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
zod: ^3.0.0
|
||||
peerDependenciesMeta:
|
||||
zod:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 1.0.0
|
||||
'@ai-sdk/provider-utils': 2.0.1(zod@3.23.8)
|
||||
zod: 3.23.8
|
||||
zod-to-json-schema: 3.23.5(zod@3.23.8)
|
||||
dev: true
|
||||
|
||||
/@ai-sdk/vue@0.0.45(vue@3.4.38)(zod@3.23.8):
|
||||
resolution: {integrity: sha512-bqeoWZqk88TQmfoPgnFUKkrvhOIcOcSH5LMPgzZ8XwDqz5tHHrMHzpPfHCj7XyYn4ROTFK/2kKdC/ta6Ko0fMw==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -16763,6 +16813,29 @@ packages:
|
||||
zod-to-json-schema: 3.23.5(zod@3.23.8)
|
||||
dev: false
|
||||
|
||||
/ai@4.0.2(react@18.3.1)(zod@3.23.8):
|
||||
resolution: {integrity: sha512-Dj17cVKCM+FgsJIAhYv7zT4YsrK3noFsLUxJOnTCVIHhmiNrl6/M6ebwQUOukLR/P2PbVzUQpHzdTvGck2b05Q==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
react: ^18 || ^19 || ^19.0.0-rc
|
||||
zod: ^3.0.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
optional: true
|
||||
zod:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 1.0.0
|
||||
'@ai-sdk/provider-utils': 2.0.1(zod@3.23.8)
|
||||
'@ai-sdk/react': 1.0.1(react@18.3.1)(zod@3.23.8)
|
||||
'@ai-sdk/ui-utils': 1.0.1(zod@3.23.8)
|
||||
'@opentelemetry/api': 1.9.0
|
||||
jsondiffpatch: 0.6.0
|
||||
react: 18.3.1
|
||||
zod: 3.23.8
|
||||
zod-to-json-schema: 3.23.5(zod@3.23.8)
|
||||
dev: true
|
||||
|
||||
/ajv-formats@2.1.1(ajv@8.12.0):
|
||||
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
|
||||
peerDependencies:
|
||||
@@ -20638,7 +20711,6 @@ packages:
|
||||
/eventsource-parser@3.0.0:
|
||||
resolution: {integrity: sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
dev: false
|
||||
|
||||
/evt@2.4.13:
|
||||
resolution: {integrity: sha512-haTVOsmjzk+28zpzvVwan9Zw2rLQF2izgi7BKjAPRzZAfcv+8scL0TpM8MzvGNKFYHiy+Bq3r6FYIIUPl9kt3A==}
|
||||
|
||||
@@ -3,6 +3,11 @@ import { logger, metadata, runs, schemaTask, task, toolTask, wait } from "@trigg
|
||||
import { streamText, type TextStreamPart } from "ai";
|
||||
import { setTimeout } from "node:timers/promises";
|
||||
import { z } from "zod";
|
||||
import OpenAI from "openai";
|
||||
|
||||
const openaiSDK = new OpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
});
|
||||
|
||||
export type STREAMS = { openai: TextStreamPart<{ getWeather: typeof weatherTask.tool }> };
|
||||
|
||||
@@ -90,7 +95,7 @@ export const openaiStreaming = schemaTask({
|
||||
run: async ({ model, prompt }) => {
|
||||
logger.info("Running OpenAI model", { model, prompt });
|
||||
|
||||
const result = await streamText({
|
||||
const result = streamText({
|
||||
model: openai(model),
|
||||
prompt,
|
||||
tools: {
|
||||
@@ -144,3 +149,65 @@ export const openaiO1Model = schemaTask({
|
||||
return { text };
|
||||
},
|
||||
});
|
||||
|
||||
export const fetchStream = schemaTask({
|
||||
id: "fetch-stream",
|
||||
description: "Stream data from fetch",
|
||||
schema: z.object({
|
||||
url: z.string().url(),
|
||||
}),
|
||||
run: async ({ url }) => {
|
||||
logger.info("Streaming response", { url });
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.body) {
|
||||
throw new Error("Response body is not readable");
|
||||
}
|
||||
|
||||
const stream = await metadata.stream(
|
||||
"fetch",
|
||||
response.body.pipeThrough(new TextDecoderStream())
|
||||
);
|
||||
|
||||
let text = "";
|
||||
|
||||
for await (const chunk of stream) {
|
||||
logger.log("Received chunk", { chunk });
|
||||
|
||||
text += chunk;
|
||||
}
|
||||
|
||||
return { text };
|
||||
},
|
||||
});
|
||||
|
||||
export const openaiSDKStreaming = schemaTask({
|
||||
id: "openai-sdk-streaming",
|
||||
description: "Stream data from the OpenAI SDK",
|
||||
schema: z.object({
|
||||
model: z.string().default("gpt-3.5-turbo"),
|
||||
prompt: z.string().default("Hello, how are you?"),
|
||||
}),
|
||||
run: async ({ model, prompt }) => {
|
||||
logger.info("Running OpenAI model", { model, prompt });
|
||||
|
||||
const completion = await openaiSDK.chat.completions.create({
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
model: "gpt-3.5-turbo",
|
||||
stream: true,
|
||||
});
|
||||
|
||||
const stream = await metadata.stream("openai", completion);
|
||||
|
||||
let text = "";
|
||||
|
||||
for await (const chunk of stream) {
|
||||
logger.log("Received chunk", { chunk });
|
||||
|
||||
text += chunk.choices.map((choice) => choice.delta?.content).join("");
|
||||
}
|
||||
|
||||
return { text };
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user