Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ concurrency:
cancel-in-progress: true

jobs:
docs-links:
name: Docs Links
docs:
name: Docs
runs-on: ubuntu-latest
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
Expand All @@ -41,6 +41,14 @@ jobs:
- name: Install Dependencies
run: pnpm install --frozen-lockfile

- name: Validate docs links
run: bun ./scripts/lint.ts
- name: Check docs links
run: pnpm run lint:links
working-directory: docs

- name: Check single quotes in code blocks
run: pnpm run lint:quotes
working-directory: docs

- name: Check TypeScript syntax in examples
run: pnpm run lint:ts-examples
working-directory: docs
Binary file added docs/app/og/[...slug]/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/app/og/[...slug]/geist-sans-regular.ttf
Binary file not shown.
Binary file added docs/app/og/[...slug]/geist-sans-semibold.ttf
Binary file not shown.
89 changes: 89 additions & 0 deletions docs/app/og/[...slug]/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { ImageResponse } from 'next/og';
import type { NextRequest } from 'next/server';
import { getPageImage, source } from '@/lib/geistdocs/source';

export const GET = async (
_request: NextRequest,
{ params }: RouteContext<'/og/[...slug]'>
) => {
const { slug } = await params;
const page = source.getPage(slug.slice(0, -1));

if (!page) {
return new Response('Not found', { status: 404 });
}

const { title, description } = page.data;

const regularFont = await readFile(
join(process.cwd(), 'app/og/[...slug]/geist-sans-regular.ttf')
);

const semiboldFont = await readFile(
join(process.cwd(), 'app/og/[...slug]/geist-sans-semibold.ttf')
);

const backgroundImage = await readFile(
join(process.cwd(), 'app/og/[...slug]/background.png')
);

const backgroundImageData = backgroundImage.buffer.slice(
backgroundImage.byteOffset,
backgroundImage.byteOffset + backgroundImage.byteLength
);

return new ImageResponse(
<div style={{ fontFamily: 'Geist' }} tw="flex h-full w-full">
{/** biome-ignore lint/performance/noImgElement: "Required for Satori" */}
<img
alt="Vercel OpenGraph Background"
height={628}
src={backgroundImageData as never}
width={1200}
/>
<div tw="flex flex-col absolute h-full w-[750px] left-[82px] top-[164px] pb-[86px] pt-[120px] max-w-2xl">
<div
style={{
textWrap: 'balance',
}}
tw="text-7xl font-medium text-white flex leading-[1.1] mb-4 text-[#ededed] tracking-[-0.04em]"
>
{title}
</div>
<div
style={{
color: '#8B8B8B',
lineHeight: '38px',
textWrap: 'balance',
}}
tw="text-[28px] text-[#A0A0A0]"
>
{description}
</div>
</div>
</div>,
{
width: 1200,
height: 628,
fonts: [
{
name: 'Geist',
data: regularFont,
weight: 400,
},
{
name: 'Geist',
data: semiboldFont,
weight: 500,
},
],
}
);
};

export const generateStaticParams = () =>
source.getPages().map((page) => ({
slug: getPageImage(page).segments,
}));
9 changes: 9 additions & 0 deletions docs/components/docs/getting-started/intro.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export function GettingStartedIntro({ framework }: { framework: string }) {
return (
<p>
This guide will walk through setting up your first workflow in a{' '}
{framework} app. Along the way, you'll learn more about the concepts that
are fundamental to using the development kit in your own projects.
</p>
);
}
5 changes: 4 additions & 1 deletion docs/components/geistdocs/docs-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import type { ComponentProps, CSSProperties } from 'react';
import { source } from '@/lib/geistdocs/source';
import { getPageImage, source } from '@/lib/geistdocs/source';
import { cn } from '@/lib/utils';

const containerStyle = {
Expand Down Expand Up @@ -75,6 +75,9 @@ export const generatePageMetadata = (slug: PageProps['slug']) => {
const metadata: Metadata = {
title: page.data.title,
description: page.data.description,
openGraph: {
images: getPageImage(page).url,
},
};

return metadata;
Expand Down
3 changes: 3 additions & 0 deletions docs/components/geistdocs/mdx-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
CodeBlockTabsList,
CodeBlockTabsTrigger,
} from './code-block-tabs';
import { GettingStartedIntro } from '../docs/getting-started/intro';
import { Mermaid } from './mermaid';
import { Video } from './video';

Expand Down Expand Up @@ -44,4 +45,6 @@ export const getMDXComponents = (
Mermaid,

Video,

GettingStartedIntro,
});
22 changes: 11 additions & 11 deletions docs/content/docs/ai/chat-session-modeling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Each user message triggers a new workflow run. The client or API route owns the

