@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.
- package/dist/cache.d.ts +59 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +145 -0
- package/dist/log.d.ts +44 -0
- package/dist/rate-limit.d.ts +37 -0
- package/dist/retry.d.ts +57 -0
- package/package.json +29 -0
package/dist/cache.d.ts
ADDED
|
@@ -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;
|
package/dist/index.d.ts
ADDED
|
@@ -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;
|
package/dist/retry.d.ts
ADDED
|
@@ -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
|
+
}
|