@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 Agent Discovery - Express/Fastify Middleware
3
+ * Serves A2A-compliant Agent Card at /.well-known/agent-card.json
4
+ * Maintains backward compatibility with legacy /.well-known/lafs.json
5
+ *
6
+ * A2A v1.0+ Compliant Implementation
7
+ * Reference: specs/external/agent-discovery.md
8
+ */
9
+ import type { RequestHandler } from "express";
10
+ /**
11
+ * A2A Agent Provider information
12
+ */
13
+ export interface AgentProvider {
14
+ /** Organization URL */
15
+ url: string;
16
+ /** Organization name */
17
+ organization: string;
18
+ }
19
+ /**
20
+ * A2A Agent Capabilities
21
+ */
22
+ export interface AgentCapabilities {
23
+ /** Supports streaming responses */
24
+ streaming?: boolean;
25
+ /** Supports push notifications */
26
+ pushNotifications?: boolean;
27
+ /** Supports extended agent card */
28
+ extendedAgentCard?: boolean;
29
+ /** Supported extensions */
30
+ extensions?: AgentExtension[];
31
+ }
32
+ /**
33
+ * A2A Agent Extension declaration
34
+ */
35
+ export interface AgentExtension {
36
+ /** Extension URI (unique identifier) */
37
+ uri: string;
38
+ /** Human-readable description */
39
+ description: string;
40
+ /** Whether the extension is required */
41
+ required: boolean;
42
+ /** Extension-specific parameters */
43
+ params?: Record<string, unknown>;
44
+ }
45
+ /**
46
+ * A2A Agent Skill
47
+ */
48
+ export interface AgentSkill {
49
+ /** Skill unique identifier */
50
+ id: string;
51
+ /** Human-readable name */
52
+ name: string;
53
+ /** Detailed description */
54
+ description: string;
55
+ /** Keywords/tags for the skill */
56
+ tags: string[];
57
+ /** Example prompts */
58
+ examples?: string[];
59
+ /** Supported input modes (overrides agent defaults) */
60
+ inputModes?: string[];
61
+ /** Supported output modes (overrides agent defaults) */
62
+ outputModes?: string[];
63
+ }
64
+ /**
65
+ * Security scheme for authentication (OpenAPI 3.0 style)
66
+ */
67
+ export interface SecurityScheme {
68
+ type: "http" | "apiKey" | "oauth2" | "openIdConnect";
69
+ description?: string;
70
+ scheme?: string;
71
+ bearerFormat?: string;
72
+ }
73
+ /**
74
+ * A2A v1.0 Agent Card - Standard format for agent discovery
75
+ * Reference: specs/external/specification.md Section 5
76
+ */
77
+ export interface AgentCard {
78
+ /** JSON Schema URL */
79
+ $schema?: string;
80
+ /** Human-readable agent name */
81
+ name: string;
82
+ /** Detailed description of agent capabilities */
83
+ description: string;
84
+ /** Agent version (SemVer) */
85
+ version: string;
86
+ /** Base URL for A2A endpoints */
87
+ url: string;
88
+ /** Service provider information */
89
+ provider?: AgentProvider;
90
+ /** Agent capabilities */
91
+ capabilities: AgentCapabilities;
92
+ /** Supported input content types */
93
+ defaultInputModes: string[];
94
+ /** Supported output content types */
95
+ defaultOutputModes: string[];
96
+ /** Agent skills/capabilities */
97
+ skills: AgentSkill[];
98
+ /** Security authentication schemes */
99
+ securitySchemes?: Record<string, SecurityScheme>;
100
+ /** Required security schemes */
101
+ security?: Array<Record<string, string[]>>;
102
+ /** Documentation URL */
103
+ documentationUrl?: string;
104
+ /** Icon URL */
105
+ iconUrl?: string;
106
+ }
107
+ /**
108
+ * @deprecated Use AgentSkill instead
109
+ */
110
+ export interface Capability {
111
+ name: string;
112
+ version: string;
113
+ description?: string;
114
+ operations: string[];
115
+ optional?: boolean;
116
+ }
117
+ /**
118
+ * @deprecated Use AgentCard instead
119
+ */
120
+ export interface ServiceConfig {
121
+ name: string;
122
+ version: string;
123
+ description?: string;
124
+ }
125
+ /**
126
+ * @deprecated Will be removed in v2.0.0
127
+ */
128
+ export interface EndpointConfig {
129
+ envelope: string;
130
+ context?: string;
131
+ discovery: string;
132
+ }
133
+ /**
134
+ * @deprecated Use AgentCard instead
135
+ */
136
+ export interface DiscoveryDocument {
137
+ $schema: string;
138
+ lafs_version: string;
139
+ service: ServiceConfig;
140
+ capabilities: Capability[];
141
+ endpoints: EndpointConfig;
142
+ }
143
+ /**
144
+ * Configuration for the discovery middleware (A2A v1.0 format)
145
+ */
146
+ export interface DiscoveryConfig {
147
+ /** Agent information (required for A2A v1.0; omit only with legacy 'service') */
148
+ agent?: Omit<AgentCard, '$schema'>;
149
+ /** Base URL for constructing absolute URLs */
150
+ baseUrl?: string;
151
+ /** Cache duration in seconds (default: 3600) */
152
+ cacheMaxAge?: number;
153
+ /** Schema URL override */
154
+ schemaUrl?: string;
155
+ /** Optional custom headers */
156
+ headers?: Record<string, string>;
157
+ /**
158
+ * Automatically include LAFS as an A2A extension in Agent Card.
159
+ * Pass `true` for defaults, or an object to customize parameters.
160
+ */
161
+ autoIncludeLafsExtension?: boolean | {
162
+ required?: boolean;
163
+ supportsContextLedger?: boolean;
164
+ supportsTokenBudgets?: boolean;
165
+ };
166
+ /**
167
+ * @deprecated Use 'agent' instead
168
+ */
169
+ service?: ServiceConfig;
170
+ /**
171
+ * @deprecated Use 'agent.skills' instead
172
+ */
173
+ capabilities?: Capability[];
174
+ /**
175
+ * @deprecated Use 'agent.url' and individual endpoints
176
+ */
177
+ endpoints?: {
178
+ envelope: string;
179
+ context?: string;
180
+ discovery?: string;
181
+ };
182
+ /**
183
+ * @deprecated Use 'agent.version' instead
184
+ */
185
+ lafsVersion?: string;
186
+ }
187
+ /**
188
+ * Discovery middleware options
189
+ */
190
+ export interface DiscoveryMiddlewareOptions {
191
+ /**
192
+ * Primary path to serve Agent Card (default: /.well-known/agent-card.json)
193
+ */
194
+ path?: string;
195
+ /**
196
+ * Legacy path for backward compatibility (default: /.well-known/lafs.json)
197
+ * @deprecated Will be removed in v2.0.0
198
+ */
199
+ legacyPath?: string;
200
+ /** Enable legacy path support (default: true) */
201
+ enableLegacyPath?: boolean;
202
+ /** Enable HEAD requests (default: true) */
203
+ enableHead?: boolean;
204
+ /** Enable ETag caching (default: true) */
205
+ enableEtag?: boolean;
206
+ }
207
+ /**
208
+ * Create Express middleware for serving A2A Agent Card
209
+ *
210
+ * Serves A2A-compliant Agent Card at /.well-known/agent-card.json
211
+ * Maintains backward compatibility with legacy /.well-known/lafs.json
212
+ *
213
+ * @param config - Discovery configuration (A2A v1.0 format)
214
+ * @param options - Middleware options
215
+ * @returns Express RequestHandler
216
+ *
217
+ * @example
218
+ * ```typescript
219
+ * import express from "express";
220
+ * import { discoveryMiddleware } from "@cleocode/lafs/discovery";
221
+ *
222
+ * const app = express();
223
+ *
224
+ * app.use(discoveryMiddleware({
225
+ * agent: {
226
+ * name: "my-lafs-agent",
227
+ * description: "A LAFS-compliant agent with A2A support",
228
+ * version: "1.0.0",
229
+ * url: "https://api.example.com",
230
+ * capabilities: {
231
+ * streaming: true,
232
+ * pushNotifications: false,
233
+ * extensions: []
234
+ * },
235
+ * defaultInputModes: ["application/json", "text/plain"],
236
+ * defaultOutputModes: ["application/json"],
237
+ * skills: [
238
+ * {
239
+ * id: "envelope-processor",
240
+ * name: "Envelope Processor",
241
+ * description: "Process LAFS envelopes",
242
+ * tags: ["lafs", "envelope", "validation"],
243
+ * examples: ["Validate this envelope", "Process envelope data"]
244
+ * }
245
+ * ]
246
+ * }
247
+ * }));
248
+ * ```
249
+ */
250
+ export declare function discoveryMiddleware(config: DiscoveryConfig, options?: DiscoveryMiddlewareOptions): RequestHandler;
251
+ /**
252
+ * Fastify plugin for A2A Agent Card discovery
253
+ *
254
+ * @param fastify - Fastify instance
255
+ * @param options - Plugin options
256
+ */
257
+ export declare function discoveryFastifyPlugin(fastify: unknown, options: {
258
+ config: DiscoveryConfig;
259
+ path?: string;
260
+ }): Promise<void>;
261
+ /**
262
+ * BREAKING CHANGES v1.2.3 → v2.0.0:
263
+ *
264
+ * 1. Discovery Endpoint Path
265
+ * - OLD: /.well-known/lafs.json
266
+ * - NEW: /.well-known/agent-card.json
267
+ * - MIGRATION: Update client code to use new path
268
+ * - BACKWARD COMPAT: Legacy path still works but logs deprecation warning
269
+ *
270
+ * 2. Discovery Document Format
271
+ * - OLD: DiscoveryDocument interface (lafs_version, service, capabilities, endpoints)
272
+ * - NEW: AgentCard interface (A2A v1.0 compliant)
273
+ * - MIGRATION: Update config from 'service' to 'agent' format
274
+ * - BACKWARD COMPAT: Legacy config format automatically converted with warning
275
+ *
276
+ * 3. Type Names
277
+ * - Capability → AgentSkill (renamed to align with A2A spec)
278
+ * - ServiceConfig → AgentCard (renamed)
279
+ * - All old types marked as @deprecated
280
+ *
281
+ * 4. Removed in v2.0.0
282
+ * - Legacy path support will be removed
283
+ * - Old type definitions will be removed
284
+ * - Automatic config migration will be removed
285
+ */
286
+ export default discoveryMiddleware;
@@ -0,0 +1,350 @@
1
+ /**
2
+ * LAFS Agent Discovery - Express/Fastify Middleware
3
+ * Serves A2A-compliant Agent Card at /.well-known/agent-card.json
4
+ * Maintains backward compatibility with legacy /.well-known/lafs.json
5
+ *
6
+ * A2A v1.0+ Compliant Implementation
7
+ * Reference: specs/external/agent-discovery.md
8
+ */
9
+ import { createRequire } from "node:module";
10
+ import { createHash } from "crypto";
11
+ import { buildLafsExtension } from './a2a/extensions.js';
12
+ const require = createRequire(import.meta.url);
13
+ // Resolve package.json from project root (works from both src/ and dist/src/)
14
+ let pkg;
15
+ try {
16
+ pkg = require('../package.json');
17
+ }
18
+ catch {
19
+ pkg = require('../../package.json');
20
+ }
21
+ // ============================================================================
22
+ // Utility Functions
23
+ // ============================================================================
24
+ /**
25
+ * Build absolute URL from base and path
26
+ */
27
+ function buildUrl(base, path, req) {
28
+ if (path.startsWith("http://") || path.startsWith("https://")) {
29
+ return path;
30
+ }
31
+ if (base) {
32
+ const separator = base.endsWith("/") || path.startsWith("/") ? "" : "/";
33
+ return `${base}${separator}${path}`;
34
+ }
35
+ if (req) {
36
+ const protocol = req.headers["x-forwarded-proto"] || req.protocol || "http";
37
+ const host = req.headers.host || "localhost";
38
+ const separator = path.startsWith("/") ? "" : "/";
39
+ return `${protocol}://${host}${separator}${path}`;
40
+ }
41
+ return path.startsWith("/") ? path : `/${path}`;
42
+ }
43
+ /**
44
+ * Generate ETag from content
45
+ */
46
+ function generateETag(content) {
47
+ return `"${createHash("sha256").update(content).digest("hex").slice(0, 32)}"`;
48
+ }
49
+ /**
50
+ * Build A2A Agent Card from configuration
51
+ */
52
+ function buildAgentCard(config, req) {
53
+ const schemaUrl = config.schemaUrl || "https://lafs.dev/schemas/v1/agent-card.schema.json";
54
+ // Handle legacy config migration
55
+ if (config.service && !config.agent) {
56
+ console.warn("[DEPRECATION] Using legacy 'service' config. Migrate to 'agent' format for A2A v1.0+ compliance.");
57
+ return {
58
+ $schema: schemaUrl,
59
+ name: config.service.name,
60
+ description: config.service.description || "LAFS-compliant agent",
61
+ version: config.lafsVersion || config.service.version || "1.0.0",
62
+ url: config.endpoints?.envelope
63
+ ? buildUrl(config.baseUrl, config.endpoints.envelope, req)
64
+ : buildUrl(config.baseUrl, "/", req),
65
+ capabilities: {
66
+ streaming: false,
67
+ pushNotifications: false,
68
+ extendedAgentCard: false,
69
+ extensions: []
70
+ },
71
+ defaultInputModes: ["application/json"],
72
+ defaultOutputModes: ["application/json"],
73
+ skills: (config.capabilities || []).map(cap => ({
74
+ id: cap.name.toLowerCase().replace(/\s+/g, "-"),
75
+ name: cap.name,
76
+ description: cap.description || `${cap.name} capability`,
77
+ tags: cap.operations || [],
78
+ examples: []
79
+ }))
80
+ };
81
+ }
82
+ // Standard A2A v1.0 Agent Card (agent is guaranteed present; legacy path returned above)
83
+ const agent = config.agent;
84
+ const card = {
85
+ $schema: schemaUrl,
86
+ ...agent,
87
+ url: agent.url || buildUrl(config.baseUrl, "/", req)
88
+ };
89
+ // Auto-include LAFS extension if configured
90
+ if (config.autoIncludeLafsExtension) {
91
+ const lafsOptions = typeof config.autoIncludeLafsExtension === 'object'
92
+ ? config.autoIncludeLafsExtension
93
+ : undefined;
94
+ const ext = buildLafsExtension(lafsOptions);
95
+ if (!card.capabilities.extensions) {
96
+ card.capabilities.extensions = [];
97
+ }
98
+ card.capabilities.extensions.push({
99
+ uri: ext.uri,
100
+ description: ext.description ?? 'LAFS envelope protocol for structured agent responses',
101
+ required: ext.required ?? false,
102
+ params: ext.params,
103
+ });
104
+ }
105
+ return card;
106
+ }
107
+ /**
108
+ * Build legacy discovery document for backward compatibility
109
+ * @deprecated Will be removed in v2.0.0
110
+ */
111
+ function buildLegacyDiscoveryDocument(config, req) {
112
+ const schemaUrl = config.schemaUrl || "https://lafs.dev/schemas/v1/discovery.schema.json";
113
+ const lafsVersion = config.lafsVersion || pkg.version;
114
+ return {
115
+ $schema: schemaUrl,
116
+ lafs_version: lafsVersion,
117
+ service: config.service || {
118
+ name: config.agent.name,
119
+ version: config.agent.version,
120
+ description: config.agent.description
121
+ },
122
+ capabilities: config.capabilities || config.agent.skills.map(skill => ({
123
+ name: skill.name,
124
+ version: config.agent.version,
125
+ description: skill.description,
126
+ operations: skill.tags,
127
+ optional: false
128
+ })),
129
+ endpoints: {
130
+ envelope: buildUrl(config.baseUrl, config.endpoints?.envelope || config.agent.url, req),
131
+ context: config.endpoints?.context ? buildUrl(config.baseUrl, config.endpoints.context, req) : undefined,
132
+ discovery: config.endpoints?.discovery || buildUrl(config.baseUrl, "/.well-known/lafs.json", req)
133
+ }
134
+ };
135
+ }
136
+ // ============================================================================
137
+ // Middleware
138
+ // ============================================================================
139
+ /**
140
+ * Create Express middleware for serving A2A Agent Card
141
+ *
142
+ * Serves A2A-compliant Agent Card at /.well-known/agent-card.json
143
+ * Maintains backward compatibility with legacy /.well-known/lafs.json
144
+ *
145
+ * @param config - Discovery configuration (A2A v1.0 format)
146
+ * @param options - Middleware options
147
+ * @returns Express RequestHandler
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * import express from "express";
152
+ * import { discoveryMiddleware } from "@cleocode/lafs/discovery";
153
+ *
154
+ * const app = express();
155
+ *
156
+ * app.use(discoveryMiddleware({
157
+ * agent: {
158
+ * name: "my-lafs-agent",
159
+ * description: "A LAFS-compliant agent with A2A support",
160
+ * version: "1.0.0",
161
+ * url: "https://api.example.com",
162
+ * capabilities: {
163
+ * streaming: true,
164
+ * pushNotifications: false,
165
+ * extensions: []
166
+ * },
167
+ * defaultInputModes: ["application/json", "text/plain"],
168
+ * defaultOutputModes: ["application/json"],
169
+ * skills: [
170
+ * {
171
+ * id: "envelope-processor",
172
+ * name: "Envelope Processor",
173
+ * description: "Process LAFS envelopes",
174
+ * tags: ["lafs", "envelope", "validation"],
175
+ * examples: ["Validate this envelope", "Process envelope data"]
176
+ * }
177
+ * ]
178
+ * }
179
+ * }));
180
+ * ```
181
+ */
182
+ export function discoveryMiddleware(config, options = {}) {
183
+ const path = options.path || "/.well-known/agent-card.json";
184
+ const legacyPath = options.legacyPath || "/.well-known/lafs.json";
185
+ // Disable legacy path by default when a custom path is set, unless explicitly enabled
186
+ const enableLegacyPath = options.enableLegacyPath ?? !options.path;
187
+ const enableHead = options.enableHead !== false;
188
+ const enableEtag = options.enableEtag !== false;
189
+ const cacheMaxAge = config.cacheMaxAge || 3600;
190
+ // Validate configuration
191
+ if (!config.agent && !config.service) {
192
+ throw new Error("Discovery config requires 'agent' (A2A v1.0) or 'service' (legacy) configuration");
193
+ }
194
+ // Validate legacy service config fields
195
+ if (config.service) {
196
+ if (!config.service.name) {
197
+ throw new Error("Discovery config requires 'service.name'");
198
+ }
199
+ if (!config.service.version) {
200
+ throw new Error("Discovery config requires 'service.version'");
201
+ }
202
+ }
203
+ // Validate legacy capabilities/endpoints when using service config
204
+ if (config.service && !config.agent) {
205
+ if (config.capabilities === undefined || config.capabilities === null) {
206
+ throw new Error("Discovery config requires 'capabilities' when using legacy 'service' config");
207
+ }
208
+ if (!config.endpoints?.envelope) {
209
+ throw new Error("Discovery config requires 'endpoints.envelope' when using legacy 'service' config");
210
+ }
211
+ }
212
+ // Cache serialized documents to ensure consistent ETags across GET/HEAD
213
+ let cachedPrimaryJson = null;
214
+ let cachedLegacyJson = null;
215
+ function getSerializedDoc(isLegacy, req) {
216
+ if (isLegacy) {
217
+ if (!cachedLegacyJson) {
218
+ cachedLegacyJson = JSON.stringify(buildLegacyDiscoveryDocument(config, req), null, 2);
219
+ }
220
+ return cachedLegacyJson;
221
+ }
222
+ if (!cachedPrimaryJson) {
223
+ cachedPrimaryJson = JSON.stringify(buildAgentCard(config, req), null, 2);
224
+ }
225
+ return cachedPrimaryJson;
226
+ }
227
+ return function discoveryHandler(req, res, next) {
228
+ const isPrimaryPath = req.path === path;
229
+ const isLegacyPath = enableLegacyPath && req.path === legacyPath;
230
+ // Only handle requests to discovery paths
231
+ if (!isPrimaryPath && !isLegacyPath) {
232
+ next();
233
+ return;
234
+ }
235
+ // Log deprecation warning for legacy path
236
+ if (isLegacyPath) {
237
+ console.warn(`[DEPRECATION] Accessing legacy discovery endpoint ${legacyPath}. ` +
238
+ `Migrate to ${path} for A2A v1.0+ compliance. Legacy support will be removed in v2.0.0.`);
239
+ }
240
+ // Handle HEAD requests
241
+ if (req.method === "HEAD") {
242
+ if (!enableHead) {
243
+ res.status(405).json({
244
+ error: "Method Not Allowed",
245
+ message: "HEAD requests are disabled for this endpoint"
246
+ });
247
+ return;
248
+ }
249
+ const json = getSerializedDoc(isLegacyPath, req);
250
+ const etag = enableEtag ? generateETag(json) : undefined;
251
+ res.set({
252
+ "Content-Type": "application/json",
253
+ "Cache-Control": `public, max-age=${cacheMaxAge}`,
254
+ ...(etag && { "ETag": etag }),
255
+ ...(isLegacyPath && { "Deprecation": "true", "Sunset": "Sat, 31 Dec 2025 23:59:59 GMT" }),
256
+ "Content-Length": Buffer.byteLength(json)
257
+ });
258
+ res.status(200).end();
259
+ return;
260
+ }
261
+ // Only handle GET requests
262
+ if (req.method !== "GET") {
263
+ res.status(405).json({
264
+ error: "Method Not Allowed",
265
+ message: `Method ${req.method} not allowed. Use GET or HEAD.`
266
+ });
267
+ return;
268
+ }
269
+ try {
270
+ const json = getSerializedDoc(isLegacyPath, req);
271
+ const etag = enableEtag ? generateETag(json) : undefined;
272
+ // Check If-None-Match for conditional request
273
+ if (enableEtag && req.headers["if-none-match"] === etag) {
274
+ res.status(304).end();
275
+ return;
276
+ }
277
+ // Set response headers
278
+ const headers = {
279
+ "Content-Type": "application/json",
280
+ "Cache-Control": `public, max-age=${cacheMaxAge}`,
281
+ ...config.headers
282
+ };
283
+ if (etag) {
284
+ headers["ETag"] = etag;
285
+ }
286
+ // Add deprecation headers for legacy path
287
+ if (isLegacyPath) {
288
+ headers["Deprecation"] = "true";
289
+ headers["Sunset"] = "Sat, 31 Dec 2025 23:59:59 GMT";
290
+ headers["Link"] = `<${buildUrl(config.baseUrl, path, req)}>; rel="successor-version"`;
291
+ }
292
+ res.set(headers);
293
+ res.status(200).send(json);
294
+ }
295
+ catch (error) {
296
+ next(error);
297
+ }
298
+ };
299
+ }
300
+ /**
301
+ * Fastify plugin for A2A Agent Card discovery
302
+ *
303
+ * @param fastify - Fastify instance
304
+ * @param options - Plugin options
305
+ */
306
+ export async function discoveryFastifyPlugin(fastify, options) {
307
+ const path = options.path || "/.well-known/agent-card.json";
308
+ const config = options.config;
309
+ const cacheMaxAge = config.cacheMaxAge || 3600;
310
+ const handler = async (request, reply) => {
311
+ const doc = buildAgentCard(config, request.raw);
312
+ const json = JSON.stringify(doc);
313
+ const etag = generateETag(json);
314
+ reply.header("Content-Type", "application/json");
315
+ reply.header("Cache-Control", `public, max-age=${cacheMaxAge}`);
316
+ reply.header("ETag", etag);
317
+ return doc;
318
+ };
319
+ // Note: Actual route registration depends on Fastify's API
320
+ // This is a type-safe signature for the plugin
321
+ }
322
+ // ============================================================================
323
+ // Breaking Changes Documentation
324
+ // ============================================================================
325
+ /**
326
+ * BREAKING CHANGES v1.2.3 → v2.0.0:
327
+ *
328
+ * 1. Discovery Endpoint Path
329
+ * - OLD: /.well-known/lafs.json
330
+ * - NEW: /.well-known/agent-card.json
331
+ * - MIGRATION: Update client code to use new path
332
+ * - BACKWARD COMPAT: Legacy path still works but logs deprecation warning
333
+ *
334
+ * 2. Discovery Document Format
335
+ * - OLD: DiscoveryDocument interface (lafs_version, service, capabilities, endpoints)
336
+ * - NEW: AgentCard interface (A2A v1.0 compliant)
337
+ * - MIGRATION: Update config from 'service' to 'agent' format
338
+ * - BACKWARD COMPAT: Legacy config format automatically converted with warning
339
+ *
340
+ * 3. Type Names
341
+ * - Capability → AgentSkill (renamed to align with A2A spec)
342
+ * - ServiceConfig → AgentCard (renamed)
343
+ * - All old types marked as @deprecated
344
+ *
345
+ * 4. Removed in v2.0.0
346
+ * - Legacy path support will be removed
347
+ * - Old type definitions will be removed
348
+ * - Automatic config migration will be removed
349
+ */
350
+ export default discoveryMiddleware;
@@ -0,0 +1,60 @@
1
+ import type { LAFSEnvelope, LAFSError, LAFSErrorCategory, LAFSAgentAction, LAFSMeta, LAFSTransport, MVILevel } from "./types.js";
2
+ export declare const LAFS_SCHEMA_URL: "https://lafs.dev/schemas/v1/envelope.schema.json";
3
+ export interface CreateEnvelopeMetaInput {
4
+ operation: string;
5
+ requestId: string;
6
+ transport?: LAFSTransport;
7
+ specVersion?: string;
8
+ schemaVersion?: string;
9
+ timestamp?: string;
10
+ strict?: boolean;
11
+ mvi?: MVILevel | boolean;
12
+ contextVersion?: number;
13
+ sessionId?: string;
14
+ warnings?: LAFSMeta["warnings"];
15
+ }
16
+ export interface CreateEnvelopeSuccessInput {
17
+ success: true;
18
+ result: LAFSEnvelope["result"];
19
+ page?: LAFSEnvelope["page"];
20
+ error?: null;
21
+ _extensions?: LAFSEnvelope["_extensions"];
22
+ meta: CreateEnvelopeMetaInput;
23
+ }
24
+ export interface CreateEnvelopeErrorInput {
25
+ success: false;
26
+ error: Partial<LAFSError> & Pick<LAFSError, "code" | "message">;
27
+ /**
28
+ * Optional result payload to include alongside the error.
29
+ * For validation tools (linters, type checkers), the actionable data
30
+ * (what to fix, suggested fixes) IS the result even when the operation
31
+ * "fails". Setting this allows agents to access both the error metadata
32
+ * and the detailed result in a single response.
33
+ *
34
+ * When omitted or null, the envelope emits `result: null` (default behavior).
35
+ */
36
+ result?: LAFSEnvelope["result"] | null;
37
+ page?: LAFSEnvelope["page"];
38
+ _extensions?: LAFSEnvelope["_extensions"];
39
+ meta: CreateEnvelopeMetaInput;
40
+ }
41
+ export type CreateEnvelopeInput = CreateEnvelopeSuccessInput | CreateEnvelopeErrorInput;
42
+ export declare const CATEGORY_ACTION_MAP: Record<LAFSErrorCategory, LAFSAgentAction>;
43
+ export declare function createEnvelope(input: CreateEnvelopeInput): LAFSEnvelope;
44
+ export declare class LafsError extends Error implements LAFSError {
45
+ code: string;
46
+ category: LAFSErrorCategory;
47
+ retryable: boolean;
48
+ retryAfterMs: number | null;
49
+ details: Record<string, unknown>;
50
+ registered: boolean;
51
+ agentAction?: LAFSAgentAction;
52
+ escalationRequired?: boolean;
53
+ suggestedAction?: string;
54
+ docUrl?: string;
55
+ constructor(error: LAFSError);
56
+ }
57
+ export interface ParseLafsResponseOptions {
58
+ requireRegisteredErrorCode?: boolean;
59
+ }
60
+ export declare function parseLafsResponse<T = unknown>(input: unknown, options?: ParseLafsResponseOptions): T;