@minpeter/pss-runtime 0.0.11 → 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 (50) hide show
  1. package/README.md +67 -5
  2. package/dist/agent-namespace.js +17 -0
  3. package/dist/agent-namespace.js.map +1 -0
  4. package/dist/agent-validation.js +35 -0
  5. package/dist/agent-validation.js.map +1 -0
  6. package/dist/agent.d.ts +11 -2
  7. package/dist/agent.js +79 -14
  8. package/dist/agent.js.map +1 -1
  9. package/dist/child-session-cleanups.js +61 -0
  10. package/dist/child-session-cleanups.js.map +1 -0
  11. package/dist/session/events.d.ts +21 -1
  12. package/dist/session/history.d.ts +1 -0
  13. package/dist/session/input-normalization.js +66 -0
  14. package/dist/session/input-normalization.js.map +1 -0
  15. package/dist/session/runtime-input.d.ts +1 -0
  16. package/dist/session/runtime-input.js +69 -0
  17. package/dist/session/runtime-input.js.map +1 -0
  18. package/dist/session/session-errors.js +23 -0
  19. package/dist/session/session-errors.js.map +1 -0
  20. package/dist/session/session-kill.js +23 -0
  21. package/dist/session/session-kill.js.map +1 -0
  22. package/dist/session/session-runtime-drain.js +22 -0
  23. package/dist/session/session-runtime-drain.js.map +1 -0
  24. package/dist/session/session-state.d.ts +1 -0
  25. package/dist/session/session-state.js +102 -0
  26. package/dist/session/session-state.js.map +1 -0
  27. package/dist/session/session-turn-error.js +35 -0
  28. package/dist/session/session-turn-error.js.map +1 -0
  29. package/dist/session/session.js +95 -240
  30. package/dist/session/session.js.map +1 -1
  31. package/dist/session/store/file.d.ts +1 -0
  32. package/dist/session/store/file.js +14 -0
  33. package/dist/session/store/file.js.map +1 -1
  34. package/dist/session/store/memory.d.ts +1 -0
  35. package/dist/session/store/memory.js +5 -0
  36. package/dist/session/store/memory.js.map +1 -1
  37. package/dist/session/store/types.d.ts +1 -0
  38. package/dist/subagent-job-cancel.js +28 -0
  39. package/dist/subagent-job-cancel.js.map +1 -0
  40. package/dist/subagent-job-output.js +63 -0
  41. package/dist/subagent-job-output.js.map +1 -0
  42. package/dist/subagent-jobs.js +151 -0
  43. package/dist/subagent-jobs.js.map +1 -0
  44. package/dist/subagent-prompt-schema.js +114 -0
  45. package/dist/subagent-prompt-schema.js.map +1 -0
  46. package/dist/subagent-run.js +111 -0
  47. package/dist/subagent-run.js.map +1 -0
  48. package/dist/subagents.js +92 -0
  49. package/dist/subagents.js.map +1 -0
  50. package/package.json +1 -1
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
  });
@@ -80,6 +80,63 @@ The public transcript protocol is `AgentEvent`: live runs emit runtime-defined
80
80
  events through `run.events()`. Provider/model message history is internal
81
81
  continuation state, not a public history API.
82
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
+
83
140
  ## Send and Steer
84
141
 
85
142
  Use `session.send(input)` for a new user turn. If a run is already active, the
@@ -135,28 +192,33 @@ Custom stores own version generation. `load(key)` returns the opaque `state` wit
135
192
  the store-minted `version`; `commit(key, { state }, { expectedVersion })` receives
136
193
  state only and should reject stale versions by returning `{ ok: false, reason:
137
194
  "conflict" }`. On success, the store persists `{ state, version }` and returns the
138
- new version to the runtime.
195
+ new version to the runtime. `delete(key)` removes the persisted session for that
196
+ key.
139
197
 
140
198
  ```ts
141
199
  import type { SessionStore } from "@minpeter/pss-runtime";
142
200
  import { MemorySessionStore } from "@minpeter/pss-runtime/session-store/memory";
143
201
 
144
- const agent = await Agent.create({
202
+ const agent = new Agent({
145
203
  model,
146
204
  sessions: {
205
+ namespace: "support-agent",
147
206
  store: new MemorySessionStore(), // default when omitted
148
207
  },
149
208
  });
150
209
  ```
151
210
 
152
- For durable sessions, use the exported file POC:
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:
153
214
 
