# Quickstart (Next.js Server Action)

This quickstart matches the current OSS implementation and mirrors `examples/kortyx-nextjs-chat-server-action`.

> **Good to know:** As of Next.js 16.1.6 (March 8, 2026), Server Actions return after completion and do not stream chunk updates to client UI in real time. For live token/chunk rendering, use [Quickstart (Next.js API Route)](./02-quickstart-nextjs.md).

## 1. Create a workflow

```ts
// src/workflows/general-chat.workflow.ts
import { defineWorkflow } from "kortyx";
import { google } from "@/lib/providers";
import { chatNode } from "@/nodes/chat.node";

export const generalChatWorkflow = defineWorkflow({
  id: "general-chat",
  version: "1.0.0",
  description: "Single-node chat workflow",
  nodes: {
    chat: {
      run: chatNode,
      params: {
        model: google("gemini-2.5-flash"),
        temperature: 0.3,
      },
    },
  },
  edges: [
    ["__start__", "chat"],
    ["chat", "__end__"],
  ],
});
```

```js
// src/workflows/general-chat.workflow.js
import { defineWorkflow } from "kortyx";
import { google } from "@/lib/providers";
import { chatNode } from "@/nodes/chat.node";

export const generalChatWorkflow = defineWorkflow({
  id: "general-chat",
  version: "1.0.0",
  description: "Single-node chat workflow",
  nodes: {
    chat: {
      run: chatNode,
      params: {
        model: google("gemini-2.5-flash"),
        temperature: 0.3,
      },
    },
  },
  edges: [
    ["__start__", "chat"],
    ["chat", "__end__"],
  ],
});
```

## 2. Add provider entrypoint

```ts
// src/lib/providers.ts
export { google } from "@kortyx/google";
```

```js
// src/lib/providers.js
export { google } from "@kortyx/google";
```

> **Good to know:** The default `google` export reads `GOOGLE_API_KEY`, `GEMINI_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY`, `KORTYX_GOOGLE_API_KEY`, or `KORTYX_GEMINI_API_KEY` on first use. If you want explicit provider setup or custom transport settings, replace this with `createGoogleGenerativeAI(...)`.

This `src/lib/providers.ts` file is a re-export only. In files where you call `google("...")` directly, import `google` from `@/lib/providers` or from `@kortyx/google`; do not expect `export { google } from "@kortyx/google"` to create a local variable in the same file.

## 3. Create a node

```ts
// src/nodes/chat.node.ts
import type { ProviderModelRef } from "kortyx";
import { useReason } from "kortyx";
import { google } from "@/lib/providers";

export type ChatNodeParams = {
  model?: ProviderModelRef;
  temperature?: number;
  system?: string;
};

export const chatNode = async ({
  input,
  params,
}: {
  input: unknown;
  params: ChatNodeParams;
}) => {
  const {
    model = google("gemini-2.5-flash"),
    temperature = 0.3,
    system = "",
  } = params;

  const res = await useReason({
    id: "chat",
    model,
    system: system || "You are a concise assistant.",
    input: String(input ?? ""),
    temperature,
    emit: true, // publish text events
    stream: true, // token-by-token output
  });

  return {
    data: { text: res.text },
  };
};
```

```js
// src/nodes/chat.node.js
import { useReason } from "kortyx";
import { google } from "@/lib/providers";

export const chatNode = async ({ input, params }) => {
  const {
    model = google("gemini-2.5-flash"),
    temperature = 0.3,
    system = "",
  } = params ?? {};

  const res = await useReason({
    id: "chat",
    model,
    system: system || "You are a concise assistant.",
    input: String(input ?? ""),
    temperature,
    emit: true, // publish text events
    stream: true, // token-by-token output
  });

  return {
    data: { text: res.text },
  };
};
```

## 4. Wire an agent

```ts
// src/lib/kortyx-client.ts
import { createAgent } from "kortyx";
import { generalChatWorkflow } from "@/workflows/general-chat.workflow";

export const agent = createAgent({
  workflows: [generalChatWorkflow],
  defaultWorkflowId: "general-chat",
});
```

```js
// src/lib/kortyx-client.js
import { createAgent } from "kortyx";
import { generalChatWorkflow } from "@/workflows/general-chat.workflow";

export const agent = createAgent({
  workflows: [generalChatWorkflow],
  defaultWorkflowId: "general-chat",
});
```

## 5. Call `streamChat` (buffered in server action)

```ts
// src/app/actions/chat.ts
"use server";

import { collectStream, type StreamChunk } from "kortyx";
import { agent } from "@/lib/kortyx-client";

export async function runChat(args: {
  sessionId: string;
  messages: Array<{ role: "user" | "assistant" | "system"; content: string }>;
}): Promise<StreamChunk[]> {
  const stream = await agent.streamChat(args.messages, {
    sessionId: args.sessionId,
  });

  return collectStream(stream);
}
```

```js
// src/app/actions/chat.js
"use server";

import { collectStream } from "kortyx";
import { agent } from "@/lib/kortyx-client";

export async function runChat(args) {
  const stream = await agent.streamChat(args.messages, {
    sessionId: args.sessionId,
  });

  return collectStream(stream);
}
```

```ts
// Optional alternative:
// import { collectBufferedStream } from "kortyx";
// return collectBufferedStream(stream); // { chunks, text, structured }
```
```js
// Optional alternative:
// import { collectBufferedStream } from "kortyx";
// return collectBufferedStream(stream); // { chunks, text, structured }
```

> **Good to know:** Use `collectStream(...)` when you want to process raw chunk events yourself. Use `collectBufferedStream(...)` when you want merged text + structured summaries.

## 6. Run

```bash tabs="run-dev" tab="pnpm"
GOOGLE_API_KEY=your_key_here pnpm dev
```

```bash tabs="run-dev" tab="npm"
GOOGLE_API_KEY=your_key_here npm run dev
```

```bash tabs="run-dev" tab="yarn"
GOOGLE_API_KEY=your_key_here yarn dev
```

```bash tabs="run-dev" tab="bun"
GOOGLE_API_KEY=your_key_here bun run dev
```

## What this gives you

- Type-safe workflow definition
- Explicit provider bootstrap at app level
- Node-level model control via `useReason(...)`
- Buffered chunk collection through a Server Action return value
- Chunk event types in the buffered result (`text-start`, `text-delta`, `text-end`, `done`)
- Built-in interrupt/resume path when your nodes use `useInterrupt`

Next:

- [Hooks](../02-core-concepts/07-hooks.md)
- [Interrupts and Resume](../03-guides/02-interrupts-and-resume.md)
