agentenkit 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 +226 -0
- package/dist/adapters/inline.d.ts +27 -0
- package/dist/adapters/inline.d.ts.map +1 -0
- package/dist/adapters/inline.js +41 -0
- package/dist/adapters/inline.js.map +1 -0
- package/dist/adapters/memory.d.ts +74 -0
- package/dist/adapters/memory.d.ts.map +1 -0
- package/dist/adapters/memory.js +194 -0
- package/dist/adapters/memory.js.map +1 -0
- package/dist/adapters/prisma.d.ts +176 -0
- package/dist/adapters/prisma.d.ts.map +1 -0
- package/dist/adapters/prisma.js +107 -0
- package/dist/adapters/prisma.js.map +1 -0
- package/dist/adapters/qstash.d.ts +36 -0
- package/dist/adapters/qstash.d.ts.map +1 -0
- package/dist/adapters/qstash.js +26 -0
- package/dist/adapters/qstash.js.map +1 -0
- package/dist/adapters/redis.d.ts +49 -0
- package/dist/adapters/redis.d.ts.map +1 -0
- package/dist/adapters/redis.js +60 -0
- package/dist/adapters/redis.js.map +1 -0
- package/dist/adapters/sqlite.d.ts +67 -0
- package/dist/adapters/sqlite.d.ts.map +1 -0
- package/dist/adapters/sqlite.js +199 -0
- package/dist/adapters/sqlite.js.map +1 -0
- package/dist/adapters/upstash.d.ts +48 -0
- package/dist/adapters/upstash.d.ts.map +1 -0
- package/dist/adapters/upstash.js +46 -0
- package/dist/adapters/upstash.js.map +1 -0
- package/dist/admin/default.d.ts +15 -0
- package/dist/admin/default.d.ts.map +1 -0
- package/dist/admin/default.js +22 -0
- package/dist/admin/default.js.map +1 -0
- package/dist/admin/memory.d.ts +29 -0
- package/dist/admin/memory.d.ts.map +1 -0
- package/dist/admin/memory.js +83 -0
- package/dist/admin/memory.js.map +1 -0
- package/dist/admin/postgres.d.ts +43 -0
- package/dist/admin/postgres.d.ts.map +1 -0
- package/dist/admin/postgres.js +201 -0
- package/dist/admin/postgres.js.map +1 -0
- package/dist/admin/sqlite.d.ts +35 -0
- package/dist/admin/sqlite.d.ts.map +1 -0
- package/dist/admin/sqlite.js +187 -0
- package/dist/admin/sqlite.js.map +1 -0
- package/dist/core/admin.d.ts +88 -0
- package/dist/core/admin.d.ts.map +1 -0
- package/dist/core/admin.js +147 -0
- package/dist/core/admin.js.map +1 -0
- package/dist/core/agent.d.ts +29 -0
- package/dist/core/agent.d.ts.map +1 -0
- package/dist/core/agent.js +41 -0
- package/dist/core/agent.js.map +1 -0
- package/dist/core/cache.d.ts +37 -0
- package/dist/core/cache.d.ts.map +1 -0
- package/dist/core/cache.js +68 -0
- package/dist/core/cache.js.map +1 -0
- package/dist/core/context.d.ts +13 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +87 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/deleteThread.d.ts +14 -0
- package/dist/core/deleteThread.d.ts.map +1 -0
- package/dist/core/deleteThread.js +34 -0
- package/dist/core/deleteThread.js.map +1 -0
- package/dist/core/engine.d.ts +79 -0
- package/dist/core/engine.d.ts.map +1 -0
- package/dist/core/engine.js +489 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/follow.d.ts +50 -0
- package/dist/core/follow.d.ts.map +1 -0
- package/dist/core/follow.js +153 -0
- package/dist/core/follow.js.map +1 -0
- package/dist/core/hitl.d.ts +98 -0
- package/dist/core/hitl.d.ts.map +1 -0
- package/dist/core/hitl.js +199 -0
- package/dist/core/hitl.js.map +1 -0
- package/dist/core/keys.d.ts +26 -0
- package/dist/core/keys.d.ts.map +1 -0
- package/dist/core/keys.js +32 -0
- package/dist/core/keys.js.map +1 -0
- package/dist/core/loop.d.ts +105 -0
- package/dist/core/loop.d.ts.map +1 -0
- package/dist/core/loop.js +234 -0
- package/dist/core/loop.js.map +1 -0
- package/dist/core/publish.d.ts +18 -0
- package/dist/core/publish.d.ts.map +1 -0
- package/dist/core/publish.js +34 -0
- package/dist/core/publish.js.map +1 -0
- package/dist/core/reclaim.d.ts +20 -0
- package/dist/core/reclaim.d.ts.map +1 -0
- package/dist/core/reclaim.js +56 -0
- package/dist/core/reclaim.js.map +1 -0
- package/dist/core/run.d.ts +8 -0
- package/dist/core/run.d.ts.map +1 -0
- package/dist/core/run.js +102 -0
- package/dist/core/run.js.map +1 -0
- package/dist/core/state.d.ts +50 -0
- package/dist/core/state.d.ts.map +1 -0
- package/dist/core/state.js +39 -0
- package/dist/core/state.js.map +1 -0
- package/dist/core/stop.d.ts +7 -0
- package/dist/core/stop.d.ts.map +1 -0
- package/dist/core/stop.js +15 -0
- package/dist/core/stop.js.map +1 -0
- package/dist/core/stream.d.ts +12 -0
- package/dist/core/stream.d.ts.map +1 -0
- package/dist/core/stream.js +21 -0
- package/dist/core/stream.js.map +1 -0
- package/dist/core/subagent.d.ts +77 -0
- package/dist/core/subagent.d.ts.map +1 -0
- package/dist/core/subagent.js +261 -0
- package/dist/core/subagent.js.map +1 -0
- package/dist/core/tools.d.ts +36 -0
- package/dist/core/tools.d.ts.map +1 -0
- package/dist/core/tools.js +22 -0
- package/dist/core/tools.js.map +1 -0
- package/dist/core/types.d.ts +286 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +49 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/usage.d.ts +40 -0
- package/dist/core/usage.d.ts.map +1 -0
- package/dist/core/usage.js +43 -0
- package/dist/core/usage.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/ports/admin.d.ts +102 -0
- package/dist/ports/admin.d.ts.map +1 -0
- package/dist/ports/admin.js +2 -0
- package/dist/ports/admin.js.map +1 -0
- package/dist/ports/bus.d.ts +13 -0
- package/dist/ports/bus.d.ts.map +1 -0
- package/dist/ports/bus.js +2 -0
- package/dist/ports/bus.js.map +1 -0
- package/dist/ports/kv.d.ts +16 -0
- package/dist/ports/kv.d.ts.map +1 -0
- package/dist/ports/kv.js +2 -0
- package/dist/ports/kv.js.map +1 -0
- package/dist/ports/queue.d.ts +18 -0
- package/dist/ports/queue.d.ts.map +1 -0
- package/dist/ports/queue.js +2 -0
- package/dist/ports/queue.js.map +1 -0
- package/dist/ports/runtime.d.ts +260 -0
- package/dist/ports/runtime.d.ts.map +1 -0
- package/dist/ports/runtime.js +2 -0
- package/dist/ports/runtime.js.map +1 -0
- package/dist/ports/storage.d.ts +72 -0
- package/dist/ports/storage.d.ts.map +1 -0
- package/dist/ports/storage.js +2 -0
- package/dist/ports/storage.js.map +1 -0
- package/dist/runtime.d.ts +5 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +177 -0
- package/dist/runtime.js.map +1 -0
- package/package.json +126 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/** Every event on a thread, replay first and then live, as one sequence.
|
|
2
|
+
*
|
|
3
|
+
* The ordering here is the whole point, and it is easy to get wrong in a route
|
|
4
|
+
* handler:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Subscribe before replaying.** An event published between the replay
|
|
7
|
+
* finishing and the tail starting is otherwise lost for ever.
|
|
8
|
+
* 2. **Never emit at or below the cursor.** The client would render it twice.
|
|
9
|
+
* 3. **`seq === 0` is a bus-only notice** (heartbeats, death notices).
|
|
10
|
+
* Always forward it, never let it move the cursor.
|
|
11
|
+
*
|
|
12
|
+
* Framework-neutral on purpose: an async iterable is something Express, Hono,
|
|
13
|
+
* Nest, Next and a plain worker can each consume in their own way. */
|
|
14
|
+
export async function* followEvents(deps, threadId, options = {}) {
|
|
15
|
+
const since = options.since ?? -1;
|
|
16
|
+
const { signal } = options;
|
|
17
|
+
let lastSeq = since;
|
|
18
|
+
let live = false;
|
|
19
|
+
let closed = false;
|
|
20
|
+
/** Published while the replay is still running. */
|
|
21
|
+
const pending = [];
|
|
22
|
+
/** Published once live, waiting for the consumer. */
|
|
23
|
+
const queue = [];
|
|
24
|
+
let wake = null;
|
|
25
|
+
const notify = () => {
|
|
26
|
+
const w = wake;
|
|
27
|
+
wake = null;
|
|
28
|
+
w?.();
|
|
29
|
+
};
|
|
30
|
+
// Rule 1: subscribe FIRST.
|
|
31
|
+
const unsubscribe = await deps.bus.subscribe(threadId, (event) => {
|
|
32
|
+
if (!live) {
|
|
33
|
+
pending.push(event);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
queue.push(event);
|
|
37
|
+
notify();
|
|
38
|
+
});
|
|
39
|
+
const onAbort = () => {
|
|
40
|
+
closed = true;
|
|
41
|
+
notify();
|
|
42
|
+
};
|
|
43
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
44
|
+
/** Rules 2 and 3 in one place, so no caller has to remember them. */
|
|
45
|
+
const admit = (event) => {
|
|
46
|
+
if (event.seq === 0)
|
|
47
|
+
return true; // a notice: forward, but do not advance
|
|
48
|
+
if (event.seq <= lastSeq)
|
|
49
|
+
return false;
|
|
50
|
+
lastSeq = event.seq;
|
|
51
|
+
return true;
|
|
52
|
+
};
|
|
53
|
+
try {
|
|
54
|
+
if (signal?.aborted)
|
|
55
|
+
return;
|
|
56
|
+
// …then the durable log…
|
|
57
|
+
for (const event of await deps.storage.events.listSince(threadId, since)) {
|
|
58
|
+
if (admit(event))
|
|
59
|
+
yield event;
|
|
60
|
+
}
|
|
61
|
+
// …then whatever arrived behind it, in order.
|
|
62
|
+
for (const event of pending.sort((a, b) => a.seq - b.seq)) {
|
|
63
|
+
if (admit(event))
|
|
64
|
+
yield event;
|
|
65
|
+
}
|
|
66
|
+
pending.length = 0;
|
|
67
|
+
live = true;
|
|
68
|
+
while (!closed && !signal?.aborted) {
|
|
69
|
+
while (queue.length > 0) {
|
|
70
|
+
const event = queue.shift();
|
|
71
|
+
if (admit(event))
|
|
72
|
+
yield event;
|
|
73
|
+
}
|
|
74
|
+
if (closed || signal?.aborted)
|
|
75
|
+
break;
|
|
76
|
+
await new Promise((resolve) => {
|
|
77
|
+
wake = resolve;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
// Runs on abort, on a consumer that stops iterating, and on error alike —
|
|
83
|
+
// a subscription that outlives its reader is a leak on every reconnect.
|
|
84
|
+
signal?.removeEventListener('abort', onAbort);
|
|
85
|
+
try {
|
|
86
|
+
await unsubscribe();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// A bus that fails to unsubscribe must not mask the reason we stopped.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Headers an SSE response needs. `X-Accel-Buffering` is for nginx, which
|
|
94
|
+
* otherwise buffers the stream and makes it look like nothing is happening. */
|
|
95
|
+
export const SSE_HEADERS = {
|
|
96
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
97
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
98
|
+
Connection: 'keep-alive',
|
|
99
|
+
'X-Accel-Buffering': 'no',
|
|
100
|
+
};
|
|
101
|
+
/** One event as an SSE frame.
|
|
102
|
+
*
|
|
103
|
+
* A bus-only notice (`seq === 0`) is sent WITHOUT an `id:` line. EventSource
|
|
104
|
+
* stores any id it sees and sends it back as `Last-Event-ID` on reconnect —
|
|
105
|
+
* so stamping `id: 0` on a heartbeat would rewind the client's cursor to the
|
|
106
|
+
* beginning of the thread and replay everything. */
|
|
107
|
+
export function sseFrame(event) {
|
|
108
|
+
const data = JSON.stringify(event);
|
|
109
|
+
return event.seq === 0 ? `data: ${data}\n\n` : `id: ${event.seq}\ndata: ${data}\n\n`;
|
|
110
|
+
}
|
|
111
|
+
/** The event sequence, encoded as Server-Sent Events.
|
|
112
|
+
*
|
|
113
|
+
* Returns the stream and the headers rather than a `Response`, because half
|
|
114
|
+
* the ecosystem does not have one. */
|
|
115
|
+
export function toSseStream(events, options = {}) {
|
|
116
|
+
const encoder = new TextEncoder();
|
|
117
|
+
let started = false;
|
|
118
|
+
const stream = new ReadableStream({
|
|
119
|
+
// `pull` rather than `start`: the consumer sets the pace, and a slow client
|
|
120
|
+
// does not make an unbounded queue of encoded frames.
|
|
121
|
+
async pull(controller) {
|
|
122
|
+
if (!started) {
|
|
123
|
+
started = true;
|
|
124
|
+
if (options.retryMs !== undefined) {
|
|
125
|
+
controller.enqueue(encoder.encode(`retry: ${options.retryMs}\n\n`));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const { value, done } = await events.next();
|
|
131
|
+
if (done) {
|
|
132
|
+
controller.close();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
controller.enqueue(encoder.encode(sseFrame(value)));
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
controller.error(error);
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
// The client hung up: stop the generator so its `finally` unsubscribes.
|
|
142
|
+
async cancel() {
|
|
143
|
+
try {
|
|
144
|
+
await events.return(undefined);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// already finished
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
return { stream, headers: SSE_HEADERS };
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=follow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"follow.js","sourceRoot":"","sources":["../../src/core/follow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { RuntimePorts } from '../ports/runtime.js';
|
|
2
|
+
import type { RespondInput, RespondResult } from '../ports/runtime.js';
|
|
3
|
+
import type { NestedDescriptor, ResumeInfo } from './types.js';
|
|
4
|
+
export declare const HITL_TTL_MS: number;
|
|
5
|
+
/** Result value a parked `requiresConfirmation` tool returns (§2.5). The
|
|
6
|
+
* engine scans a step's tool results for this marker to end the run segment;
|
|
7
|
+
* it is never persisted as a tool result — the resumed segment appends the
|
|
8
|
+
* user's verdict (or the timeout denial) instead. */
|
|
9
|
+
export declare const HITL_PARKED = "__hitl_parked__";
|
|
10
|
+
export declare const hitlKey: (toolCallId: string) => string;
|
|
11
|
+
export interface HitlResponse {
|
|
12
|
+
approved: boolean;
|
|
13
|
+
payload?: unknown;
|
|
14
|
+
}
|
|
15
|
+
/** One tool call left waiting on an approval further down (§2.7). A park by
|
|
16
|
+
* the main agent has none; a park inside a nested run has one per level, the
|
|
17
|
+
* innermost waiter first. `agentId` names the stream the waiting call lives
|
|
18
|
+
* in — `null` for the main agent. */
|
|
19
|
+
export interface HitlFrame {
|
|
20
|
+
agentId: string | null;
|
|
21
|
+
toolCallId: string;
|
|
22
|
+
/** How to re-enter the owner's loop when this frame unwinds. Absent for the
|
|
23
|
+
* main agent, whose loop the engine re-enters itself. */
|
|
24
|
+
nested?: NestedDescriptor;
|
|
25
|
+
}
|
|
26
|
+
export interface ParkInput {
|
|
27
|
+
threadId: string;
|
|
28
|
+
toolCallId: string;
|
|
29
|
+
toolName: string;
|
|
30
|
+
args: unknown;
|
|
31
|
+
agentId?: string | null;
|
|
32
|
+
/** The calls waiting on this answer, innermost first (§2.7). Empty for a
|
|
33
|
+
* main-agent park, which reduces this to the plain §2.5 flow. */
|
|
34
|
+
frames?: HitlFrame[];
|
|
35
|
+
/** The nested run that raised this park; absent when the main agent did. */
|
|
36
|
+
nested?: NestedDescriptor;
|
|
37
|
+
/** Dispatch ticket persisted in the INPUT_REQUIRED payload (§2.5) */
|
|
38
|
+
resume: ResumeInfo;
|
|
39
|
+
}
|
|
40
|
+
/** Wrap every marked tool so a call parks (§2.5) instead of executing: the
|
|
41
|
+
* request is persisted as INPUT_REQUIRED and the wrapper returns the park
|
|
42
|
+
* sentinel — nothing blocks. The real tool runs when the park is resolved
|
|
43
|
+
* (see resumePendingHitl), in whichever stream owns it.
|
|
44
|
+
*
|
|
45
|
+
* Shared by the main agent and every nested run (§2.7): the only difference
|
|
46
|
+
* is the `agentId` asking and the `frames` waiting on the answer. */
|
|
47
|
+
export declare function withHitl(deps: RuntimePorts, threadId: string, tools: Record<string, any>, ctx: {
|
|
48
|
+
resume: ResumeInfo;
|
|
49
|
+
agentId?: string | null;
|
|
50
|
+
frames?: HitlFrame[];
|
|
51
|
+
nested?: NestedDescriptor;
|
|
52
|
+
}): Record<string, any>;
|
|
53
|
+
/** The §2.5 suspension as a durable state transition — NO process waits.
|
|
54
|
+
* Flips WAITING_FOR_INPUT on both homes and appends INPUT_REQUIRED to the
|
|
55
|
+
* replayable event log (with the resume ticket). The engine then ends the
|
|
56
|
+
* run segment; /api/agent/respond (or the expiry job below) resumes it via
|
|
57
|
+
* the queue.
|
|
58
|
+
*
|
|
59
|
+
* The park also schedules its OWN expiry: one delayed dispatch of the same
|
|
60
|
+
* run, timed for just after the TTL. Without it the deadline only exists
|
|
61
|
+
* while somebody happens to be watching the thread — close the tab and the
|
|
62
|
+
* approval never times out at all. The delayed job holds no process; the
|
|
63
|
+
* queue holds it, exactly like the original dispatch (§2.8).
|
|
64
|
+
*
|
|
65
|
+
* It carries the PARKED run's id, so the answer and the expiry are two
|
|
66
|
+
* deliveries of one run: whichever resolves the park first wins, and the
|
|
67
|
+
* run lock makes the other a no-op. */
|
|
68
|
+
export declare function parkForApproval(deps: RuntimePorts, i: ParkInput): Promise<void>;
|
|
69
|
+
/** The pending request behind a WAITING_FOR_INPUT thread, hydrated from the
|
|
70
|
+
* durable event log (§2.5). */
|
|
71
|
+
export interface PendingHitl {
|
|
72
|
+
toolCallId: string;
|
|
73
|
+
toolName: string;
|
|
74
|
+
agentId: string | null;
|
|
75
|
+
arguments: unknown;
|
|
76
|
+
/** Calls waiting on this answer, innermost first (§2.7). */
|
|
77
|
+
frames: HitlFrame[];
|
|
78
|
+
/** The nested run that raised it; absent for a main-agent park. */
|
|
79
|
+
nested?: NestedDescriptor;
|
|
80
|
+
/** Epoch ms of the INPUT_REQUIRED event — the TTL clock (§2.5) */
|
|
81
|
+
requestedAt: number;
|
|
82
|
+
}
|
|
83
|
+
export declare function loadPendingHitl(deps: RuntimePorts, threadId: string): Promise<PendingHitl | null>;
|
|
84
|
+
/** Every approval on the thread that is still open (§2.7).
|
|
85
|
+
*
|
|
86
|
+
* Derived from durable state, never cached: a request is settled once a tool
|
|
87
|
+
* result carries its `toolCallId` — in whichever stream owns it — or an
|
|
88
|
+
* INPUT_EXPIRED event names it. Both are already written on the settling
|
|
89
|
+
* path. A cached set would be a read-modify-write race between exactly the
|
|
90
|
+
* concurrent siblings this exists to serve, since Kv has no set operations. */
|
|
91
|
+
export declare function loadOpenHitls(deps: RuntimePorts, threadId: string): Promise<PendingHitl[]>;
|
|
92
|
+
/** The §5.4 behavior: heal orphans first (§2.5), then record the answer in
|
|
93
|
+
* the handoff key and resume the run segment via the queue (§2.8) — the
|
|
94
|
+
* resumed worker appends the tool result and continues the loop.
|
|
95
|
+
* Answer-latest policy: only the most recent pending INPUT_REQUIRED is
|
|
96
|
+
* answerable (§2.7). */
|
|
97
|
+
export declare function respond(deps: RuntimePorts, input: RespondInput): Promise<RespondResult>;
|
|
98
|
+
//# sourceMappingURL=hitl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hitl.d.ts","sourceRoot":"","sources":["../../src/core/hitl.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAc,gBAAgB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAK3E,eAAO,MAAM,WAAW,QAAc,CAAC;AAEvC;;;sDAGsD;AACtD,eAAO,MAAM,WAAW,oBAAoB,CAAC;AAE7C,eAAO,MAAM,OAAO,GAAI,YAAY,MAAM,WAA+B,CAAC;AAE1E,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;sCAGsC;AACtC,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB;8DAC0D;IAC1D,MAAM,CAAC,EAAE,gBAAgB,CAAC;CAC3B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;sEACkE;IAClE,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,qEAAqE;IACrE,MAAM,EAAE,UAAU,CAAC;CACpB;AAED;;;;;;sEAMsE;AACtE,wBAAgB,QAAQ,CACtB,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC1B,GAAG,EAAE;IACH,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,gBAAgB,CAAC;CAC3B,GACA,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CA0BrB;AAED;;;;;;;;;;;;;;wCAcwC;AACxC,wBAAsB,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAuCrF;AAED;gCACgC;AAChC,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,4DAA4D;IAC5D,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,mEAAmE;IACnE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,kEAAkE;IAClE,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,eAAe,CACnC,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAI7B;AAuBD;;;;;;gFAMgF;AAChF,wBAAsB,aAAa,CACjC,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,EAAE,CAAC,CAqBxB;AAED;;;;yBAIyB;AACzB,wBAAsB,OAAO,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC,CAgE7F"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { publish, setThreadState } from './publish.js';
|
|
3
|
+
import { currentRunId } from './keys.js';
|
|
4
|
+
import { reclaimGraceAfterMs, reclaimIfOrphaned } from './reclaim.js';
|
|
5
|
+
export const HITL_TTL_MS = 15 * 60_000;
|
|
6
|
+
/** Result value a parked `requiresConfirmation` tool returns (§2.5). The
|
|
7
|
+
* engine scans a step's tool results for this marker to end the run segment;
|
|
8
|
+
* it is never persisted as a tool result — the resumed segment appends the
|
|
9
|
+
* user's verdict (or the timeout denial) instead. */
|
|
10
|
+
export const HITL_PARKED = '__hitl_parked__';
|
|
11
|
+
export const hitlKey = (toolCallId) => `agent:hitl:${toolCallId}`;
|
|
12
|
+
/** Wrap every marked tool so a call parks (§2.5) instead of executing: the
|
|
13
|
+
* request is persisted as INPUT_REQUIRED and the wrapper returns the park
|
|
14
|
+
* sentinel — nothing blocks. The real tool runs when the park is resolved
|
|
15
|
+
* (see resumePendingHitl), in whichever stream owns it.
|
|
16
|
+
*
|
|
17
|
+
* Shared by the main agent and every nested run (§2.7): the only difference
|
|
18
|
+
* is the `agentId` asking and the `frames` waiting on the answer. */
|
|
19
|
+
export function withHitl(deps, threadId, tools, ctx) {
|
|
20
|
+
const out = {};
|
|
21
|
+
for (const [name, t] of Object.entries(tools)) {
|
|
22
|
+
if (!t?.requiresConfirmation) {
|
|
23
|
+
out[name] = t;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
out[name] = {
|
|
27
|
+
...t,
|
|
28
|
+
execute: async (args, opts) => {
|
|
29
|
+
const toolCallId = opts?.toolCallId ?? randomUUID();
|
|
30
|
+
await parkForApproval(deps, {
|
|
31
|
+
threadId,
|
|
32
|
+
toolCallId,
|
|
33
|
+
toolName: name,
|
|
34
|
+
args,
|
|
35
|
+
agentId: ctx.agentId ?? null,
|
|
36
|
+
frames: ctx.frames ?? [],
|
|
37
|
+
...(ctx.nested ? { nested: ctx.nested } : {}),
|
|
38
|
+
resume: ctx.resume,
|
|
39
|
+
});
|
|
40
|
+
return { [HITL_PARKED]: toolCallId };
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
/** The §2.5 suspension as a durable state transition — NO process waits.
|
|
47
|
+
* Flips WAITING_FOR_INPUT on both homes and appends INPUT_REQUIRED to the
|
|
48
|
+
* replayable event log (with the resume ticket). The engine then ends the
|
|
49
|
+
* run segment; /api/agent/respond (or the expiry job below) resumes it via
|
|
50
|
+
* the queue.
|
|
51
|
+
*
|
|
52
|
+
* The park also schedules its OWN expiry: one delayed dispatch of the same
|
|
53
|
+
* run, timed for just after the TTL. Without it the deadline only exists
|
|
54
|
+
* while somebody happens to be watching the thread — close the tab and the
|
|
55
|
+
* approval never times out at all. The delayed job holds no process; the
|
|
56
|
+
* queue holds it, exactly like the original dispatch (§2.8).
|
|
57
|
+
*
|
|
58
|
+
* It carries the PARKED run's id, so the answer and the expiry are two
|
|
59
|
+
* deliveries of one run: whichever resolves the park first wins, and the
|
|
60
|
+
* run lock makes the other a no-op. */
|
|
61
|
+
export async function parkForApproval(deps, i) {
|
|
62
|
+
await deps.kv.set(`agent:state:${i.threadId}`, 'WAITING_FOR_INPUT');
|
|
63
|
+
await setThreadState(deps, i.threadId, 'WAITING_FOR_INPUT', i.resume.model);
|
|
64
|
+
await publish(deps, i.threadId, 'INPUT_REQUIRED', {
|
|
65
|
+
toolCallId: i.toolCallId,
|
|
66
|
+
toolName: i.toolName,
|
|
67
|
+
agentId: i.agentId ?? null,
|
|
68
|
+
arguments: i.args,
|
|
69
|
+
inputSchema: null,
|
|
70
|
+
// The unwind chain (§2.7) — empty for a main-agent park.
|
|
71
|
+
frames: i.frames ?? [],
|
|
72
|
+
...(i.nested ? { nested: i.nested } : {}),
|
|
73
|
+
resume: i.resume,
|
|
74
|
+
});
|
|
75
|
+
await publish(deps, i.threadId, 'STATE_CHANGE', { state: 'WAITING_FOR_INPUT' });
|
|
76
|
+
// Best-effort, and deliberately last. The park is ALREADY durable by this
|
|
77
|
+
// point — state flipped on both homes, INPUT_REQUIRED on the event log — so
|
|
78
|
+
// a queue that cannot schedule must not be allowed to throw back through the
|
|
79
|
+
// tool call and fail the run. Reclamation (§2.5) covers the thread instead.
|
|
80
|
+
//
|
|
81
|
+
// Arriving early is equally harmless: an unexpired, unanswered request
|
|
82
|
+
// resolves to nothing and the job is a no-op (see resumePendingHitl).
|
|
83
|
+
try {
|
|
84
|
+
await deps.queue.enqueue({
|
|
85
|
+
threadId: i.threadId,
|
|
86
|
+
runId: await currentRunId(deps, i.threadId),
|
|
87
|
+
model: i.resume.model,
|
|
88
|
+
agent: i.resume.agent,
|
|
89
|
+
...(i.resume.tokenBudget !== undefined ? { tokenBudget: i.resume.tokenBudget } : {}),
|
|
90
|
+
...(i.resume.providerOptions ? { providerOptions: i.resume.providerOptions } : {}),
|
|
91
|
+
...(i.resume.state ? { state: i.resume.state } : {}),
|
|
92
|
+
}, { delaySeconds: Math.ceil(reclaimGraceAfterMs(deps) / 1000) });
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// No expiry scheduled — the thread still heals on first touch (§2.5).
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export async function loadPendingHitl(deps, threadId) {
|
|
99
|
+
const pending = await deps.storage.events.latest(threadId, 'INPUT_REQUIRED');
|
|
100
|
+
if (!pending)
|
|
101
|
+
return null;
|
|
102
|
+
return fromInputRequired(pending);
|
|
103
|
+
}
|
|
104
|
+
function fromInputRequired(pending) {
|
|
105
|
+
const p = pending.payload;
|
|
106
|
+
return {
|
|
107
|
+
toolCallId: p.toolCallId,
|
|
108
|
+
toolName: p.toolName,
|
|
109
|
+
agentId: p.agentId ?? null,
|
|
110
|
+
arguments: p.arguments,
|
|
111
|
+
// A park recorded before frames existed unwinds as a main-agent park.
|
|
112
|
+
frames: p.frames ?? [],
|
|
113
|
+
...(p.nested ? { nested: p.nested } : {}),
|
|
114
|
+
requestedAt: new Date(pending.createdAt).getTime(),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Every approval on the thread that is still open (§2.7).
|
|
118
|
+
*
|
|
119
|
+
* Derived from durable state, never cached: a request is settled once a tool
|
|
120
|
+
* result carries its `toolCallId` — in whichever stream owns it — or an
|
|
121
|
+
* INPUT_EXPIRED event names it. Both are already written on the settling
|
|
122
|
+
* path. A cached set would be a read-modify-write race between exactly the
|
|
123
|
+
* concurrent siblings this exists to serve, since Kv has no set operations. */
|
|
124
|
+
export async function loadOpenHitls(deps, threadId) {
|
|
125
|
+
const requested = await deps.storage.events.listByType(threadId, 'INPUT_REQUIRED');
|
|
126
|
+
if (requested.length === 0)
|
|
127
|
+
return [];
|
|
128
|
+
const expired = new Set((await deps.storage.events.listByType(threadId, 'INPUT_EXPIRED')).map((e) => e.payload?.toolCallId));
|
|
129
|
+
const answered = new Set();
|
|
130
|
+
for (const m of await deps.storage.messages.list(threadId, undefined)) {
|
|
131
|
+
if (m.role !== 'tool')
|
|
132
|
+
continue;
|
|
133
|
+
for (const part of Array.isArray(m.content) ? m.content : []) {
|
|
134
|
+
const id = part?.toolCallId;
|
|
135
|
+
if (id)
|
|
136
|
+
answered.add(id);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return requested
|
|
140
|
+
.map((e) => fromInputRequired(e))
|
|
141
|
+
.filter((p) => !answered.has(p.toolCallId) && !expired.has(p.toolCallId));
|
|
142
|
+
}
|
|
143
|
+
/** The §5.4 behavior: heal orphans first (§2.5), then record the answer in
|
|
144
|
+
* the handoff key and resume the run segment via the queue (§2.8) — the
|
|
145
|
+
* resumed worker appends the tool result and continues the loop.
|
|
146
|
+
* Answer-latest policy: only the most recent pending INPUT_REQUIRED is
|
|
147
|
+
* answerable (§2.7). */
|
|
148
|
+
export async function respond(deps, input) {
|
|
149
|
+
await reclaimIfOrphaned(deps, input.threadId);
|
|
150
|
+
const thread = await deps.storage.threads.get(input.threadId);
|
|
151
|
+
// ANY open request is answerable, not just the newest (§2.7): one parent
|
|
152
|
+
// step can park several nested runs at once, and each is answered on its
|
|
153
|
+
// own. The run resumes when the last of them is settled.
|
|
154
|
+
const open = await loadOpenHitls(deps, input.threadId);
|
|
155
|
+
const match = open.find((p) => p.toolCallId === input.toolCallId);
|
|
156
|
+
if (thread?.state !== 'WAITING_FOR_INPUT' || !match) {
|
|
157
|
+
return { delivered: false, error: 'No matching pending input request' };
|
|
158
|
+
}
|
|
159
|
+
// Remaining-TTL expiry so a stale answer can never outlive its request: the
|
|
160
|
+
// key vanishing is what makes the resumed segment treat the request as
|
|
161
|
+
// unanswered (§2.5)
|
|
162
|
+
const remainingSec = Math.max(60, Math.floor((deps.config.hitlTtlMs - (Date.now() - match.requestedAt)) / 1000));
|
|
163
|
+
await deps.kv.set(hitlKey(input.toolCallId), JSON.stringify({ approved: input.approved, payload: input.payload }), { exSeconds: remainingSec });
|
|
164
|
+
// Bus-only fast-path notice (seq 0 = never persisted) for live UIs (§2.5)
|
|
165
|
+
await deps.bus.publish(input.threadId, {
|
|
166
|
+
threadId: input.threadId,
|
|
167
|
+
seq: 0,
|
|
168
|
+
type: 'HITL_RESPONSE',
|
|
169
|
+
payload: { toolCallId: input.toolCallId, approved: input.approved },
|
|
170
|
+
createdAt: new Date(),
|
|
171
|
+
});
|
|
172
|
+
// Resume the run segment through the queue — same dispatch path as the
|
|
173
|
+
// original run, rebuilt from the ticket persisted in the event payload.
|
|
174
|
+
// A legacy park without a ticket falls back to the default handle.
|
|
175
|
+
const requested = await deps.storage.events.listByType(input.threadId, 'INPUT_REQUIRED');
|
|
176
|
+
const event = requested.find((e) => e.payload?.toolCallId === input.toolCallId);
|
|
177
|
+
const resume = event?.payload?.resume;
|
|
178
|
+
// REUSE the parked run's id, never mint a new one: this dispatch and the
|
|
179
|
+
// park's expiry job are the same run, and the lock must be able to tell
|
|
180
|
+
// that. Bumping here would let both run and reply twice (§2.5).
|
|
181
|
+
const runId = await currentRunId(deps, input.threadId);
|
|
182
|
+
await deps.queue.enqueue({
|
|
183
|
+
threadId: input.threadId,
|
|
184
|
+
runId,
|
|
185
|
+
model: resume?.model ?? thread.model,
|
|
186
|
+
...(resume
|
|
187
|
+
? {
|
|
188
|
+
agent: resume.agent,
|
|
189
|
+
...(resume.tokenBudget !== undefined ? { tokenBudget: resume.tokenBudget } : {}),
|
|
190
|
+
...(resume.providerOptions ? { providerOptions: resume.providerOptions } : {}),
|
|
191
|
+
// The answer resumes the SAME run, so it must scope storage the same
|
|
192
|
+
// way the parked segment did (§2.10).
|
|
193
|
+
...(resume.state ? { state: resume.state } : {}),
|
|
194
|
+
}
|
|
195
|
+
: {}),
|
|
196
|
+
});
|
|
197
|
+
return { delivered: true };
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=hitl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hitl.js","sourceRoot":"","sources":["../../src/core/hitl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAIzC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtE,MAAM,CAAC,MAAM,WAAW,GAAG,EAAE,GAAG,MAAM,CAAC;AAEvC;;;sDAGsD;AACtD,MAAM,CAAC,MAAM,WAAW,GAAG,iBAAiB,CAAC;AAE7C,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,UAAkB,EAAE,EAAE,CAAC,cAAc,UAAU,EAAE,CAAC;AAkC1E;;;;;;sEAMsE;AACtE,MAAM,UAAU,QAAQ,CACtB,IAAkB,EAClB,QAAgB,EAChB,KAA0B,EAC1B,GAKC;IAED,MAAM,GAAG,GAAwB,EAAE,CAAC;IACpC,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9C,IAAI,CAAE,CAAS,EAAE,oBAAoB,EAAE,CAAC;YACtC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACd,SAAS;QACX,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,GAAG;YACV,GAAG,CAAC;YACJ,OAAO,EAAE,KAAK,EAAE,IAAa,EAAE,IAA6B,EAAE,EAAE;gBAC9D,MAAM,UAAU,GAAG,IAAI,EAAE,UAAU,IAAI,UAAU,EAAE,CAAC;gBACpD,MAAM,eAAe,CAAC,IAAI,EAAE;oBAC1B,QAAQ;oBACR,UAAU;oBACV,QAAQ,EAAE,IAAI;oBACd,IAAI;oBACJ,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,IAAI;oBAC5B,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE;oBACxB,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC7C,MAAM,EAAE,GAAG,CAAC,MAAM;iBACnB,CAAC,CAAC;gBACH,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;YACvC,CAAC;SACF,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;wCAcwC;AACxC,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAkB,EAAE,CAAY;IACpE,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,QAAQ,EAAE,EAAE,mBAAmB,CAAC,CAAC;IACpE,MAAM,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,mBAAmB,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5E,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,gBAAgB,EAAE;QAChD,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,IAAI;QAC1B,SAAS,EAAE,CAAC,CAAC,IAAI;QACjB,WAAW,EAAE,IAAI;QACjB,yDAAyD;QACzD,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE;QACtB,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,MAAM,EAAE,CAAC,CAAC,MAAM;KACjB,CAAC,CAAC;IACH,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,cAAc,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAC;IAEhF,0EAA0E;IAC1E,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,EAAE;IACF,uEAAuE;IACvE,sEAAsE;IACtE,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CACtB;YACE,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,KAAK,EAAE,MAAM,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC;YAC3C,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK;YACrB,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK;YACrB,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpF,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClF,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACrD,EACD,EAAE,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,CAC9D,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,sEAAsE;IACxE,CAAC;AACH,CAAC;AAiBD,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAkB,EAClB,QAAgB;IAEhB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;IAC7E,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,OAAO,iBAAiB,CAAC,OAAO,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAmB;IAC5C,MAAM,CAAC,GAAG,OAAO,CAAC,OAOjB,CAAC;IACF,OAAO;QACL,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,IAAI;QAC1B,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,sEAAsE;QACtE,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE;QACtB,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,WAAW,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;KACnD,CAAC;AACJ,CAAC;AAED;;;;;;gFAMgF;AAChF,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAkB,EAClB,QAAgB;IAEhB,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;IACnF,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEtC,MAAM,OAAO,GAAG,IAAI,GAAG,CACrB,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,CACnE,CAAC,CAAC,EAAE,EAAE,CAAE,CAAC,CAAC,OAA0C,EAAE,UAAU,CACjE,CACF,CAAC;IACF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,KAAK,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC;QACtE,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM;YAAE,SAAS;QAChC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YAC7D,MAAM,EAAE,GAAI,IAAuC,EAAE,UAAU,CAAC;YAChE,IAAI,EAAE;gBAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,OAAO,SAAS;SACb,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;SAChC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED;;;;yBAIyB;AACzB,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAkB,EAAE,KAAmB;IACnE,MAAM,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IAE9C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC9D,yEAAyE;IACzE,yEAAyE;IACzE,yDAAyD;IACzD,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,KAAK,CAAC,UAAU,CAAC,CAAC;IAElE,IAAI,MAAM,EAAE,KAAK,KAAK,mBAAmB,IAAI,CAAC,KAAK,EAAE,CAAC;QACpD,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,mCAAmC,EAAE,CAAC;IAC1E,CAAC;IAED,4EAA4E;IAC5E,uEAAuE;IACvE,oBAAoB;IACpB,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAC3B,EAAE,EACF,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,GAAG,IAAI,CAAC,CAC9E,CAAC;IACF,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG,CACf,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,EACzB,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,EACpE,EAAE,SAAS,EAAE,YAAY,EAAE,CAC5B,CAAC;IACF,0EAA0E;IAC1E,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE;QACrC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,GAAG,EAAE,CAAC;QACN,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE;QACnE,SAAS,EAAE,IAAI,IAAI,EAAE;KACR,CAAC,CAAC;IAEjB,uEAAuE;IACvE,wEAAwE;IACxE,mEAAmE;IACnE,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;IACzF,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAC1B,CAAC,CAAC,EAAE,EAAE,CAAE,CAAC,CAAC,OAA0C,EAAE,UAAU,KAAK,KAAK,CAAC,UAAU,CACtF,CAAC;IACF,MAAM,MAAM,GAAI,KAAK,EAAE,OAAe,EAAE,MAAgC,CAAC;IACzE,yEAAyE;IACzE,wEAAwE;IACxE,gEAAgE;IAChE,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACvD,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;QACvB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK;QACL,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,MAAM,CAAC,KAAK;QACpC,GAAG,CAAC,MAAM;YACR,CAAC,CAAC;gBACE,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,GAAG,CAAC,MAAM,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChF,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC9E,qEAAqE;gBACrE,sCAAsC;gBACtC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACjD;YACH,CAAC,CAAC,EAAE,CAAC;KACR,CAAC,CAAC;IAEH,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { RuntimePorts } from '../ports/runtime.js';
|
|
2
|
+
/** The thread's CURRENT run id (§2.1).
|
|
3
|
+
*
|
|
4
|
+
* Stop and start-a-new-run both write `agent:state:{threadId}`, so the state
|
|
5
|
+
* key alone can never tell a worker its run is over: a user who stops and
|
|
6
|
+
* then sends another message overwrites CANCELLED with RUNNING before the
|
|
7
|
+
* worker's poll ever reads it, and the old run keeps going.
|
|
8
|
+
*
|
|
9
|
+
* This key only moves forward. A worker whose id no longer matches knows it
|
|
10
|
+
* has been replaced, whatever the state key says. */
|
|
11
|
+
export declare const runIdKey: (threadId: string) => string;
|
|
12
|
+
/** Re-dispatch counter for a job that keeps finding the run lock held by an
|
|
13
|
+
* OLDER run (§2.8). Separate from `agent:attempts:` — a blocked job has not
|
|
14
|
+
* failed, it simply has not started yet. */
|
|
15
|
+
export declare const redriveKey: (threadId: string) => string;
|
|
16
|
+
/** The run that owns the thread right now, or null on a thread that predates
|
|
17
|
+
* run ids. Resuming a parked run (§2.5) REUSES this — a resume is the same
|
|
18
|
+
* run continuing, not a new one, so it must never bump the id. */
|
|
19
|
+
export declare function currentRunId(deps: RuntimePorts, threadId: string): Promise<string | undefined>;
|
|
20
|
+
/** Claim the thread for a brand new run and return its id (§2.1).
|
|
21
|
+
*
|
|
22
|
+
* Always called BEFORE the state key is written: bumping this id is what
|
|
23
|
+
* retires an older worker, and it must not depend on the state key that the
|
|
24
|
+
* new run is about to overwrite. */
|
|
25
|
+
export declare function claimRun(deps: RuntimePorts, threadId: string): Promise<string>;
|
|
26
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/core/keys.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD;;;;;;;;sDAQsD;AACtD,eAAO,MAAM,QAAQ,GAAI,UAAU,MAAM,WAA4B,CAAC;AAEtE;;6CAE6C;AAC7C,eAAO,MAAM,UAAU,GAAI,UAAU,MAAM,WAAgC,CAAC;AAE5E;;mEAEmE;AACnE,wBAAsB,YAAY,CAChC,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE7B;AAED;;;;qCAIqC;AACrC,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAIpF"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
/** The thread's CURRENT run id (§2.1).
|
|
3
|
+
*
|
|
4
|
+
* Stop and start-a-new-run both write `agent:state:{threadId}`, so the state
|
|
5
|
+
* key alone can never tell a worker its run is over: a user who stops and
|
|
6
|
+
* then sends another message overwrites CANCELLED with RUNNING before the
|
|
7
|
+
* worker's poll ever reads it, and the old run keeps going.
|
|
8
|
+
*
|
|
9
|
+
* This key only moves forward. A worker whose id no longer matches knows it
|
|
10
|
+
* has been replaced, whatever the state key says. */
|
|
11
|
+
export const runIdKey = (threadId) => `agent:run:${threadId}`;
|
|
12
|
+
/** Re-dispatch counter for a job that keeps finding the run lock held by an
|
|
13
|
+
* OLDER run (§2.8). Separate from `agent:attempts:` — a blocked job has not
|
|
14
|
+
* failed, it simply has not started yet. */
|
|
15
|
+
export const redriveKey = (threadId) => `agent:redrive:${threadId}`;
|
|
16
|
+
/** The run that owns the thread right now, or null on a thread that predates
|
|
17
|
+
* run ids. Resuming a parked run (§2.5) REUSES this — a resume is the same
|
|
18
|
+
* run continuing, not a new one, so it must never bump the id. */
|
|
19
|
+
export async function currentRunId(deps, threadId) {
|
|
20
|
+
return (await deps.kv.get(runIdKey(threadId))) ?? undefined;
|
|
21
|
+
}
|
|
22
|
+
/** Claim the thread for a brand new run and return its id (§2.1).
|
|
23
|
+
*
|
|
24
|
+
* Always called BEFORE the state key is written: bumping this id is what
|
|
25
|
+
* retires an older worker, and it must not depend on the state key that the
|
|
26
|
+
* new run is about to overwrite. */
|
|
27
|
+
export async function claimRun(deps, threadId) {
|
|
28
|
+
const runId = randomUUID();
|
|
29
|
+
await deps.kv.set(runIdKey(threadId), runId);
|
|
30
|
+
return runId;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/core/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC;;;;;;;;sDAQsD;AACtD,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,QAAgB,EAAE,EAAE,CAAC,aAAa,QAAQ,EAAE,CAAC;AAEtE;;6CAE6C;AAC7C,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,QAAgB,EAAE,EAAE,CAAC,iBAAiB,QAAQ,EAAE,CAAC;AAE5E;;mEAEmE;AACnE,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAkB,EAClB,QAAgB;IAEhB,OAAO,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC;AAC9D,CAAC;AAED;;;;qCAIqC;AACrC,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAkB,EAAE,QAAgB;IACjE,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;IAC3B,MAAM,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC;IAC7C,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { LanguageModel } from 'ai';
|
|
2
|
+
import type { RuntimePorts } from '../ports/runtime.js';
|
|
3
|
+
import type { AgentKind, ProviderOptions } from './types.js';
|
|
4
|
+
import type { RegisteredAgent } from './agent.js';
|
|
5
|
+
import { type TokenAttribution } from './usage.js';
|
|
6
|
+
/** True for the sentinel a parked `requiresConfirmation` tool returns (§2.5).
|
|
7
|
+
* It is never a real tool result and is never persisted. */
|
|
8
|
+
export declare const isParked: (result: unknown) => boolean;
|
|
9
|
+
/** One platform-owned step (§2.1, §5.6): a single SDK round-trip with
|
|
10
|
+
* maxSteps: 1. The SDK executes the step's tool calls and reports a
|
|
11
|
+
* structured result; whether to continue is the loop's decision, never
|
|
12
|
+
* the SDK's. */
|
|
13
|
+
export interface StepResult {
|
|
14
|
+
text: string;
|
|
15
|
+
finishReason: string;
|
|
16
|
+
usage: Record<string, number> | undefined;
|
|
17
|
+
/** Assistant + tool messages this step produced — appended to the
|
|
18
|
+
* conversation (in memory AND storage) before the next step. */
|
|
19
|
+
responseMessages: Array<{
|
|
20
|
+
role: string;
|
|
21
|
+
content: unknown;
|
|
22
|
+
}>;
|
|
23
|
+
/** The step's executed tool calls and their results. */
|
|
24
|
+
toolResults: Array<{
|
|
25
|
+
toolCallId: string;
|
|
26
|
+
toolName: string;
|
|
27
|
+
result: unknown;
|
|
28
|
+
}>;
|
|
29
|
+
/** Provider metadata for this step — the only place a cache hit is reported
|
|
30
|
+
* (§2.6). Without carrying it, cachedInputTokens can never be anything but
|
|
31
|
+
* zero. */
|
|
32
|
+
providerMetadata?: Record<string, Record<string, unknown> | undefined>;
|
|
33
|
+
}
|
|
34
|
+
export declare function executeStep(agent: RegisteredAgent, call: {
|
|
35
|
+
kind: AgentKind;
|
|
36
|
+
model: LanguageModel;
|
|
37
|
+
messages: Array<any>;
|
|
38
|
+
tools: Record<string, any>;
|
|
39
|
+
providerOptions?: ProviderOptions;
|
|
40
|
+
abortSignal: AbortSignal;
|
|
41
|
+
onChunk?: (chunk: unknown) => Promise<void>;
|
|
42
|
+
/** Overrides the user's spec args — a nested run brings its own persona
|
|
43
|
+
* and must not inherit the parent's (§2.7). */
|
|
44
|
+
system?: string;
|
|
45
|
+
/** Move the system prompt into `messages` as a stamped message so it can
|
|
46
|
+
* carry a cache breakpoint (§2.6). As the SDK's `system:` string it
|
|
47
|
+
* reaches the provider with no metadata channel and never caches. */
|
|
48
|
+
cacheSystemPrompt?: boolean;
|
|
49
|
+
}): Promise<StepResult>;
|
|
50
|
+
/** Tokens a run has spent, main agent and nested runs together (§2.7). Shared
|
|
51
|
+
* by reference so a child's spend counts against the run's safety cap the
|
|
52
|
+
* moment it happens — a budget that ignores delegated work is not a budget. */
|
|
53
|
+
export interface RunLedger {
|
|
54
|
+
tokensUsed: number;
|
|
55
|
+
}
|
|
56
|
+
export interface LoopInput {
|
|
57
|
+
/** Whose stream this loop persists to (§2.7). `null` is the main agent. */
|
|
58
|
+
agentId: string | null;
|
|
59
|
+
/** The run these steps belong to (§2.9) — a thread has many runs, so a step
|
|
60
|
+
* marker without it cannot be attributed to one. */
|
|
61
|
+
runId?: string;
|
|
62
|
+
kind: AgentKind;
|
|
63
|
+
model: LanguageModel;
|
|
64
|
+
/** Seeded context: compacted history for the main agent, the brief plus its
|
|
65
|
+
* own persisted turns for a nested run. Mutated as steps are appended. */
|
|
66
|
+
messages: Array<any>;
|
|
67
|
+
tools: Record<string, any>;
|
|
68
|
+
maxSteps: number;
|
|
69
|
+
abortSignal: AbortSignal;
|
|
70
|
+
providerOptions?: ProviderOptions;
|
|
71
|
+
/** Cumulative cap for the whole run, checked against the shared ledger. */
|
|
72
|
+
tokenBudget?: number;
|
|
73
|
+
onChunk?: (chunk: unknown) => Promise<void>;
|
|
74
|
+
/** Persona for a nested run; omitted, the agent's own spec `system` stands. */
|
|
75
|
+
system?: string;
|
|
76
|
+
/** Carry the system prompt as a stamped message rather than the SDK's
|
|
77
|
+
* `system:` string, so it can hold a cache breakpoint (§2.6). */
|
|
78
|
+
cacheSystemPrompt?: boolean;
|
|
79
|
+
}
|
|
80
|
+
export interface LoopOutcome {
|
|
81
|
+
text: string;
|
|
82
|
+
finishReason: string;
|
|
83
|
+
/** What THIS loop spent — the caller records it against its own agentId. */
|
|
84
|
+
attribution: TokenAttribution;
|
|
85
|
+
tokensUsed: number;
|
|
86
|
+
/** A requiresConfirmation tool parked: the segment ends here (§2.5). */
|
|
87
|
+
parked: boolean;
|
|
88
|
+
/** The parked call, when `parked`. */
|
|
89
|
+
parkedToolCallId?: string;
|
|
90
|
+
/** The abort signal fired mid-loop — a user stop (§2.1). */
|
|
91
|
+
aborted: boolean;
|
|
92
|
+
/** Iterations this loop completed (§2.9). */
|
|
93
|
+
steps: number;
|
|
94
|
+
}
|
|
95
|
+
/** The platform-owned loop (§2.1, §5.6), run by the main agent and by every
|
|
96
|
+
* nested run alike (§2.7).
|
|
97
|
+
*
|
|
98
|
+
* One single-round-trip step per iteration. After EVERY step the produced
|
|
99
|
+
* messages are persisted under this loop's `agentId`, so a worker that dies
|
|
100
|
+
* mid-run resumes from the last step — and so a parked nested run can be
|
|
101
|
+
* re-entered later instead of restarted. Every continuation decision — tool
|
|
102
|
+
* results ready, budget spent, step ceiling, HITL park, user stop — is made
|
|
103
|
+
* here between steps, never inside the SDK. */
|
|
104
|
+
export declare function runLoop(deps: RuntimePorts, agent: RegisteredAgent, threadId: string, input: LoopInput, ledger: RunLedger): Promise<LoopOutcome>;
|
|
105
|
+
//# sourceMappingURL=loop.d.ts.map
|