@an-sdk/nextjs 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +21 -0
- package/docs/01-overview.md +61 -0
- package/docs/02-getting-started.md +110 -0
- package/docs/03-defining-agents.md +159 -0
- package/docs/04-react-ui.md +186 -0
- package/docs/05-nextjs.md +117 -0
- package/docs/06-node-sdk.md +125 -0
- package/docs/07-cli.md +88 -0
- package/docs/08-custom-tools.md +88 -0
- package/package.json +11 -2
- package/src/index.ts +2 -0
- package/src/server.ts +62 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# @an-sdk/nextjs
|
|
2
|
+
|
|
3
|
+
Next.js integration for AN agent chat — server-side token handler so your API key never reaches the browser.
|
|
4
|
+
|
|
5
|
+
## Docs
|
|
6
|
+
|
|
7
|
+
Full documentation: `./docs/` directory (8 guides covering the entire AN platform).
|
|
8
|
+
|
|
9
|
+
## Source
|
|
10
|
+
|
|
11
|
+
Source code: `./src/` directory.
|
|
12
|
+
|
|
13
|
+
## Key Entry Points
|
|
14
|
+
|
|
15
|
+
- `src/index.ts` — Re-exports everything from `@an-sdk/react`
|
|
16
|
+
- `src/server.ts` — `exchangeToken()` and `createAnTokenHandler()` for Next.js API routes
|
|
17
|
+
|
|
18
|
+
## Two Entry Points
|
|
19
|
+
|
|
20
|
+
- `@an-sdk/nextjs` — Client-side: all React components from `@an-sdk/react`
|
|
21
|
+
- `@an-sdk/nextjs/server` — Server-side: token exchange (Node.js only)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# AN Platform Overview
|
|
2
|
+
|
|
3
|
+
AN is a platform for building, deploying, and embedding AI agents. You define an agent in TypeScript, deploy it with one command, and embed a chat UI in any React app.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Your Code (agent.ts)
|
|
9
|
+
|
|
|
10
|
+
v
|
|
11
|
+
an deploy (CLI)
|
|
12
|
+
|
|
|
13
|
+
v
|
|
14
|
+
AN Platform
|
|
15
|
+
|
|
|
16
|
+
+---> E2B Sandbox (isolated Node.js environment)
|
|
17
|
+
| |
|
|
18
|
+
| +---> Claude Agent SDK / Codex (executes your agent)
|
|
19
|
+
| |
|
|
20
|
+
| +---> Your custom tools run here
|
|
21
|
+
|
|
|
22
|
+
+---> AN Relay (relay.an.dev)
|
|
23
|
+
|
|
|
24
|
+
+---> SSE streaming to clients
|
|
25
|
+
|
|
|
26
|
+
+---> Token exchange (API key -> short-lived JWT)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Packages
|
|
30
|
+
|
|
31
|
+
| Package | Purpose |
|
|
32
|
+
|---------|---------|
|
|
33
|
+
| `@an-sdk/agent` | Define agents and tools with type inference |
|
|
34
|
+
| `@an-sdk/cli` | Deploy agents from your terminal |
|
|
35
|
+
| `@an-sdk/react` | Chat UI components (theming, tool renderers) |
|
|
36
|
+
| `@an-sdk/nextjs` | Next.js server-side token handler |
|
|
37
|
+
| `@an-sdk/node` | Server-side SDK (sandboxes, threads, tokens) |
|
|
38
|
+
|
|
39
|
+
## Key Concepts
|
|
40
|
+
|
|
41
|
+
- **Agent**: A TypeScript config defining model, system prompt, tools, and hooks. Runs in a cloud sandbox.
|
|
42
|
+
- **Tool**: A function your agent can call, with a Zod schema for input validation and full type inference.
|
|
43
|
+
- **Sandbox**: An isolated E2B cloud environment where your agent executes. Has Node.js, git, and system tools.
|
|
44
|
+
- **Thread**: A conversation within a sandbox. One sandbox can have multiple threads.
|
|
45
|
+
- **Relay**: The streaming gateway at `relay.an.dev`. Handles auth, routing, and SSE streaming.
|
|
46
|
+
|
|
47
|
+
## Runtimes
|
|
48
|
+
|
|
49
|
+
AN supports two runtimes:
|
|
50
|
+
|
|
51
|
+
- **`claude-code`** (default) — Uses the Claude Agent SDK. Full tool use, file editing, bash execution.
|
|
52
|
+
- **`codex`** — Uses OpenAI Codex via ACP provider.
|
|
53
|
+
|
|
54
|
+
## Auth Model
|
|
55
|
+
|
|
56
|
+
Two layers of authentication:
|
|
57
|
+
|
|
58
|
+
1. **Client -> Relay**: API key (`an_sk_...`) or short-lived JWT (via token exchange)
|
|
59
|
+
2. **Sandbox -> AI Provider**: Handled internally by the platform (Claude Proxy)
|
|
60
|
+
|
|
61
|
+
For web apps, use `@an-sdk/nextjs` to exchange your API key for a short-lived JWT on the server, so the key never reaches the browser.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
## 1. Get an API Key
|
|
4
|
+
|
|
5
|
+
Sign up at [an.dev](https://an.dev) and get your API key from [an.dev/agents/dashboard/api](https://an.dev/agents/dashboard/api).
|
|
6
|
+
|
|
7
|
+
## 2. Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @an-sdk/agent zod
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 3. Define Your Agent
|
|
14
|
+
|
|
15
|
+
Create `src/agent.ts`:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { agent, tool } from "@an-sdk/agent"
|
|
19
|
+
import { z } from "zod"
|
|
20
|
+
|
|
21
|
+
export default agent({
|
|
22
|
+
model: "claude-sonnet-4-6",
|
|
23
|
+
systemPrompt: "You are a helpful coding assistant.",
|
|
24
|
+
tools: {
|
|
25
|
+
greet: tool({
|
|
26
|
+
description: "Greet a user by name",
|
|
27
|
+
inputSchema: z.object({ name: z.string() }),
|
|
28
|
+
execute: async ({ name }) => ({
|
|
29
|
+
content: [{ type: "text", text: `Hello, ${name}!` }],
|
|
30
|
+
}),
|
|
31
|
+
}),
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 4. Login & Deploy
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx @an-sdk/cli login
|
|
40
|
+
# Enter your API key: an_sk_...
|
|
41
|
+
|
|
42
|
+
npx @an-sdk/cli deploy
|
|
43
|
+
# Bundling src/agent.ts...
|
|
44
|
+
# Deploying my-agent...
|
|
45
|
+
# https://api.an.dev/v1/chat/my-agent
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Your agent is live.
|
|
49
|
+
|
|
50
|
+
## 5. Embed in a React App
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install @an-sdk/nextjs @an-sdk/react ai @ai-sdk/react
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Create a token route (keeps your API key on the server):
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
// app/api/an/token/route.ts
|
|
60
|
+
import { createAnTokenHandler } from "@an-sdk/nextjs/server"
|
|
61
|
+
|
|
62
|
+
export const POST = createAnTokenHandler({
|
|
63
|
+
apiKey: process.env.AN_API_KEY!,
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Add the chat UI:
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
// app/page.tsx
|
|
71
|
+
"use client"
|
|
72
|
+
|
|
73
|
+
import { useChat } from "@ai-sdk/react"
|
|
74
|
+
import { AnAgentChat, createAnChat } from "@an-sdk/nextjs"
|
|
75
|
+
import "@an-sdk/react/styles.css"
|
|
76
|
+
import { useMemo } from "react"
|
|
77
|
+
|
|
78
|
+
export default function Chat() {
|
|
79
|
+
const chat = useMemo(
|
|
80
|
+
() => createAnChat({
|
|
81
|
+
agent: "your-agent-slug",
|
|
82
|
+
tokenUrl: "/api/an/token",
|
|
83
|
+
}),
|
|
84
|
+
[],
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
const { messages, sendMessage, status, stop, error } = useChat({ chat })
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<AnAgentChat
|
|
91
|
+
messages={messages}
|
|
92
|
+
onSend={(msg) =>
|
|
93
|
+
sendMessage({ parts: [{ type: "text", text: msg.content }] })
|
|
94
|
+
}
|
|
95
|
+
status={status}
|
|
96
|
+
onStop={stop}
|
|
97
|
+
error={error}
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Next Steps
|
|
104
|
+
|
|
105
|
+
- [Defining Agents](./03-defining-agents.md) — Models, tools, hooks, permissions
|
|
106
|
+
- [React UI](./04-react-ui.md) — Theming, slots, tool renderers
|
|
107
|
+
- [Next.js Integration](./05-nextjs.md) — Server-side token handler
|
|
108
|
+
- [Node SDK](./06-node-sdk.md) — Sandboxes, threads, tokens from server code
|
|
109
|
+
- [CLI Reference](./07-cli.md) — All CLI commands
|
|
110
|
+
- [Custom Tools](./08-custom-tools.md) — Build custom tool renderers
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Defining Agents
|
|
2
|
+
|
|
3
|
+
Agents are defined with the `agent()` and `tool()` functions from `@an-sdk/agent`. These are config-only — they return exactly what you pass in, with type inference added. The actual execution happens in the AN runtime (E2B sandbox).
|
|
4
|
+
|
|
5
|
+
## `agent(config)`
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { agent } from "@an-sdk/agent"
|
|
9
|
+
|
|
10
|
+
export default agent({
|
|
11
|
+
// Model (default: "claude-sonnet-4-6")
|
|
12
|
+
model: "claude-sonnet-4-6",
|
|
13
|
+
|
|
14
|
+
// System prompt
|
|
15
|
+
systemPrompt: "You are a PR reviewer...",
|
|
16
|
+
|
|
17
|
+
// Custom tools (see below)
|
|
18
|
+
tools: { /* ... */ },
|
|
19
|
+
|
|
20
|
+
// Runtime: "claude-code" (default) or "codex"
|
|
21
|
+
runtime: "claude-code",
|
|
22
|
+
|
|
23
|
+
// Permission mode: "default", "acceptEdits", or "bypassPermissions"
|
|
24
|
+
permissionMode: "default",
|
|
25
|
+
|
|
26
|
+
// Max conversation turns (default: 50)
|
|
27
|
+
maxTurns: 50,
|
|
28
|
+
|
|
29
|
+
// Max budget in USD
|
|
30
|
+
maxBudgetUsd: 5,
|
|
31
|
+
|
|
32
|
+
// Lifecycle hooks (see below)
|
|
33
|
+
onStart: async () => {},
|
|
34
|
+
onToolCall: async ({ toolName, input }) => {},
|
|
35
|
+
onToolResult: async ({ toolName, input, output }) => {},
|
|
36
|
+
onStepFinish: async ({ step }) => {},
|
|
37
|
+
onFinish: async ({ result }) => {},
|
|
38
|
+
onError: async ({ error }) => {},
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Defaults
|
|
43
|
+
|
|
44
|
+
| Field | Default |
|
|
45
|
+
|-------|---------|
|
|
46
|
+
| `model` | `"claude-sonnet-4-6"` |
|
|
47
|
+
| `runtime` | `"claude-code"` |
|
|
48
|
+
| `permissionMode` | `"default"` |
|
|
49
|
+
| `maxTurns` | `50` |
|
|
50
|
+
| `tools` | `{}` |
|
|
51
|
+
|
|
52
|
+
## `tool(definition)`
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { tool } from "@an-sdk/agent"
|
|
56
|
+
import { z } from "zod"
|
|
57
|
+
|
|
58
|
+
const myTool = tool({
|
|
59
|
+
description: "What this tool does",
|
|
60
|
+
inputSchema: z.object({
|
|
61
|
+
path: z.string(),
|
|
62
|
+
verbose: z.boolean().optional(),
|
|
63
|
+
}),
|
|
64
|
+
execute: async (args) => {
|
|
65
|
+
// args is fully typed: { path: string; verbose?: boolean }
|
|
66
|
+
return {
|
|
67
|
+
content: [{ type: "text", text: "result" }],
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The `execute` function receives args typed from the Zod schema. Return value must have `content` array with `{ type: "text", text: string }` items.
|
|
74
|
+
|
|
75
|
+
## Hooks
|
|
76
|
+
|
|
77
|
+
### `onToolCall`
|
|
78
|
+
|
|
79
|
+
Runs before a tool executes. Return `false` to block it.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
onToolCall: async ({ toolName, input }) => {
|
|
83
|
+
if (toolName === "Bash" && input.command?.includes("rm -rf")) {
|
|
84
|
+
return false // blocked
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `onToolResult`
|
|
90
|
+
|
|
91
|
+
Runs after a tool executes with the result.
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
onToolResult: async ({ toolName, input, output }) => {
|
|
95
|
+
console.log(`Tool ${toolName} returned:`, output)
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### `onFinish`
|
|
100
|
+
|
|
101
|
+
Runs when the agent completes successfully.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
onFinish: async ({ result }) => {
|
|
105
|
+
await fetch("https://my-api.com/webhook", {
|
|
106
|
+
method: "POST",
|
|
107
|
+
body: JSON.stringify({ done: true }),
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `onError`
|
|
113
|
+
|
|
114
|
+
Runs when the agent encounters an error.
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
onError: async ({ error }) => {
|
|
118
|
+
console.error("Agent failed:", error)
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Permission Modes
|
|
123
|
+
|
|
124
|
+
| Mode | Behavior |
|
|
125
|
+
|------|----------|
|
|
126
|
+
| `"default"` | Agent asks for confirmation on risky operations |
|
|
127
|
+
| `"acceptEdits"` | Auto-approve file edits, still confirm other actions |
|
|
128
|
+
| `"bypassPermissions"` | Auto-approve everything (use with caution) |
|
|
129
|
+
|
|
130
|
+
## Type Reference
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
// Agent config (all fields required — defaults filled by agent())
|
|
134
|
+
interface AgentConfig {
|
|
135
|
+
model: string
|
|
136
|
+
systemPrompt: string
|
|
137
|
+
tools: ToolSet
|
|
138
|
+
runtime: "claude-code" | "codex"
|
|
139
|
+
permissionMode: "default" | "acceptEdits" | "bypassPermissions"
|
|
140
|
+
maxTurns: number
|
|
141
|
+
maxBudgetUsd?: number
|
|
142
|
+
onStart?: () => Promise<void>
|
|
143
|
+
onToolCall?: (payload: { toolName: string; input: any }) => Promise<boolean | void>
|
|
144
|
+
onToolResult?: (payload: { toolName: string; input: any; output: any }) => Promise<void>
|
|
145
|
+
onStepFinish?: (payload: { step: any }) => Promise<void>
|
|
146
|
+
onFinish?: (payload: { result: any }) => Promise<void>
|
|
147
|
+
onError?: (payload: { error: Error }) => Promise<void>
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Tool definition — generic over Zod schema
|
|
151
|
+
interface ToolDefinition<TInput> {
|
|
152
|
+
description: string
|
|
153
|
+
inputSchema: ZodType<TInput>
|
|
154
|
+
execute: (args: TInput) => Promise<{ content: { type: string; text: string }[] }>
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Tool set
|
|
158
|
+
type ToolSet = Record<string, ToolDefinition<any>>
|
|
159
|
+
```
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# React UI
|
|
2
|
+
|
|
3
|
+
`@an-sdk/react` provides a full chat UI for AN agents. Built on [Vercel AI SDK v5](https://sdk.vercel.ai) — uses standard `useChat()` from `@ai-sdk/react`.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @an-sdk/react ai @ai-sdk/react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Basic Usage
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
"use client"
|
|
15
|
+
|
|
16
|
+
import { useChat } from "@ai-sdk/react"
|
|
17
|
+
import { AnAgentChat, createAnChat } from "@an-sdk/react"
|
|
18
|
+
import "@an-sdk/react/styles.css"
|
|
19
|
+
import { useMemo } from "react"
|
|
20
|
+
|
|
21
|
+
export default function Chat() {
|
|
22
|
+
const chat = useMemo(
|
|
23
|
+
() => createAnChat({
|
|
24
|
+
agent: "your-agent-slug",
|
|
25
|
+
getToken: async () => "your_an_sk_token",
|
|
26
|
+
}),
|
|
27
|
+
[],
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const { messages, sendMessage, status, stop, error } = useChat({ chat })
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<AnAgentChat
|
|
34
|
+
messages={messages}
|
|
35
|
+
onSend={(msg) =>
|
|
36
|
+
sendMessage({ parts: [{ type: "text", text: msg.content }] })
|
|
37
|
+
}
|
|
38
|
+
status={status}
|
|
39
|
+
onStop={stop}
|
|
40
|
+
error={error}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## `createAnChat(options)`
|
|
47
|
+
|
|
48
|
+
Creates an AI SDK `Chat` instance pointed at the AN Relay.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
createAnChat({
|
|
52
|
+
agent: string // Agent slug from dashboard
|
|
53
|
+
getToken: () => Promise<string> // Returns an_sk_ key or JWT
|
|
54
|
+
apiUrl?: string // Default: "https://relay.an.dev"
|
|
55
|
+
projectId?: string // Session persistence key
|
|
56
|
+
onFinish?: () => void
|
|
57
|
+
onError?: (error: Error) => void
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For Next.js apps, use `tokenUrl` instead of `getToken` — see [Next.js Integration](./05-nextjs.md).
|
|
62
|
+
|
|
63
|
+
## `<AnAgentChat />` Props
|
|
64
|
+
|
|
65
|
+
| Prop | Type | Description |
|
|
66
|
+
|------|------|-------------|
|
|
67
|
+
| `messages` | `UIMessage[]` | From `useChat()` |
|
|
68
|
+
| `onSend` | `(msg) => void` | Send handler |
|
|
69
|
+
| `status` | `ChatStatus` | `"ready" \| "submitted" \| "streaming" \| "error"` |
|
|
70
|
+
| `onStop` | `() => void` | Stop generation |
|
|
71
|
+
| `error` | `Error` | Error to display |
|
|
72
|
+
| `theme` | `AnTheme` | Theme from playground |
|
|
73
|
+
| `colorMode` | `"light" \| "dark" \| "auto"` | Color mode |
|
|
74
|
+
| `classNames` | `Partial<AnClassNames>` | Per-element CSS overrides |
|
|
75
|
+
| `slots` | `Partial<AnSlots>` | Component swapping |
|
|
76
|
+
| `className` | `string` | Root element class |
|
|
77
|
+
| `style` | `CSSProperties` | Root element style |
|
|
78
|
+
|
|
79
|
+
## Customization
|
|
80
|
+
|
|
81
|
+
Four levels, from simple to full control:
|
|
82
|
+
|
|
83
|
+
### 1. Theme tokens
|
|
84
|
+
|
|
85
|
+
Apply a theme JSON from the [AN Playground](https://an.dev/an/playground):
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
<AnAgentChat theme={playgroundTheme} colorMode="dark" />
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 2. Class overrides
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
<AnAgentChat
|
|
95
|
+
classNames={{
|
|
96
|
+
root: "rounded-2xl border",
|
|
97
|
+
messageList: "px-8",
|
|
98
|
+
inputBar: "bg-gray-50",
|
|
99
|
+
userMessage: "bg-blue-100",
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 3. Slot components
|
|
105
|
+
|
|
106
|
+
Swap sub-components:
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
<AnAgentChat
|
|
110
|
+
slots={{
|
|
111
|
+
InputBar: MyCustomInput,
|
|
112
|
+
UserMessage: MyUserBubble,
|
|
113
|
+
ToolRenderer: MyToolRenderer,
|
|
114
|
+
}}
|
|
115
|
+
/>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 4. Individual component imports
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import { MessageList, InputBar, ToolRenderer } from "@an-sdk/react"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## CSS
|
|
125
|
+
|
|
126
|
+
Import once in your app:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import "@an-sdk/react/styles.css"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
No Tailwind peer dependency — CSS is pre-compiled. All elements have stable `an-*` class names:
|
|
133
|
+
|
|
134
|
+
```css
|
|
135
|
+
.an-root { }
|
|
136
|
+
.an-message-list { }
|
|
137
|
+
.an-user-message { }
|
|
138
|
+
.an-assistant-message { }
|
|
139
|
+
.an-input-bar { }
|
|
140
|
+
.an-send-button { }
|
|
141
|
+
.an-tool-bash { }
|
|
142
|
+
.an-tool-edit { }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Theme Type
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
interface AnTheme {
|
|
149
|
+
theme: Record<string, string> // Shared: font, spacing, accent
|
|
150
|
+
light: Record<string, string> // Light mode CSS vars
|
|
151
|
+
dark: Record<string, string> // Dark mode CSS vars
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## `applyTheme(element, theme, colorMode?)`
|
|
156
|
+
|
|
157
|
+
Manually inject CSS variables from a theme JSON onto a DOM element.
|
|
158
|
+
|
|
159
|
+
## Components
|
|
160
|
+
|
|
161
|
+
| Component | Description |
|
|
162
|
+
|-----------|-------------|
|
|
163
|
+
| `MessageList` | Auto-scrolling message container with grouping |
|
|
164
|
+
| `UserMessage` | User message bubble |
|
|
165
|
+
| `AssistantMessage` | Assistant response with parts routing |
|
|
166
|
+
| `StreamingMarkdown` | Markdown renderer with syntax highlighting |
|
|
167
|
+
| `InputBar` | Text input with send/stop buttons |
|
|
168
|
+
| `MessageActions` | Copy button |
|
|
169
|
+
| `ToolRenderer` | Routes tool parts to the correct component |
|
|
170
|
+
|
|
171
|
+
## Built-in Tool Renderers
|
|
172
|
+
|
|
173
|
+
| Component | Renders |
|
|
174
|
+
|-----------|---------|
|
|
175
|
+
| `BashTool` | Terminal commands with output |
|
|
176
|
+
| `EditTool` | File edits with diff display |
|
|
177
|
+
| `WriteTool` | File creation |
|
|
178
|
+
| `SearchTool` | Web search results |
|
|
179
|
+
| `TodoTool` | Task checklists |
|
|
180
|
+
| `PlanTool` | Step-by-step plans |
|
|
181
|
+
| `TaskTool` | Sub-agent tasks |
|
|
182
|
+
| `McpTool` | MCP protocol calls |
|
|
183
|
+
| `ThinkingTool` | Reasoning/thinking indicator |
|
|
184
|
+
| `GenericTool` | Fallback for unknown tools |
|
|
185
|
+
|
|
186
|
+
See [Custom Tools](./08-custom-tools.md) for building your own tool renderers.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Next.js Integration
|
|
2
|
+
|
|
3
|
+
`@an-sdk/nextjs` provides a server-side token handler so your `an_sk_` API key never reaches the browser. It also re-exports everything from `@an-sdk/react` for convenience.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @an-sdk/nextjs @an-sdk/react ai @ai-sdk/react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
### 1. Set your API key
|
|
14
|
+
|
|
15
|
+
```env
|
|
16
|
+
# .env.local
|
|
17
|
+
AN_API_KEY=an_sk_your_key_here
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Get your API key from [an.dev/agents/dashboard/api](https://an.dev/agents/dashboard/api).
|
|
21
|
+
|
|
22
|
+
### 2. Create the token route
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
// app/api/an/token/route.ts
|
|
26
|
+
import { createAnTokenHandler } from "@an-sdk/nextjs/server"
|
|
27
|
+
|
|
28
|
+
export const POST = createAnTokenHandler({
|
|
29
|
+
apiKey: process.env.AN_API_KEY!,
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 3. Use in your page
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// app/page.tsx
|
|
37
|
+
"use client"
|
|
38
|
+
|
|
39
|
+
import { useChat } from "@ai-sdk/react"
|
|
40
|
+
import { AnAgentChat, createAnChat } from "@an-sdk/nextjs"
|
|
41
|
+
import "@an-sdk/react/styles.css"
|
|
42
|
+
import { useMemo } from "react"
|
|
43
|
+
|
|
44
|
+
export default function Chat() {
|
|
45
|
+
const chat = useMemo(
|
|
46
|
+
() => createAnChat({
|
|
47
|
+
agent: "your-agent-slug",
|
|
48
|
+
tokenUrl: "/api/an/token",
|
|
49
|
+
}),
|
|
50
|
+
[],
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const { messages, sendMessage, status, stop, error } = useChat({ chat })
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<AnAgentChat
|
|
57
|
+
messages={messages}
|
|
58
|
+
onSend={(msg) =>
|
|
59
|
+
sendMessage({ parts: [{ type: "text", text: msg.content }] })
|
|
60
|
+
}
|
|
61
|
+
status={status}
|
|
62
|
+
onStop={stop}
|
|
63
|
+
error={error}
|
|
64
|
+
/>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## How It Works
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
Browser Your Next.js Server AN Relay
|
|
73
|
+
| | |
|
|
74
|
+
|-- POST /api/an/token --------->| |
|
|
75
|
+
| |-- POST /v1/tokens -------->|
|
|
76
|
+
| | (with an_sk_ key) |
|
|
77
|
+
| |<-- { token, expiresAt } ---|
|
|
78
|
+
|<-- { token, expiresAt } ------| |
|
|
79
|
+
| |
|
|
80
|
+
|-- POST /v1/chat/:agent ------(with short-lived JWT)------->|
|
|
81
|
+
|<-- streaming response -----(SSE)---------------------------|
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The client only receives short-lived JWTs. Your API key stays on the server.
|
|
85
|
+
|
|
86
|
+
## API
|
|
87
|
+
|
|
88
|
+
### `createAnTokenHandler(options)`
|
|
89
|
+
|
|
90
|
+
Returns a Next.js `POST` route handler.
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
createAnTokenHandler({
|
|
94
|
+
apiKey: string // Your an_sk_ API key
|
|
95
|
+
relayUrl?: string // Default: "https://relay.an.dev"
|
|
96
|
+
expiresIn?: string // Default: "1h"
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### `exchangeToken(options)`
|
|
101
|
+
|
|
102
|
+
Lower-level function for custom token exchange logic.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { exchangeToken } from "@an-sdk/nextjs/server"
|
|
106
|
+
|
|
107
|
+
const { token, expiresAt } = await exchangeToken({
|
|
108
|
+
apiKey: process.env.AN_API_KEY!,
|
|
109
|
+
relayUrl: "https://relay.an.dev",
|
|
110
|
+
expiresIn: "1h",
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Entry Points
|
|
115
|
+
|
|
116
|
+
- `@an-sdk/nextjs` — Re-exports everything from `@an-sdk/react` (components, types, `createAnChat`)
|
|
117
|
+
- `@an-sdk/nextjs/server` — Server-only: `createAnTokenHandler`, `exchangeToken`
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Node SDK
|
|
2
|
+
|
|
3
|
+
`@an-sdk/node` is a server-side client for the AN API. Use it to manage sandboxes, threads, and tokens programmatically.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @an-sdk/node
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { AnClient } from "@an-sdk/node"
|
|
15
|
+
|
|
16
|
+
const an = new AnClient({
|
|
17
|
+
apiKey: process.env.AN_API_KEY!, // an_sk_...
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
// Create a sandbox for your agent
|
|
21
|
+
const sandbox = await an.sandboxes.create({ agent: "my-agent" })
|
|
22
|
+
|
|
23
|
+
// Create a thread
|
|
24
|
+
const thread = await an.threads.create({
|
|
25
|
+
sandboxId: sandbox.sandboxId,
|
|
26
|
+
name: "Review PR #42",
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// Generate a short-lived token for browser clients
|
|
30
|
+
const { token, expiresAt } = await an.tokens.create({
|
|
31
|
+
agent: "my-agent",
|
|
32
|
+
expiresIn: "1h",
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## `AnClient`
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
new AnClient({
|
|
40
|
+
apiKey: string // Your an_sk_ API key
|
|
41
|
+
baseUrl?: string // Default: "https://relay.an.dev"
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Resources
|
|
46
|
+
|
|
47
|
+
### `client.sandboxes`
|
|
48
|
+
|
|
49
|
+
| Method | Description |
|
|
50
|
+
|--------|-------------|
|
|
51
|
+
| `create({ agent })` | Create a new sandbox for an agent |
|
|
52
|
+
| `get(sandboxId)` | Get sandbox details (status, threads, agent info) |
|
|
53
|
+
| `delete(sandboxId)` | Delete a sandbox |
|
|
54
|
+
|
|
55
|
+
### `client.threads`
|
|
56
|
+
|
|
57
|
+
| Method | Description |
|
|
58
|
+
|--------|-------------|
|
|
59
|
+
| `list({ sandboxId })` | List all threads in a sandbox |
|
|
60
|
+
| `create({ sandboxId, name? })` | Create a new thread |
|
|
61
|
+
| `get({ sandboxId, threadId })` | Get thread with messages |
|
|
62
|
+
| `delete({ sandboxId, threadId })` | Delete a thread |
|
|
63
|
+
|
|
64
|
+
### `client.tokens`
|
|
65
|
+
|
|
66
|
+
| Method | Description |
|
|
67
|
+
|--------|-------------|
|
|
68
|
+
| `create({ agent?, userId?, expiresIn? })` | Create a short-lived JWT |
|
|
69
|
+
|
|
70
|
+
Default `expiresIn` is `"1h"`.
|
|
71
|
+
|
|
72
|
+
## Types
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
interface Sandbox {
|
|
76
|
+
id: string
|
|
77
|
+
sandboxId: string
|
|
78
|
+
status: string
|
|
79
|
+
createdAt: string
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface SandboxDetail {
|
|
83
|
+
id: string
|
|
84
|
+
sandboxId: string
|
|
85
|
+
status: string
|
|
86
|
+
error?: string | null
|
|
87
|
+
agent: { slug: string; name: string }
|
|
88
|
+
threads: ThreadSummary[]
|
|
89
|
+
createdAt: string
|
|
90
|
+
updatedAt: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface ThreadSummary {
|
|
94
|
+
id: string
|
|
95
|
+
name?: string | null
|
|
96
|
+
status: string
|
|
97
|
+
createdAt: string
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface Thread {
|
|
101
|
+
id: string
|
|
102
|
+
name?: string | null
|
|
103
|
+
status: string
|
|
104
|
+
messages?: unknown
|
|
105
|
+
createdAt: string
|
|
106
|
+
updatedAt: string
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface Token {
|
|
110
|
+
token: string
|
|
111
|
+
expiresAt: string
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Error Handling
|
|
116
|
+
|
|
117
|
+
All methods throw on non-2xx responses. The error message comes from the API response body when available.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
try {
|
|
121
|
+
const sandbox = await an.sandboxes.get("nonexistent")
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.error(err.message) // "Sandbox not found" or similar
|
|
124
|
+
}
|
|
125
|
+
```
|
package/docs/07-cli.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# CLI Reference
|
|
2
|
+
|
|
3
|
+
`@an-sdk/cli` provides the `an` command for deploying agents.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @an-sdk/cli
|
|
9
|
+
# or use npx
|
|
10
|
+
npx @an-sdk/cli <command>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
### `an login`
|
|
16
|
+
|
|
17
|
+
Authenticate with the AN platform.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx @an-sdk/cli login
|
|
21
|
+
# Enter your API key: an_sk_...
|
|
22
|
+
# Authenticated as John (team: my-team)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Your key is saved to `~/.an/credentials`.
|
|
26
|
+
|
|
27
|
+
### `an deploy`
|
|
28
|
+
|
|
29
|
+
Bundle and deploy your agent.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx @an-sdk/cli deploy
|
|
33
|
+
# Bundling src/agent.ts...
|
|
34
|
+
# Bundled (12.3kb)
|
|
35
|
+
# Deploying my-agent...
|
|
36
|
+
# https://api.an.dev/v1/chat/my-agent
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The CLI:
|
|
40
|
+
1. Finds your entry point (see detection order below)
|
|
41
|
+
2. Bundles your code + dependencies with esbuild
|
|
42
|
+
3. Deploys to a secure cloud sandbox
|
|
43
|
+
4. Returns your agent's URL
|
|
44
|
+
|
|
45
|
+
## Entry Point Detection
|
|
46
|
+
|
|
47
|
+
The CLI looks for your agent file in this order:
|
|
48
|
+
|
|
49
|
+
1. `src/agent.ts`
|
|
50
|
+
2. `src/index.ts`
|
|
51
|
+
3. `agent.ts`
|
|
52
|
+
4. `index.ts`
|
|
53
|
+
|
|
54
|
+
Your entry file must `export default agent(...)`.
|
|
55
|
+
|
|
56
|
+
## Project Linking
|
|
57
|
+
|
|
58
|
+
After first deploy, the CLI saves `.an/project.json` in your project directory:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"agentId": "abc123",
|
|
63
|
+
"slug": "my-agent"
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Subsequent deploys update the existing agent.
|
|
68
|
+
|
|
69
|
+
## Bundling
|
|
70
|
+
|
|
71
|
+
The CLI uses esbuild to bundle your agent code:
|
|
72
|
+
|
|
73
|
+
- Target: Node 22, ESM
|
|
74
|
+
- `@an-sdk/agent` is externalized (provided by the sandbox runtime)
|
|
75
|
+
- All other dependencies are bundled into a single file
|
|
76
|
+
|
|
77
|
+
## Configuration Files
|
|
78
|
+
|
|
79
|
+
| File | Location | Purpose |
|
|
80
|
+
|------|----------|---------|
|
|
81
|
+
| `~/.an/credentials` | Global | API key (`{ "apiKey": "an_sk_..." }`) |
|
|
82
|
+
| `.an/project.json` | Per-project | Agent ID and slug for redeployment |
|
|
83
|
+
|
|
84
|
+
## Environment Variables
|
|
85
|
+
|
|
86
|
+
| Variable | Default | Description |
|
|
87
|
+
|----------|---------|-------------|
|
|
88
|
+
| `AN_API_URL` | `https://an.dev/api/v1` | Override API endpoint |
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Custom Tool Renderers
|
|
2
|
+
|
|
3
|
+
The `@an-sdk/react` chat UI automatically renders tool calls from your agent. It includes built-in renderers for all standard Claude tools and supports custom renderers for your own tools.
|
|
4
|
+
|
|
5
|
+
## Built-in Tool Renderers
|
|
6
|
+
|
|
7
|
+
These are rendered automatically when your agent uses standard tools:
|
|
8
|
+
|
|
9
|
+
| Tool | Renderer | Display |
|
|
10
|
+
|------|----------|---------|
|
|
11
|
+
| `Bash` | `BashTool` | Terminal card with command and output |
|
|
12
|
+
| `Edit` | `EditTool` | Diff card with file path and changes |
|
|
13
|
+
| `Write` | `WriteTool` | File creation card |
|
|
14
|
+
| `WebSearch` | `SearchTool` | Search results list |
|
|
15
|
+
| `TodoWrite` | `TodoTool` | Task checklist with progress |
|
|
16
|
+
| `EnterPlanMode` | `PlanTool` | Step list with progress bar |
|
|
17
|
+
| `Task` | `TaskTool` | Sub-agent task with nested tools |
|
|
18
|
+
| `mcp__*` | `McpTool` | MCP tool call with params and output |
|
|
19
|
+
| `thinking` | `ThinkingTool` | Collapsible reasoning block |
|
|
20
|
+
| Other | `GenericTool` | Fallback JSON display |
|
|
21
|
+
|
|
22
|
+
## Custom Tool Renderers via Slots
|
|
23
|
+
|
|
24
|
+
To render your custom tools differently, use the `slots.ToolRenderer` prop:
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { AnAgentChat, ToolRenderer } from "@an-sdk/react"
|
|
28
|
+
import type { ToolPart } from "@an-sdk/react"
|
|
29
|
+
|
|
30
|
+
function MyToolRenderer(props: { part: ToolPart; status: string }) {
|
|
31
|
+
const { part, status } = props
|
|
32
|
+
|
|
33
|
+
// Handle your custom tool
|
|
34
|
+
if (part.toolInvocation.toolName === "weather") {
|
|
35
|
+
const args = part.toolInvocation.args
|
|
36
|
+
const result = part.toolInvocation.state === "result"
|
|
37
|
+
? part.toolInvocation.result
|
|
38
|
+
: null
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className="weather-card">
|
|
42
|
+
<h3>Weather for {args.city}</h3>
|
|
43
|
+
{result ? <p>{result.content[0].text}</p> : <p>Loading...</p>}
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Fall back to default renderer for standard tools
|
|
49
|
+
return <ToolRenderer part={part} status={status} />
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
<AnAgentChat
|
|
53
|
+
slots={{ ToolRenderer: MyToolRenderer }}
|
|
54
|
+
// ... other props
|
|
55
|
+
/>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## MCP Tool Naming
|
|
59
|
+
|
|
60
|
+
MCP (Model Context Protocol) tools follow the naming pattern `mcp__<server>__<tool>`. The built-in `McpTool` renderer parses this automatically and shows the server name and tool name.
|
|
61
|
+
|
|
62
|
+
## Tool States
|
|
63
|
+
|
|
64
|
+
Each tool invocation has a state:
|
|
65
|
+
|
|
66
|
+
| State | Meaning |
|
|
67
|
+
|-------|---------|
|
|
68
|
+
| `"call"` | Tool has been called, waiting for execution |
|
|
69
|
+
| `"result"` | Tool has returned a result |
|
|
70
|
+
|
|
71
|
+
During streaming, tools may be in `"call"` state before transitioning to `"result"`.
|
|
72
|
+
|
|
73
|
+
## CSS Classes
|
|
74
|
+
|
|
75
|
+
All tool renderers have stable CSS class names for custom styling:
|
|
76
|
+
|
|
77
|
+
```css
|
|
78
|
+
.an-tool-bash { }
|
|
79
|
+
.an-tool-edit { }
|
|
80
|
+
.an-tool-write { }
|
|
81
|
+
.an-tool-search { }
|
|
82
|
+
.an-tool-todo { }
|
|
83
|
+
.an-tool-plan { }
|
|
84
|
+
.an-tool-task { }
|
|
85
|
+
.an-tool-mcp { }
|
|
86
|
+
.an-tool-thinking { }
|
|
87
|
+
.an-tool-generic { }
|
|
88
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@an-sdk/nextjs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Next.js integration for AN AI agent chat — server-side token handler",
|
|
6
6
|
"homepage": "https://an.dev",
|
|
@@ -32,9 +32,16 @@
|
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
34
|
"dist",
|
|
35
|
+
"docs/**/*",
|
|
36
|
+
"src",
|
|
37
|
+
"!src/**/*.test.ts",
|
|
38
|
+
"AGENTS.md",
|
|
35
39
|
"LICENSE",
|
|
36
40
|
"README.md"
|
|
37
41
|
],
|
|
42
|
+
"directories": {
|
|
43
|
+
"doc": "./docs"
|
|
44
|
+
},
|
|
38
45
|
"keywords": [
|
|
39
46
|
"nextjs",
|
|
40
47
|
"react",
|
|
@@ -46,6 +53,8 @@
|
|
|
46
53
|
"an.dev"
|
|
47
54
|
],
|
|
48
55
|
"scripts": {
|
|
56
|
+
"prepack": "cp -r ../docs ./docs",
|
|
57
|
+
"postpack": "rm -rf ./docs",
|
|
49
58
|
"build": "tsup",
|
|
50
59
|
"lint": "tsc --noEmit"
|
|
51
60
|
},
|
|
@@ -54,7 +63,7 @@
|
|
|
54
63
|
"react": "^18.0.0 || ^19.0.0",
|
|
55
64
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
56
65
|
"@ai-sdk/react": "^2.0.0",
|
|
57
|
-
"@an-sdk/react": "^0.0.
|
|
66
|
+
"@an-sdk/react": "^0.0.5",
|
|
58
67
|
"ai": "^5.0.0"
|
|
59
68
|
},
|
|
60
69
|
"devDependencies": {
|
package/src/index.ts
ADDED
package/src/server.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export interface TokenHandlerOptions {
|
|
2
|
+
/** Your an_sk_ API key — keep in process.env, never expose to client */
|
|
3
|
+
apiKey: string
|
|
4
|
+
/** Relay URL. Default: "https://relay.an.dev" */
|
|
5
|
+
relayUrl?: string
|
|
6
|
+
/** Token expiry. Default: "1h" */
|
|
7
|
+
expiresIn?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ExchangeTokenOptions extends TokenHandlerOptions {
|
|
11
|
+
/** Agent slug to scope the token to */
|
|
12
|
+
agent?: string
|
|
13
|
+
/** User identifier for the token */
|
|
14
|
+
userId?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Exchange an an_sk_ API key for a short-lived JWT via the relay */
|
|
18
|
+
export async function exchangeToken(options: ExchangeTokenOptions) {
|
|
19
|
+
const {
|
|
20
|
+
apiKey,
|
|
21
|
+
relayUrl = "https://relay.an.dev",
|
|
22
|
+
expiresIn = "1h",
|
|
23
|
+
agent,
|
|
24
|
+
userId,
|
|
25
|
+
} = options
|
|
26
|
+
|
|
27
|
+
const res = await fetch(`${relayUrl}/v1/tokens`, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: {
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
Authorization: `Bearer ${apiKey}`,
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify({
|
|
34
|
+
userId,
|
|
35
|
+
agents: agent ? [agent] : undefined,
|
|
36
|
+
expiresIn,
|
|
37
|
+
}),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
const err = await res.json().catch(() => ({ error: "Failed to exchange token" }))
|
|
42
|
+
throw new Error(err.error || `Token exchange failed: ${res.status}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return res.json() as Promise<{ token: string; expiresAt: string }>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Create a Next.js API route handler that exchanges an_sk_ keys for JWTs */
|
|
49
|
+
export function createAnTokenHandler(options: TokenHandlerOptions) {
|
|
50
|
+
return async function POST(req: Request) {
|
|
51
|
+
try {
|
|
52
|
+
const body = await req.json().catch(() => ({}))
|
|
53
|
+
const { agent, userId } = body as { agent?: string; userId?: string }
|
|
54
|
+
|
|
55
|
+
const data = await exchangeToken({ ...options, agent, userId })
|
|
56
|
+
return Response.json(data)
|
|
57
|
+
} catch (err) {
|
|
58
|
+
const message = err instanceof Error ? err.message : "Internal error"
|
|
59
|
+
return Response.json({ error: message }, { status: 500 })
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|