Usage
Create your first agent app.
Build a small Better Agent app in server.ts, client.ts, and page.tsx.
1. Define the server
Create a Better Agent app with one agent.
import { betterAgent, defineAgent } from "@better-agent/core";
import { createOpenAI } from "@better-agent/providers/openai";
const openai = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
const helloAgent = defineAgent({
name: "helloAgent",
model: openai.text("gpt-5-mini"),
instruction: "You are a friendly assistant. Keep answers short.",
});
const app = betterAgent({
agents: [helloAgent],
baseURL: "/api",
secret: "dev-secret",
});
export default app;2. Create a typed client
Create a typed client from the server app so agent names, inputs, outputs, tools, and other app types stay aligned with your backend.
import { createClient } from "@better-agent/client";
import type app from "./server";
export const client = createClient<typeof app>({
baseURL: "/api",
secret: "dev-secret",
});3. Connect the UI
Pick the client helper for your UI framework. These examples keep the UI intentionally small for a first setup.
import { useState } from "react";
import { useAgent } from "@better-agent/client/react";
import { client } from "./client";
export default function Page() {
const [input, setInput] = useState("");
const { messages, status, sendMessage } = useAgent(client, {
agent: "helloAgent",
});
return (
<div>
<form
onSubmit={async (event) => {
event.preventDefault();
if (!input.trim()) return;
await sendMessage({ input });
setInput("");
}}
>
<input value={input} onChange={(event) => setInput(event.target.value)} />
<button type="submit">Send</button>
</form>
<p>Status: {status}</p>
<ul>
{messages.map((message) => (
<li key={message.localId}>
{message.role}: {message.parts.map((part) => part.type === "text" ? part.text : "").join("")}
</li>
))}
</ul>
</div>
);
}<script setup lang="ts">
import { ref } from "vue";
import { useAgent } from "@better-agent/client/vue";
import { client } from "./client";
const agent = useAgent(client, {
agent: "helloAgent",
});
const input = ref("");
</script>
<template>
<div>
<form @submit.prevent="input.trim() && agent.sendMessage({ input }).then(() => (input = ''))">
<input v-model="input" />
<button type="submit">Send</button>
</form>
<p>Status: {{ agent.status.value }}</p>
<ul>
<li v-for="message in agent.messages.value" :key="message.localId">
{{ message.role }}:
{{ message.parts.map((part) => part.type === "text" ? part.text : "").join("") }}
</li>
</ul>
</div>
</template><script lang="ts">
import { createAgentChat } from "@better-agent/client/svelte";
import { client } from "./client";
const chat = createAgentChat(client, {
agent: "helloAgent",
});
let input = "";
</script>
<div>
<form
on:submit|preventDefault={async () => {
if (!input.trim()) return;
await chat.sendMessage({ input });
input = "";
}}
>
<input bind:value={input} />
<button type="submit">Send</button>
</form>
<p>Status: {$chat.status}</p>
<ul>
{#each $chat.messages as message (message.localId)}
<li>
{message.role}: {message.parts.map((part) => part.type === "text" ? part.text : "").join("")}
</li>
{/each}
</ul>
</div>import { useAgent } from "@better-agent/client/solid";
import { For, createSignal } from "solid-js";
import { client } from "./client";
export default function Page() {
const { messages, status, sendMessage } = useAgent(client, {
agent: "helloAgent",
});
const [input, setInput] = createSignal("");
return (
<div>
<form
onSubmit={async (event) => {
event.preventDefault();
if (!input().trim()) return;
await sendMessage({ input: input() });
setInput("");
}}
>
<input value={input()} onInput={(event) => setInput(event.currentTarget.value)} />
<button type="submit">Send</button>
</form>
<p>Status: {status()}</p>
<ul>
<For each={messages()}>
{(message) => (
<li>
{message.role}: {message.parts.map((part) => part.type === "text" ? part.text : "").join("")}
</li>
)}
</For>
</ul>
</div>
);
}import { useState } from "preact/hooks";
import { useAgent } from "@better-agent/client/preact";
import { client } from "./client";
export default function App() {
const { messages, status, sendMessage } = useAgent(client, {
agent: "helloAgent",
});
const [input, setInput] = useState("");
return (
<div>
<form
onSubmit={async (event) => {
event.preventDefault();
if (!input.trim()) return;
await sendMessage({ input });
setInput("");
}}
>
<input value={input} onInput={(event) => setInput((event.target as HTMLInputElement).value)} />
<button type="submit">Send</button>
</form>
<p>Status: {status}</p>
<ul>
{messages.map((message) => (
<li key={message.localId}>
{message.role}: {message.parts.map((part) => part.type === "text" ? part.text : "").join("")}
</li>
))}
</ul>
</div>
);
}From here, you can add tools, structured output, persistence and others without changing the basic shape of the app.
What's Next
- Agent: understand the core building block.
- Tools: let agents call typed functions during a run.
- Structured Output: return typed results from a schema.