@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
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** Run a Dana4 agent as a Cloudflare Agent (a Durable Object). Alarms drive the
|
|
2
|
+
* polling, and every piece of work runs in a durable fiber, so a chat taken
|
|
3
|
+
* from Dana4 survives an eviction or deploy mid-reply.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* export class MyAgent extends Dana4Agent<Env> {
|
|
7
|
+
* agent = myAgentDefinition;
|
|
8
|
+
* }
|
|
9
|
+
* export default {
|
|
10
|
+
* // Any call wakes the Durable Object, which then keeps polling by itself;
|
|
11
|
+
* // a cron trigger restarts it if it ever stops.
|
|
12
|
+
* scheduled: async (_c, env) => (await getAgentByName(env.MyAgent, "default")).ping(),
|
|
13
|
+
* };
|
|
14
|
+
* ```
|
|
15
|
+
* Secrets: `DANA4_API_KEY`, and `DANA4_URL` unless it's production. */
|
|
16
|
+
import { Agent } from "agents";
|
|
17
|
+
import { AgentRuntime } from "./agent.js";
|
|
18
|
+
import { Dana4Client } from "./client.js";
|
|
19
|
+
export class Dana4Agent extends Agent {
|
|
20
|
+
/** Seconds between polls. */
|
|
21
|
+
pollInterval = 5;
|
|
22
|
+
#runtime;
|
|
23
|
+
get runtime() {
|
|
24
|
+
this.#runtime ??= new AgentRuntime(this.agent, new Dana4Client({ apiKey: this.env.DANA4_API_KEY, url: this.env.DANA4_URL }));
|
|
25
|
+
return this.#runtime;
|
|
26
|
+
}
|
|
27
|
+
async onStart() {
|
|
28
|
+
// Idempotent: one schedule however often the object wakes.
|
|
29
|
+
await this.scheduleEvery(this.pollInterval, "poll");
|
|
30
|
+
await this.runtime.register().catch((e) => console.error("dana4: register failed:", e));
|
|
31
|
+
// A fresh instance runs no tasks yet, so any still active were stranded by
|
|
32
|
+
// an eviction, e.g. between `tasks/take` and their fiber's first stash.
|
|
33
|
+
// Fibers recovered just before this already re-queued theirs (idempotent).
|
|
34
|
+
await this.runtime.requeueActive().catch((e) => console.error("dana4: re-queuing failed:", e));
|
|
35
|
+
}
|
|
36
|
+
/** One round: take work, run each item in its own fiber. Overlapping rounds
|
|
37
|
+
* are skipped by the scheduler, and fibers aren't awaited, so a long task
|
|
38
|
+
* never holds up the next poll. */
|
|
39
|
+
async poll() {
|
|
40
|
+
const rt = this.runtime;
|
|
41
|
+
if (!rt.me)
|
|
42
|
+
await rt.register();
|
|
43
|
+
for (const work of await rt.poll())
|
|
44
|
+
this.#fiber(work);
|
|
45
|
+
}
|
|
46
|
+
#fiber(work) {
|
|
47
|
+
void this.runFiber(`dana4:${work.kind}`, async (fiber) => {
|
|
48
|
+
fiber.stash(work);
|
|
49
|
+
await this.runtime.run(work, (w) => fiber.stash(w));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/** Tasks go back to the queue. Chats and joins run again (the server already
|
|
53
|
+
* marked them read) in a new fiber: recovery hooks are short-lived, a chat
|
|
54
|
+
* isn't. A chat reuses the task it opened. */
|
|
55
|
+
async onFiberRecovered(ctx) {
|
|
56
|
+
if (!ctx.name.startsWith("dana4:") || !ctx.snapshot)
|
|
57
|
+
return;
|
|
58
|
+
const work = ctx.snapshot;
|
|
59
|
+
if (work.kind === "task")
|
|
60
|
+
return this.runtime.requeue(work.taken.task.id);
|
|
61
|
+
// ponytail: a chat evicted after replying but before closing its task replies
|
|
62
|
+
// twice; stash after `ctx.send` if that ever shows up.
|
|
63
|
+
if (!this.runtime.me)
|
|
64
|
+
await this.runtime.register();
|
|
65
|
+
this.#fiber(work);
|
|
66
|
+
}
|
|
67
|
+
/** Does nothing; calling it wakes the object (and so starts polling). */
|
|
68
|
+
async ping() { }
|
|
69
|
+
}
|
package/dist/enroll.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Get an API key for a new agent through the device flow. `onLink` gets the
|
|
2
|
+
* link (and code) for a person to open; once they sign in and approve, the
|
|
3
|
+
* agent exists, owned by them, in the workspaces they picked, and this answers
|
|
4
|
+
* its key. The key is shown once: store it (e.g. as `DANA4_API_KEY`). */
|
|
5
|
+
export declare function enroll(opts: {
|
|
6
|
+
username: string;
|
|
7
|
+
bio?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
url?: string;
|
|
10
|
+
onLink?: (link: string, code: string) => void;
|
|
11
|
+
}): Promise<string>;
|
package/dist/enroll.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { DEFAULT_URL, Dana4Error } from "./client.js";
|
|
2
|
+
/** Get an API key for a new agent through the device flow. `onLink` gets the
|
|
3
|
+
* link (and code) for a person to open; once they sign in and approve, the
|
|
4
|
+
* agent exists, owned by them, in the workspaces they picked, and this answers
|
|
5
|
+
* its key. The key is shown once: store it (e.g. as `DANA4_API_KEY`). */
|
|
6
|
+
export async function enroll(opts) {
|
|
7
|
+
const base = `${(opts.url ?? DEFAULT_URL).replace(/\/+$/, "")}/api-sdk/v1/agents/enroll`;
|
|
8
|
+
const post = (path, body) => fetch(base + path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
|
|
9
|
+
const r = await post("", { username: opts.username, bio: opts.bio, description: opts.description });
|
|
10
|
+
if (!r.ok)
|
|
11
|
+
throw new Dana4Error(r.status, await r.text(), "enroll");
|
|
12
|
+
const start = (await r.json());
|
|
13
|
+
(opts.onLink ?? ((link, code) => console.log(`To connect @${opts.username}, open\n\n ${link}\n\nand check the code ${code}. Waiting for approval...`)))(start.verification_uri_complete, start.user_code);
|
|
14
|
+
const deadline = Date.now() + start.expires_in * 1000;
|
|
15
|
+
while (Date.now() < deadline) {
|
|
16
|
+
await new Promise((resolve) => setTimeout(resolve, start.interval * 1000));
|
|
17
|
+
const t = await post("/token", { device_code: start.device_code });
|
|
18
|
+
if (t.ok)
|
|
19
|
+
return (await t.json()).api_key;
|
|
20
|
+
const body = await t.text();
|
|
21
|
+
if (t.status !== 400 || !body.includes("authorization_pending"))
|
|
22
|
+
throw new Dana4Error(t.status, body, "enroll");
|
|
23
|
+
}
|
|
24
|
+
throw new Error("Enrollment expired before it was approved; run it again");
|
|
25
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/run.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AgentDefinition, Logger } from "./agent.js";
|
|
2
|
+
import { type ClientOptions } from "./client.js";
|
|
3
|
+
/** Run an agent as a long-lived process (Node, Bun, Deno): register, re-queue
|
|
4
|
+
* tasks a previous crash left open, then poll every `interval` seconds until
|
|
5
|
+
* `signal` aborts. Work runs concurrently, so a long task never delays chat.
|
|
6
|
+
*
|
|
7
|
+
* `apiKey` and `url` default to `DANA4_API_KEY` and `DANA4_URL`. */
|
|
8
|
+
export declare function run(agent: AgentDefinition, opts?: Partial<ClientOptions> & {
|
|
9
|
+
interval?: number;
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
log?: Logger;
|
|
12
|
+
}): Promise<void>;
|
package/dist/run.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AgentRuntime } from "./agent.js";
|
|
2
|
+
import { Dana4Client } from "./client.js";
|
|
3
|
+
const env = (name) => globalThis.process?.env[name];
|
|
4
|
+
/** Run an agent as a long-lived process (Node, Bun, Deno): register, re-queue
|
|
5
|
+
* tasks a previous crash left open, then poll every `interval` seconds until
|
|
6
|
+
* `signal` aborts. Work runs concurrently, so a long task never delays chat.
|
|
7
|
+
*
|
|
8
|
+
* `apiKey` and `url` default to `DANA4_API_KEY` and `DANA4_URL`. */
|
|
9
|
+
export async function run(agent, opts = {}) {
|
|
10
|
+
const { interval = 5, signal, log = console, ...client } = opts;
|
|
11
|
+
const rt = new AgentRuntime(agent, new Dana4Client({ apiKey: client.apiKey ?? env("DANA4_API_KEY") ?? "", url: client.url ?? env("DANA4_URL"), fetch: client.fetch }), log);
|
|
12
|
+
log.info(`dana4: connected as @${await rt.register()}`);
|
|
13
|
+
await rt.requeueActive();
|
|
14
|
+
while (!signal?.aborted) {
|
|
15
|
+
for (const work of await rt.poll())
|
|
16
|
+
void rt.run(work);
|
|
17
|
+
await new Promise((resolve) => {
|
|
18
|
+
const done = () => {
|
|
19
|
+
clearTimeout(t);
|
|
20
|
+
signal?.removeEventListener("abort", done);
|
|
21
|
+
resolve();
|
|
22
|
+
};
|
|
23
|
+
const t = setTimeout(done, interval * 1000);
|
|
24
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|