Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/named-next-agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eve": patch
---

Add named multi-agent routing to `withEve` and `useEveAgent`. Next.js apps can now configure multiple eve roots with `agents`, then target one from the frontend with `useEveAgent({ agent: "name" })`.
1 change: 1 addition & 0 deletions apps/frameworks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
These apps verify eve's frontend framework integrations and act as runnable examples for maintainers.

- `framework-next` covers `eve/next` and `withEve()`.
- `framework-next-multi-agent` covers `withEve({ agents })` and named `useEveAgent({ agent })` calls.
- `framework-nuxt` covers the `eve/nuxt` module.
- `framework-sveltekit` covers the `eve/sveltekit` Vite plugin.

Expand Down
16 changes: 16 additions & 0 deletions apps/frameworks/next-multi-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Next.js multi-agent eve demo

This app demonstrates `withEve({ agents })` with three independent eve agents
mounted into one Next.js app:

- `support` at `/eve/agents/support/eve/v1/*`
- `billing` at `/eve/agents/billing/eve/v1/*`
- `research` at `/eve/agents/research/eve/v1/*`

Run it locally with:

```sh
pnpm --filter framework-next-multi-agent dev
```

The page calls each agent with `useEveAgent({ agent: "<name>" })`.
5 changes: 5 additions & 0 deletions apps/frameworks/next-multi-agent/agents/billing/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineAgent } from "eve";

export default defineAgent({
model: "anthropic/claude-opus-4.6",
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
You are the billing agent in the Next.js multi-agent fixture.

Keep replies to one or two short sentences. Help with invoices, plans, refunds,
and payment status.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineTool } from "eve/tools";
import { z } from "zod";

export default defineTool({
description: "Return the identity and focus area of this fixture agent.",
inputSchema: z.object({}),
execute: async () => ({
agent: "billing",
focus: "Invoices, plans, and payment questions",
}),
});
5 changes: 5 additions & 0 deletions apps/frameworks/next-multi-agent/agents/research/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineAgent } from "eve";

export default defineAgent({
model: "anthropic/claude-opus-4.6",
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
You are the research agent in the Next.js multi-agent fixture.

Keep replies to one or two short sentences. Help turn research questions into a
clear next step or brief summary.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineTool } from "eve/tools";
import { z } from "zod";

export default defineTool({
description: "Return the identity and focus area of this fixture agent.",
inputSchema: z.object({}),
execute: async () => ({
agent: "research",
focus: "Research summaries and source planning",
}),
});
5 changes: 5 additions & 0 deletions apps/frameworks/next-multi-agent/agents/support/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineAgent } from "eve";

export default defineAgent({
model: "anthropic/claude-opus-4.6",
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
You are the support agent in the Next.js multi-agent fixture.

Keep replies to one or two short sentences. Help triage product issues and ask
for only the next missing detail.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineTool } from "eve/tools";
import { z } from "zod";

export default defineTool({
description: "Return the identity and focus area of this fixture agent.",
inputSchema: z.object({}),
execute: async () => ({
agent: "support",
focus: "Customer-facing triage and troubleshooting",
}),
});
19 changes: 19 additions & 0 deletions apps/frameworks/next-multi-agent/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Metadata } from "next";
import "./styles.css";

