@slashfi/agents-sdk 0.15.0 → 0.17.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 (93) hide show
  1. package/dist/agent-definitions/auth.d.ts.map +1 -1
  2. package/dist/agent-definitions/auth.js +44 -11
  3. package/dist/agent-definitions/auth.js.map +1 -1
  4. package/dist/agent-definitions/integrations.d.ts.map +1 -1
  5. package/dist/agent-definitions/integrations.js +106 -45
  6. package/dist/agent-definitions/integrations.js.map +1 -1
  7. package/dist/agent-definitions/remote-registry.d.ts.map +1 -1
  8. package/dist/agent-definitions/remote-registry.js +174 -45
  9. package/dist/agent-definitions/remote-registry.js.map +1 -1
  10. package/dist/agent-definitions/secrets.d.ts.map +1 -1
  11. package/dist/agent-definitions/secrets.js +1 -4
  12. package/dist/agent-definitions/secrets.js.map +1 -1
  13. package/dist/agent-definitions/users.d.ts.map +1 -1
  14. package/dist/agent-definitions/users.js +14 -3
  15. package/dist/agent-definitions/users.js.map +1 -1
  16. package/dist/define-config.d.ts +125 -0
  17. package/dist/define-config.d.ts.map +1 -0
  18. package/dist/define-config.js +75 -0
  19. package/dist/define-config.js.map +1 -0
  20. package/dist/define.d.ts +11 -2
  21. package/dist/define.d.ts.map +1 -1
  22. package/dist/define.js +57 -26
  23. package/dist/define.js.map +1 -1
  24. package/dist/events.d.ts +133 -0
  25. package/dist/events.d.ts.map +1 -0
  26. package/dist/events.js +57 -0
  27. package/dist/events.js.map +1 -0
  28. package/dist/index.d.ts +16 -8
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +9 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/integration-interface.d.ts +3 -3
  33. package/dist/integration-interface.d.ts.map +1 -1
  34. package/dist/integration-interface.js +29 -21
  35. package/dist/integration-interface.js.map +1 -1
  36. package/dist/integrations-store.d.ts +2 -2
  37. package/dist/integrations-store.d.ts.map +1 -1
  38. package/dist/integrations-store.js +3 -3
  39. package/dist/integrations-store.js.map +1 -1
  40. package/dist/jwt.d.ts.map +1 -1
  41. package/dist/jwt.js +7 -5
  42. package/dist/jwt.js.map +1 -1
  43. package/dist/key-manager.d.ts.map +1 -1
  44. package/dist/key-manager.js +5 -3
  45. package/dist/key-manager.js.map +1 -1
  46. package/dist/oidc-signin.d.ts +32 -0
  47. package/dist/oidc-signin.d.ts.map +1 -0
  48. package/dist/oidc-signin.js +138 -0
  49. package/dist/oidc-signin.js.map +1 -0
  50. package/dist/registry-consumer.d.ts +104 -0
  51. package/dist/registry-consumer.d.ts.map +1 -0
  52. package/dist/registry-consumer.js +230 -0
  53. package/dist/registry-consumer.js.map +1 -0
  54. package/dist/registry.d.ts +20 -1
  55. package/dist/registry.d.ts.map +1 -1
  56. package/dist/registry.js +167 -20
  57. package/dist/registry.js.map +1 -1
  58. package/dist/secret-collection.d.ts.map +1 -1
  59. package/dist/secret-collection.js.map +1 -1
  60. package/dist/server.d.ts +3 -0
  61. package/dist/server.d.ts.map +1 -1
  62. package/dist/server.js +222 -27
  63. package/dist/server.js.map +1 -1
  64. package/dist/test-utils/mock-oidc-server.d.ts +36 -0
  65. package/dist/test-utils/mock-oidc-server.d.ts.map +1 -0
  66. package/dist/test-utils/mock-oidc-server.js +96 -0
  67. package/dist/test-utils/mock-oidc-server.js.map +1 -0
  68. package/dist/types.d.ts +27 -0
  69. package/dist/types.d.ts.map +1 -1
  70. package/package.json +1 -1
  71. package/src/agent-definitions/auth.ts +106 -38
  72. package/src/agent-definitions/integrations.ts +201 -73
  73. package/src/agent-definitions/remote-registry.ts +262 -65
  74. package/src/agent-definitions/secrets.ts +22 -8
  75. package/src/agent-definitions/users.ts +16 -4
  76. package/src/consumer.test.ts +536 -0
  77. package/src/define-config.ts +205 -0
  78. package/src/define.ts +134 -46
  79. package/src/events.ts +237 -0
  80. package/src/index.ts +90 -8
  81. package/src/integration-interface.ts +52 -28
  82. package/src/integrations-store.ts +9 -5
  83. package/src/jwt.ts +48 -19
  84. package/src/key-manager.test.ts +22 -13
  85. package/src/key-manager.ts +8 -10
  86. package/src/oidc-signin.ts +223 -0
  87. package/src/registry-consumer.ts +413 -0
  88. package/src/registry.ts +237 -29
  89. package/src/secret-collection.ts +2 -1
  90. package/src/server.test.ts +304 -238
  91. package/src/server.ts +371 -69
  92. package/src/test-utils/mock-oidc-server.ts +123 -0
  93. package/src/types.ts +80 -18
