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
2 changes: 1 addition & 1 deletion docs/content/docs/api-reference/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ In non-interactive mode, dependencies are installed without starting the long-ru

The CLI applies the selected framework's final route/page files, dependencies, and deployment configuration over the base template. Both Vercel AI SDK variants are standard Next.js deployments with `streamText()`, `toUIMessageStreamResponse()`, and `vercelAIAdapter()`. Both LangGraph variants separate the Agent Server described by `langgraph.json` from the Next.js frontend/proxy. The proxy uses `@openuidev/langchain`, and the browser consumes its AG-UI stream with `agUIAdapter()`. Both Eve variants wrap Next.js with `withEve()` and talk to Eve's native `/eve/v1/session*` protocol from the browser; a small client adapter maps Eve events to AG-UI for `agUIAdapter()`.

For Gateway-backed LangGraph and Vercel AI SDK variants, the framework owns orchestration and application tool execution. OpenUI Gateway is attached as the Responses/Chat Completions model provider and conversation store. Reports, presentations, web search, image search, and configured MCP tools remain Gateway-hosted tools. The Eve Gateway overlay uses Gateway as the Chat Completions provider and conversation store, without those hosted tools. For either LangGraph template, `pnpm dev` starts the local Agent Server and Next.js together. Deploy the Next.js frontend to Vercel and point `LANGGRAPH_API_URL` at wherever the Agent Server runs. Eve embeds in the Next.js process, so `pnpm dev` is enough for both Eve variants.
For Gateway-backed LangGraph and Vercel AI SDK variants, the framework owns orchestration and application tool execution. OpenUI Gateway is attached as the Responses/Chat Completions model provider and conversation store. Reports, presentations, web search, image search, and configured MCP tools remain Gateway-hosted tools. The Eve Gateway overlay uses Gateway as the Responses provider and conversation store, without those hosted tools. For either LangGraph template, `pnpm dev` starts the local Agent Server and Next.js together. Deploy the Next.js frontend to Vercel and point `LANGGRAPH_API_URL` at wherever the Agent Server runs. Eve embeds in the Next.js process, so `pnpm dev` is enough for both Eve variants.

Every framework overlay includes a `get_weather` example backed by Open-Meteo. Ask “What’s the weather in Berlin?” to verify the selected framework’s native multi-step tool loop.

Expand Down
4 changes: 2 additions & 2 deletions packages/openui-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,15 @@ Overlay names are loaded from the fetched template's `overlays/` directory (`def

The default implementation is part of each base template. For LangGraph or Vercel AI SDK, the CLI applies a framework-specific set of files plus a manifest for its dependencies, scripts, removals, and onboarding text. Both Vercel AI SDK variants are standard Next.js deployments whose `streamText()` result returns `toUIMessageStreamResponse()` for `vercelAIAdapter()`.

In Cloud framework variants, the selected framework owns the agent orchestration and application tool loop. OpenUI Cloud is attached as the model provider and conversation store. Reports, presentations, web search, image search, and configured MCP tools remain provider-executed Cloud tools on the default, LangGraph, and Vercel AI SDK Cloud routes, while application tools such as `get_weather` execute inside the selected framework. The Eve Cloud overlay uses Cloud as the Chat Completions provider and does not attach those provider-executed Cloud tools. Choosing a Cloud framework does not configure a user-owned model provider; choose `openui-self-hosted` for that.
In Cloud framework variants, the selected framework owns the agent orchestration and application tool loop. OpenUI Cloud is attached as the model provider and conversation store. Reports, presentations, web search, image search, and configured MCP tools remain provider-executed Cloud tools on the default, LangGraph, and Vercel AI SDK Cloud routes, while application tools such as `get_weather` execute inside the selected framework. The Eve Cloud overlay uses Cloud as the Responses provider and does not attach those provider-executed Cloud tools. Choosing a Cloud framework does not configure a user-owned model provider; choose `openui-self-hosted` for that.

The Cloud graph needs `THESYS_API_KEY`; the self-hosted graph needs the selected provider credentials such as `OPENAI_API_KEY`.

Every framework overlay includes `get_weather` as its example app-owned function tool. Ask “What’s the weather in Berlin?” to exercise the selected backend’s native tool loop.

#### Conversation storage

Every OpenUI Cloud variant uses OpenUI Cloud as its only durable conversation and artifact store. The browser connects directly through `useOpenuiCloudStorage()` with a short-lived frontend token, and `/api/chat` appends each turn to the same Cloud conversation with `conversation: threadId` and `store: true`. Vercel does not add a second store. Configure a LangGraph checkpointer separately only when the graph itself needs durable state, interrupts, or resumable runs.
Every OpenUI Cloud variant uses OpenUI Cloud as its durable conversation store. The browser connects directly through `useOpenuiCloudStorage()` with a short-lived frontend token. Default, LangGraph, and Vercel AI SDK routes append each turn via `/api/chat` with `conversation: threadId` and `store: true`. The Eve Cloud overlay uses the same Cloud thread store (`features: { artifact: false }`) and appends Responses turns with `conversation: threadId` and `store: true`; it does not use `/api/chat`. Vercel does not add a second store. Configure a LangGraph checkpointer separately only when the graph itself needs durable state, interrupts, or resumable runs.

