@agentier/middleware 0.1.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.
@@ -0,0 +1,59 @@
1
+ import type { Middleware, AgentAction } from '@agentier/core';
2
+ /**
3
+ * Configuration options for the caching middleware.
4
+ */
5
+ export interface CacheMiddlewareOptions {
6
+ /**
7
+ * Time-to-live for cached entries in milliseconds.
8
+ * After this duration, a cached result is considered stale and will be evicted.
9
+ *
10
+ * @default 300000 (5 minutes)
11
+ */
12
+ ttl?: number;
13
+ /**
14
+ * Maximum number of entries the cache may hold. When exceeded, the oldest
15
+ * entry (by insertion order) is evicted.
16
+ *
17
+ * @default 100
18
+ */
19
+ maxEntries?: number;
20
+ /**
21
+ * Custom function that derives a cache key from an action. Return `null` to
22
+ * skip caching for that action. When omitted, a built-in key function is used
23
+ * that caches `model_call` actions based on model, messages, and tools.
24
+ *
25
+ * @param action - The agent action to compute a key for.
26
+ * @returns A string cache key, or `null` to bypass caching.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * {
31
+ * keyFn: (action) => action.type === 'tool_call'
32
+ * ? `tool:${action.payload.name}:${JSON.stringify(action.payload.args)}`
33
+ * : null,
34
+ * }
35
+ * ```
36
+ */
37
+ keyFn?: (action: AgentAction) => string | null;
38
+ }
39
+ /**
40
+ * Creates a middleware that caches action results in memory using a simple
41
+ * TTL + max-size eviction strategy. Identical actions (as determined by
42
+ * their cache key) that hit a live cache entry are returned immediately
43
+ * without invoking the downstream pipeline.
44
+ *
45
+ * @param options - Optional configuration for TTL, capacity, and key derivation.
46
+ * @returns A {@link Middleware} function that adds response caching to the pipeline.
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * import { cacheMiddleware } from '@agentier/middleware'
51
+ *
52
+ * const agent = createAgent({
53
+ * middleware: [
54
+ * cacheMiddleware({ ttl: 60_000, maxEntries: 50 }),
55
+ * ],
56
+ * })
57
+ * ```
58
+ */
59
+ export declare function cacheMiddleware(options?: CacheMiddlewareOptions): Middleware;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @module @agentier/middleware
3
+ *
4
+ * A collection of composable middleware functions for the Agentier agent
5
+ * pipeline. Each middleware wraps a specific cross-cutting concern --
6
+ * logging, retry, rate limiting, or caching -- and can be combined freely.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import {
11
+ * logMiddleware,
12
+ * retryMiddleware,
13
+ * rateLimitMiddleware,
14
+ * cacheMiddleware,
15
+ * } from '@agentier/middleware'
16
+ *
17
+ * const agent = createAgent({
18
+ * middleware: [
19
+ * logMiddleware(),
20
+ * retryMiddleware({ maxRetries: 3 }),
21
+ * rateLimitMiddleware({ rpm: 60 }),
22
+ * cacheMiddleware({ ttl: 60_000 }),
23
+ * ],
24
+ * })
25
+ * ```
26
+ */
27
+ export { logMiddleware } from './log';
28
+ export type { LogMiddlewareOptions } from './log';
29
+ export { retryMiddleware } from './retry';
30
+ export type { RetryMiddlewareOptions } from './retry';
31
+ export { rateLimitMiddleware } from './rate-limit';
32
+ export type { RateLimitMiddlewareOptions } from './rate-limit';
33
+ export { cacheMiddleware } from './cache';
34
+ export type { CacheMiddlewareOptions } from './cache';
package/dist/index.js ADDED
@@ -0,0 +1,145 @@
1
+ // src/log.ts
2
+ function logMiddleware(options) {
3
+ const { actions, logger = console } = options ?? {};
4
+ return async (action, next) => {
5
+ if (actions && !actions.includes(action.type)) {
6
+ return next();
7
+ }
8
+ const start = Date.now();
9
+ const summary = formatAction(action);
10
+ logger.log(`[agentier] ${action.type} ${summary}`);
11
+ try {
12
+ const result = await next();
13
+ logger.log(`[agentier] ${action.type} done (${Date.now() - start}ms)`);
14
+ return result;
15
+ } catch (error) {
16
+ logger.error(`[agentier] ${action.type} failed (${Date.now() - start}ms)`, error);
17
+ throw error;
18
+ }
19
+ };
20
+ }
21
+ function formatAction(action) {
22
+ const p = action.payload;
23
+ switch (action.type) {
24
+ case "model_call":
25
+ return `model=${p.model}`;
26
+ case "tool_call":
27
+ return `tool=${p.name}`;
28
+ case "tool_result":
29
+ return `tool=${p.name} duration=${p.duration}ms`;
30
+ case "error":
31
+ return `source=${p.source} ${p.name ? `tool=${p.name}` : ""}`;
32
+ default:
33
+ return "";
34
+ }
35
+ }
36
+ // src/retry.ts
37
+ function retryMiddleware(options) {
38
+ const {
39
+ maxRetries = 3,
40
+ retryOn = ["model_call"],
41
+ backoff = "exponential",
42
+ baseDelay = 1000
43
+ } = options ?? {};
44
+ return async (action, next) => {
45
+ if (!retryOn.includes(action.type)) {
46
+ return next();
47
+ }
48
+ let lastError = null;
49
+ for (let attempt = 0;attempt <= maxRetries; attempt++) {
50
+ try {
51
+ return await next();
52
+ } catch (error) {
53
+ lastError = error instanceof Error ? error : new Error(String(error));
54
+ if (attempt === maxRetries)
55
+ break;
56
+ const delay = backoff === "exponential" ? baseDelay * Math.pow(2, attempt) : baseDelay;
57
+ await new Promise((resolve) => setTimeout(resolve, delay));
58
+ }
59
+ }
60
+ throw lastError;
61
+ };
62
+ }
63
+ // src/rate-limit.ts
64
+ function rateLimitMiddleware(options) {
65
+ const { rpm, limitOn = ["model_call"] } = options;
66
+ const windowMs = 60000;
67
+ const timestamps = [];
68
+ return async (action, next) => {
69
+ if (!limitOn.includes(action.type)) {
70
+ return next();
71
+ }
72
+ const now = Date.now();
73
+ while (timestamps.length > 0 && timestamps[0] <= now - windowMs) {
74
+ timestamps.shift();
75
+ }
76
+ if (timestamps.length >= rpm) {
77
+ const waitUntil = timestamps[0] + windowMs;
78
+ const waitMs = waitUntil - now;
79
+ if (waitMs > 0) {
80
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
81
+ }
82
+ while (timestamps.length > 0 && timestamps[0] <= Date.now() - windowMs) {
83
+ timestamps.shift();
84
+ }
85
+ }
86
+ timestamps.push(Date.now());
87
+ return next();
88
+ };
89
+ }
90
+ // src/cache.ts
91
+ function cacheMiddleware(options) {
92
+ const { ttl = 5 * 60 * 1000, maxEntries = 100, keyFn } = options ?? {};
93
+ const cache = new Map;
94
+ function defaultKeyFn(action) {
95
+ if (action.type !== "model_call")
96
+ return null;
97
+ const payload = action.payload;
98
+ const key = JSON.stringify({
99
+ model: payload.model,
100
+ messages: payload.messages,
101
+ tools: payload.tools
102
+ });
103
+ return key;
104
+ }
105
+ function evictExpired() {
106
+ const now = Date.now();
107
+ for (const [key, entry] of cache) {
108
+ if (entry.expiresAt <= now) {
109
+ cache.delete(key);
110
+ }
111
+ }
112
+ }
113
+ function evictOldest() {
114
+ if (cache.size <= maxEntries)
115
+ return;
116
+ const firstKey = cache.keys().next().value;
117
+ if (firstKey !== undefined) {
118
+ cache.delete(firstKey);
119
+ }
120
+ }
121
+ return async (action, next) => {
122
+ const key = keyFn ? keyFn(action) : defaultKeyFn(action);
123
+ if (!key) {
124
+ return next();
125
+ }
126
+ evictExpired();
127
+ const cached = cache.get(key);
128
+ if (cached && cached.expiresAt > Date.now()) {
129
+ return cached.result;
130
+ }
131
+ const result = await next();
132
+ cache.set(key, {
133
+ result,
134
+ expiresAt: Date.now() + ttl
135
+ });
136
+ evictOldest();
137
+ return result;
138
+ };
139
+ }
140
+ export {
141
+ retryMiddleware,
142
+ rateLimitMiddleware,
143
+ logMiddleware,
144
+ cacheMiddleware
145
+ };
package/dist/log.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ import type { Middleware, AgentActionType } from '@agentier/core';
2
+ /**
3
+ * Configuration options for the logging middleware.
4
+ */
5
+ export interface LogMiddlewareOptions {
6
+ /**
7
+ * Action types to log. When provided, only matching actions are logged.
8
+ * If omitted, all actions are logged.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * { actions: ['model_call', 'tool_call'] }
13
+ * ```
14
+ */
15
+ actions?: AgentActionType[];
16
+ /**
17
+ * Logger instance to use for output. Must implement `log` and `error` methods.
18
+ * Defaults to the global `console`.
19
+ *
20
+ * @default console
21
+ */
22
+ logger?: Pick<Console, 'log' | 'error'>;
23
+ }
24
+ /**
25
+ * Creates a middleware that logs agent actions with timing information.
26
+ *
27
+ * Each action is logged at the start and on completion (or failure) with
28
+ * elapsed time in milliseconds. Output is prefixed with `[agentier]`.
29
+ *
30
+ * @param options - Optional configuration for filtering and logger selection.
31
+ * @returns A {@link Middleware} function that logs actions passing through the pipeline.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * import { logMiddleware } from '@agentier/middleware'
36
+ *
37
+ * const agent = createAgent({
38
+ * middleware: [
39
+ * logMiddleware({ actions: ['model_call', 'tool_call'] }),
40
+ * ],
41
+ * })
42
+ * ```
43
+ */
44
+ export declare function logMiddleware(options?: LogMiddlewareOptions): Middleware;
@@ -0,0 +1,37 @@
1
+ import type { Middleware, AgentActionType } from '@agentier/core';
2
+ /**
3
+ * Configuration options for the rate-limiting middleware.
4
+ */
5
+ export interface RateLimitMiddlewareOptions {
6
+ /**
7
+ * Maximum number of requests permitted per minute (rolling window).
8
+ */
9
+ rpm: number;
10
+ /**
11
+ * Action types subject to rate limiting.
12
+ * Actions whose type is not in this list pass through without throttling.
13
+ *
14
+ * @default ['model_call']
15
+ */
16
+ limitOn?: AgentActionType[];
17
+ }
18
+ /**
19
+ * Creates a middleware that enforces a per-minute request rate limit using a
20
+ * sliding window. When the limit is reached, subsequent calls are delayed
21
+ * until the oldest tracked request falls outside the 60-second window.
22
+ *
23
+ * @param options - Configuration specifying the rate cap and target action types.
24
+ * @returns A {@link Middleware} function that throttles actions in the pipeline.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * import { rateLimitMiddleware } from '@agentier/middleware'
29
+ *
30
+ * const agent = createAgent({
31
+ * middleware: [
32
+ * rateLimitMiddleware({ rpm: 60, limitOn: ['model_call'] }),
33
+ * ],
34
+ * })
35
+ * ```
36
+ */
37
+ export declare function rateLimitMiddleware(options: RateLimitMiddlewareOptions): Middleware;
@@ -0,0 +1,57 @@
1
+ import type { Middleware, AgentActionType } from '@agentier/core';
2
+ /**
3
+ * Configuration options for the retry middleware.
4
+ */
5
+ export interface RetryMiddlewareOptions {
6
+ /**
7
+ * Maximum number of retry attempts after the initial call fails.
8
+ *
9
+ * @default 3
10
+ */
11
+ maxRetries?: number;
12
+ /**
13
+ * Action types that should be retried on failure.
14
+ * Actions whose type is not in this list pass through without retry logic.
15
+ *
16
+ * @default ['model_call']
17
+ */
18
+ retryOn?: AgentActionType[];
19
+ /**
20
+ * Backoff strategy used to calculate the delay between retries.
21
+ *
22
+ * - `'fixed'` -- waits `baseDelay` ms between every attempt.
23
+ * - `'exponential'` -- waits `baseDelay * 2^attempt` ms, doubling each time.
24
+ *
25
+ * @default 'exponential'
26
+ */
27
+ backoff?: 'fixed' | 'exponential';
28
+ /**
29
+ * Base delay in milliseconds used for backoff calculation.
30
+ *
31
+ * @default 1000
32
+ */
33
+ baseDelay?: number;
34
+ }
35
+ /**
36
+ * Creates a middleware that automatically retries failed actions.
37
+ *
38
+ * When an action whose type is listed in `retryOn` throws an error, the
39
+ * middleware re-invokes the downstream pipeline up to `maxRetries` times,
40
+ * waiting between attempts according to the chosen `backoff` strategy.
41
+ * If all attempts fail, the last error is re-thrown.
42
+ *
43
+ * @param options - Optional configuration for retry behaviour.
44
+ * @returns A {@link Middleware} function that adds retry logic to the pipeline.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * import { retryMiddleware } from '@agentier/middleware'
49
+ *
50
+ * const agent = createAgent({
51
+ * middleware: [
52
+ * retryMiddleware({ maxRetries: 5, backoff: 'exponential', baseDelay: 500 }),
53
+ * ],
54
+ * })
55
+ * ```
56
+ */
57
+ export declare function retryMiddleware(options?: RetryMiddlewareOptions): Middleware;
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@agentier/middleware",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "scripts": {
17
+ "build": "bun build ./src/index.ts --outdir ./dist --target node",
18
+ "test": "bun test",
19
+ "typecheck": "tsc --noEmit"
20
+ },
21
+ "peerDependencies": {
22
+ "@agentier/core": "^0.1.0"
23
+ },
24
+ "devDependencies": {
25
+ "@agentier/core": "workspace:*",
26
+ "typescript": "^5.5.0",
27
+ "@types/bun": "latest"
28
+ }
29
+ }