@minpeter/pss-runtime 0.1.0-next.0 → 0.1.0-next.1

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 (96) hide show
  1. package/README.md +85 -194
  2. package/dist/agent-loop.js +8 -14
  3. package/dist/agent-loop.js.map +1 -1
  4. package/dist/agent-namespace.js +17 -0
  5. package/dist/agent-namespace.js.map +1 -0
  6. package/dist/agent-validation.js +35 -0
  7. package/dist/agent-validation.js.map +1 -0
  8. package/dist/agent.d.ts +21 -10
  9. package/dist/agent.js +81 -37
  10. package/dist/agent.js.map +1 -1
  11. package/dist/child-session-cleanups.js +61 -0
  12. package/dist/child-session-cleanups.js.map +1 -0
  13. package/dist/hooks.d.ts +32 -0
  14. package/dist/index.d.ts +4 -8
  15. package/dist/index.js +1 -6
  16. package/dist/llm.js +1 -7
  17. package/dist/llm.js.map +1 -1
  18. package/dist/session/events.d.ts +23 -20
  19. package/dist/session/input-normalization.js +66 -0
  20. package/dist/session/input-normalization.js.map +1 -0
  21. package/dist/session/input.d.ts +0 -4
  22. package/dist/session/mapping.js +1 -2
  23. package/dist/session/mapping.js.map +1 -1
  24. package/dist/session/run.js +1 -0
  25. package/dist/session/run.js.map +1 -1
  26. package/dist/session/runtime-input.js +38 -58
  27. package/dist/session/runtime-input.js.map +1 -1
  28. package/dist/session/session-errors.js +23 -0
  29. package/dist/session/session-errors.js.map +1 -0
  30. package/dist/session/session-kill.js +23 -0
  31. package/dist/session/session-kill.js.map +1 -0
  32. package/dist/session/session-runtime-drain.js +22 -0
  33. package/dist/session/session-runtime-drain.js.map +1 -0
  34. package/dist/session/session-state.js +102 -0
  35. package/dist/session/session-state.js.map +1 -0
  36. package/dist/session/session-turn-error.js +35 -0
  37. package/dist/session/session-turn-error.js.map +1 -0
  38. package/dist/session/session.js +103 -285
  39. package/dist/session/session.js.map +1 -1
  40. package/dist/session/snapshot.js +5 -31
  41. package/dist/session/snapshot.js.map +1 -1
  42. package/dist/session/store/file.d.ts +1 -0
  43. package/dist/session/store/file.js +14 -0
  44. package/dist/session/store/file.js.map +1 -1
  45. package/dist/session/store/memory.d.ts +1 -0
  46. package/dist/session/store/memory.js +5 -0
  47. package/dist/session/store/memory.js.map +1 -1
  48. package/dist/session/store/types.d.ts +1 -0
  49. package/dist/subagent-job-cancel.js +28 -0
  50. package/dist/subagent-job-cancel.js.map +1 -0
  51. package/dist/subagent-job-output.js +63 -0
  52. package/dist/subagent-job-output.js.map +1 -0
  53. package/dist/subagent-jobs.js +151 -0
  54. package/dist/subagent-jobs.js.map +1 -0
  55. package/dist/subagent-prompt-schema.js +114 -0
  56. package/dist/subagent-prompt-schema.js.map +1 -0
  57. package/dist/subagent-run.js +111 -0
  58. package/dist/subagent-run.js.map +1 -0
  59. package/dist/subagents.js +92 -0
  60. package/dist/subagents.js.map +1 -0
  61. package/package.json +1 -6
  62. package/dist/plugins/compaction.d.ts +0 -15
  63. package/dist/plugins/compaction.js +0 -98
  64. package/dist/plugins/compaction.js.map +0 -1
  65. package/dist/plugins/index.d.ts +0 -5
  66. package/dist/plugins/index.js +0 -5
  67. package/dist/plugins/memory.d.ts +0 -11
  68. package/dist/plugins/memory.js +0 -146
  69. package/dist/plugins/memory.js.map +0 -1
  70. package/dist/plugins/runner.d.ts +0 -1
  71. package/dist/plugins/runner.js +0 -83
  72. package/dist/plugins/runner.js.map +0 -1
  73. package/dist/plugins/scope.js +0 -13
  74. package/dist/plugins/scope.js.map +0 -1
  75. package/dist/plugins/sessions.d.ts +0 -12
  76. package/dist/plugins/sessions.js +0 -34
  77. package/dist/plugins/sessions.js.map +0 -1
  78. package/dist/plugins/tool-hook-handlers.js +0 -77
  79. package/dist/plugins/tool-hook-handlers.js.map +0 -1
  80. package/dist/plugins/tool-hook-results.js +0 -64
  81. package/dist/plugins/tool-hook-results.js.map +0 -1
  82. package/dist/plugins/tool-hooks.js +0 -111
  83. package/dist/plugins/tool-hooks.js.map +0 -1
  84. package/dist/plugins/types.d.ts +0 -105
  85. package/dist/plugins/types.js +0 -20
  86. package/dist/plugins/types.js.map +0 -1
  87. package/dist/session/lifecycle.d.ts +0 -12
  88. package/dist/session/lifecycle.js +0 -126
  89. package/dist/session/lifecycle.js.map +0 -1
  90. package/dist/session/overlay-anchor.js +0 -151
  91. package/dist/session/overlay-anchor.js.map +0 -1
  92. package/dist/session/overlay.js +0 -141
  93. package/dist/session/overlay.js.map +0 -1
  94. package/dist/session/snapshot.d.ts +0 -1
  95. /package/dist/{agent-loop.d.ts → session/history.d.ts} +0 -0
  96. /package/dist/{plugins/scope.d.ts → session/session-state.d.ts} +0 -0
package/README.md CHANGED
@@ -13,7 +13,7 @@ Minimal, platform-agnostic agent runtime with keyed sessions, synchronized
13
13
  import { Agent } from "@minpeter/pss-runtime";
14
14
  import { createYourLanguageModel } from "...";
15
15
 