The self-hosted variants do not configure durable storage. `AgentInterface` keeps the conversation in memory for the current page session and sends that history to `/api/chat`; refreshing the page loses it. Pass a storage implementation to `AgentInterface` and back it with your own database when persistence is required; add a LangGraph checkpointer only for graph-specific durable state.

Expand Down
25 changes: 16 additions & 9 deletions templates/openui-cloud/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,29 @@ variant. The browser connects directly through `useOpenuiCloudStorage()` with a
short-lived token from `/api/frontend-token`. For default, LangGraph, and
Vercel AI SDK routes, the `threadId` sent to `/api/chat` is the Cloud
conversation id, and the route appends each model turn to it with
`conversation: threadId` and `store: true`. The Eve overlay uses that same Cloud
thread store and maps each Cloud `threadId` to an Eve session cursor in the
browser; it does not use `/api/chat`.
Browser `localStorage` holds only the selected model (and, for Eve, the session
cursor), not conversation messages.
`conversation: threadId` and `store: true`.

The Eve overlay uses that same Cloud thread store via `useOpenuiCloudStorage()`
(`features: { artifact: false }`) and maps each Cloud `threadId` to an
in-memory Eve session cursor; it does not use `/api/chat`. Each Eve model turn
still appends to the Cloud conversation with `conversation: threadId` and
`store: true`. Provider-executed artifact and search tools are not attached.
Browser `localStorage` holds only the selected model on non-Eve scaffolds.

The Vercel AI SDK route does not create a second store. Add a LangGraph
checkpointer separately only if the graph needs durable state, interrupts, or
resumable runs.

## Switching Models

Use the model switcher in the chat header to choose a model for new messages. The starter keeps a
small curated model list in `src/lib/models.tsx` and sends the selected `provider/model` id to
`/api/chat`, which validates it against the same list. The built-in list includes Gemini, GPT,
Claude Sonnet, and Claude Opus options; free Gemini variants are marked with a `Free` badge.
Default, LangGraph, and Vercel AI SDK scaffolds put a model switcher in the chat
header. The starter keeps a small curated model list in `src/lib/models.tsx` and
sends the selected `provider/model` id to `/api/chat`, which validates it against
the same list. The built-in list includes Gemini, GPT, Claude Sonnet, and Claude
Opus options; free Gemini variants are marked with a `Free` badge.

The Vercel Eve scaffold has no dropdown: set `OPENUI_MODEL` in `.env`. The same
ids live in `src/lib/models.ts`. An unknown name fails when the agent loads.

