@cleocode/lafs 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +235 -0
  3. package/dist/schemas/v1/conformance-profiles.json +39 -0
  4. package/dist/schemas/v1/envelope.schema.json +306 -0
  5. package/dist/schemas/v1/error-registry.json +162 -0
  6. package/dist/src/a2a/bindings/grpc.d.ts +67 -0
  7. package/dist/src/a2a/bindings/grpc.js +148 -0
  8. package/dist/src/a2a/bindings/http.d.ts +102 -0
  9. package/dist/src/a2a/bindings/http.js +120 -0
  10. package/dist/src/a2a/bindings/index.d.ts +35 -0
  11. package/dist/src/a2a/bindings/index.js +79 -0
  12. package/dist/src/a2a/bindings/jsonrpc.d.ts +77 -0
  13. package/dist/src/a2a/bindings/jsonrpc.js +114 -0
  14. package/dist/src/a2a/bridge.d.ts +175 -0
  15. package/dist/src/a2a/bridge.js +286 -0
  16. package/dist/src/a2a/extensions.d.ts +121 -0
  17. package/dist/src/a2a/extensions.js +205 -0
  18. package/dist/src/a2a/index.d.ts +40 -0
  19. package/dist/src/a2a/index.js +76 -0
  20. package/dist/src/a2a/streaming.d.ts +74 -0
  21. package/dist/src/a2a/streaming.js +265 -0
  22. package/dist/src/a2a/task-lifecycle.d.ts +109 -0
  23. package/dist/src/a2a/task-lifecycle.js +313 -0
  24. package/dist/src/budgetEnforcement.d.ts +84 -0
  25. package/dist/src/budgetEnforcement.js +328 -0
  26. package/dist/src/circuit-breaker/index.d.ts +121 -0
  27. package/dist/src/circuit-breaker/index.js +249 -0
  28. package/dist/src/cli.d.ts +16 -0
  29. package/dist/src/cli.js +63 -0
  30. package/dist/src/compliance.d.ts +31 -0
  31. package/dist/src/compliance.js +89 -0
  32. package/dist/src/conformance.d.ts +7 -0
  33. package/dist/src/conformance.js +248 -0
  34. package/dist/src/conformanceProfiles.d.ts +11 -0
  35. package/dist/src/conformanceProfiles.js +34 -0
  36. package/dist/src/deprecationRegistry.d.ts +13 -0
  37. package/dist/src/deprecationRegistry.js +39 -0
  38. package/dist/src/discovery.d.ts +286 -0
  39. package/dist/src/discovery.js +350 -0
  40. package/dist/src/envelope.d.ts +60 -0
  41. package/dist/src/envelope.js +136 -0
  42. package/dist/src/errorRegistry.d.ts +28 -0
  43. package/dist/src/errorRegistry.js +36 -0
  44. package/dist/src/fieldExtraction.d.ts +67 -0
  45. package/dist/src/fieldExtraction.js +133 -0
  46. package/dist/src/flagResolver.d.ts +46 -0
  47. package/dist/src/flagResolver.js +47 -0
  48. package/dist/src/flagSemantics.d.ts +16 -0
  49. package/dist/src/flagSemantics.js +45 -0
  50. package/dist/src/health/index.d.ts +105 -0
  51. package/dist/src/health/index.js +220 -0
  52. package/dist/src/index.d.ts +24 -0
  53. package/dist/src/index.js +34 -0
  54. package/dist/src/mcpAdapter.d.ts +28 -0
  55. package/dist/src/mcpAdapter.js +281 -0
  56. package/dist/src/mviProjection.d.ts +19 -0
  57. package/dist/src/mviProjection.js +116 -0
  58. package/dist/src/problemDetails.d.ts +34 -0
  59. package/dist/src/problemDetails.js +45 -0
  60. package/dist/src/shutdown/index.d.ts +69 -0
  61. package/dist/src/shutdown/index.js +160 -0
  62. package/dist/src/tokenEstimator.d.ts +87 -0
  63. package/dist/src/tokenEstimator.js +238 -0
  64. package/dist/src/types.d.ts +135 -0
  65. package/dist/src/types.js +12 -0
  66. package/dist/src/validateEnvelope.d.ts +15 -0
  67. package/dist/src/validateEnvelope.js +31 -0
  68. package/lafs.md +819 -0
  69. package/package.json +88 -0
  70. package/schemas/v1/agent-card.schema.json +230 -0
  71. package/schemas/v1/conformance-profiles.json +39 -0
  72. package/schemas/v1/context-ledger.schema.json +70 -0
  73. package/schemas/v1/discovery.schema.json +132 -0
  74. package/schemas/v1/envelope.schema.json +306 -0
  75. package/schemas/v1/error-registry.json +162 -0
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Project an envelope to the declared MVI verbosity level.
3
+ * - 'minimal': Only fields required for agent control flow
4
+ * - 'standard': All commonly useful fields (current default behavior)
5
+ * - 'full': Complete echo-back including request parameters
6
+ * - 'custom': No projection (controlled by _fields)
7
+ */
8
+ export function projectEnvelope(envelope, mviLevel) {
9
+ const level = mviLevel ?? envelope._meta.mvi ?? 'standard';
10
+ switch (level) {
11
+ case 'minimal':
12
+ return projectMinimal(envelope);
13
+ case 'standard':
14
+ return projectStandard(envelope);
15
+ case 'full':
16
+ case 'custom':
17
+ return envelope;
18
+ }
19
+ }
20
+ function projectMinimal(env) {
21
+ const result = {
22
+ success: env.success,
23
+ _meta: projectMetaMinimal(env._meta),
24
+ };
25
+ if (env.success) {
26
+ result.result = env.result;
27
+ }
28
+ else if (env.error) {
29
+ result.error = projectErrorMinimal(env.error);
30
+ }
31
+ if (env._extensions && Object.keys(env._extensions).length > 0) {
32
+ result._extensions = env._extensions;
33
+ }
34
+ return result;
35
+ }
36
+ function projectStandard(env) {
37
+ const result = {
38
+ $schema: env.$schema,
39
+ success: env.success,
40
+ _meta: projectMetaStandard(env._meta),
41
+ };
42
+ if (env.success) {
43
+ result.result = env.result;
44
+ }
45
+ else {
46
+ result.result = null;
47
+ if (env.error) {
48
+ result.error = env.error;
49
+ }
50
+ }
51
+ if (env.page) {
52
+ result.page = env.page;
53
+ }
54
+ if (env._extensions && Object.keys(env._extensions).length > 0) {
55
+ result._extensions = env._extensions;
56
+ }
57
+ return result;
58
+ }
59
+ function projectMetaMinimal(meta) {
60
+ const m = meta;
61
+ const projected = {
62
+ requestId: m.requestId,
63
+ contextVersion: m.contextVersion,
64
+ };
65
+ if (m.sessionId)
66
+ projected.sessionId = m.sessionId;
67
+ if (m.warnings?.length)
68
+ projected.warnings = m.warnings;
69
+ if (m._tokenEstimate)
70
+ projected._tokenEstimate = m._tokenEstimate;
71
+ return projected;
72
+ }
73
+ function projectMetaStandard(meta) {
74
+ const m = meta;
75
+ const projected = {
76
+ timestamp: m.timestamp,
77
+ operation: m.operation,
78
+ requestId: m.requestId,
79
+ mvi: m.mvi,
80
+ contextVersion: m.contextVersion,
81
+ };
82
+ if (m.sessionId)
83
+ projected.sessionId = m.sessionId;
84
+ if (m.warnings?.length)
85
+ projected.warnings = m.warnings;
86
+ if (m._tokenEstimate)
87
+ projected._tokenEstimate = m._tokenEstimate;
88
+ return projected;
89
+ }
90
+ function projectErrorMinimal(error) {
91
+ const e = error;
92
+ const projected = {
93
+ code: e.code,
94
+ };
95
+ if (e.agentAction) {
96
+ projected.agentAction = e.agentAction;
97
+ }
98
+ if (e.retryAfterMs !== null && e.retryAfterMs !== undefined) {
99
+ projected.retryAfterMs = e.retryAfterMs;
100
+ }
101
+ if (e.details && Object.keys(e.details).length > 0) {
102
+ projected.details = e.details;
103
+ }
104
+ if (e.escalationRequired !== undefined) {
105
+ projected.escalationRequired = e.escalationRequired;
106
+ }
107
+ return projected;
108
+ }
109
+ /**
110
+ * Estimate token count for a projected envelope.
111
+ * Uses simple heuristic: 1 token per ~4 characters of JSON.
112
+ */
113
+ export function estimateProjectedTokens(projected) {
114
+ const json = JSON.stringify(projected);
115
+ return Math.ceil(json.length / 4);
116
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Core RFC 9457 Problem Details bridge.
3
+ * Converts LAFSError to RFC 9457-compliant Problem Details objects.
4
+ * Available for any transport, not just HTTP.
5
+ */
6
+ import type { LAFSError } from './types.js';
7
+ /** RFC 9457 Problem Details with LAFS extensions */
8
+ export interface LafsProblemDetails {
9
+ type: string;
10
+ title: string;
11
+ status: number;
12
+ detail: string;
13
+ instance?: string;
14
+ retryable: boolean;
15
+ agentAction?: string;
16
+ retryAfterMs?: number;
17
+ escalationRequired?: boolean;
18
+ suggestedAction?: string;
19
+ docUrl?: string;
20
+ [key: string]: unknown;
21
+ }
22
+ /**
23
+ * Convert a LAFSError to an RFC 9457 Problem Details object.
24
+ * Uses the error registry for HTTP status and type URI resolution.
25
+ *
26
+ * Agent-actionable fields (agentAction, escalationRequired, suggestedAction, docUrl)
27
+ * are extracted from error.details if present, enabling forward-compatible extension
28
+ * without requiring LAFSError type changes.
29
+ */
30
+ export declare function lafsErrorToProblemDetails(error: LAFSError, requestId?: string): LafsProblemDetails;
31
+ /**
32
+ * Content-Type for RFC 9457 Problem Details responses.
33
+ */
34
+ export declare const PROBLEM_DETAILS_CONTENT_TYPE: "application/problem+json";
@@ -0,0 +1,45 @@
1
+ import { getRegistryCode } from './errorRegistry.js';
2
+ /**
3
+ * Convert a LAFSError to an RFC 9457 Problem Details object.
4
+ * Uses the error registry for HTTP status and type URI resolution.
5
+ *
6
+ * Agent-actionable fields (agentAction, escalationRequired, suggestedAction, docUrl)
7
+ * are extracted from error.details if present, enabling forward-compatible extension
8
+ * without requiring LAFSError type changes.
9
+ */
10
+ export function lafsErrorToProblemDetails(error, requestId) {
11
+ const registry = getRegistryCode(error.code);
12
+ const pd = {
13
+ type: `https://lafs.dev/errors/v1/${error.code}`,
14
+ title: error.code,
15
+ status: registry?.httpStatus ?? 500,
16
+ detail: error.message,
17
+ retryable: error.retryable,
18
+ };
19
+ if (requestId)
20
+ pd.instance = requestId;
21
+ if (error.retryAfterMs != null)
22
+ pd.retryAfterMs = error.retryAfterMs;
23
+ // Map top-level agent-actionable fields
24
+ if (error.agentAction != null)
25
+ pd.agentAction = error.agentAction;
26
+ if (error.escalationRequired != null)
27
+ pd.escalationRequired = error.escalationRequired;
28
+ if (error.suggestedAction != null)
29
+ pd.suggestedAction = error.suggestedAction;
30
+ if (error.docUrl != null)
31
+ pd.docUrl = error.docUrl;
32
+ // Spread non-empty details as extension members
33
+ if (error.details && Object.keys(error.details).length > 0) {
34
+ for (const [key, value] of Object.entries(error.details)) {
35
+ if (!(key in pd)) {
36
+ pd[key] = value;
37
+ }
38
+ }
39
+ }
40
+ return pd;
41
+ }
42
+ /**
43
+ * Content-Type for RFC 9457 Problem Details responses.
44
+ */
45
+ export const PROBLEM_DETAILS_CONTENT_TYPE = 'application/problem+json';
@@ -0,0 +1,69 @@
1
+ /**
2
+ * LAFS Graceful Shutdown Module
3
+ *
4
+ * Handles graceful shutdown of LAFS servers
5
+ */
6
+ import { Server } from 'http';
7
+ export interface GracefulShutdownConfig {
8
+ timeout?: number;
9
+ signals?: NodeJS.Signals[];
10
+ onShutdown?: () => Promise<void> | void;
11
+ onClose?: () => Promise<void> | void;
12
+ }
13
+ export interface ShutdownState {
14
+ isShuttingDown: boolean;
15
+ activeConnections: number;
16
+ shutdownStartTime?: Date;
17
+ }
18
+ /**
19
+ * Enable graceful shutdown for an HTTP server
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * import express from 'express';
24
+ * import { gracefulShutdown } from '@cleocode/lafs/shutdown';
25
+ *
26
+ * const app = express();
27
+ * const server = app.listen(3000);
28
+ *
29
+ * gracefulShutdown(server, {
30
+ * timeout: 30000,
31
+ * signals: ['SIGTERM', 'SIGINT'],
32
+ * onShutdown: async () => {
33
+ * console.log('Shutting down...');
34
+ * await db.close();
35
+ * }
36
+ * });
37
+ * ```
38
+ */
39
+ export declare function gracefulShutdown(server: Server, config?: GracefulShutdownConfig): void;
40
+ /**
41
+ * Check if server is shutting down
42
+ */
43
+ export declare function isShuttingDown(): boolean;
44
+ /**
45
+ * Get shutdown state
46
+ */
47
+ export declare function getShutdownState(): ShutdownState;
48
+ /**
49
+ * Force immediate shutdown (emergency use only)
50
+ */
51
+ export declare function forceShutdown(exitCode?: number): void;
52
+ /**
53
+ * Middleware to reject requests during shutdown
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * app.use(shutdownMiddleware());
58
+ * ```
59
+ */
60
+ export declare function shutdownMiddleware(): (req: any, res: any, next: any) => void;
61
+ /**
62
+ * Wait for shutdown to complete
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * await waitForShutdown();
67
+ * ```
68
+ */
69
+ export declare function waitForShutdown(): Promise<void>;
@@ -0,0 +1,160 @@
1
+ /**
2
+ * LAFS Graceful Shutdown Module
3
+ *
4
+ * Handles graceful shutdown of LAFS servers
5
+ */
6
+ const state = {
7
+ isShuttingDown: false,
8
+ activeConnections: 0
9
+ };
10
+ /**
11
+ * Enable graceful shutdown for an HTTP server
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import express from 'express';
16
+ * import { gracefulShutdown } from '@cleocode/lafs/shutdown';
17
+ *
18
+ * const app = express();
19
+ * const server = app.listen(3000);
20
+ *
21
+ * gracefulShutdown(server, {
22
+ * timeout: 30000,
23
+ * signals: ['SIGTERM', 'SIGINT'],
24
+ * onShutdown: async () => {
25
+ * console.log('Shutting down...');
26
+ * await db.close();
27
+ * }
28
+ * });
29
+ * ```
30
+ */
31
+ export function gracefulShutdown(server, config = {}) {
32
+ const { timeout = 30000, signals = ['SIGTERM', 'SIGINT'], onShutdown, onClose } = config;
33
+ // Track active connections
34
+ server.on('connection', (socket) => {
35
+ if (state.isShuttingDown) {
36
+ socket.destroy();
37
+ return;
38
+ }
39
+ state.activeConnections++;
40
+ socket.on('close', () => {
41
+ state.activeConnections--;
42
+ });
43
+ });
44
+ // Handle shutdown signals
45
+ signals.forEach((signal) => {
46
+ process.on(signal, async () => {
47
+ console.log(`${signal} received, starting graceful shutdown...`);
48
+ await performShutdown(server, timeout, onShutdown, onClose);
49
+ });
50
+ });
51
+ // Handle uncaught errors
52
+ process.on('uncaughtException', async (error) => {
53
+ console.error('Uncaught exception:', error);
54
+ await performShutdown(server, timeout, onShutdown, onClose);
55
+ });
56
+ process.on('unhandledRejection', async (reason) => {
57
+ console.error('Unhandled rejection:', reason);
58
+ await performShutdown(server, timeout, onShutdown, onClose);
59
+ });
60
+ }
61
+ async function performShutdown(server, timeout, onShutdown, onClose) {
62
+ if (state.isShuttingDown) {
63
+ console.log('Shutdown already in progress...');
64
+ return;
65
+ }
66
+ state.isShuttingDown = true;
67
+ state.shutdownStartTime = new Date();
68
+ try {
69
+ // Call user shutdown handler
70
+ if (onShutdown) {
71
+ await Promise.race([
72
+ onShutdown(),
73
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Shutdown timeout')), timeout))
74
+ ]);
75
+ }
76
+ // Stop accepting new connections
77
+ server.close((err) => {
78
+ if (err) {
79
+ console.error('Error closing server:', err);
80
+ }
81
+ else {
82
+ console.log('Server closed');
83
+ }
84
+ });
85
+ // Wait for active connections to close
86
+ const startTime = Date.now();
87
+ while (state.activeConnections > 0 && Date.now() - startTime < timeout) {
88
+ console.log(`Waiting for ${state.activeConnections} connections to close...`);
89
+ await sleep(1000);
90
+ }
91
+ if (state.activeConnections > 0) {
92
+ console.warn(`Forcing shutdown with ${state.activeConnections} active connections`);
93
+ }
94
+ // Call user close handler
95
+ if (onClose) {
96
+ await onClose();
97
+ }
98
+ console.log('Graceful shutdown complete');
99
+ process.exit(0);
100
+ }
101
+ catch (error) {
102
+ console.error('Error during shutdown:', error);
103
+ process.exit(1);
104
+ }
105
+ }
106
+ function sleep(ms) {
107
+ return new Promise(resolve => setTimeout(resolve, ms));
108
+ }
109
+ /**
110
+ * Check if server is shutting down
111
+ */
112
+ export function isShuttingDown() {
113
+ return state.isShuttingDown;
114
+ }
115
+ /**
116
+ * Get shutdown state
117
+ */
118
+ export function getShutdownState() {
119
+ return { ...state };
120
+ }
121
+ /**
122
+ * Force immediate shutdown (emergency use only)
123
+ */
124
+ export function forceShutdown(exitCode = 1) {
125
+ console.log('Force shutting down...');
126
+ process.exit(exitCode);
127
+ }
128
+ /**
129
+ * Middleware to reject requests during shutdown
130
+ *
131
+ * @example
132
+ * ```typescript
133
+ * app.use(shutdownMiddleware());
134
+ * ```
135
+ */
136
+ export function shutdownMiddleware() {
137
+ return (req, res, next) => {
138
+ if (state.isShuttingDown) {
139
+ res.status(503).json({
140
+ error: 'Service is shutting down',
141
+ status: 'unavailable'
142
+ });
143
+ return;
144
+ }
145
+ next();
146
+ };
147
+ }
148
+ /**
149
+ * Wait for shutdown to complete
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * await waitForShutdown();
154
+ * ```
155
+ */
156
+ export async function waitForShutdown() {
157
+ while (!state.isShuttingDown) {
158
+ await sleep(100);
159
+ }
160
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * LAFS Token Estimator
3
+ *
4
+ * Provides character-based token estimation for LAFS envelopes and JSON payloads.
5
+ * Uses the approximation: 1 token ≈ 4 characters.
6
+ * Properly handles nested objects, arrays, Unicode graphemes, and circular references.
7
+ */
8
+ export interface TokenEstimatorOptions {
9
+ /**
10
+ * Characters per token ratio (default: 4)
11
+ */
12
+ charsPerToken?: number;
13
+ /**
14
+ * Maximum depth to traverse for circular reference detection (default: 100)
15
+ */
16
+ maxDepth?: number;
17
+ /**
18
+ * Maximum string length to process for Unicode grapheme counting (default: 100000)
19
+ */
20
+ maxStringLength?: number;
21
+ }
22
+ /**
23
+ * TokenEstimator provides character-based token counting for JSON payloads.
24
+ *
25
+ * Algorithm:
26
+ * 1. Serialize value to JSON (handling circular refs)
27
+ * 2. Count Unicode graphemes (not bytes)
28
+ * 3. Divide by charsPerToken ratio (default 4)
29
+ * 4. Add overhead for structural characters
30
+ */
31
+ export declare class TokenEstimator {
32
+ private options;
33
+ constructor(options?: TokenEstimatorOptions);
34
+ /**
35
+ * Estimate tokens for any JavaScript value.
36
+ * Handles circular references, nested objects, arrays, and Unicode.
37
+ *
38
+ * @param value - Any value to estimate
39
+ * @returns Estimated token count
40
+ */
41
+ estimate(value: unknown): number;
42
+ /**
43
+ * Estimate tokens from a JSON string.
44
+ * More efficient if you already have the JSON string.
45
+ *
46
+ * @param json - JSON string to estimate
47
+ * @returns Estimated token count
48
+ */
49
+ estimateJSON(json: string): number;
50
+ /**
51
+ * Internal recursive estimation with circular reference tracking.
52
+ */
53
+ private estimateWithTracking;
54
+ /**
55
+ * Estimate tokens for an array.
56
+ */
57
+ private estimateArray;
58
+ /**
59
+ * Estimate tokens for a plain object.
60
+ */
61
+ private estimateObject;
62
+ /**
63
+ * Check if a value can be safely serialized (no circular refs).
64
+ */
65
+ canSerialize(value: unknown): boolean;
66
+ /**
67
+ * Serialize value to JSON with circular reference handling.
68
+ * Circular refs are replaced with "[Circular]".
69
+ */
70
+ safeStringify(value: unknown): string;
71
+ /**
72
+ * Create a safe copy of a value with circular refs removed.
73
+ */
74
+ safeCopy<T>(value: T): T;
75
+ }
76
+ /**
77
+ * Global token estimator instance with default settings.
78
+ */
79
+ export declare const defaultEstimator: TokenEstimator;
80
+ /**
81
+ * Convenience function to estimate tokens for a value.
82
+ */
83
+ export declare function estimateTokens(value: unknown, options?: TokenEstimatorOptions): number;
84
+ /**
85
+ * Convenience function to estimate tokens from a JSON string.
86
+ */
87
+ export declare function estimateTokensJSON(json: string, options?: TokenEstimatorOptions): number;