agents 0.4.1 → 0.5.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.
@@ -1,4 +1,4 @@
1
- import { n as BaseEvent } from "./mcp-Dw5vDrY8.js";
1
+ import { n as BaseEvent } from "./mcp-DA0kDE7K.js";
2
2
 
3
3
  //#region src/observability/agent.d.ts
4
4
  /**
@@ -23,6 +23,15 @@ type AgentObservabilityEvent =
23
23
  id: string;
24
24
  }
25
25
  >
26
+ | BaseEvent<
27
+ "queue:retry" | "schedule:retry",
28
+ {
29
+ callback: string;
30
+ id: string;
31
+ attempt: number;
32
+ maxAttempts: number;
33
+ }
34
+ >
26
35
  | BaseEvent<"destroy">
27
36
  | BaseEvent<
28
37
  "connect",
@@ -48,4 +57,4 @@ type AgentObservabilityEvent =
48
57
  >;
49
58
  //#endregion
50
59
  export { AgentObservabilityEvent as t };
51
- //# sourceMappingURL=agent-DY6QmSI_.d.ts.map
60
+ //# sourceMappingURL=agent-B4_kEsdK.d.ts.map
@@ -1,4 +1,4 @@
1
- import { t as MCPObservabilityEvent } from "./mcp-Dw5vDrY8.js";
1
+ import { t as MCPObservabilityEvent } from "./mcp-DA0kDE7K.js";
2
2
  import { AgentMcpOAuthProvider } from "./mcp/do-oauth-client-provider.js";
3
3
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4
4
  import {
@@ -352,4 +352,4 @@ export {
352
352
  MCPServerRow as t,
353
353
  Emitter as u
354
354
  };
355
- //# sourceMappingURL=client-storage-Cvy5r9FG.d.ts.map
355
+ //# sourceMappingURL=client-storage-D633wI1S.d.ts.map
@@ -0,0 +1,118 @@
1
+ import { RetryOptions } from "../retries.js";
2
+ import "../client-storage-D633wI1S.js";
3
+ import { Agent, Schedule } from "../index.js";
4
+
5
+ //#region src/experimental/forever.d.ts
6
+ type FiberState = {
7
+ id: string;
8
+ callback: string;
9
+ payload: unknown;
10
+ snapshot: unknown | null;
11
+ status: "running" | "completed" | "failed" | "interrupted" | "cancelled";
12
+ retryCount: number;
13
+ maxRetries: number;
14
+ result: unknown | null;
15
+ error: string | null;
16
+ startedAt: number | null;
17
+ updatedAt: number | null;
18
+ completedAt: number | null;
19
+ createdAt: number;
20
+ };
21
+ type FiberRecoveryContext = {
22
+ id: string;
23
+ methodName: string;
24
+ payload: unknown;
25
+ snapshot: unknown | null;
26
+ retryCount: number;
27
+ };
28
+ type FiberContext = {
29
+ id: string;
30
+ snapshot: unknown | null;
31
+ retryCount: number;
32
+ };
33
+ type FiberCompleteContext = {
34
+ id: string;
35
+ methodName: string;
36
+ payload: unknown;
37
+ result: unknown;
38
+ };
39
+ type RawFiberRow = {
40
+ id: string;
41
+ callback: string;
42
+ payload: string | null;
43
+ snapshot: string | null;
44
+ status: string;
45
+ retry_count: number;
46
+ max_retries: number;
47
+ result: string | null;
48
+ error: string | null;
49
+ started_at: number | null;
50
+ updated_at: number | null;
51
+ completed_at: number | null;
52
+ created_at: number;
53
+ };
54
+ type Constructor<T = object> = new (...args: any[]) => T;
55
+ type AgentLike = Constructor<Pick<Agent<Cloudflare.Env>, "sql" | "scheduleEvery" | "cancelSchedule" | "alarm">>;
56
+ declare function withFibers<TBase extends AgentLike>(Base: TBase, options?: {
57
+ debugFibers?: boolean;
58
+ }): {
59
+ new (...args: any[]): {
60
+ /** @internal */_fiberActiveFibers: Set<string>; /** @internal */
61
+ _fiberRecoveryInProgress: boolean; /** @internal */
62
+ _fiberLastCleanupTime: number; /** @internal */
63
+ _fiberDebug(msg: string, ...args: unknown[]): void; /** @internal */
64
+ _cf_fiberHeartbeat(): Promise<void>;
65
+ keepAlive(): Promise<() => void>;
66
+ spawnFiber(methodName: keyof /*elided*/any, payload?: unknown, options?: {
67
+ maxRetries?: number;
68
+ }): string;
69
+ stashFiber(data: unknown): void;
70
+ /**
71
+ * Note: cancellation is cooperative. The status is set to 'cancelled'
72
+ * in SQLite, and the _runFiber retry loop checks for this status at
73
+ * the top of each iteration.
74
+ */
75
+ cancelFiber(fiberId: string): boolean;
76
+ getFiber(fiberId: string): FiberState | null;
77
+ restartFiber(fiberId: string): void;
78
+ /**
79
+ * Manually trigger fiber recovery check.
80
+ * In production, this runs automatically via the heartbeat schedule.
81
+ * Useful for testing or when you need immediate recovery after
82
+ * detecting an eviction.
83
+ */
84
+ checkFibers(): Promise<void>;
85
+ onFiberComplete(_ctx: FiberCompleteContext): void | Promise<void>;
86
+ onFiberRecovered(ctx: FiberRecoveryContext): void | Promise<void>;
87
+ onFibersRecovered(fibers: FiberRecoveryContext[]): Promise<void>; /** @internal */
88
+ _getRawFiber(fiberId: string): RawFiberRow | null; /** @internal */
89
+ _safeJsonParse(value: string | null): unknown; /** @internal */
90
+ _toFiberState(raw: RawFiberRow): FiberState; /** @internal */
91
+ _startFiber(id: string, methodName: string, payload: unknown, maxRetries: number): Promise<void>; /** @internal */
92
+ _runFiber(id: string, methodName: string, payload: unknown, maxRetries: number, disposeKeepAlive: () => void): Promise<void>; /** @internal */
93
+ _checkInterruptedFibers(): Promise<void>; /** @internal */
94
+ _cleanupOrphanedHeartbeats(): void; /** @internal */
95
+ _maybeCleanupFibers(): void;
96
+ sql: <T = Record<string, string | number | boolean | null>>(strings: TemplateStringsArray, ...values: (string | number | boolean | null)[]) => T[];
97
+ scheduleEvery: <T = string>(intervalSeconds: number, callback: keyof Agent<Cloudflare.Env, unknown, Record<string, unknown>>, payload?: T | undefined, options?: {
98
+ retry?: RetryOptions;
99
+ }) => Promise<Schedule<T>>;
100
+ cancelSchedule: (id: string) => Promise<boolean>;
101
+ readonly alarm: () => Promise<void>;
102
+ };
103
+ } & TBase;
104
+ /**
105
+ * Keep a Durable Object alive via a scheduled heartbeat.
106
+ * Returns a disposer function that cancels the heartbeat.
107
+ *
108
+ * Standalone version usable by any Agent subclass without requiring
109
+ * the full fiber mixin. The agent must have a no-op method with the
110
+ * given callbackName for the scheduler to invoke.
111
+ *
112
+ * @param agent - The agent instance (must have scheduleEvery and cancelSchedule)
113
+ * @param callbackName - Name of a no-op method on the agent class (must exist)
114
+ */
115
+ declare function keepAlive(agent: Pick<Agent<Cloudflare.Env>, "scheduleEvery" | "cancelSchedule">, callbackName: string): Promise<() => void>;
116
+ //#endregion
117
+ export { FiberCompleteContext, FiberContext, FiberRecoveryContext, FiberState, keepAlive, withFibers };
118
+ //# sourceMappingURL=forever.d.ts.map
@@ -0,0 +1,381 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { nanoid } from "nanoid";
3
+
4
+ //#region src/experimental/forever.ts
5
+ /**
6
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
7
+ * !! WARNING: EXPERIMENTAL — DO NOT USE IN PRODUCTION !!
8
+ * !! !!
9
+ * !! This API is under active development and WILL break between !!
10
+ * !! releases. Method names, types, behavior, and the mixin signature !!
11
+ * !! are all subject to change without notice. !!
12
+ * !! !!
13
+ * !! If you use this, pin your agents version and expect to rewrite !!
14
+ * !! your code when upgrading. !!
15
+ * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
16
+ *
17
+ * Experimental fiber mixin for durable long-running execution.
18
+ *
19
+ * Usage:
20
+ * import { Agent } from "agents";
21
+ * import { withFibers } from "agents/experimental/forever";
22
+ *
23
+ * class MyAgent extends withFibers(Agent)<Env, State> {
24
+ * async doWork(payload, fiberCtx) { ... }
25
+ * }
26
+ *
27
+ * This mixin adds:
28
+ * - keepAlive() — keep the DO alive via scheduled heartbeats
29
+ * - spawnFiber() — fire-and-forget durable execution
30
+ * - stashFiber() — checkpoint progress that survives eviction
31
+ * - cancelFiber() / getFiber() — manage running fibers
32
+ * - onFiberComplete / onFiberRecovered / onFibersRecovered — lifecycle hooks
33
+ *
34
+ * @experimental This API is not yet stable and may change.
35
+ */
36
+ console.warn("[agents/experimental/forever] WARNING: You are using an experimental API that WILL break between releases. Do not use in production.");
37
+ const KEEP_ALIVE_INTERVAL_MS = 1e4;
38
+ const FIBER_CLEANUP_INTERVAL_MS = 600 * 1e3;
39
+ const FIBER_CLEANUP_COMPLETED_MS = 1440 * 60 * 1e3;
40
+ const FIBER_CLEANUP_FAILED_MS = 10080 * 60 * 1e3;
41
+ const fiberContext = new AsyncLocalStorage();
42
+ function withFibers(Base, options) {
43
+ const debugEnabled = options?.debugFibers ?? false;
44
+ class FiberAgent extends Base {
45
+ constructor(...args) {
46
+ super(...args);
47
+ this._fiberActiveFibers = /* @__PURE__ */ new Set();
48
+ this._fiberRecoveryInProgress = false;
49
+ this._fiberLastCleanupTime = 0;
50
+ this.sql`
51
+ CREATE TABLE IF NOT EXISTS cf_agents_fibers (
52
+ id TEXT PRIMARY KEY NOT NULL,
53
+ callback TEXT NOT NULL,
54
+ payload TEXT,
55
+ snapshot TEXT,
56
+ status TEXT NOT NULL DEFAULT 'running'
57
+ CHECK(status IN ('running', 'completed', 'failed', 'interrupted', 'cancelled')),
58
+ retry_count INTEGER NOT NULL DEFAULT 0,
59
+ max_retries INTEGER NOT NULL DEFAULT 3,
60
+ result TEXT,
61
+ error TEXT,
62
+ started_at INTEGER,
63
+ updated_at INTEGER,
64
+ completed_at INTEGER,
65
+ created_at INTEGER NOT NULL
66
+ )
67
+ `;
68
+ }
69
+ /** @internal */ _fiberDebug(msg, ...args) {
70
+ if (debugEnabled) console.debug(`[fiber] ${msg}`, ...args);
71
+ }
72
+ /** @internal */ async _cf_fiberHeartbeat() {
73
+ await this._checkInterruptedFibers();
74
+ }
75
+ async keepAlive() {
76
+ const heartbeatSeconds = Math.ceil(KEEP_ALIVE_INTERVAL_MS / 1e3);
77
+ const schedule = await this.scheduleEvery(heartbeatSeconds, "_cf_fiberHeartbeat");
78
+ this._fiberDebug("keepAlive started, schedule=%s", schedule.id);
79
+ let disposed = false;
80
+ return () => {
81
+ if (disposed) return;
82
+ disposed = true;
83
+ this._fiberDebug("keepAlive disposed, schedule=%s", schedule.id);
84
+ this.cancelSchedule(schedule.id);
85
+ };
86
+ }
87
+ spawnFiber(methodName, payload, options) {
88
+ this._maybeCleanupFibers();
89
+ const name = methodName;
90
+ if (typeof this[methodName] !== "function") throw new Error(`this.${name} is not a function`);
91
+ const id = nanoid();
92
+ const now = Date.now();
93
+ const maxRetries = options?.maxRetries ?? 3;
94
+ this.sql`
95
+ INSERT INTO cf_agents_fibers (id, callback, payload, status, max_retries, retry_count, started_at, updated_at, created_at)
96
+ VALUES (${id}, ${name}, ${JSON.stringify(payload ?? null)}, 'running', ${maxRetries}, 0, ${now}, ${now}, ${now})
97
+ `;
98
+ this._fiberActiveFibers.add(id);
99
+ this._fiberDebug("spawned fiber=%s method=%s maxRetries=%d", id, name, maxRetries);
100
+ this._startFiber(id, name, payload, maxRetries).catch((e) => {
101
+ console.error(`Unhandled error in fiber ${id}:`, e);
102
+ });
103
+ return id;
104
+ }
105
+ stashFiber(data) {
106
+ const ctx = fiberContext.getStore();
107
+ if (!ctx) throw new Error("stashFiber() can only be called within a fiber execution context");
108
+ const now = Date.now();
109
+ this.sql`
110
+ UPDATE cf_agents_fibers
111
+ SET snapshot = ${JSON.stringify(data)}, updated_at = ${now}
112
+ WHERE id = ${ctx.fiberId}
113
+ `;
114
+ this._fiberDebug("stash fiber=%s", ctx.fiberId);
115
+ }
116
+ /**
117
+ * Note: cancellation is cooperative. The status is set to 'cancelled'
118
+ * in SQLite, and the _runFiber retry loop checks for this status at
119
+ * the top of each iteration.
120
+ */
121
+ cancelFiber(fiberId) {
122
+ const fiber = this._getRawFiber(fiberId);
123
+ if (!fiber) return false;
124
+ if (fiber.status === "completed" || fiber.status === "failed" || fiber.status === "cancelled") return false;
125
+ const now = Date.now();
126
+ this.sql`
127
+ UPDATE cf_agents_fibers
128
+ SET status = 'cancelled', updated_at = ${now}
129
+ WHERE id = ${fiberId}
130
+ `;
131
+ this._fiberActiveFibers.delete(fiberId);
132
+ this._fiberDebug("cancelled fiber=%s", fiberId);
133
+ return true;
134
+ }
135
+ getFiber(fiberId) {
136
+ const raw = this._getRawFiber(fiberId);
137
+ if (!raw) return null;
138
+ return this._toFiberState(raw);
139
+ }
140
+ restartFiber(fiberId) {
141
+ const fiber = this._getRawFiber(fiberId);
142
+ if (!fiber) throw new Error(`Fiber ${fiberId} not found`);
143
+ const now = Date.now();
144
+ this.sql`
145
+ UPDATE cf_agents_fibers
146
+ SET status = 'running', started_at = ${now}, updated_at = ${now}
147
+ WHERE id = ${fiberId}
148
+ `;
149
+ this._fiberActiveFibers.add(fiberId);
150
+ this._fiberDebug("restarting fiber=%s method=%s retryCount=%d", fiberId, fiber.callback, fiber.retry_count);
151
+ const parsedPayload = fiber.payload ? JSON.parse(fiber.payload) : void 0;
152
+ this._startFiber(fiberId, fiber.callback, parsedPayload, fiber.max_retries).catch((e) => {
153
+ console.error(`Error restarting fiber ${fiberId}:`, e);
154
+ });
155
+ }
156
+ /**
157
+ * Manually trigger fiber recovery check.
158
+ * In production, this runs automatically via the heartbeat schedule.
159
+ * Useful for testing or when you need immediate recovery after
160
+ * detecting an eviction.
161
+ */
162
+ async checkFibers() {
163
+ await this._checkInterruptedFibers();
164
+ }
165
+ onFiberComplete(_ctx) {}
166
+ onFiberRecovered(ctx) {
167
+ this.restartFiber(ctx.id);
168
+ }
169
+ async onFibersRecovered(fibers) {
170
+ for (const fiber of fibers) await this.onFiberRecovered(fiber);
171
+ }
172
+ /** @internal */ _getRawFiber(fiberId) {
173
+ const result = this.sql`
174
+ SELECT * FROM cf_agents_fibers WHERE id = ${fiberId}
175
+ `;
176
+ return result && result.length > 0 ? result[0] : null;
177
+ }
178
+ /** @internal */ _safeJsonParse(value) {
179
+ if (value === null) return null;
180
+ try {
181
+ return JSON.parse(value);
182
+ } catch {
183
+ return null;
184
+ }
185
+ }
186
+ /** @internal */ _toFiberState(raw) {
187
+ return {
188
+ id: raw.id,
189
+ callback: raw.callback,
190
+ payload: this._safeJsonParse(raw.payload),
191
+ snapshot: this._safeJsonParse(raw.snapshot),
192
+ status: raw.status,
193
+ retryCount: raw.retry_count,
194
+ maxRetries: raw.max_retries,
195
+ result: this._safeJsonParse(raw.result),
196
+ error: raw.error,
197
+ startedAt: raw.started_at,
198
+ updatedAt: raw.updated_at,
199
+ completedAt: raw.completed_at,
200
+ createdAt: raw.created_at
201
+ };
202
+ }
203
+ /** @internal */ async _startFiber(id, methodName, payload, maxRetries) {
204
+ const disposeKeepAlive = await this.keepAlive();
205
+ await this._runFiber(id, methodName, payload, maxRetries, disposeKeepAlive);
206
+ }
207
+ /** @internal */ async _runFiber(id, methodName, payload, maxRetries, disposeKeepAlive) {
208
+ try {
209
+ while (true) {
210
+ const fiber = this._getRawFiber(id);
211
+ if (!fiber || fiber.status === "cancelled") {
212
+ this._fiberDebug("fiber=%s exiting: %s", id, !fiber ? "not found" : "cancelled");
213
+ return;
214
+ }
215
+ try {
216
+ await fiberContext.run({ fiberId: id }, async () => {
217
+ const snapshot = this._safeJsonParse(fiber.snapshot);
218
+ const retryCount = fiber.retry_count;
219
+ const callback = this[methodName];
220
+ if (typeof callback !== "function") throw new Error(`Fiber method ${methodName} not found`);
221
+ const result = await callback.call(this, payload, {
222
+ id,
223
+ snapshot,
224
+ retryCount
225
+ });
226
+ const now = Date.now();
227
+ this.sql`
228
+ UPDATE cf_agents_fibers
229
+ SET status = 'completed',
230
+ result = ${JSON.stringify(result ?? null)},
231
+ completed_at = ${now},
232
+ updated_at = ${now}
233
+ WHERE id = ${id}
234
+ `;
235
+ this._fiberDebug("fiber=%s completed method=%s", id, methodName);
236
+ try {
237
+ await this.onFiberComplete({
238
+ id,
239
+ methodName,
240
+ payload,
241
+ result
242
+ });
243
+ } catch (e) {
244
+ console.error("Error in onFiberComplete:", e);
245
+ }
246
+ });
247
+ return;
248
+ } catch (e) {
249
+ const now = Date.now();
250
+ const newRetryCount = (this._getRawFiber(id)?.retry_count ?? 0) + 1;
251
+ if (newRetryCount > maxRetries) {
252
+ const errorMsg = e instanceof Error ? e.message : String(e);
253
+ this.sql`
254
+ UPDATE cf_agents_fibers
255
+ SET status = 'failed',
256
+ error = ${errorMsg},
257
+ retry_count = ${newRetryCount},
258
+ updated_at = ${now}
259
+ WHERE id = ${id}
260
+ `;
261
+ this._fiberDebug("fiber=%s failed after %d retries: %s", id, newRetryCount, errorMsg);
262
+ return;
263
+ }
264
+ this.sql`
265
+ UPDATE cf_agents_fibers
266
+ SET retry_count = ${newRetryCount}, updated_at = ${now}
267
+ WHERE id = ${id}
268
+ `;
269
+ this._fiberDebug("fiber=%s retrying (%d/%d)", id, newRetryCount, maxRetries);
270
+ continue;
271
+ }
272
+ }
273
+ } finally {
274
+ this._fiberActiveFibers.delete(id);
275
+ disposeKeepAlive();
276
+ }
277
+ }
278
+ /** @internal */ async _checkInterruptedFibers() {
279
+ if (this._fiberRecoveryInProgress) return;
280
+ this._fiberRecoveryInProgress = true;
281
+ try {
282
+ const runningFibers = this.sql`
283
+ SELECT * FROM cf_agents_fibers
284
+ WHERE status = 'running'
285
+ ORDER BY created_at ASC
286
+ `;
287
+ if (!runningFibers || runningFibers.length === 0) return;
288
+ const interrupted = [];
289
+ for (const fiber of runningFibers) {
290
+ if (this._fiberActiveFibers.has(fiber.id)) continue;
291
+ const newRetryCount = fiber.retry_count + 1;
292
+ const now = Date.now();
293
+ if (newRetryCount > fiber.max_retries) {
294
+ this.sql`
295
+ UPDATE cf_agents_fibers
296
+ SET status = 'failed',
297
+ error = 'max retries exceeded (eviction recovery)',
298
+ retry_count = ${newRetryCount},
299
+ updated_at = ${now}
300
+ WHERE id = ${fiber.id}
301
+ `;
302
+ this._fiberDebug("fiber=%s max retries exceeded on recovery", fiber.id);
303
+ } else {
304
+ this.sql`
305
+ UPDATE cf_agents_fibers
306
+ SET status = 'interrupted',
307
+ retry_count = ${newRetryCount},
308
+ updated_at = ${now}
309
+ WHERE id = ${fiber.id}
310
+ `;
311
+ interrupted.push({
312
+ id: fiber.id,
313
+ methodName: fiber.callback,
314
+ payload: this._safeJsonParse(fiber.payload),
315
+ snapshot: this._safeJsonParse(fiber.snapshot),
316
+ retryCount: newRetryCount
317
+ });
318
+ }
319
+ }
320
+ if (interrupted.length > 0) {
321
+ this._fiberDebug("recovering %d interrupted fibers", interrupted.length);
322
+ this._cleanupOrphanedHeartbeats();
323
+ try {
324
+ await this.onFibersRecovered(interrupted);
325
+ } catch (e) {
326
+ console.error("Error in onFibersRecovered:", e);
327
+ }
328
+ }
329
+ } finally {
330
+ this._fiberRecoveryInProgress = false;
331
+ }
332
+ }
333
+ /** @internal */ _cleanupOrphanedHeartbeats() {
334
+ this.sql`
335
+ DELETE FROM cf_agents_schedules
336
+ WHERE callback = '_cf_fiberHeartbeat'
337
+ `;
338
+ this._fiberDebug("cleaned up orphaned heartbeat schedules");
339
+ }
340
+ /** @internal */ _maybeCleanupFibers() {
341
+ const now = Date.now();
342
+ if (now - this._fiberLastCleanupTime < FIBER_CLEANUP_INTERVAL_MS) return;
343
+ this._fiberLastCleanupTime = now;
344
+ const completedCutoff = now - FIBER_CLEANUP_COMPLETED_MS;
345
+ const failedCutoff = now - FIBER_CLEANUP_FAILED_MS;
346
+ this.sql`
347
+ DELETE FROM cf_agents_fibers
348
+ WHERE (status = 'completed' AND completed_at < ${completedCutoff})
349
+ OR (status = 'failed' AND updated_at < ${failedCutoff})
350
+ OR (status = 'cancelled' AND updated_at < ${completedCutoff})
351
+ `;
352
+ this._fiberDebug("cleanup: checked for old completed/failed/cancelled fibers");
353
+ }
354
+ }
355
+ return FiberAgent;
356
+ }
357
+ /**
358
+ * Keep a Durable Object alive via a scheduled heartbeat.
359
+ * Returns a disposer function that cancels the heartbeat.
360
+ *
361
+ * Standalone version usable by any Agent subclass without requiring
362
+ * the full fiber mixin. The agent must have a no-op method with the
363
+ * given callbackName for the scheduler to invoke.
364
+ *
365
+ * @param agent - The agent instance (must have scheduleEvery and cancelSchedule)
366
+ * @param callbackName - Name of a no-op method on the agent class (must exist)
367
+ */
368
+ async function keepAlive(agent, callbackName) {
369
+ const heartbeatSeconds = Math.ceil(KEEP_ALIVE_INTERVAL_MS / 1e3);
370
+ const schedule = await agent.scheduleEvery(heartbeatSeconds, callbackName);
371
+ let disposed = false;
372
+ return () => {
373
+ if (disposed) return;
374
+ disposed = true;
375
+ agent.cancelSchedule(schedule.id);
376
+ };
377
+ }
378
+
379
+ //#endregion
380
+ export { keepAlive, withFibers };
381
+ //# sourceMappingURL=forever.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forever.js","names":[],"sources":["../../src/experimental/forever.ts"],"sourcesContent":["/**\n * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n * !! WARNING: EXPERIMENTAL — DO NOT USE IN PRODUCTION !!\n * !! !!\n * !! This API is under active development and WILL break between !!\n * !! releases. Method names, types, behavior, and the mixin signature !!\n * !! are all subject to change without notice. !!\n * !! !!\n * !! If you use this, pin your agents version and expect to rewrite !!\n * !! your code when upgrading. !!\n * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n *\n * Experimental fiber mixin for durable long-running execution.\n *\n * Usage:\n * import { Agent } from \"agents\";\n * import { withFibers } from \"agents/experimental/forever\";\n *\n * class MyAgent extends withFibers(Agent)<Env, State> {\n * async doWork(payload, fiberCtx) { ... }\n * }\n *\n * This mixin adds:\n * - keepAlive() — keep the DO alive via scheduled heartbeats\n * - spawnFiber() — fire-and-forget durable execution\n * - stashFiber() — checkpoint progress that survives eviction\n * - cancelFiber() / getFiber() — manage running fibers\n * - onFiberComplete / onFiberRecovered / onFibersRecovered — lifecycle hooks\n *\n * @experimental This API is not yet stable and may change.\n */\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { nanoid } from \"nanoid\";\nimport type { Agent } from \"../index\";\n\nconsole.warn(\n \"[agents/experimental/forever] WARNING: You are using an experimental API that WILL break between releases. Do not use in production.\"\n);\n\n// ── Types ─────────────────────────────────────────────────────────────\n\nexport type FiberState = {\n id: string;\n callback: string;\n payload: unknown;\n snapshot: unknown | null;\n status: \"running\" | \"completed\" | \"failed\" | \"interrupted\" | \"cancelled\";\n retryCount: number;\n maxRetries: number;\n result: unknown | null;\n error: string | null;\n startedAt: number | null;\n updatedAt: number | null;\n completedAt: number | null;\n createdAt: number;\n};\n\nexport type FiberRecoveryContext = {\n id: string;\n methodName: string;\n payload: unknown;\n snapshot: unknown | null;\n retryCount: number;\n};\n\nexport type FiberContext = {\n id: string;\n snapshot: unknown | null;\n retryCount: number;\n};\n\nexport type FiberCompleteContext = {\n id: string;\n methodName: string;\n payload: unknown;\n result: unknown;\n};\n\n// ── Internal types ────────────────────────────────────────────────────\n\ntype RawFiberRow = {\n id: string;\n callback: string;\n payload: string | null;\n snapshot: string | null;\n status: string;\n retry_count: number;\n max_retries: number;\n result: string | null;\n error: string | null;\n started_at: number | null;\n updated_at: number | null;\n completed_at: number | null;\n created_at: number;\n};\n\n// ── Constants ─────────────────────────────────────────────────────────\n\nconst KEEP_ALIVE_INTERVAL_MS = 10_000;\nconst FIBER_CLEANUP_INTERVAL_MS = 10 * 60 * 1000;\nconst FIBER_CLEANUP_COMPLETED_MS = 24 * 60 * 60 * 1000;\nconst FIBER_CLEANUP_FAILED_MS = 7 * 24 * 60 * 60 * 1000;\n\nconst fiberContext = new AsyncLocalStorage<{ fiberId: string }>();\n\n// ── Mixin ─────────────────────────────────────────────────────────────\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any -- mixin constructor constraint\ntype Constructor<T = object> = new (...args: any[]) => T;\n\ntype AgentLike = Constructor<\n Pick<\n Agent<Cloudflare.Env>,\n \"sql\" | \"scheduleEvery\" | \"cancelSchedule\" | \"alarm\"\n >\n>;\n\nexport function withFibers<TBase extends AgentLike>(\n Base: TBase,\n options?: { debugFibers?: boolean }\n) {\n const debugEnabled = options?.debugFibers ?? false;\n\n class FiberAgent extends Base {\n // ── Fiber state ───────────────────────────────────────────────\n\n /** @internal */ _fiberActiveFibers = new Set<string>();\n /** @internal */ _fiberRecoveryInProgress = false;\n /** @internal */ _fiberLastCleanupTime = 0;\n\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any -- mixin constructor\n constructor(...args: any[]) {\n super(...args);\n\n // Create the fibers table\n (this as unknown as Agent<Cloudflare.Env>).sql`\n CREATE TABLE IF NOT EXISTS cf_agents_fibers (\n id TEXT PRIMARY KEY NOT NULL,\n callback TEXT NOT NULL,\n payload TEXT,\n snapshot TEXT,\n status TEXT NOT NULL DEFAULT 'running'\n CHECK(status IN ('running', 'completed', 'failed', 'interrupted', 'cancelled')),\n retry_count INTEGER NOT NULL DEFAULT 0,\n max_retries INTEGER NOT NULL DEFAULT 3,\n result TEXT,\n error TEXT,\n started_at INTEGER,\n updated_at INTEGER,\n completed_at INTEGER,\n created_at INTEGER NOT NULL\n )\n `;\n }\n\n // ── Debug logging ─────────────────────────────────────────────\n\n /** @internal */ _fiberDebug(msg: string, ...args: unknown[]) {\n if (debugEnabled) {\n console.debug(`[fiber] ${msg}`, ...args);\n }\n }\n\n // ── Heartbeat callback ────────────────────────────────────────\n\n // Note: TypeScript `private` is compile-time only. The scheduler\n // dispatches callbacks by string name (`this[row.callback]`),\n // which works at runtime. The name is stable (stored in SQLite).\n /** @internal */ async _cf_fiberHeartbeat() {\n await this._checkInterruptedFibers();\n }\n\n // ── Public API ────────────────────────────────────────────────\n\n async keepAlive(): Promise<() => void> {\n const heartbeatSeconds = Math.ceil(KEEP_ALIVE_INTERVAL_MS / 1000);\n const schedule = await (\n this as unknown as Agent<Cloudflare.Env>\n ).scheduleEvery(\n heartbeatSeconds,\n \"_cf_fiberHeartbeat\" as keyof Agent<Cloudflare.Env>\n );\n\n this._fiberDebug(\"keepAlive started, schedule=%s\", schedule.id);\n\n let disposed = false;\n return () => {\n if (disposed) return;\n disposed = true;\n this._fiberDebug(\"keepAlive disposed, schedule=%s\", schedule.id);\n void this.cancelSchedule(schedule.id);\n };\n }\n\n spawnFiber(\n methodName: keyof this,\n payload?: unknown,\n options?: { maxRetries?: number }\n ): string {\n this._maybeCleanupFibers();\n\n const name = methodName as string;\n if (typeof this[methodName] !== \"function\") {\n throw new Error(`this.${name} is not a function`);\n }\n\n const id = nanoid();\n const now = Date.now();\n const maxRetries = options?.maxRetries ?? 3;\n\n (this as unknown as Agent<Cloudflare.Env>).sql`\n INSERT INTO cf_agents_fibers (id, callback, payload, status, max_retries, retry_count, started_at, updated_at, created_at)\n VALUES (${id}, ${name}, ${JSON.stringify(payload ?? null)}, 'running', ${maxRetries}, 0, ${now}, ${now}, ${now})\n `;\n\n this._fiberActiveFibers.add(id);\n this._fiberDebug(\n \"spawned fiber=%s method=%s maxRetries=%d\",\n id,\n name,\n maxRetries\n );\n\n void this._startFiber(id, name, payload, maxRetries).catch((e) => {\n console.error(`Unhandled error in fiber ${id}:`, e);\n });\n\n return id;\n }\n\n stashFiber(data: unknown): void {\n const ctx = fiberContext.getStore();\n if (!ctx) {\n throw new Error(\n \"stashFiber() can only be called within a fiber execution context\"\n );\n }\n const now = Date.now();\n (this as unknown as Agent<Cloudflare.Env>).sql`\n UPDATE cf_agents_fibers\n SET snapshot = ${JSON.stringify(data)}, updated_at = ${now}\n WHERE id = ${ctx.fiberId}\n `;\n this._fiberDebug(\"stash fiber=%s\", ctx.fiberId);\n }\n\n /**\n * Note: cancellation is cooperative. The status is set to 'cancelled'\n * in SQLite, and the _runFiber retry loop checks for this status at\n * the top of each iteration.\n */\n cancelFiber(fiberId: string): boolean {\n const fiber = this._getRawFiber(fiberId);\n if (!fiber) return false;\n if (\n fiber.status === \"completed\" ||\n fiber.status === \"failed\" ||\n fiber.status === \"cancelled\"\n ) {\n return false;\n }\n\n const now = Date.now();\n (this as unknown as Agent<Cloudflare.Env>).sql`\n UPDATE cf_agents_fibers\n SET status = 'cancelled', updated_at = ${now}\n WHERE id = ${fiberId}\n `;\n this._fiberActiveFibers.delete(fiberId);\n this._fiberDebug(\"cancelled fiber=%s\", fiberId);\n return true;\n }\n\n getFiber(fiberId: string): FiberState | null {\n const raw = this._getRawFiber(fiberId);\n if (!raw) return null;\n return this._toFiberState(raw);\n }\n\n restartFiber(fiberId: string): void {\n const fiber = this._getRawFiber(fiberId);\n if (!fiber) {\n throw new Error(`Fiber ${fiberId} not found`);\n }\n\n const now = Date.now();\n (this as unknown as Agent<Cloudflare.Env>).sql`\n UPDATE cf_agents_fibers\n SET status = 'running', started_at = ${now}, updated_at = ${now}\n WHERE id = ${fiberId}\n `;\n\n this._fiberActiveFibers.add(fiberId);\n this._fiberDebug(\n \"restarting fiber=%s method=%s retryCount=%d\",\n fiberId,\n fiber.callback,\n fiber.retry_count\n );\n\n const parsedPayload = fiber.payload\n ? JSON.parse(fiber.payload)\n : undefined;\n\n void this._startFiber(\n fiberId,\n fiber.callback,\n parsedPayload,\n fiber.max_retries\n ).catch((e) => {\n console.error(`Error restarting fiber ${fiberId}:`, e);\n });\n }\n\n // ── Lifecycle hooks (override in subclass) ────────────────────\n\n /**\n * Manually trigger fiber recovery check.\n * In production, this runs automatically via the heartbeat schedule.\n * Useful for testing or when you need immediate recovery after\n * detecting an eviction.\n */\n async checkFibers(): Promise<void> {\n await this._checkInterruptedFibers();\n }\n\n // oxlint-disable-next-line @typescript-eslint/no-unused-vars -- overridable hook\n onFiberComplete(_ctx: FiberCompleteContext): void | Promise<void> {}\n\n onFiberRecovered(ctx: FiberRecoveryContext): void | Promise<void> {\n this.restartFiber(ctx.id);\n }\n\n async onFibersRecovered(fibers: FiberRecoveryContext[]): Promise<void> {\n for (const fiber of fibers) {\n await this.onFiberRecovered(fiber);\n }\n }\n\n // ── Private implementation ────────────────────────────────────\n\n /** @internal */ _getRawFiber(fiberId: string): RawFiberRow | null {\n const result = (this as unknown as Agent<Cloudflare.Env>)\n .sql<RawFiberRow>`\n SELECT * FROM cf_agents_fibers WHERE id = ${fiberId}\n `;\n return result && result.length > 0 ? result[0] : null;\n }\n\n /** @internal */ _safeJsonParse(value: string | null): unknown {\n if (value === null) return null;\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n }\n\n /** @internal */ _toFiberState(raw: RawFiberRow): FiberState {\n return {\n id: raw.id,\n callback: raw.callback,\n payload: this._safeJsonParse(raw.payload),\n snapshot: this._safeJsonParse(raw.snapshot),\n status: raw.status as FiberState[\"status\"],\n retryCount: raw.retry_count,\n maxRetries: raw.max_retries,\n result: this._safeJsonParse(raw.result),\n error: raw.error,\n startedAt: raw.started_at,\n updatedAt: raw.updated_at,\n completedAt: raw.completed_at,\n createdAt: raw.created_at\n };\n }\n\n /** @internal */ async _startFiber(\n id: string,\n methodName: string,\n payload: unknown,\n maxRetries: number\n ): Promise<void> {\n const disposeKeepAlive = await this.keepAlive();\n await this._runFiber(\n id,\n methodName,\n payload,\n maxRetries,\n disposeKeepAlive\n );\n }\n\n /** @internal */ async _runFiber(\n id: string,\n methodName: string,\n payload: unknown,\n maxRetries: number,\n disposeKeepAlive: () => void\n ): Promise<void> {\n try {\n while (true) {\n const fiber = this._getRawFiber(id);\n if (!fiber || fiber.status === \"cancelled\") {\n this._fiberDebug(\n \"fiber=%s exiting: %s\",\n id,\n !fiber ? \"not found\" : \"cancelled\"\n );\n return;\n }\n\n try {\n await fiberContext.run({ fiberId: id }, async () => {\n const snapshot = this._safeJsonParse(fiber.snapshot);\n const retryCount = fiber.retry_count;\n\n const callback = this[methodName as keyof this];\n if (typeof callback !== \"function\") {\n throw new Error(`Fiber method ${methodName} not found`);\n }\n\n const result = await (\n callback as (p: unknown, ctx: FiberContext) => Promise<unknown>\n ).call(this, payload, { id, snapshot, retryCount });\n\n const now = Date.now();\n (this as unknown as Agent<Cloudflare.Env>).sql`\n UPDATE cf_agents_fibers\n SET status = 'completed',\n result = ${JSON.stringify(result ?? null)},\n completed_at = ${now},\n updated_at = ${now}\n WHERE id = ${id}\n `;\n\n this._fiberDebug(\"fiber=%s completed method=%s\", id, methodName);\n\n try {\n await this.onFiberComplete({\n id,\n methodName,\n payload,\n result\n });\n } catch (e) {\n console.error(\"Error in onFiberComplete:\", e);\n }\n });\n\n return;\n } catch (e) {\n const now = Date.now();\n const currentFiber = this._getRawFiber(id);\n const newRetryCount = (currentFiber?.retry_count ?? 0) + 1;\n\n if (newRetryCount > maxRetries) {\n const errorMsg = e instanceof Error ? e.message : String(e);\n (this as unknown as Agent<Cloudflare.Env>).sql`\n UPDATE cf_agents_fibers\n SET status = 'failed',\n error = ${errorMsg},\n retry_count = ${newRetryCount},\n updated_at = ${now}\n WHERE id = ${id}\n `;\n this._fiberDebug(\n \"fiber=%s failed after %d retries: %s\",\n id,\n newRetryCount,\n errorMsg\n );\n return;\n }\n\n (this as unknown as Agent<Cloudflare.Env>).sql`\n UPDATE cf_agents_fibers\n SET retry_count = ${newRetryCount}, updated_at = ${now}\n WHERE id = ${id}\n `;\n this._fiberDebug(\n \"fiber=%s retrying (%d/%d)\",\n id,\n newRetryCount,\n maxRetries\n );\n continue;\n }\n }\n } finally {\n this._fiberActiveFibers.delete(id);\n disposeKeepAlive();\n }\n }\n\n /** @internal */ async _checkInterruptedFibers(): Promise<void> {\n if (this._fiberRecoveryInProgress) return;\n this._fiberRecoveryInProgress = true;\n\n try {\n const runningFibers = (this as unknown as Agent<Cloudflare.Env>)\n .sql<RawFiberRow>`\n SELECT * FROM cf_agents_fibers\n WHERE status = 'running'\n ORDER BY created_at ASC\n `;\n\n if (!runningFibers || runningFibers.length === 0) return;\n\n const interrupted: FiberRecoveryContext[] = [];\n\n for (const fiber of runningFibers) {\n if (this._fiberActiveFibers.has(fiber.id)) continue;\n\n const newRetryCount = fiber.retry_count + 1;\n const now = Date.now();\n\n if (newRetryCount > fiber.max_retries) {\n (this as unknown as Agent<Cloudflare.Env>).sql`\n UPDATE cf_agents_fibers\n SET status = 'failed',\n error = 'max retries exceeded (eviction recovery)',\n retry_count = ${newRetryCount},\n updated_at = ${now}\n WHERE id = ${fiber.id}\n `;\n this._fiberDebug(\n \"fiber=%s max retries exceeded on recovery\",\n fiber.id\n );\n } else {\n (this as unknown as Agent<Cloudflare.Env>).sql`\n UPDATE cf_agents_fibers\n SET status = 'interrupted',\n retry_count = ${newRetryCount},\n updated_at = ${now}\n WHERE id = ${fiber.id}\n `;\n\n interrupted.push({\n id: fiber.id,\n methodName: fiber.callback,\n payload: this._safeJsonParse(fiber.payload),\n snapshot: this._safeJsonParse(fiber.snapshot),\n retryCount: newRetryCount\n });\n }\n }\n\n if (interrupted.length > 0) {\n this._fiberDebug(\n \"recovering %d interrupted fibers\",\n interrupted.length\n );\n\n this._cleanupOrphanedHeartbeats();\n\n try {\n await this.onFibersRecovered(interrupted);\n } catch (e) {\n console.error(\"Error in onFibersRecovered:\", e);\n }\n }\n } finally {\n this._fiberRecoveryInProgress = false;\n }\n }\n\n /** @internal */ _cleanupOrphanedHeartbeats() {\n (this as unknown as Agent<Cloudflare.Env>).sql`\n DELETE FROM cf_agents_schedules\n WHERE callback = '_cf_fiberHeartbeat'\n `;\n this._fiberDebug(\"cleaned up orphaned heartbeat schedules\");\n }\n\n /** @internal */ _maybeCleanupFibers() {\n const now = Date.now();\n if (now - this._fiberLastCleanupTime < FIBER_CLEANUP_INTERVAL_MS) {\n return;\n }\n this._fiberLastCleanupTime = now;\n\n const completedCutoff = now - FIBER_CLEANUP_COMPLETED_MS;\n const failedCutoff = now - FIBER_CLEANUP_FAILED_MS;\n\n (this as unknown as Agent<Cloudflare.Env>).sql`\n DELETE FROM cf_agents_fibers\n WHERE (status = 'completed' AND completed_at < ${completedCutoff})\n OR (status = 'failed' AND updated_at < ${failedCutoff})\n OR (status = 'cancelled' AND updated_at < ${completedCutoff})\n `;\n\n this._fiberDebug(\n \"cleanup: checked for old completed/failed/cancelled fibers\"\n );\n }\n }\n\n return FiberAgent;\n}\n\n// ── Standalone keepAlive ──────────────────────────────────────────────\n\n/**\n * Keep a Durable Object alive via a scheduled heartbeat.\n * Returns a disposer function that cancels the heartbeat.\n *\n * Standalone version usable by any Agent subclass without requiring\n * the full fiber mixin. The agent must have a no-op method with the\n * given callbackName for the scheduler to invoke.\n *\n * @param agent - The agent instance (must have scheduleEvery and cancelSchedule)\n * @param callbackName - Name of a no-op method on the agent class (must exist)\n */\nexport async function keepAlive(\n agent: Pick<Agent<Cloudflare.Env>, \"scheduleEvery\" | \"cancelSchedule\">,\n callbackName: string\n): Promise<() => void> {\n const heartbeatSeconds = Math.ceil(KEEP_ALIVE_INTERVAL_MS / 1000);\n const schedule = await agent.scheduleEvery(\n heartbeatSeconds,\n callbackName as keyof Agent<Cloudflare.Env>\n );\n\n let disposed = false;\n return () => {\n if (disposed) return;\n disposed = true;\n void agent.cancelSchedule(schedule.id);\n };\n}\n"],"mappings":"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"}