@minpeter/pss-runtime 0.0.11 → 0.1.0-next.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 (60) hide show
  1. package/README.md +191 -20
  2. package/dist/agent-loop.d.ts +1 -0
  3. package/dist/agent-loop.js +14 -8
  4. package/dist/agent-loop.js.map +1 -1
  5. package/dist/agent.d.ts +8 -10
  6. package/dist/agent.js +34 -13
  7. package/dist/agent.js.map +1 -1
  8. package/dist/index.d.ts +8 -4
  9. package/dist/index.js +6 -1
  10. package/dist/llm.js +7 -1
  11. package/dist/llm.js.map +1 -1
  12. package/dist/plugins/compaction.d.ts +15 -0
  13. package/dist/plugins/compaction.js +98 -0
  14. package/dist/plugins/compaction.js.map +1 -0
  15. package/dist/plugins/index.d.ts +5 -0
  16. package/dist/plugins/index.js +5 -0
  17. package/dist/plugins/memory.d.ts +11 -0
  18. package/dist/plugins/memory.js +146 -0
  19. package/dist/plugins/memory.js.map +1 -0
  20. package/dist/plugins/runner.d.ts +1 -0
  21. package/dist/plugins/runner.js +83 -0
  22. package/dist/plugins/runner.js.map +1 -0
  23. package/dist/plugins/scope.d.ts +1 -0
  24. package/dist/plugins/scope.js +13 -0
  25. package/dist/plugins/scope.js.map +1 -0
  26. package/dist/plugins/sessions.d.ts +12 -0
  27. package/dist/plugins/sessions.js +34 -0
  28. package/dist/plugins/sessions.js.map +1 -0
  29. package/dist/plugins/tool-hook-handlers.js +77 -0
  30. package/dist/plugins/tool-hook-handlers.js.map +1 -0
  31. package/dist/plugins/tool-hook-results.js +64 -0
  32. package/dist/plugins/tool-hook-results.js.map +1 -0
  33. package/dist/plugins/tool-hooks.js +111 -0
  34. package/dist/plugins/tool-hooks.js.map +1 -0
  35. package/dist/plugins/types.d.ts +105 -0
  36. package/dist/plugins/types.js +20 -0
  37. package/dist/plugins/types.js.map +1 -0
  38. package/dist/session/events.d.ts +19 -2
  39. package/dist/session/input.d.ts +4 -0
  40. package/dist/session/lifecycle.d.ts +12 -0
  41. package/dist/session/lifecycle.js +126 -0
  42. package/dist/session/lifecycle.js.map +1 -0
  43. package/dist/session/mapping.js +2 -1
  44. package/dist/session/mapping.js.map +1 -1
  45. package/dist/session/overlay-anchor.js +151 -0
  46. package/dist/session/overlay-anchor.js.map +1 -0
  47. package/dist/session/overlay.js +141 -0
  48. package/dist/session/overlay.js.map +1 -0
  49. package/dist/session/run.js +0 -1
  50. package/dist/session/run.js.map +1 -1
  51. package/dist/session/runtime-input.d.ts +1 -0
  52. package/dist/session/runtime-input.js +89 -0
  53. package/dist/session/runtime-input.js.map +1 -0
  54. package/dist/session/session.js +166 -129
  55. package/dist/session/session.js.map +1 -1
  56. package/dist/session/snapshot.d.ts +1 -0
  57. package/dist/session/snapshot.js +31 -5
  58. package/dist/session/snapshot.js.map +1 -1
  59. package/package.json +6 -1
  60. package/dist/hooks.d.ts +0 -32
package/README.md CHANGED
@@ -28,7 +28,23 @@ 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.
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
+ ```
32
48
 
33
49
  Per-key conversations use `session(key)`:
34
50
 
@@ -122,46 +138,201 @@ for await (const event of run.events()) {
122
138
  pending steering path or, when idle, when a new run is scheduled. It does not wait
123
139
  for a later model snapshot.
124
140
 
125
- ## Session storage and portability
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
126
178
 
127
179
  The runtime owns full session state encoding and history compaction semantics.
128
- Adapters own persistence only through `SessionStore`:
180
+ Persistence, memory, and compaction are configured through in-process plugins:
129
181
 
130
- Stored session state is an opaque, versioned runtime snapshot for continuation.
131
- Do not inspect it as a replay log; exact replay should be modeled separately as
132
- an `AgentEvent` log if that capability is added later.
182
+ ```ts
183
+ import { Agent } from "@minpeter/pss-runtime";
184
+ import { compaction, memory, sessions } from "@minpeter/pss-runtime/plugins";
185
+
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.
133
193
 
134
- Custom stores own version generation. `load(key)` returns the opaque `state` with
135
- the store-minted `version`; `commit(key, { state }, { expectedVersion })` receives
136
- state only and should reject stale versions by returning `{ ok: false, reason:
137
- "conflict" }`. On success, the store persists `{ state, version }` and returns the
138
- new version to the runtime.
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.
202
+
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`.
139
207
 
140
208
  ```ts
141
- import type { SessionStore } from "@minpeter/pss-runtime";
142
- import { MemorySessionStore } from "@minpeter/pss-runtime/session-store/memory";
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
+ });
143
222
 
144
223
  const agent = await Agent.create({
145
224
  model,
146
- sessions: {
147
- store: new MemorySessionStore(), // default when omitted
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
+ });
148
274
  },
149
275
  });
150
276
  ```
151
277
 
152
- For durable sessions, use the exported file POC:
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.
153
283
 
154
284
  ```ts
