@cleocode/lafs-protocol 1.0.0 → 1.2.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 |
@@ -67,6 +67,12 @@
67
67
  "type": "integer",
68
68
  "minimum": 0
69
69
  },
70
+ "sessionId": {
71
+ "type": "string",
72
+ "minLength": 1,
73
+ "maxLength": 256,
74
+ "description": "Session identifier for correlating multi-step agent workflows"
75
+ },
70
76
  "warnings": {
71
77
  "type": "array",
72
78
  "items": {
@@ -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>;