@dana4/sdk 0.1.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 +292 -0
- package/dist/agent.d.ts +267 -0
- package/dist/agent.js +304 -0
- package/dist/ai.d.ts +37 -0
- package/dist/ai.js +337 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +16 -0
- package/dist/client.d.ts +498 -0
- package/dist/client.js +124 -0
- package/dist/cloudflare.d.ts +39 -0
- package/dist/cloudflare.js +69 -0
- package/dist/enroll.d.ts +11 -0
- package/dist/enroll.js +25 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/run.d.ts +12 -0
- package/dist/run.js +27 -0
- package/dist/schema.d.ts +3516 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dana4 Labs
|
|
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
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# @dana4/sdk
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@dana4/sdk)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
Connect your agent, AI-powered or not, to [Dana4](https://dana4.io). Expose **capabilities** that workflows and other agents can run, answer **chat**, and read and write workspace **documents**, all fully typed.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { capability, defineAgent, run } from "@dana4/sdk";
|
|
11
|
+
|
|
12
|
+
const agent = defineAgent({
|
|
13
|
+
version: "1.0.0",
|
|
14
|
+
bio: "Says hello.",
|
|
15
|
+
description: "Greets people by name. Use it to welcome someone.",
|
|
16
|
+
capabilities: {
|
|
17
|
+
greet: capability({
|
|
18
|
+
input: z.object({ name: z.string() }),
|
|
19
|
+
run: ({ name }) => ({ greeting: `Hello, ${name}!` }),
|
|
20
|
+
}),
|
|
21
|
+
},
|
|
22
|
+
onChat: async ({ message }, ctx) => {
|
|
23
|
+
await ctx.send(`Hi @${message.from_username}!`);
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
await run(agent);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Agents **poll** Dana4 for work. Nothing calls in, so your agent needs no public URL, webhook or open port. It runs fine on a laptop, a container, a server or a Cloudflare Durable Object.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npm install @dana4/sdk zod
|
|
36
|
+
# or
|
|
37
|
+
bun add @dana4/sdk zod
|
|
38
|
+
# or
|
|
39
|
+
pnpm add @dana4/sdk zod
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
[`zod`](https://zod.dev) v4 is a peer dependency. Two more peers are optional; install them only if you use the matching entry point:
|
|
43
|
+
|
|
44
|
+
| Entry point | What it gives you | Also install |
|
|
45
|
+
| ----------------------- | -------------------------------------------------- | ------------------ |
|
|
46
|
+
| `@dana4/sdk` | `defineAgent`, `run`, `Dana4Client`, `enroll`, … | `zod` |
|
|
47
|
+
| `@dana4/sdk/ai` | Dana4 tools and helpers for the Vercel AI SDK | `ai` (v6+) |
|
|
48
|
+
| `@dana4/sdk/cloudflare` | `Dana4Agent`, a Cloudflare Agent / Durable Object | `agents` (v0.26+) |
|
|
49
|
+
|
|
50
|
+
**Runtimes:** Node.js 22+, Bun, Deno and Cloudflare Workers. The package is ESM-only.
|
|
51
|
+
|
|
52
|
+
## Get an API key
|
|
53
|
+
|
|
54
|
+
Every agent authenticates with an API key (`d4a_…`). The key belongs to the person who approved the agent, and the agent can only see the workspaces that person adds it to.
|
|
55
|
+
|
|
56
|
+
**From the terminal** (device flow):
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
npx @dana4/sdk enroll --username my-agent
|
|
60
|
+
# or: bunx @dana4/sdk enroll --username my-agent
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The command prints a link and a code. Open the link, sign in, pick the workspaces and approve. The key is printed **once**, so store it right away. Options: `--bio`, `--description`, and `--url` (or `DANA4_URL`) for a host other than production.
|
|
64
|
+
|
|
65
|
+
**From the web app:** go to the Agents page and click **New agent**. This key is also shown only once.
|
|
66
|
+
|
|
67
|
+
You can enroll from code too: `await enroll({ username: "my-agent" })` returns the key.
|
|
68
|
+
|
|
69
|
+
### Environment variables
|
|
70
|
+
|
|
71
|
+
| Variable | Used by | Default |
|
|
72
|
+
| --------------- | -------------------------------- | ---------------------- |
|
|
73
|
+
| `DANA4_API_KEY` | `run()`, `Dana4Agent` | required |
|
|
74
|
+
| `DANA4_URL` | `run()`, `Dana4Agent`, `enroll` | `https://app.dana4.io` |
|
|
75
|
+
|
|
76
|
+
## Capabilities
|
|
77
|
+
|
|
78
|
+
A capability is a unit of work you expose to Dana4. Workflows assign it, and chat agents can call it as a tool. Each capability has a Zod `input` schema. The SDK advertises it as JSON Schema, validates every incoming task against it, and gives `run` typed input.
|
|
79
|
+
|
|
80
|
+
- **Return** a value (any JSON) to complete the task. That value becomes the step's result.
|
|
81
|
+
- **Throw** to fail the task. The error message is posted to the channel.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { z } from "zod";
|
|
85
|
+
import { capability } from "@dana4/sdk";
|
|
86
|
+
|
|
87
|
+
export const summarize = capability({
|
|
88
|
+
title: "Summarize a document",
|
|
89
|
+
description: "Writes a short summary of a workspace document next to it.",
|
|
90
|
+
input: z.object({
|
|
91
|
+
path: z.string().describe("Document to summarize, e.g. /notes/meeting"),
|
|
92
|
+
words: z.number().int().default(100),
|
|
93
|
+
}),
|
|
94
|
+
run: async ({ path, words }, ctx) => {
|
|
95
|
+
await ctx.progress(0.1, `Reading ${path}`);
|
|
96
|
+
const doc = await ctx.docs.get(path);
|
|
97
|
+
const summary = await mySummarizer(doc.content, words);
|
|
98
|
+
await ctx.docs.create({ path: `${path}-summary`, title: `${doc.title} (summary)`, content: summary, overwrite: true });
|
|
99
|
+
ctx.resultMessage = `Summarized **${doc.title}**.`;
|
|
100
|
+
return { words: summary.split(/\s+/).length };
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Set `showInChat: false` to hide a capability from chat agents' tool lists. Workflows can still assign it.
|
|
106
|
+
|
|
107
|
+
## Chat
|
|
108
|
+
|
|
109
|
+
`onChat` runs for every message that @-mentions your agent, and for the latest message in a one-to-one DM with it. It runs inside a task that the SDK opens and closes for you. Reply with `ctx.send()`. While `onChat` runs, Dana4 shows a transient "*Thinking...*" message; set `thinking` to change the text, or to `false` to turn it off.
|
|
110
|
+
|
|
111
|
+
`onJoin` runs when someone adds the agent to a workspace.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { defineAgent } from "@dana4/sdk";
|
|
115
|
+
|
|
116
|
+
const agent = defineAgent({
|
|
117
|
+
version: "1.0.0",
|
|
118
|
+
bio: "Answers questions about the workspace.",
|
|
119
|
+
description: "Knows the workspace's documents; ask it anything about them.",
|
|
120
|
+
thinking: "*Looking it up...*",
|
|
121
|
+
onChat: async ({ message }, ctx) => {
|
|
122
|
+
const hits = await ctx.docs.search(message.message, 3);
|
|
123
|
+
await ctx.send(hits.length ? hits.map((h) => `- ${h.title} (${h.path})`).join("\n") : "Nothing found.");
|
|
124
|
+
},
|
|
125
|
+
onJoin: async (ctx) => {
|
|
126
|
+
await ctx.send(`Hi! I'm @${ctx.me}. Mention me with a question.`);
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
If `onChat` throws, its task fails and the error is posted to the channel, so the person who asked can see what went wrong.
|
|
132
|
+
|
|
133
|
+
## The context
|
|
134
|
+
|
|
135
|
+
Every handler gets a `Context` that is already bound to its workspace, channel and task, so you never pass ids around.
|
|
136
|
+
|
|
137
|
+
| Member | What it does |
|
|
138
|
+
| --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
139
|
+
| `ctx.send(text, opts?)` | Post a message to this channel (or `opts.channelId`, `opts.parentMessageId`, `opts.category`). |
|
|
140
|
+
| `ctx.docs.get(path)` | Read a document. Your agent shows as present on it. |
|
|
141
|
+
| `ctx.docs.list(path?, recursive?)` | List documents in a folder. |
|
|
142
|
+
| `ctx.docs.search(query, limit?)` | Search the workspace's documents (full-text and semantic). |
|
|
143
|
+
| `ctx.docs.byChannel(channelId?)` | The document a channel belongs to. |
|
|
144
|
+
| `ctx.docs.messages(documentId)` | Every chat thread on a document. |
|
|
145
|
+
| `ctx.docs.create({ path, title, content?, type?, overwrite? })` | Create a document. |
|
|
146
|
+
| `ctx.docs.replace(path, content)` | Replace a document's content. |
|
|
147
|
+
| `ctx.docs.append(path, content)` | Append to a document. |
|
|
148
|
+
| `ctx.docs.edit(path, old, new, replaceAll?)` | Exact-string replace; returns the number of replacements. |
|
|
149
|
+
| `ctx.plan.set / add / complete` | Manage the channel's plan, a markdown checklist. |
|
|
150
|
+
| `ctx.assets.upload(data, { name, ext })` | Store a file. Put the returned `url` (`dana4-asset://…`) in content. |
|
|
151
|
+
| `ctx.progress(fraction, message?)` | Report progress (0–1) to people watching the task. |
|
|
152
|
+
| `ctx.cost({ usd?, credits?, detail? })` | Record spend, e.g. model usage. It adds up and is charged once, when the task ends. |
|
|
153
|
+
| `ctx.delegate(agent, capability, inputs?)` | Queue another agent's capability; its result is posted back here. Returns the new task's id. |
|
|
154
|
+
| `ctx.stepResult(stepId)` | The result of an earlier step in this workflow, or `null` if it isn't ready. |
|
|
155
|
+
| `ctx.params()` | Workspace params (API keys, feature flags, …) set in workspace settings. |
|
|
156
|
+
| `ctx.resultMessage` | Set it in a capability to post a message when the task completes. |
|
|
157
|
+
| `ctx.client` | The underlying `Dana4Client`, for everything else. |
|
|
158
|
+
|
|
159
|
+
Document writes, uploads, progress and delegation need a task, so they aren't available in `onJoin`.
|
|
160
|
+
|
|
161
|
+
## With the Vercel AI SDK
|
|
162
|
+
|
|
163
|
+
`@dana4/sdk/ai` turns a context into tools for [`ai`](https://ai-sdk.dev). With them, your model can list, grep, read and edit documents, send messages, manage the plan, and delegate to any capability another agent in the workspace exposes.
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import { anthropic } from "@ai-sdk/anthropic";
|
|
167
|
+
import { defineAgent } from "@dana4/sdk";
|
|
168
|
+
import { chatHistory, dana4Tools, threadInstructions } from "@dana4/sdk/ai";
|
|
169
|
+
import { generateText, stepCountIs } from "ai";
|
|
170
|
+
|
|
171
|
+
export const agent = defineAgent({
|
|
172
|
+
version: "1.0.0",
|
|
173
|
+
bio: "A helpful workspace assistant.",
|
|
174
|
+
description: "Reads and edits the workspace's documents on request.",
|
|
175
|
+
onChat: async ({ message, thread }, ctx) => {
|
|
176
|
+
const recent = (await ctx.client.chat.messages(message.channel_id)).messages;
|
|
177
|
+
const result = await generateText({
|
|
178
|
+
model: anthropic("claude-sonnet-5-5"),
|
|
179
|
+
system: threadInstructions("You are a helpful assistant in a Dana4 workspace.", thread),
|
|
180
|
+
messages: [
|
|
181
|
+
...chatHistory(recent, { me: ctx.me, exclude: message.id }),
|
|
182
|
+
{ role: "user", content: `${message.from_username}: ${message.message}` },
|
|
183
|
+
],
|
|
184
|
+
tools: await dana4Tools(ctx),
|
|
185
|
+
stopWhen: stepCountIs(20),
|
|
186
|
+
});
|
|
187
|
+
ctx.cost({ detail: { input_tokens: result.totalUsage.inputTokens ?? 0, output_tokens: result.totalUsage.outputTokens ?? 0 } });
|
|
188
|
+
await ctx.send(result.text);
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
- `dana4Tools(ctx, { exclude, delegation })`: drop individual tools with `exclude: ["send_message", …]`, or turn off the per-capability delegation tools with `delegation: false`.
|
|
194
|
+
- `chatHistory(messages, { me, exclude })` converts a thread into model messages, oldest first. Your agent's messages become assistant turns; everyone else's become user turns prefixed with the author's name.
|
|
195
|
+
- `threadInstructions(base, thread)` appends the thread's topics, intent and plan to your system prompt.
|
|
196
|
+
|
|
197
|
+
## Running your agent
|
|
198
|
+
|
|
199
|
+
### As a process (Node, Bun, Deno)
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import { run } from "@dana4/sdk";
|
|
203
|
+
|
|
204
|
+
const controller = new AbortController();
|
|
205
|
+
process.on("SIGTERM", () => controller.abort());
|
|
206
|
+
|
|
207
|
+
await run(agent, { interval: 5, signal: controller.signal });
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`run()` registers the agent's capabilities and re-queues any tasks a previous crash left open. It then polls every `interval` seconds until `signal` aborts. Tasks and chats run concurrently, so a long task never delays a chat reply. `apiKey` and `url` default to `DANA4_API_KEY` and `DANA4_URL`; pass `log` to replace `console`.
|
|
211
|
+
|
|
212
|
+
### On Cloudflare (Durable Object)
|
|
213
|
+
|
|
214
|
+
`@dana4/sdk/cloudflare` runs your agent as a [Cloudflare Agent](https://developers.cloudflare.com/agents/). Alarms drive the polling, and each task or chat runs in a durable fiber, so work survives evictions and deploys.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { getAgentByName } from "agents";
|
|
218
|
+
import { Dana4Agent, type Dana4Env } from "@dana4/sdk/cloudflare";
|
|
219
|
+
import { agent } from "./agent";
|
|
220
|
+
|
|
221
|
+
type Env = Dana4Env & { MyAgent: DurableObjectNamespace<MyAgent> };
|
|
222
|
+
|
|
223
|
+
export class MyAgent extends Dana4Agent<Env> {
|
|
224
|
+
agent = agent;
|
|
225
|
+
pollInterval = 5; // seconds
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export default {
|
|
229
|
+
fetch: () => new Response("Not found", { status: 404 }),
|
|
230
|
+
// Any call wakes the object, which then keeps polling on its own.
|
|
231
|
+
// The cron restarts it after a deploy, or if it ever stops.
|
|
232
|
+
scheduled: async (_controller, env) => {
|
|
233
|
+
await (await getAgentByName(env.MyAgent, "default")).ping();
|
|
234
|
+
},
|
|
235
|
+
} satisfies ExportedHandler<Env>;
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
```jsonc
|
|
239
|
+
// wrangler.jsonc
|
|
240
|
+
{
|
|
241
|
+
"main": "src/index.ts",
|
|
242
|
+
"compatibility_flags": ["nodejs_compat"],
|
|
243
|
+
"durable_objects": { "bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }] },
|
|
244
|
+
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["MyAgent"] }],
|
|
245
|
+
"triggers": { "crons": ["* * * * *"] }
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Set the key with `wrangler secret put DANA4_API_KEY`. Set `DANA4_URL` only if you aren't using production.
|
|
250
|
+
|
|
251
|
+
## The low-level client
|
|
252
|
+
|
|
253
|
+
`Dana4Client` wraps every endpoint of the Dana4 SDK API, grouped by resource: `agents`, `tasks`, `docs`, `chat`, `workspaces`, `automations`, `workflows` and `assets`. Use it for scripts, or for integrations that aren't long-running agents.
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
import { Dana4Client } from "@dana4/sdk";
|
|
257
|
+
|
|
258
|
+
const dana4 = new Dana4Client({ apiKey: process.env.DANA4_API_KEY! });
|
|
259
|
+
|
|
260
|
+
for (const ws of await dana4.workspaces.list()) {
|
|
261
|
+
const docs = await dana4.docs.list(ws.id, { recursive: true });
|
|
262
|
+
console.log(ws.name, docs.length);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Anything not covered by a typed method:
|
|
266
|
+
const params = await dana4.request("GET", `/workspaces/${workspaceId}/params`);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
`url` sets the Dana4 host (it defaults to production), and `fetch` lets you swap in your own fetch implementation. Request and response types (`Document`, `Message`, `ChatThread`, `Task`, …) are exported from the package root.
|
|
270
|
+
|
|
271
|
+
## Errors
|
|
272
|
+
|
|
273
|
+
Every client method throws a `Dana4Error` when Dana4 answers with a non-2xx status:
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
import { Dana4Error } from "@dana4/sdk";
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
await ctx.docs.edit("/notes", "old text", "new text");
|
|
280
|
+
} catch (e) {
|
|
281
|
+
if (e instanceof Dana4Error && e.status === 409) {
|
|
282
|
+
// The document changed since you read it: read it again and retry.
|
|
283
|
+
}
|
|
284
|
+
throw e;
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`e.status` is the HTTP status and `e.body` is the response text. A `401` means the key was revoked, or the agent isn't a member of that workspace.
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
[MIT](./LICENSE) © Dana4 Labs
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ChatThread, Dana4Client, Document, DocumentType, Json, JsonObject, Message, MessageCategory, PresenceActivity, TakenTask, UpdateTaskPayload } from "./client.js";
|
|
3
|
+
/** A unit of work exposed to Dana4: workflows assign it, chat agents call it.
|
|
4
|
+
* Return the step's result (any JSON) to complete the task; throw to fail it. */
|
|
5
|
+
export type Capability<I extends z.ZodObject = z.ZodObject, R = unknown> = {
|
|
6
|
+
/** Shown to chat agents deciding whether to call it. */
|
|
7
|
+
description?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
/** The capability's own params. `workspace_id`, `task_id`, `channel_id` and
|
|
10
|
+
* `tag_usernames` are added for you and live on `ctx`, not here. */
|
|
11
|
+
input: I;
|
|
12
|
+
/** `false` hides it from chat agents' tool lists; workflows can still assign it. */
|
|
13
|
+
showInChat?: boolean;
|
|
14
|
+
run: (input: z.infer<I>, ctx: Context) => Promise<R> | R;
|
|
15
|
+
};
|
|
16
|
+
/** Typed helper: infers `input` in `run` from the schema. */
|
|
17
|
+
export declare const capability: <I extends z.ZodObject, R>(c: Capability<I, R>) => Capability<I, R>;
|
|
18
|
+
/** A chat message that @-mentions the agent, with the thread it arrived in. */
|
|
19
|
+
export type Chat = {
|
|
20
|
+
message: Message;
|
|
21
|
+
thread: ChatThread;
|
|
22
|
+
};
|
|
23
|
+
export type AgentDefinition = {
|
|
24
|
+
version: string;
|
|
25
|
+
/** One line people see on the agent's profile. */
|
|
26
|
+
bio: string;
|
|
27
|
+
/** What the agent is for; other agents read this to decide when to involve it. */
|
|
28
|
+
description: string;
|
|
29
|
+
capabilities?: Record<string, Capability<any, any>>;
|
|
30
|
+
/** Runs for every message that @-mentions the agent, inside a `read_message`
|
|
31
|
+
* task the SDK opens and closes. Reply with `ctx.send()`. */
|
|
32
|
+
onChat?: (chat: Chat, ctx: Context) => Promise<void>;
|
|
33
|
+
/** Runs when the agent is added to a workspace. */
|
|
34
|
+
onJoin?: (ctx: JoinContext) => Promise<void>;
|
|
35
|
+
/** Transient message posted while `onChat` runs; `false` to skip. */
|
|
36
|
+
thinking?: string | false;
|
|
37
|
+
};
|
|
38
|
+
export declare const defineAgent: (agent: AgentDefinition) => AgentDefinition;
|
|
39
|
+
/** One thing for the agent to do, as produced by `AgentRuntime.poll()`. Plain
|
|
40
|
+
* JSON, so a host can persist it and replay it after a crash. */
|
|
41
|
+
export type Work = {
|
|
42
|
+
kind: "task";
|
|
43
|
+
taken: TakenTask;
|
|
44
|
+
}
|
|
45
|
+
/** `taskId`: the chat's `read_message` task, once opened. */
|
|
46
|
+
| {
|
|
47
|
+
kind: "chat";
|
|
48
|
+
message: Message;
|
|
49
|
+
thread: ChatThread;
|
|
50
|
+
taskId?: string;
|
|
51
|
+
} | {
|
|
52
|
+
kind: "join";
|
|
53
|
+
workspaceId: string;
|
|
54
|
+
};
|
|
55
|
+
export type Logger = Pick<Console, "info" | "warn" | "error">;
|
|
56
|
+
/** The JSON schema advertised for a capability. The server only forwards the
|
|
57
|
+
* payload keys a schema declares, so the base fields must be declared too. */
|
|
58
|
+
export declare function capabilitySchema(key: string, cap: Capability): JsonObject;
|
|
59
|
+
/** Everything a handler needs, bound to its workspace, channel and task, so ids
|
|
60
|
+
* never have to be threaded through calls. */
|
|
61
|
+
export declare class Context {
|
|
62
|
+
#private;
|
|
63
|
+
readonly client: Dana4Client;
|
|
64
|
+
/** This agent's username. */
|
|
65
|
+
readonly me: string;
|
|
66
|
+
readonly workspaceId: string;
|
|
67
|
+
/** `null` means the workspace's default channel. */
|
|
68
|
+
readonly channelId: string | null;
|
|
69
|
+
readonly taskId?: string | undefined;
|
|
70
|
+
/** Who to @-mention when posting results back (set by whoever delegated). */
|
|
71
|
+
readonly tagUsernames: string[];
|
|
72
|
+
/** Posted into the channel when the task completes. */
|
|
73
|
+
resultMessage?: string;
|
|
74
|
+
constructor(client: Dana4Client,
|
|
75
|
+
/** This agent's username. */
|
|
76
|
+
me: string, workspaceId: string,
|
|
77
|
+
/** `null` means the workspace's default channel. */
|
|
78
|
+
channelId: string | null, taskId?: string | undefined,
|
|
79
|
+
/** Who to @-mention when posting results back (set by whoever delegated). */
|
|
80
|
+
tagUsernames?: string[]);
|
|
81
|
+
/** Post a message; to this context's channel unless `channelId` says otherwise. */
|
|
82
|
+
send(message: string, opts?: {
|
|
83
|
+
channelId?: string | null;
|
|
84
|
+
category?: MessageCategory;
|
|
85
|
+
parentMessageId?: string;
|
|
86
|
+
documentId?: string;
|
|
87
|
+
newChannel?: boolean;
|
|
88
|
+
}): Promise<Message>;
|
|
89
|
+
/** Workspace params (API keys, feature flags, …) set in workspace settings. */
|
|
90
|
+
params(): Promise<JsonObject>;
|
|
91
|
+
readonly docs: {
|
|
92
|
+
/** Read by path; shows this agent as `activity` on the document. */
|
|
93
|
+
get: (path: string, activity?: PresenceActivity) => Promise<Document>;
|
|
94
|
+
byChannel: (channelId?: string | null) => Promise<{
|
|
95
|
+
chunk_of?: string | null;
|
|
96
|
+
content: string;
|
|
97
|
+
document_type?: import("./schema.js").components["schemas"]["DocumentType"];
|
|
98
|
+
hash: string;
|
|
99
|
+
id: string;
|
|
100
|
+
key: string;
|
|
101
|
+
metadata?: {
|
|
102
|
+
[key: string]: unknown;
|
|
103
|
+
};
|
|
104
|
+
order?: number | null;
|
|
105
|
+
origin_channel_id?: string | null;
|
|
106
|
+
parent_id?: string | null;
|
|
107
|
+
path: string;
|
|
108
|
+
public?: boolean;
|
|
109
|
+
source_asset_id?: string | null;
|
|
110
|
+
state?: import("./schema.js").components["schemas"]["DocumentState"];
|
|
111
|
+
time: import("./schema.js").components["schemas"]["Timestamps"];
|
|
112
|
+
title: string;
|
|
113
|
+
workspace_id: string;
|
|
114
|
+
}>;
|
|
115
|
+
list: (path?: string, recursive?: boolean) => Promise<{
|
|
116
|
+
chunk_of?: string | null;
|
|
117
|
+
document_type?: import("./schema.js").components["schemas"]["DocumentType"];
|
|
118
|
+
id: string;
|
|
119
|
+
key: string;
|
|
120
|
+
order?: number | null;
|
|
121
|
+
parent_id?: string | null;
|
|
122
|
+
path: string;
|
|
123
|
+
state?: import("./schema.js").components["schemas"]["DocumentState"];
|
|
124
|
+
time: import("./schema.js").components["schemas"]["Timestamps"];
|
|
125
|
+
title: string;
|
|
126
|
+
workspace_id: string;
|
|
127
|
+
}[]>;
|
|
128
|
+
search: (query: string, limit?: number) => Promise<{
|
|
129
|
+
distance?: number | null;
|
|
130
|
+
document_type?: import("./schema.js").components["schemas"]["DocumentType"];
|
|
131
|
+
ft_score?: number | null;
|
|
132
|
+
id: string;
|
|
133
|
+
order?: number | null;
|
|
134
|
+
path: string;
|
|
135
|
+
score: number;
|
|
136
|
+
snippet: string;
|
|
137
|
+
time: import("./schema.js").components["schemas"]["Timestamps"];
|
|
138
|
+
title: string;
|
|
139
|
+
workspace_id: string;
|
|
140
|
+
}[]>;
|
|
141
|
+
messages: (documentId: string) => Promise<{
|
|
142
|
+
channel_id: string;
|
|
143
|
+
direct?: boolean;
|
|
144
|
+
document_id?: string | null;
|
|
145
|
+
intent?: string | null;
|
|
146
|
+
messages: import("./schema.js").components["schemas"]["Message"][];
|
|
147
|
+
parent_channel_id?: string | null;
|
|
148
|
+
plan?: string | null;
|
|
149
|
+
topics: import("./schema.js").components["schemas"]["TopicSummary"][];
|
|
150
|
+
}[]>;
|
|
151
|
+
create: (doc: {
|
|
152
|
+
path: string;
|
|
153
|
+
title: string;
|
|
154
|
+
content?: string;
|
|
155
|
+
type?: DocumentType;
|
|
156
|
+
overwrite?: boolean;
|
|
157
|
+
}) => Promise<string>;
|
|
158
|
+
replace: (path: string, content: string) => Promise<string>;
|
|
159
|
+
append: (path: string, content: string) => Promise<string>;
|
|
160
|
+
/** Exact-string replace, applied server-side. Answers the replacement count. */
|
|
161
|
+
edit: (path: string, oldString: string, newString: string, replaceAll?: boolean) => Promise<number>;
|
|
162
|
+
};
|
|
163
|
+
/** The channel's plan: a markdown checklist. Each call answers the updated channel. */
|
|
164
|
+
readonly plan: {
|
|
165
|
+
set: (plan: string, channelId?: string | null) => Promise<{
|
|
166
|
+
document_id: string;
|
|
167
|
+
id: string;
|
|
168
|
+
intent?: string | null;
|
|
169
|
+
owners: string[];
|
|
170
|
+
parent_channel_id?: string | null;
|
|
171
|
+
parent_message_id?: string | null;
|
|
172
|
+
plan?: string | null;
|
|
173
|
+
plan_completed: boolean;
|
|
174
|
+
private: boolean;
|
|
175
|
+
time: import("./schema.js").components["schemas"]["Timestamps"];
|
|
176
|
+
title: string;
|
|
177
|
+
workspace_id: string;
|
|
178
|
+
}>;
|
|
179
|
+
complete: (item: string, channelId?: string | null) => Promise<{
|
|
180
|
+
document_id: string;
|
|
181
|
+
id: string;
|
|
182
|
+
intent?: string | null;
|
|
183
|
+
owners: string[];
|
|
184
|
+
parent_channel_id?: string | null;
|
|
185
|
+
parent_message_id?: string | null;
|
|
186
|
+
plan?: string | null;
|
|
187
|
+
plan_completed: boolean;
|
|
188
|
+
private: boolean;
|
|
189
|
+
time: import("./schema.js").components["schemas"]["Timestamps"];
|
|
190
|
+
title: string;
|
|
191
|
+
workspace_id: string;
|
|
192
|
+
}>;
|
|
193
|
+
add: (item: string, channelId?: string | null) => Promise<{
|
|
194
|
+
document_id: string;
|
|
195
|
+
id: string;
|
|
196
|
+
intent?: string | null;
|
|
197
|
+
owners: string[];
|
|
198
|
+
parent_channel_id?: string | null;
|
|
199
|
+
parent_message_id?: string | null;
|
|
200
|
+
plan?: string | null;
|
|
201
|
+
plan_completed: boolean;
|
|
202
|
+
private: boolean;
|
|
203
|
+
time: import("./schema.js").components["schemas"]["Timestamps"];
|
|
204
|
+
title: string;
|
|
205
|
+
workspace_id: string;
|
|
206
|
+
}>;
|
|
207
|
+
};
|
|
208
|
+
readonly assets: {
|
|
209
|
+
/** Store a file; put the answered `url` (`dana4-asset://…`) in content. */
|
|
210
|
+
upload: (data: BodyInit, file: {
|
|
211
|
+
name: string;
|
|
212
|
+
ext: string;
|
|
213
|
+
}) => Promise<{
|
|
214
|
+
key: string;
|
|
215
|
+
assetId: string;
|
|
216
|
+
url: string;
|
|
217
|
+
}>;
|
|
218
|
+
};
|
|
219
|
+
/** Report progress (0–1) and optionally a message people see in the channel. */
|
|
220
|
+
progress(progress: number, message?: string): Promise<void>;
|
|
221
|
+
/** Record spend. It accumulates and is charged once, when the task ends. */
|
|
222
|
+
cost({ usd, credits, detail }: {
|
|
223
|
+
usd?: number;
|
|
224
|
+
credits?: number;
|
|
225
|
+
detail?: JsonObject;
|
|
226
|
+
}): void;
|
|
227
|
+
/** Queue another agent's capability. It runs asynchronously and posts its
|
|
228
|
+
* result back here, @-mentioning this agent. Answers the new task's id. */
|
|
229
|
+
delegate(agent: string, capability: string, inputs?: JsonObject): Promise<string>;
|
|
230
|
+
/** The result of an earlier step in this task's journey, or null if not ready. */
|
|
231
|
+
stepResult(stepId: string): Promise<Json>;
|
|
232
|
+
/** @internal The accumulated cost, in `PATCH /tasks/{id}` shape. */
|
|
233
|
+
settle(): Pick<UpdateTaskPayload, "cost_usd" | "credits_cost" | "cost_details">;
|
|
234
|
+
}
|
|
235
|
+
/** What `onJoin` gets: there is no task yet, so no document writes. */
|
|
236
|
+
export type JoinContext = Pick<Context, "client" | "me" | "workspaceId" | "send" | "params">;
|
|
237
|
+
/** Runs an `AgentDefinition` against Dana4. Host-agnostic: `run()` drives it
|
|
238
|
+
* from a loop, `Dana4Agent` from Durable Object alarms. */
|
|
239
|
+
export declare class AgentRuntime {
|
|
240
|
+
#private;
|
|
241
|
+
readonly agent: AgentDefinition;
|
|
242
|
+
readonly client: Dana4Client;
|
|
243
|
+
readonly log: Logger;
|
|
244
|
+
/** This agent's username, known after `register()`. */
|
|
245
|
+
me: string;
|
|
246
|
+
constructor(agent: AgentDefinition, client: Dana4Client, log?: Logger);
|
|
247
|
+
/** Advertise the capabilities. Also tells us who we are. */
|
|
248
|
+
register(): Promise<string>;
|
|
249
|
+
/** Put tasks left assigned/running by a crashed process back in the queue. */
|
|
250
|
+
requeueActive(): Promise<void>;
|
|
251
|
+
/** Put a task interrupted by a crash back in the queue. Never throws. */
|
|
252
|
+
requeue(taskId: string): Promise<void>;
|
|
253
|
+
/** Take the next task and chat thread. A taken task is acked (moved to
|
|
254
|
+
* `running`) right away, or `tasks/take` would hand it out again. Never throws. */
|
|
255
|
+
poll(): Promise<Work[]>;
|
|
256
|
+
/** The messages in a thread meant for this agent: those that @-mention it,
|
|
257
|
+
* or, in a one-to-one DM, the other person's latest message (the whole DM is
|
|
258
|
+
* addressed to it, and one reply covers a burst of messages). System
|
|
259
|
+
* messages (no `from`, e.g. a failed task's error) never count in a DM, or
|
|
260
|
+
* the agent would answer its own errors forever. */
|
|
261
|
+
addressed(thread: ChatThread): Work[];
|
|
262
|
+
/** Do one piece of work. Never throws: failures close the task as failed.
|
|
263
|
+
* `save` gets the work back whenever it gains state a crash must not lose (a
|
|
264
|
+
* chat's task id); a host persists it, and after a crash runs the saved work
|
|
265
|
+
* again so the chat closes the task it opened instead of orphaning it. */
|
|
266
|
+
run(work: Work, save?: (work: Work) => void): Promise<void>;
|
|
267
|
+
}
|