16
- const agent = await Agent.create({
16
+ const agent = new Agent({
17
17
  instructions: "Answer briefly.",
18
18
  model: createYourLanguageModel(),
19
19
  });
@@ -28,23 +28,7 @@ for await (const event of run.events()) {
28
28
  boundaries until the events consumer asks for the next event, so callers must
29
29
  consume the events for the run to progress. This is what lets code react to
30
30
  `turn-start`, `step-start`, and `step-end` before the next model snapshot is
31
- created. `AgentRun.events()` is single-consumer by design: keep rendering,
32
- logging, tracing, and continuation policy in the same app-owned loop when those
33
- concerns must share synchronized boundary control.
34
-
35
- ```ts
36
- const run = await agent.send("Implement the plan.");
37
- const session = agent.session("default");
38
-
39
- for await (const event of run.events()) {
40
- renderEvent(event);
41
- traceEvent(event);
42
-
43
- if (event.type === "step-end" && shouldContinueWork()) {
44
- await session.steer("Continue. The task is not complete yet.");
45
- }
46
- }
47
- ```
31
+ created.
48
32
 
49
33
  Per-key conversations use `session(key)`:
50
34
 
@@ -96,6 +80,63 @@ The public transcript protocol is `AgentEvent`: live runs emit runtime-defined
96
80
  events through `run.events()`. Provider/model message history is internal
97
81
  continuation state, not a public history API.
98
82
 
83
+ ## Subagents
84
+
85
+ Compose specialist agents by constructing them first and passing them as an
86
+ array. Top-level agents may omit metadata, but agents used as subagents need a
87
+ stable `name` and `description` so the runtime can expose clear model-facing
88
+ delegate tools.
89
+
90
+ ```ts
91
+ const researcher = new Agent({
92
+ name: "researcher",
93
+ description: "Researches facts and returns concise evidence.",
94
+ model,
95
+ instructions: "Research facts and return concise evidence.",
96
+ });
97
+
98
+ const coordinator = new Agent({
99
+ model,
100
+ instructions: "Coordinate work and delegate when useful.",
101
+ subagents: [researcher],
102
+ });
103
+ ```
104
+
105
+ For each subagent, the parent model receives a generated
106
+ `delegate_to_<name>` tool. The tool accepts `prompt`, optional `description`,
107
+ optional `sessionKey` suffix, and `run_in_background`. A provided `sessionKey`
108
+ is always scoped under the parent session and subagent name; the model cannot
109
+ select an arbitrary child session key. Omitting `run_in_background` defaults to
110
+ blocking behavior and returns compact child text, not the full child event
111
+ stream.
112
+
113
+ ```ts
114
+ delegate_to_researcher({
115
+ prompt: "Find the current release notes and summarize the evidence.",
116
+ });
117
+ ```
118
+
119
+ When the model sets `run_in_background: true`, the parent run can finish while
120
+ the child keeps working. The launch result includes a `bg_...` `task_id`. A
121
+ compact runtime reminder is queued for the parent when the child finishes, and
122
+ the model can retrieve the result with `background_output`.
123
+
124
+ ```ts
125
+ delegate_to_researcher({
126
+ prompt: "Compare the API designs.",
127
+ run_in_background: true,
128
+ });
129
+
130
+ background_output({ task_id: "bg_...", block: true });
131
+ background_cancel({ task_id: "bg_..." });
132
+ ```
133
+
134
+ The parent model context stays compact by default: completion reminders include
135
+ the task id, subagent name, description, and retrieval instruction. Full child
136
+ traces are not injected into the parent transcript by default. Background jobs
137
+ run in task-scoped child sessions, and retrieved completed jobs are forgotten
138
+ after `background_output` returns.
139
+
99
140
  ## Send and Steer
100
141
 
101
142
  Use `session.send(input)` for a new user turn. If a run is already active, the
@@ -138,201 +179,51 @@ for await (const event of run.events()) {
138
179
  pending steering path or, when idle, when a new run is scheduled. It does not wait
139
180
  for a later model snapshot.
140
181
 
141
- ## Overlay
142
-
143
- Use `session.overlay(input)` for turn-scoped model context that should affect
144
- the next model snapshot without becoming canonical session history. It accepts
145
- the same input shapes as `send()` and `steer()` and has no options in v0.
146
-
147
- Overlay context accepted before the first model inference in a turn is composed
148
- above the current user prompt. This keeps the current prompt near the bottom of
149
- the model snapshot while still giving the model fresh runtime context first.
150
- Overlay context accepted after a model inference has already happened is
151
- append-only: it is added after the messages that already existed for that turn
152
- and never moves earlier content around.
153
-
154
- ```ts
155
- const session = agent.session("room:123:user:456");
156
-
157
- await session.overlay("Current wall-clock time: 2026-06-05T19:00:00Z");
158
- const run = await session.send("What should I do next?");
159
-
160
- for await (const event of run.events()) {
161
- if (event.type === "step-end" && needsOneMorePass()) {
162
- await session.overlay("Additional constraint: answer in two sentences.");
163
- }
164
- }
165
- ```
166
-
167
- Active `step-end` overlays intentionally continue the current turn for one more
168
- model snapshot, like `step-end` steering, but they are not persisted as
169
- `runtime-input`. Idle overlays do not start a turn; they queue for the next
170
- `send()` on that session.
171
-
172
- Runs emit `overlay-accepted` when overlay input is accepted and
173
- `overlay-expired` when the turn-scoped frame is discarded at turn end, abort,
174
- error, or kill. Overlay text is not written to stored history, not encoded in
175
- session snapshots, and does not survive session reload.
176
-
177
- ## Plugins, Session Storage, Memory, And Compaction
182
+ ## Session storage and portability
178
183
 
179
184
  The runtime owns full session state encoding and history compaction semantics.
180
- Persistence, memory, and compaction are configured through in-process plugins:
181
-
182
- ```ts
183
- import { Agent } from "@minpeter/pss-runtime";
184
- import { compaction, memory, sessions } from "@minpeter/pss-runtime/plugins";
185
+ Adapters own persistence only through `SessionStore`:
185
186
 
186
- const agent = await Agent.create({
187
- model,
188
- plugins: [sessions.file(".pss/sessions"), memory(), compaction()],
189
- });
190
- ```
191
-
192
- If no persistence plugin is provided, sessions are memory-backed by default.
193
-
194
- Reusable middleware belongs in plugins. Plugins can observe turn and step
195
- lifecycle events and call the scoped `steer` function to insert runtime input at
196
- the active boundary or the scoped `overlay` function to add turn-scoped
197
- non-persistent context before the turn ends. Plugin `overlay` is available from
198
- `turn.before`, `step.before`, and `step.after`; `turn.after` runs after the
199
- turn-scoped overlay frame is closed and rejects overlay calls. App-level control
200
- should stay with `run.events()` plus `session.steer()` or `session.overlay()`;
201
- plugin lifecycle is for reusable policy.
187
+ Stored session state is an opaque, versioned runtime snapshot for continuation.
188
+ Do not inspect it as a replay log; exact replay should be modeled separately as
189
+ an `AgentEvent` log if that capability is added later.
202
190
 
203
- Plugin event names are dotted middleware names: `turn.before`, `step.before`,
204
- `step.after`, `turn.after`, `tool.call`, and `tool.result`. These are separate
205
- from public `run.events()` transcript names such as `turn-start`, `step-start`,
206
- `assistant-text`, `tool-call`, `tool-result`, `step-end`, and `turn-end`.
191
+ Custom stores own version generation. `load(key)` returns the opaque `state` with
192
+ the store-minted `version`; `commit(key, { state }, { expectedVersion })` receives
193
+ state only and should reject stale versions by returning `{ ok: false, reason:
194
+ "conflict" }`. On success, the store persists `{ state, version }` and returns the
195
+ new version to the runtime. `delete(key)` removes the persisted session for that
196
+ key.
207
197
 
208
198
  ```ts
209
- import { Agent, definePlugin } from "@minpeter/pss-runtime";
210
-
211
- const continuePlugin = definePlugin({
212
- name: "continue-policy",
213
- setup(host) {
214
- host.on("step.after", async ({ result, history, overlay, steer, stepIndex }) => {
215
- if (result === "completed" && stepIndex === 0 && shouldContinueWork(history)) {
216
- await overlay("Continue, but do not persist this policy text.");
217
- await steer("Continue. The task is not complete yet.");
218
- }
219
- });
220
- },
221
- });
199
+ import type { SessionStore } from "@minpeter/pss-runtime";
200
+ import { MemorySessionStore } from "@minpeter/pss-runtime/session-store/memory";
222
201
 
223
- const agent = await Agent.create({
202
+ const agent = new Agent({
224
203
  model,
225
- plugins: [continuePlugin],
226
- });
227
- ```
228
-
229
- `turn.after` is useful for audit, metrics, or scheduling a separate follow-up
230
- run after the current turn has committed.
231
-
232
- ```ts
233
- const auditPlugin = definePlugin({
234
- name: "turn-audit",
235
- setup(host) {
236
- host.on("turn.after", ({ result, sessionKey }) => {
237
- recordTurnResult(sessionKey, result);
238
- });
239
- },
240
- });
241
- ```
242
-
243
- Tool policy hooks apply to runtime-owned tools in the `Agent.create({ model,
244
- tools })` path, including tools registered by plugins. Custom `llm` callers own
245
- their tool execution and do not receive synthetic tool hook events.
246
-
247
- `tool.call` runs after AI SDK input parsing and before the original tool
248
- `execute`. Handlers run in plugin registration order. `allow` continues to the
249
- next handler, `modify` replaces the input for later handlers and execution,
250
- `reject-and-continue` skips the original tool and returns a rejection payload to
251
- the model, `synthesize` skips the original tool and returns a synthetic output,
252
- and `error` fails the active run.
253
-
254
- ```ts
255
- import { definePlugin } from "@minpeter/pss-runtime";
256
-
257
- const toolPolicyPlugin = definePlugin({
258
- name: "tool-policy",
259
- setup(host) {
260
- host.on("tool.call", ({ input, tool }) => {
261
- if (tool === "delete_file") {
262
- return {
263
- action: "reject-and-continue",
264
- message: "delete_file is disabled in this workspace.",
265
- };
266
- }
267
-
268
- if (tool === "search" && shouldNarrowSearch(input)) {
269
- return { action: "modify", input: narrowSearchInput(input) };
270
- }
271
-
272
- return { action: "allow" };
273
- });
204
+ sessions: {
205
+ namespace: "support-agent",
206
+ store: new MemorySessionStore(), // default when omitted
274
207
  },
275
208
  });
276
209
  ```
277
210
 
278
- `tool.result` runs after allowed/modified execution, rejected calls, synthesized
279
- calls, and original tool errors. It can observe or replace the model-facing
280
- result with `{ status: "done", output }`, `{ status: "error", error, output }`,
281
- or `{ status: "cancelled", error, output }`. Replacements flow into later
282
- `tool.result` handlers.
211
+ For durable sessions, use the exported file POC. Set a stable `namespace` when
212
+ subagents also use durable stores, so reconstructed agents map the same parent
213
+ session and child `sessionKey` suffixes back to the same child transcripts:
283
214
 
284
215
  ```ts
285
- const resultPolicyPlugin = definePlugin({
286
- name: "tool-result-policy",
287
- setup(host) {
288
- host.on("tool.result", ({ output, status, tool }) => {
289
- if (tool === "read_secret" && status === "done") {
290
- return {
291
- status: "done",
292
- output: redactSecretOutput(output),
293
- };
294
- }
295
- });
296
- },
297
- });
298
- ```
299
-
300
- Custom stores still own version generation through `SessionStore`. Use
301
- `sessions.custom(store)` when the runtime should persist through a caller-owned
302
- store:
216
+ import { FileSessionStore } from "@minpeter/pss-runtime/session-store/file";
303
217
 
304
- ```ts
305
- import type { SessionStore } from "@minpeter/pss-runtime";
306
- import { sessions } from "@minpeter/pss-runtime/plugins";
307
-
308
- declare const store: SessionStore;
309
-
310
- const agent = await Agent.create({
218
+ const agent = new Agent({
311
219
  model,
312
- plugins: [sessions.custom(store)],
220
+ sessions: {
221
+ namespace: "support-agent",
222
+ store: new FileSessionStore(".pss/sessions"),
223
+ },
313
224
  });
314
225
  ```
315
226
 
316
- Stored session state is opaque, versioned runtime continuation state:
317
-
318
- Do not inspect it as a replay log; exact replay should be modeled separately as
319
- an `AgentEvent` log if that capability is added later.
320
-
321
- `load(key)` returns the opaque `state` with the store-minted `version`;
322
- `commit(key, { state }, { expectedVersion })` receives state only and should
323
- reject stale versions by returning `{ ok: false, reason: "conflict" }`. On
324
- success, the store persists `{ state, version }` and returns the new version to
325
- the runtime.
326
-
327
- `memory()` adds session-scoped tools named `set_context`, `load_context`, and
328
- `search_context`. Search is deterministic lexical matching by default; no
329
- embedding provider is required. Memory is injected into model-facing context
330
- without mutating top-level instructions.
331
-
332
- `compaction()` stores non-destructive overlays with `startIndex` and `endIndex`.
333
- The full canonical history remains in the session snapshot; summaries are
334
- applied only to model-facing context.
335
-
336
227
  ## Future adapter boundary: Cloudflare multi-user DX
337
228
 
338
229
  Cloudflare Durable Objects are a future adapter target, not a runtime dependency.
@@ -1,10 +1,10 @@
1
1
  import { modelMessageToAgentEvents } from "./session/mapping.js";
2
2
  //#region src/agent-loop.ts
3
- async function runAgentLoop({ emit, history, llm, signal = new AbortController().signal, stepLifecycle }) {
3
+ async function runAgentLoop({ emit, history, hooks, llm, signal = new AbortController().signal }) {
4
4
  let stepIndex = 0;
5
5
  while (true) {
6
6
  if (signal.aborted) return "aborted";
7
- await stepLifecycle?.beforeStep?.({
7
+ await hooks?.beforeStep?.({
8
8
  history: history.modelSnapshot(),
9
9
  signal,
10
10
  stepIndex
@@ -15,12 +15,6 @@ async function runAgentLoop({ emit, history, llm, signal = new AbortController()
15
15
  event: { type: "step-start" },
16
16
  signal
17
17
  }) === "aborted") return "aborted";
18
- await stepLifecycle?.beforeInference?.({
19
- history: history.modelSnapshot(),
20
- signal,
21
- stepIndex
22
- });
23
- if (signal.aborted) return "aborted";
24
18
  const output = await readLlmOutput({
25
19
  history,
26
20
  llm,
@@ -34,7 +28,7 @@ async function runAgentLoop({ emit, history, llm, signal = new AbortController()
34
28
  signal
35
29
  });
36
30
  if (result === "aborted") return "aborted";
37
- await runAfterStepLifecycle(stepLifecycle, {
31
+ await runAfterStepHook(hooks, {
38
32
  history: history.modelSnapshot(),
39
33
  result,
40
34
  signal,
@@ -46,7 +40,7 @@ async function runAgentLoop({ emit, history, llm, signal = new AbortController()
46
40
  signal
47
41
  });
48
42
  if (stepEndDecision === "aborted") return "aborted";
49
- if (result === "completed" && !stepEndDecision?.runtimeInputAdded && !stepEndDecision?.overlayInputAdded) return "completed";
43
+ if (result === "completed" && !stepEndDecision?.runtimeInputAdded) return "completed";
50
44
  stepIndex += 1;
51
45
  }
52
46
  }
@@ -74,10 +68,10 @@ function createAbortBoundary(signal) {
74
68
  promise
75
69
  };
76
70
  }
77
- async function runAfterStepLifecycle(stepLifecycle, context) {
78
- const afterStep = stepLifecycle?.afterStep;
79
- if (!afterStep) return;
80
- await Promise.allSettled([Promise.resolve().then(() => afterStep(context))]);
71
+ async function runAfterStepHook(hooks, context) {
72
+ const hook = hooks?.afterStep;
73
+ if (!hook) return;
74
+ await Promise.allSettled([Promise.resolve().then(() => hook(context))]);
81
75
  }
82
76
  async function readLlmOutput({ history, llm, signal }) {
83
77
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"agent-loop.js","names":[],"sources":["../src/agent-loop.ts"],"sourcesContent":["import type { ModelMessage } from \"ai\";\nimport type { Llm, LlmOutput } from \"./llm\";\nimport type { AgentEvent, AgentEventListener } from \"./session/events\";\nimport { modelMessageToAgentEvents } from \"./session/mapping\";\n\ntype MaybePromise<T> = Promise<T> | T;\n\ninterface ModelHistory {\n appendModelMessage(message: ModelMessage): void;\n modelSnapshot(): ModelMessage[];\n}\n\ninterface RunAgentLoopOptions {\n emit: AgentLoopEventListener;\n history: ModelHistory;\n llm: Llm;\n signal?: AbortSignal;\n stepLifecycle?: AgentStepLifecycle;\n}\n\nexport type AgentLoopResult = \"completed\" | \"aborted\";\nexport type AgentStepResult = \"completed\" | \"continue\";\nexport interface AgentBeforeStepContext {\n readonly history: readonly ModelMessage[];\n readonly signal: AbortSignal;\n readonly stepIndex: number;\n}\nexport interface AgentAfterStepContext extends AgentBeforeStepContext {\n readonly result: AgentStepResult;\n}\nexport interface AgentStepLifecycle {\n afterStep?(context: AgentAfterStepContext): MaybePromise<void>;\n beforeInference?(context: AgentBeforeStepContext): MaybePromise<void>;\n beforeStep?(context: AgentBeforeStepContext): MaybePromise<void>;\n}\ntype AgentLoopBoundaryEvent = Extract<\n AgentEvent,\n { type: \"step-end\" } | { type: \"step-start\" }\n>;\ninterface AgentLoopBoundaryDecision {\n readonly overlayInputAdded?: boolean;\n readonly runtimeInputAdded?: boolean;\n}\ntype AgentLoopEventListener = (\n event: AgentEvent\n) =>\n | AgentLoopBoundaryDecision\n | Promise<AgentLoopBoundaryDecision | undefined>\n | undefined;\ntype StepOutputResult = AgentStepResult | \"aborted\";\n\nexport async function runAgentLoop({\n emit,\n history,\n llm,\n signal = new AbortController().signal,\n stepLifecycle,\n}: RunAgentLoopOptions): Promise<AgentLoopResult> {\n let stepIndex = 0;\n\n while (true) {\n if (signal.aborted) {\n return \"aborted\";\n }\n\n await stepLifecycle?.beforeStep?.({\n history: history.modelSnapshot(),\n signal,\n stepIndex,\n });\n\n if (signal.aborted) {\n return \"aborted\";\n }\n\n const stepStartDecision = await emitBoundary({\n emit,\n event: { type: \"step-start\" },\n signal,\n });\n\n if (stepStartDecision === \"aborted\") {\n return \"aborted\";\n }\n\n await stepLifecycle?.beforeInference?.({\n history: history.modelSnapshot(),\n signal,\n stepIndex,\n });\n\n if (signal.aborted) {\n return \"aborted\";\n }\n\n const output = await readLlmOutput({ history, llm, signal });\n\n if (output === \"aborted\") {\n return \"aborted\";\n }\n\n const result = appendStepOutput({ emit, history, output, signal });\n\n if (result === \"aborted\") {\n return \"aborted\";\n }\n\n await runAfterStepLifecycle(stepLifecycle, {\n history: history.modelSnapshot(),\n result,\n signal,\n stepIndex,\n });\n\n const stepEndDecision = await emitBoundary({\n emit,\n event: { type: \"step-end\" },\n signal,\n });\n\n if (stepEndDecision === \"aborted\") {\n return \"aborted\";\n }\n\n // Runtime input after step-end intentionally forces another inference step,\n // even after final-looking assistant text. Unconditional insertion on every\n // step-end can create an unbounded loop.\n if (\n result === \"completed\" &&\n !stepEndDecision?.runtimeInputAdded &&\n !stepEndDecision?.overlayInputAdded\n ) {\n return \"completed\";\n }\n\n stepIndex += 1;\n }\n}\n\nasync function emitBoundary({\n emit,\n event,\n signal,\n}: Pick<RunAgentLoopOptions, \"emit\"> & {\n event: AgentLoopBoundaryEvent;\n signal: AbortSignal;\n}): Promise<AgentLoopBoundaryDecision | \"aborted\" | undefined> {\n if (signal.aborted) {\n return \"aborted\";\n }\n\n const abort = createAbortBoundary(signal);\n try {\n return await Promise.race([Promise.resolve(emit(event)), abort.promise]);\n } catch (error) {\n if (signal.aborted) {\n return \"aborted\";\n }\n\n throw error;\n } finally {\n abort.dispose();\n }\n}\n\nfunction createAbortBoundary(signal: AbortSignal): {\n dispose: () => void;\n promise: Promise<\"aborted\">;\n} {\n let dispose: () => void = () => undefined;\n\n const promise = new Promise<\"aborted\">((resolve) => {\n const onAbort = () => resolve(\"aborted\");\n dispose = () => signal.removeEventListener(\"abort\", onAbort);\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n\n return { dispose, promise };\n}\n\nasync function runAfterStepLifecycle(\n stepLifecycle: AgentStepLifecycle | undefined,\n context: AgentAfterStepContext\n): Promise<void> {\n const afterStep = stepLifecycle?.afterStep;\n if (!afterStep) {\n return;\n }\n\n await Promise.allSettled([Promise.resolve().then(() => afterStep(context))]);\n}\n\nasync function readLlmOutput({\n history,\n llm,\n signal,\n}: Pick<RunAgentLoopOptions, \"history\" | \"llm\"> & {\n signal: AbortSignal;\n}): Promise<LlmOutput | \"aborted\"> {\n try {\n return await llm({ history: history.modelSnapshot(), signal });\n } catch (error) {\n if (signal.aborted) {\n return \"aborted\";\n }\n\n throw error;\n }\n}\n\nfunction appendStepOutput({\n emit,\n history,\n output,\n signal,\n}: { emit: AgentEventListener; history: ModelHistory } & {\n output: LlmOutput;\n signal: AbortSignal;\n}): StepOutputResult {\n if (signal.aborted) {\n return \"aborted\";\n }\n\n let shouldContinue = false;\n\n for (const message of output) {\n if (signal.aborted) {\n return \"aborted\";\n }\n\n history.appendModelMessage(message);\n const events = modelMessageToAgentEvents(message);\n\n for (const event of events) {\n emit(event);\n }\n\n if (events.some((event) => event.type === \"tool-call\")) {\n shouldContinue = true;\n }\n }\n\n return shouldContinue ? \"continue\" : \"completed\";\n}\n"],"mappings":";;AAmDA,eAAsB,aAAa,EACjC,MACA,SACA,KACA,SAAS,IAAI,gBAAgB,EAAE,QAC/B,iBACgD;CAChD,IAAI,YAAY;CAEhB,OAAO,MAAM;EACX,IAAI,OAAO,SACT,OAAO;EAGT,MAAM,eAAe,aAAa;GAChC,SAAS,QAAQ,cAAc;GAC/B;GACA;EACF,CAAC;EAED,IAAI,OAAO,SACT,OAAO;EAST,IAAI,MAN4B,aAAa;GAC3C;GACA,OAAO,EAAE,MAAM,aAAa;GAC5B;EACF,CAAC,MAEyB,WACxB,OAAO;EAGT,MAAM,eAAe,kBAAkB;GACrC,SAAS,QAAQ,cAAc;GAC/B;GACA;EACF,CAAC;EAED,IAAI,OAAO,SACT,OAAO;EAGT,MAAM,SAAS,MAAM,cAAc;GAAE;GAAS;GAAK;EAAO,CAAC;EAE3D,IAAI,WAAW,WACb,OAAO;EAGT,MAAM,SAAS,iBAAiB;GAAE;GAAM;GAAS;GAAQ;EAAO,CAAC;EAEjE,IAAI,WAAW,WACb,OAAO;EAGT,MAAM,sBAAsB,eAAe;GACzC,SAAS,QAAQ,cAAc;GAC/B;GACA;GACA;EACF,CAAC;EAED,MAAM,kBAAkB,MAAM,aAAa;GACzC;GACA,OAAO,EAAE,MAAM,WAAW;GAC1B;EACF,CAAC;EAED,IAAI,oBAAoB,WACtB,OAAO;EAMT,IACE,WAAW,eACX,CAAC,iBAAiB,qBAClB,CAAC,iBAAiB,mBAElB,OAAO;EAGT,aAAa;CACf;AACF;AAEA,eAAe,aAAa,EAC1B,MACA,OACA,UAI6D;CAC7D,IAAI,OAAO,SACT,OAAO;CAGT,MAAM,QAAQ,oBAAoB,MAAM;CACxC,IAAI;EACF,OAAO,MAAM,QAAQ,KAAK,CAAC,QAAQ,QAAQ,KAAK,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC;CACzE,SAAS,OAAO;EACd,IAAI,OAAO,SACT,OAAO;EAGT,MAAM;CACR,UAAU;EACR,MAAM,QAAQ;CAChB;AACF;AAEA,SAAS,oBAAoB,QAG3B;CACA,IAAI,gBAA4B,KAAA;CAEhC,MAAM,UAAU,IAAI,SAAoB,YAAY;EAClD,MAAM,gBAAgB,QAAQ,SAAS;EACvC,gBAAgB,OAAO,oBAAoB,SAAS,OAAO;EAC3D,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAC1D,CAAC;CAED,OAAO;EAAE;EAAS;CAAQ;AAC5B;AAEA,eAAe,sBACb,eACA,SACe;CACf,MAAM,YAAY,eAAe;CACjC,IAAI,CAAC,WACH;CAGF,MAAM,QAAQ,WAAW,CAAC,QAAQ,QAAQ,EAAE,WAAW,UAAU,OAAO,CAAC,CAAC,CAAC;AAC7E;AAEA,eAAe,cAAc,EAC3B,SACA,KACA,UAGiC;CACjC,IAAI;EACF,OAAO,MAAM,IAAI;GAAE,SAAS,QAAQ,cAAc;GAAG;EAAO,CAAC;CAC/D,SAAS,OAAO;EACd,IAAI,OAAO,SACT,OAAO;EAGT,MAAM;CACR;AACF;AAEA,SAAS,iBAAiB,EACxB,MACA,SACA,QACA,UAImB;CACnB,IAAI,OAAO,SACT,OAAO;CAGT,IAAI,iBAAiB;CAErB,KAAK,MAAM,WAAW,QAAQ;EAC5B,IAAI,OAAO,SACT,OAAO;EAGT,QAAQ,mBAAmB,OAAO;EAClC,MAAM,SAAS,0BAA0B,OAAO;EAEhD,KAAK,MAAM,SAAS,QAClB,KAAK,KAAK;EAGZ,IAAI,OAAO,MAAM,UAAU,MAAM,SAAS,WAAW,GACnD,iBAAiB;CAErB;CAEA,OAAO,iBAAiB,aAAa;AACvC"}
1
+ {"version":3,"file":"agent-loop.js","names":[],"sources":["../src/agent-loop.ts"],"sourcesContent":["import type { ModelMessage } from \"ai\";\nimport type { AgentHooks, AgentStepResult } from \"./hooks\";\nimport type { Llm, LlmOutput } from \"./llm\";\nimport type { AgentEvent, AgentEventListener } from \"./session/events\";\nimport { modelMessageToAgentEvents } from \"./session/mapping\";\n\ninterface ModelHistory {\n appendModelMessage(message: ModelMessage): void;\n modelSnapshot(): ModelMessage[];\n}\n\ninterface RunAgentLoopOptions {\n emit: AgentLoopEventListener;\n history: ModelHistory;\n hooks?: AgentHooks;\n llm: Llm;\n signal?: AbortSignal;\n}\n\nexport type AgentLoopResult = \"completed\" | \"aborted\";\ntype AgentLoopBoundaryEvent = Extract<\n AgentEvent,\n { type: \"step-end\" } | { type: \"step-start\" }\n>;\ninterface AgentLoopBoundaryDecision {\n readonly runtimeInputAdded?: boolean;\n}\ntype AgentLoopEventListener = (\n event: AgentEvent\n) =>\n | AgentLoopBoundaryDecision\n | Promise<AgentLoopBoundaryDecision | undefined>\n | undefined;\ntype StepOutputResult = AgentStepResult | \"aborted\";\n\nexport async function runAgentLoop({\n emit,\n history,\n hooks,\n llm,\n signal = new AbortController().signal,\n}: RunAgentLoopOptions): Promise<AgentLoopResult> {\n let stepIndex = 0;\n\n while (true) {\n if (signal.aborted) {\n return \"aborted\";\n }\n\n await hooks?.beforeStep?.({\n history: history.modelSnapshot(),\n signal,\n stepIndex,\n });\n\n if (signal.aborted) {\n return \"aborted\";\n }\n\n const stepStartDecision = await emitBoundary({\n emit,\n event: { type: \"step-start\" },\n signal,\n });\n\n if (stepStartDecision === \"aborted\") {\n return \"aborted\";\n }\n\n const output = await readLlmOutput({ history, llm, signal });\n\n if (output === \"aborted\") {\n return \"aborted\";\n }\n\n const result = appendStepOutput({ emit, history, output, signal });\n\n if (result === \"aborted\") {\n return \"aborted\";\n }\n\n await runAfterStepHook(hooks, {\n history: history.modelSnapshot(),\n result,\n signal,\n stepIndex,\n });\n\n const stepEndDecision = await emitBoundary({\n emit,\n event: { type: \"step-end\" },\n signal,\n });\n\n if (stepEndDecision === \"aborted\") {\n return \"aborted\";\n }\n\n // Runtime input after step-end intentionally forces another inference step,\n // even after final-looking assistant text. Unconditional insertion on every\n // step-end can create an unbounded loop.\n if (result === \"completed\" && !stepEndDecision?.runtimeInputAdded) {\n return \"completed\";\n }\n\n stepIndex += 1;\n }\n}\n\nasync function emitBoundary({\n emit,\n event,\n signal,\n}: Pick<RunAgentLoopOptions, \"emit\"> & {\n event: AgentLoopBoundaryEvent;\n signal: AbortSignal;\n}): Promise<AgentLoopBoundaryDecision | \"aborted\" | undefined> {\n if (signal.aborted) {\n return \"aborted\";\n }\n\n const abort = createAbortBoundary(signal);\n try {\n return await Promise.race([Promise.resolve(emit(event)), abort.promise]);\n } catch (error) {\n if (signal.aborted) {\n return \"aborted\";\n }\n\n throw error;\n } finally {\n abort.dispose();\n }\n}\n\nfunction createAbortBoundary(signal: AbortSignal): {\n dispose: () => void;\n promise: Promise<\"aborted\">;\n} {\n let dispose: () => void = () => undefined;\n\n const promise = new Promise<\"aborted\">((resolve) => {\n const onAbort = () => resolve(\"aborted\");\n dispose = () => signal.removeEventListener(\"abort\", onAbort);\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n\n return { dispose, promise };\n}\n\nasync function runAfterStepHook(\n hooks: AgentHooks | undefined,\n context: Parameters<NonNullable<AgentHooks[\"afterStep\"]>>[0]\n): Promise<void> {\n const hook = hooks?.afterStep;\n if (!hook) {\n return;\n }\n\n await Promise.allSettled([Promise.resolve().then(() => hook(context))]);\n}\n\nasync function readLlmOutput({\n history,\n llm,\n signal,\n}: Pick<RunAgentLoopOptions, \"history\" | \"llm\"> & {\n signal: AbortSignal;\n}): Promise<LlmOutput | \"aborted\"> {\n try {\n return await llm({ history: history.modelSnapshot(), signal });\n } catch (error) {\n if (signal.aborted) {\n return \"aborted\";\n }\n\n throw error;\n }\n}\n\nfunction appendStepOutput({\n emit,\n history,\n output,\n signal,\n}: { emit: AgentEventListener; history: ModelHistory } & {\n output: LlmOutput;\n signal: AbortSignal;\n}): StepOutputResult {\n if (signal.aborted) {\n return \"aborted\";\n }\n\n let shouldContinue = false;\n\n for (const message of output) {\n if (signal.aborted) {\n return \"aborted\";\n }\n\n history.appendModelMessage(message);\n const events = modelMessageToAgentEvents(message);\n\n for (const event of events) {\n emit(event);\n }\n\n if (events.some((event) => event.type === \"tool-call\")) {\n shouldContinue = true;\n }\n }\n\n return shouldContinue ? \"continue\" : \"completed\";\n}\n"],"mappings":";;AAmCA,eAAsB,aAAa,EACjC,MACA,SACA,OACA,KACA,SAAS,IAAI,gBAAgB,EAAE,UACiB;CAChD,IAAI,YAAY;CAEhB,OAAO,MAAM;EACX,IAAI,OAAO,SACT,OAAO;EAGT,MAAM,OAAO,aAAa;GACxB,SAAS,QAAQ,cAAc;GAC/B;GACA;EACF,CAAC;EAED,IAAI,OAAO,SACT,OAAO;EAST,IAAI,MAN4B,aAAa;GAC3C;GACA,OAAO,EAAE,MAAM,aAAa;GAC5B;EACF,CAAC,MAEyB,WACxB,OAAO;EAGT,MAAM,SAAS,MAAM,cAAc;GAAE;GAAS;GAAK;EAAO,CAAC;EAE3D,IAAI,WAAW,WACb,OAAO;EAGT,MAAM,SAAS,iBAAiB;GAAE;GAAM;GAAS;GAAQ;EAAO,CAAC;EAEjE,IAAI,WAAW,WACb,OAAO;EAGT,MAAM,iBAAiB,OAAO;GAC5B,SAAS,QAAQ,cAAc;GAC/B;GACA;GACA;EACF,CAAC;EAED,MAAM,kBAAkB,MAAM,aAAa;GACzC;GACA,OAAO,EAAE,MAAM,WAAW;GAC1B;EACF,CAAC;EAED,IAAI,oBAAoB,WACtB,OAAO;EAMT,IAAI,WAAW,eAAe,CAAC,iBAAiB,mBAC9C,OAAO;EAGT,aAAa;CACf;AACF;AAEA,eAAe,aAAa,EAC1B,MACA,OACA,UAI6D;CAC7D,IAAI,OAAO,SACT,OAAO;CAGT,MAAM,QAAQ,oBAAoB,MAAM;CACxC,IAAI;EACF,OAAO,MAAM,QAAQ,KAAK,CAAC,QAAQ,QAAQ,KAAK,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC;CACzE,SAAS,OAAO;EACd,IAAI,OAAO,SACT,OAAO;EAGT,MAAM;CACR,UAAU;EACR,MAAM,QAAQ;CAChB;AACF;AAEA,SAAS,oBAAoB,QAG3B;CACA,IAAI,gBAA4B,KAAA;CAEhC,MAAM,UAAU,IAAI,SAAoB,YAAY;EAClD,MAAM,gBAAgB,QAAQ,SAAS;EACvC,gBAAgB,OAAO,oBAAoB,SAAS,OAAO;EAC3D,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;CAC1D,CAAC;CAED,OAAO;EAAE;EAAS;CAAQ;AAC5B;AAEA,eAAe,iBACb,OACA,SACe;CACf,MAAM,OAAO,OAAO;CACpB,IAAI,CAAC,MACH;CAGF,MAAM,QAAQ,WAAW,CAAC,QAAQ,QAAQ,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC;AACxE;AAEA,eAAe,cAAc,EAC3B,SACA,KACA,UAGiC;CACjC,IAAI;EACF,OAAO,MAAM,IAAI;GAAE,SAAS,QAAQ,cAAc;GAAG;EAAO,CAAC;CAC/D,SAAS,OAAO;EACd,IAAI,OAAO,SACT,OAAO;EAGT,MAAM;CACR;AACF;AAEA,SAAS,iBAAiB,EACxB,MACA,SACA,QACA,UAImB;CACnB,IAAI,OAAO,SACT,OAAO;CAGT,IAAI,iBAAiB;CAErB,KAAK,MAAM,WAAW,QAAQ;EAC5B,IAAI,OAAO,SACT,OAAO;EAGT,QAAQ,mBAAmB,OAAO;EAClC,MAAM,SAAS,0BAA0B,OAAO;EAEhD,KAAK,MAAM,SAAS,QAClB,KAAK,KAAK;EAGZ,IAAI,OAAO,MAAM,UAAU,MAAM,SAAS,WAAW,GACnD,iBAAiB;CAErB;CAEA,OAAO,iBAAiB,aAAa;AACvC"}
@@ -0,0 +1,17 @@
1
+ //#region src/agent-namespace.ts
2
+ function randomAgentNamespace() {
3
+ return agentNamespace(crypto.randomUUID());
4
+ }
5
+ function agentNamespace(namespace) {
6
+ return `agent:${namespacePart(namespace)}`;
7
+ }
8
+ function namespacePart(value) {
9
+ return encodeURIComponent(value);
10
+ }
11
+ function parentSessionNamespace({ generation, sessionKey, sessionNamespace }) {
12
+ return `${sessionNamespace}:session:${namespacePart(sessionKey)}:generation:${generation}`;
13
+ }
14
+ //#endregion
15
+ export { agentNamespace, parentSessionNamespace, randomAgentNamespace };
16
+
17
+ //# sourceMappingURL=agent-namespace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-namespace.js","names":[],"sources":["../src/agent-namespace.ts"],"sourcesContent":["export function randomAgentNamespace(): string {\n return agentNamespace(crypto.randomUUID());\n}\n\nexport function agentNamespace(namespace: string): string {\n return `agent:${namespacePart(namespace)}`;\n}\n\nexport function namespacePart(value: string): string {\n return encodeURIComponent(value);\n}\n\nexport function parentSessionNamespace({\n generation,\n sessionKey,\n sessionNamespace,\n}: {\n readonly generation: number;\n readonly sessionKey: string;\n readonly sessionNamespace: string;\n}): string {\n return `${sessionNamespace}:session:${namespacePart(\n sessionKey\n )}:generation:${generation}`;\n}\n"],"mappings":";AAAA,SAAgB,uBAA+B;CAC7C,OAAO,eAAe,OAAO,WAAW,CAAC;AAC3C;AAEA,SAAgB,eAAe,WAA2B;CACxD,OAAO,SAAS,cAAc,SAAS;AACzC;AAEA,SAAgB,cAAc,OAAuB;CACnD,OAAO,mBAAmB,KAAK;AACjC;AAEA,SAAgB,uBAAuB,EACrC,YACA,YACA,oBAKS;CACT,OAAO,GAAG,iBAAiB,WAAW,cACpC,UACF,EAAE,cAAc;AAClB"}
@@ -0,0 +1,35 @@
1
+ //#region src/agent-validation.ts
2
+ const subagentNamePattern = /^[a-z][a-z0-9_-]{0,51}$/;
3
+ function assertSubagents(options, agentClass, hasCustomLlm) {
4
+ if (!("subagents" in options) || options.subagents === void 0) return;
5
+ if (hasCustomLlm) throw new TypeError("Agent: subagents require options.model.");
6
+ if (!Array.isArray(options.subagents)) throw new TypeError("Agent: subagents must be an array.");
7
+ assertSubagentTools(options.subagents, agentClass, options.tools ?? {});
8
+ }
9
+ function assertSubagentTools(subagents, agentClass, tools) {
10
+ const toolNames = new Set(Object.keys(tools));
11
+ const generatedToolNames = /* @__PURE__ */ new Set();
12
+ for (const [index, subagent] of subagents.entries()) {
13
+ const toolName = `delegate_to_${assertSubagentMetadata(subagent, index, agentClass).replaceAll("-", "_")}`;
14
+ if (toolNames.has(toolName)) throw new TypeError(`Agent: subagent tool ${toolName} collides with an existing tool.`);
15
+ if (generatedToolNames.has(toolName)) throw new TypeError(`Agent: duplicate subagent tool name ${toolName}.`);
16
+ generatedToolNames.add(toolName);
17
+ }
18
+ for (const reservedToolName of ["background_output", "background_cancel"]) if (toolNames.has(reservedToolName)) throw new TypeError(`Agent: ${reservedToolName} collides with a reserved subagent tool.`);
19
+ }
20
+ function assertSubagentMetadata(subagent, index, agentClass) {
21
+ if (!(subagent instanceof agentClass)) throw new TypeError(`Agent: subagents[${index}] must be an Agent.`);
22
+ if (!isValidSubagentName(subagent.name)) throw new TypeError(`Agent: subagents[${index}].name is required or too long.`);
23
+ if (!isNonEmptyText(subagent.description)) throw new TypeError(`Agent: subagents[${index}].description is required.`);
24
+ return subagent.name;
25
+ }
26
+ function isNonEmptyText(value) {
27
+ return typeof value === "string" && value.trim().length > 0;
28
+ }
29
+ function isValidSubagentName(value) {
30
+ return typeof value === "string" && subagentNamePattern.test(value);
31
+ }
32
+ //#endregion
33
+ export { assertSubagents };
34
+
35
+ //# sourceMappingURL=agent-validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-validation.js","names":[],"sources":["../src/agent-validation.ts"],"sourcesContent":["import type { ToolSet } from \"ai\";\nimport type { Agent, AgentOptions } from \"./agent\";\n\nconst subagentNamePattern = /^[a-z][a-z0-9_-]{0,51}$/;\n\nexport function assertSubagents(\n options: AgentOptions,\n agentClass: new (options: AgentOptions) => Agent,\n hasCustomLlm: boolean\n): void {\n if (!(\"subagents\" in options) || options.subagents === undefined) {\n return;\n }\n\n if (hasCustomLlm) {\n throw new TypeError(\"Agent: subagents require options.model.\");\n }\n\n if (!Array.isArray(options.subagents)) {\n throw new TypeError(\"Agent: subagents must be an array.\");\n }\n\n assertSubagentTools(options.subagents, agentClass, options.tools ?? {});\n}\n\nfunction assertSubagentTools(\n subagents: readonly Agent[],\n agentClass: new (options: AgentOptions) => Agent,\n tools: ToolSet\n): void {\n const toolNames = new Set(Object.keys(tools));\n const generatedToolNames = new Set<string>();\n for (const [index, subagent] of subagents.entries()) {\n const name = assertSubagentMetadata(subagent, index, agentClass);\n const toolName = `delegate_to_${name.replaceAll(\"-\", \"_\")}`;\n if (toolNames.has(toolName)) {\n throw new TypeError(\n `Agent: subagent tool ${toolName} collides with an existing tool.`\n );\n }\n\n if (generatedToolNames.has(toolName)) {\n throw new TypeError(`Agent: duplicate subagent tool name ${toolName}.`);\n }\n\n generatedToolNames.add(toolName);\n }\n\n for (const reservedToolName of [\"background_output\", \"background_cancel\"]) {\n if (toolNames.has(reservedToolName)) {\n throw new TypeError(\n `Agent: ${reservedToolName} collides with a reserved subagent tool.`\n );\n }\n }\n}\n\nfunction assertSubagentMetadata(\n subagent: Agent,\n index: number,\n agentClass: new (options: AgentOptions) => Agent\n): string {\n if (!(subagent instanceof agentClass)) {\n throw new TypeError(`Agent: subagents[${index}] must be an Agent.`);\n }\n\n if (!isValidSubagentName(subagent.name)) {\n throw new TypeError(\n `Agent: subagents[${index}].name is required or too long.`\n );\n }\n\n if (!isNonEmptyText(subagent.description)) {\n throw new TypeError(`Agent: subagents[${index}].description is required.`);\n }\n\n return subagent.name;\n}\n\nfunction isNonEmptyText(value: string | undefined): value is string {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n\nfunction isValidSubagentName(value: string | undefined): value is string {\n return typeof value === \"string\" && subagentNamePattern.test(value);\n}\n"],"mappings":";AAGA,MAAM,sBAAsB;AAE5B,SAAgB,gBACd,SACA,YACA,cACM;CACN,IAAI,EAAE,eAAe,YAAY,QAAQ,cAAc,KAAA,GACrD;CAGF,IAAI,cACF,MAAM,IAAI,UAAU,yCAAyC;CAG/D,IAAI,CAAC,MAAM,QAAQ,QAAQ,SAAS,GAClC,MAAM,IAAI,UAAU,oCAAoC;CAG1D,oBAAoB,QAAQ,WAAW,YAAY,QAAQ,SAAS,CAAC,CAAC;AACxE;AAEA,SAAS,oBACP,WACA,YACA,OACM;CACN,MAAM,YAAY,IAAI,IAAI,OAAO,KAAK,KAAK,CAAC;CAC5C,MAAM,qCAAqB,IAAI,IAAY;CAC3C,KAAK,MAAM,CAAC,OAAO,aAAa,UAAU,QAAQ,GAAG;EAEnD,MAAM,WAAW,eADJ,uBAAuB,UAAU,OAAO,UAClB,EAAE,WAAW,KAAK,GAAG;EACxD,IAAI,UAAU,IAAI,QAAQ,GACxB,MAAM,IAAI,UACR,wBAAwB,SAAS,iCACnC;EAGF,IAAI,mBAAmB,IAAI,QAAQ,GACjC,MAAM,IAAI,UAAU,uCAAuC,SAAS,EAAE;EAGxE,mBAAmB,IAAI,QAAQ;CACjC;CAEA,KAAK,MAAM,oBAAoB,CAAC,qBAAqB,mBAAmB,GACtE,IAAI,UAAU,IAAI,gBAAgB,GAChC,MAAM,IAAI,UACR,UAAU,iBAAiB,yCAC7B;AAGN;AAEA,SAAS,uBACP,UACA,OACA,YACQ;CACR,IAAI,EAAE,oBAAoB,aACxB,MAAM,IAAI,UAAU,oBAAoB,MAAM,oBAAoB;CAGpE,IAAI,CAAC,oBAAoB,SAAS,IAAI,GACpC,MAAM,IAAI,UACR,oBAAoB,MAAM,gCAC5B;CAGF,IAAI,CAAC,eAAe,SAAS,WAAW,GACtC,MAAM,IAAI,UAAU,oBAAoB,MAAM,2BAA2B;CAG3E,OAAO,SAAS;AAClB;AAEA,SAAS,eAAe,OAA4C;CAClE,OAAO,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS;AAC5D;AAEA,SAAS,oBAAoB,OAA4C;CACvE,OAAO,OAAO,UAAU,YAAY,oBAAoB,KAAK,KAAK;AACpE"}
package/dist/agent.d.ts CHANGED
@@ -1,44 +1,55 @@
1
1
  import { AgentToolChoice, Llm } from "./llm.js";
2
2
  import { AgentInput } from "./session/input.js";
3
+ import { AgentHooks } from "./hooks.js";
3
4
  import { AgentRun } from "./session/run.js";
4
- import { AgentPlugin } from "./plugins/types.js";
5
- import { AgentPluginErrorHandler } from "./session/lifecycle.js";
5
+ import { SessionStore } from "./session/store/types.js";
6
6
  import { LanguageModel, ToolSet } from "ai";
7
7
 
8
8
  //#region src/agent.d.ts
9
9
  interface AgentLanguageModelOptions {
10
+ description?: string;
11
+ hooks?: AgentHooks;
10
12
  instructions?: string;
11
13
  llm?: never;
12
14
  model: LanguageModel;
13
- onPluginError?: AgentPluginErrorHandler;
14
- plugins?: readonly AgentPlugin[];
15
+ name?: string;
16
+ sessions?: AgentSessionOptions;
17
+ subagents?: readonly Agent[];
15
18
  toolChoice?: AgentToolChoice;
16
19
  tools?: ToolSet;
17
20
  }
18
21
  interface AgentLlmOptions {
22
+ description?: string;
23
+ hooks?: AgentHooks;
19
24
  instructions?: never;
20
25
  llm: Llm;
21
26
  model?: never;
22
- onPluginError?: AgentPluginErrorHandler;
23
- plugins?: readonly AgentPlugin[];
27
+ name?: string;
28
+ sessions?: AgentSessionOptions;
29
+ subagents?: never;
24
30
  toolChoice?: never;
25
31
  tools?: never;
26
32
  }
33
+ interface AgentSessionOptions {
34
+ namespace?: string;
35
+ store?: SessionStore;
36
+ }
27
37
  interface SessionHandle {
38
+ delete(): Promise<void>;
28
39
  interrupt(): void;
29
40
  kill(): void;
30
- overlay(input: AgentInput): Promise<AgentRun>;
31
41
  send(input: AgentInput): Promise<AgentRun>;
32
42
  steer(input: AgentInput): Promise<AgentRun>;
33
43
  }
34
44
  type AgentOptions = AgentLanguageModelOptions | AgentLlmOptions;
35
45
  declare class Agent {
36
46
  #private;
37
- private constructor();
38
- static create(options: AgentOptions): Promise<Agent>;
47
+ readonly description?: string;
48
+ readonly name?: string;
49
+ constructor(options: AgentOptions);
39
50
  send(input: AgentInput): Promise<AgentRun>;
40
51
  session(key: string): SessionHandle;
41
52
  }
42
53
  //#endregion
43
- export { Agent, AgentOptions, SessionHandle };
54
+ export { Agent, AgentOptions, AgentSessionOptions, SessionHandle };
44
55
  //# sourceMappingURL=agent.d.ts.map