The built-in model ids are available on [models.dev's OpenRouter provider
list](https://models.dev/providers/openrouter/).
Expand Down
62 changes: 62 additions & 0 deletions templates/openui-cloud/overlays/vercel-eve/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
This is an [OpenUI](https://openui.com) Cloud project bootstrapped with [`openui-cli`](https://openui.com/docs/chat/quick-start), using a [Vercel Eve](https://github.com/vercel/eve) agent.

## Setup

The CLI writes `.env` for you. If you cloned the generated project elsewhere,
run `pnpm generate:apiKey` to mint `THESYS_API_KEY`, then add `DEMO_USER_ID`
and `APP_ID`.

Optional: set `OPENUI_MODEL` to a Cloud model id such as
`google/gemini-3.6-flash-free`. An unknown id fails when the Eve agent loads —
the app will not sit idle until the first message.

## Prerequisites

- Node.js 24

## Getting Started

```bash
pnpm dev
```

Open [http://localhost:3000](http://localhost:3000). Ask "What's the weather in
Berlin?" to exercise the included Eve tool.

## Conversation storage

OpenUI Cloud is the durable conversation store, wired the same way as the other
Cloud scaffolds: `useOpenuiCloudStorage()` with a short-lived token from
`/api/frontend-token` and `features: { artifact: false }`. Each Eve turn
appends to that conversation with `conversation: threadId` and `store: true`.
Provider-executed artifact and search tools are not attached.

## Switching models

This scaffold has no model dropdown. Set `OPENUI_MODEL` in `.env`. The built-in
ids live in `src/lib/models.ts`.

## Eve commands

The normal development command is `pnpm dev`. The package also exposes Eve
directly:

```bash
pnpm eve:dev
pnpm eve:build
pnpm eve:start
```

## SDK packages

- `@openuidev/lang-core` — `generateSystemPrompt({ cloud: true })` used by Eve instructions.
- `@openuidev/thesys` — `chatLibrary` and Cloud conversation storage.
- `@openuidev/react-ui` — `AgentInterface`, `eveAdapter`.

A devtools widget is available automatically in development.

## Learn More

- [OpenUI Documentation](https://openui.com/docs)
- [OpenUI GitHub repository](https://github.com/thesysdev/openui)
- [Vercel Eve](https://github.com/vercel/eve)
6 changes: 4 additions & 2 deletions templates/openui-cloud/overlays/vercel-eve/agent/agent.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { createOpenAI } from "@ai-sdk/openai";
import { defineAgent } from "eve";
import { withCloudConversation } from "./cloud-conversation.ts";
import { resolveOpenuiModel, DEFAULT_MODEL } from "../src/lib/models.ts";

const apiKey = process.env.THESYS_API_KEY;
if (!apiKey) throw new Error("Missing required env var: THESYS_API_KEY");
Expand All @@ -9,8 +11,8 @@ const openai = createOpenAI({
baseURL: "https://api.thesys.dev/v1/embed",
});

const model = openai.chat(
process.env.OPENUI_MODEL ?? "google/gemini-3.6-flash-free",
const model = withCloudConversation(
openai.responses(resolveOpenuiModel(DEFAULT_MODEL)),
);

export default defineAgent({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { wrapLanguageModel, type LanguageModel } from "ai";

/** Eve renders `clientContext` as this prefix plus a JSON object. */
const CLIENT_CONTEXT_PREFIX = "Client context:\n";

type PromptMessage = { role: string; content: unknown };

function userText(message: PromptMessage): string {
if (message.role !== "user") return "";
const content = message.content;
if (typeof content === "string") return content;
if (!Array.isArray(content)) return "";
return content
.map((part) =>
part && typeof part === "object" && "type" in part && part.type === "text" && "text" in part
? String((part as { text: unknown }).text ?? "")
: "",
)
.join("");
}

function isClientContext(message: PromptMessage): boolean {
return userText(message).startsWith(CLIENT_CONTEXT_PREFIX);
}

function conversationIdFrom(prompt: readonly PromptMessage[]): string {
for (const message of prompt) {
if (!isClientContext(message)) continue;
try {
const id = JSON.parse(userText(message).slice(CLIENT_CONTEXT_PREFIX.length)).conversationId;
if (typeof id === "string" && id) return id;
} catch {
// ignore malformed client context
}
}
return "";
}

/** Cloud already has prior turns. Send only the new user message or tool results. */
function latestStep<T extends PromptMessage>(prompt: readonly T[]): T[] {
const system = prompt.filter((message) => message.role === "system");
const rest = prompt.filter((message) => message.role !== "system");
if (rest.length === 0) return system;

const last = rest[rest.length - 1]!;
if (last.role === "tool") {
let start = rest.length - 1;
while (start > 0 && rest[start - 1]!.role === "tool") start -= 1;
return [...system, ...rest.slice(start)];
}

const user = [...rest].reverse().find((message) => message.role === "user");
return user ? [...system, user] : [...system, last];
}

/**
* Read `conversationId` from Eve `clientContext`, strip that synthetic user
* message, and set `store` + `conversation` on Responses stream calls.
*/
export function withCloudConversation(model: LanguageModel): LanguageModel {
return wrapLanguageModel({
model,
middleware: {
async transformParams({ params, type }) {
const prompt = params.prompt as PromptMessage[];
const conversationId = conversationIdFrom(prompt);
const withoutCtx = prompt.filter((message) => !isClientContext(message));

// Compaction uses generateText. Don't write those calls into Cloud.
if (type !== "stream" || !conversationId) {
return { ...params, prompt: withoutCtx };
}

const openai =
params.providerOptions &&
typeof params.providerOptions === "object" &&
"openai" in params.providerOptions &&
params.providerOptions.openai &&
typeof params.providerOptions.openai === "object"
? params.providerOptions.openai
: {};

return {
...params,
prompt: latestStep(withoutCtx),
providerOptions: {
...params.providerOptions,
openai: { ...openai, store: true, conversation: conversationId },
},
};
},
},
});
}
11 changes: 8 additions & 3 deletions templates/openui-cloud/overlays/vercel-eve/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"@openuidev/react-headless": "^0.9.13",
"@vercel/connect": "0.2.2",
"ai": "7.0.0-beta.178",
"eve": "^0.11.7",
"eve": "^0.52.2",
"lru-cache": "^11.2.6"
},
"scripts": {
Expand All @@ -18,7 +18,12 @@
"removeDependencies": ["openai"]
},
"files": {
"remove": ["src/app/api/chat/route.ts", "src/lib/tool-loop.ts"]
"remove": [
"src/app/api/chat/route.ts",
"src/lib/tool-loop.ts",
"src/lib/models.tsx",
"src/hooks/use-persisted-model.ts"
]
},
"gettingStarted": "The generated Vercel Eve agent uses OpenUI Cloud as its model provider. `{{packageManager}} run dev` starts Next.js with Eve embedded via withEve(). Ask \"What's the weather in Berlin?\" to exercise the included Eve tool."
"gettingStarted": "The generated Vercel Eve agent uses OpenUI Cloud Responses as its model provider. `{{packageManager}} run dev` starts Next.js with Eve embedded via withEve(). Set OPENUI_MODEL to a Cloud model id — an unknown name fails at agent load. Ask \"What's the weather in Berlin?\" to exercise the included Eve tool."
}
Loading
Loading