155
- import { FileSessionStore } from "@minpeter/pss-runtime/session-store/file";
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:
303
+
304
+ ```ts
305
+ import type { SessionStore } from "@minpeter/pss-runtime";
306
+ import { sessions } from "@minpeter/pss-runtime/plugins";
307
+
308
+ declare const store: SessionStore;
156
309
 
157
310
  const agent = await Agent.create({
158
311
  model,
159
- sessions: {
160
- store: new FileSessionStore(".pss/sessions"),
161
- },
312
+ plugins: [sessions.custom(store)],
162
313
  });
163
314
  ```
164
315
 
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
+
165
336
  ## Future adapter boundary: Cloudflare multi-user DX
166
337
 
167
338
  Cloudflare Durable Objects are a future adapter target, not a runtime dependency.
@@ -0,0 +1 @@
1
+ import { ModelMessage } from "ai";
@@ -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, hooks, llm, signal = new AbortController().signal }) {
3
+ async function runAgentLoop({ emit, history, llm, signal = new AbortController().signal, stepLifecycle }) {
4
4
  let stepIndex = 0;
5
5
  while (true) {
6
6
  if (signal.aborted) return "aborted";
7
- await hooks?.beforeStep?.({
7
+ await stepLifecycle?.beforeStep?.({
8
8
  history: history.modelSnapshot(),
9
9
  signal,
10
10
  stepIndex
@@ -15,6 +15,12 @@ async function runAgentLoop({ emit, history, hooks, llm, signal = new AbortContr
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";
18
24
  const output = await readLlmOutput({
19
25
  history,
20
26
  llm,
@@ -28,7 +34,7 @@ async function runAgentLoop({ emit, history, hooks, llm, signal = new AbortContr
28
34
  signal
29
35
  });
30
36
  if (result === "aborted") return "aborted";
31
- await runAfterStepHook(hooks, {
37
+ await runAfterStepLifecycle(stepLifecycle, {
32
38
  history: history.modelSnapshot(),
33
39
  result,
34
40
  signal,
@@ -40,7 +46,7 @@ async function runAgentLoop({ emit, history, hooks, llm, signal = new AbortContr
40
46
  signal
41
47
  });
42
48
  if (stepEndDecision === "aborted") return "aborted";
43
- if (result === "completed" && !stepEndDecision?.runtimeInputAdded) return "completed";
49
+ if (result === "completed" && !stepEndDecision?.runtimeInputAdded && !stepEndDecision?.overlayInputAdded) return "completed";
44
50
  stepIndex += 1;
45
51
  }
46
52
  }
@@ -68,10 +74,10 @@ function createAbortBoundary(signal) {
68
74
  promise
69
75
  };
70
76
  }
71
- async function runAfterStepHook(hooks, context) {
72
- const hook = hooks?.afterStep;
73
- if (!hook) return;
74
- await Promise.allSettled([Promise.resolve().then(() => hook(context))]);
77
+ async function runAfterStepLifecycle(stepLifecycle, context) {
78
+ const afterStep = stepLifecycle?.afterStep;
79
+ if (!afterStep) return;
80
+ await Promise.allSettled([Promise.resolve().then(() => afterStep(context))]);
75
81
  }
76
82
  async function readLlmOutput({ history, llm, signal }) {
77
83
  try {
@@ -1 +1 @@
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"}
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"}
package/dist/agent.d.ts CHANGED
@@ -1,35 +1,33 @@
1
1
  import { AgentToolChoice, Llm } from "./llm.js";
2
2
  import { AgentInput } from "./session/input.js";
3
- import { AgentHooks } from "./hooks.js";
4
3
  import { AgentRun } from "./session/run.js";
5
- import { SessionStore } from "./session/store/types.js";
4
+ import { AgentPlugin } from "./plugins/types.js";
5
+ import { AgentPluginErrorHandler } from "./session/lifecycle.js";
6
6
  import { LanguageModel, ToolSet } from "ai";
7
7
 
8
8
  //#region src/agent.d.ts
9
9
  interface AgentLanguageModelOptions {
10
- hooks?: AgentHooks;
11
10
  instructions?: string;
12
11
  llm?: never;
13
12
  model: LanguageModel;
14
- sessions?: AgentSessionOptions;
13
+ onPluginError?: AgentPluginErrorHandler;
14
+ plugins?: readonly AgentPlugin[];
15
15
  toolChoice?: AgentToolChoice;
16
16
  tools?: ToolSet;
17
17
  }
18
18
  interface AgentLlmOptions {
19
- hooks?: AgentHooks;
20
19
  instructions?: never;
21
20
  llm: Llm;
22
21
  model?: never;
23
- sessions?: AgentSessionOptions;
22
+ onPluginError?: AgentPluginErrorHandler;
23
+ plugins?: readonly AgentPlugin[];
24
24
  toolChoice?: never;
25
25
  tools?: never;
26
26
  }
27
- interface AgentSessionOptions {
28
- store?: SessionStore;
29
- }
30
27
  interface SessionHandle {
31
28
  interrupt(): void;
32
29
  kill(): void;
30
+ overlay(input: AgentInput): Promise<AgentRun>;
33
31
  send(input: AgentInput): Promise<AgentRun>;
34
32
  steer(input: AgentInput): Promise<AgentRun>;
35
33
  }
@@ -42,5 +40,5 @@ declare class Agent {
42
40
  session(key: string): SessionHandle;
43
41
  }
44
42
  //#endregion
45
- export { Agent, AgentOptions, AgentSessionOptions, SessionHandle };
43
+ export { Agent, AgentOptions, SessionHandle };
46
44
  //# sourceMappingURL=agent.d.ts.map
package/dist/agent.js CHANGED
@@ -1,25 +1,42 @@
1
1
  import { createLlm } from "./llm.js";
2
+ import { resolveAgentPlugins } from "./plugins/runner.js";
2
3
  import { AgentSession } from "./session/session.js";
3
4
  import { MemorySessionStore } from "./session/store/memory.js";
4
5
  //#region src/agent.ts
5
6
  var Agent = class Agent {
6
- #hooks;
7
+ #internalLlm;
7
8
  #llm;
9
+ #onPluginError;
10
+ #plugins;
8
11
  #sessions = /* @__PURE__ */ new Map();
9
12
  #store;
10
- constructor(options) {
13
+ constructor(options, resolvedPlugins) {
11
14
  assertAgentOptions(options);
12
- this.#store = options.sessions?.store ?? new MemorySessionStore();
13
- this.#hooks = options.hooks;
14
- this.#llm = hasCustomLlm(options) ? options.llm : createLlm({
15
- instructions: options.instructions,
16
- model: options.model,
17
- toolChoice: options.toolChoice,
18
- tools: options.tools
19
- });
15
+ this.#plugins = resolvedPlugins;
16
+ this.#onPluginError = options.onPluginError;
17
+ this.#store = resolvedPlugins.sessionStore?.store ?? new MemorySessionStore();
18
+ if (hasCustomLlm(options)) {
19
+ this.#internalLlm = options.llm;
20
+ this.#llm = options.llm;
21
+ } else {
22
+ this.#internalLlm = createLlm({
23
+ instructions: options.instructions,
24
+ model: options.model
25
+ });
26
+ this.#llm = createLlm({
27
+ instructions: options.instructions,
28
+ model: options.model,
29
+ toolChoice: options.toolChoice,
30
+ tools: resolvedPlugins.tools
31
+ });
32
+ }
20
33
  }
21
- static create(options) {
22
- return Promise.resolve().then(() => new Agent(options));
34
+ static async create(options) {
35
+ assertAgentOptions(options);
36
+ return new Agent(options, await resolveAgentPlugins({
37
+ callerTools: hasCustomLlm(options) ? void 0 : options.tools,
38
+ plugins: options.plugins
39
+ }));
23
40
  }
24
41
  send(input) {
25
42
  return this.session("default").send(input);
@@ -30,13 +47,14 @@ var Agent = class Agent {
30
47
  const session = new AgentSession(this.#llm, {
31
48
  key,
32
49
  store: this.#store
33
- }, this.#hooks);
50
+ }, this.#plugins, this.#internalLlm, this.#onPluginError);
34
51
  const handle = {
35
52
  interrupt: () => session.interrupt(),
36
53
  kill: () => {
37
54
  session.kill();
38
55
  this.#sessions.delete(key);
39
56
  },
57
+ overlay: (input) => session.overlay(input),
40
58
  send: (input) => session.send(input),
41
59
  steer: (input) => session.steer(input)
42
60
  };
@@ -46,10 +64,13 @@ var Agent = class Agent {
46
64
  };
47
65
  function assertAgentOptions(options) {
48
66
  if (options === null || typeof options !== "object") throw new TypeError("Agent options are required. Provide either { model } or { llm }.");
67
+ if ("sessions" in options) throw new TypeError("Agent.create: options.sessions was removed. Use plugins: [sessions.custom(store)].");
68
+ if ("hooks" in options) throw new TypeError("Agent.create: options.hooks was removed. Use run.events() with session.steer() for app control or plugin lifecycle handlers for middleware.");
49
69
  const hasLlm = hasCustomLlm(options);
50
70
  const hasModel = "model" in options && options.model !== void 0 && options.model !== null;
51
71
  if (hasLlm && hasModel) throw new TypeError("Agent.create: provide either options.llm or options.model, not both.");
52
72
  if ("llm" in options && options.llm !== void 0 && !hasLlm) throw new TypeError("Agent.create: invalid options.llm.");
73
+ if ("onPluginError" in options && options.onPluginError !== void 0 && typeof options.onPluginError !== "function") throw new TypeError("Agent.create: invalid options.onPluginError.");
53
74
  if (!(hasLlm || hasModel)) throw new TypeError("Agent.create: missing options.model.");
54
75
  }
55
76
  function hasCustomLlm(options) {
package/dist/agent.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"agent.js","names":["#hooks","#llm","#sessions","#store"],"sources":["../src/agent.ts"],"sourcesContent":["import type { LanguageModel, ToolSet } from \"ai\";\nimport type { AgentHooks } from \"./hooks\";\nimport { type AgentToolChoice, createLlm, type Llm } from \"./llm\";\nimport type { AgentRun } from \"./session/run\";\nimport { type AgentInput, AgentSession } from \"./session/session\";\nimport { MemorySessionStore } from \"./session/store/memory\";\nimport type { SessionStore } from \"./session/store/types\";\n\ninterface AgentLanguageModelOptions {\n hooks?: AgentHooks;\n instructions?: string;\n llm?: never;\n model: LanguageModel;\n sessions?: AgentSessionOptions;\n toolChoice?: AgentToolChoice;\n tools?: ToolSet;\n}\n\ninterface AgentLlmOptions {\n hooks?: AgentHooks;\n instructions?: never;\n llm: Llm;\n model?: never;\n sessions?: AgentSessionOptions;\n toolChoice?: never;\n tools?: never;\n}\n\nexport interface AgentSessionOptions {\n store?: SessionStore;\n}\n\nexport interface SessionHandle {\n interrupt(): void;\n kill(): void;\n send(input: AgentInput): Promise<AgentRun>;\n steer(input: AgentInput): Promise<AgentRun>;\n}\n\nexport type AgentOptions = AgentLanguageModelOptions | AgentLlmOptions;\n\nexport class Agent {\n readonly #hooks?: AgentHooks;\n readonly #llm: Llm;\n readonly #sessions = new Map<string, SessionHandle>();\n readonly #store: SessionStore;\n\n private constructor(options: AgentOptions) {\n assertAgentOptions(options);\n\n this.#store = options.sessions?.store ?? new MemorySessionStore();\n this.#hooks = options.hooks;\n this.#llm = hasCustomLlm(options)\n ? options.llm\n : createLlm({\n instructions: options.instructions,\n model: options.model,\n toolChoice: options.toolChoice,\n tools: options.tools,\n });\n }\n\n static create(options: AgentOptions): Promise<Agent> {\n return Promise.resolve().then(() => new Agent(options));\n }\n\n send(input: AgentInput): Promise<AgentRun> {\n return this.session(\"default\").send(input);\n }\n\n session(key: string): SessionHandle {\n const existing = this.#sessions.get(key);\n if (existing) {\n return existing;\n }\n\n const session = new AgentSession(\n this.#llm,\n { key, store: this.#store },\n this.#hooks\n );\n const handle: SessionHandle = {\n interrupt: () => session.interrupt(),\n kill: () => {\n session.kill();\n this.#sessions.delete(key);\n },\n send: (input) => session.send(input),\n steer: (input) => session.steer(input),\n };\n this.#sessions.set(key, handle);\n return handle;\n }\n}\n\nfunction assertAgentOptions(options: unknown): asserts options is AgentOptions {\n if (options === null || typeof options !== \"object\") {\n throw new TypeError(\n \"Agent options are required. Provide either { model } or { llm }.\"\n );\n }\n\n const hasLlm = hasCustomLlm(options);\n const hasModel =\n \"model\" in options && options.model !== undefined && options.model !== null;\n\n if (hasLlm && hasModel) {\n throw new TypeError(\n \"Agent.create: provide either options.llm or options.model, not both.\"\n );\n }\n\n if (\"llm\" in options && options.llm !== undefined && !hasLlm) {\n throw new TypeError(\"Agent.create: invalid options.llm.\");\n }\n\n if (!(hasLlm || hasModel)) {\n throw new TypeError(\"Agent.create: missing options.model.\");\n }\n}\n\nfunction hasCustomLlm(options: object): options is AgentLlmOptions {\n return \"llm\" in options && typeof options.llm === \"function\";\n}\n"],"mappings":";;;;AAyCA,IAAa,QAAb,MAAa,MAAM;CACjB;CACA;CACA,4BAAqB,IAAI,IAA2B;CACpD;CAEA,YAAoB,SAAuB;EACzC,mBAAmB,OAAO;EAE1B,KAAKG,SAAS,QAAQ,UAAU,SAAS,IAAI,mBAAmB;EAChE,KAAKH,SAAS,QAAQ;EACtB,KAAKC,OAAO,aAAa,OAAO,IAC5B,QAAQ,MACR,UAAU;GACR,cAAc,QAAQ;GACtB,OAAO,QAAQ;GACf,YAAY,QAAQ;GACpB,OAAO,QAAQ;EACjB,CAAC;CACP;CAEA,OAAO,OAAO,SAAuC;EACnD,OAAO,QAAQ,QAAQ,EAAE,WAAW,IAAI,MAAM,OAAO,CAAC;CACxD;CAEA,KAAK,OAAsC;EACzC,OAAO,KAAK,QAAQ,SAAS,EAAE,KAAK,KAAK;CAC3C;CAEA,QAAQ,KAA4B;EAClC,MAAM,WAAW,KAAKC,UAAU,IAAI,GAAG;EACvC,IAAI,UACF,OAAO;EAGT,MAAM,UAAU,IAAI,aAClB,KAAKD,MACL;GAAE;GAAK,OAAO,KAAKE;EAAO,GAC1B,KAAKH,MACP;EACA,MAAM,SAAwB;GAC5B,iBAAiB,QAAQ,UAAU;GACnC,YAAY;IACV,QAAQ,KAAK;IACb,KAAKE,UAAU,OAAO,GAAG;GAC3B;GACA,OAAO,UAAU,QAAQ,KAAK,KAAK;GACnC,QAAQ,UAAU,QAAQ,MAAM,KAAK;EACvC;EACA,KAAKA,UAAU,IAAI,KAAK,MAAM;EAC9B,OAAO;CACT;AACF;AAEA,SAAS,mBAAmB,SAAmD;CAC7E,IAAI,YAAY,QAAQ,OAAO,YAAY,UACzC,MAAM,IAAI,UACR,kEACF;CAGF,MAAM,SAAS,aAAa,OAAO;CACnC,MAAM,WACJ,WAAW,WAAW,QAAQ,UAAU,KAAA,KAAa,QAAQ,UAAU;CAEzE,IAAI,UAAU,UACZ,MAAM,IAAI,UACR,sEACF;CAGF,IAAI,SAAS,WAAW,QAAQ,QAAQ,KAAA,KAAa,CAAC,QACpD,MAAM,IAAI,UAAU,oCAAoC;CAG1D,IAAI,EAAE,UAAU,WACd,MAAM,IAAI,UAAU,sCAAsC;AAE9D;AAEA,SAAS,aAAa,SAA6C;CACjE,OAAO,SAAS,WAAW,OAAO,QAAQ,QAAQ;AACpD"}
1
+ {"version":3,"file":"agent.js","names":["#internalLlm","#llm","#onPluginError","#plugins","#sessions","#store"],"sources":["../src/agent.ts"],"sourcesContent":["import type { LanguageModel, ToolSet } from \"ai\";\nimport { type AgentToolChoice, createLlm, type Llm } from \"./llm\";\nimport type { AgentPlugin } from \"./plugins\";\nimport {\n type ResolvedAgentPlugins,\n resolveAgentPlugins,\n} from \"./plugins/runner\";\nimport type { AgentPluginErrorHandler } from \"./session/lifecycle\";\nimport type { AgentRun } from \"./session/run\";\nimport { type AgentInput, AgentSession } from \"./session/session\";\nimport { MemorySessionStore } from \"./session/store/memory\";\nimport type { SessionStore } from \"./session/store/types\";\n\ninterface AgentLanguageModelOptions {\n instructions?: string;\n llm?: never;\n model: LanguageModel;\n onPluginError?: AgentPluginErrorHandler;\n plugins?: readonly AgentPlugin[];\n toolChoice?: AgentToolChoice;\n tools?: ToolSet;\n}\n\ninterface AgentLlmOptions {\n instructions?: never;\n llm: Llm;\n model?: never;\n onPluginError?: AgentPluginErrorHandler;\n plugins?: readonly AgentPlugin[];\n toolChoice?: never;\n tools?: never;\n}\n\nexport interface SessionHandle {\n interrupt(): void;\n kill(): void;\n overlay(input: AgentInput): Promise<AgentRun>;\n send(input: AgentInput): Promise<AgentRun>;\n steer(input: AgentInput): Promise<AgentRun>;\n}\n\nexport type AgentOptions = AgentLanguageModelOptions | AgentLlmOptions;\n\nexport class Agent {\n readonly #internalLlm: Llm;\n readonly #llm: Llm;\n readonly #onPluginError?: AgentPluginErrorHandler;\n readonly #plugins: ResolvedAgentPlugins;\n readonly #sessions = new Map<string, SessionHandle>();\n readonly #store: SessionStore;\n\n private constructor(\n options: AgentOptions,\n resolvedPlugins: ResolvedAgentPlugins\n ) {\n assertAgentOptions(options);\n\n this.#plugins = resolvedPlugins;\n this.#onPluginError = options.onPluginError;\n this.#store =\n resolvedPlugins.sessionStore?.store ?? new MemorySessionStore();\n if (hasCustomLlm(options)) {\n this.#internalLlm = options.llm;\n this.#llm = options.llm;\n } else {\n this.#internalLlm = createLlm({\n instructions: options.instructions,\n model: options.model,\n });\n this.#llm = createLlm({\n instructions: options.instructions,\n model: options.model,\n toolChoice: options.toolChoice,\n tools: resolvedPlugins.tools,\n });\n }\n }\n\n static async create(options: AgentOptions): Promise<Agent> {\n assertAgentOptions(options);\n const resolvedPlugins = await resolveAgentPlugins({\n callerTools: hasCustomLlm(options) ? undefined : options.tools,\n plugins: options.plugins,\n });\n return new Agent(options, resolvedPlugins);\n }\n\n send(input: AgentInput): Promise<AgentRun> {\n return this.session(\"default\").send(input);\n }\n\n session(key: string): SessionHandle {\n const existing = this.#sessions.get(key);\n if (existing) {\n return existing;\n }\n\n const session = new AgentSession(\n this.#llm,\n { key, store: this.#store },\n this.#plugins,\n this.#internalLlm,\n this.#onPluginError\n );\n const handle: SessionHandle = {\n interrupt: () => session.interrupt(),\n kill: () => {\n session.kill();\n this.#sessions.delete(key);\n },\n overlay: (input) => session.overlay(input),\n send: (input) => session.send(input),\n steer: (input) => session.steer(input),\n };\n this.#sessions.set(key, handle);\n return handle;\n }\n}\n\nfunction assertAgentOptions(options: unknown): asserts options is AgentOptions {\n if (options === null || typeof options !== \"object\") {\n throw new TypeError(\n \"Agent options are required. Provide either { model } or { llm }.\"\n );\n }\n\n if (\"sessions\" in options) {\n throw new TypeError(\n \"Agent.create: options.sessions was removed. Use plugins: [sessions.custom(store)].\"\n );\n }\n\n if (\"hooks\" in options) {\n throw new TypeError(\n \"Agent.create: options.hooks was removed. Use run.events() with session.steer() for app control or plugin lifecycle handlers for middleware.\"\n );\n }\n\n const hasLlm = hasCustomLlm(options);\n const hasModel =\n \"model\" in options && options.model !== undefined && options.model !== null;\n\n if (hasLlm && hasModel) {\n throw new TypeError(\n \"Agent.create: provide either options.llm or options.model, not both.\"\n );\n }\n\n if (\"llm\" in options && options.llm !== undefined && !hasLlm) {\n throw new TypeError(\"Agent.create: invalid options.llm.\");\n }\n\n if (\n \"onPluginError\" in options &&\n options.onPluginError !== undefined &&\n typeof options.onPluginError !== \"function\"\n ) {\n throw new TypeError(\"Agent.create: invalid options.onPluginError.\");\n }\n\n if (!(hasLlm || hasModel)) {\n throw new TypeError(\"Agent.create: missing options.model.\");\n }\n}\n\nfunction hasCustomLlm(options: object): options is AgentLlmOptions {\n return \"llm\" in options && typeof options.llm === \"function\";\n}\n"],"mappings":";;;;;AA2CA,IAAa,QAAb,MAAa,MAAM;CACjB;CACA;CACA;CACA;CACA,4BAAqB,IAAI,IAA2B;CACpD;CAEA,YACE,SACA,iBACA;EACA,mBAAmB,OAAO;EAE1B,KAAKG,WAAW;EAChB,KAAKD,iBAAiB,QAAQ;EAC9B,KAAKG,SACH,gBAAgB,cAAc,SAAS,IAAI,mBAAmB;EAChE,IAAI,aAAa,OAAO,GAAG;GACzB,KAAKL,eAAe,QAAQ;GAC5B,KAAKC,OAAO,QAAQ;EACtB,OAAO;GACL,KAAKD,eAAe,UAAU;IAC5B,cAAc,QAAQ;IACtB,OAAO,QAAQ;GACjB,CAAC;GACD,KAAKC,OAAO,UAAU;IACpB,cAAc,QAAQ;IACtB,OAAO,QAAQ;IACf,YAAY,QAAQ;IACpB,OAAO,gBAAgB;GACzB,CAAC;EACH;CACF;CAEA,aAAa,OAAO,SAAuC;EACzD,mBAAmB,OAAO;EAK1B,OAAO,IAAI,MAAM,SAAS,MAJI,oBAAoB;GAChD,aAAa,aAAa,OAAO,IAAI,KAAA,IAAY,QAAQ;GACzD,SAAS,QAAQ;EACnB,CAAC,CACwC;CAC3C;CAEA,KAAK,OAAsC;EACzC,OAAO,KAAK,QAAQ,SAAS,EAAE,KAAK,KAAK;CAC3C;CAEA,QAAQ,KAA4B;EAClC,MAAM,WAAW,KAAKG,UAAU,IAAI,GAAG;EACvC,IAAI,UACF,OAAO;EAGT,MAAM,UAAU,IAAI,aAClB,KAAKH,MACL;GAAE;GAAK,OAAO,KAAKI;EAAO,GAC1B,KAAKF,UACL,KAAKH,cACL,KAAKE,cACP;EACA,MAAM,SAAwB;GAC5B,iBAAiB,QAAQ,UAAU;GACnC,YAAY;IACV,QAAQ,KAAK;IACb,KAAKE,UAAU,OAAO,GAAG;GAC3B;GACA,UAAU,UAAU,QAAQ,QAAQ,KAAK;GACzC,OAAO,UAAU,QAAQ,KAAK,KAAK;GACnC,QAAQ,UAAU,QAAQ,MAAM,KAAK;EACvC;EACA,KAAKA,UAAU,IAAI,KAAK,MAAM;EAC9B,OAAO;CACT;AACF;AAEA,SAAS,mBAAmB,SAAmD;CAC7E,IAAI,YAAY,QAAQ,OAAO,YAAY,UACzC,MAAM,IAAI,UACR,kEACF;CAGF,IAAI,cAAc,SAChB,MAAM,IAAI,UACR,oFACF;CAGF,IAAI,WAAW,SACb,MAAM,IAAI,UACR,6IACF;CAGF,MAAM,SAAS,aAAa,OAAO;CACnC,MAAM,WACJ,WAAW,WAAW,QAAQ,UAAU,KAAA,KAAa,QAAQ,UAAU;CAEzE,IAAI,UAAU,UACZ,MAAM,IAAI,UACR,sEACF;CAGF,IAAI,SAAS,WAAW,QAAQ,QAAQ,KAAA,KAAa,CAAC,QACpD,MAAM,IAAI,UAAU,oCAAoC;CAG1D,IACE,mBAAmB,WACnB,QAAQ,kBAAkB,KAAA,KAC1B,OAAO,QAAQ,kBAAkB,YAEjC,MAAM,IAAI,UAAU,8CAA8C;CAGpE,IAAI,EAAE,UAAU,WACd,MAAM,IAAI,UAAU,sCAAsC;AAE9D;AAEA,SAAS,aAAa,SAA6C;CACjE,OAAO,SAAS,WAAW,OAAO,QAAQ,QAAQ;AACpD"}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,12 @@
1
1
  import { AgentToolChoice, AgentToolExecute, AgentToolExecutionOptions, LlmOutputPart, RuntimeCreateLlmOptions, RuntimeLlm, RuntimeLlmContext, RuntimeLlmOutput, createLlm } from "./llm.js";
2
2
  import { AgentInput, SessionInput, UserInput, UserMessage, UserMessageContent, UserMessageContentPart, UserMessageFileData, UserMessageFilePart, UserMessageImagePart, UserMessageTextPart, UserText, UserTextContent } from "./session/input.js";
3
- import { AgentAfterStepContext, AgentAfterTurnContext, AgentBeforeStepContext, AgentBeforeTurnContext, AgentHooks, AgentStepResult, AgentTurnResult } from "./hooks.js";
4
- import { AgentEvent, AgentEventListener, AssistantReasoning, AssistantText, RuntimeInput, ToolCall, ToolResult } from "./session/events.js";
3
+ import { AgentEvent, AgentEventListener, AssistantReasoning, AssistantText, OverlayAccepted, OverlayExpired, OverlayInputSummary, OverlayPlacement, RuntimeInput, ToolCall, ToolResult } from "./session/events.js";
5
4
  import { AgentRun } from "./session/run.js";
6
5
  import { CommitResult, ExpectedSessionVersion, SessionStore, SessionStoreCommit, StoredSession } from "./session/store/types.js";
7
- import { Agent, AgentOptions, AgentSessionOptions, SessionHandle } from "./agent.js";
8
- export { Agent, type AgentAfterStepContext, type AgentAfterTurnContext, type AgentBeforeStepContext, type AgentBeforeTurnContext, type AgentEvent, type AgentEventListener, type AgentHooks, type AgentInput, type AgentOptions, type AgentRun, type AgentSessionOptions, type AgentStepResult, type AgentToolChoice, type AgentToolExecute, type AgentToolExecutionOptions, type AgentTurnResult, type AssistantReasoning, type AssistantText, type CommitResult, type ExpectedSessionVersion, type LlmOutputPart, type RuntimeCreateLlmOptions, type RuntimeInput, type RuntimeLlm, type RuntimeLlmContext, type RuntimeLlmOutput, type SessionHandle, type SessionInput, type SessionStore, type SessionStoreCommit, type StoredSession, type ToolCall, type ToolResult, type UserInput, type UserMessage, type UserMessageContent, type UserMessageContentPart, type UserMessageFileData, type UserMessageFilePart, type UserMessageImagePart, type UserMessageTextPart, type UserText, type UserTextContent, createLlm };
6
+ import { AgentContextTransform, AgentPlugin, AgentPluginEvent, AgentPluginEventFor, AgentPluginEventName, AgentPluginHandler, AgentPluginHandlerResult, AgentPluginHandlerReturn, AgentPluginHost, AgentPluginMaybePromise, AgentPluginStepAfterEvent, AgentPluginStepBeforeEvent, AgentPluginStepResult, AgentPluginToolCallEvent, AgentPluginToolCallResult, AgentPluginToolResultEvent, AgentPluginToolResultResult, AgentPluginToolResultStatus, AgentPluginToolSyntheticResult, AgentPluginTurnAfterEvent, AgentPluginTurnBeforeEvent, AgentPluginTurnResult, definePlugin } from "./plugins/types.js";
7
+ import { CompactionOptions, compaction } from "./plugins/compaction.js";
8
+ import { memory } from "./plugins/memory.js";
9
+ import { sessions } from "./plugins/sessions.js";
10
+ import { AgentPluginErrorHandler, AgentPluginHandlerError } from "./session/lifecycle.js";
11
+ import { Agent, AgentOptions, SessionHandle } from "./agent.js";
12
+ export { Agent, type AgentContextTransform, type AgentEvent, type AgentEventListener, type AgentInput, type AgentOptions, type AgentPlugin, type AgentPluginErrorHandler, type AgentPluginEvent, type AgentPluginEventFor, type AgentPluginEventName, type AgentPluginHandler, type AgentPluginHandlerError, type AgentPluginHandlerResult, type AgentPluginHandlerReturn, type AgentPluginHost, type AgentPluginMaybePromise, type AgentPluginStepAfterEvent, type AgentPluginStepBeforeEvent, type AgentPluginStepResult, type AgentPluginToolCallEvent, type AgentPluginToolCallResult, type AgentPluginToolResultEvent, type AgentPluginToolResultResult, type AgentPluginToolResultStatus, type AgentPluginToolSyntheticResult, type AgentPluginTurnAfterEvent, type AgentPluginTurnBeforeEvent, type AgentPluginTurnResult, type AgentRun, type AgentToolChoice, type AgentToolExecute, type AgentToolExecutionOptions, type AssistantReasoning, type AssistantText, type CommitResult, type CompactionOptions, type ExpectedSessionVersion, type LlmOutputPart, type OverlayAccepted, type OverlayExpired, type OverlayInputSummary, type OverlayPlacement, type RuntimeCreateLlmOptions, type RuntimeInput, type RuntimeLlm, type RuntimeLlmContext, type RuntimeLlmOutput, type SessionHandle, type SessionInput, type SessionStore, type SessionStoreCommit, type StoredSession, type ToolCall, type ToolResult, type UserInput, type UserMessage, type UserMessageContent, type UserMessageContentPart, type UserMessageFileData, type UserMessageFilePart, type UserMessageImagePart, type UserMessageTextPart, type UserText, type UserTextContent, compaction, createLlm, definePlugin, memory, sessions };
package/dist/index.js CHANGED
@@ -1,3 +1,8 @@
1
1
  import { createLlm } from "./llm.js";
2
+ import { definePlugin } from "./plugins/types.js";
2
3
  import { Agent } from "./agent.js";
3
- export { Agent, createLlm };
4
+ import { compaction } from "./plugins/compaction.js";
5
+ import { memory } from "./plugins/memory.js";
6
+ import { sessions } from "./plugins/sessions.js";
7
+ import "./plugins/index.js";
8
+ export { Agent, compaction, createLlm, definePlugin, memory, sessions };
package/dist/llm.js CHANGED
@@ -1,14 +1,20 @@
1
+ import { wrapToolsWithPluginHooks } from "./plugins/tool-hooks.js";
1
2
  import { generateText } from "ai";
2
3
  //#region src/llm.ts
3
4
  function createLlm({ model, instructions, toolChoice, tools }) {
4
5
  return async ({ history, signal }) => {
6
+ const scopedTools = wrapToolsWithPluginHooks({
7
+ history,
8
+ signal,
9
+ tools
10
+ });
5
11
  const { responseMessages } = await generateText({
6
12
  abortSignal: signal,
7
13
  instructions,
8
14
  messages: [...history],
9
15
  model,
10
16
  toolChoice,
11
- tools
17
+ tools: scopedTools
12
18
  });
13
19
  return responseMessages;
14
20
  };
package/dist/llm.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"llm.js","names":[],"sources":["../src/llm.ts"],"sourcesContent":["import type {\n LanguageModel,\n ModelMessage,\n Tool,\n ToolExecutionOptions,\n ToolSet,\n} from \"ai\";\nimport { generateText } from \"ai\";\n\nexport type AgentToolExecutionOptions = ToolExecutionOptions<unknown>;\nexport type AgentToolExecute = NonNullable<Tool[\"execute\"]>;\nexport type AgentToolChoice = \"auto\" | \"required\";\nexport type LlmOutput = Awaited<\n ReturnType<typeof generateText>\n>[\"responseMessages\"];\nexport type LlmOutputPart = LlmOutput[number];\n\nexport interface LlmContext {\n history: readonly ModelMessage[];\n signal: AbortSignal;\n}\n\nexport type Llm = (context: LlmContext) => Promise<LlmOutput>;\n\nexport interface CreateLlmOptions {\n instructions?: string;\n model: LanguageModel;\n toolChoice?: AgentToolChoice;\n tools?: ToolSet;\n}\n\nexport type RuntimeCreateLlmOptions = CreateLlmOptions;\nexport type RuntimeLlm = Llm;\nexport type RuntimeLlmContext = LlmContext;\nexport type RuntimeLlmOutput = LlmOutput;\n\nexport function createLlm({\n model,\n instructions,\n toolChoice,\n tools,\n}: CreateLlmOptions): Llm {\n return async ({ history, signal }) => {\n const { responseMessages } = await generateText({\n abortSignal: signal,\n instructions,\n messages: [...history],\n model,\n toolChoice,\n tools,\n });\n\n return responseMessages;\n };\n}\n"],"mappings":";;AAoCA,SAAgB,UAAU,EACxB,OACA,cACA,YACA,SACwB;CACxB,OAAO,OAAO,EAAE,SAAS,aAAa;EACpC,MAAM,EAAE,qBAAqB,MAAM,aAAa;GAC9C,aAAa;GACb;GACA,UAAU,CAAC,GAAG,OAAO;GACrB;GACA;GACA;EACF,CAAC;EAED,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"llm.js","names":[],"sources":["../src/llm.ts"],"sourcesContent":["import type {\n LanguageModel,\n ModelMessage,\n Tool,\n ToolExecutionOptions,\n ToolSet,\n} from \"ai\";\nimport { generateText } from \"ai\";\nimport { wrapToolsWithPluginHooks } from \"./plugins/tool-hooks\";\n\nexport type AgentToolExecutionOptions = ToolExecutionOptions<unknown>;\nexport type AgentToolExecute = NonNullable<Tool[\"execute\"]>;\nexport type AgentToolChoice = \"auto\" | \"required\";\nexport type LlmOutput = Awaited<\n ReturnType<typeof generateText>\n>[\"responseMessages\"];\nexport type LlmOutputPart = LlmOutput[number];\n\nexport interface LlmContext {\n history: readonly ModelMessage[];\n signal: AbortSignal;\n}\n\nexport type Llm = (context: LlmContext) => Promise<LlmOutput>;\n\nexport interface CreateLlmOptions {\n instructions?: string;\n model: LanguageModel;\n toolChoice?: AgentToolChoice;\n tools?: ToolSet;\n}\n\nexport type RuntimeCreateLlmOptions = CreateLlmOptions;\nexport type RuntimeLlm = Llm;\nexport type RuntimeLlmContext = LlmContext;\nexport type RuntimeLlmOutput = LlmOutput;\n\nexport function createLlm({\n model,\n instructions,\n toolChoice,\n tools,\n}: CreateLlmOptions): Llm {\n return async ({ history, signal }) => {\n const scopedTools = wrapToolsWithPluginHooks({\n history,\n signal,\n tools,\n });\n const { responseMessages } = await generateText({\n abortSignal: signal,\n instructions,\n messages: [...history],\n model,\n toolChoice,\n tools: scopedTools,\n });\n\n return responseMessages;\n };\n}\n"],"mappings":";;;AAqCA,SAAgB,UAAU,EACxB,OACA,cACA,YACA,SACwB;CACxB,OAAO,OAAO,EAAE,SAAS,aAAa;EACpC,MAAM,cAAc,yBAAyB;GAC3C;GACA;GACA;EACF,CAAC;EACD,MAAM,EAAE,qBAAqB,MAAM,aAAa;GAC9C,aAAa;GACb;GACA,UAAU,CAAC,GAAG,OAAO;GACrB;GACA;GACA,OAAO;EACT,CAAC;EAED,OAAO;CACT;AACF"}
@@ -0,0 +1,15 @@
1
+ import { AgentPlugin } from "./types.js";
2
+ import { ModelMessage } from "ai";
3
+
4
+ //#region src/plugins/compaction.d.ts
5
+ type CompactionSummarizer = (context: {
6
+ readonly messages: readonly ModelMessage[];
7
+ }) => Promise<string> | string;
8
+ interface CompactionOptions {
9
+ readonly summarize?: CompactionSummarizer;
10
+ readonly thresholdMessages?: number;
11
+ }
12
+ declare function compaction(options?: CompactionOptions): AgentPlugin;
13
+ //#endregion
14
+ export { CompactionOptions, compaction };
15
+ //# sourceMappingURL=compaction.d.ts.map