@toolpack-sdk/agents 2.0.0-alpha.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.
- package/README.md +827 -0
- package/dist/base-agent-CjrUlo6Y.d.cts +189 -0
- package/dist/base-agent-Cx2kWzLF.d.ts +189 -0
- package/dist/capabilities/index.cjs +17 -0
- package/dist/capabilities/index.d.cts +74 -0
- package/dist/capabilities/index.d.ts +74 -0
- package/dist/capabilities/index.js +17 -0
- package/dist/channels/index.cjs +3 -0
- package/dist/channels/index.d.cts +616 -0
- package/dist/channels/index.d.ts +616 -0
- package/dist/channels/index.js +3 -0
- package/dist/index.cjs +20 -0
- package/dist/index.d.cts +334 -0
- package/dist/index.d.ts +334 -0
- package/dist/index.js +20 -0
- package/dist/intent-classifier-agent-BLXXcbNJ.d.cts +45 -0
- package/dist/intent-classifier-agent-BLpDwKVf.d.ts +45 -0
- package/dist/interceptors/index.cjs +1 -0
- package/dist/interceptors/index.d.cts +539 -0
- package/dist/interceptors/index.d.ts +539 -0
- package/dist/interceptors/index.js +1 -0
- package/dist/registry/index.cjs +1 -0
- package/dist/registry/index.d.cts +159 -0
- package/dist/registry/index.d.ts +159 -0
- package/dist/registry/index.js +1 -0
- package/dist/testing/index.cjs +3 -0
- package/dist/testing/index.d.cts +389 -0
- package/dist/testing/index.d.ts +389 -0
- package/dist/testing/index.js +3 -0
- package/dist/types-BWoRx1ZE.d.cts +395 -0
- package/dist/types-BWoRx1ZE.d.ts +395 -0
- package/package.json +121 -0
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
import { A as AgentInput, h as InterceptorResult, I as Interceptor, e as AgentInstance, C as ChannelInterface, b as IAgentRegistry, f as InterceptorChainConfig, a as AgentResult } from '../types-BWoRx1ZE.js';
|
|
2
|
+
export { g as InterceptorContext, N as NextFunction, S as SKIP_SENTINEL, i as isSkipSentinel, s as skip } from '../types-BWoRx1ZE.js';
|
|
3
|
+
import { Participant, ConversationStore, ConversationScope, StoredMessage } from 'toolpack-sdk';
|
|
4
|
+
import { I as IntentClassification } from '../intent-classifier-agent-BLpDwKVf.js';
|
|
5
|
+
import 'events';
|
|
6
|
+
import '../base-agent-Cx2kWzLF.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Composed chain of interceptors ready to execute.
|
|
10
|
+
*/
|
|
11
|
+
interface ComposedChain {
|
|
12
|
+
/** Execute the chain with the given input */
|
|
13
|
+
execute(input: AgentInput): Promise<InterceptorResult>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Error thrown when invocation depth exceeds the configured maximum.
|
|
17
|
+
*/
|
|
18
|
+
declare class InvocationDepthExceededError extends Error {
|
|
19
|
+
constructor(currentDepth: number, maxDepth: number);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Compose an array of interceptors into an executable chain.
|
|
23
|
+
*
|
|
24
|
+
* The first interceptor in the array is outermost (runs first on way in,
|
|
25
|
+
* last on way out). The final handler invokes the agent directly.
|
|
26
|
+
*
|
|
27
|
+
* @param interceptors Ordered array of interceptors (empty = direct agent call)
|
|
28
|
+
* @param agent The agent to invoke at the end of the chain
|
|
29
|
+
* @param channel The triggering channel
|
|
30
|
+
* @param registry The agent registry
|
|
31
|
+
* @param config Chain configuration
|
|
32
|
+
* @returns Composed chain ready to execute
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* const chain = composeChain(
|
|
37
|
+
* [eventDedup, noiseFilter, intentClassifier],
|
|
38
|
+
* agent,
|
|
39
|
+
* channel,
|
|
40
|
+
* registry,
|
|
41
|
+
* { maxInvocationDepth: 5 }
|
|
42
|
+
* );
|
|
43
|
+
* const result = await chain.execute(input);
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
declare function composeChain(interceptors: Interceptor[], agent: AgentInstance, channel: ChannelInterface, registry: IAgentRegistry | null, config?: InterceptorChainConfig): ComposedChain;
|
|
47
|
+
/**
|
|
48
|
+
* Execute a chain with the given input, handling the skip sentinel.
|
|
49
|
+
*
|
|
50
|
+
* Returns `null` if the chain was skipped (caller should not send to channel),
|
|
51
|
+
* otherwise returns the AgentResult.
|
|
52
|
+
*
|
|
53
|
+
* @param chain The composed chain
|
|
54
|
+
* @param input The agent input
|
|
55
|
+
* @returns AgentResult or null if skipped
|
|
56
|
+
*/
|
|
57
|
+
declare function executeChain(chain: ComposedChain, input: AgentInput): Promise<AgentResult | null>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Configuration for the event deduplication interceptor.
|
|
61
|
+
*/
|
|
62
|
+
interface EventDedupConfig {
|
|
63
|
+
/** Maximum number of event IDs to cache (default: 1000) */
|
|
64
|
+
maxCacheSize?: number;
|
|
65
|
+
/** Function to extract event ID from input. Defaults to input.conversationId */
|
|
66
|
+
getEventId?: (input: AgentInput) => string | undefined;
|
|
67
|
+
/** Optional callback when duplicate is detected */
|
|
68
|
+
onDuplicate?: (eventId: string, input: AgentInput) => void;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Creates an event deduplication interceptor.
|
|
72
|
+
*
|
|
73
|
+
* Drops duplicate events based on event ID (e.g., Slack retries, webhook redeliveries).
|
|
74
|
+
* Uses an LRU cache to track recently seen event IDs.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* const registry = new AgentRegistry([
|
|
79
|
+
* {
|
|
80
|
+
* agent: MyAgent,
|
|
81
|
+
* channels: [slackChannel],
|
|
82
|
+
* interceptors: [
|
|
83
|
+
* createEventDedupInterceptor({ maxCacheSize: 500 })
|
|
84
|
+
* ]
|
|
85
|
+
* }
|
|
86
|
+
* ]);
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
declare function createEventDedupInterceptor(config?: EventDedupConfig): Interceptor;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Configuration for the noise filter interceptor.
|
|
93
|
+
*/
|
|
94
|
+
interface NoiseFilterConfig {
|
|
95
|
+
/** List of subtypes to drop (e.g., ['message_changed', 'message_deleted']) */
|
|
96
|
+
denySubtypes: string[];
|
|
97
|
+
/** Optional function to extract subtype from input */
|
|
98
|
+
getSubtype?: (input: AgentInput) => string | undefined;
|
|
99
|
+
/** Optional callback when noise is filtered */
|
|
100
|
+
onFiltered?: (subtype: string, input: AgentInput) => void;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Creates a noise filter interceptor.
|
|
104
|
+
*
|
|
105
|
+
* Drops messages whose subtype is in the deny-list.
|
|
106
|
+
* Useful for filtering out message edits, deletions, bot messages, etc.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* const registry = new AgentRegistry([
|
|
111
|
+
* {
|
|
112
|
+
* agent: MyAgent,
|
|
113
|
+
* channels: [slackChannel],
|
|
114
|
+
* interceptors: [
|
|
115
|
+
* createNoiseFilterInterceptor({
|
|
116
|
+
* denySubtypes: ['message_changed', 'message_deleted', 'bot_message']
|
|
117
|
+
* })
|
|
118
|
+
* ]
|
|
119
|
+
* }
|
|
120
|
+
* ]);
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
declare function createNoiseFilterInterceptor(config: NoiseFilterConfig): Interceptor;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Configuration for the self filter interceptor.
|
|
127
|
+
*/
|
|
128
|
+
interface SelfFilterConfig {
|
|
129
|
+
/**
|
|
130
|
+
* Platform-specific agent ID (e.g., Slack user ID "U123456").
|
|
131
|
+
* If not provided, falls back to the agent's registered name.
|
|
132
|
+
*/
|
|
133
|
+
agentId?: string;
|
|
134
|
+
/** Function to extract sender ID from input */
|
|
135
|
+
getSenderId: (input: AgentInput) => string | undefined;
|
|
136
|
+
/** Optional callback when self-message is detected */
|
|
137
|
+
onSelfMessage?: (senderId: string, input: AgentInput) => void;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Creates a self filter interceptor (loop guard).
|
|
141
|
+
*
|
|
142
|
+
* Drops messages where the sender ID equals the agent's own ID.
|
|
143
|
+
* Prevents infinite loops where the agent responds to its own messages.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* const registry = new AgentRegistry([
|
|
148
|
+
* {
|
|
149
|
+
* agent: MyAgent,
|
|
150
|
+
* channels: [slackChannel],
|
|
151
|
+
* interceptors: [
|
|
152
|
+
* createSelfFilterInterceptor({
|
|
153
|
+
* agentId: 'U0123456', // Slack bot user ID
|
|
154
|
+
* getSenderId: (input) => input.context?.senderId as string
|
|
155
|
+
* })
|
|
156
|
+
* ]
|
|
157
|
+
* }
|
|
158
|
+
* ]);
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
declare function createSelfFilterInterceptor(config: SelfFilterConfig): Interceptor;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Configuration for the rate limit interceptor.
|
|
165
|
+
*/
|
|
166
|
+
interface RateLimitConfig {
|
|
167
|
+
/** Tokens per interval (default: 10) */
|
|
168
|
+
tokensPerInterval?: number;
|
|
169
|
+
/** Interval in milliseconds (default: 60000 = 1 minute) */
|
|
170
|
+
interval?: number;
|
|
171
|
+
/** Maximum number of buckets to cache (default: 1000). LRU eviction when exceeded. */
|
|
172
|
+
maxBuckets?: number;
|
|
173
|
+
/** Function to extract rate limit key from input (e.g., user ID, conversation ID) */
|
|
174
|
+
getKey: (input: AgentInput) => string;
|
|
175
|
+
/** Behavior when rate limit exceeded: 'skip' silently or 'reject' with error (default: 'skip') */
|
|
176
|
+
onExceeded?: 'skip' | 'reject';
|
|
177
|
+
/** Optional callback when rate limit is hit */
|
|
178
|
+
onRateLimited?: (key: string, input: AgentInput) => void;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Creates a rate limit interceptor.
|
|
182
|
+
*
|
|
183
|
+
* Token-bucket rate limiting per user or conversation.
|
|
184
|
+
* Skips or rejects when rate exceeded.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```ts
|
|
188
|
+
* const registry = new AgentRegistry([
|
|
189
|
+
* {
|
|
190
|
+
* agent: MyAgent,
|
|
191
|
+
* channels: [slackChannel],
|
|
192
|
+
* interceptors: [
|
|
193
|
+
* createRateLimitInterceptor({
|
|
194
|
+
* getKey: (input) => input.context?.userId as string || 'default',
|
|
195
|
+
* tokensPerInterval: 5,
|
|
196
|
+
* interval: 60000, // 5 messages per minute per user
|
|
197
|
+
* onExceeded: 'skip'
|
|
198
|
+
* })
|
|
199
|
+
* ]
|
|
200
|
+
* }
|
|
201
|
+
* ]);
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
declare function createRateLimitInterceptor(config: RateLimitConfig): Interceptor;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Configuration for the participant resolver interceptor.
|
|
208
|
+
*/
|
|
209
|
+
interface ParticipantResolverConfig {
|
|
210
|
+
/**
|
|
211
|
+
* Explicit resolver function. Takes precedence over the channel's
|
|
212
|
+
* `resolveParticipant` hook when provided.
|
|
213
|
+
*
|
|
214
|
+
* If omitted, the interceptor will call `ctx.channel.resolveParticipant`
|
|
215
|
+
* if the channel defines one. If neither is available, the input's
|
|
216
|
+
* existing `participant` field (populated by `channel.normalize()`) is
|
|
217
|
+
* passed through unchanged.
|
|
218
|
+
*/
|
|
219
|
+
resolveParticipant?: (input: AgentInput) => Participant | undefined | Promise<Participant | undefined>;
|
|
220
|
+
/**
|
|
221
|
+
* Optional callback fired after a participant is resolved (from any
|
|
222
|
+
* source, including `channel.normalize()`).
|
|
223
|
+
*/
|
|
224
|
+
onResolved?: (input: AgentInput, participant: Participant) => void;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Creates a participant resolver interceptor.
|
|
228
|
+
*
|
|
229
|
+
* Resolves the participant from input and enriches the input with participant
|
|
230
|
+
* information for downstream interceptors. This is a foundational interceptor
|
|
231
|
+
* that should typically be placed early in the chain so downstream interceptors
|
|
232
|
+
* have access to participant context.
|
|
233
|
+
*
|
|
234
|
+
* Note: This interceptor does NOT write to conversation history. It only
|
|
235
|
+
* enriches the input with participant metadata. History persistence must be
|
|
236
|
+
* handled separately by the application layer or a future history interceptor.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```ts
|
|
240
|
+
* const registry = new AgentRegistry([
|
|
241
|
+
* {
|
|
242
|
+
* agent: MyAgent,
|
|
243
|
+
* channels: [slackChannel],
|
|
244
|
+
* interceptors: [
|
|
245
|
+
* createParticipantResolverInterceptor({
|
|
246
|
+
* resolveParticipant: (input) => ({
|
|
247
|
+
* kind: 'user',
|
|
248
|
+
* id: input.context?.userId as string,
|
|
249
|
+
* displayName: input.context?.userName as string
|
|
250
|
+
* }),
|
|
251
|
+
* onResolved: (input, participant) => {
|
|
252
|
+
* // Optionally persist to your own history store
|
|
253
|
+
* historyStore.append({ participant, message: input.message });
|
|
254
|
+
* }
|
|
255
|
+
* })
|
|
256
|
+
* ]
|
|
257
|
+
* }
|
|
258
|
+
* ]);
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
declare function createParticipantResolverInterceptor(config?: ParticipantResolverConfig): Interceptor;
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Configuration for the capture-history interceptor.
|
|
265
|
+
*/
|
|
266
|
+
interface CaptureHistoryConfig {
|
|
267
|
+
/**
|
|
268
|
+
* The conversation store to write messages into.
|
|
269
|
+
* Typically `new InMemoryConversationStore()` for single-process deployments,
|
|
270
|
+
* or a database-backed adapter for production.
|
|
271
|
+
*/
|
|
272
|
+
store: ConversationStore;
|
|
273
|
+
/**
|
|
274
|
+
* Derive the scope of an incoming message.
|
|
275
|
+
* Default: reads `input.context?.channelType` — `'im'` → `'dm'`,
|
|
276
|
+
* presence of `context?.threadId` → `'thread'`, otherwise `'channel'`.
|
|
277
|
+
*/
|
|
278
|
+
getScope?: (input: AgentInput) => ConversationScope;
|
|
279
|
+
/**
|
|
280
|
+
* Derive a stable message id for dedup.
|
|
281
|
+
* Default: `input.context?.messageId ?? input.context?.eventId ?? randomUUID()`.
|
|
282
|
+
* Supply this if your channel puts the platform message id somewhere else.
|
|
283
|
+
*/
|
|
284
|
+
getMessageId?: (input: AgentInput) => string;
|
|
285
|
+
/**
|
|
286
|
+
* Derive explicit @-mention ids from the message for addressed-only filtering.
|
|
287
|
+
* Default: `(input.context?.mentions as string[] | undefined) ?? []`.
|
|
288
|
+
*/
|
|
289
|
+
getMentions?: (input: AgentInput) => string[];
|
|
290
|
+
/**
|
|
291
|
+
* Called after a message is successfully written to the store.
|
|
292
|
+
* Useful for metrics or debug logging.
|
|
293
|
+
*/
|
|
294
|
+
onCaptured?: (message: StoredMessage) => void;
|
|
295
|
+
/**
|
|
296
|
+
* When true, the interceptor also writes the agent's reply to the store
|
|
297
|
+
* as a `kind: 'agent'` turn after `next()` returns.
|
|
298
|
+
* Default: true.
|
|
299
|
+
*/
|
|
300
|
+
captureAgentReplies?: boolean;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Creates a capture-history interceptor.
|
|
304
|
+
*
|
|
305
|
+
* **Purpose:** The capture stage runs for *every* allowed inbound message,
|
|
306
|
+
* regardless of whether the agent ends up replying. It writes the message to
|
|
307
|
+
* the `ConversationStore` so it is available as future context for the assembler.
|
|
308
|
+
*
|
|
309
|
+
* The interceptor wraps `next()`:
|
|
310
|
+
* 1. Before calling downstream: write the inbound user message.
|
|
311
|
+
* 2. After `next()` resolves (and the result is not a skip sentinel): write the
|
|
312
|
+
* agent's reply as a `kind: 'agent'` turn. This keeps the reply in the log
|
|
313
|
+
* automatically without any changes to agent code.
|
|
314
|
+
*
|
|
315
|
+
* **Placement:** Put this interceptor *after* `createParticipantResolverInterceptor`
|
|
316
|
+
* (so `input.participant` is already enriched) and *before*
|
|
317
|
+
* `createAddressCheckInterceptor` (so even ignored messages are captured).
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* ```ts
|
|
321
|
+
* const store = new InMemoryConversationStore();
|
|
322
|
+
*
|
|
323
|
+
* interceptors: [
|
|
324
|
+
* createParticipantResolverInterceptor(),
|
|
325
|
+
* createCaptureInterceptor({ store }), // ← before address-check
|
|
326
|
+
* createAddressCheckInterceptor({ agentName: 'kael', ... }),
|
|
327
|
+
* createIntentClassifierInterceptor({ ... }),
|
|
328
|
+
* ]
|
|
329
|
+
* ```
|
|
330
|
+
*/
|
|
331
|
+
/**
|
|
332
|
+
* Symbol stamped onto every interceptor function returned by `createCaptureInterceptor`.
|
|
333
|
+
* Used by `BaseAgent._bindChannel` to detect whether a capture interceptor has already
|
|
334
|
+
* been wired — preventing the auto-inserted one from duplicating an explicit one.
|
|
335
|
+
*/
|
|
336
|
+
declare const CAPTURE_INTERCEPTOR_MARKER: unique symbol;
|
|
337
|
+
declare function createCaptureInterceptor(config: CaptureHistoryConfig): Interceptor;
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Classification result from address checking.
|
|
341
|
+
*/
|
|
342
|
+
type AddressCheckResult = 'direct' | 'indirect' | 'passive' | 'ignore' | 'ambiguous';
|
|
343
|
+
/**
|
|
344
|
+
* Configuration for the address-check rules interceptor.
|
|
345
|
+
*/
|
|
346
|
+
interface AddressCheckConfig {
|
|
347
|
+
/** The agent's display name (e.g., "Assistant") */
|
|
348
|
+
agentName: string;
|
|
349
|
+
/** The agent's ID/slack ID (e.g., "U123456") */
|
|
350
|
+
agentId?: string;
|
|
351
|
+
/** Function to extract message text from input */
|
|
352
|
+
getMessageText: (input: AgentInput) => string | undefined;
|
|
353
|
+
/** Optional: Check if input is a direct message (DM) */
|
|
354
|
+
isDirectMessage?: (input: AgentInput) => boolean;
|
|
355
|
+
/** Optional: Extract mentioned user IDs from message */
|
|
356
|
+
getMentions?: (input: AgentInput) => string[];
|
|
357
|
+
/** Optional callback when classification is made */
|
|
358
|
+
onClassified?: (result: AddressCheckResult, input: AgentInput) => void;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Creates an address-check rules interceptor.
|
|
362
|
+
*
|
|
363
|
+
* Stage-3 rule-based classifier:
|
|
364
|
+
* - Vocative detection ("Hey Assistant...")
|
|
365
|
+
* - Possessive patterns ("my Assistant...")
|
|
366
|
+
* - Code/URL detection (likely not addressing)
|
|
367
|
+
* - Co-mention detection
|
|
368
|
+
*
|
|
369
|
+
* Returns 'ambiguous' for cases that need LLM classification by intent-classifier.
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* ```ts
|
|
373
|
+
* const registry = new AgentRegistry([
|
|
374
|
+
* {
|
|
375
|
+
* agent: MyAgent,
|
|
376
|
+
* channels: [slackChannel],
|
|
377
|
+
* interceptors: [
|
|
378
|
+
* createAddressCheckInterceptor({
|
|
379
|
+
* agentName: 'Assistant',
|
|
380
|
+
* agentId: 'U123456',
|
|
381
|
+
* getMessageText: (input) => input.message || ''
|
|
382
|
+
* })
|
|
383
|
+
* ]
|
|
384
|
+
* }
|
|
385
|
+
* ]);
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
declare function createAddressCheckInterceptor(config: AddressCheckConfig): Interceptor;
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Configuration for the intent classifier interceptor.
|
|
392
|
+
*/
|
|
393
|
+
interface IntentClassifierInterceptorConfig {
|
|
394
|
+
/** Name of the IntentClassifierAgent in the registry */
|
|
395
|
+
classifierAgentName?: string;
|
|
396
|
+
/** Function to extract message text from input */
|
|
397
|
+
getMessageText: (input: AgentInput) => string | undefined;
|
|
398
|
+
/** Agent's display name for classification context */
|
|
399
|
+
agentName: string;
|
|
400
|
+
/** Agent's unique ID */
|
|
401
|
+
agentId: string;
|
|
402
|
+
/** Sender name for classification context */
|
|
403
|
+
getSenderName: (input: AgentInput) => string;
|
|
404
|
+
/** Channel name for classification context */
|
|
405
|
+
getChannelName: (input: AgentInput) => string;
|
|
406
|
+
/** Check if this is a direct message */
|
|
407
|
+
isDirectMessage?: (input: AgentInput) => boolean;
|
|
408
|
+
/** Optional: Get recent context messages */
|
|
409
|
+
getRecentContext?: (input: AgentInput) => Array<{
|
|
410
|
+
sender: string;
|
|
411
|
+
content: string;
|
|
412
|
+
}>;
|
|
413
|
+
/** Optional callback when classification is made */
|
|
414
|
+
onClassified?: (classification: IntentClassification, input: AgentInput) => void;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Creates an intent classifier interceptor.
|
|
418
|
+
*
|
|
419
|
+
* Delegates to the IntentClassifierAgent for ambiguous address-check cases.
|
|
420
|
+
* Should be placed AFTER the address-check interceptor.
|
|
421
|
+
*
|
|
422
|
+
* Only runs when address-check result is 'ambiguous' or 'indirect'.
|
|
423
|
+
* Skips response for 'passive' and 'ignore' classifications.
|
|
424
|
+
*
|
|
425
|
+
* @example
|
|
426
|
+
* ```ts
|
|
427
|
+
* const registry = new AgentRegistry([
|
|
428
|
+
* {
|
|
429
|
+
* agent: MyAgent,
|
|
430
|
+
* channels: [slackChannel],
|
|
431
|
+
* interceptors: [
|
|
432
|
+
* createAddressCheckInterceptor({ agentName: 'Assistant', ... }),
|
|
433
|
+
* createIntentClassifierInterceptor({
|
|
434
|
+
* agentName: 'Assistant',
|
|
435
|
+
* agentId: 'U123456',
|
|
436
|
+
* getMessageText: (input) => input.message || '',
|
|
437
|
+
* getSenderName: (input) => input.context?.userName as string || 'Unknown',
|
|
438
|
+
* getChannelName: (input) => input.context?.channelName as string || 'general',
|
|
439
|
+
* classifierAgentName: 'intent-classifier' // capability agent name
|
|
440
|
+
* })
|
|
441
|
+
* ]
|
|
442
|
+
* }
|
|
443
|
+
* ]);
|
|
444
|
+
* ```
|
|
445
|
+
*/
|
|
446
|
+
declare function createIntentClassifierInterceptor(config: IntentClassifierInterceptorConfig): Interceptor;
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Configuration for the depth guard interceptor.
|
|
450
|
+
*/
|
|
451
|
+
interface DepthGuardConfig {
|
|
452
|
+
/** Maximum allowed invocation depth (default: 5) */
|
|
453
|
+
maxDepth?: number;
|
|
454
|
+
/** Optional callback when depth limit is exceeded */
|
|
455
|
+
onDepthExceeded?: (currentDepth: number, maxDepth: number, input: AgentInput) => void;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Error thrown when invocation depth exceeds the configured maximum.
|
|
459
|
+
*/
|
|
460
|
+
declare class DepthExceededError extends Error {
|
|
461
|
+
readonly currentDepth: number;
|
|
462
|
+
readonly maxDepth: number;
|
|
463
|
+
constructor(currentDepth: number, maxDepth: number);
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Creates a depth guard interceptor.
|
|
467
|
+
*
|
|
468
|
+
* Enforces maximum invocation depth on delegate chains.
|
|
469
|
+
* This provides an early check before the actual delegation happens,
|
|
470
|
+
* complementing the depth tracking in the chain composer.
|
|
471
|
+
*
|
|
472
|
+
* **Limitation:** This interceptor checks `ctx.invocationDepth`, which is always 0
|
|
473
|
+
* for top-level chain invocations. It only fires when a delegated agent (called via
|
|
474
|
+
* `ctx.delegateAndWait`) also has this interceptor and enters via the registry's
|
|
475
|
+
* interceptor chain. Since `ctx.delegateAndWait` calls `targetAgent.invokeAgent`
|
|
476
|
+
* directly (bypassing the chain), this interceptor is primarily belt-and-suspenders
|
|
477
|
+
* for future scenarios where delegated calls may route through the registry.
|
|
478
|
+
*
|
|
479
|
+
* The actual depth protection lives in `ctx.delegateAndWait`'s internal
|
|
480
|
+
* `nextDepth > maxDepth` check in the chain composer.
|
|
481
|
+
*
|
|
482
|
+
* @example
|
|
483
|
+
* ```ts
|
|
484
|
+
* const registry = new AgentRegistry([
|
|
485
|
+
* {
|
|
486
|
+
* agent: MyAgent,
|
|
487
|
+
* channels: [slackChannel],
|
|
488
|
+
* interceptors: [
|
|
489
|
+
* createDepthGuardInterceptor({ maxDepth: 5 })
|
|
490
|
+
* ]
|
|
491
|
+
* }
|
|
492
|
+
* ]);
|
|
493
|
+
* ```
|
|
494
|
+
*/
|
|
495
|
+
declare function createDepthGuardInterceptor(config?: DepthGuardConfig): Interceptor;
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Configuration for the tracer interceptor.
|
|
499
|
+
*/
|
|
500
|
+
interface TracerConfig {
|
|
501
|
+
/**
|
|
502
|
+
* Log level for tracing (default: 'debug')
|
|
503
|
+
*/
|
|
504
|
+
level?: 'debug' | 'info';
|
|
505
|
+
/**
|
|
506
|
+
* Whether to include full input data in logs (default: false)
|
|
507
|
+
*/
|
|
508
|
+
includeInputData?: boolean;
|
|
509
|
+
/**
|
|
510
|
+
* Whether to include full result output in logs (default: false)
|
|
511
|
+
*/
|
|
512
|
+
includeResultOutput?: boolean;
|
|
513
|
+
/**
|
|
514
|
+
* Optional: Filter which inputs to trace
|
|
515
|
+
*/
|
|
516
|
+
shouldTrace?: (input: AgentInput) => boolean;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Creates a tracer interceptor.
|
|
520
|
+
*
|
|
521
|
+
* Structured logging of each hop for debugging.
|
|
522
|
+
* Logs entry (before calling next) and exit (after receiving result).
|
|
523
|
+
*
|
|
524
|
+
* @example
|
|
525
|
+
* ```ts
|
|
526
|
+
* const registry = new AgentRegistry([
|
|
527
|
+
* {
|
|
528
|
+
* agent: MyAgent,
|
|
529
|
+
* channels: [slackChannel],
|
|
530
|
+
* interceptors: [
|
|
531
|
+
* createTracerInterceptor({ level: 'debug', includeInputData: true })
|
|
532
|
+
* ]
|
|
533
|
+
* }
|
|
534
|
+
* ]);
|
|
535
|
+
* ```
|
|
536
|
+
*/
|
|
537
|
+
declare function createTracerInterceptor(config?: TracerConfig): Interceptor;
|
|
538
|
+
|
|
539
|
+
export { type AddressCheckConfig, type AddressCheckResult, CAPTURE_INTERCEPTOR_MARKER, type CaptureHistoryConfig, type ComposedChain, DepthExceededError, type DepthGuardConfig, type EventDedupConfig, type IntentClassifierInterceptorConfig, Interceptor, InterceptorChainConfig, InterceptorResult, InvocationDepthExceededError, type NoiseFilterConfig, type ParticipantResolverConfig, type RateLimitConfig, type SelfFilterConfig, type TracerConfig, composeChain, createAddressCheckInterceptor, createCaptureInterceptor, createDepthGuardInterceptor, createEventDedupInterceptor, createIntentClassifierInterceptor, createNoiseFilterInterceptor, createParticipantResolverInterceptor, createRateLimitInterceptor, createSelfFilterInterceptor, createTracerInterceptor, executeChain };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var y=Symbol("interceptor-skip-sentinel");function C(e){return e===y}function A(){return y}var v=class extends Error{constructor(t,n){super(`Invocation depth ${t} exceeds maximum ${n}`),this.name="InvocationDepthExceededError"}};function K(e,t,n,s,i={}){let o=i.maxInvocationDepth??5;return{async execute(r){let g=(u=>({agent:t,channel:n,registry:s,invocationDepth:u,delegateAndWait:async(l,d)=>{let p=u+1;if(p>o)throw new v(p,o);if(!s)throw new Error(`Cannot delegate to "${l}": agent is running in standalone mode without a registry`);let f=s.getAgent(l);if(!f)throw new Error(`Agent "${l}" not found for delegation`);let h={message:d.message??"",intent:d.intent,data:d.data,context:d.context,conversationId:d.conversationId??r.conversationId??`delegation-${Date.now()}`};return await f.invokeAgent(h)},skip:A}))(0),a=async u=>{let l=u??r;return await t.invokeAgent(l)};for(let u=e.length-1;u>=0;u--){let l=e[u],d=a;a=async p=>await l(p??r,g,d)}return await a()}}}async function z(e,t){let n=await e.execute(t);return n===y?null:n}var R=class{constructor(t){this.maxSize=t}maxSize;cache=new Map;has(t){return this.cache.has(t)}set(t,n){if(this.cache.has(t)&&this.cache.delete(t),this.cache.size>=this.maxSize){let s=this.cache.keys().next().value;s!==void 0&&this.cache.delete(s)}this.cache.set(t,n)}clear(){this.cache.clear()}size(){return this.cache.size}};function k(e={}){let t=e.maxCacheSize??1e3,n=e.getEventId??(i=>i.context?.eventId),s=new R(t);return async(i,o,r)=>{let c=n(i);if(c){if(s.has(c))return e.onDuplicate?.(c,i),o.logger?.debug(`Event dedup: dropping duplicate event ${c}`,{eventId:c}),o.skip();s.set(c,!0)}return await r()}}function S(e){let t=e.getSubtype??(s=>s.context?.subtype),n=new Set(e.denySubtypes);return async(s,i,o)=>{let r=t(s);return r&&n.has(r)?(e.onFiltered?.(r,s),i.logger?.debug(`Noise filter: dropping message with subtype "${r}"`,{subtype:r}),i.skip()):await o()}}function E(e){return async(t,n,s)=>{let i=e.getSenderId(t),o=e.agentId??n.agent.name;return i&&i===o?(e.onSelfMessage?.(i,t),n.logger?.debug(`Self filter: dropping self-message from ${i}`,{senderId:i,agentId:o}),n.skip()):await s()}}var w=class{constructor(t){this.maxSize=t}maxSize;cache=new Map;get(t){let n=this.cache.get(t);return n!==void 0&&(this.cache.delete(t),this.cache.set(t,n)),n}set(t,n){if(this.cache.has(t))this.cache.delete(t);else if(this.cache.size>=this.maxSize){let s=this.cache.keys().next().value;s!==void 0&&this.cache.delete(s)}this.cache.set(t,n)}clear(){this.cache.clear()}size(){return this.cache.size}},b=class{constructor(t,n,s){this.capacity=t;this.refillRate=n;this.refillInterval=s;this.tokens=t,this.lastRefill=Date.now()}capacity;refillRate;refillInterval;tokens;lastRefill;consume(t=1){return this.refill(),this.tokens>=t?(this.tokens-=t,!0):!1}refill(){let t=Date.now(),n=t-this.lastRefill,s=Math.floor(n/this.refillInterval*this.refillRate);s>0&&(this.tokens=Math.min(this.capacity,this.tokens+s),this.lastRefill=t)}getTokens(){return this.refill(),this.tokens}};function D(e){let t=e.tokensPerInterval??10,n=e.interval??6e4,s=e.maxBuckets??1e3,i=e.onExceeded??"skip",o=new w(s);return async(r,c,g)=>{let a=e.getKey(r),u=o.get(a);if(u||(u=new b(t,t,n),o.set(a,u)),!u.consume()){if(e.onRateLimited?.(a,r),c.logger?.warn(`Rate limit exceeded for key: ${a}`,{key:a}),i==="reject")throw new Error("Rate limit exceeded. Please try again later.");return c.skip()}return await g()}}function P(e={}){return async(t,n,s)=>{let i;if(e.resolveParticipant)i=await e.resolveParticipant(t);else if(typeof n.channel.resolveParticipant=="function")try{i=await n.channel.resolveParticipant(t)}catch(r){n.logger?.warn("Channel resolveParticipant threw; falling back",{error:r instanceof Error?r.message:"Unknown error"})}let o=i??t.participant;if(o){let r={...t,participant:o,context:{...t.context,_participant:o}};return e.onResolved?.(r,o),n.logger?.debug("Resolved participant",{participantId:o.id,participantKind:o.kind}),await s(r)}return await s()}}import{randomUUID as T}from"crypto";function B(e){let t=e.context??{},n=t.channelType;return n==="im"||n==="private"||n==="dm"?"dm":t.threadId!==void 0?"thread":"channel"}var N=Symbol.for("toolpack:capture-history");function F(e){let t=e.captureAgentReplies??!0,n=e.getScope??B,s=e.getMessageId??(r=>r.context?.messageId??r.context?.eventId??T()),i=e.getMentions??(r=>r.context?.mentions??[]),o=async(r,c,g)=>{let a=r.conversationId;if(!a)return c.logger?.warn("[capture-history] Message has no conversationId \u2014 skipping capture"),await g();let u=r.participant;if(u){let d={id:s(r),conversationId:a,participant:u,content:r.message??"",timestamp:new Date().toISOString(),scope:n(r),metadata:{channelType:r.context?.channelType,threadId:r.context?.threadId,messageId:r.context?.messageId,mentions:i(r),channelName:r.context?.channelName,channelId:r.context?.channelId}};try{await e.store.append(d),e.onCaptured?.(d),c.logger?.debug("[capture-history] Captured inbound message",{messageId:d.id,participantId:u.id,conversationId:a})}catch(p){c.logger?.warn("[capture-history] Failed to store inbound message",{error:p instanceof Error?p.message:String(p)})}}let l=await g();if(t&&!C(l)&&l.output!=null){let d={kind:"agent",id:c.agent.name,displayName:c.agent.name},p={id:T(),conversationId:a,participant:d,content:l.output,timestamp:new Date().toISOString(),scope:n(r),metadata:{channelType:r.context?.channelType,threadId:r.context?.threadId,channelName:r.context?.channelName,channelId:r.context?.channelId}};try{await e.store.append(p),e.onCaptured?.(p),c.logger?.debug("[capture-history] Captured agent reply",{messageId:p.id,agentId:c.agent.name,conversationId:a})}catch(f){c.logger?.warn("[capture-history] Failed to store agent reply",{error:f instanceof Error?f.message:String(f)})}}return l};return o[N]=!0,o}function O(e,t){let n=t.toLowerCase();if(!e.toLowerCase().includes(n))return!1;let i=[],o=/```[\s\S]*?```/g,r;for(;(r=o.exec(e))!==null;)i.push([r.index,r.index+r[0].length]);let c=/`[^`\n]*`/g;for(;(r=c.exec(e))!==null;){let d=r.index,p=d+r[0].length;i.some(([h,_])=>d>=h&&p<=_)||i.push([d,p])}if(i.length===0)return!1;i.sort((d,p)=>d[0]-p[0]);let g="",a=0;for(let[d,p]of i)d>a&&(g+=e.slice(a,d)),a=Math.max(a,p);return a<e.length&&(g+=e.slice(a)),g.toLowerCase().includes(n)?!1:i.some(([d,p])=>e.slice(d,p).toLowerCase().includes(n))}function M(e){return async(t,n,s)=>{let i=e.getMessageText(t)??"";if(e.isDirectMessage?.(t)){let p={...t,context:{...t.context,_addressCheck:"direct",_isDM:!0}};return e.onClassified?.("direct",t),await s(p)}let o="ambiguous",r=i.toLowerCase(),c=e.agentName.toLowerCase(),g=p=>p.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),a=g(c),u=e.agentId?`|^@${g(e.agentId)}\\b`:"";if(new RegExp(`^(hey\\s+)?@?${a}\\b${u}`,"i").test(r))o="direct";else if(new RegExp(`\\b(my|our|the)\\s+${a}\\b`,"i").test(r))o="ambiguous";else if(O(i,e.agentName))o="ignore";else if(/^https?:\/\//.test(i))o="ignore";else if(e.getMentions){let p=e.getMentions(t),f=p.some(h=>h.toLowerCase()===c||h===e.agentId);f&&p.length>1?o="indirect":f&&(o="ambiguous")}else r.includes(c)?o="ambiguous":o="passive";let d={...t,context:{...t.context,_addressCheck:o}};return e.onClassified?.(o,t),n.logger?.debug(`Address check classified as: ${o}`,{result:o}),await s(d)}}function j(e){let t=e.classifierAgentName??"intent-classifier";return async(n,s,i)=>{let o=n.context?._addressCheck;if(o==="direct")return await i();if(o==="ignore"||o==="passive")return e.onClassified?.(o,n),s.skip();let r=e.getMessageText(n)??"";if(!r.trim())return await i();let c={message:r,agentName:e.agentName,agentId:e.agentId,senderName:e.getSenderName(n),channelName:e.getChannelName(n),isDirectMessage:e.isDirectMessage?.(n)??!1,recentContext:e.getRecentContext?.(n)};try{let a=(await s.delegateAndWait(t,{message:"classify",data:c,conversationId:n.conversationId})).output.trim();e.onClassified?.(a,n),s.logger?.debug(`Intent classified as: ${a}`,{classification:a});let u={...n,context:{...n.context,_intentClassification:a}};return a==="direct"?await i(u):s.skip()}catch(g){return s.logger?.error("Intent classification failed, allowing message",{error:g instanceof Error?g.message:"Unknown error"}),await i()}}}var x=class extends Error{constructor(n,s){super(`Maximum invocation depth exceeded: ${n} > ${s}`);this.currentDepth=n;this.maxDepth=s;this.name="DepthExceededError"}currentDepth;maxDepth};function L(e={}){let t=e.maxDepth??5;return async(n,s,i)=>{if(s.invocationDepth>t)throw e.onDepthExceeded?.(s.invocationDepth,t,n),s.logger?.error(`Depth guard: invocation depth ${s.invocationDepth} exceeds maximum ${t}`,{currentDepth:s.invocationDepth,maxDepth:t}),new x(s.invocationDepth,t);return await i()}}function $(e={}){let t=e.level??"debug",n=e.includeInputData??!1,s=e.includeResultOutput??!1;return async(i,o,r)=>{if(e.shouldTrace&&!e.shouldTrace(i))return await r();let c=t==="info"?o.logger?.info:o.logger?.debug;c?.("Interceptor entry",{agent:o.agent.name,channel:o.channel.name,depth:o.invocationDepth,conversationId:i.conversationId,intent:i.intent,input:n?i:void 0});let g=performance.now();try{let a=await r(),u=performance.now()-g;return C(a)?c?.("Interceptor exit: skipped",{agent:o.agent.name,channel:o.channel.name,depth:o.invocationDepth,conversationId:i.conversationId,durationMs:u.toFixed(2)}):c?.("Interceptor exit: success",{agent:o.agent.name,channel:o.channel.name,depth:o.invocationDepth,conversationId:i.conversationId,durationMs:u.toFixed(2),outputLength:a.output.length,result:s?a:void 0}),a}catch(a){let u=performance.now()-g;throw o.logger?.error("Interceptor exit: error",{agent:o.agent.name,channel:o.channel.name,depth:o.invocationDepth,conversationId:i.conversationId,durationMs:u.toFixed(2),error:a instanceof Error?a.message:"Unknown error",errorType:a?.constructor?.name}),a}}}export{N as CAPTURE_INTERCEPTOR_MARKER,x as DepthExceededError,v as InvocationDepthExceededError,y as SKIP_SENTINEL,K as composeChain,M as createAddressCheckInterceptor,F as createCaptureInterceptor,L as createDepthGuardInterceptor,k as createEventDedupInterceptor,j as createIntentClassifierInterceptor,S as createNoiseFilterInterceptor,P as createParticipantResolverInterceptor,D as createRateLimitInterceptor,E as createSelfFilterInterceptor,$ as createTracerInterceptor,z as executeChain,C as isSkipSentinel,A as skip};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var h=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var R=Object.getOwnPropertyNames;var S=Object.prototype.hasOwnProperty;var b=(r,e)=>{for(var o in e)h(r,o,{get:e[o],enumerable:!0})},A=(r,e,o,g)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of R(e))!S.call(r,s)&&s!==o&&h(r,s,{get:()=>e[s],enumerable:!(g=w(e,s))||g.enumerable});return r};var j=r=>A(h({},"__esModule",{value:!0}),r);var M={};b(M,{RegistryError:()=>c,searchRegistry:()=>l});module.exports=j(M);async function l(r={}){let{keyword:e,category:o,tag:g,limit:s=20,offset:p=0,registryUrl:f="https://registry.npmjs.org"}=r,d=["toolpack-agent"];e&&d.push(e),g&&d.push(g);let k=d.join(" "),y=new URL("/-/v1/search",f);y.searchParams.set("text",k),y.searchParams.set("size",String(Math.min(s+p,250))),y.searchParams.set("from",String(0));try{let n=await fetch(y.toString(),{headers:{Accept:"application/json"}});if(!n.ok)throw new c(`NPM registry search failed: ${n.status} ${n.statusText}`);let a=(await n.json()).objects.map(i=>{let t=i.package,u=L(t);return{name:t.name,version:t.version,description:t.description,toolpack:u,keywords:t.keywords,author:t.author,date:t.date,links:t.links,publisher:t.publisher,maintainers:t.maintainers}});if(o&&(a=a.filter(i=>i.toolpack?.category?.toLowerCase()===o.toLowerCase())),g){let i=g.toLowerCase();a=a.filter(t=>t.toolpack?.tags?.some(u=>u.toLowerCase()===i)||t.keywords?.some(u=>u.toLowerCase()===i))}a=a.filter(i=>i.toolpack?.agent===!0);let m=a.length;return a=a.slice(p,p+s),{agents:a,total:m,offset:p,limit:s,hasMore:m>p+s}}catch(n){throw n instanceof c?n:new c(`Failed to search registry: ${n instanceof Error?n.message:String(n)}`)}}function L(r){let e=r.toolpack;if(!(!e||e.agent!==!0))return{agent:!0,category:typeof e.category=="string"?e.category:void 0,description:typeof e.description=="string"?e.description:void 0,tags:Array.isArray(e.tags)?e.tags.filter(o=>typeof o=="string"):void 0,author:typeof e.author=="string"?e.author:void 0,repository:typeof e.repository=="string"?e.repository:void 0,homepage:typeof e.homepage=="string"?e.homepage:void 0}}var c=class extends Error{constructor(e){super(e),this.name="RegistryError"}};0&&(module.exports={RegistryError,searchRegistry});
|