<Tab value="Workflow">

```typescript title="workflows/chat/workflow.ts" lineNumbers
```ts title="workflows/chat/workflow.ts" lineNumbers
import { DurableAgent } from "@workflow/ai/agent";
import { getWritable } from "workflow";
import { flightBookingTools, FLIGHT_ASSISTANT_PROMPT } from "./steps/tools";
Expand Down Expand Up @@ -44,7 +44,7 @@ export async function chatWorkflow(messages: ModelMessage[]) {

<Tab value="API Route">

```typescript title="app/api/chat/route.ts" lineNumbers
```ts title="app/api/chat/route.ts" lineNumbers
import type { UIMessage } from "ai";
import { createUIMessageStreamResponse, convertToModelMessages } from "ai";
import { start } from "workflow/api";
Expand All @@ -68,7 +68,7 @@ export async function POST(req: Request) {

Chat messages need to be stored somewhere—typically a database. In this example, we assume a route like `/chats/:id` passes the session ID, allowing us to fetch existing messages and persist new ones.

```typescript title="app/chats/[id]/page.tsx" lineNumbers
```ts title="app/chats/[id]/page.tsx" lineNumbers
"use client";

import { useChat } from "@ai-sdk/react";
Expand Down Expand Up @@ -131,7 +131,7 @@ A single workflow handles the entire conversation session across multiple turns,

<Tab value="Workflow">

```typescript title="workflows/chat/workflow.ts" lineNumbers
```ts title="workflows/chat/workflow.ts" lineNumbers
import { DurableAgent } from "@workflow/ai/agent";
import { getWritable } from "workflow";
import { chatMessageHook } from "./hooks/chat-message";
Expand Down Expand Up @@ -179,7 +179,7 @@ export async function chatWorkflow(threadId: string, initialMessage: string) {

Two endpoints: one to start the session, one to send follow-up messages.

```typescript title="app/api/chat/route.ts" lineNumbers
```ts title="app/api/chat/route.ts" lineNumbers
import { createUIMessageStreamResponse } from "ai";
import { start } from "workflow/api";
import { chatWorkflow } from "@/workflows/chat/workflow";
Expand All @@ -195,7 +195,7 @@ export async function POST(req: Request) {
}
```

```typescript title="app/api/chat/[id]/route.ts" lineNumbers
```ts title="app/api/chat/[id]/route.ts" lineNumbers
import { chatMessageHook } from "@/workflows/chat/hooks/chat-message";

export async function POST(
Expand All @@ -215,7 +215,7 @@ export async function POST(

<Tab value="Hook Definition">

```typescript title="workflows/chat/hooks/chat-message.ts" lineNumbers
```ts title="workflows/chat/hooks/chat-message.ts" lineNumbers
import { defineHook } from "workflow";
import { z } from "zod";

Expand All @@ -232,7 +232,7 @@ export const chatMessageHook = defineHook({

We can replace our `useChat` react hook with a custom hook that manages the chat session. This hook will handle switching between the API endpoints for creating a new thread and sending follow-up messages.

```typescript title="hooks/use-multi-turn-chat.ts" lineNumbers
```ts title="hooks/use-multi-turn-chat.ts" lineNumbers
"use client";

import type { UIMessage } from "ai";
Expand Down Expand Up @@ -324,7 +324,7 @@ The multi-turn pattern also easily enables multi-player chat sessions. New messa

Internal system events like scheduled tasks, background jobs, or database triggers can inject updates into an active conversation.

```typescript title="app/api/internal/flight-update/route.ts" lineNumbers
```ts title="app/api/internal/flight-update/route.ts" lineNumbers
import { chatMessageHook } from "@/workflows/chat/hooks/chat-message";

// Called by your flight status monitoring system
Expand All @@ -345,7 +345,7 @@ export async function POST(req: Request) {

External webhooks from third-party services (Stripe, Twilio, etc.) can notify the conversation of events.

```typescript title="app/api/webhooks/payment/route.ts" lineNumbers
```ts title="app/api/webhooks/payment/route.ts" lineNumbers
import { chatMessageHook } from "@/workflows/chat/hooks/chat-message";

export async function POST(req: Request) {
Expand All @@ -367,7 +367,7 @@ export async function POST(req: Request) {

Multiple human users can participate in the same conversation. Each user's client connects to the same workflow stream.

```typescript title="app/api/chat/[id]/route.ts" lineNumbers
```ts title="app/api/chat/[id]/route.ts" lineNumbers
import { chatMessageHook } from "@/workflows/chat/hooks/chat-message";
import { getUser } from "@/lib/auth";

Expand Down
6 changes: 3 additions & 3 deletions docs/content/docs/ai/defining-tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Just like in regular AI SDK tool definitions, tool in DurableAgent are called wi

When you tool needs access to the full message history, you can access it via the `messages` property of the tool call context:

```typescript title="tools.ts" lineNumbers
```ts title="tools.ts" lineNumbers
async function getWeather(
{ city }: { city: string },
{ messages, toolCallId }: { messages: LanguageModelV2Prompt, toolCallId: string }) { // [!code highlight]
Expand All @@ -27,7 +27,7 @@ As discussed in [Streaming Updates from Tools](/docs/ai/streaming-updates-from-t

This can be made generic, by creating a helper step function to write arbitrary data to the stream:

```typescript title="tools.ts" lineNumbers
```ts title="tools.ts" lineNumbers
import { getWritable } from "workflow";

async function writeToStream(data: any) {
Expand All @@ -54,7 +54,7 @@ Tools can be implemented either at the step level or the workflow level, with di

Tools can also combine both by starting out on the workflow level, and calling into steps for I/O operations, like so:

```typescript title="tools.ts" lineNumbers
```ts title="tools.ts" lineNumbers
// Step: handles I/O with retries
async function performFetch(url: string) {
"use step";
Expand Down
12 changes: 6 additions & 6 deletions docs/content/docs/ai/human-in-the-loop.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Add a tool that allows the agent to deliberately pause execution until a human a

Create a typed hook with a Zod schema for validation:

```typescript title="workflow/steps/tools.ts" lineNumbers
```ts title="workflow/steps/tools.ts" lineNumbers
import { defineHook } from "workflow";
import { z } from "zod";
// ... existing imports ...
Expand All @@ -71,7 +71,7 @@ export const bookingApprovalHook = defineHook({

Create a tool that creates a hook instance using the tool call ID as the token. The UI will use this ID to submit the approval.

```typescript title="workflows/chat/steps/tools.ts" lineNumbers
```ts title="workflows/chat/steps/tools.ts" lineNumbers
import { z } from "zod";

// ...
Expand Down Expand Up @@ -122,7 +122,7 @@ Note that the `defineHook().create()` function must be called from within a work

Create a new API endpoint that the UI will call to submit the approval decision:

```typescript title="app/api/approve-booking/route.ts" lineNumbers
```ts title="app/api/approve-booking/route.ts" lineNumbers
import { bookingApprovalHook } from "@/workflow/steps/tools";

export async function POST(request: Request) {
Expand All @@ -147,7 +147,7 @@ export async function POST(request: Request) {

Build a new component that reacts to the tool call data, and allows the user to approve or reject the booking:

```typescript title="components/booking-approval.tsx" lineNumbers
```ts title="components/booking-approval.tsx" lineNumbers
"use client";

import { useState } from "react";
Expand Down Expand Up @@ -236,7 +236,7 @@ export function BookingApproval({ toolCallId, input, output }: BookingApprovalPr

Use the component we just created to render the tool call and approval controls in your chat interface:

```typescript title="app/page.tsx" lineNumbers
```ts title="app/page.tsx" lineNumbers
// ... existing imports ...
import { BookingApproval } from "@/components/booking-approval";

Expand Down Expand Up @@ -313,7 +313,7 @@ export default function ChatPage() {

For simpler cases where you don't need type-safe validation or programmatic resumption, you can use [`createWebhook()`](/docs/api-reference/workflow/create-webhook) directly. This generates a unique URL that can be called to resume the workflow:

```typescript title="workflows/chat/steps/tools.ts" lineNumbers
```ts title="workflows/chat/steps/tools.ts" lineNumbers
import { createWebhook } from "workflow";
import { z } from "zod";

Expand Down
Loading
Loading