export const metadata: Metadata = {
title: "eve multi-agent Next.js fixture",
description: "Next.js fixture for withEve named agents.",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
100 changes: 100 additions & 0 deletions apps/frameworks/next-multi-agent/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"use client";

import { useState, type ComponentProps } from "react";
import { useEveAgent } from "eve/react";

const AGENTS = [
{
description: "Customer-facing triage and troubleshooting",
initialPrompt: "My checkout page is failing.",
name: "support",
},
{
description: "Invoices, plans, and payment questions",
initialPrompt: "Why did my invoice increase?",
name: "billing",
},
{
description: "Research summaries and source planning",
initialPrompt: "Compare two options briefly.",
name: "research",
},
] as const;

type AgentName = (typeof AGENTS)[number]["name"];

export default function Home() {
return (
<main>
<header>
<p>Next.js named agents fixture</p>
<h1>Three eve agents mounted through one Next.js app</h1>
</header>
<section className="grid">
{AGENTS.map((agent) => (
<AgentPanel key={agent.name} agent={agent} />
))}
</section>
</main>
);
}

function AgentPanel({
agent,
}: {
readonly agent: {
readonly description: string;
readonly initialPrompt: string;
readonly name: AgentName;
};
}) {
const eve = useEveAgent({ agent: agent.name });
const [message, setMessage] = useState(agent.initialPrompt);
const isBusy = eve.status === "submitted" || eve.status === "streaming";

const submit: ComponentProps<"form">["onSubmit"] = async (event) => {
event.preventDefault();
const trimmed = message.trim();
if (trimmed.length === 0 || isBusy) return;
setMessage("");
await eve.send({ message: trimmed });
};

return (
<article>
<div className="panel-header">
<div>
<h2>{agent.name}</h2>
<p>{agent.description}</p>
</div>
<span>{eve.status}</span>
</div>

<div className="messages">
{eve.data.messages.length === 0 ? (
<p className="empty">Send a short prompt to the {agent.name} agent.</p>
) : (
eve.data.messages.map((item) => (
<div className="message" data-role={item.role} key={item.id}>
<strong>{item.role}</strong>
{item.parts.map((part, index) =>
part.type === "text" ? <p key={index}>{part.text}</p> : null,
)}
</div>
))
)}
</div>

<form onSubmit={submit}>
<textarea
aria-label={`Message ${agent.name} agent`}
onChange={(event) => setMessage(event.currentTarget.value)}
value={message}
/>
<button disabled={isBusy || message.trim().length === 0} type="submit">
Send to {agent.name}
</button>
</form>
</article>
);
}
173 changes: 173 additions & 0 deletions apps/frameworks/next-multi-agent/app/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
:root {
color: #171717;
background: #f7f7f5;
font-family:
Inter,
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
}

* {
box-sizing: border-box;
}

body {
margin: 0;
}

main {
width: min(1180px, calc(100vw - 48px));
margin: 0 auto;
padding: 40px 0;
}

header {
margin-bottom: 28px;
}

header p {
margin: 0 0 8px;
color: #5f656f;
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
}

h1 {
max-width: 760px;
margin: 0;
font-size: 36px;
line-height: 1.1;
}

.grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
}

article {
display: flex;
min-height: 620px;
flex-direction: column;
border: 1px solid #deded8;
border-radius: 8px;
background: #ffffff;
}

.panel-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
border-bottom: 1px solid #ebebe6;
padding: 16px;
}

h2 {
margin: 0 0 4px;
font-size: 18px;
}

.panel-header p {
margin: 0;
color: #626873;
font-size: 14px;
line-height: 1.4;
}

.panel-header span {
border-radius: 999px;
background: #eef2ff;
color: #32458f;
font-size: 12px;
font-weight: 700;
padding: 4px 8px;
}

.messages {
flex: 1;
overflow: auto;
padding: 16px;
}

.empty {
margin: 0;
color: #737780;
font-size: 14px;
line-height: 1.5;
}

.message {
border-radius: 8px;
margin-bottom: 12px;
padding: 12px;
}

.message[data-role="user"] {
background: #f2f3f5;
}

.message[data-role="assistant"] {
background: #eff8f4;
}

.message strong {
display: block;
margin-bottom: 6px;
font-size: 12px;
text-transform: uppercase;
}

.message p {
margin: 0 0 8px;
font-size: 14px;
line-height: 1.5;
}

.message p:last-child {
margin-bottom: 0;
}

form {
display: grid;
gap: 10px;
border-top: 1px solid #ebebe6;
padding: 16px;
}

textarea {
min-height: 92px;
resize: vertical;
border: 1px solid #cfd1d5;
border-radius: 8px;
color: inherit;
font: inherit;
padding: 10px 12px;
}

button {
border: 0;
border-radius: 8px;
background: #171717;
color: #ffffff;
cursor: pointer;
font: inherit;
font-weight: 700;
padding: 10px 12px;
}

button:disabled {
cursor: not-allowed;
opacity: 0.5;
}

@media (max-width: 900px) {
.grid {
grid-template-columns: 1fr;
}
}
Loading
Loading