@cleocode/lafs-protocol 1.0.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/README.md CHANGED
@@ -4,7 +4,10 @@
4
4
 
5
5
  LAFS defines a standard envelope format for structured responses from LLM-powered agents and tools. It complements transport protocols like [MCP](https://modelcontextprotocol.io/) and [A2A](https://github.com/google/A2A) by standardizing what comes back — not how it gets there.
6
6
 
7
- **Current version:** 0.5.0 | [Spec](lafs.md) | [Migration Guides](migrations/)
7
+ **Current version:** 1.0.0 | [📚 Documentation](https://codluv.gitbook.io/lafs-protocol/) | [Spec](lafs.md) | [Migration Guides](migrations/)
8
+
9
+ [![GitBook](https://img.shields.io/badge/docs-gitbook-blue)](https://codluv.gitbook.io/lafs-protocol/)
10
+ [![npm](https://img.shields.io/npm/v/@cleocode/lafs-protocol)](https://www.npmjs.com/package/@cleocode/lafs-protocol)
8
11
 
9
12
  ## What LAFS provides
10
13
 
@@ -17,7 +20,7 @@ LAFS defines a standard envelope format for structured responses from LLM-powere
17
20
  | **Tooling** | `src/` | TypeScript validation, conformance runner, CLI diagnostic tool |
18
21
  | **Tests** | `tests/` | 31 tests covering envelope, pagination, strict mode, error handling |
19
22
  | **Fixtures** | `fixtures/` | 14 JSON fixtures (valid + invalid) for conformance testing |
20
- | **Docs** | `docs/` | Positioning, vision, conformance tiers, deprecation policy |
23
+ | **Docs** | `docs/` | [GitBook documentation](https://codluv.gitbook.io/lafs-protocol/) with guides, SDK reference, and specs |
21
24
 
22
25
  ## Install
23
26
 
@@ -64,7 +67,7 @@ npm run typecheck
64
67
  {
65
68
  "$schema": "https://lafs.dev/schemas/v1/envelope.schema.json",
66
69
  "_meta": {
67
- "specVersion": "0.5.0",
70
+ "specVersion": "1.0.0",
68
71
  "schemaVersion": "1.0.0",
69
72
  "timestamp": "2026-02-13T00:00:00Z",
70
73
  "operation": "example.list",
@@ -139,6 +142,7 @@ CONTRIBUTING.md # Contributor guidelines, RFC process
139
142
 
140
143
  | Version | Phase | Description |
141
144
  |---------|-------|-------------|
145
+ | **v1.0.0** | **3** | **Production release: Token budgets, agent discovery, MCP integration, complete SDKs** |
142
146
  | v0.5.0 | 2B | Conditional pagination, MVI field selection/expansion, context ledger schema |
143
147
  | v0.4.0 | 2A | Optional page/error, extensions, strict/lenient mode, warnings |
144
148
  | v0.3.0 | 1 | Strategic positioning, vision alignment, adoption tiers |
@@ -0,0 +1,129 @@
1
+ /**
2
+ * LAFS A2A Bridge
3
+ *
4
+ * Integration with official @a2a-js/sdk for Agent-to-Agent communication.
5
+ * LAFS provides envelope wrapping and token budget support.
6
+ */
7
+ import { A2AClient } from '@a2a-js/sdk/client';
8
+ import { Artifact, Part, SendMessageResponse, JSONRPCErrorResponse } from '@a2a-js/sdk';
9
+ export interface LafsA2AConfig {
10
+ defaultBudget?: {
11
+ maxTokens?: number;
12
+ maxItems?: number;
13
+ };
14
+ envelopeResponses?: boolean;
15
+ }
16
+ export interface LafsEnvelope {
17
+ $schema: string;
18
+ _meta: {
19
+ specVersion: string;
20
+ operation: string;
21
+ requestId: string;
22
+ mvi: string;
23
+ _tokenEstimate?: {
24
+ estimated: number;
25
+ budget?: number;
26
+ };
27
+ };
28
+ success: boolean;
29
+ result: unknown;
30
+ error: null | {
31
+ code: string;
32
+ message: string;
33
+ category: string;
34
+ retryable: boolean;
35
+ };
36
+ }
37
+ /**
38
+ * Wrap A2A client with LAFS envelope support
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * import { ClientFactory } from '@a2a-js/sdk/client';
43
+ * import { withLafsEnvelope } from '@lafs/envelope/a2a';
44
+ *
45
+ * const factory = new ClientFactory();
46
+ * const a2aClient = await factory.createFromUrl('http://localhost:4000');
47
+ *
48
+ * const client = withLafsEnvelope(a2aClient, {
49
+ * envelopeResponses: true,
50
+ * defaultBudget: { maxTokens: 4000 }
51
+ * });
52
+ *
53
+ * const result = await client.sendMessage({
54
+ * message: { role: 'user', parts: [{ text: 'Hello' }] }
55
+ * });
56
+ *
57
+ * // Access LAFS envelope
58
+ * const envelope = result.getLafsEnvelope();
59
+ * console.log(envelope._meta._tokenEstimate);
60
+ * ```
61
+ */
62
+ export declare function withLafsEnvelope(client: A2AClient, config?: LafsA2AConfig): LafsA2AClient;
63
+ export declare class LafsA2AClient {
64
+ private client;
65
+ private config;
66
+ constructor(client: A2AClient, config: LafsA2AConfig);
67
+ sendMessage(params: {
68
+ message: {
69
+ role: 'user' | 'agent';
70
+ parts: Part[];
71
+ };
72
+ budget?: {
73
+ maxTokens?: number;
74
+ maxItems?: number;
75
+ };
76
+ }): Promise<LafsA2AResult>;
77
+ private generateId;
78
+ }
79
+ export declare class LafsA2AResult {
80
+ private result;
81
+ private budget;
82
+ constructor(result: SendMessageResponse, budget: {
83
+ maxTokens?: number;
84
+ maxItems?: number;
85
+ });
86
+ /**
87
+ * Get the underlying A2A result
88
+ */
89
+ getA2AResult(): SendMessageResponse;
90
+ /**
91
+ * Check if result is an error
92
+ */
93
+ isError(): boolean;
94
+ /**
95
+ * Get error details if result is an error
96
+ */
97
+ getError(): JSONRPCErrorResponse | null;
98
+ /**
99
+ * Extract LAFS envelope from A2A artifact
100
+ */
101
+ getLafsEnvelope(): LafsEnvelope | null;
102
+ /**
103
+ * Check if result contains LAFS envelope
104
+ */
105
+ hasLafsEnvelope(): boolean;
106
+ /**
107
+ * Get token estimate from envelope
108
+ */
109
+ getTokenEstimate(): {
110
+ estimated: number;
111
+ budget?: number;
112
+ } | null;
113
+ private isLafsEnvelope;
114
+ }
115
+ /**
116
+ * Create a LAFS artifact for A2A
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * const artifact = createLafsArtifact({
121
+ * success: true,
122
+ * result: { data: '...' },
123
+ * meta: { operation: 'analysis.run' }
124
+ * });
125
+ *
126
+ * task.artifacts.push(artifact);
127
+ * ```
128
+ */
129
+ export declare function createLafsArtifact(envelope: LafsEnvelope): Artifact;
@@ -0,0 +1,173 @@
1
+ /**
2
+ * LAFS A2A Bridge
3
+ *
4
+ * Integration with official @a2a-js/sdk for Agent-to-Agent communication.
5
+ * LAFS provides envelope wrapping and token budget support.
6
+ */
7
+ /**
8
+ * Wrap A2A client with LAFS envelope support
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { ClientFactory } from '@a2a-js/sdk/client';
13
+ * import { withLafsEnvelope } from '@lafs/envelope/a2a';
14
+ *
15
+ * const factory = new ClientFactory();
16
+ * const a2aClient = await factory.createFromUrl('http://localhost:4000');
17
+ *
18
+ * const client = withLafsEnvelope(a2aClient, {
19
+ * envelopeResponses: true,
20
+ * defaultBudget: { maxTokens: 4000 }
21
+ * });
22
+ *
23
+ * const result = await client.sendMessage({
24
+ * message: { role: 'user', parts: [{ text: 'Hello' }] }
25
+ * });
26
+ *
27
+ * // Access LAFS envelope
28
+ * const envelope = result.getLafsEnvelope();
29
+ * console.log(envelope._meta._tokenEstimate);
30
+ * ```
31
+ */
32
+ export function withLafsEnvelope(client, config = {}) {
33
+ return new LafsA2AClient(client, config);
34
+ }
35
+ export class LafsA2AClient {
36
+ client;
37
+ config;
38
+ constructor(client, config) {
39
+ this.client = client;
40
+ this.config = config;
41
+ }
42
+ async sendMessage(params) {
43
+ // Merge budget with defaults
44
+ const budget = {
45
+ ...this.config.defaultBudget,
46
+ ...params.budget
47
+ };
48
+ // Send via official A2A SDK
49
+ const result = await this.client.sendMessage({
50
+ message: {
51
+ kind: 'message',
52
+ messageId: this.generateId(),
53
+ role: params.message.role,
54
+ parts: params.message.parts
55
+ }
56
+ });
57
+ // Wrap result with LAFS envelope support
58
+ return new LafsA2AResult(result, budget);
59
+ }
60
+ generateId() {
61
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
62
+ const r = Math.random() * 16 | 0;
63
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
64
+ return v.toString(16);
65
+ });
66
+ }
67
+ }
68
+ export class LafsA2AResult {
69
+ result;
70
+ budget;
71
+ constructor(result, budget) {
72
+ this.result = result;
73
+ this.budget = budget;
74
+ }
75
+ /**
76
+ * Get the underlying A2A result
77
+ */
78
+ getA2AResult() {
79
+ return this.result;
80
+ }
81
+ /**
82
+ * Check if result is an error
83
+ */
84
+ isError() {
85
+ return 'error' in this.result;
86
+ }
87
+ /**
88
+ * Get error details if result is an error
89
+ */
90
+ getError() {
91
+ if (this.isError()) {
92
+ return this.result;
93
+ }
94
+ return null;
95
+ }
96
+ /**
97
+ * Extract LAFS envelope from A2A artifact
98
+ */
99
+ getLafsEnvelope() {
100
+ if (this.isError()) {
101
+ return null;
102
+ }
103
+ const successResult = this.result;
104
+ // Check if result is a Task
105
+ if (successResult.result?.kind !== 'task') {
106
+ return null;
107
+ }
108
+ const task = successResult.result;
109
+ if (!task.artifacts || task.artifacts.length === 0) {
110
+ return null;
111
+ }
112
+ // Find LAFS envelope in artifacts
113
+ for (const artifact of task.artifacts) {
114
+ for (const part of artifact.parts) {
115
+ if (part.kind === 'data' && this.isLafsEnvelope(part.data)) {
116
+ return part.data;
117
+ }
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+ /**
123
+ * Check if result contains LAFS envelope
124
+ */
125
+ hasLafsEnvelope() {
126
+ return this.getLafsEnvelope() !== null;
127
+ }
128
+ /**
129
+ * Get token estimate from envelope
130
+ */
131
+ getTokenEstimate() {
132
+ const envelope = this.getLafsEnvelope();
133
+ return envelope?._meta?._tokenEstimate ?? null;
134
+ }
135
+ isLafsEnvelope(data) {
136
+ return (typeof data === 'object' &&
137
+ data !== null &&
138
+ '$schema' in data &&
139
+ '_meta' in data &&
140
+ 'success' in data);
141
+ }
142
+ }
143
+ /**
144
+ * Create a LAFS artifact for A2A
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * const artifact = createLafsArtifact({
149
+ * success: true,
150
+ * result: { data: '...' },
151
+ * meta: { operation: 'analysis.run' }
152
+ * });
153
+ *
154
+ * task.artifacts.push(artifact);
155
+ * ```
156
+ */
157
+ export function createLafsArtifact(envelope) {
158
+ return {
159
+ artifactId: generateId(),
160
+ name: 'lafs_response',
161
+ parts: [{
162
+ kind: 'data',
163
+ data: envelope
164
+ }]
165
+ };
166
+ }
167
+ function generateId() {
168
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
169
+ const r = Math.random() * 16 | 0;
170
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
171
+ return v.toString(16);
172
+ });
173
+ }
@@ -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, type LafsA2AConfig, type LafsEnvelope } from './bridge.js';
@@ -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,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>;
@@ -0,0 +1,249 @@
1
+ /**
2
+ * LAFS Circuit Breaker Module
3
+ *
4
+ * Provides circuit breaker pattern for resilient service calls
5
+ */
6
+ export class CircuitBreakerError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = 'CircuitBreakerError';
10
+ }
11
+ }
12
+ /**
13
+ * Circuit breaker for protecting against cascading failures
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { CircuitBreaker } from '@cleocode/lafs-protocol/circuit-breaker';
18
+ *
19
+ * const breaker = new CircuitBreaker({
20
+ * name: 'external-api',
21
+ * failureThreshold: 5,
22
+ * resetTimeout: 30000
23
+ * });
24
+ *
25
+ * try {
26
+ * const result = await breaker.execute(async () => {
27
+ * return await externalApi.call();
28
+ * });
29
+ * } catch (error) {
30
+ * if (error instanceof CircuitBreakerError) {
31
+ * console.log('Circuit breaker is open');
32
+ * }
33
+ * }
34
+ * ```
35
+ */
36
+ export class CircuitBreaker {
37
+ config;
38
+ state = 'CLOSED';
39
+ failures = 0;
40
+ successes = 0;
41
+ lastFailureTime;
42
+ consecutiveSuccesses = 0;
43
+ totalCalls = 0;
44
+ halfOpenCalls = 0;
45
+ resetTimer;
46
+ constructor(config) {
47
+ this.config = config;
48
+ this.config = {
49
+ failureThreshold: 5,
50
+ resetTimeout: 30000,
51
+ halfOpenMaxCalls: 3,
52
+ successThreshold: 2,
53
+ ...config
54
+ };
55
+ }
56
+ /**
57
+ * Execute a function with circuit breaker protection
58
+ */
59
+ async execute(fn) {
60
+ this.totalCalls++;
61
+ if (this.state === 'OPEN') {
62
+ if (this.shouldAttemptReset()) {
63
+ this.transitionTo('HALF_OPEN');
64
+ }
65
+ else {
66
+ throw new CircuitBreakerError(`Circuit breaker '${this.config.name}' is OPEN`);
67
+ }
68
+ }
69
+ if (this.state === 'HALF_OPEN') {
70
+ if (this.halfOpenCalls >= (this.config.halfOpenMaxCalls || 3)) {
71
+ throw new CircuitBreakerError(`Circuit breaker '${this.config.name}' is HALF_OPEN (max calls reached)`);
72
+ }
73
+ this.halfOpenCalls++;
74
+ }
75
+ try {
76
+ const result = await fn();
77
+ this.onSuccess();
78
+ return result;
79
+ }
80
+ catch (error) {
81
+ this.onFailure();
82
+ throw error;
83
+ }
84
+ }
85
+ /**
86
+ * Get current circuit breaker state
87
+ */
88
+ getState() {
89
+ return this.state;
90
+ }
91
+ /**
92
+ * Get circuit breaker metrics
93
+ */
94
+ getMetrics() {
95
+ return {
96
+ state: this.state,
97
+ failures: this.failures,
98
+ successes: this.successes,
99
+ lastFailureTime: this.lastFailureTime,
100
+ consecutiveSuccesses: this.consecutiveSuccesses,
101
+ totalCalls: this.totalCalls
102
+ };
103
+ }
104
+ /**
105
+ * Manually open the circuit breaker
106
+ */
107
+ forceOpen() {
108
+ this.transitionTo('OPEN');
109
+ }
110
+ /**
111
+ * Manually close the circuit breaker
112
+ */
113
+ forceClose() {
114
+ this.transitionTo('CLOSED');
115
+ this.reset();
116
+ }
117
+ onSuccess() {
118
+ this.successes++;
119
+ this.consecutiveSuccesses++;
120
+ if (this.state === 'HALF_OPEN') {
121
+ if (this.consecutiveSuccesses >= (this.config.successThreshold || 2)) {
122
+ this.transitionTo('CLOSED');
123
+ this.reset();
124
+ }
125
+ }
126
+ }
127
+ onFailure() {
128
+ this.failures++;
129
+ this.consecutiveSuccesses = 0;
130
+ this.lastFailureTime = new Date();
131
+ if (this.state === 'HALF_OPEN') {
132
+ this.transitionTo('OPEN');
133
+ this.scheduleReset();
134
+ }
135
+ else if (this.state === 'CLOSED') {
136
+ if (this.failures >= (this.config.failureThreshold || 5)) {
137
+ this.transitionTo('OPEN');
138
+ this.scheduleReset();
139
+ }
140
+ }
141
+ }
142
+ transitionTo(newState) {
143
+ console.log(`Circuit breaker '${this.config.name}': ${this.state} -> ${newState}`);
144
+ this.state = newState;
145
+ if (newState === 'HALF_OPEN') {
146
+ this.halfOpenCalls = 0;
147
+ }
148
+ }
149
+ shouldAttemptReset() {
150
+ if (!this.lastFailureTime)
151
+ return true;
152
+ const elapsed = Date.now() - this.lastFailureTime.getTime();
153
+ return elapsed >= (this.config.resetTimeout || 30000);
154
+ }
155
+ scheduleReset() {
156
+ if (this.resetTimer) {
157
+ clearTimeout(this.resetTimer);
158
+ }
159
+ this.resetTimer = setTimeout(() => {
160
+ if (this.state === 'OPEN') {
161
+ this.transitionTo('HALF_OPEN');
162
+ }
163
+ }, this.config.resetTimeout || 30000);
164
+ }
165
+ reset() {
166
+ this.failures = 0;
167
+ this.consecutiveSuccesses = 0;
168
+ this.halfOpenCalls = 0;
169
+ if (this.resetTimer) {
170
+ clearTimeout(this.resetTimer);
171
+ this.resetTimer = undefined;
172
+ }
173
+ }
174
+ }
175
+ /**
176
+ * Circuit breaker registry for managing multiple breakers
177
+ *
178
+ * @example
179
+ * ```typescript
180
+ * const registry = new CircuitBreakerRegistry();
181
+ *
182
+ * registry.add('payment-api', {
183
+ * failureThreshold: 3,
184
+ * resetTimeout: 60000
185
+ * });
186
+ *
187
+ * const paymentBreaker = registry.get('payment-api');
188
+ * ```
189
+ */
190
+ export class CircuitBreakerRegistry {
191
+ breakers = new Map();
192
+ add(name, config) {
193
+ const breaker = new CircuitBreaker({ ...config, name });
194
+ this.breakers.set(name, breaker);
195
+ return breaker;
196
+ }
197
+ get(name) {
198
+ return this.breakers.get(name);
199
+ }
200
+ getOrCreate(name, config) {
201
+ let breaker = this.breakers.get(name);
202
+ if (!breaker) {
203
+ breaker = this.add(name, config);
204
+ }
205
+ return breaker;
206
+ }
207
+ getAllMetrics() {
208
+ const metrics = {};
209
+ this.breakers.forEach((breaker, name) => {
210
+ metrics[name] = breaker.getMetrics();
211
+ });
212
+ return metrics;
213
+ }
214
+ resetAll() {
215
+ this.breakers.forEach(breaker => breaker.forceClose());
216
+ }
217
+ }
218
+ /**
219
+ * Create a circuit breaker middleware for Express
220
+ *
221
+ * @example
222
+ * ```typescript
223
+ * app.use('/external-api', circuitBreakerMiddleware({
224
+ * name: 'external-api',
225
+ * failureThreshold: 5
226
+ * }));
227
+ * ```
228
+ */
229
+ export function circuitBreakerMiddleware(config) {
230
+ const breaker = new CircuitBreaker(config);
231
+ return async (req, res, next) => {
232
+ try {
233
+ await breaker.execute(async () => {
234
+ next();
235
+ });
236
+ }
237
+ catch (error) {
238
+ if (error instanceof CircuitBreakerError) {
239
+ res.status(503).json({
240
+ error: 'Service temporarily unavailable',
241
+ reason: 'Circuit breaker is open'
242
+ });
243
+ }
244
+ else {
245
+ throw error;
246
+ }
247
+ }
248
+ };
249
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * LAFS Health Check Module
3
+ *
4
+ * Provides health check endpoints for monitoring and orchestration
5
+ */
6
+ export interface HealthCheckConfig {
7
+ path?: string;
8
+ checks?: HealthCheckFunction[];
9
+ }
10
+ export type HealthCheckFunction = () => Promise<HealthCheckResult> | HealthCheckResult;
11
+ export interface HealthCheckResult {
12
+ name: string;
13
+ status: 'ok' | 'warning' | 'error';
14
+ message?: string;
15
+ duration?: number;
16
+ }
17
+ export interface HealthStatus {
18
+ status: 'healthy' | 'degraded' | 'unhealthy';
19
+ timestamp: string;
20
+ version: string;
21
+ uptime: number;
22
+ checks: HealthCheckResult[];
23
+ }
24
+ /**
25
+ * Health check middleware for Express applications
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * import express from 'express';
30
+ * import { healthCheck } from '@cleocode/lafs-protocol/health';
31
+ *
32
+ * const app = express();
33
+ *
34
+ * // Basic health check
35
+ * app.use('/health', healthCheck());
36
+ *
37
+ * // Custom health checks
38
+ * app.use('/health', healthCheck({
39
+ * checks: [
40
+ * async () => ({
41
+ * name: 'database',
42
+ * status: await checkDatabase() ? 'ok' : 'error'
43
+ * })
44
+ * ]
45
+ * }));
46
+ * ```
47
+ */
48
+ export declare function healthCheck(config?: HealthCheckConfig): (req: any, res: any) => Promise<void>;
49
+ /**
50
+ * Create a database health check
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * const dbCheck = createDatabaseHealthCheck({
55
+ * checkConnection: async () => await db.ping()
56
+ * });
57
+ *
58
+ * app.use('/health', healthCheck({
59
+ * checks: [dbCheck]
60
+ * }));
61
+ * ```
62
+ */
63
+ export declare function createDatabaseHealthCheck(config: {
64
+ checkConnection: () => Promise<boolean>;
65
+ name?: string;
66
+ }): HealthCheckFunction;
67
+ /**
68
+ * Create an external service health check
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * const apiCheck = createExternalServiceHealthCheck({
73
+ * name: 'payment-api',
74
+ * url: 'https://api.payment.com/health',
75
+ * timeout: 5000
76
+ * });
77
+ * ```
78
+ */
79
+ export declare function createExternalServiceHealthCheck(config: {
80
+ name: string;
81
+ url: string;
82
+ timeout?: number;
83
+ }): HealthCheckFunction;
84
+ /**
85
+ * Liveness probe - basic check that service is running
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * app.get('/health/live', livenessProbe());
90
+ * ```
91
+ */
92
+ export declare function livenessProbe(): (req: any, res: any) => void;
93
+ /**
94
+ * Readiness probe - check that service is ready to accept traffic
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * app.get('/health/ready', readinessProbe({
99
+ * checks: [dbCheck, cacheCheck]
100
+ * }));
101
+ * ```
102
+ */
103
+ export declare function readinessProbe(config?: {
104
+ checks?: HealthCheckFunction[];
105
+ }): (req: any, res: any) => Promise<void>;
@@ -0,0 +1,211 @@
1
+ /**
2
+ * LAFS Health Check Module
3
+ *
4
+ * Provides health check endpoints for monitoring and orchestration
5
+ */
6
+ /**
7
+ * Health check middleware for Express applications
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import express from 'express';
12
+ * import { healthCheck } from '@cleocode/lafs-protocol/health';
13
+ *
14
+ * const app = express();
15
+ *
16
+ * // Basic health check
17
+ * app.use('/health', healthCheck());
18
+ *
19
+ * // Custom health checks
20
+ * app.use('/health', healthCheck({
21
+ * checks: [
22
+ * async () => ({
23
+ * name: 'database',
24
+ * status: await checkDatabase() ? 'ok' : 'error'
25
+ * })
26
+ * ]
27
+ * }));
28
+ * ```
29
+ */
30
+ export function healthCheck(config = {}) {
31
+ const { path = '/health', checks = [] } = config;
32
+ const startTime = Date.now();
33
+ return async (req, res) => {
34
+ const timestamp = new Date().toISOString();
35
+ const checkResults = [];
36
+ // Run all health checks
37
+ for (const check of checks) {
38
+ const start = Date.now();
39
+ try {
40
+ const result = await check();
41
+ result.duration = Date.now() - start;
42
+ checkResults.push(result);
43
+ }
44
+ catch (error) {
45
+ checkResults.push({
46
+ name: 'unknown',
47
+ status: 'error',
48
+ message: error instanceof Error ? error.message : 'Check failed',
49
+ duration: Date.now() - start
50
+ });
51
+ }
52
+ }
53
+ // Add default checks
54
+ checkResults.push({
55
+ name: 'envelopeValidation',
56
+ status: 'ok'
57
+ });
58
+ checkResults.push({
59
+ name: 'tokenBudgets',
60
+ status: 'ok'
61
+ });
62
+ // Determine overall status
63
+ const hasErrors = checkResults.some(c => c.status === 'error');
64
+ const hasWarnings = checkResults.some(c => c.status === 'warning');
65
+ const status = hasErrors
66
+ ? 'unhealthy'
67
+ : hasWarnings
68
+ ? 'degraded'
69
+ : 'healthy';
70
+ const health = {
71
+ status,
72
+ timestamp,
73
+ version: process.env.npm_package_version || '1.1.0',
74
+ uptime: Math.floor((Date.now() - startTime) / 1000),
75
+ checks: checkResults
76
+ };
77
+ const statusCode = status === 'healthy' ? 200 : status === 'degraded' ? 200 : 503;
78
+ res.status(statusCode).json(health);
79
+ };
80
+ }
81
+ /**
82
+ * Create a database health check
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * const dbCheck = createDatabaseHealthCheck({
87
+ * checkConnection: async () => await db.ping()
88
+ * });
89
+ *
90
+ * app.use('/health', healthCheck({
91
+ * checks: [dbCheck]
92
+ * }));
93
+ * ```
94
+ */
95
+ export function createDatabaseHealthCheck(config) {
96
+ return async () => {
97
+ try {
98
+ const isConnected = await config.checkConnection();
99
+ return {
100
+ name: config.name || 'database',
101
+ status: isConnected ? 'ok' : 'error',
102
+ message: isConnected ? 'Connected' : 'Connection failed'
103
+ };
104
+ }
105
+ catch (error) {
106
+ return {
107
+ name: config.name || 'database',
108
+ status: 'error',
109
+ message: error instanceof Error ? error.message : 'Database check failed'
110
+ };
111
+ }
112
+ };
113
+ }
114
+ /**
115
+ * Create an external service health check
116
+ *
117
+ * @example
118
+ * ```typescript
119
+ * const apiCheck = createExternalServiceHealthCheck({
120
+ * name: 'payment-api',
121
+ * url: 'https://api.payment.com/health',
122
+ * timeout: 5000
123
+ * });
124
+ * ```
125
+ */
126
+ export function createExternalServiceHealthCheck(config) {
127
+ return async () => {
128
+ const start = Date.now();
129
+ try {
130
+ const controller = new AbortController();
131
+ const timeout = setTimeout(() => controller.abort(), config.timeout || 5000);
132
+ const response = await fetch(config.url, {
133
+ signal: controller.signal
134
+ });
135
+ clearTimeout(timeout);
136
+ return {
137
+ name: config.name,
138
+ status: response.ok ? 'ok' : 'error',
139
+ message: response.ok ? 'Service healthy' : `HTTP ${response.status}`,
140
+ duration: Date.now() - start
141
+ };
142
+ }
143
+ catch (error) {
144
+ return {
145
+ name: config.name,
146
+ status: 'error',
147
+ message: error instanceof Error ? error.message : 'Service unreachable',
148
+ duration: Date.now() - start
149
+ };
150
+ }
151
+ };
152
+ }
153
+ /**
154
+ * Liveness probe - basic check that service is running
155
+ *
156
+ * @example
157
+ * ```typescript
158
+ * app.get('/health/live', livenessProbe());
159
+ * ```
160
+ */
161
+ export function livenessProbe() {
162
+ return (req, res) => {
163
+ res.status(200).json({
164
+ status: 'alive',
165
+ timestamp: new Date().toISOString()
166
+ });
167
+ };
168
+ }
169
+ /**
170
+ * Readiness probe - check that service is ready to accept traffic
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * app.get('/health/ready', readinessProbe({
175
+ * checks: [dbCheck, cacheCheck]
176
+ * }));
177
+ * ```
178
+ */
179
+ export function readinessProbe(config = {}) {
180
+ return async (req, res) => {
181
+ const checkResults = [];
182
+ if (config.checks) {
183
+ for (const check of config.checks) {
184
+ try {
185
+ const result = await check();
186
+ checkResults.push(result);
187
+ }
188
+ catch (error) {
189
+ checkResults.push({
190
+ name: 'unknown',
191
+ status: 'error',
192
+ message: error instanceof Error ? error.message : 'Check failed'
193
+ });
194
+ }
195
+ }
196
+ }
197
+ const hasErrors = checkResults.some(c => c.status === 'error');
198
+ if (hasErrors) {
199
+ res.status(503).json({
200
+ status: 'not ready',
201
+ checks: checkResults
202
+ });
203
+ }
204
+ else {
205
+ res.status(200).json({
206
+ status: 'ready',
207
+ checks: checkResults
208
+ });
209
+ }
210
+ };
211
+ }
@@ -7,3 +7,7 @@ export * from "./tokenEstimator.js";
7
7
  export * from "./budgetEnforcement.js";
8
8
  export * from "./mcpAdapter.js";
9
9
  export * from "./discovery.js";
10
+ export * from "./health/index.js";
11
+ export * from "./shutdown/index.js";
12
+ export * from "./circuit-breaker/index.js";
13
+ export * from "./a2a/index.js";
package/dist/src/index.js CHANGED
@@ -7,3 +7,9 @@ export * from "./tokenEstimator.js";
7
7
  export * from "./budgetEnforcement.js";
8
8
  export * from "./mcpAdapter.js";
9
9
  export * from "./discovery.js";
10
+ // Operations & Reliability
11
+ export * from "./health/index.js";
12
+ export * from "./shutdown/index.js";
13
+ export * from "./circuit-breaker/index.js";
14
+ // A2A Integration
15
+ export * from "./a2a/index.js";
@@ -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-protocol/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-protocol/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
+ }
package/lafs.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # LAFS: LLM-Agent-First Specification
2
2
 
3
+ > 📚 **Documentation:** https://codluv.gitbook.io/lafs-protocol/
4
+ > **Version:** 1.0.0 | **Status:** Production Ready
5
+
3
6
  ## 1. Scope
4
7
 
5
8
  LAFS is a **response envelope contract specification**. It defines the canonical shape of structured responses — success envelopes, error envelopes, pagination metadata, and context preservation — for software systems whose primary consumer is an LLM agent or AI-driven tool.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/lafs-protocol",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "LLM-Agent-First Specification schemas and conformance tooling",
@@ -23,7 +23,7 @@
23
23
  "type": "git",
24
24
  "url": "git+https://github.com/kryptobaseddev/lafs-protocol.git"
25
25
  },
26
- "homepage": "https://github.com/kryptobaseddev/lafs-protocol#readme",
26
+ "homepage": "https://codluv.gitbook.io/lafs-protocol/",
27
27
  "bugs": {
28
28
  "url": "https://github.com/kryptobaseddev/lafs-protocol/issues"
29
29
  },
@@ -60,6 +60,7 @@
60
60
  "vitest": "^2.1.9"
61
61
  },
62
62
  "dependencies": {
63
+ "@a2a-js/sdk": "^0.3.10",
63
64
  "ajv": "^8.18.0",
64
65
  "ajv-formats": "^3.0.1",
65
66
  "express": "^5.2.1"