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 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
- # Temporary Holding Version
1
+ # dopus
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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 {}