@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,286 @@
1
+ /**
2
+ * LAFS A2A Bridge v2.0
3
+ *
4
+ * Full integration with official @a2a-js/sdk for Agent-to-Agent communication.
5
+ * Implements A2A Protocol v1.0+ specification.
6
+ *
7
+ * Reference: specs/external/specification.md
8
+ */
9
+ // ============================================================================
10
+ // Imports - Use official A2A SDK types
11
+ // ============================================================================
12
+ import { AGENT_CARD_PATH, HTTP_EXTENSION_HEADER, } from '@a2a-js/sdk';
13
+ // ============================================================================
14
+ // Result Wrapper
15
+ // ============================================================================
16
+ /**
17
+ * Wrapper for A2A responses with LAFS envelope support
18
+ */
19
+ export class LafsA2AResult {
20
+ result;
21
+ config;
22
+ requestId;
23
+ constructor(result, config, requestId) {
24
+ this.result = result;
25
+ this.config = config;
26
+ this.requestId = requestId;
27
+ }
28
+ /**
29
+ * Get the raw A2A response
30
+ */
31
+ getA2AResult() {
32
+ return this.result;
33
+ }
34
+ /**
35
+ * Check if result is an error
36
+ */
37
+ isError() {
38
+ return 'error' in this.result;
39
+ }
40
+ /**
41
+ * Get error details if result is an error
42
+ */
43
+ getError() {
44
+ if (this.isError()) {
45
+ return this.result;
46
+ }
47
+ return null;
48
+ }
49
+ /**
50
+ * Get success result
51
+ */
52
+ getSuccess() {
53
+ if (!this.isError()) {
54
+ return this.result;
55
+ }
56
+ return null;
57
+ }
58
+ /**
59
+ * Extract Task from response (if present)
60
+ */
61
+ getTask() {
62
+ const success = this.getSuccess();
63
+ if (!success)
64
+ return null;
65
+ // Check if result is a Task (has id, contextId, status)
66
+ const result = success.result;
67
+ if (result && typeof result === 'object') {
68
+ // Task objects have these properties
69
+ if ('id' in result && 'status' in result) {
70
+ return result;
71
+ }
72
+ }
73
+ return null;
74
+ }
75
+ /**
76
+ * Extract Message from response (if present)
77
+ */
78
+ getMessage() {
79
+ const success = this.getSuccess();
80
+ if (!success)
81
+ return null;
82
+ const result = success.result;
83
+ if (result && typeof result === 'object') {
84
+ // Message objects have messageId
85
+ if ('messageId' in result && !('status' in result)) {
86
+ return result;
87
+ }
88
+ }
89
+ return null;
90
+ }
91
+ /**
92
+ * Check if response contains a LAFS envelope
93
+ */
94
+ hasLafsEnvelope() {
95
+ return this.getLafsEnvelope() !== null;
96
+ }
97
+ /**
98
+ * Extract LAFS envelope from A2A artifact
99
+ *
100
+ * A2A agents can return LAFS envelopes in artifacts for structured data.
101
+ * This method extracts the envelope from the first artifact containing one.
102
+ */
103
+ getLafsEnvelope() {
104
+ const task = this.getTask();
105
+ if (!task?.artifacts?.length)
106
+ return null;
107
+ for (const artifact of task.artifacts) {
108
+ for (const part of artifact.parts) {
109
+ if (this.isDataPart(part)) {
110
+ const data = part.data;
111
+ if (this.isLafsEnvelope(data)) {
112
+ return data;
113
+ }
114
+ }
115
+ }
116
+ }
117
+ return null;
118
+ }
119
+ /**
120
+ * Get token estimate from LAFS envelope
121
+ */
122
+ getTokenEstimate() {
123
+ const envelope = this.getLafsEnvelope();
124
+ if (!envelope?._meta)
125
+ return null;
126
+ // Access LAFS meta fields
127
+ const meta = envelope._meta;
128
+ return meta._tokenEstimate ?? null;
129
+ }
130
+ /**
131
+ * Get task status
132
+ */
133
+ getTaskStatus() {
134
+ return this.getTask()?.status ?? null;
135
+ }
136
+ /**
137
+ * Get task state
138
+ */
139
+ getTaskState() {
140
+ return this.getTaskStatus()?.state ?? null;
141
+ }
142
+ /**
143
+ * Check if task is in a terminal state
144
+ */
145
+ isTerminal() {
146
+ const state = this.getTaskState();
147
+ if (!state)
148
+ return false;
149
+ const terminalStates = [
150
+ 'completed',
151
+ 'failed',
152
+ 'canceled',
153
+ 'rejected'
154
+ ];
155
+ return terminalStates.includes(state);
156
+ }
157
+ /**
158
+ * Check if task requires input
159
+ */
160
+ isInputRequired() {
161
+ return this.getTaskState() === 'input-required';
162
+ }
163
+ /**
164
+ * Check if task requires authentication
165
+ */
166
+ isAuthRequired() {
167
+ return this.getTaskState() === 'auth-required';
168
+ }
169
+ /**
170
+ * Get all artifacts from task
171
+ */
172
+ getArtifacts() {
173
+ return this.getTask()?.artifacts ?? [];
174
+ }
175
+ isDataPart(part) {
176
+ return part.kind === 'data';
177
+ }
178
+ isLafsEnvelope(data) {
179
+ return (typeof data === 'object' &&
180
+ data !== null &&
181
+ '$schema' in data &&
182
+ '_meta' in data &&
183
+ 'success' in data);
184
+ }
185
+ }
186
+ // ============================================================================
187
+ // LAFS Artifact Creation Helpers
188
+ // ============================================================================
189
+ /**
190
+ * Create a LAFS envelope artifact for A2A
191
+ *
192
+ * @example
193
+ * ```typescript
194
+ * const envelope = createEnvelope({
195
+ * success: true,
196
+ * result: { data: '...' },
197
+ * meta: { operation: 'analysis.run' }
198
+ * });
199
+ *
200
+ * const artifact = createLafsArtifact(envelope);
201
+ * task.artifacts.push(artifact);
202
+ * ```
203
+ */
204
+ export function createLafsArtifact(envelope) {
205
+ return {
206
+ artifactId: generateId(),
207
+ name: 'lafs_response',
208
+ description: 'LAFS-formatted response envelope',
209
+ parts: [{
210
+ kind: 'data',
211
+ data: envelope,
212
+ }],
213
+ metadata: {
214
+ 'x-lafs-version': '2.0.0',
215
+ 'x-content-type': 'application/vnd.lafs.envelope+json',
216
+ },
217
+ };
218
+ }
219
+ /**
220
+ * Create a text artifact
221
+ */
222
+ export function createTextArtifact(text, name = 'text_response') {
223
+ const part = {
224
+ kind: 'text',
225
+ text,
226
+ };
227
+ return {
228
+ artifactId: generateId(),
229
+ name,
230
+ parts: [part],
231
+ };
232
+ }
233
+ /**
234
+ * Create a file artifact
235
+ */
236
+ export function createFileArtifact(fileUrl, mediaType, filename) {
237
+ const part = {
238
+ kind: 'file',
239
+ file: {
240
+ kind: 'uri',
241
+ uri: fileUrl,
242
+ mimeType: mediaType,
243
+ ...(filename && { filename }),
244
+ },
245
+ };
246
+ return {
247
+ artifactId: generateId(),
248
+ name: filename || 'file',
249
+ parts: [part],
250
+ };
251
+ }
252
+ function generateId() {
253
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
254
+ const r = Math.random() * 16 | 0;
255
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
256
+ return v.toString(16);
257
+ });
258
+ }
259
+ // ============================================================================
260
+ // Extension Helpers
261
+ // ============================================================================
262
+ /**
263
+ * Check if an extension is required
264
+ */
265
+ export function isExtensionRequired(agentCard, extensionUri) {
266
+ return agentCard.capabilities?.extensions?.some(ext => ext.uri === extensionUri && ext.required) ?? false;
267
+ }
268
+ /**
269
+ * Get extension parameters
270
+ */
271
+ export function getExtensionParams(agentCard, extensionUri) {
272
+ return agentCard.capabilities?.extensions?.find(ext => ext.uri === extensionUri)?.params;
273
+ }
274
+ // ============================================================================
275
+ // Constants
276
+ // ============================================================================
277
+ /**
278
+ * A2A Agent Card well-known path
279
+ * Reference: specs/external/agent-discovery.md
280
+ */
281
+ export { AGENT_CARD_PATH };
282
+ /**
283
+ * HTTP header for A2A Extensions
284
+ * Reference: specs/external/extensions.md
285
+ */
286
+ export { HTTP_EXTENSION_HEADER };
@@ -0,0 +1,121 @@
1
+ /**
2
+ * A2A Extensions Support
3
+ *
4
+ * Extension negotiation, LAFS extension builder, and Express middleware
5
+ * for A2A Protocol v1.0+ compliance.
6
+ *
7
+ * Reference: specs/external/extensions.md, A2A spec Section 3.2.6
8
+ */
9
+ import type { AgentExtension } from '@a2a-js/sdk';
10
+ import type { RequestHandler } from 'express';
11
+ /** Canonical LAFS extension URI */
12
+ export declare const LAFS_EXTENSION_URI = "https://lafs.dev/extensions/envelope/v1";
13
+ /** Canonical A2A Extensions header per spec Section 3.2.6 */
14
+ export declare const A2A_EXTENSIONS_HEADER = "A2A-Extensions";
15
+ /** LAFS extension parameters declared in Agent Card */
16
+ export interface LafsExtensionParams {
17
+ supportsContextLedger: boolean;
18
+ supportsTokenBudgets: boolean;
19
+ envelopeSchema: string;
20
+ kind?: ExtensionKind;
21
+ }
22
+ export type ExtensionKind = 'data-only' | 'profile' | 'method' | 'state-machine';
23
+ /** Result of extension negotiation between client and agent */
24
+ export interface ExtensionNegotiationResult {
25
+ /** URIs requested by the client */
26
+ requested: string[];
27
+ /** URIs that matched agent-declared extensions */
28
+ activated: string[];
29
+ /** Requested URIs not declared by the agent (ignored per spec) */
30
+ unsupported: string[];
31
+ /** Agent-required URIs not present in client request */
32
+ missingRequired: string[];
33
+ /** Activated extensions grouped by declared kind (when provided) */
34
+ activatedByKind: Partial<Record<ExtensionKind, string[]>>;
35
+ }
36
+ /**
37
+ * Parse A2A-Extensions header value into URI array.
38
+ * Splits comma-separated URIs, trims whitespace, removes empty strings.
39
+ */
40
+ export declare function parseExtensionsHeader(headerValue: string | undefined): string[];
41
+ /**
42
+ * Negotiate extensions between client-requested and agent-declared sets.
43
+ * Unsupported extensions are ignored per spec. Required agent extensions
44
+ * not requested by the client are flagged in missingRequired.
45
+ */
46
+ export declare function negotiateExtensions(requestedUris: string[], agentExtensions: AgentExtension[]): ExtensionNegotiationResult;
47
+ /**
48
+ * Format activated extension URIs into header value.
49
+ * Joins URIs with comma separator.
50
+ */
51
+ export declare function formatExtensionsHeader(activatedUris: string[]): string;
52
+ /** Options for building the LAFS extension declaration */
53
+ export interface BuildLafsExtensionOptions {
54
+ required?: boolean;
55
+ supportsContextLedger?: boolean;
56
+ supportsTokenBudgets?: boolean;
57
+ envelopeSchema?: string;
58
+ }
59
+ /**
60
+ * Build an A2A AgentExtension object declaring LAFS support.
61
+ * Suitable for inclusion in Agent Card capabilities.extensions[].
62
+ */
63
+ export declare function buildLafsExtension(options?: BuildLafsExtensionOptions): AgentExtension;
64
+ export interface BuildExtensionOptions {
65
+ uri: string;
66
+ description: string;
67
+ required?: boolean;
68
+ kind: ExtensionKind;
69
+ params?: Record<string, unknown>;
70
+ }
71
+ export declare function buildExtension(options: BuildExtensionOptions): AgentExtension;
72
+ export declare function isValidExtensionKind(kind: string): kind is ExtensionKind;
73
+ export declare function validateExtensionDeclaration(extension: AgentExtension): {
74
+ valid: boolean;
75
+ error?: string;
76
+ };
77
+ /**
78
+ * Error thrown when required A2A extensions are not supported by the client.
79
+ * Code -32008 (not in SDK, which stops at -32007).
80
+ */
81
+ export declare class ExtensionSupportRequiredError extends Error {
82
+ readonly code: -32008;
83
+ readonly httpStatus: 400;
84
+ readonly grpcStatus: "FAILED_PRECONDITION";
85
+ readonly missingExtensions: string[];
86
+ constructor(missingExtensions: string[]);
87
+ /** Convert to JSON-RPC error object */
88
+ toJSONRPCError(): {
89
+ code: number;
90
+ message: string;
91
+ data: Record<string, unknown>;
92
+ };
93
+ /** Convert to RFC 9457 Problem Details object with agent-actionable fields */
94
+ toProblemDetails(): Record<string, unknown> & {
95
+ agentAction: string;
96
+ };
97
+ /** Convert to a LAFSError-compatible object */
98
+ toLafsError(): {
99
+ code: string;
100
+ message: string;
101
+ category: 'CONTRACT';
102
+ retryable: boolean;
103
+ retryAfterMs: null;
104
+ details: Record<string, unknown>;
105
+ };
106
+ }
107
+ /** Options for the extension negotiation middleware */
108
+ export interface ExtensionNegotiationMiddlewareOptions {
109
+ /** Agent-declared extensions to negotiate against */
110
+ extensions: AgentExtension[];
111
+ /** Return 400 if required extensions are missing (default: true) */
112
+ enforceRequired?: boolean;
113
+ }
114
+ /**
115
+ * Express middleware for A2A extension negotiation.
116
+ *
117
+ * Parses A2A-Extensions header (and X-A2A-Extensions for SDK compat),
118
+ * validates against declared extensions, sets response header with
119
+ * activated extensions, attaches result to res.locals.a2aExtensions.
120
+ */
121
+ export declare function extensionNegotiationMiddleware(options: ExtensionNegotiationMiddlewareOptions): RequestHandler;
@@ -0,0 +1,205 @@
1
+ /**
2
+ * A2A Extensions Support
3
+ *
4
+ * Extension negotiation, LAFS extension builder, and Express middleware
5
+ * for A2A Protocol v1.0+ compliance.
6
+ *
7
+ * Reference: specs/external/extensions.md, A2A spec Section 3.2.6
8
+ */
9
+ // ============================================================================
10
+ // Constants
11
+ // ============================================================================
12
+ /** Canonical LAFS extension URI */
13
+ export const LAFS_EXTENSION_URI = 'https://lafs.dev/extensions/envelope/v1';
14
+ /** Canonical A2A Extensions header per spec Section 3.2.6 */
15
+ export const A2A_EXTENSIONS_HEADER = 'A2A-Extensions';
16
+ /**
17
+ * SDK header name (differs from spec).
18
+ * Middleware checks both for SDK compatibility.
19
+ */
20
+ const SDK_EXTENSIONS_HEADER = 'x-a2a-extensions';
21
+ const VALID_EXTENSION_KINDS = ['data-only', 'profile', 'method', 'state-machine'];
22
+ // ============================================================================
23
+ // Functions
24
+ // ============================================================================
25
+ /**
26
+ * Parse A2A-Extensions header value into URI array.
27
+ * Splits comma-separated URIs, trims whitespace, removes empty strings.
28
+ */
29
+ export function parseExtensionsHeader(headerValue) {
30
+ if (!headerValue)
31
+ return [];
32
+ return headerValue
33
+ .split(',')
34
+ .map(uri => uri.trim())
35
+ .filter(Boolean);
36
+ }
37
+ /**
38
+ * Negotiate extensions between client-requested and agent-declared sets.
39
+ * Unsupported extensions are ignored per spec. Required agent extensions
40
+ * not requested by the client are flagged in missingRequired.
41
+ */
42
+ export function negotiateExtensions(requestedUris, agentExtensions) {
43
+ const declared = new Map(agentExtensions.map(ext => [ext.uri, ext]));
44
+ const activated = [];
45
+ const unsupported = [];
46
+ for (const uri of requestedUris) {
47
+ if (declared.has(uri)) {
48
+ activated.push(uri);
49
+ }
50
+ else {
51
+ unsupported.push(uri);
52
+ }
53
+ }
54
+ const requestedSet = new Set(requestedUris);
55
+ const missingRequired = [];
56
+ for (const ext of agentExtensions) {
57
+ if (ext.required && !requestedSet.has(ext.uri)) {
58
+ missingRequired.push(ext.uri);
59
+ }
60
+ }
61
+ const activatedByKind = {};
62
+ for (const uri of activated) {
63
+ const ext = declared.get(uri);
64
+ const kind = ext?.params && typeof ext.params === 'object'
65
+ ? ext.params['kind']
66
+ : undefined;
67
+ if (typeof kind === 'string' && VALID_EXTENSION_KINDS.includes(kind)) {
68
+ const typedKind = kind;
69
+ if (!activatedByKind[typedKind]) {
70
+ activatedByKind[typedKind] = [];
71
+ }
72
+ activatedByKind[typedKind].push(uri);
73
+ }
74
+ }
75
+ return { requested: requestedUris, activated, unsupported, missingRequired, activatedByKind };
76
+ }
77
+ /**
78
+ * Format activated extension URIs into header value.
79
+ * Joins URIs with comma separator.
80
+ */
81
+ export function formatExtensionsHeader(activatedUris) {
82
+ return activatedUris.join(',');
83
+ }
84
+ /**
85
+ * Build an A2A AgentExtension object declaring LAFS support.
86
+ * Suitable for inclusion in Agent Card capabilities.extensions[].
87
+ */
88
+ export function buildLafsExtension(options) {
89
+ return {
90
+ uri: LAFS_EXTENSION_URI,
91
+ description: 'LAFS envelope protocol for structured agent responses',
92
+ required: options?.required ?? false,
93
+ params: {
94
+ supportsContextLedger: options?.supportsContextLedger ?? false,
95
+ supportsTokenBudgets: options?.supportsTokenBudgets ?? false,
96
+ envelopeSchema: options?.envelopeSchema ?? 'https://lafs.dev/schemas/v1/envelope.schema.json',
97
+ kind: 'profile',
98
+ },
99
+ };
100
+ }
101
+ export function buildExtension(options) {
102
+ return {
103
+ uri: options.uri,
104
+ description: options.description,
105
+ required: options.required ?? false,
106
+ params: {
107
+ kind: options.kind,
108
+ ...(options.params ?? {}),
109
+ },
110
+ };
111
+ }
112
+ export function isValidExtensionKind(kind) {
113
+ return VALID_EXTENSION_KINDS.includes(kind);
114
+ }
115
+ export function validateExtensionDeclaration(extension) {
116
+ const kind = extension.params && typeof extension.params === 'object'
117
+ ? extension.params['kind']
118
+ : undefined;
119
+ if (kind === undefined) {
120
+ return { valid: true };
121
+ }
122
+ if (typeof kind !== 'string' || !isValidExtensionKind(kind)) {
123
+ return { valid: false, error: `invalid extension kind: ${String(kind)}` };
124
+ }
125
+ return { valid: true };
126
+ }
127
+ // ============================================================================
128
+ // Error Class
129
+ // ============================================================================
130
+ /**
131
+ * Error thrown when required A2A extensions are not supported by the client.
132
+ * Code -32008 (not in SDK, which stops at -32007).
133
+ */
134
+ export class ExtensionSupportRequiredError extends Error {
135
+ code = -32008;
136
+ httpStatus = 400;
137
+ grpcStatus = 'FAILED_PRECONDITION';
138
+ missingExtensions;
139
+ constructor(missingExtensions) {
140
+ super(`Required extensions not supported: ${missingExtensions.join(', ')}`);
141
+ this.name = 'ExtensionSupportRequiredError';
142
+ this.missingExtensions = missingExtensions;
143
+ }
144
+ /** Convert to JSON-RPC error object */
145
+ toJSONRPCError() {
146
+ return {
147
+ code: this.code,
148
+ message: this.message,
149
+ data: { missingExtensions: this.missingExtensions },
150
+ };
151
+ }
152
+ /** Convert to RFC 9457 Problem Details object with agent-actionable fields */
153
+ toProblemDetails() {
154
+ return {
155
+ type: 'https://a2a-protocol.org/errors/extension-support-required',
156
+ title: 'Extension Support Required',
157
+ status: this.httpStatus,
158
+ detail: this.message,
159
+ missingExtensions: this.missingExtensions,
160
+ agentAction: 'retry_modified',
161
+ };
162
+ }
163
+ /** Convert to a LAFSError-compatible object */
164
+ toLafsError() {
165
+ return {
166
+ code: 'E_CONTRACT_EXTENSION_REQUIRED',
167
+ message: this.message,
168
+ category: 'CONTRACT',
169
+ retryable: true,
170
+ retryAfterMs: null,
171
+ details: {
172
+ missingExtensions: this.missingExtensions,
173
+ agentAction: 'retry_modified',
174
+ },
175
+ };
176
+ }
177
+ }
178
+ /**
179
+ * Express middleware for A2A extension negotiation.
180
+ *
181
+ * Parses A2A-Extensions header (and X-A2A-Extensions for SDK compat),
182
+ * validates against declared extensions, sets response header with
183
+ * activated extensions, attaches result to res.locals.a2aExtensions.
184
+ */
185
+ export function extensionNegotiationMiddleware(options) {
186
+ const { extensions, enforceRequired = true } = options;
187
+ return function extensionNegotiationHandler(req, res, next) {
188
+ // Check both canonical and SDK headers (Express normalizes to lowercase)
189
+ const headerValue = req.headers[A2A_EXTENSIONS_HEADER.toLowerCase()] ??
190
+ req.headers[SDK_EXTENSIONS_HEADER];
191
+ const requested = parseExtensionsHeader(headerValue);
192
+ const result = negotiateExtensions(requested, extensions);
193
+ if (enforceRequired && result.missingRequired.length > 0) {
194
+ const error = new ExtensionSupportRequiredError(result.missingRequired);
195
+ res.setHeader('Content-Type', 'application/problem+json');
196
+ res.status(error.httpStatus).json(error.toProblemDetails());
197
+ return;
198
+ }
199
+ if (result.activated.length > 0) {
200
+ res.setHeader(A2A_EXTENSIONS_HEADER, formatExtensionsHeader(result.activated));
201
+ }
202
+ res.locals['a2aExtensions'] = result;
203
+ next();
204
+ };
205
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * LAFS Agent-to-Agent (A2A) Integration v2.0
3
+ *
4
+ * Full integration with the official @a2a-js/sdk for Agent-to-Agent communication.
5
+ * Implements A2A Protocol v1.0+ specification.
6
+ *
7
+ * Reference: specs/external/specification.md
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import type { AgentCard, Task } from '@cleocode/lafs/a2a';
12
+ * import {
13
+ * createLafsArtifact,
14
+ * createTextArtifact,
15
+ * LafsA2AResult,
16
+ * isExtensionRequired
17
+ * } from '@cleocode/lafs/a2a';
18
+ *
19
+ * // Use A2A SDK directly for client operations
20
+ * import { ClientFactory } from '@a2a-js/sdk/client';
21
+ *
22
+ * const factory = new ClientFactory();
23
+ * const client = await factory.createFromUrl('https://agent.example.com');
24
+ * const result = await client.sendMessage({...});
25
+ *
26
+ * // Wrap result with LAFS helpers
27
+ * const lafsResult = new LafsA2AResult(result, {}, 'req-001');
28
+ * const envelope = lafsResult.getLafsEnvelope();
29
+ * ```
30
+ */
31
+ export { LafsA2AResult, createLafsArtifact, createTextArtifact, createFileArtifact, isExtensionRequired, getExtensionParams, AGENT_CARD_PATH, HTTP_EXTENSION_HEADER, } from './bridge.js';
32
+ export { LAFS_EXTENSION_URI, A2A_EXTENSIONS_HEADER, parseExtensionsHeader, negotiateExtensions, formatExtensionsHeader, buildLafsExtension, ExtensionSupportRequiredError, extensionNegotiationMiddleware, } from './extensions.js';
33
+ export type { LafsExtensionParams, ExtensionNegotiationResult, BuildLafsExtensionOptions, ExtensionNegotiationMiddlewareOptions, } from './extensions.js';
34
+ export { TERMINAL_STATES, INTERRUPTED_STATES, VALID_TRANSITIONS, isValidTransition, isTerminalState, isInterruptedState, InvalidStateTransitionError, TaskImmutabilityError, TaskNotFoundError, TaskRefinementError, TaskManager, attachLafsEnvelope, } from './task-lifecycle.js';
35
+ export { TaskEventBus, PushNotificationConfigStore, PushNotificationDispatcher, TaskArtifactAssembler, streamTaskEvents, } from './streaming.js';
36
+ export type { TaskStreamEvent, StreamIteratorOptions, PushNotificationDeliveryResult, PushTransport, } from './streaming.js';
37
+ export type { CreateTaskOptions, ListTasksOptions, ListTasksResult, } from './task-lifecycle.js';
38
+ export * from './bindings/index.js';
39
+ export type { LafsA2AConfig, LafsSendMessageParams, } from './bridge.js';
40
+ export type { Task, TaskState, TaskStatus, Artifact, Part, Message, AgentCard, AgentSkill, AgentCapabilities, AgentExtension, PushNotificationConfig, MessageSendConfiguration, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, SendMessageResponse, SendMessageSuccessResponse, JSONRPCErrorResponse, TextPart, DataPart, FilePart, } from '@a2a-js/sdk';