@cleocode/lafs-protocol 0.5.0 → 1.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/LICENSE +0 -0
- package/README.md +7 -3
- package/dist/examples/discovery-server.d.ts +8 -0
- package/dist/examples/discovery-server.js +216 -0
- package/dist/examples/mcp-lafs-client.d.ts +10 -0
- package/dist/examples/mcp-lafs-client.js +427 -0
- package/dist/examples/mcp-lafs-server.d.ts +10 -0
- package/dist/examples/mcp-lafs-server.js +358 -0
- package/dist/schemas/v1/envelope.schema.json +0 -0
- package/dist/schemas/v1/error-registry.json +0 -0
- package/dist/src/a2a/bridge.d.ts +129 -0
- package/dist/src/a2a/bridge.js +173 -0
- package/dist/src/a2a/index.d.ts +36 -0
- package/dist/src/a2a/index.js +36 -0
- package/dist/src/budgetEnforcement.d.ts +84 -0
- package/dist/src/budgetEnforcement.js +328 -0
- package/dist/src/circuit-breaker/index.d.ts +121 -0
- package/dist/src/circuit-breaker/index.js +249 -0
- package/dist/src/cli.d.ts +0 -0
- package/dist/src/cli.js +0 -0
- package/dist/src/conformance.d.ts +0 -0
- package/dist/src/conformance.js +0 -0
- package/dist/src/discovery.d.ts +127 -0
- package/dist/src/discovery.js +304 -0
- package/dist/src/errorRegistry.d.ts +0 -0
- package/dist/src/errorRegistry.js +0 -0
- package/dist/src/flagSemantics.d.ts +0 -0
- package/dist/src/flagSemantics.js +0 -0
- package/dist/src/health/index.d.ts +105 -0
- package/dist/src/health/index.js +211 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.js +10 -0
- package/dist/src/mcpAdapter.d.ts +28 -0
- package/dist/src/mcpAdapter.js +281 -0
- package/dist/src/shutdown/index.d.ts +69 -0
- package/dist/src/shutdown/index.js +160 -0
- package/dist/src/tokenEstimator.d.ts +87 -0
- package/dist/src/tokenEstimator.js +238 -0
- package/dist/src/types.d.ts +25 -0
- package/dist/src/types.js +0 -0
- package/dist/src/validateEnvelope.d.ts +0 -0
- package/dist/src/validateEnvelope.js +0 -0
- package/lafs.md +167 -0
- package/package.json +10 -4
- package/schemas/v1/context-ledger.schema.json +0 -0
- package/schemas/v1/discovery.schema.json +132 -0
- package/schemas/v1/envelope.schema.json +0 -0
- package/schemas/v1/error-registry.json +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LAFS Agent-to-Agent (A2A) Integration
|
|
3
|
+
*
|
|
4
|
+
* This module provides integration between LAFS and the official
|
|
5
|
+
* @a2a-js/sdk for Agent-to-Agent communication.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { ClientFactory } from '@a2a-js/sdk/client';
|
|
10
|
+
* import { withLafsEnvelope } from '@cleocode/lafs-protocol/a2a';
|
|
11
|
+
*
|
|
12
|
+
* // Create official A2A client
|
|
13
|
+
* const factory = new ClientFactory();
|
|
14
|
+
* const a2aClient = await factory.createFromUrl('http://agent.example.com');
|
|
15
|
+
*
|
|
16
|
+
* // Wrap with LAFS support
|
|
17
|
+
* const client = withLafsEnvelope(a2aClient, {
|
|
18
|
+
* defaultBudget: { maxTokens: 4000 }
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // Send message
|
|
22
|
+
* const result = await client.sendMessage({
|
|
23
|
+
* message: {
|
|
24
|
+
* role: 'user',
|
|
25
|
+
* parts: [{ text: 'Analyze data' }]
|
|
26
|
+
* }
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* // Extract LAFS envelope from response
|
|
30
|
+
* const envelope = result.getLafsEnvelope();
|
|
31
|
+
* if (envelope) {
|
|
32
|
+
* console.log(envelope._meta._tokenEstimate);
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export { withLafsEnvelope, LafsA2AClient, LafsA2AResult, createLafsArtifact } from './bridge.js';
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LAFS Budget Enforcement
|
|
3
|
+
*
|
|
4
|
+
* Middleware for enforcing MVI (Minimal Viable Interface) token budgets on LAFS envelopes.
|
|
5
|
+
* Provides budget checking, truncation, and error generation for exceeded budgets.
|
|
6
|
+
*/
|
|
7
|
+
import type { LAFSEnvelope } from "./types.js";
|
|
8
|
+
import type { BudgetEnforcementOptions, TokenEstimate, BudgetEnforcementResult } from "./types.js";
|
|
9
|
+
import { TokenEstimator } from "./tokenEstimator.js";
|
|
10
|
+
/**
|
|
11
|
+
* Budget exceeded error code from LAFS error registry
|
|
12
|
+
*/
|
|
13
|
+
declare const BUDGET_EXCEEDED_CODE = "E_MVI_BUDGET_EXCEEDED";
|
|
14
|
+
/**
|
|
15
|
+
* Apply budget enforcement to an envelope.
|
|
16
|
+
*
|
|
17
|
+
* @param envelope - The LAFS envelope to check
|
|
18
|
+
* @param budget - Maximum allowed tokens
|
|
19
|
+
* @param options - Budget enforcement options
|
|
20
|
+
* @returns Enforce result with potentially modified envelope
|
|
21
|
+
*/
|
|
22
|
+
export declare function applyBudgetEnforcement(envelope: LAFSEnvelope, budget: number, options?: BudgetEnforcementOptions): BudgetEnforcementResult;
|
|
23
|
+
/**
|
|
24
|
+
* Type for middleware function
|
|
25
|
+
*/
|
|
26
|
+
type EnvelopeMiddleware = (envelope: LAFSEnvelope, next: () => LAFSEnvelope | Promise<LAFSEnvelope>) => Promise<LAFSEnvelope> | LAFSEnvelope;
|
|
27
|
+
/**
|
|
28
|
+
* Create a budget enforcement middleware function.
|
|
29
|
+
*
|
|
30
|
+
* @param budget - Maximum allowed tokens for response
|
|
31
|
+
* @param options - Budget enforcement options
|
|
32
|
+
* @returns Middleware function that enforces budget
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const middleware = withBudget(1000, { truncateOnExceed: true });
|
|
37
|
+
* const result = await middleware(envelope, async () => nextEnvelope);
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function withBudget(budget: number, options?: BudgetEnforcementOptions): EnvelopeMiddleware;
|
|
41
|
+
/**
|
|
42
|
+
* Check if an envelope has exceeded its budget without modifying it.
|
|
43
|
+
*
|
|
44
|
+
* @param envelope - The LAFS envelope to check
|
|
45
|
+
* @param budget - Maximum allowed tokens
|
|
46
|
+
* @returns Budget check result
|
|
47
|
+
*/
|
|
48
|
+
export declare function checkBudget(envelope: LAFSEnvelope, budget: number): {
|
|
49
|
+
exceeded: boolean;
|
|
50
|
+
estimated: number;
|
|
51
|
+
remaining: number;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Synchronous version of withBudget for non-async contexts.
|
|
55
|
+
*
|
|
56
|
+
* @param budget - Maximum allowed tokens for response
|
|
57
|
+
* @param options - Budget enforcement options
|
|
58
|
+
* @returns Middleware function that enforces budget synchronously
|
|
59
|
+
*/
|
|
60
|
+
export declare function withBudgetSync(budget: number, options?: BudgetEnforcementOptions): (envelope: LAFSEnvelope, next: () => LAFSEnvelope) => LAFSEnvelope;
|
|
61
|
+
/**
|
|
62
|
+
* Higher-order function that wraps a handler with budget enforcement.
|
|
63
|
+
*
|
|
64
|
+
* @param handler - The handler function to wrap
|
|
65
|
+
* @param budget - Maximum allowed tokens
|
|
66
|
+
* @param options - Budget enforcement options
|
|
67
|
+
* @returns Wrapped handler with budget enforcement
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
* const myHandler = async (request: Request) => ({ success: true, result: { data } });
|
|
72
|
+
* const budgetedHandler = wrapWithBudget(myHandler, 1000, { truncateOnExceed: true });
|
|
73
|
+
* const result = await budgetedHandler(request);
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare function wrapWithBudget<TArgs extends unknown[], TResult extends LAFSEnvelope>(handler: (...args: TArgs) => TResult | Promise<TResult>, budget: number, options?: BudgetEnforcementOptions): (...args: TArgs) => Promise<LAFSEnvelope>;
|
|
77
|
+
/**
|
|
78
|
+
* Compose multiple middleware functions into a single middleware.
|
|
79
|
+
* Middleware is executed in order (left to right).
|
|
80
|
+
*/
|
|
81
|
+
export declare function composeMiddleware(...middlewares: EnvelopeMiddleware[]): EnvelopeMiddleware;
|
|
82
|
+
export type { BudgetEnforcementOptions, TokenEstimate, BudgetEnforcementResult };
|
|
83
|
+
export { TokenEstimator };
|
|
84
|
+
export { BUDGET_EXCEEDED_CODE };
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LAFS Budget Enforcement
|
|
3
|
+
*
|
|
4
|
+
* Middleware for enforcing MVI (Minimal Viable Interface) token budgets on LAFS envelopes.
|
|
5
|
+
* Provides budget checking, truncation, and error generation for exceeded budgets.
|
|
6
|
+
*/
|
|
7
|
+
import { TokenEstimator } from "./tokenEstimator.js";
|
|
8
|
+
/**
|
|
9
|
+
* Budget exceeded error code from LAFS error registry
|
|
10
|
+
*/
|
|
11
|
+
const BUDGET_EXCEEDED_CODE = "E_MVI_BUDGET_EXCEEDED";
|
|
12
|
+
/**
|
|
13
|
+
* Default category for budget exceeded errors
|
|
14
|
+
*/
|
|
15
|
+
const BUDGET_ERROR_CATEGORY = "VALIDATION";
|
|
16
|
+
/**
|
|
17
|
+
* Create a budget exceeded error object
|
|
18
|
+
*/
|
|
19
|
+
function createBudgetExceededError(estimated, budget) {
|
|
20
|
+
return {
|
|
21
|
+
code: BUDGET_EXCEEDED_CODE,
|
|
22
|
+
message: `Response exceeds declared MVI budget: estimated ${estimated} tokens, budget ${budget} tokens`,
|
|
23
|
+
category: BUDGET_ERROR_CATEGORY,
|
|
24
|
+
retryable: false,
|
|
25
|
+
retryAfterMs: null,
|
|
26
|
+
details: {
|
|
27
|
+
estimatedTokens: estimated,
|
|
28
|
+
budgetTokens: budget,
|
|
29
|
+
exceededBy: estimated - budget,
|
|
30
|
+
exceededByPercent: Math.round(((estimated - budget) / budget) * 100),
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Truncate a result to fit within budget.
|
|
36
|
+
* Returns the truncated result and whether truncation occurred.
|
|
37
|
+
*/
|
|
38
|
+
function truncateResult(result, targetTokens, estimator) {
|
|
39
|
+
if (result === null) {
|
|
40
|
+
return { result: null, wasTruncated: false };
|
|
41
|
+
}
|
|
42
|
+
const currentEstimate = estimator.estimate(result);
|
|
43
|
+
// If already within budget, no truncation needed
|
|
44
|
+
if (currentEstimate <= targetTokens) {
|
|
45
|
+
return { result, wasTruncated: false };
|
|
46
|
+
}
|
|
47
|
+
// Calculate target size (conservative: assume 10% overhead)
|
|
48
|
+
const targetChars = Math.floor(targetTokens * 4 * 0.9);
|
|
49
|
+
if (Array.isArray(result)) {
|
|
50
|
+
return truncateArray(result, targetChars, targetTokens, estimator);
|
|
51
|
+
}
|
|
52
|
+
return truncateObject(result, targetChars, targetTokens, estimator);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Truncate an array to fit within budget.
|
|
56
|
+
*/
|
|
57
|
+
function truncateArray(arr, targetChars, targetTokens, estimator) {
|
|
58
|
+
if (arr.length === 0) {
|
|
59
|
+
return { result: arr, wasTruncated: false };
|
|
60
|
+
}
|
|
61
|
+
// Binary search to find how many items fit
|
|
62
|
+
let left = 0;
|
|
63
|
+
let right = arr.length;
|
|
64
|
+
let bestFit = 0;
|
|
65
|
+
while (left <= right) {
|
|
66
|
+
const mid = Math.floor((left + right) / 2);
|
|
67
|
+
const subset = arr.slice(0, mid);
|
|
68
|
+
const estimate = estimator.estimate(subset);
|
|
69
|
+
if (estimate <= targetTokens) {
|
|
70
|
+
bestFit = mid;
|
|
71
|
+
left = mid + 1;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
right = mid - 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// If we can fit all items, no truncation needed
|
|
78
|
+
if (bestFit >= arr.length) {
|
|
79
|
+
return { result: arr, wasTruncated: false };
|
|
80
|
+
}
|
|
81
|
+
// Create truncated result
|
|
82
|
+
const truncated = arr.slice(0, bestFit);
|
|
83
|
+
// If we couldn't fit any items, return minimal response
|
|
84
|
+
if (bestFit === 0 && arr.length > 0) {
|
|
85
|
+
return {
|
|
86
|
+
result: [{ _truncated: true, reason: "budget_exceeded" }],
|
|
87
|
+
wasTruncated: true
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Add truncation indicator to last element if it's an object
|
|
91
|
+
if (bestFit > 0 && typeof truncated[bestFit - 1] === 'object' && truncated[bestFit - 1] !== null) {
|
|
92
|
+
const lastItem = truncated[bestFit - 1];
|
|
93
|
+
truncated[bestFit - 1] = {
|
|
94
|
+
...lastItem,
|
|
95
|
+
_truncated: true,
|
|
96
|
+
remainingItems: arr.length - bestFit,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return { result: truncated, wasTruncated: true };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Truncate an object to fit within budget.
|
|
103
|
+
*/
|
|
104
|
+
function truncateObject(obj, targetChars, targetTokens, estimator) {
|
|
105
|
+
const keys = Object.keys(obj);
|
|
106
|
+
if (keys.length === 0) {
|
|
107
|
+
return { result: obj, wasTruncated: false };
|
|
108
|
+
}
|
|
109
|
+
// Try to fit as many top-level properties as possible
|
|
110
|
+
let left = 0;
|
|
111
|
+
let right = keys.length;
|
|
112
|
+
let bestFit = 0;
|
|
113
|
+
while (left <= right) {
|
|
114
|
+
const mid = Math.floor((left + right) / 2);
|
|
115
|
+
const subsetKeys = keys.slice(0, mid);
|
|
116
|
+
const subset = {};
|
|
117
|
+
for (const key of subsetKeys) {
|
|
118
|
+
subset[key] = obj[key];
|
|
119
|
+
}
|
|
120
|
+
const estimate = estimator.estimate(subset);
|
|
121
|
+
if (estimate <= targetTokens) {
|
|
122
|
+
bestFit = mid;
|
|
123
|
+
left = mid + 1;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
right = mid - 1;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// If we can fit all properties, no truncation needed
|
|
130
|
+
if (bestFit >= keys.length) {
|
|
131
|
+
return { result: obj, wasTruncated: false };
|
|
132
|
+
}
|
|
133
|
+
// Create truncated result
|
|
134
|
+
const subsetKeys = keys.slice(0, bestFit);
|
|
135
|
+
const truncated = {};
|
|
136
|
+
for (const key of subsetKeys) {
|
|
137
|
+
truncated[key] = obj[key];
|
|
138
|
+
}
|
|
139
|
+
// If we couldn't fit any properties, return minimal response
|
|
140
|
+
if (bestFit === 0) {
|
|
141
|
+
return {
|
|
142
|
+
result: { _truncated: true, reason: "budget_exceeded" },
|
|
143
|
+
wasTruncated: true
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
// Add truncation metadata
|
|
147
|
+
truncated._truncated = true;
|
|
148
|
+
truncated._truncatedFields = keys.slice(bestFit);
|
|
149
|
+
return { result: truncated, wasTruncated: true };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Apply budget enforcement to an envelope.
|
|
153
|
+
*
|
|
154
|
+
* @param envelope - The LAFS envelope to check
|
|
155
|
+
* @param budget - Maximum allowed tokens
|
|
156
|
+
* @param options - Budget enforcement options
|
|
157
|
+
* @returns Enforce result with potentially modified envelope
|
|
158
|
+
*/
|
|
159
|
+
export function applyBudgetEnforcement(envelope, budget, options = {}) {
|
|
160
|
+
const { truncateOnExceed = false, onBudgetExceeded } = options;
|
|
161
|
+
const estimator = new TokenEstimator();
|
|
162
|
+
// Estimate the result payload
|
|
163
|
+
const estimatedTokens = estimator.estimate(envelope.result);
|
|
164
|
+
// Add estimate to metadata
|
|
165
|
+
const tokenEstimate = {
|
|
166
|
+
estimated: estimatedTokens,
|
|
167
|
+
};
|
|
168
|
+
// Check if within budget
|
|
169
|
+
const withinBudget = estimatedTokens <= budget;
|
|
170
|
+
// If within budget, just add the estimate to metadata
|
|
171
|
+
if (withinBudget) {
|
|
172
|
+
return {
|
|
173
|
+
envelope: {
|
|
174
|
+
...envelope,
|
|
175
|
+
_meta: {
|
|
176
|
+
...envelope._meta,
|
|
177
|
+
_tokenEstimate: tokenEstimate,
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
withinBudget: true,
|
|
181
|
+
estimatedTokens,
|
|
182
|
+
budget,
|
|
183
|
+
truncated: false,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// Budget exceeded - call callback if provided
|
|
187
|
+
if (onBudgetExceeded) {
|
|
188
|
+
onBudgetExceeded(estimatedTokens, budget);
|
|
189
|
+
}
|
|
190
|
+
// If truncation is enabled, try to truncate
|
|
191
|
+
if (truncateOnExceed) {
|
|
192
|
+
const { result, wasTruncated } = truncateResult(envelope.result, budget, estimator);
|
|
193
|
+
const truncatedEstimate = estimator.estimate(result);
|
|
194
|
+
if (truncatedEstimate <= budget) {
|
|
195
|
+
return {
|
|
196
|
+
envelope: {
|
|
197
|
+
...envelope,
|
|
198
|
+
result,
|
|
199
|
+
_meta: {
|
|
200
|
+
...envelope._meta,
|
|
201
|
+
_tokenEstimate: {
|
|
202
|
+
estimated: truncatedEstimate,
|
|
203
|
+
truncated: true,
|
|
204
|
+
originalEstimate: estimatedTokens,
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
withinBudget: true,
|
|
209
|
+
estimatedTokens: truncatedEstimate,
|
|
210
|
+
budget,
|
|
211
|
+
truncated: true,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Return budget exceeded error
|
|
216
|
+
return {
|
|
217
|
+
envelope: {
|
|
218
|
+
...envelope,
|
|
219
|
+
success: false,
|
|
220
|
+
result: null,
|
|
221
|
+
error: createBudgetExceededError(estimatedTokens, budget),
|
|
222
|
+
_meta: {
|
|
223
|
+
...envelope._meta,
|
|
224
|
+
_tokenEstimate: tokenEstimate,
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
withinBudget: false,
|
|
228
|
+
estimatedTokens,
|
|
229
|
+
budget,
|
|
230
|
+
truncated: false,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Create a budget enforcement middleware function.
|
|
235
|
+
*
|
|
236
|
+
* @param budget - Maximum allowed tokens for response
|
|
237
|
+
* @param options - Budget enforcement options
|
|
238
|
+
* @returns Middleware function that enforces budget
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```typescript
|
|
242
|
+
* const middleware = withBudget(1000, { truncateOnExceed: true });
|
|
243
|
+
* const result = await middleware(envelope, async () => nextEnvelope);
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
export function withBudget(budget, options = {}) {
|
|
247
|
+
return async (envelope, next) => {
|
|
248
|
+
// Execute next middleware/handler
|
|
249
|
+
const result = await next();
|
|
250
|
+
// Apply budget enforcement to the result
|
|
251
|
+
const enforcement = applyBudgetEnforcement(result, budget, options);
|
|
252
|
+
return enforcement.envelope;
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Check if an envelope has exceeded its budget without modifying it.
|
|
257
|
+
*
|
|
258
|
+
* @param envelope - The LAFS envelope to check
|
|
259
|
+
* @param budget - Maximum allowed tokens
|
|
260
|
+
* @returns Budget check result
|
|
261
|
+
*/
|
|
262
|
+
export function checkBudget(envelope, budget) {
|
|
263
|
+
const estimator = new TokenEstimator();
|
|
264
|
+
const estimated = estimator.estimate(envelope.result);
|
|
265
|
+
return {
|
|
266
|
+
exceeded: estimated > budget,
|
|
267
|
+
estimated,
|
|
268
|
+
remaining: Math.max(0, budget - estimated),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Synchronous version of withBudget for non-async contexts.
|
|
273
|
+
*
|
|
274
|
+
* @param budget - Maximum allowed tokens for response
|
|
275
|
+
* @param options - Budget enforcement options
|
|
276
|
+
* @returns Middleware function that enforces budget synchronously
|
|
277
|
+
*/
|
|
278
|
+
export function withBudgetSync(budget, options = {}) {
|
|
279
|
+
return (envelope, next) => {
|
|
280
|
+
const result = next();
|
|
281
|
+
const enforcement = applyBudgetEnforcement(result, budget, options);
|
|
282
|
+
return enforcement.envelope;
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Higher-order function that wraps a handler with budget enforcement.
|
|
287
|
+
*
|
|
288
|
+
* @param handler - The handler function to wrap
|
|
289
|
+
* @param budget - Maximum allowed tokens
|
|
290
|
+
* @param options - Budget enforcement options
|
|
291
|
+
* @returns Wrapped handler with budget enforcement
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```typescript
|
|
295
|
+
* const myHandler = async (request: Request) => ({ success: true, result: { data } });
|
|
296
|
+
* const budgetedHandler = wrapWithBudget(myHandler, 1000, { truncateOnExceed: true });
|
|
297
|
+
* const result = await budgetedHandler(request);
|
|
298
|
+
* ```
|
|
299
|
+
*/
|
|
300
|
+
export function wrapWithBudget(handler, budget, options = {}) {
|
|
301
|
+
return async (...args) => {
|
|
302
|
+
const result = await handler(...args);
|
|
303
|
+
const enforcement = applyBudgetEnforcement(result, budget, options);
|
|
304
|
+
return enforcement.envelope;
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Compose multiple middleware functions into a single middleware.
|
|
309
|
+
* Middleware is executed in order (left to right).
|
|
310
|
+
*/
|
|
311
|
+
export function composeMiddleware(...middlewares) {
|
|
312
|
+
return async (envelope, next) => {
|
|
313
|
+
let index = 0;
|
|
314
|
+
async function dispatch(i) {
|
|
315
|
+
if (i >= middlewares.length) {
|
|
316
|
+
return next();
|
|
317
|
+
}
|
|
318
|
+
const middleware = middlewares[i];
|
|
319
|
+
if (!middleware) {
|
|
320
|
+
return dispatch(i + 1);
|
|
321
|
+
}
|
|
322
|
+
return middleware(envelope, () => dispatch(i + 1));
|
|
323
|
+
}
|
|
324
|
+
return dispatch(0);
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
export { TokenEstimator };
|
|
328
|
+
export { BUDGET_EXCEEDED_CODE };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LAFS Circuit Breaker Module
|
|
3
|
+
*
|
|
4
|
+
* Provides circuit breaker pattern for resilient service calls
|
|
5
|
+
*/
|
|
6
|
+
export type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';
|
|
7
|
+
export interface CircuitBreakerConfig {
|
|
8
|
+
name: string;
|
|
9
|
+
failureThreshold?: number;
|
|
10
|
+
resetTimeout?: number;
|
|
11
|
+
halfOpenMaxCalls?: number;
|
|
12
|
+
successThreshold?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface CircuitBreakerMetrics {
|
|
15
|
+
state: CircuitState;
|
|
16
|
+
failures: number;
|
|
17
|
+
successes: number;
|
|
18
|
+
lastFailureTime?: Date;
|
|
19
|
+
consecutiveSuccesses: number;
|
|
20
|
+
totalCalls: number;
|
|
21
|
+
}
|
|
22
|
+
export declare class CircuitBreakerError extends Error {
|
|
23
|
+
constructor(message: string);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Circuit breaker for protecting against cascading failures
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import { CircuitBreaker } from '@cleocode/lafs-protocol/circuit-breaker';
|
|
31
|
+
*
|
|
32
|
+
* const breaker = new CircuitBreaker({
|
|
33
|
+
* name: 'external-api',
|
|
34
|
+
* failureThreshold: 5,
|
|
35
|
+
* resetTimeout: 30000
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* try {
|
|
39
|
+
* const result = await breaker.execute(async () => {
|
|
40
|
+
* return await externalApi.call();
|
|
41
|
+
* });
|
|
42
|
+
* } catch (error) {
|
|
43
|
+
* if (error instanceof CircuitBreakerError) {
|
|
44
|
+
* console.log('Circuit breaker is open');
|
|
45
|
+
* }
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare class CircuitBreaker {
|
|
50
|
+
private config;
|
|
51
|
+
private state;
|
|
52
|
+
private failures;
|
|
53
|
+
private successes;
|
|
54
|
+
private lastFailureTime?;
|
|
55
|
+
private consecutiveSuccesses;
|
|
56
|
+
private totalCalls;
|
|
57
|
+
private halfOpenCalls;
|
|
58
|
+
private resetTimer?;
|
|
59
|
+
constructor(config: CircuitBreakerConfig);
|
|
60
|
+
/**
|
|
61
|
+
* Execute a function with circuit breaker protection
|
|
62
|
+
*/
|
|
63
|
+
execute<T>(fn: () => Promise<T>): Promise<T>;
|
|
64
|
+
/**
|
|
65
|
+
* Get current circuit breaker state
|
|
66
|
+
*/
|
|
67
|
+
getState(): CircuitState;
|
|
68
|
+
/**
|
|
69
|
+
* Get circuit breaker metrics
|
|
70
|
+
*/
|
|
71
|
+
getMetrics(): CircuitBreakerMetrics;
|
|
72
|
+
/**
|
|
73
|
+
* Manually open the circuit breaker
|
|
74
|
+
*/
|
|
75
|
+
forceOpen(): void;
|
|
76
|
+
/**
|
|
77
|
+
* Manually close the circuit breaker
|
|
78
|
+
*/
|
|
79
|
+
forceClose(): void;
|
|
80
|
+
private onSuccess;
|
|
81
|
+
private onFailure;
|
|
82
|
+
private transitionTo;
|
|
83
|
+
private shouldAttemptReset;
|
|
84
|
+
private scheduleReset;
|
|
85
|
+
private reset;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Circuit breaker registry for managing multiple breakers
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const registry = new CircuitBreakerRegistry();
|
|
93
|
+
*
|
|
94
|
+
* registry.add('payment-api', {
|
|
95
|
+
* failureThreshold: 3,
|
|
96
|
+
* resetTimeout: 60000
|
|
97
|
+
* });
|
|
98
|
+
*
|
|
99
|
+
* const paymentBreaker = registry.get('payment-api');
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export declare class CircuitBreakerRegistry {
|
|
103
|
+
private breakers;
|
|
104
|
+
add(name: string, config: Omit<CircuitBreakerConfig, 'name'>): CircuitBreaker;
|
|
105
|
+
get(name: string): CircuitBreaker | undefined;
|
|
106
|
+
getOrCreate(name: string, config: Omit<CircuitBreakerConfig, 'name'>): CircuitBreaker;
|
|
107
|
+
getAllMetrics(): Record<string, CircuitBreakerMetrics>;
|
|
108
|
+
resetAll(): void;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Create a circuit breaker middleware for Express
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```typescript
|
|
115
|
+
* app.use('/external-api', circuitBreakerMiddleware({
|
|
116
|
+
* name: 'external-api',
|
|
117
|
+
* failureThreshold: 5
|
|
118
|
+
* }));
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export declare function circuitBreakerMiddleware(config: CircuitBreakerConfig): (req: any, res: any, next: any) => Promise<void>;
|