@@ -0,0 +1,205 @@
1
+ /**
2
+ * defineConfig — Declarative configuration for agent consumers.
3
+ *
4
+ * A consumer's config declares which registries to connect to and which
5
+ * agent refs to use. This is the "package.json" of the agent world:
6
+ * registries are like npm registries, refs are like dependencies.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { defineConfig } from '@slashfi/agents-sdk';
11
+ *
12
+ * export default defineConfig({
13
+ * registries: [
14
+ * { url: 'https://registry.slash.com' },
15
+ * { url: 'https://twin.slash.com/tenants/slash', auth: { type: 'bearer' } },
16
+ * ],
17
+ * refs: [
18
+ * 'notion',
19
+ * { ref: 'postgres', as: 'prod-db', config: { url: 'https://twin.slash.com/secrets/crdb-url' } },
20
+ * { ref: 'postgres', as: 'staging', config: { url: 'https://twin.slash.com/secrets/staging-url' } },
21
+ * ],
22
+ * });
23
+ * ```
24
+ */
25
+
26
+ // ============================================
27
+ // Registry Config
28
+ // ============================================
29
+
30
+ /** Authentication methods for connecting to a registry */
31
+ export type RegistryAuth =
32
+ | { type: "none" }
33
+ | { type: "bearer"; token?: string; tokenUrl?: string }
34
+ | { type: "api-key"; key?: string; header?: string }
35
+ | { type: "jwt"; issuer?: string };
36
+
37
+ /** A registry endpoint the consumer connects to */
38
+ export interface RegistryEntry {
39
+ /** Registry URL (e.g., 'https://registry.slash.com') */
40
+ url: string;
41
+
42
+ /** How to authenticate with this registry */
43
+ auth?: RegistryAuth;
44
+
45
+ /** Human-readable name / alias for this registry */
46
+ name?: string;
47
+
48
+ /** Publisher name shown in the app store UI */
49
+ publisher?: string;
50
+ }
51
+
52
+ // ============================================
53
+ // Ref Config
54
+ // ============================================
55
+
56
+ /** Inline config for a ref — values can be literals or secret URLs */
57
+ export type RefConfig = Record<string, string | number | boolean>;
58
+
59
+ /** A ref can be a simple string or a full object */
60
+ export type RefEntry =
61
+ | string
62
+ | {
63
+ /** Agent definition path (resolved from registries) */
64
+ ref: string;
65
+
66
+ /** Direct URL to the agent (e.g. http://localhost:3000/agents/notion) */
67
+ url?: string;
68
+
69
+ /** Local alias for this instance (required for multi-instance) */
70
+ as?: string;
71
+
72
+ /** Per-instance config (secrets as URIs, literals as values) */
73
+ config?: RefConfig;
74
+
75
+ /** Override the registry to resolve from */
76
+ registry?: string;
77
+ };
78
+
79
+ // ============================================
80
+ // Consumer Config
81
+ // ============================================
82
+
83
+ /** The full consumer configuration */
84
+ export interface ConsumerConfig {
85
+ /** Registries to connect to, in resolution order */
86
+ registries?: (string | RegistryEntry)[];
87
+
88
+ /** Agent refs to use — your "dependencies" */
89
+ refs?: RefEntry[];
90
+
91
+ /** Optional metadata */
92
+ meta?: {
93
+ /** Config owner (user ID, tenant ID, etc.) */
94
+ owner?: string;
95
+ /** Human-readable description */
96
+ description?: string;
97
+ [key: string]: unknown;
98
+ };
99
+ }
100
+
101
+ // ============================================
102
+ // Resolved Config (indexed output)
103
+ // ============================================
104
+
105
+ /** A normalized registry entry (after resolution) */
106
+ export interface ResolvedRegistry {
107
+ url: string;
108
+ name: string;
109
+ publisher: string;
110
+ auth: RegistryAuth;
111
+ }
112
+
113
+ /** A normalized ref entry (after resolution) */
114
+ export interface ResolvedRef {
115
+ /** Original ref name from the definition */
116
+ ref: string;
117
+
118
+ /** Local name (alias or ref name) */
119
+ name: string;
120
+
121
+ /** Which registry this was resolved from */
122
+ registry: string;
123
+
124
+ /** Resolved config (secret URLs NOT resolved — kept as URLs) */
125
+ config: RefConfig;
126
+ }
127
+
128
+ /** The serialized/indexed output stored in VCS */
129
+ export interface ResolvedConfig {
130
+ /** Timestamp of resolution */
131
+ resolvedAt: string;
132
+
133
+ /** Source config hash (for cache invalidation) */
134
+ sourceHash: string;
135
+
136
+ /** Normalized registries */
137
+ registries: ResolvedRegistry[];
138
+
139
+ /** Normalized refs */
140
+ refs: ResolvedRef[];
141
+
142
+ /** Metadata */
143
+ meta?: ConsumerConfig["meta"];
144
+ }
145
+
146
+ // ============================================
147
+ // Helpers
148
+ // ============================================
149
+
150
+ /** Normalize a ref entry to its full form */
151
+ export function normalizeRef(entry: RefEntry): {
152
+ ref: string;
153
+ name: string;
154
+ config: RefConfig;
155
+ registry?: string;
156
+ } {
157
+ if (typeof entry === "string") {
158
+ return { ref: entry, name: entry, config: {} };
159
+ }
160
+ return {
161
+ ref: entry.ref,
162
+ name: entry.as ?? entry.ref,
163
+ config: entry.config ?? {},
164
+ registry: entry.registry,
165
+ };
166
+ }
167
+
168
+ /** Normalize a registry entry to its full form */
169
+ export function normalizeRegistry(
170
+ entry: string | RegistryEntry,
171
+ ): ResolvedRegistry {
172
+ if (typeof entry === "string") {
173
+ const url = new URL(entry);
174
+ return {
175
+ url: entry,
176
+ name: url.hostname,
177
+ publisher: url.hostname.split(".")[0],
178
+ auth: { type: "none" },
179
+ };
180
+ }
181
+ const url = new URL(entry.url);
182
+ return {
183
+ url: entry.url,
184
+ name: entry.name ?? url.hostname,
185
+ publisher: entry.publisher ?? url.hostname.split(".")[0],
186
+ auth: entry.auth ?? { type: "none" },
187
+ };
188
+ }
189
+
190
+ /** Supported secret URI schemes */
191
+ const SECRET_SCHEMES = ["file:", "https:", "http:", "env:"];
192
+
193
+ /** Check if a value is a secret URI (file://, https://, env://) */
194
+ export function isSecretUri(value: unknown): boolean {
195
+ if (typeof value !== "string") return false;
196
+ try {
197
+ const url = new URL(value);
198
+ return SECRET_SCHEMES.includes(url.protocol);
199
+ } catch {
200
+ return false;
201
+ }
202
+ }
203
+
204
+ /** @deprecated Use isSecretUri instead */
205
+ export const isSecretUrl = isSecretUri;
package/src/define.ts CHANGED
@@ -4,12 +4,14 @@
4
4
  * Factory functions for creating agent and tool definitions.
