dopus 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +286 -2
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +977 -0
- package/dist/index.d.ts +154 -0
- package/dist/index.js +278 -0
- package/dist/version-DOEBWDfi.js +31 -0
- package/package.json +75 -4
- package/templates/prompts/app.ts +18 -0
- package/templates/prompts/presets/customer-support.ts +13 -0
- package/templates/prompts/presets/general.ts +12 -0
- package/templates/prompts/presets/internal-ops.ts +13 -0
- package/templates/ui/agent-chat.module.css +255 -0
- package/templates/ui/agent-chat.tsx +294 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 akodm
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,287 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dopus
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Drop an AI agent into your existing Next.js app. Your server functions become its tools.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm add dopus
|
|
7
|
+
pnpm dopus init
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
- **Reuse what you have.** Register the server actions and functions your app already has. You don't write a second API for the agent.
|
|
11
|
+
- **Permissions come for free.** Tools run inside the agent's request, so `cookies()` and `headers()` in your server functions see the signed-in user. Your own auth and ownership checks keep working unchanged.
|
|
12
|
+
- **Writes wait for the user.** Mark a tool `confirm: true` and the user approves each call in the chat. Approvals are signed so a client cannot forge one.
|
|
13
|
+
- **You own the generated code.** `init` copies the route, prompts and chat UI into your project, shadcn-style. Only the runtime lives in the package.
|
|
14
|
+
|
|
15
|
+
Built on the [Vercel AI SDK](https://ai-sdk.dev) 7. Supports OpenAI, Anthropic and Google models.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
Requirements: Next.js App Router, TypeScript, Node.js 22+, zod 4.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pnpm add dopus # or npm / yarn / bun
|
|
25
|
+
pnpm dopus init
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`init` asks for a provider, an API key and a prompt preset, then:
|
|
29
|
+
|
|
30
|
+
| Writes | What it is |
|
|
31
|
+
| --------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
32
|
+
| `.env.local` | Your API key and a generated `DOPUS_APPROVAL_SECRET`. Never `.env`. Mode 600. |
|
|
33
|
+
| `agent.config.ts` | Model, tools and prompt layers. Edit this. |
|
|
34
|
+
| `agent/prompts/preset.ts`, `agent/prompts/app.ts` | Your prompt layers. Yours to edit. |
|
|
35
|
+
| `app/api/agent/route.ts` | `POST /api/agent`. It speaks the AI SDK `useChat` protocol. |
|
|
36
|
+
| `components/agent/agent-chat.tsx` (+ `.module.css`) | Chat panel and floating widget. |
|
|
37
|
+
|
|
38
|
+
It also installs whatever is missing of `ai`, `zod`, `@ai-sdk/react` and the provider package. It checks that `.env.local` is git-ignored.
|
|
39
|
+
|
|
40
|
+
`init` is safe to run again. Existing files and env values are left alone unless you pass `--force` and confirm.
|
|
41
|
+
|
|
42
|
+
Then add the widget to your root layout:
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
// app/layout.tsx
|
|
46
|
+
import { AgentWidget } from '../components/agent/agent-chat'
|
|
47
|
+
|
|
48
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
49
|
+
return (
|
|
50
|
+
<html lang="en">
|
|
51
|
+
<body>
|
|
52
|
+
{children}
|
|
53
|
+
<AgentWidget suggestions={['What are my open orders?']} />
|
|
54
|
+
</body>
|
|
55
|
+
</html>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### CLI options
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
dopus init [--provider openai|anthropic|google] [--model <id>]
|
|
64
|
+
[--preset general|customer-support|internal-ops]
|
|
65
|
+
[--yes] [--no-install] [--force] [--cwd <dir>]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`--yes` runs without prompts. It needs `--provider` and skips the API key prompt, so add the key to `.env.local` yourself afterwards.
|
|
69
|
+
|
|
70
|
+
## Tools
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
// agent.config.ts
|
|
74
|
+
import { anthropic } from '@ai-sdk/anthropic'
|
|
75
|
+
import { defineAgent, defineTool } from 'dopus'
|
|
76
|
+
import { z } from 'zod'
|
|
77
|
+
import { cancelOrder, listMyOrders } from './app/actions/orders' // your existing server actions
|
|
78
|
+
import app, { locales } from './agent/prompts/app'
|
|
79
|
+
import preset from './agent/prompts/preset'
|
|
80
|
+
|
|
81
|
+
export default defineAgent({
|
|
82
|
+
model: anthropic('claude-sonnet-5-5'),
|
|
83
|
+
tools: {
|
|
84
|
+
listMyOrders: defineTool(() => listMyOrders(), {
|
|
85
|
+
description: "Lists the signed-in user's orders with id, item and status.",
|
|
86
|
+
usage: 'When the user asks about their orders.',
|
|
87
|
+
input: z.object({}),
|
|
88
|
+
}),
|
|
89
|
+
cancelOrder: defineTool((input) => cancelOrder(input.orderId), {
|
|
90
|
+
description: 'Cancels one open order of the signed-in user.',
|
|
91
|
+
input: z.object({ orderId: z.string() }),
|
|
92
|
+
confirm: true, // the user approves each call in the chat
|
|
93
|
+
}),
|
|
94
|
+
},
|
|
95
|
+
prompt: { preset, app, locales },
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`defineTool(fn, options)` calls `fn(input, ctx)`. If your function takes positional arguments, adapt it inline: `(input) => cancelOrder(input.orderId)`.
|
|
100
|
+
|
|
101
|
+
| Option | |
|
|
102
|
+
| ------------- | -------------------------------------------------------------------------------------------------------- |
|
|
103
|
+
| `description` | **Required.** What the tool does. The model reads it. |
|
|
104
|
+
| `usage` | When the tool should, or should not, be used. Appended to the description. |
|
|
105
|
+
| `input` | **Required.** A zod schema. The model's arguments are validated before `fn` runs. |
|
|
106
|
+
| `confirm` | `true`, or `(input, ctx) => boolean`, to ask the user before each call. Use it for anything that writes. |
|
|
107
|
+
| `output` | `(result) => unknown`. Trims the result before the model sees it, for example to drop internal fields. |
|
|
108
|
+
|
|
109
|
+
`ctx` is `{ user, toolCallId, abortSignal }`. `user` is only set when you configure `identify` (see below).
|
|
110
|
+
|
|
111
|
+
### Errors
|
|
112
|
+
|
|
113
|
+
Only two kinds of error message reach the model:
|
|
114
|
+
|
|
115
|
+
- a `ToolError` you throw on purpose, such as `throw new ToolError('Order o-1 has already shipped')`;
|
|
116
|
+
- Next.js `redirect()`, `notFound()`, `forbidden()` and `unauthorized()`, which are translated into plain messages such as "the user is not signed in".
|
|
117
|
+
|
|
118
|
+
Any other error goes to `onError` and is replaced with a generic message, so database errors and stack details stay on the server. The browser only ever sees a generic error text.
|
|
119
|
+
|
|
120
|
+
## Permissions and security
|
|
121
|
+
|
|
122
|
+
**How permission inheritance works.** `createAgentHandler` runs the whole tool loop inside the `POST /api/agent` request. A server function called as a tool therefore sees that request's cookies and headers, just as it does when your own UI calls it. The repo's end-to-end test checks this on a real `next start` server. It confirms that tools read the caller's session cookie, return only the caller's data, and still apply the app's ownership check after an approval.
|
|
123
|
+
|
|
124
|
+
**What stays your job.**
|
|
125
|
+
|
|
126
|
+
- Authorize inside your server functions, as you already should. dopus does not add permissions; it inherits yours.
|
|
127
|
+
- Treat the agent like any other client. A user can only make it do what that user could already do through your server functions.
|
|
128
|
+
- Register only the functions you want the agent to call.
|
|
129
|
+
- Protect the route like your other API routes, with rate limits and auth if anonymous use costs you money.
|
|
130
|
+
|
|
131
|
+
**Approvals.** With `DOPUS_APPROVAL_SECRET` set (`init` generates it), every approval request is HMAC-signed. An approval with a missing or tampered signature is rejected and the tool does not run.
|
|
132
|
+
|
|
133
|
+
**Browser context is untrusted.** The chat UI sends `{ page, locale }` so the agent knows where the user is. The server accepts only those two keys, strips control characters and caps their length. Anything that matters for authorization, such as role, plan or tenant, must come from the server via `runtime`.
|
|
134
|
+
|
|
135
|
+
**Running tools outside the request.** Background jobs and queues lose the request's cookies. For that case, define `identify(request)` to resolve a serialisable user. Tools then receive it as `ctx.user` and can pass it on explicitly. Infra adapters that run tools elsewhere will build on this.
|
|
136
|
+
|
|
137
|
+
## Prompts
|
|
138
|
+
|
|
139
|
+
dopus builds the system prompt in four layers, ordered from stable to volatile so that providers can cache the prefix:
|
|
140
|
+
|
|
141
|
+
1. **Core**: about 15 lines of tool-use and safety rules, shipped in the package. Replace it with `prompt.core = '...'` or drop it with `false`. It is exported as `CORE_PROMPT`.
|
|
142
|
+
2. **Preset**: `agent/prompts/preset.ts`, copied by `init`. The presets are `general`, `customer-support` and `internal-ops`. Edit it freely.
|
|
143
|
+
3. **App context**: `agent/prompts/app.ts`, a description of your service. You write this.
|
|
144
|
+
4. **Runtime**: per-request facts. These are the browser's `locale` and `page`, plus whatever `runtime` returns.
|
|
145
|
+
|
|
146
|
+
Write your prompts once, in one language. The agent answers in the request's locale. For per-language tone or terminology, add short notes to `locales` (`{ ko: '존댓말을 사용하세요.' }`). Only the note that matches the request locale is included.
|
|
147
|
+
|
|
148
|
+
Anthropic gets an explicit cache breakpoint after layer 3. OpenAI and Gemini cache prompt prefixes automatically.
|
|
149
|
+
|
|
150
|
+
## Chat UI
|
|
151
|
+
|
|
152
|
+
`components/agent/agent-chat.tsx` exports two components:
|
|
153
|
+
|
|
154
|
+
- `<AgentChat />`: the panel;
|
|
155
|
+
- `<AgentWidget />`: a floating launcher that opens the panel.
|
|
156
|
+
|
|
157
|
+
Both accept `api`, `suggestions`, `labels` (every UI string, for translation) and `className`.
|
|
158
|
+
|
|
159
|
+
The UI shows each tool call as it runs. It renders an approve/decline card for confirmed tools and continues automatically once the user answers. It also has stop and retry buttons, and it does not send on Enter while an IME (Korean, Japanese, Chinese) is still composing.
|
|
160
|
+
|
|
161
|
+
Styling is a plain CSS module themed through `--dopus-*` variables, with dark mode included. It needs no Tailwind and no component library. The file is yours, so restyle it or swap in your own components.
|
|
162
|
+
|
|
163
|
+
## Configuration
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
defineAgent({
|
|
167
|
+
model, // any AI SDK LanguageModel, e.g. openai('gpt-5.5')
|
|
168
|
+
tools, // { name: defineTool(...) }; names match [a-zA-Z0-9_-]{1,64}
|
|
169
|
+
prompt: { core, preset, app, locales },
|
|
170
|
+
runtime: async (request, { user, client }) => ({ role: 'admin' }),
|
|
171
|
+
identify: async (request) => ({ id: 'user-1' }), // optional
|
|
172
|
+
maxSteps: 10, // model ↔ tool round trips per request
|
|
173
|
+
approvalSecret, // default: process.env.DOPUS_APPROVAL_SECRET
|
|
174
|
+
callOptions: {
|
|
175
|
+
temperature,
|
|
176
|
+
maxOutputTokens,
|
|
177
|
+
providerOptions,
|
|
178
|
+
maxRetries,
|
|
179
|
+
headers,
|
|
180
|
+
onEnd,
|
|
181
|
+
onStepEnd,
|
|
182
|
+
},
|
|
183
|
+
onError: (error) => {}, // default: console.error
|
|
184
|
+
})
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Deploying
|
|
188
|
+
|
|
189
|
+
**Vercel / any Node server.** Nothing extra. The route streams. Raise `maxDuration` in `app/api/agent/route.ts` if your plan allows it.
|
|
190
|
+
|
|
191
|
+
**AWS (OpenNext / SST).**
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
pnpm dopus infra aws
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The command writes `open-next.config.ts` with Lambda response streaming (`aws-lambda-streaming`) and installs `@opennextjs/aws`. It also prints an `sst.aws.Nextjs` snippet that passes your API key and `DOPUS_APPROVAL_SECRET` as SST secrets.
|
|
198
|
+
|
|
199
|
+
SST reads OpenNext's streaming flag and sets the Function URL to `RESPONSE_STREAM`. Nothing is deployed for you.
|
|
200
|
+
|
|
201
|
+
Things to know:
|
|
202
|
+
|
|
203
|
+
- **Keep-alive.** While a tool runs and nothing else is sent, dopus writes an SSE comment (`: keep-alive`) every 15 s (`keepAlive` option). Clients ignore it. CloudFront, ALB and nginx time out on gaps between packets, not on total time, so a slow tool does not cut the stream.
|
|
204
|
+
- **Turn length.** A turn still has to finish within the Lambda timeout. Behind CloudFront that is 60 s by default (up to 180 s with a quota increase). A bare Function URL allows up to 15 min.
|
|
205
|
+
- **No API Gateway.** Don't put API Gateway HTTP APIs in front of `/api/agent`. They cap requests at 30 s and buffer the response.
|
|
206
|
+
- **Long-running work.** Work that outlives a request, such as queues and background jobs, loses the request's cookies. Use `identify` and pass `ctx.user` on explicitly.
|
|
207
|
+
|
|
208
|
+
`pnpm aws:emu` in this repo builds the example with OpenNext and invokes the Lambda handler locally through a stand-in for the streaming runtime. It checks that the response streams and that tools still read the caller's cookie.
|
|
209
|
+
|
|
210
|
+
**Cloudflare Workers (OpenNext).**
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
pnpm dopus infra cloudflare
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The command writes `open-next.config.ts` (`defineCloudflareConfig`), `wrangler.jsonc` (`nodejs_compat`) and `.dev.vars`. It also adds `cf:preview` and `cf:deploy` scripts, git-ignores `.open-next`, `.dev.vars` and `.wrangler`, and installs `@opennextjs/cloudflare` and `wrangler`.
|
|
217
|
+
|
|
218
|
+
Wrangler reads `.dev.vars` instead of `.env.local`. So the command offers to copy your API key and `DOPUS_APPROVAL_SECRET` into `.dev.vars` for the local preview. The values are never printed.
|
|
219
|
+
|
|
220
|
+
For production, set the same two values with `npx wrangler secret put`.
|
|
221
|
+
|
|
222
|
+
Things to know:
|
|
223
|
+
|
|
224
|
+
- **No wall-clock limit.** A Worker can keep streaming as long as the client stays connected, and waiting on the LLM does not count as CPU time.
|
|
225
|
+
- **CPU time.** Rendering and tool code do count toward CPU time. Paid plans allow 30 s by default and up to 5 min with `limits.cpu_ms`. The free plan allows 10 ms per request, which a Next.js app usually exceeds.
|
|
226
|
+
- **Node runtime.** OpenNext runs the Node.js runtime, so remove `export const runtime = 'edge'`.
|
|
227
|
+
|
|
228
|
+
`pnpm cf:e2e` in this repo copies the example and runs `dopus infra cloudflare` on it. It then builds the copy, serves it with `wrangler dev` (local workerd) and runs the same e2e suite against it, covering cookies, ownership checks and signed approvals.
|
|
229
|
+
|
|
230
|
+
**Google Cloud Run.**
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
pnpm dopus infra gcp
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The command sets `output: 'standalone'` in `next.config` (it asks first). It writes a multi-stage `Dockerfile` that pins your pnpm or yarn version through corepack. It also writes a `.dockerignore` that keeps `.env*` and `.dev.vars` out of the image.
|
|
237
|
+
|
|
238
|
+
It then prints the deploy commands: Secret Manager setup, and `gcloud run deploy --source . --timeout 3600 --set-secrets ...`.
|
|
239
|
+
|
|
240
|
+
Things to know:
|
|
241
|
+
|
|
242
|
+
- **Timeout.** A request may last up to 60 min (`--timeout 3600`; the default is 5 min). The timeout covers the whole streamed reply.
|
|
243
|
+
- **Longer work.** Use Cloud Run Jobs and pass the user explicitly (`identify` → `ctx.user`), because there are no cookies outside the request.
|
|
244
|
+
- **Build-time variables.** `.env` files are not copied into the image. If your build needs `NEXT_PUBLIC_*` values, pass them as build args.
|
|
245
|
+
|
|
246
|
+
`pnpm gcp:e2e` in this repo copies the example and runs `dopus infra gcp` on it. It builds the image with Docker and runs it the way Cloud Run does (`PORT=8080`, secrets as env vars). It checks that a canary secret from `.env.local` is not in the image, then runs the e2e suite against the container.
|
|
247
|
+
|
|
248
|
+
## Providers
|
|
249
|
+
|
|
250
|
+
| Provider | Package | API key env | Default model |
|
|
251
|
+
| --------- | ------------------- | ------------------------------ | ------------------- |
|
|
252
|
+
| OpenAI | `@ai-sdk/openai` | `OPENAI_API_KEY` | `gpt-5.5` |
|
|
253
|
+
| Anthropic | `@ai-sdk/anthropic` | `ANTHROPIC_API_KEY` | `claude-sonnet-5-5` |
|
|
254
|
+
| Google | `@ai-sdk/google` | `GOOGLE_GENERATIVE_AI_API_KEY` | `gemini-3.5-flash` |
|
|
255
|
+
|
|
256
|
+
To switch providers or models, change `model` in `agent.config.ts`. Any AI SDK model works there.
|
|
257
|
+
|
|
258
|
+
## Testing without an API key
|
|
259
|
+
|
|
260
|
+
Swap in a scripted `MockLanguageModelV4` from `ai/test` behind an environment variable. [`examples/next-app`](https://github.com/akodm/dopus/tree/main/examples/next-app) does this with `DOPUS_MOCK_MODEL=1`. Its e2e tests drive the real route through `next start` without calling any LLM.
|
|
261
|
+
|
|
262
|
+
## Status and roadmap
|
|
263
|
+
|
|
264
|
+
dopus is pre-1.0, so expect breaking changes before 1.0. It is tested on Next.js 16 with the Node.js runtime.
|
|
265
|
+
|
|
266
|
+
Planned next:
|
|
267
|
+
|
|
268
|
+
- `dopus prompts diff` to compare your copies with newer templates;
|
|
269
|
+
- tool input schemas inferred from TypeScript types.
|
|
270
|
+
|
|
271
|
+
## Development
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
pnpm install
|
|
275
|
+
pnpm build && pnpm lint && pnpm typecheck && pnpm test
|
|
276
|
+
pnpm example:build && pnpm example:e2e # next start + scripted model
|
|
277
|
+
pnpm smoke # packed tarball → fresh app → dopus init → next build
|
|
278
|
+
pnpm aws:emu # OpenNext build → local Lambda streaming invoke
|
|
279
|
+
pnpm cf:e2e # dopus infra cloudflare → wrangler dev (workerd) → e2e
|
|
280
|
+
pnpm gcp:e2e # dopus infra gcp → docker build/run (Cloud Run style) → e2e
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Design notes: [`docs/design.md`](docs/design.md).
|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
MIT
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|