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.
Files changed (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +226 -0
  3. package/dist/adapters/inline.d.ts +27 -0
  4. package/dist/adapters/inline.d.ts.map +1 -0
  5. package/dist/adapters/inline.js +41 -0
  6. package/dist/adapters/inline.js.map +1 -0
  7. package/dist/adapters/memory.d.ts +74 -0
  8. package/dist/adapters/memory.d.ts.map +1 -0
  9. package/dist/adapters/memory.js +194 -0
  10. package/dist/adapters/memory.js.map +1 -0
  11. package/dist/adapters/prisma.d.ts +176 -0
  12. package/dist/adapters/prisma.d.ts.map +1 -0
  13. package/dist/adapters/prisma.js +107 -0
  14. package/dist/adapters/prisma.js.map +1 -0
  15. package/dist/adapters/qstash.d.ts +36 -0
  16. package/dist/adapters/qstash.d.ts.map +1 -0
  17. package/dist/adapters/qstash.js +26 -0
  18. package/dist/adapters/qstash.js.map +1 -0
  19. package/dist/adapters/redis.d.ts +49 -0
  20. package/dist/adapters/redis.d.ts.map +1 -0
  21. package/dist/adapters/redis.js +60 -0
  22. package/dist/adapters/redis.js.map +1 -0
  23. package/dist/adapters/sqlite.d.ts +67 -0
  24. package/dist/adapters/sqlite.d.ts.map +1 -0
  25. package/dist/adapters/sqlite.js +199 -0
  26. package/dist/adapters/sqlite.js.map +1 -0
  27. package/dist/adapters/upstash.d.ts +48 -0
  28. package/dist/adapters/upstash.d.ts.map +1 -0
  29. package/dist/adapters/upstash.js +46 -0
  30. package/dist/adapters/upstash.js.map +1 -0
  31. package/dist/admin/default.d.ts +15 -0
  32. package/dist/admin/default.d.ts.map +1 -0
  33. package/dist/admin/default.js +22 -0
  34. package/dist/admin/default.js.map +1 -0
  35. package/dist/admin/memory.d.ts +29 -0
  36. package/dist/admin/memory.d.ts.map +1 -0
  37. package/dist/admin/memory.js +83 -0
  38. package/dist/admin/memory.js.map +1 -0
  39. package/dist/admin/postgres.d.ts +43 -0
  40. package/dist/admin/postgres.d.ts.map +1 -0
  41. package/dist/admin/postgres.js +201 -0
  42. package/dist/admin/postgres.js.map +1 -0
  43. package/dist/admin/sqlite.d.ts +35 -0
  44. package/dist/admin/sqlite.d.ts.map +1 -0
  45. package/dist/admin/sqlite.js +187 -0
  46. package/dist/admin/sqlite.js.map +1 -0
  47. package/dist/core/admin.d.ts +88 -0
  48. package/dist/core/admin.d.ts.map +1 -0
  49. package/dist/core/admin.js +147 -0
  50. package/dist/core/admin.js.map +1 -0
  51. package/dist/core/agent.d.ts +29 -0
  52. package/dist/core/agent.d.ts.map +1 -0
  53. package/dist/core/agent.js +41 -0
  54. package/dist/core/agent.js.map +1 -0
  55. package/dist/core/cache.d.ts +37 -0
  56. package/dist/core/cache.d.ts.map +1 -0
  57. package/dist/core/cache.js +68 -0
  58. package/dist/core/cache.js.map +1 -0
  59. package/dist/core/context.d.ts +13 -0
  60. package/dist/core/context.d.ts.map +1 -0
  61. package/dist/core/context.js +87 -0
  62. package/dist/core/context.js.map +1 -0
  63. package/dist/core/deleteThread.d.ts +14 -0
  64. package/dist/core/deleteThread.d.ts.map +1 -0
  65. package/dist/core/deleteThread.js +34 -0
  66. package/dist/core/deleteThread.js.map +1 -0
  67. package/dist/core/engine.d.ts +79 -0
  68. package/dist/core/engine.d.ts.map +1 -0
  69. package/dist/core/engine.js +489 -0
  70. package/dist/core/engine.js.map +1 -0
  71. package/dist/core/follow.d.ts +50 -0
  72. package/dist/core/follow.d.ts.map +1 -0
  73. package/dist/core/follow.js +153 -0
  74. package/dist/core/follow.js.map +1 -0
  75. package/dist/core/hitl.d.ts +98 -0
  76. package/dist/core/hitl.d.ts.map +1 -0
  77. package/dist/core/hitl.js +199 -0
  78. package/dist/core/hitl.js.map +1 -0
  79. package/dist/core/keys.d.ts +26 -0
  80. package/dist/core/keys.d.ts.map +1 -0
  81. package/dist/core/keys.js +32 -0
  82. package/dist/core/keys.js.map +1 -0
  83. package/dist/core/loop.d.ts +105 -0
  84. package/dist/core/loop.d.ts.map +1 -0
  85. package/dist/core/loop.js +234 -0
  86. package/dist/core/loop.js.map +1 -0
  87. package/dist/core/publish.d.ts +18 -0
  88. package/dist/core/publish.d.ts.map +1 -0
  89. package/dist/core/publish.js +34 -0
  90. package/dist/core/publish.js.map +1 -0
  91. package/dist/core/reclaim.d.ts +20 -0
  92. package/dist/core/reclaim.d.ts.map +1 -0
  93. package/dist/core/reclaim.js +56 -0
  94. package/dist/core/reclaim.js.map +1 -0
  95. package/dist/core/run.d.ts +8 -0
  96. package/dist/core/run.d.ts.map +1 -0
  97. package/dist/core/run.js +102 -0
  98. package/dist/core/run.js.map +1 -0
  99. package/dist/core/state.d.ts +50 -0
  100. package/dist/core/state.d.ts.map +1 -0
  101. package/dist/core/state.js +39 -0
  102. package/dist/core/state.js.map +1 -0
  103. package/dist/core/stop.d.ts +7 -0
  104. package/dist/core/stop.d.ts.map +1 -0
  105. package/dist/core/stop.js +15 -0
  106. package/dist/core/stop.js.map +1 -0
  107. package/dist/core/stream.d.ts +12 -0
  108. package/dist/core/stream.d.ts.map +1 -0
  109. package/dist/core/stream.js +21 -0
  110. package/dist/core/stream.js.map +1 -0
  111. package/dist/core/subagent.d.ts +77 -0
  112. package/dist/core/subagent.d.ts.map +1 -0
  113. package/dist/core/subagent.js +261 -0
  114. package/dist/core/subagent.js.map +1 -0
  115. package/dist/core/tools.d.ts +36 -0
  116. package/dist/core/tools.d.ts.map +1 -0
  117. package/dist/core/tools.js +22 -0
  118. package/dist/core/tools.js.map +1 -0
  119. package/dist/core/types.d.ts +286 -0
  120. package/dist/core/types.d.ts.map +1 -0
  121. package/dist/core/types.js +49 -0
  122. package/dist/core/types.js.map +1 -0
  123. package/dist/core/usage.d.ts +40 -0
  124. package/dist/core/usage.d.ts.map +1 -0
  125. package/dist/core/usage.js +43 -0
  126. package/dist/core/usage.js.map +1 -0
  127. package/dist/index.d.ts +25 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +23 -0
  130. package/dist/index.js.map +1 -0
  131. package/dist/ports/admin.d.ts +102 -0
  132. package/dist/ports/admin.d.ts.map +1 -0
  133. package/dist/ports/admin.js +2 -0
  134. package/dist/ports/admin.js.map +1 -0
  135. package/dist/ports/bus.d.ts +13 -0
  136. package/dist/ports/bus.d.ts.map +1 -0
  137. package/dist/ports/bus.js +2 -0
  138. package/dist/ports/bus.js.map +1 -0
  139. package/dist/ports/kv.d.ts +16 -0
  140. package/dist/ports/kv.d.ts.map +1 -0
  141. package/dist/ports/kv.js +2 -0
  142. package/dist/ports/kv.js.map +1 -0
  143. package/dist/ports/queue.d.ts +18 -0
  144. package/dist/ports/queue.d.ts.map +1 -0
  145. package/dist/ports/queue.js +2 -0
  146. package/dist/ports/queue.js.map +1 -0
  147. package/dist/ports/runtime.d.ts +260 -0
  148. package/dist/ports/runtime.d.ts.map +1 -0
  149. package/dist/ports/runtime.js +2 -0
  150. package/dist/ports/runtime.js.map +1 -0
  151. package/dist/ports/storage.d.ts +72 -0
  152. package/dist/ports/storage.d.ts.map +1 -0
  153. package/dist/ports/storage.js +2 -0
  154. package/dist/ports/storage.js.map +1 -0
  155. package/dist/runtime.d.ts +5 -0
  156. package/dist/runtime.d.ts.map +1 -0
  157. package/dist/runtime.js +177 -0
  158. package/dist/runtime.js.map +1 -0
  159. 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":"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"}
@@ -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