agentenkit 0.7.0 → 0.11.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/dist/adapters/flow.d.ts +160 -0
- package/dist/adapters/flow.d.ts.map +1 -0
- package/dist/adapters/flow.js +484 -0
- package/dist/adapters/flow.js.map +1 -0
- package/dist/adapters/mcp.js +1 -1
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/core/builtin/index.js +1 -1
- package/dist/core/builtin/index.js.map +1 -1
- package/dist/core/builtin/run.d.ts +2 -0
- package/dist/core/builtin/run.d.ts.map +1 -1
- package/dist/core/builtin/run.js.map +1 -1
- package/dist/core/builtin/sandbox-tools.d.ts.map +1 -1
- package/dist/core/builtin/sandbox-tools.js +57 -31
- package/dist/core/builtin/sandbox-tools.js.map +1 -1
- package/dist/core/builtin/sandbox.d.ts +28 -2
- package/dist/core/builtin/sandbox.d.ts.map +1 -1
- package/dist/core/builtin/sandbox.js +103 -8
- package/dist/core/builtin/sandbox.js.map +1 -1
- package/dist/core/builtin/web.d.ts +6 -0
- package/dist/core/builtin/web.d.ts.map +1 -1
- package/dist/core/builtin/web.js +58 -6
- package/dist/core/builtin/web.js.map +1 -1
- package/dist/core/cache.d.ts.map +1 -1
- package/dist/core/cache.js +3 -6
- package/dist/core/cache.js.map +1 -1
- package/dist/core/context.d.ts +18 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +132 -47
- package/dist/core/context.js.map +1 -1
- package/dist/core/engine.d.ts.map +1 -1
- package/dist/core/engine.js +92 -48
- package/dist/core/engine.js.map +1 -1
- package/dist/core/hitl.d.ts.map +1 -1
- package/dist/core/hitl.js +5 -1
- package/dist/core/hitl.js.map +1 -1
- package/dist/core/loop.d.ts +15 -5
- package/dist/core/loop.d.ts.map +1 -1
- package/dist/core/loop.js +51 -46
- package/dist/core/loop.js.map +1 -1
- package/dist/core/sdk.d.ts +71 -0
- package/dist/core/sdk.d.ts.map +1 -0
- package/dist/core/sdk.js +281 -0
- package/dist/core/sdk.js.map +1 -0
- package/dist/core/settle.d.ts.map +1 -1
- package/dist/core/settle.js +13 -1
- package/dist/core/settle.js.map +1 -1
- package/dist/core/stream.d.ts +4 -4
- package/dist/core/stream.d.ts.map +1 -1
- package/dist/core/stream.js +14 -7
- package/dist/core/stream.js.map +1 -1
- package/dist/core/subagent.d.ts +255 -20
- package/dist/core/subagent.d.ts.map +1 -1
- package/dist/core/subagent.js +1 -1
- package/dist/core/subagent.js.map +1 -1
- package/dist/core/tools.d.ts +7 -9
- package/dist/core/tools.d.ts.map +1 -1
- package/dist/core/tools.js +7 -4
- package/dist/core/tools.js.map +1 -1
- package/dist/core/types.d.ts +21 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +9 -0
- package/dist/core/types.js.map +1 -1
- package/dist/core/usage.d.ts +14 -4
- package/dist/core/usage.d.ts.map +1 -1
- package/dist/core/usage.js +31 -4
- package/dist/core/usage.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/ports/runtime.d.ts +23 -2
- package/dist/ports/runtime.d.ts.map +1 -1
- package/dist/ports/sandbox.d.ts +10 -0
- package/dist/ports/sandbox.d.ts.map +1 -1
- package/dist/ports/sandbox.js.map +1 -1
- package/dist/ports/tools.d.ts +22 -2
- package/dist/ports/tools.d.ts.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +19 -1
- package/dist/runtime.js.map +1 -1
- package/package.json +13 -5
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { Duration, Flow, FlowEnv, FlowFunction } from '@clevix/flow';
|
|
2
|
+
import { type EnqueueOptions, type Queue, type QueueStats, type QueuedJob } from '../ports/queue.js';
|
|
3
|
+
import type { AgentCore } from '../ports/runtime.js';
|
|
4
|
+
import type { EventBus } from '../ports/bus.js';
|
|
5
|
+
import type { Kv } from '../ports/kv.js';
|
|
6
|
+
import { type RunStreams, type StreamEnd, type StreamEvent, type StreamItem, type StreamMeta, type StreamSnapshot } from '../ports/streams.js';
|
|
7
|
+
import type { AgentEvent, RunJob } from '../core/types.js';
|
|
8
|
+
/** The function ID the queue starts and the worker serves, unless both are
|
|
9
|
+
* given another. */
|
|
10
|
+
export declare const FLOW_FUNCTION_ID = "agentenkit-run";
|
|
11
|
+
/** The event the function lists as its trigger. Nothing sends it: runs come
|
|
12
|
+
* from `flow.start`. Flow needs every function to have a trigger. */
|
|
13
|
+
export declare const FLOW_JOB_EVENT = "agentenkit/job";
|
|
14
|
+
/** What one run of the function carries. */
|
|
15
|
+
export interface FlowJobData {
|
|
16
|
+
job: RunJob;
|
|
17
|
+
/** The singleton key: the enqueue's key, or the job's dispatch id. */
|
|
18
|
+
key: string;
|
|
19
|
+
/** In seconds, for `priority.run`. */
|
|
20
|
+
priority: number;
|
|
21
|
+
/** Epoch ms. The run sleeps until then before it runs the job. */
|
|
22
|
+
notBefore?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface FlowQueueOptions {
|
|
25
|
+
/** The function to start. Defaults to `agentenkit-run`. Must match the
|
|
26
|
+
* ID given to `agentRunFunction`. */
|
|
27
|
+
functionId?: string;
|
|
28
|
+
/** Cloudflare Workers: the handler's `env`, where the Flow keys live. */
|
|
29
|
+
env?: FlowEnv;
|
|
30
|
+
}
|
|
31
|
+
/** Queue adapter over Clevix Flow.
|
|
32
|
+
*
|
|
33
|
+
* Each job is one run of one Flow function (see `agentRunFunction`),
|
|
34
|
+
* started with `flow.start`. A key is the function's singleton key in
|
|
35
|
+
* `skip` mode, so at most one live run holds it, and it is free again as
|
|
36
|
+
* soon as that run ends. A delay is a sleep at the start of the run, so the
|
|
37
|
+
* key stays held while the job waits. `cancel` cancels the run that holds a
|
|
38
|
+
* key. `find` and `stats` read Flow's runs index, which lags the runs by a
|
|
39
|
+
* few seconds.
|
|
40
|
+
*
|
|
41
|
+
* Needs Flow SDK 0.7.0 or later and an engine with the run routes. */
|
|
42
|
+
export declare class FlowQueue implements Queue {
|
|
43
|
+
private readonly flow;
|
|
44
|
+
private readonly opts;
|
|
45
|
+
private readonly functionId;
|
|
46
|
+
constructor(flow: Flow, opts?: FlowQueueOptions);
|
|
47
|
+
private get env();
|
|
48
|
+
enqueue(job: RunJob, opts?: EnqueueOptions): Promise<void>;
|
|
49
|
+
cancel(key: string): Promise<void>;
|
|
50
|
+
find(runId: string): Promise<QueuedJob | null>;
|
|
51
|
+
stats(): Promise<QueueStats>;
|
|
52
|
+
}
|
|
53
|
+
export interface AgentRunFunctionOptions {
|
|
54
|
+
/** Defaults to `agentenkit-run`. Must match `FlowQueue`'s `functionId`.
|
|
55
|
+
* At most 56 characters, since the failure function adds `-failure`. */
|
|
56
|
+
id?: string;
|
|
57
|
+
/** How many jobs of one `partitionKey` run at once. Unset: no limit.
|
|
58
|
+
* Job priorities only take effect under a limit. */
|
|
59
|
+
concurrency?: number;
|
|
60
|
+
/** Flow's retries for a job that throws. Flow's default when unset. */
|
|
61
|
+
retries?: number;
|
|
62
|
+
/** How long Flow waits for one job. Defaults to 14 minutes, Flow's most.
|
|
63
|
+
* When a job runs longer, Flow stops waiting and tries the step again,
|
|
64
|
+
* but nothing stops the first call: it keeps running. The retry finds the
|
|
65
|
+
* run lock held and does nothing, so keep segments shorter than this
|
|
66
|
+
* (`segmentTimeoutMs`). */
|
|
67
|
+
stepTimeout?: Duration;
|
|
68
|
+
}
|
|
69
|
+
/** The Flow function that runs `FlowQueue`'s jobs. Serve it with the app's
|
|
70
|
+
* other Flow functions.
|
|
71
|
+
*
|
|
72
|
+
* A job runs in one step. `handleJob` only throws for what a queue should
|
|
73
|
+
* retry (an unknown agent, a store that is down), so Flow's retries cover
|
|
74
|
+
* delivery; agentic-kit retries a failed run itself. When Flow gives up, the
|
|
75
|
+
* failure function hands the job to `handleDeadJob`, which fails the run. */
|
|
76
|
+
export declare function agentRunFunction(flow: Flow, core: Pick<AgentCore, 'worker'>, opts?: AgentRunFunctionOptions): FlowFunction;
|
|
77
|
+
/** The channel a thread's events go on. */
|
|
78
|
+
export declare function flowThreadChannel(threadId: string): Promise<string>;
|
|
79
|
+
export interface FlowBusOptions {
|
|
80
|
+
/** Holds events too big for one realtime message (over about 60 KB). */
|
|
81
|
+
kv: Kv;
|
|
82
|
+
/** How often each subscriber gets a HEARTBEAT notice. Default one minute. */
|
|
83
|
+
heartbeatMs?: number;
|
|
84
|
+
/** How long `subscribe` waits for its socket to open before it returns
|
|
85
|
+
* anyway. Default 10 seconds. */
|
|
86
|
+
openTimeoutMs?: number;
|
|
87
|
+
/** Cloudflare Workers: the handler's `env`, where the Flow keys live. */
|
|
88
|
+
env?: FlowEnv;
|
|
89
|
+
/** A WebSocket class, where the runtime has none. */
|
|
90
|
+
WebSocket?: typeof WebSocket;
|
|
91
|
+
}
|
|
92
|
+
/** EventBus over Clevix Flow realtime.
|
|
93
|
+
*
|
|
94
|
+
* Each thread has its own channel, and a process opens one socket per thread
|
|
95
|
+
* it follows, shared by every subscriber in the process. Realtime keeps
|
|
96
|
+
* nothing and drops what comes while a socket reconnects: the bus is
|
|
97
|
+
* at-most-once anyway, and a follower fills any gap from the log. An event
|
|
98
|
+
* over about 60 KB waits in the kv for a minute, and the channel carries its
|
|
99
|
+
* key. While anyone subscribes, each subscriber gets a HEARTBEAT notice
|
|
100
|
+
* (seq 0) every `heartbeatMs`, as with the other buses.
|
|
101
|
+
*
|
|
102
|
+
* Needs Flow SDK 0.7.0 or later. */
|
|
103
|
+
export declare class FlowBus implements EventBus {
|
|
104
|
+
private readonly flow;
|
|
105
|
+
private readonly opts;
|
|
106
|
+
private readonly channels;
|
|
107
|
+
private heartbeat?;
|
|
108
|
+
constructor(flow: Flow, opts: FlowBusOptions);
|
|
109
|
+
private get env();
|
|
110
|
+
publish(threadId: string, event: AgentEvent): Promise<void>;
|
|
111
|
+
subscribe(threadId: string, handler: (event: AgentEvent) => void): Promise<() => void>;
|
|
112
|
+
/** Opens the thread's socket. `open` settles when it is open, or after
|
|
113
|
+
* `openTimeoutMs`: an event published before then may be missed, which a
|
|
114
|
+
* follower's gap read covers. */
|
|
115
|
+
private connect;
|
|
116
|
+
private receive;
|
|
117
|
+
private deliver;
|
|
118
|
+
private startHeartbeat;
|
|
119
|
+
private stopHeartbeat;
|
|
120
|
+
}
|
|
121
|
+
/** The Flow stream a run stream is kept in. */
|
|
122
|
+
export declare function flowStreamId(streamId: string): Promise<string>;
|
|
123
|
+
export interface FlowStreamsOptions {
|
|
124
|
+
/** Holds items too big for one stream item (over about 60 KB). */
|
|
125
|
+
kv: Kv;
|
|
126
|
+
/** How long such an item stays in the kv. Default 8 days: past Flow's
|
|
127
|
+
* longest time to live, so it outlives its stream. */
|
|
128
|
+
refTtlMs?: number;
|
|
129
|
+
/** Cloudflare Workers: the handler's `env`, where the Flow keys live. */
|
|
130
|
+
env?: FlowEnv;
|
|
131
|
+
/** A WebSocket class, where the runtime has none. */
|
|
132
|
+
WebSocket?: typeof WebSocket;
|
|
133
|
+
}
|
|
134
|
+
/** RunStreams over Clevix Flow streams.
|
|
135
|
+
*
|
|
136
|
+
* Flow keeps the items, gives offsets, and lets a reader start from any
|
|
137
|
+
* offset and follow live, which is what a run stream needs. An item over
|
|
138
|
+
* about 60 KB is kept in the kv and the stream carries its key; readers get
|
|
139
|
+
* the event back. Flow keeps a stream at most 7 days and a grace window at
|
|
140
|
+
* most 7 days, so longer times are cut to that. A stream holds at most
|
|
141
|
+
* 20,000 items and 32 MB; an append past that fails.
|
|
142
|
+
*
|
|
143
|
+
* Needs Flow SDK 0.7.0 or later. */
|
|
144
|
+
export declare class FlowStreams implements RunStreams {
|
|
145
|
+
private readonly flow;
|
|
146
|
+
private readonly opts;
|
|
147
|
+
constructor(flow: Flow, opts: FlowStreamsOptions);
|
|
148
|
+
private get env();
|
|
149
|
+
open(streamId: string, meta: StreamMeta, ttlMs: number): Promise<void>;
|
|
150
|
+
append(streamId: string, events: StreamEvent[]): Promise<string[]>;
|
|
151
|
+
read(streamId: string, after: string | null, signal?: AbortSignal): AsyncIterable<StreamItem>;
|
|
152
|
+
snapshot(streamId: string, after?: string | null): Promise<StreamSnapshot | null>;
|
|
153
|
+
close(streamId: string, end: StreamEnd, graceMs: number): Promise<void>;
|
|
154
|
+
delete(streamId: string): Promise<void>;
|
|
155
|
+
private call;
|
|
156
|
+
/** A Flow item as a run stream item: the event, read back from the kv when
|
|
157
|
+
* the stream holds its key. Null when that kv copy has expired. */
|
|
158
|
+
private decode;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../../src/adapters/flow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import { DuplicateJobError, PayloadTooLargeError } from '../ports/queue.js';
|
|
2
|
+
import { StreamClosedError, StreamGoneError, } from '../ports/streams.js';
|
|
3
|
+
import { isStreamEnd } from '../core/stream-events.js';
|
|
4
|
+
/** The function ID the queue starts and the worker serves, unless both are
|
|
5
|
+
* given another. */
|
|
6
|
+
export const FLOW_FUNCTION_ID = 'agentenkit-run';
|
|
7
|
+
/** The event the function lists as its trigger. Nothing sends it: runs come
|
|
8
|
+
* from `flow.start`. Flow needs every function to have a trigger. */
|
|
9
|
+
export const FLOW_JOB_EVENT = 'agentenkit/job';
|
|
10
|
+
/** A run's data is at most 1 MB on Flow. */
|
|
11
|
+
const MAX_DATA_BYTES = 1_000_000;
|
|
12
|
+
/** Flow's `priority.run` is in seconds, from -600 to 600. */
|
|
13
|
+
const PRIORITY_SECONDS = 60;
|
|
14
|
+
const MAX_PRIORITY = 600;
|
|
15
|
+
const ACTIVE = ['queued', 'running', 'sleeping', 'waiting', 'waiting_for_app', 'paused'];
|
|
16
|
+
/** Queue adapter over Clevix Flow.
|
|
17
|
+
*
|
|
18
|
+
* Each job is one run of one Flow function (see `agentRunFunction`),
|
|
19
|
+
* started with `flow.start`. A key is the function's singleton key in
|
|
20
|
+
* `skip` mode, so at most one live run holds it, and it is free again as
|
|
21
|
+
* soon as that run ends. A delay is a sleep at the start of the run, so the
|
|
22
|
+
* key stays held while the job waits. `cancel` cancels the run that holds a
|
|
23
|
+
* key. `find` and `stats` read Flow's runs index, which lags the runs by a
|
|
24
|
+
* few seconds.
|
|
25
|
+
*
|
|
26
|
+
* Needs Flow SDK 0.7.0 or later and an engine with the run routes. */
|
|
27
|
+
export class FlowQueue {
|
|
28
|
+
flow;
|
|
29
|
+
opts;
|
|
30
|
+
functionId;
|
|
31
|
+
constructor(flow, opts = {}) {
|
|
32
|
+
this.flow = flow;
|
|
33
|
+
this.opts = opts;
|
|
34
|
+
this.functionId = opts.functionId ?? FLOW_FUNCTION_ID;
|
|
35
|
+
}
|
|
36
|
+
get env() {
|
|
37
|
+
return this.opts.env ? { env: this.opts.env } : {};
|
|
38
|
+
}
|
|
39
|
+
async enqueue(job, opts) {
|
|
40
|
+
// An unkeyed job still needs a singleton key, or every unkeyed job would
|
|
41
|
+
// share one. Its dispatch id is its own; a second start of the same
|
|
42
|
+
// dispatch is the same job, so it is dropped without an error.
|
|
43
|
+
const key = opts?.key || job.dispatchId || crypto.randomUUID();
|
|
44
|
+
const data = {
|
|
45
|
+
job,
|
|
46
|
+
key,
|
|
47
|
+
priority: Math.max(-MAX_PRIORITY, Math.min(MAX_PRIORITY, (opts?.priority ?? 0) * PRIORITY_SECONDS)),
|
|
48
|
+
...(opts?.delaySeconds && opts.delaySeconds > 0
|
|
49
|
+
? { notBefore: Date.now() + Math.ceil(opts.delaySeconds * 1000) }
|
|
50
|
+
: {}),
|
|
51
|
+
};
|
|
52
|
+
if (new TextEncoder().encode(JSON.stringify(data)).length > MAX_DATA_BYTES)
|
|
53
|
+
throw new PayloadTooLargeError();
|
|
54
|
+
// Twice at most: a singleton answer with no active run means the run that
|
|
55
|
+
// held the key ended in between, so the key is free now.
|
|
56
|
+
for (let i = 0; i < 2; i++) {
|
|
57
|
+
const res = await this.flow.start(this.functionId, { data: data, ...this.env });
|
|
58
|
+
if (res.started)
|
|
59
|
+
return;
|
|
60
|
+
if (res.reason === 'singleton') {
|
|
61
|
+
if (res.activeRunId === null)
|
|
62
|
+
continue;
|
|
63
|
+
if (opts?.key)
|
|
64
|
+
throw new DuplicateJobError(opts.key);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (res.reason === 'paused')
|
|
68
|
+
throw new Error(`flow: function ${this.functionId} is paused`);
|
|
69
|
+
throw new Error(`flow: no function ${this.functionId}; is the app served and synced?`);
|
|
70
|
+
}
|
|
71
|
+
// Both tries found the key held by a run that had just ended. No run
|
|
72
|
+
// started, so the caller must hear it, or the job is lost.
|
|
73
|
+
throw new Error(`flow: no run started for key ${key}; the key was still being freed`);
|
|
74
|
+
}
|
|
75
|
+
async cancel(key) {
|
|
76
|
+
await this.flow.runs.cancel({ function: this.functionId, singletonKey: key }, this.env);
|
|
77
|
+
}
|
|
78
|
+
async find(runId) {
|
|
79
|
+
const page = await this.flow.runs.list({
|
|
80
|
+
function: this.functionId, tags: { runId }, status: ACTIVE, limit: 50, ...this.env,
|
|
81
|
+
});
|
|
82
|
+
// Newest first: the earliest is the last one.
|
|
83
|
+
const run = page.items[page.items.length - 1];
|
|
84
|
+
return run ? queuedJob(run) : null;
|
|
85
|
+
}
|
|
86
|
+
async stats() {
|
|
87
|
+
const [counts, queued] = await Promise.all([
|
|
88
|
+
this.flow.runs.counts({ function: this.functionId, ...this.env }),
|
|
89
|
+
this.flow.runs.list({ function: this.functionId, status: ['queued'], limit: 200, ...this.env }),
|
|
90
|
+
]);
|
|
91
|
+
// Newest first. With more than one page this is the oldest of the
|
|
92
|
+
// newest 200, so it can read low.
|
|
93
|
+
const oldest = queued.items[queued.items.length - 1];
|
|
94
|
+
return {
|
|
95
|
+
ready: counts.queued + counts.waiting_for_app,
|
|
96
|
+
delayed: counts.sleeping + counts.waiting,
|
|
97
|
+
inFlight: counts.running,
|
|
98
|
+
dead: counts.failed,
|
|
99
|
+
oldestReadyMs: oldest ? Math.max(0, Date.now() - oldest.createdAt) : 0,
|
|
100
|
+
paused: counts.paused > 0,
|
|
101
|
+
claimErrors: 0,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function queuedJob(run) {
|
|
106
|
+
return {
|
|
107
|
+
id: run.id,
|
|
108
|
+
runId: run.tags.runId ?? '',
|
|
109
|
+
threadId: run.tags.threadId ?? '',
|
|
110
|
+
kind: (run.tags.kind || undefined),
|
|
111
|
+
attempts: 0,
|
|
112
|
+
runAt: new Date(run.createdAt),
|
|
113
|
+
position: -1,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** The Flow function that runs `FlowQueue`'s jobs. Serve it with the app's
|
|
117
|
+
* other Flow functions.
|
|
118
|
+
*
|
|
119
|
+
* A job runs in one step. `handleJob` only throws for what a queue should
|
|
120
|
+
* retry (an unknown agent, a store that is down), so Flow's retries cover
|
|
121
|
+
* delivery; agentic-kit retries a failed run itself. When Flow gives up, the
|
|
122
|
+
* failure function hands the job to `handleDeadJob`, which fails the run. */
|
|
123
|
+
export function agentRunFunction(flow, core, opts = {}) {
|
|
124
|
+
return flow.createFunction({
|
|
125
|
+
id: opts.id ?? FLOW_FUNCTION_ID,
|
|
126
|
+
singleton: { key: 'event.data.key', mode: 'skip' },
|
|
127
|
+
tags: { runId: 'event.data.job.runId', threadId: 'event.data.job.threadId', kind: 'event.data.job.kind' },
|
|
128
|
+
priority: { run: 'event.data.priority' },
|
|
129
|
+
...(opts.concurrency ? { concurrency: { limit: opts.concurrency, key: 'event.data.job.partitionKey' } } : {}),
|
|
130
|
+
...(opts.retries !== undefined ? { retries: opts.retries } : {}),
|
|
131
|
+
timeouts: { step: opts.stepTimeout ?? '14m' },
|
|
132
|
+
onFailure: async ({ event, error, attempts }) => {
|
|
133
|
+
const data = event.data?.event?.data;
|
|
134
|
+
if (!data?.job)
|
|
135
|
+
return;
|
|
136
|
+
await core.worker.handleDeadJob(data.job, attempts, new Error(error?.message ?? 'the Flow run failed'));
|
|
137
|
+
},
|
|
138
|
+
}, { event: FLOW_JOB_EVENT }, async ({ event, step }) => {
|
|
139
|
+
const data = event.data;
|
|
140
|
+
if (data.notBefore)
|
|
141
|
+
await step.sleepUntil('delay', data.notBefore);
|
|
142
|
+
return step.run('handle', () => core.worker.handleJob(data.job));
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
// ---- names and big values, shared by the bus and the streams ----
|
|
146
|
+
/** Flow's charset for channel and stream names. */
|
|
147
|
+
const NAME_RE = /^[A-Za-z0-9:_.-]{1,256}$/;
|
|
148
|
+
/** Under Flow's 64 KB cap on one message or one stream item, with room for
|
|
149
|
+
* the rest of the request. A value over it goes through the kv. */
|
|
150
|
+
const MAX_INLINE_BYTES = 60_000;
|
|
151
|
+
const byteLength = (s) => new TextEncoder().encode(s).length;
|
|
152
|
+
async function sha256Hex(s) {
|
|
153
|
+
const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(s));
|
|
154
|
+
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
155
|
+
}
|
|
156
|
+
/** `prefix + id` when Flow allows it, else `prefix + "h." + sha256(id)`. */
|
|
157
|
+
async function flowName(prefix, id) {
|
|
158
|
+
const name = prefix + id;
|
|
159
|
+
if (NAME_RE.test(name) && name !== '.' && name !== '..' && name !== 'token' && name !== 'connect')
|
|
160
|
+
return name;
|
|
161
|
+
return `${prefix}h.${await sha256Hex(id)}`;
|
|
162
|
+
}
|
|
163
|
+
// ---- FlowBus ----
|
|
164
|
+
/** The channel a thread's events go on. */
|
|
165
|
+
export function flowThreadChannel(threadId) {
|
|
166
|
+
return flowName('agentenkit.thread.', threadId);
|
|
167
|
+
}
|
|
168
|
+
const BUS_TOPIC = 'events';
|
|
169
|
+
/** How long an event too big for one message waits in the kv. */
|
|
170
|
+
const BUS_REF_SECONDS = 60;
|
|
171
|
+
/** EventBus over Clevix Flow realtime.
|
|
172
|
+
*
|
|
173
|
+
* Each thread has its own channel, and a process opens one socket per thread
|
|
174
|
+
* it follows, shared by every subscriber in the process. Realtime keeps
|
|
175
|
+
* nothing and drops what comes while a socket reconnects: the bus is
|
|
176
|
+
* at-most-once anyway, and a follower fills any gap from the log. An event
|
|
177
|
+
* over about 60 KB waits in the kv for a minute, and the channel carries its
|
|
178
|
+
* key. While anyone subscribes, each subscriber gets a HEARTBEAT notice
|
|
179
|
+
* (seq 0) every `heartbeatMs`, as with the other buses.
|
|
180
|
+
*
|
|
181
|
+
* Needs Flow SDK 0.7.0 or later. */
|
|
182
|
+
export class FlowBus {
|
|
183
|
+
flow;
|
|
184
|
+
opts;
|
|
185
|
+
channels = new Map();
|
|
186
|
+
heartbeat;
|
|
187
|
+
constructor(flow, opts) {
|
|
188
|
+
this.flow = flow;
|
|
189
|
+
this.opts = opts;
|
|
190
|
+
}
|
|
191
|
+
get env() {
|
|
192
|
+
return this.opts.env ? { env: this.opts.env } : {};
|
|
193
|
+
}
|
|
194
|
+
async publish(threadId, event) {
|
|
195
|
+
const raw = JSON.stringify(event);
|
|
196
|
+
let frame = { event };
|
|
197
|
+
if (byteLength(raw) > MAX_INLINE_BYTES) {
|
|
198
|
+
const ref = `agentenkit:flowbus:${threadId}:${crypto.randomUUID()}`;
|
|
199
|
+
await this.opts.kv.set(ref, raw, { exSeconds: BUS_REF_SECONDS });
|
|
200
|
+
frame = { ref };
|
|
201
|
+
}
|
|
202
|
+
const channel = await flowThreadChannel(threadId);
|
|
203
|
+
await this.flow.realtime.publish({ channel, topic: BUS_TOPIC, data: frame }, this.env);
|
|
204
|
+
}
|
|
205
|
+
async subscribe(threadId, handler) {
|
|
206
|
+
let ch = this.channels.get(threadId);
|
|
207
|
+
if (!ch)
|
|
208
|
+
ch = this.connect(threadId);
|
|
209
|
+
ch.handlers.add(handler);
|
|
210
|
+
this.startHeartbeat();
|
|
211
|
+
await ch.open;
|
|
212
|
+
if (ch.failed) {
|
|
213
|
+
ch.handlers.delete(handler);
|
|
214
|
+
if (this.channels.size === 0)
|
|
215
|
+
this.stopHeartbeat();
|
|
216
|
+
throw ch.failed.error;
|
|
217
|
+
}
|
|
218
|
+
let done = false;
|
|
219
|
+
const mine = ch;
|
|
220
|
+
return () => {
|
|
221
|
+
if (done)
|
|
222
|
+
return;
|
|
223
|
+
done = true;
|
|
224
|
+
mine.handlers.delete(handler);
|
|
225
|
+
if (mine.handlers.size === 0 && this.channels.get(threadId) === mine) {
|
|
226
|
+
this.channels.delete(threadId);
|
|
227
|
+
mine.stop();
|
|
228
|
+
}
|
|
229
|
+
if (this.channels.size === 0)
|
|
230
|
+
this.stopHeartbeat();
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/** Opens the thread's socket. `open` settles when it is open, or after
|
|
234
|
+
* `openTimeoutMs`: an event published before then may be missed, which a
|
|
235
|
+
* follower's gap read covers. */
|
|
236
|
+
connect(threadId) {
|
|
237
|
+
let opened;
|
|
238
|
+
const open = new Promise((resolve) => (opened = resolve));
|
|
239
|
+
const timer = setTimeout(opened, this.opts.openTimeoutMs ?? 10_000);
|
|
240
|
+
timer.unref?.();
|
|
241
|
+
let stop;
|
|
242
|
+
let stopped = false;
|
|
243
|
+
const ch = {
|
|
244
|
+
handlers: new Set(),
|
|
245
|
+
open,
|
|
246
|
+
queue: Promise.resolve(),
|
|
247
|
+
stop: () => {
|
|
248
|
+
stopped = true;
|
|
249
|
+
clearTimeout(timer);
|
|
250
|
+
opened();
|
|
251
|
+
stop?.();
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
this.channels.set(threadId, ch);
|
|
255
|
+
flowThreadChannel(threadId)
|
|
256
|
+
.then((channel) => {
|
|
257
|
+
if (stopped)
|
|
258
|
+
return;
|
|
259
|
+
stop = this.flow.realtime.subscribe({
|
|
260
|
+
channel,
|
|
261
|
+
topics: [BUS_TOPIC],
|
|
262
|
+
...this.env,
|
|
263
|
+
...(this.opts.WebSocket ? { WebSocket: this.opts.WebSocket } : {}),
|
|
264
|
+
onState: (state) => {
|
|
265
|
+
if (state === 'open') {
|
|
266
|
+
clearTimeout(timer);
|
|
267
|
+
opened();
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
onMessage: (message) => {
|
|
271
|
+
ch.queue = ch.queue.then(() => this.receive(ch, message.data));
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
})
|
|
275
|
+
.catch((error) => {
|
|
276
|
+
// No socket (no WebSocket class, bad options): every subscriber
|
|
277
|
+
// waiting on it gets the error, as Go's Subscribe returns it.
|
|
278
|
+
ch.failed = { error };
|
|
279
|
+
if (this.channels.get(threadId) === ch)
|
|
280
|
+
this.channels.delete(threadId);
|
|
281
|
+
clearTimeout(timer);
|
|
282
|
+
opened();
|
|
283
|
+
});
|
|
284
|
+
return ch;
|
|
285
|
+
}
|
|
286
|
+
async receive(ch, frame) {
|
|
287
|
+
let event;
|
|
288
|
+
try {
|
|
289
|
+
if ('event' in frame) {
|
|
290
|
+
event = frame.event;
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
const raw = await this.opts.kv.get(frame.ref);
|
|
294
|
+
if (raw === null)
|
|
295
|
+
return; // expired: the follower reads it from the log
|
|
296
|
+
event = JSON.parse(raw);
|
|
297
|
+
}
|
|
298
|
+
event.createdAt = new Date(event.createdAt);
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
return; // a malformed frame never kills the subscription
|
|
302
|
+
}
|
|
303
|
+
this.deliver(ch.handlers, event);
|
|
304
|
+
}
|
|
305
|
+
deliver(handlers, event) {
|
|
306
|
+
for (const handler of [...handlers]) {
|
|
307
|
+
try {
|
|
308
|
+
handler(event);
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
// one throwing handler must not stop the others
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
startHeartbeat() {
|
|
316
|
+
if (this.heartbeat)
|
|
317
|
+
return;
|
|
318
|
+
this.heartbeat = setInterval(() => {
|
|
319
|
+
for (const [threadId, ch] of this.channels) {
|
|
320
|
+
this.deliver(ch.handlers, { threadId, seq: 0, type: 'HEARTBEAT', payload: null, createdAt: new Date() });
|
|
321
|
+
}
|
|
322
|
+
}, this.opts.heartbeatMs ?? 60_000);
|
|
323
|
+
this.heartbeat.unref?.();
|
|
324
|
+
}
|
|
325
|
+
stopHeartbeat() {
|
|
326
|
+
if (this.heartbeat)
|
|
327
|
+
clearInterval(this.heartbeat);
|
|
328
|
+
this.heartbeat = undefined;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// ---- FlowStreams ----
|
|
332
|
+
/** The Flow stream a run stream is kept in. */
|
|
333
|
+
export function flowStreamId(streamId) {
|
|
334
|
+
return flowName('agentenkit.', streamId);
|
|
335
|
+
}
|
|
336
|
+
/** Flow's limits on one stream call. */
|
|
337
|
+
const MAX_APPEND_ITEMS = 100;
|
|
338
|
+
const MAX_APPEND_BYTES = 900_000;
|
|
339
|
+
const MAX_STREAM_MS = 7 * 24 * 60 * 60_000;
|
|
340
|
+
/** RunStreams over Clevix Flow streams.
|
|
341
|
+
*
|
|
342
|
+
* Flow keeps the items, gives offsets, and lets a reader start from any
|
|
343
|
+
* offset and follow live, which is what a run stream needs. An item over
|
|
344
|
+
* about 60 KB is kept in the kv and the stream carries its key; readers get
|
|
345
|
+
* the event back. Flow keeps a stream at most 7 days and a grace window at
|
|
346
|
+
* most 7 days, so longer times are cut to that. A stream holds at most
|
|
347
|
+
* 20,000 items and 32 MB; an append past that fails.
|
|
348
|
+
*
|
|
349
|
+
* Needs Flow SDK 0.7.0 or later. */
|
|
350
|
+
export class FlowStreams {
|
|
351
|
+
flow;
|
|
352
|
+
opts;
|
|
353
|
+
constructor(flow, opts) {
|
|
354
|
+
this.flow = flow;
|
|
355
|
+
this.opts = opts;
|
|
356
|
+
}
|
|
357
|
+
get env() {
|
|
358
|
+
return this.opts.env ? { env: this.opts.env } : {};
|
|
359
|
+
}
|
|
360
|
+
async open(streamId, meta, ttlMs) {
|
|
361
|
+
const id = await flowStreamId(streamId);
|
|
362
|
+
const ttl = Math.min(MAX_STREAM_MS, Math.max(1000, Math.ceil(ttlMs)));
|
|
363
|
+
await this.flow.streams.open(id, { meta: { threadId: meta.threadId, runId: meta.runId }, ttl, ...this.env });
|
|
364
|
+
}
|
|
365
|
+
async append(streamId, events) {
|
|
366
|
+
if (events.length === 0)
|
|
367
|
+
return [];
|
|
368
|
+
const id = await flowStreamId(streamId);
|
|
369
|
+
const items = [];
|
|
370
|
+
for (const event of events) {
|
|
371
|
+
const raw = JSON.stringify(event);
|
|
372
|
+
if (byteLength(raw) <= MAX_INLINE_BYTES) {
|
|
373
|
+
items.push({ json: event, bytes: byteLength(raw) });
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
const ref = { agentenkitRef: `agentenkit:flowstream:${streamId}:${crypto.randomUUID()}` };
|
|
377
|
+
await this.opts.kv.set(ref.agentenkitRef, raw, { exSeconds: Math.ceil((this.opts.refTtlMs ?? 8 * 24 * 60 * 60_000) / 1000) });
|
|
378
|
+
items.push({ json: ref, bytes: byteLength(JSON.stringify(ref)) });
|
|
379
|
+
}
|
|
380
|
+
// In batches Flow takes: 100 items and under 1 MB a call.
|
|
381
|
+
const offsets = [];
|
|
382
|
+
let batch = [];
|
|
383
|
+
let size = 0;
|
|
384
|
+
const flush = async () => {
|
|
385
|
+
if (batch.length === 0)
|
|
386
|
+
return;
|
|
387
|
+
offsets.push(...(await this.call(streamId, () => this.flow.streams.append(id, batch, this.env))));
|
|
388
|
+
batch = [];
|
|
389
|
+
size = 0;
|
|
390
|
+
};
|
|
391
|
+
for (const item of items) {
|
|
392
|
+
if (batch.length === MAX_APPEND_ITEMS || size + item.bytes > MAX_APPEND_BYTES)
|
|
393
|
+
await flush();
|
|
394
|
+
batch.push(item.json);
|
|
395
|
+
size += item.bytes;
|
|
396
|
+
}
|
|
397
|
+
await flush();
|
|
398
|
+
return offsets;
|
|
399
|
+
}
|
|
400
|
+
async *read(streamId, after, signal) {
|
|
401
|
+
const id = await flowStreamId(streamId);
|
|
402
|
+
const items = this.flow.streams.read(id, {
|
|
403
|
+
...(after ? { after } : {}),
|
|
404
|
+
...(signal ? { signal } : {}),
|
|
405
|
+
...(this.opts.WebSocket ? { WebSocket: this.opts.WebSocket } : {}),
|
|
406
|
+
...this.env,
|
|
407
|
+
});
|
|
408
|
+
try {
|
|
409
|
+
for await (const item of items) {
|
|
410
|
+
const decoded = await this.decode(item);
|
|
411
|
+
if (decoded)
|
|
412
|
+
yield decoded;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
throw mapStreamError(streamId, err);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
async snapshot(streamId, after) {
|
|
420
|
+
const id = await flowStreamId(streamId);
|
|
421
|
+
const snap = await this.call(streamId, () => this.flow.streams.snapshot(id, { ...(after ? { after } : {}), ...this.env }));
|
|
422
|
+
if (!snap)
|
|
423
|
+
return null;
|
|
424
|
+
const items = [];
|
|
425
|
+
for (const item of snap.items) {
|
|
426
|
+
const decoded = await this.decode(item);
|
|
427
|
+
if (decoded)
|
|
428
|
+
items.push(decoded);
|
|
429
|
+
}
|
|
430
|
+
// The end item is the last one; `end` is the event, without its offset.
|
|
431
|
+
let end = null;
|
|
432
|
+
const last = items[items.length - 1];
|
|
433
|
+
if (snap.ended && last && isStreamEnd(last)) {
|
|
434
|
+
const { offset: _, ...event } = last;
|
|
435
|
+
end = event;
|
|
436
|
+
}
|
|
437
|
+
const meta = snap.meta;
|
|
438
|
+
return {
|
|
439
|
+
meta: { threadId: meta?.threadId ?? '', runId: meta?.runId ?? '' },
|
|
440
|
+
items,
|
|
441
|
+
end,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
async close(streamId, end, graceMs) {
|
|
445
|
+
const id = await flowStreamId(streamId);
|
|
446
|
+
const grace = Math.min(MAX_STREAM_MS, Math.max(0, Math.ceil(graceMs)));
|
|
447
|
+
await this.call(streamId, () => this.flow.streams.close(id, { end: end, grace, ...this.env }));
|
|
448
|
+
}
|
|
449
|
+
async delete(streamId) {
|
|
450
|
+
const id = await flowStreamId(streamId);
|
|
451
|
+
await this.flow.streams.delete(id, this.env);
|
|
452
|
+
}
|
|
453
|
+
async call(streamId, fn) {
|
|
454
|
+
try {
|
|
455
|
+
return await fn();
|
|
456
|
+
}
|
|
457
|
+
catch (err) {
|
|
458
|
+
throw mapStreamError(streamId, err);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/** A Flow item as a run stream item: the event, read back from the kv when
|
|
462
|
+
* the stream holds its key. Null when that kv copy has expired. */
|
|
463
|
+
async decode(item) {
|
|
464
|
+
let event = item.item;
|
|
465
|
+
if (event && typeof event === 'object' && 'agentenkitRef' in event) {
|
|
466
|
+
const raw = await this.opts.kv.get(event.agentenkitRef);
|
|
467
|
+
if (raw === null)
|
|
468
|
+
return null;
|
|
469
|
+
event = JSON.parse(raw);
|
|
470
|
+
}
|
|
471
|
+
return { ...event, offset: item.offset };
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/** Flow's stream errors as the port's, by name: the adapter does not load
|
|
475
|
+
* `@clevix/flow` itself. */
|
|
476
|
+
function mapStreamError(streamId, err) {
|
|
477
|
+
const name = err?.name;
|
|
478
|
+
if (name === 'StreamGoneError')
|
|
479
|
+
return new StreamGoneError(streamId);
|
|
480
|
+
if (name === 'StreamClosedError')
|
|
481
|
+
return new StreamClosedError(streamId);
|
|
482
|
+
return err;
|
|
483
|
+
}
|
|
484
|
+
//# sourceMappingURL=flow.js.map
|