5
5
  */
6
6
 
7
+ import type { EventCallback, EventType } from "./events.js";
7
8
  import type {
8
- IntegrationHooks,
9
9
  AgentConfig,
10
10
  AgentDefinition,
11
11
  AgentRuntime,
12
+ IntegrationHooks,
12
13
  JsonSchema,
14
+ ListenerEntry,
13
15
  ToolContext,
14
16
  ToolDefinition,
15
17
  Visibility,
@@ -65,14 +67,37 @@ export interface DefineToolOptions<
65
67
  * });
66
68
  * ```
67
69
  */
70
+ /** A ToolDefinition with .on() chaining support */
71
+ export type ToolWithHooks<
72
+ TContext extends ToolContext = ToolContext,
73
+ TInput = unknown,
74
+ TOutput = unknown,
75
+ > = ToolDefinition<TContext, TInput, TOutput> & {
76
+ on<T extends EventType>(
77
+ eventType: T,
78
+ callback: EventCallback<T>,
79
+ ): ToolWithHooks<TContext, TInput, TOutput>;
80
+ };
81
+
82
+ /** An AgentDefinition with .on() chaining support */
83
+ export type AgentWithHooks<TContext extends ToolContext = ToolContext> =
84
+ AgentDefinition<TContext> & {
85
+ on<T extends EventType>(
86
+ eventType: T,
87
+ callback: EventCallback<T>,
88
+ ): AgentWithHooks<TContext>;
89
+ };
90
+
68
91
  export function defineTool<
69
92
  TContext extends ToolContext = ToolContext,
70
93
  TInput = unknown,
71
94
  TOutput = unknown,
72
95
  >(
73
96
  options: DefineToolOptions<TContext, TInput, TOutput>,
74
- ): ToolDefinition<TContext, TInput, TOutput> {
75
- return {
97
+ ): ToolWithHooks<TContext, TInput, TOutput> {
98
+ const listeners: ListenerEntry[] = [];
99
+
100
+ const def: ToolWithHooks<TContext, TInput, TOutput> = {
76
101
  name: options.name,
77
102
  description: options.description,
78
103
  inputSchema: options.inputSchema,
@@ -80,7 +105,17 @@ export function defineTool<
80
105
  visibility: options.visibility,
81
106
  allowedCallers: options.allowedCallers,
82
107
  execute: options.execute,
108
+ _listeners: listeners,
109
+ on<T extends EventType>(eventType: T, callback: EventCallback<T>) {
110
+ listeners.push({
111
+ eventType,
112
+ callback: callback as EventCallback<EventType>,
113
+ });
114
+ return def;
115
+ },
83
116
  };
117
+
118
+ return def;
84
119
  }
85
120
 
86
121
  // ============================================
@@ -179,68 +214,111 @@ export function defineAgent<TContext extends ToolContext = ToolContext>(
179
214
 
180
215
  if (h.setup) {
181
216
  const fn = h.setup;
182
- tools.push(defineTool({
183
- name: "setup_integration",
184
- description: `Set up ${h.displayName} integration.`,
185
- visibility: "public" as const,
186
- inputSchema: { type: "object" as const, properties: { url: { type: "string" }, name: { type: "string" }, config: { type: "object" } } },
187
- execute: (input: any, ctx: any) => fn(input, ctx),
188
- }) as any);
217
+ tools.push(
218
+ defineTool({
219
+ name: "setup_integration",
220
+ description: `Set up ${h.displayName} integration.`,
221
+ visibility: "public" as const,
222
+ inputSchema: {
223
+ type: "object" as const,
224
+ properties: {
225
+ url: { type: "string" },
226
+ name: { type: "string" },
227
+ config: { type: "object" },
228
+ },
229
+ },
230
+ execute: (input: any, ctx: any) => fn(input, ctx),
231
+ }) as any,
232
+ );
189
233
  }
190
234
  if (h.connect) {
191
235
  const fn = h.connect;
192
- tools.push(defineTool({
193
- name: "connect_integration",
194
- description: `Connect a user to ${h.displayName}.`,
195
- visibility: "public" as const,
196
- inputSchema: { type: "object" as const, properties: { registryId: { type: "string" }, oidcUserId: { type: "string" }, redirectUri: { type: "string" } }, required: ["registryId"] as const },
197
- execute: (input: any, ctx: any) => fn(input, ctx),
198
- }) as any);
236
+ tools.push(
237
+ defineTool({
238
+ name: "connect_integration",
239
+ description: `Connect a user to ${h.displayName}.`,
240
+ visibility: "public" as const,
241
+ inputSchema: {
242
+ type: "object" as const,
243
+ properties: {
244
+ registryId: { type: "string" },
245
+ oidcUserId: { type: "string" },
246
+ redirectUri: { type: "string" },
247
+ },
248
+ required: ["registryId"] as const,
249
+ },
250
+ execute: (input: any, ctx: any) => fn(input, ctx),
251
+ }) as any,
252
+ );
199
253
  }
200
254
  if (h.discover) {
201
255
  const fn = h.discover;
202
- tools.push(defineTool({
203
- name: "discover_integrations",
204
- description: `Discover available ${h.displayName} instances.`,
205
- visibility: "public" as const,
206
- inputSchema: { type: "object" as const, properties: { url: { type: "string" } } },
207
- execute: (input: any, ctx: any) => fn(input, ctx),
208
- }) as any);
256
+ tools.push(
257
+ defineTool({
258
+ name: "discover_integrations",
259
+ description: `Discover available ${h.displayName} instances.`,
260
+ visibility: "public" as const,
261
+ inputSchema: {
262
+ type: "object" as const,
263
+ properties: { url: { type: "string" } },
264
+ },
265
+ execute: (input: any, ctx: any) => fn(input, ctx),
266
+ }) as any,
267
+ );
209
268
  }
210
269
  if (h.list) {
211
270
  const fn = h.list;
212
- tools.push(defineTool({
213
- name: "list_integrations",
214
- description: `List connected ${h.displayName} instances.`,
215
- visibility: "public" as const,
216
- inputSchema: { type: "object" as const, properties: {} },
217
- execute: (input: any, ctx: any) => fn(input, ctx),
218
- }) as any);
271
+ tools.push(
272
+ defineTool({
273
+ name: "list_integrations",
274
+ description: `List connected ${h.displayName} instances.`,
275
+ visibility: "public" as const,
276
+ inputSchema: { type: "object" as const, properties: {} },
277
+ execute: (input: any, ctx: any) => fn(input, ctx),
278
+ }) as any,
279
+ );
219
280
  }
220
281
  if (h.get) {
221
282
  const fn = h.get;
222
- tools.push(defineTool({
223
- name: "get_integration",
224
- description: `Get details of a ${h.displayName} instance.`,
225
- visibility: "public" as const,
226
- inputSchema: { type: "object" as const, properties: { registryId: { type: "string" } }, required: ["registryId"] as const },
227
- execute: (input: any, ctx: any) => fn(input, ctx),
228
- }) as any);
283
+ tools.push(
284
+ defineTool({
285
+ name: "get_integration",
286
+ description: `Get details of a ${h.displayName} instance.`,
287
+ visibility: "public" as const,
288
+ inputSchema: {
289
+ type: "object" as const,
290
+ properties: { registryId: { type: "string" } },
291
+ required: ["registryId"] as const,
292
+ },
293
+ execute: (input: any, ctx: any) => fn(input, ctx),
294
+ }) as any,
295
+ );
229
296
  }
230
297
  if (h.update) {
231
298
  const fn = h.update;
232
- tools.push(defineTool({
233
- name: "update_integration",
234
- description: `Update a ${h.displayName} instance.`,
235
- visibility: "public" as const,
236
- inputSchema: { type: "object" as const, properties: { registryId: { type: "string" }, name: { type: "string" }, url: { type: "string" } }, required: ["registryId"] as const },
237
- execute: (input: any, ctx: any) => fn(input, ctx),
238
- }) as any);
299
+ tools.push(
300
+ defineTool({
301
+ name: "update_integration",
302
+ description: `Update a ${h.displayName} instance.`,
303
+ visibility: "public" as const,
304
+ inputSchema: {
305
+ type: "object" as const,
306
+ properties: {
307
+ registryId: { type: "string" },
308
+ name: { type: "string" },
309
+ url: { type: "string" },
310
+ },
311
+ required: ["registryId"] as const,
312
+ },
313
+ execute: (input: any, ctx: any) => fn(input, ctx),
314
+ }) as any,
315
+ );
239
316
  }
240
317
  }
241
318
 
319
+ const agentListeners: ListenerEntry[] = [];
242
320
 
243
- return {
321
+ const def: AgentWithHooks<TContext> = {
244
322
  path: options.path,
245
323
  entrypoint: options.entrypoint,
246
324
  config,
@@ -249,5 +327,15 @@ export function defineAgent<TContext extends ToolContext = ToolContext>(
249
327
  visibility: options.visibility,
250
328
  allowedCallers: options.allowedCallers,
251
329
  loadListeners: options.loadListeners,
330
+ _listeners: agentListeners,
331
+ on<T extends EventType>(eventType: T, callback: EventCallback<T>) {
332
+ agentListeners.push({
333
+ eventType,
334
+ callback: callback as EventCallback<EventType>,
335
+ });
336
+ return def;
337
+ },
252
338
  };
339
+
340
+ return def;
253
341
  }
package/src/events.ts ADDED
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Event Bus — Generic event system for the agents-sdk.
3
+ *
4
+ * Three scopes, one bus:
5
+ * registry.on(event, cb) — global, all agents
6
+ * agent.on(event, cb) — scoped to one agent
7
+ * tool.on(event, cb) — scoped to one tool
8
+ *
9
+ * All are sugar for the same underlying EventBus.
10
+ * Filtering happens in the callback, not the API.
11
+ */
12
+
13
+ // =============================================================================
14
+ // Event Types
15
+ // =============================================================================
16
+
17
+ /**
18
+ * All supported event types.
19
+ */
20
+ export type EventType =
21
+ | "tool/call"
22
+ | "tool/result"
23
+ | "tool/error"
24
+ | "step"
25
+ | "invoke";
26
+
27
+ /**
28
+ * Base event shape — every event has these fields.
29
+ */
30
+ export interface BaseEvent {
31
+ /** Event type */
32
+ type: EventType;
33
+ /** Agent path (e.g., '/agents/atlas-slack') */
34
+ agentPath: string;
35
+ /** Timestamp */
36
+ timestamp: number;
37
+ }
38
+
39
+ /**
40
+ * Event emitted before a tool executes.
41
+ */
42
+ export interface ToolCallEvent extends BaseEvent {
43
+ type: "tool/call";
44
+ /** Tool name */
45
+ tool: string;
46
+ /** Input parameters */
47
+ params: unknown;
48
+ }
49
+
50
+ /**
51
+ * Event emitted after a tool succeeds.
52
+ */
53
+ export interface ToolResultEvent extends BaseEvent {
54
+ type: "tool/result";
55
+ /** Tool name */
56
+ tool: string;
57
+ /** Input parameters */
58
+ params: unknown;
59
+ /** Tool result */
60
+ result: unknown;
61
+ /** Execution duration in ms */
62
+ durationMs: number;
63
+ }
64
+
65
+ /**
66
+ * Event emitted when a tool throws.
67
+ */
68
+ export interface ToolErrorEvent extends BaseEvent {
69
+ type: "tool/error";
70
+ /** Tool name */
71
+ tool: string;
72
+ /** Input parameters */
73
+ params: unknown;
74
+ /** The error */
75
+ error: unknown;
76
+ /** Execution duration in ms */
77
+ durationMs: number;
78
+ }
79
+
80
+ /**
81
+ * Event emitted when a step finishes.
82
+ */
83
+ export interface StepEvent extends BaseEvent {
84
+ type: "step";
85
+ /** Branch ID */
86
+ branchId: string;
87
+ /** Step outcome */
88
+ stepResult: "continue" | "stop";
89
+ /** Tools called in this step */
90
+ toolNames?: string[];
91
+ }
92
+
93
+ /**
94
+ * Event emitted when an agent is invoked.
95
+ */
96
+ export interface InvokeEvent extends BaseEvent {
97
+ type: "invoke";
98
+ /** The prompt */
99
+ prompt: string;
100
+ /** Session/branch ID */
101
+ sessionId?: string;
102
+ }
103
+
104
+ /**
105
+ * Union of all event types.
106
+ */
107
+ export type AgentEvent =
108
+ | ToolCallEvent
109
+ | ToolResultEvent
110
+ | ToolErrorEvent
111
+ | StepEvent
112
+ | InvokeEvent;
113
+
114
+ /**
115
+ * Map from event type string to event interface.
116
+ */
117
+ export interface EventMap {
118
+ "tool/call": ToolCallEvent;
119
+ "tool/result": ToolResultEvent;
120
+ "tool/error": ToolErrorEvent;
121
+ step: StepEvent;
122
+ invoke: InvokeEvent;
123
+ }
124
+
125
+ /**
126
+ * Callback for a specific event type.
127
+ */
128
+ export type EventCallback<T extends EventType = EventType> = (
129
+ event: EventMap[T],
130
+ ) => void | Promise<void>;
131
+
132
+ // =============================================================================
133
+ // Event Bus
134
+ // =============================================================================
135
+
136
+ /**
137
+ * Listener entry — callback + optional scope for agent/tool filtering.
138
+ */
139
+ interface ListenerEntry {
140
+ eventType: EventType;
141
+ callback: EventCallback<EventType>;
142
+ /** If set, only fire for events matching this agent path */
143
+ agentScope?: string;
144
+ /** If set, only fire for tool events matching this tool name */
145
+ toolScope?: string;
146
+ }
147
+
148
+ export interface EventBus {
149
+ /**
150
+ * Register a listener for an event type.
151
+ *
152
+ * @example
153
+ * ```ts
154
+ * bus.on('tool/result', (event) => { ... })
155
+ * ```
156
+ */
157
+ on<T extends EventType>(eventType: T, callback: EventCallback<T>): void;
158
+
159
+ /**
160
+ * Emit an event to all matching listeners.
161
+ * Listeners are called in registration order.
162
+ * Errors in listeners are caught and logged, never propagated.
163
+ */
164
+ emit(event: AgentEvent): Promise<void>;
165
+
166
+ /**
167
+ * Register a scoped listener (used internally by agent.on / tool.on).
168
+ */
169
+ _onScoped<T extends EventType>(
170
+ eventType: T,
171
+ callback: EventCallback<T>,
172
+ scope: { agentPath?: string; toolName?: string },
173
+ ): void;
174
+ }
175
+
176
+ /**
177
+ * Create an event bus.
178
+ */
179
+ export function createEventBus(): EventBus {
180
+ const listeners: ListenerEntry[] = [];
181
+
182
+ function on<T extends EventType>(
183
+ eventType: T,
184
+ callback: EventCallback<T>,
185
+ ): void {
186
+ listeners.push({
187
+ eventType,
188
+ callback: callback as EventCallback<EventType>,
189
+ });
190
+ }
191
+
192
+ function _onScoped<T extends EventType>(
193
+ eventType: T,
194
+ callback: EventCallback<T>,
195
+ scope: { agentPath?: string; toolName?: string },
196
+ ): void {
197
+ listeners.push({
198
+ eventType,
199
+ callback: callback as EventCallback<EventType>,
200
+ agentScope: scope.agentPath,
201
+ toolScope: scope.toolName,
202
+ });
203
+ }
204
+
205
+ async function emit(event: AgentEvent): Promise<void> {
206
+ for (const listener of listeners) {
207
+ // Match event type
208
+ if (listener.eventType !== event.type) continue;
209
+
210
+ // Match agent scope
211
+ if (listener.agentScope && listener.agentScope !== event.agentPath) {
212
+ continue;
213
+ }
214
+
215
+ // Match tool scope (only for tool:* events)
216
+ if (
217
+ listener.toolScope &&
218
+ "tool" in event &&
219
+ listener.toolScope !== event.tool
220
+ ) {
221
+ continue;
222
+ }
223
+
224
+ try {
225
+ await listener.callback(event);
226
+ } catch (err) {
227
+ // Never propagate listener errors — log and continue
228
+ console.error(
229
+ `[agents-sdk] Event listener error for ${event.type}:`,
230
+ err,
231
+ );
232
+ }
233
+ }
234
+ }
235
+
236
+ return { on, emit, _onScoped };
237
+ }