154
215
  ```ts
155
216
  import { FileSessionStore } from "@minpeter/pss-runtime/session-store/file";
156
217
 
157
- const agent = await Agent.create({
218
+ const agent = new Agent({
158
219
  model,
159
220
  sessions: {
221
+ namespace: "support-agent",
160
222
  store: new FileSessionStore(".pss/sessions"),
161
223
  },
162
224
  });
@@ -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
@@ -7,27 +7,35 @@ import { LanguageModel, ToolSet } from "ai";
7
7
 
8
8
  //#region src/agent.d.ts
9
9
  interface AgentLanguageModelOptions {
10
+ description?: string;
10
11
  hooks?: AgentHooks;
11
12
  instructions?: string;
12
13
  llm?: never;
13
14
  model: LanguageModel;
15
+ name?: string;
14
16
  sessions?: AgentSessionOptions;
17
+ subagents?: readonly Agent[];
15
18
  toolChoice?: AgentToolChoice;
16
19
  tools?: ToolSet;
17
20
  }
18
21
  interface AgentLlmOptions {
22
+ description?: string;
19
23
  hooks?: AgentHooks;
20
24
  instructions?: never;
21
25
  llm: Llm;
22
26
  model?: never;
27
+ name?: string;
23
28
  sessions?: AgentSessionOptions;
29
+ subagents?: never;
24
30
  toolChoice?: never;
25
31
  tools?: never;
26
32
  }
27
33
  interface AgentSessionOptions {
34
+ namespace?: string;
28
35
  store?: SessionStore;
29
36
  }
30
37
  interface SessionHandle {
38
+ delete(): Promise<void>;
31
39
  interrupt(): void;
32
40
  kill(): void;
33
41
  send(input: AgentInput): Promise<AgentRun>;
@@ -36,8 +44,9 @@ interface SessionHandle {
36
44
  type AgentOptions = AgentLanguageModelOptions | AgentLlmOptions;
37
45
  declare class Agent {
38
46
  #private;
39
- private constructor();
40
- static create(options: AgentOptions): Promise<Agent>;
47
+ readonly description?: string;
48
+ readonly name?: string;
49
+ constructor(options: AgentOptions);
41
50
  send(input: AgentInput): Promise<AgentRun>;
42
51
  session(key: string): SessionHandle;
43
52
  }
package/dist/agent.js CHANGED
@@ -1,25 +1,42 @@
1
+ import { agentNamespace, parentSessionNamespace, randomAgentNamespace } from "./agent-namespace.js";
2
+ import { assertSubagents } from "./agent-validation.js";
3
+ import { ChildSessionCleanups } from "./child-session-cleanups.js";
1
4
  import { createLlm } from "./llm.js";
2
5
  import { AgentSession } from "./session/session.js";
3
6
  import { MemorySessionStore } from "./session/store/memory.js";
7
+ import { createSubagentTools } from "./subagents.js";
4
8
  //#region src/agent.ts
5
9
  var Agent = class Agent {
10
+ #baseTools;
6
11
  #hooks;
7
12
  #llm;
13
+ #modelOptions;
14
+ #childSessionCleanups = new ChildSessionCleanups();
15
+ #sessionGenerations = /* @__PURE__ */ new Map();
8
16
  #sessions = /* @__PURE__ */ new Map();
17
+ #sessionNamespace;
9
18
  #store;
19
+ #subagents;
20
+ description;
21
+ name;
10
22
  constructor(options) {
11
23
  assertAgentOptions(options);
24
+ this.description = options.description;
25
+ this.name = options.name;
26
+ this.#sessionNamespace = stableAgentNamespace(options);
12
27
  this.#store = options.sessions?.store ?? new MemorySessionStore();
13
28
  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
- });
20
- }
21
- static create(options) {
22
- return Promise.resolve().then(() => new Agent(options));
29
+ assertSubagents(options, Agent, hasCustomLlm(options));
30
+ this.#subagents = hasCustomLlm(options) ? [] : options.subagents ?? [];
31
+ if (hasCustomLlm(options)) this.#llm = options.llm;
32
+ else {
33
+ this.#baseTools = options.tools;
34
+ this.#modelOptions = {
35
+ instructions: options.instructions,
36
+ model: options.model,
37
+ toolChoice: options.toolChoice
38
+ };
39
+ }
23
40
  }
24
41
  send(input) {
25
42
  return this.session("default").send(input);
@@ -27,14 +44,32 @@ var Agent = class Agent {
27
44
  session(key) {
28
45
  const existing = this.#sessions.get(key);
29
46
  if (existing) return existing;
30
- const session = new AgentSession(this.#llm, {
47
+ let session;
48
+ const getSession = () => {
49
+ if (!session) throw new Error("Agent session is not initialized.");
50
+ return session;
51
+ };
52
+ const parentAgentNamespace = parentSessionNamespace({
53
+ generation: this.#sessionGenerations.get(key) ?? 0,
54
+ sessionKey: key,
55
+ sessionNamespace: this.#sessionNamespace
56
+ });
57
+ session = new AgentSession(this.#llm ?? createLlm(this.#createLlmOptionsForSession(key, parentAgentNamespace, (input, placement) => getSession().enqueueRuntimeInput(input, placement), (event) => getSession().emitObserverEvent(event))), {
31
58
  key,
32
59
  store: this.#store
33
60
  }, this.#hooks);
34
61
  const handle = {
62
+ delete: async () => {
63
+ await session.delete();
64
+ this.#sessions.delete(key);
65
+ this.#sessionGenerations.set(key, (this.#sessionGenerations.get(key) ?? 0) + 1);
66
+ await this.#deleteChildSessions(key);
67
+ },
35
68
  interrupt: () => session.interrupt(),
36
69
  kill: () => {
37
70
  session.kill();
71
+ this.#sessionGenerations.set(key, (this.#sessionGenerations.get(key) ?? 0) + 1);
72
+ this.#deleteChildSessions(key).catch(() => void 0);
38
73
  this.#sessions.delete(key);
39
74
  },
40
75
  send: (input) => session.send(input),
@@ -43,14 +78,44 @@ var Agent = class Agent {
43
78
  this.#sessions.set(key, handle);
44
79
  return handle;
45
80
  }
81
+ #createLlmOptionsForSession(key, parentAgentNamespace, enqueueRuntimeInput, emitObserverEvent) {
82
+ const modelOptions = this.#modelOptions;
83
+ if (!modelOptions) throw new Error("Agent: missing model options.");
84
+ const tools = this.#subagents.length === 0 ? this.#baseTools : {
85
+ ...this.#baseTools,
86
+ ...createSubagentTools({
87
+ parentAgentNamespace,
88
+ parentSession: {
89
+ emitObserverEvent,
90
+ enqueueRuntimeInput
91
+ },
92
+ parentSessionKey: key,
93
+ registerChildSession: (sessionKey, cleanup) => this.#childSessionCleanups.register(sessionKey, cleanup),
94
+ subagents: this.#subagents
95
+ })
96
+ };
97
+ return {
98
+ instructions: modelOptions.instructions,
99
+ model: modelOptions.model,
100
+ toolChoice: modelOptions.toolChoice,
101
+ tools
102
+ };
103
+ }
104
+ async #deleteChildSessions(parentSessionKey) {
105
+ await this.#childSessionCleanups.delete(parentSessionKey);
106
+ }
46
107
  };
108
+ function stableAgentNamespace(options) {
109
+ const namespace = options.sessions?.namespace ?? options.name;
110
+ return namespace ? agentNamespace(namespace) : randomAgentNamespace();
111
+ }
47
112
  function assertAgentOptions(options) {
48
113
  if (options === null || typeof options !== "object") throw new TypeError("Agent options are required. Provide either { model } or { llm }.");
49
114
  const hasLlm = hasCustomLlm(options);
50
- const hasModel = "model" in options && options.model !== void 0 && options.model !== null;
51
- if (hasLlm && hasModel) throw new TypeError("Agent.create: provide either options.llm or options.model, not both.");
52
- if ("llm" in options && options.llm !== void 0 && !hasLlm) throw new TypeError("Agent.create: invalid options.llm.");
53
- if (!(hasLlm || hasModel)) throw new TypeError("Agent.create: missing options.model.");
115
+ const hasModel = "model" in options && options.model != null;
116
+ if (hasLlm && hasModel) throw new TypeError("Agent: provide either options.llm or options.model.");
117
+ if ("llm" in options && options.llm !== void 0 && !hasLlm) throw new TypeError("Agent: invalid options.llm.");
118
+ if (!(hasLlm || hasModel)) throw new TypeError("Agent: missing options.model.");
54
119
  }
55
120
  function hasCustomLlm(options) {
56
121
  return "llm" in options && typeof options.llm === "function";
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":["#baseTools","#hooks","#llm","#modelOptions","#childSessionCleanups","#sessionGenerations","#sessions","#sessionNamespace","#store","#subagents","#createLlmOptionsForSession","#deleteChildSessions"],"sources":["../src/agent.ts"],"sourcesContent":["import type { LanguageModel, ToolSet } from \"ai\";\nimport {\n agentNamespace,\n parentSessionNamespace,\n randomAgentNamespace,\n} from \"./agent-namespace\";\nimport { assertSubagents } from \"./agent-validation\";\nimport { ChildSessionCleanups } from \"./child-session-cleanups\";\nimport type { AgentHooks } from \"./hooks\";\nimport { type AgentToolChoice, createLlm, type Llm } from \"./llm\";\nimport type { UserInput } from \"./session/events\";\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\";\nimport { createSubagentTools } from \"./subagents\";\n\ninterface AgentLanguageModelOptions {\n description?: string;\n hooks?: AgentHooks;\n instructions?: string;\n llm?: never;\n model: LanguageModel;\n name?: string;\n sessions?: AgentSessionOptions;\n subagents?: readonly Agent[];\n toolChoice?: AgentToolChoice;\n tools?: ToolSet;\n}\n\ninterface AgentLlmOptions {\n description?: string;\n hooks?: AgentHooks;\n instructions?: never;\n llm: Llm;\n model?: never;\n name?: string;\n sessions?: AgentSessionOptions;\n subagents?: never;\n toolChoice?: never;\n tools?: never;\n}\n\nexport interface AgentSessionOptions {\n namespace?: string;\n store?: SessionStore;\n}\n\nexport interface SessionHandle {\n delete(): Promise<void>;\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;\ntype AgentModelOptions = Pick<\n AgentLanguageModelOptions,\n \"instructions\" | \"model\" | \"toolChoice\"\n>;\n\nexport class Agent {\n readonly #baseTools?: ToolSet;\n readonly #hooks?: AgentHooks;\n readonly #llm?: Llm;\n readonly #modelOptions?: AgentModelOptions;\n readonly #childSessionCleanups = new ChildSessionCleanups();\n readonly #sessionGenerations = new Map<string, number>();\n readonly #sessions = new Map<string, SessionHandle>();\n readonly #sessionNamespace: string;\n readonly #store: SessionStore;\n readonly #subagents: readonly Agent[];\n readonly description?: string;\n readonly name?: string;\n\n constructor(options: AgentOptions) {\n assertAgentOptions(options);\n\n this.description = options.description;\n this.name = options.name;\n this.#sessionNamespace = stableAgentNamespace(options);\n this.#store = options.sessions?.store ?? new MemorySessionStore();\n this.#hooks = options.hooks;\n assertSubagents(options, Agent, hasCustomLlm(options));\n this.#subagents = hasCustomLlm(options) ? [] : (options.subagents ?? []);\n if (hasCustomLlm(options)) {\n this.#llm = options.llm;\n } else {\n this.#baseTools = options.tools;\n this.#modelOptions = {\n instructions: options.instructions,\n model: options.model,\n toolChoice: options.toolChoice,\n };\n }\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 let session: AgentSession | undefined;\n const getSession = () => {\n if (!session) {\n throw new Error(\"Agent session is not initialized.\");\n }\n return session;\n };\n const parentAgentNamespace = parentSessionNamespace({\n generation: this.#sessionGenerations.get(key) ?? 0,\n sessionKey: key,\n sessionNamespace: this.#sessionNamespace,\n });\n const llm =\n this.#llm ??\n createLlm(\n this.#createLlmOptionsForSession(\n key,\n parentAgentNamespace,\n (input: UserInput, placement?: \"turn-start\") =>\n getSession().enqueueRuntimeInput(input, placement),\n (event) => getSession().emitObserverEvent(event)\n )\n );\n session = new AgentSession(llm, { key, store: this.#store }, this.#hooks);\n const handle: SessionHandle = {\n delete: async () => {\n await session.delete();\n this.#sessions.delete(key);\n this.#sessionGenerations.set(\n key,\n (this.#sessionGenerations.get(key) ?? 0) + 1\n );\n await this.#deleteChildSessions(key);\n },\n interrupt: () => session.interrupt(),\n kill: () => {\n session.kill();\n this.#sessionGenerations.set(\n key,\n (this.#sessionGenerations.get(key) ?? 0) + 1\n );\n this.#deleteChildSessions(key).catch(() => undefined);\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 #createLlmOptionsForSession(\n key: string,\n parentAgentNamespace: string,\n enqueueRuntimeInput: AgentSession[\"enqueueRuntimeInput\"],\n emitObserverEvent: AgentSession[\"emitObserverEvent\"]\n ): Parameters<typeof createLlm>[0] {\n const modelOptions = this.#modelOptions;\n if (!modelOptions) {\n throw new Error(\"Agent: missing model options.\");\n }\n const tools =\n this.#subagents.length === 0\n ? this.#baseTools\n : {\n ...this.#baseTools,\n ...createSubagentTools({\n parentAgentNamespace,\n parentSession: { emitObserverEvent, enqueueRuntimeInput },\n parentSessionKey: key,\n registerChildSession: (sessionKey, cleanup) =>\n this.#childSessionCleanups.register(sessionKey, cleanup),\n subagents: this.#subagents,\n }),\n };\n\n return {\n instructions: modelOptions.instructions,\n model: modelOptions.model,\n toolChoice: modelOptions.toolChoice,\n tools,\n };\n }\n\n async #deleteChildSessions(parentSessionKey: string): Promise<void> {\n await this.#childSessionCleanups.delete(parentSessionKey);\n }\n}\n\nfunction stableAgentNamespace(options: AgentOptions): string {\n const namespace = options.sessions?.namespace ?? options.name;\n return namespace ? agentNamespace(namespace) : randomAgentNamespace();\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 = \"model\" in options && options.model != null;\n\n if (hasLlm && hasModel) {\n throw new TypeError(\"Agent: provide either options.llm or options.model.\");\n }\n\n if (\"llm\" in options && options.llm !== undefined && !hasLlm) {\n throw new TypeError(\"Agent: invalid options.llm.\");\n }\n\n if (!(hasLlm || hasModel)) {\n throw new TypeError(\"Agent: 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":"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"}
@@ -0,0 +1,61 @@
1
+ //#region src/child-session-cleanups.ts
2
+ var ChildSessionCleanups = class {
3
+ #byParentSession = /* @__PURE__ */ new Map();
4
+ async delete(parentSessionKey) {
5
+ const cleanups = this.#byParentSession.get(parentSessionKey);
6
+ if (!cleanups) return;
7
+ this.#byParentSession.delete(parentSessionKey);
8
+ const results = await Promise.all([...cleanups].map(runCleanup));
9
+ const failedCleanups = [];
10
+ let firstError;
11
+ for (const result of results) {
12
+ if (result.ok) continue;
13
+ firstError ??= result.error;
14
+ failedCleanups.push(result.cleanup);
15
+ }
16
+ if (failedCleanups.length === 0) return;
17
+ this.#restore(parentSessionKey, failedCleanups);
18
+ throw firstError instanceof Error ? firstError : new Error(String(firstError));
19
+ }
20
+ register(parentSessionKey, cleanup) {
21
+ const existing = this.#byParentSession.get(parentSessionKey);
22
+ if (existing) {
23
+ existing.add(cleanup);
24
+ return () => this.#unregister(parentSessionKey, existing, cleanup);
25
+ }
26
+ const cleanups = new Set([cleanup]);
27
+ this.#byParentSession.set(parentSessionKey, cleanups);
28
+ return () => this.#unregister(parentSessionKey, cleanups, cleanup);
29
+ }
30
+ #unregister(parentSessionKey, cleanups, cleanup) {
31
+ cleanups.delete(cleanup);
32
+ if (cleanups.size === 0 && this.#byParentSession.get(parentSessionKey) === cleanups) this.#byParentSession.delete(parentSessionKey);
33
+ }
34
+ #restore(parentSessionKey, failedCleanups) {
35
+ const current = this.#byParentSession.get(parentSessionKey);
36
+ if (current) {
37
+ for (const cleanup of failedCleanups) current.add(cleanup);
38
+ return;
39
+ }
40
+ this.#byParentSession.set(parentSessionKey, new Set(failedCleanups));
41
+ }
42
+ };
43
+ async function runCleanup(cleanup) {
44
+ try {
45
+ await cleanup();
46
+ return {
47
+ cleanup,
48
+ ok: true
49
+ };
50
+ } catch (error) {
51
+ return {
52
+ cleanup,
53
+ error,
54
+ ok: false
55
+ };
56
+ }
57
+ }
58
+ //#endregion
59
+ export { ChildSessionCleanups };
60
+
61
+ //# sourceMappingURL=child-session-cleanups.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"child-session-cleanups.js","names":["#byParentSession","#restore","#unregister"],"sources":["../src/child-session-cleanups.ts"],"sourcesContent":["type ChildSessionCleanup = () => Promise<void>;\n\ntype CleanupResult =\n | { readonly cleanup: ChildSessionCleanup; readonly ok: true }\n | {\n readonly cleanup: ChildSessionCleanup;\n readonly error: unknown;\n readonly ok: false;\n };\n\nexport class ChildSessionCleanups {\n readonly #byParentSession = new Map<string, Set<ChildSessionCleanup>>();\n\n async delete(parentSessionKey: string): Promise<void> {\n const cleanups = this.#byParentSession.get(parentSessionKey);\n if (!cleanups) {\n return;\n }\n\n this.#byParentSession.delete(parentSessionKey);\n const results = await Promise.all([...cleanups].map(runCleanup));\n const failedCleanups: ChildSessionCleanup[] = [];\n let firstError: unknown;\n for (const result of results) {\n if (result.ok) {\n continue;\n }\n\n firstError ??= result.error;\n failedCleanups.push(result.cleanup);\n }\n\n if (failedCleanups.length === 0) {\n return;\n }\n\n this.#restore(parentSessionKey, failedCleanups);\n throw firstError instanceof Error\n ? firstError\n : new Error(String(firstError));\n }\n\n register(parentSessionKey: string, cleanup: ChildSessionCleanup): () => void {\n const existing = this.#byParentSession.get(parentSessionKey);\n if (existing) {\n existing.add(cleanup);\n return () => this.#unregister(parentSessionKey, existing, cleanup);\n }\n\n const cleanups = new Set([cleanup]);\n this.#byParentSession.set(parentSessionKey, cleanups);\n return () => this.#unregister(parentSessionKey, cleanups, cleanup);\n }\n\n #unregister(\n parentSessionKey: string,\n cleanups: Set<ChildSessionCleanup>,\n cleanup: ChildSessionCleanup\n ): void {\n cleanups.delete(cleanup);\n if (\n cleanups.size === 0 &&\n this.#byParentSession.get(parentSessionKey) === cleanups\n ) {\n this.#byParentSession.delete(parentSessionKey);\n }\n }\n\n #restore(\n parentSessionKey: string,\n failedCleanups: readonly ChildSessionCleanup[]\n ): void {\n const current = this.#byParentSession.get(parentSessionKey);\n if (current) {\n for (const cleanup of failedCleanups) {\n current.add(cleanup);\n }\n return;\n }\n\n this.#byParentSession.set(parentSessionKey, new Set(failedCleanups));\n }\n}\n\nasync function runCleanup(\n cleanup: ChildSessionCleanup\n): Promise<CleanupResult> {\n try {\n await cleanup();\n return { cleanup, ok: true };\n } catch (error) {\n return { cleanup, error, ok: false };\n }\n}\n"],"mappings":";AAUA,IAAa,uBAAb,MAAkC;CAChC,mCAA4B,IAAI,IAAsC;CAEtE,MAAM,OAAO,kBAAyC;EACpD,MAAM,WAAW,KAAKA,iBAAiB,IAAI,gBAAgB;EAC3D,IAAI,CAAC,UACH;EAGF,KAAKA,iBAAiB,OAAO,gBAAgB;EAC7C,MAAM,UAAU,MAAM,QAAQ,IAAI,CAAC,GAAG,QAAQ,EAAE,IAAI,UAAU,CAAC;EAC/D,MAAM,iBAAwC,CAAC;EAC/C,IAAI;EACJ,KAAK,MAAM,UAAU,SAAS;GAC5B,IAAI,OAAO,IACT;GAGF,eAAe,OAAO;GACtB,eAAe,KAAK,OAAO,OAAO;EACpC;EAEA,IAAI,eAAe,WAAW,GAC5B;EAGF,KAAKC,SAAS,kBAAkB,cAAc;EAC9C,MAAM,sBAAsB,QACxB,aACA,IAAI,MAAM,OAAO,UAAU,CAAC;CAClC;CAEA,SAAS,kBAA0B,SAA0C;EAC3E,MAAM,WAAW,KAAKD,iBAAiB,IAAI,gBAAgB;EAC3D,IAAI,UAAU;GACZ,SAAS,IAAI,OAAO;GACpB,aAAa,KAAKE,YAAY,kBAAkB,UAAU,OAAO;EACnE;EAEA,MAAM,WAAW,IAAI,IAAI,CAAC,OAAO,CAAC;EAClC,KAAKF,iBAAiB,IAAI,kBAAkB,QAAQ;EACpD,aAAa,KAAKE,YAAY,kBAAkB,UAAU,OAAO;CACnE;CAEA,YACE,kBACA,UACA,SACM;EACN,SAAS,OAAO,OAAO;EACvB,IACE,SAAS,SAAS,KAClB,KAAKF,iBAAiB,IAAI,gBAAgB,MAAM,UAEhD,KAAKA,iBAAiB,OAAO,gBAAgB;CAEjD;CAEA,SACE,kBACA,gBACM;EACN,MAAM,UAAU,KAAKA,iBAAiB,IAAI,gBAAgB;EAC1D,IAAI,SAAS;GACX,KAAK,MAAM,WAAW,gBACpB,QAAQ,IAAI,OAAO;GAErB;EACF;EAEA,KAAKA,iBAAiB,IAAI,kBAAkB,IAAI,IAAI,cAAc,CAAC;CACrE;AACF;AAEA,eAAe,WACb,SACwB;CACxB,IAAI;EACF,MAAM,QAAQ;EACd,OAAO;GAAE;GAAS,IAAI;EAAK;CAC7B,SAAS,OAAO;EACd,OAAO;GAAE;GAAS;GAAO,IAAI;EAAM;CACrC;AACF"}
@@ -30,6 +30,7 @@ interface ToolResult {
30
30
  toolName: string;
31
31
  type: "tool-result";
32
32
  }
33
+ type SubagentJobStatus = "aborted" | "cancelled" | "completed" | "error" | "pending" | "running";
33
34
  type AgentEvent = /** User input was accepted into the session queue. */UserText /** User multipart input was accepted into the session queue. */ | UserMessage /** Runtime/API-originated input inserted into the current turn, not human input. */ | RuntimeInput /** A queued user input started running as a turn. */ | {
34
35
  type: "turn-start";
35
36
  } /** The active turn was interrupted before normal completion. */ | {
@@ -41,7 +42,26 @@ type AgentEvent = /** User input was accepted into the session queue. */UserText
41
42
  type: "turn-end";
42
43
  } /** One model/tool-loop iteration started within the active turn. */ | {
43
44
  type: "step-start";
44
- } /** The model produced reasoning content. */ | AssistantReasoning /** The model produced visible assistant text. */ | AssistantText /** The model requested a tool call. */ | ToolCall /** A tool call returned a result. */ | ToolResult /** One model/tool-loop iteration finished within the active turn. */ | {
45
+ } /** The model produced reasoning content. */ | AssistantReasoning /** The model produced visible assistant text. */ | AssistantText /** The model requested a tool call. */ | ToolCall /** A tool call returned a result. */ | ToolResult | {
46
+ description?: string;
47
+ run_in_background: boolean;
48
+ subagent: string;
49
+ task_id?: string;
50
+ type: "subagent-job-start";
51
+ } | {
52
+ eventType?: AgentEvent["type"];
53
+ status: SubagentJobStatus;
54
+ subagent: string;
55
+ task_id: string;
56
+ type: "subagent-job-update";
57
+ } | {
58
+ error?: string;
59
+ eventCount: number;
60
+ status: Exclude<SubagentJobStatus, "pending" | "running">;
61
+ subagent: string;
62
+ task_id?: string;
63
+ type: "subagent-job-end";
64
+ } /** One model/tool-loop iteration finished within the active turn. */ | {
45
65
  type: "step-end";
46
66
  };
47
67
  type AgentEventListener = (event: AgentEvent) => void;
@@ -0,0 +1 @@
1
+ import { ModelMessage } from "ai";
@@ -0,0 +1,66 @@
1
+ //#region src/session/input-normalization.ts
2
+ function normalizeAgentInput(input) {
3
+ if (typeof input === "string") return {
4
+ type: "user-text",
5
+ text: input
6
+ };
7
+ if (isStringArrayInput(input)) return {
8
+ type: "user-text",
9
+ text: structuredClone(input)
10
+ };
11
+ if (isArrayInput(input)) {
12
+ assertUserMessageContent(input);
13
+ return {
14
+ type: "user-message",
15
+ content: structuredClone(input)
16
+ };
17
+ }
18
+ if (isUserMessage(input)) {
19
+ assertUserMessageContent(input.content);
20
+ return structuredClone(input);
21
+ }
22
+ if (isUserText(input)) return structuredClone(input);
23
+ throw new TypeError("Agent input must be text, text parts, content parts, user-text, or user-message.");
24
+ }
25
+ function isStringArrayInput(input) {
26
+ return Array.isArray(input) && hasDenseItems(input, isString);
27
+ }
28
+ function isArrayInput(input) {
29
+ return Array.isArray(input);
30
+ }
31
+ function isUserMessage(input) {
32
+ return input !== null && typeof input === "object" && !isArrayInput(input) && input.type === "user-message" && "content" in input && Array.isArray(input.content);
33
+ }
34
+ function isUserText(input) {
35
+ return input !== null && typeof input === "object" && !isArrayInput(input) && input.type === "user-text" && (typeof input.text === "string" || isStringArrayInput(input.text));
36
+ }
37
+ function assertUserMessageContent(input) {
38
+ for (const part of input) if (!isUserMessageContentPart(part)) throw new TypeError("Agent input content parts must be { type: \"text\", text }, { type: \"image\", image }, or { type: \"file\", data, mediaType }.");
39
+ }
40
+ function isUserMessageContentPart(part) {
41
+ if (part === null || typeof part !== "object" || !("type" in part)) return false;
42
+ if (part.type === "text") return "text" in part && typeof part.text === "string";
43
+ if (part.type === "image") return "image" in part && typeof part.image === "string" && (!("mediaType" in part) || typeof part.mediaType === "string");
44
+ if (part.type === "file") return "data" in part && isUserMessageFileData(part.data) && "mediaType" in part && typeof part.mediaType === "string" && (!("filename" in part) || typeof part.filename === "string");
45
+ return false;
46
+ }
47
+ function isUserMessageFileData(data) {
48
+ if (typeof data === "string") return true;
49
+ if (data === null || typeof data !== "object" || !("type" in data)) return false;
50
+ if (data.type === "data") return "data" in data && typeof data.data === "string";
51
+ if (data.type === "reference") return "reference" in data && data.reference !== null && typeof data.reference === "object" && Object.values(data.reference).every((value) => typeof value === "string");
52
+ if (data.type === "text") return "text" in data && typeof data.text === "string";
53
+ if (data.type === "url") return "url" in data && typeof data.url === "string";
54
+ return false;
55
+ }
56
+ function hasDenseItems(input, predicate) {
57
+ for (let index = 0; index < input.length; index += 1) if (!(index in input && predicate(input[index]))) return false;
58
+ return true;
59
+ }
60
+ function isString(value) {
61
+ return typeof value === "string";
62
+ }
63
+ //#endregion
64
+ export { normalizeAgentInput };
65
+
66
+ //# sourceMappingURL=input-normalization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-normalization.js","names":[],"sources":["../../src/session/input-normalization.ts"],"sourcesContent":["import type {\n AgentInput,\n UserInput,\n UserMessage,\n UserMessageContentPart,\n UserText,\n} from \"./input\";\n\nexport function normalizeAgentInput(input: AgentInput): UserInput {\n if (typeof input === \"string\") {\n return {\n type: \"user-text\",\n text: input,\n };\n }\n\n if (isStringArrayInput(input)) {\n return {\n type: \"user-text\",\n text: structuredClone(input),\n };\n }\n\n if (isArrayInput(input)) {\n assertUserMessageContent(input);\n return {\n type: \"user-message\",\n content: structuredClone(input),\n };\n }\n\n if (isUserMessage(input)) {\n assertUserMessageContent(input.content);\n return structuredClone(input);\n }\n\n if (isUserText(input)) {\n return structuredClone(input);\n }\n\n throw new TypeError(\n \"Agent input must be text, text parts, content parts, user-text, or user-message.\"\n );\n}\n\nfunction isStringArrayInput(input: unknown): input is readonly string[] {\n return Array.isArray(input) && hasDenseItems(input, isString);\n}\n\nfunction isArrayInput(\n input: AgentInput\n): input is readonly string[] | readonly UserMessageContentPart[] {\n return Array.isArray(input);\n}\n\nfunction isUserMessage(input: AgentInput): input is UserMessage {\n return (\n input !== null &&\n typeof input === \"object\" &&\n !isArrayInput(input) &&\n input.type === \"user-message\" &&\n \"content\" in input &&\n Array.isArray(input.content)\n );\n}\n\nfunction isUserText(input: AgentInput): input is UserText {\n return (\n input !== null &&\n typeof input === \"object\" &&\n !isArrayInput(input) &&\n input.type === \"user-text\" &&\n (typeof input.text === \"string\" || isStringArrayInput(input.text))\n );\n}\n\nfunction assertUserMessageContent(\n input: readonly unknown[]\n): asserts input is readonly UserMessageContentPart[] {\n for (const part of input) {\n if (!isUserMessageContentPart(part)) {\n throw new TypeError(\n 'Agent input content parts must be { type: \"text\", text }, { type: \"image\", image }, or { type: \"file\", data, mediaType }.'\n );\n }\n }\n}\n\nfunction isUserMessageContentPart(\n part: unknown\n): part is UserMessageContentPart {\n if (part === null || typeof part !== \"object\" || !(\"type\" in part)) {\n return false;\n }\n\n if (part.type === \"text\") {\n return \"text\" in part && typeof part.text === \"string\";\n }\n\n if (part.type === \"image\") {\n return (\n \"image\" in part &&\n typeof part.image === \"string\" &&\n (!(\"mediaType\" in part) || typeof part.mediaType === \"string\")\n );\n }\n\n if (part.type === \"file\") {\n return (\n \"data\" in part &&\n isUserMessageFileData(part.data) &&\n \"mediaType\" in part &&\n typeof part.mediaType === \"string\" &&\n (!(\"filename\" in part) || typeof part.filename === \"string\")\n );\n }\n\n return false;\n}\n\nfunction isUserMessageFileData(data: unknown): boolean {\n if (typeof data === \"string\") {\n return true;\n }\n\n if (data === null || typeof data !== \"object\" || !(\"type\" in data)) {\n return false;\n }\n\n if (data.type === \"data\") {\n return \"data\" in data && typeof data.data === \"string\";\n }\n\n if (data.type === \"reference\") {\n return (\n \"reference\" in data &&\n data.reference !== null &&\n typeof data.reference === \"object\" &&\n Object.values(data.reference).every((value) => typeof value === \"string\")\n );\n }\n\n if (data.type === \"text\") {\n return \"text\" in data && typeof data.text === \"string\";\n }\n\n if (data.type === \"url\") {\n return \"url\" in data && typeof data.url === \"string\";\n }\n\n return false;\n}\n\nfunction hasDenseItems<T>(\n input: readonly unknown[],\n predicate: (value: unknown) => value is T\n): input is readonly T[] {\n for (let index = 0; index < input.length; index += 1) {\n if (!(index in input && predicate(input[index]))) {\n return false;\n }\n }\n\n return true;\n}\n\nfunction isString(value: unknown): value is string {\n return typeof value === \"string\";\n}\n"],"mappings":";AAQA,SAAgB,oBAAoB,OAA8B;CAChE,IAAI,OAAO,UAAU,UACnB,OAAO;EACL,MAAM;EACN,MAAM;CACR;CAGF,IAAI,mBAAmB,KAAK,GAC1B,OAAO;EACL,MAAM;EACN,MAAM,gBAAgB,KAAK;CAC7B;CAGF,IAAI,aAAa,KAAK,GAAG;EACvB,yBAAyB,KAAK;EAC9B,OAAO;GACL,MAAM;GACN,SAAS,gBAAgB,KAAK;EAChC;CACF;CAEA,IAAI,cAAc,KAAK,GAAG;EACxB,yBAAyB,MAAM,OAAO;EACtC,OAAO,gBAAgB,KAAK;CAC9B;CAEA,IAAI,WAAW,KAAK,GAClB,OAAO,gBAAgB,KAAK;CAG9B,MAAM,IAAI,UACR,kFACF;AACF;AAEA,SAAS,mBAAmB,OAA4C;CACtE,OAAO,MAAM,QAAQ,KAAK,KAAK,cAAc,OAAO,QAAQ;AAC9D;AAEA,SAAS,aACP,OACgE;CAChE,OAAO,MAAM,QAAQ,KAAK;AAC5B;AAEA,SAAS,cAAc,OAAyC;CAC9D,OACE,UAAU,QACV,OAAO,UAAU,YACjB,CAAC,aAAa,KAAK,KACnB,MAAM,SAAS,kBACf,aAAa,SACb,MAAM,QAAQ,MAAM,OAAO;AAE/B;AAEA,SAAS,WAAW,OAAsC;CACxD,OACE,UAAU,QACV,OAAO,UAAU,YACjB,CAAC,aAAa,KAAK,KACnB,MAAM,SAAS,gBACd,OAAO,MAAM,SAAS,YAAY,mBAAmB,MAAM,IAAI;AAEpE;AAEA,SAAS,yBACP,OACoD;CACpD,KAAK,MAAM,QAAQ,OACjB,IAAI,CAAC,yBAAyB,IAAI,GAChC,MAAM,IAAI,UACR,iIACF;AAGN;AAEA,SAAS,yBACP,MACgC;CAChC,IAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,EAAE,UAAU,OAC3D,OAAO;CAGT,IAAI,KAAK,SAAS,QAChB,OAAO,UAAU,QAAQ,OAAO,KAAK,SAAS;CAGhD,IAAI,KAAK,SAAS,SAChB,OACE,WAAW,QACX,OAAO,KAAK,UAAU,aACrB,EAAE,eAAe,SAAS,OAAO,KAAK,cAAc;CAIzD,IAAI,KAAK,SAAS,QAChB,OACE,UAAU,QACV,sBAAsB,KAAK,IAAI,KAC/B,eAAe,QACf,OAAO,KAAK,cAAc,aACzB,EAAE,cAAc,SAAS,OAAO,KAAK,aAAa;CAIvD,OAAO;AACT;AAEA,SAAS,sBAAsB,MAAwB;CACrD,IAAI,OAAO,SAAS,UAClB,OAAO;CAGT,IAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,EAAE,UAAU,OAC3D,OAAO;CAGT,IAAI,KAAK,SAAS,QAChB,OAAO,UAAU,QAAQ,OAAO,KAAK,SAAS;CAGhD,IAAI,KAAK,SAAS,aAChB,OACE,eAAe,QACf,KAAK,cAAc,QACnB,OAAO,KAAK,cAAc,YAC1B,OAAO,OAAO,KAAK,SAAS,EAAE,OAAO,UAAU,OAAO,UAAU,QAAQ;CAI5E,IAAI,KAAK,SAAS,QAChB,OAAO,UAAU,QAAQ,OAAO,KAAK,SAAS;CAGhD,IAAI,KAAK,SAAS,OAChB,OAAO,SAAS,QAAQ,OAAO,KAAK,QAAQ;CAG9C,OAAO;AACT;AAEA,SAAS,cACP,OACA,WACuB;CACvB,KAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS,GACjD,IAAI,EAAE,SAAS,SAAS,UAAU,MAAM,MAAM,IAC5C,OAAO;CAIX,OAAO;AACT;AAEA,SAAS,SAAS,OAAiC;CACjD,OAAO,OAAO,UAAU;AAC1B"}
@@ -0,0 +1 @@
1
+ export { };