@cleocode/lafs-protocol 1.2.2 → 1.3.1
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 +58 -5
- package/dist/examples/discovery-server.js +4 -4
- package/dist/src/a2a/bindings/grpc.d.ts +67 -0
- package/dist/src/a2a/bindings/grpc.js +148 -0
- package/dist/src/a2a/bindings/http.d.ts +92 -0
- package/dist/src/a2a/bindings/http.js +99 -0
- package/dist/src/a2a/bindings/index.d.ts +31 -0
- package/dist/src/a2a/bindings/index.js +54 -0
- package/dist/src/a2a/bindings/jsonrpc.d.ts +77 -0
- package/dist/src/a2a/bindings/jsonrpc.js +114 -0
- package/dist/src/a2a/bridge.d.ts +121 -76
- package/dist/src/a2a/bridge.js +185 -89
- package/dist/src/a2a/extensions.d.ts +93 -0
- package/dist/src/a2a/extensions.js +147 -0
- package/dist/src/a2a/index.d.ts +27 -25
- package/dist/src/a2a/index.js +59 -25
- package/dist/src/a2a/task-lifecycle.d.ts +98 -0
- package/dist/src/a2a/task-lifecycle.js +263 -0
- package/dist/src/discovery.d.ts +203 -44
- package/dist/src/discovery.js +204 -166
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +10 -1
- package/lafs.md +1 -1
- package/package.json +14 -1
- package/schemas/v1/agent-card.schema.json +230 -0
package/dist/src/discovery.js
CHANGED
|
@@ -1,204 +1,234 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LAFS Agent Discovery - Express/Fastify Middleware
|
|
3
|
-
* Serves
|
|
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
|
|
4
8
|
*/
|
|
5
9
|
import { createRequire } from "node:module";
|
|
6
10
|
import { createHash } from "crypto";
|
|
7
|
-
import {
|
|
8
|
-
import { fileURLToPath } from "url";
|
|
9
|
-
import { dirname, join } from "path";
|
|
10
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
-
const __dirname = dirname(__filename);
|
|
12
|
-
// Handle ESM/CommonJS interop for AJV
|
|
11
|
+
import { buildLafsExtension } from './a2a/extensions.js';
|
|
13
12
|
const require = createRequire(import.meta.url);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const addFormats = (typeof AddFormatsModule === "function" ? AddFormatsModule : AddFormatsModule.default);
|
|
18
|
-
let ajvInstance = null;
|
|
19
|
-
let validateDiscovery = null;
|
|
20
|
-
/**
|
|
21
|
-
* Initialize AJV validator for discovery documents
|
|
22
|
-
*/
|
|
23
|
-
function initValidator() {
|
|
24
|
-
if (ajvInstance && validateDiscovery)
|
|
25
|
-
return;
|
|
26
|
-
ajvInstance = new AjvCtor({ strict: true, allErrors: true });
|
|
27
|
-
addFormats(ajvInstance);
|
|
28
|
-
try {
|
|
29
|
-
// Try to load schema from schemas directory
|
|
30
|
-
const schemaPath = join(__dirname, "..", "..", "schemas", "v1", "discovery.schema.json");
|
|
31
|
-
const schema = JSON.parse(readFileSync(schemaPath, "utf-8"));
|
|
32
|
-
validateDiscovery = ajvInstance.compile(schema);
|
|
33
|
-
}
|
|
34
|
-
catch (e) {
|
|
35
|
-
// Fallback to inline schema if file not found
|
|
36
|
-
const fallbackSchema = {
|
|
37
|
-
$schema: "http://json-schema.org/draft-07/schema#",
|
|
38
|
-
type: "object",
|
|
39
|
-
required: ["$schema", "lafs_version", "service", "capabilities", "endpoints"],
|
|
40
|
-
properties: {
|
|
41
|
-
$schema: { type: "string", format: "uri" },
|
|
42
|
-
lafs_version: { type: "string", pattern: "^\\d+\\.\\d+\\.\\d+$" },
|
|
43
|
-
service: {
|
|
44
|
-
type: "object",
|
|
45
|
-
required: ["name", "version"],
|
|
46
|
-
properties: {
|
|
47
|
-
name: { type: "string", minLength: 1 },
|
|
48
|
-
version: { type: "string", pattern: "^\\d+\\.\\d+\\.\\d+$" },
|
|
49
|
-
description: { type: "string" }
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
capabilities: {
|
|
53
|
-
type: "array",
|
|
54
|
-
items: {
|
|
55
|
-
type: "object",
|
|
56
|
-
required: ["name", "version", "operations"],
|
|
57
|
-
properties: {
|
|
58
|
-
name: { type: "string", minLength: 1 },
|
|
59
|
-
version: { type: "string", pattern: "^\\d+\\.\\d+\\.\\d+$" },
|
|
60
|
-
description: { type: "string" },
|
|
61
|
-
operations: { type: "array", items: { type: "string" } },
|
|
62
|
-
optional: { type: "boolean" }
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
endpoints: {
|
|
67
|
-
type: "object",
|
|
68
|
-
required: ["envelope", "discovery"],
|
|
69
|
-
properties: {
|
|
70
|
-
envelope: { type: "string", minLength: 1 },
|
|
71
|
-
context: { type: "string", minLength: 1 },
|
|
72
|
-
discovery: { type: "string", minLength: 1 }
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
validateDiscovery = ajvInstance.compile(fallbackSchema);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Utility Functions
|
|
15
|
+
// ============================================================================
|
|
80
16
|
/**
|
|
81
17
|
* Build absolute URL from base and path
|
|
82
18
|
*/
|
|
83
19
|
function buildUrl(base, path, req) {
|
|
84
|
-
// If path is already absolute, return it
|
|
85
20
|
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
86
21
|
return path;
|
|
87
22
|
}
|
|
88
|
-
// If base is provided, use it
|
|
89
23
|
if (base) {
|
|
90
24
|
const separator = base.endsWith("/") || path.startsWith("/") ? "" : "/";
|
|
91
25
|
return `${base}${separator}${path}`;
|
|
92
26
|
}
|
|
93
|
-
// Otherwise try to construct from request
|
|
94
27
|
if (req) {
|
|
95
28
|
const protocol = req.headers["x-forwarded-proto"] || req.protocol || "http";
|
|
96
29
|
const host = req.headers.host || "localhost";
|
|
97
30
|
const separator = path.startsWith("/") ? "" : "/";
|
|
98
31
|
return `${protocol}://${host}${separator}${path}`;
|
|
99
32
|
}
|
|
100
|
-
// Fallback to relative path
|
|
101
33
|
return path.startsWith("/") ? path : `/${path}`;
|
|
102
34
|
}
|
|
103
35
|
/**
|
|
104
|
-
* Generate ETag from
|
|
36
|
+
* Generate ETag from content
|
|
105
37
|
*/
|
|
106
38
|
function generateETag(content) {
|
|
107
39
|
return `"${createHash("sha256").update(content).digest("hex").slice(0, 32)}"`;
|
|
108
40
|
}
|
|
109
41
|
/**
|
|
110
|
-
* Build
|
|
42
|
+
* Build A2A Agent Card from configuration
|
|
43
|
+
*/
|
|
44
|
+
function buildAgentCard(config, req) {
|
|
45
|
+
const schemaUrl = config.schemaUrl || "https://lafs.dev/schemas/v1/agent-card.schema.json";
|
|
46
|
+
// Handle legacy config migration
|
|
47
|
+
if (config.service && !config.agent) {
|
|
48
|
+
console.warn("[DEPRECATION] Using legacy 'service' config. Migrate to 'agent' format for A2A v1.0+ compliance.");
|
|
49
|
+
return {
|
|
50
|
+
$schema: schemaUrl,
|
|
51
|
+
name: config.service.name,
|
|
52
|
+
description: config.service.description || "LAFS-compliant agent",
|
|
53
|
+
version: config.lafsVersion || config.service.version || "1.0.0",
|
|
54
|
+
url: config.endpoints?.envelope
|
|
55
|
+
? buildUrl(config.baseUrl, config.endpoints.envelope, req)
|
|
56
|
+
: buildUrl(config.baseUrl, "/", req),
|
|
57
|
+
capabilities: {
|
|
58
|
+
streaming: false,
|
|
59
|
+
pushNotifications: false,
|
|
60
|
+
extendedAgentCard: false,
|
|
61
|
+
extensions: []
|
|
62
|
+
},
|
|
63
|
+
defaultInputModes: ["application/json"],
|
|
64
|
+
defaultOutputModes: ["application/json"],
|
|
65
|
+
skills: (config.capabilities || []).map(cap => ({
|
|
66
|
+
id: cap.name.toLowerCase().replace(/\s+/g, "-"),
|
|
67
|
+
name: cap.name,
|
|
68
|
+
description: cap.description || `${cap.name} capability`,
|
|
69
|
+
tags: cap.operations || [],
|
|
70
|
+
examples: []
|
|
71
|
+
}))
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// Standard A2A v1.0 Agent Card (agent is guaranteed present; legacy path returned above)
|
|
75
|
+
const agent = config.agent;
|
|
76
|
+
const card = {
|
|
77
|
+
$schema: schemaUrl,
|
|
78
|
+
...agent,
|
|
79
|
+
url: agent.url || buildUrl(config.baseUrl, "/", req)
|
|
80
|
+
};
|
|
81
|
+
// Auto-include LAFS extension if configured
|
|
82
|
+
if (config.autoIncludeLafsExtension) {
|
|
83
|
+
const lafsOptions = typeof config.autoIncludeLafsExtension === 'object'
|
|
84
|
+
? config.autoIncludeLafsExtension
|
|
85
|
+
: undefined;
|
|
86
|
+
const ext = buildLafsExtension(lafsOptions);
|
|
87
|
+
if (!card.capabilities.extensions) {
|
|
88
|
+
card.capabilities.extensions = [];
|
|
89
|
+
}
|
|
90
|
+
card.capabilities.extensions.push({
|
|
91
|
+
uri: ext.uri,
|
|
92
|
+
description: ext.description ?? 'LAFS envelope protocol for structured agent responses',
|
|
93
|
+
required: ext.required ?? false,
|
|
94
|
+
params: ext.params,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return card;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Build legacy discovery document for backward compatibility
|
|
101
|
+
* @deprecated Will be removed in v2.0.0
|
|
111
102
|
*/
|
|
112
|
-
function
|
|
103
|
+
function buildLegacyDiscoveryDocument(config, req) {
|
|
113
104
|
const schemaUrl = config.schemaUrl || "https://lafs.dev/schemas/v1/discovery.schema.json";
|
|
114
|
-
const lafsVersion = config.lafsVersion || "1.
|
|
105
|
+
const lafsVersion = config.lafsVersion || "1.3.1";
|
|
115
106
|
return {
|
|
116
107
|
$schema: schemaUrl,
|
|
117
108
|
lafs_version: lafsVersion,
|
|
118
|
-
service: config.service
|
|
119
|
-
|
|
109
|
+
service: config.service || {
|
|
110
|
+
name: config.agent.name,
|
|
111
|
+
version: config.agent.version,
|
|
112
|
+
description: config.agent.description
|
|
113
|
+
},
|
|
114
|
+
capabilities: config.capabilities || config.agent.skills.map(skill => ({
|
|
115
|
+
name: skill.name,
|
|
116
|
+
version: config.agent.version,
|
|
117
|
+
description: skill.description,
|
|
118
|
+
operations: skill.tags,
|
|
119
|
+
optional: false
|
|
120
|
+
})),
|
|
120
121
|
endpoints: {
|
|
121
|
-
envelope: buildUrl(config.baseUrl, config.endpoints.
|
|
122
|
-
context: config.endpoints.context
|
|
123
|
-
|
|
124
|
-
: undefined,
|
|
125
|
-
discovery: config.endpoints.discovery
|
|
126
|
-
? buildUrl(config.baseUrl, config.endpoints.discovery, req)
|
|
127
|
-
: buildUrl(config.baseUrl, "/.well-known/lafs.json", req)
|
|
122
|
+
envelope: buildUrl(config.baseUrl, config.endpoints?.envelope || config.agent.url, req),
|
|
123
|
+
context: config.endpoints?.context ? buildUrl(config.baseUrl, config.endpoints.context, req) : undefined,
|
|
124
|
+
discovery: config.endpoints?.discovery || buildUrl(config.baseUrl, "/.well-known/lafs.json", req)
|
|
128
125
|
}
|
|
129
126
|
};
|
|
130
127
|
}
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// Middleware
|
|
130
|
+
// ============================================================================
|
|
131
131
|
/**
|
|
132
|
-
*
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (!validateDiscovery) {
|
|
137
|
-
throw new Error("Discovery document validator not initialized");
|
|
138
|
-
}
|
|
139
|
-
const valid = validateDiscovery(doc);
|
|
140
|
-
if (!valid) {
|
|
141
|
-
const errors = validateDiscovery.errors;
|
|
142
|
-
const errorMessages = errors?.map((e) => `${e.instancePath || "root"}: ${e.message}`).join("; ");
|
|
143
|
-
throw new Error(`Discovery document validation failed: ${errorMessages}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Create Express middleware for serving LAFS discovery document
|
|
132
|
+
* Create Express middleware for serving A2A Agent Card
|
|
133
|
+
*
|
|
134
|
+
* Serves A2A-compliant Agent Card at /.well-known/agent-card.json
|
|
135
|
+
* Maintains backward compatibility with legacy /.well-known/lafs.json
|
|
148
136
|
*
|
|
149
|
-
* @param config - Discovery configuration
|
|
137
|
+
* @param config - Discovery configuration (A2A v1.0 format)
|
|
150
138
|
* @param options - Middleware options
|
|
151
139
|
* @returns Express RequestHandler
|
|
152
140
|
*
|
|
153
141
|
* @example
|
|
154
142
|
* ```typescript
|
|
155
143
|
* import express from "express";
|
|
156
|
-
* import { discoveryMiddleware } from "
|
|
144
|
+
* import { discoveryMiddleware } from "@cleocode/lafs-protocol/discovery";
|
|
157
145
|
*
|
|
158
146
|
* const app = express();
|
|
159
147
|
*
|
|
160
148
|
* app.use(discoveryMiddleware({
|
|
161
|
-
*
|
|
162
|
-
* name: "my-lafs-
|
|
149
|
+
* agent: {
|
|
150
|
+
* name: "my-lafs-agent",
|
|
151
|
+
* description: "A LAFS-compliant agent with A2A support",
|
|
163
152
|
* version: "1.0.0",
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
153
|
+
* url: "https://api.example.com",
|
|
154
|
+
* capabilities: {
|
|
155
|
+
* streaming: true,
|
|
156
|
+
* pushNotifications: false,
|
|
157
|
+
* extensions: []
|
|
158
|
+
* },
|
|
159
|
+
* defaultInputModes: ["application/json", "text/plain"],
|
|
160
|
+
* defaultOutputModes: ["application/json"],
|
|
161
|
+
* skills: [
|
|
162
|
+
* {
|
|
163
|
+
* id: "envelope-processor",
|
|
164
|
+
* name: "Envelope Processor",
|
|
165
|
+
* description: "Process LAFS envelopes",
|
|
166
|
+
* tags: ["lafs", "envelope", "validation"],
|
|
167
|
+
* examples: ["Validate this envelope", "Process envelope data"]
|
|
168
|
+
* }
|
|
169
|
+
* ]
|
|
177
170
|
* }
|
|
178
171
|
* }));
|
|
179
172
|
* ```
|
|
180
173
|
*/
|
|
181
174
|
export function discoveryMiddleware(config, options = {}) {
|
|
182
|
-
const path = options.path || "/.well-known/
|
|
175
|
+
const path = options.path || "/.well-known/agent-card.json";
|
|
176
|
+
const legacyPath = options.legacyPath || "/.well-known/lafs.json";
|
|
177
|
+
// Disable legacy path by default when a custom path is set, unless explicitly enabled
|
|
178
|
+
const enableLegacyPath = options.enableLegacyPath ?? !options.path;
|
|
183
179
|
const enableHead = options.enableHead !== false;
|
|
184
180
|
const enableEtag = options.enableEtag !== false;
|
|
185
181
|
const cacheMaxAge = config.cacheMaxAge || 3600;
|
|
186
182
|
// Validate configuration
|
|
187
|
-
if (!config.
|
|
188
|
-
throw new Error("Discovery config requires
|
|
183
|
+
if (!config.agent && !config.service) {
|
|
184
|
+
throw new Error("Discovery config requires 'agent' (A2A v1.0) or 'service' (legacy) configuration");
|
|
189
185
|
}
|
|
190
|
-
|
|
191
|
-
|
|
186
|
+
// Validate legacy service config fields
|
|
187
|
+
if (config.service) {
|
|
188
|
+
if (!config.service.name) {
|
|
189
|
+
throw new Error("Discovery config requires 'service.name'");
|
|
190
|
+
}
|
|
191
|
+
if (!config.service.version) {
|
|
192
|
+
throw new Error("Discovery config requires 'service.version'");
|
|
193
|
+
}
|
|
192
194
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
+
// Validate legacy capabilities/endpoints when using service config
|
|
196
|
+
if (config.service && !config.agent) {
|
|
197
|
+
if (config.capabilities === undefined || config.capabilities === null) {
|
|
198
|
+
throw new Error("Discovery config requires 'capabilities' when using legacy 'service' config");
|
|
199
|
+
}
|
|
200
|
+
if (!config.endpoints?.envelope) {
|
|
201
|
+
throw new Error("Discovery config requires 'endpoints.envelope' when using legacy 'service' config");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Cache serialized documents to ensure consistent ETags across GET/HEAD
|
|
205
|
+
let cachedPrimaryJson = null;
|
|
206
|
+
let cachedLegacyJson = null;
|
|
207
|
+
function getSerializedDoc(isLegacy, req) {
|
|
208
|
+
if (isLegacy) {
|
|
209
|
+
if (!cachedLegacyJson) {
|
|
210
|
+
cachedLegacyJson = JSON.stringify(buildLegacyDiscoveryDocument(config, req), null, 2);
|
|
211
|
+
}
|
|
212
|
+
return cachedLegacyJson;
|
|
213
|
+
}
|
|
214
|
+
if (!cachedPrimaryJson) {
|
|
215
|
+
cachedPrimaryJson = JSON.stringify(buildAgentCard(config, req), null, 2);
|
|
216
|
+
}
|
|
217
|
+
return cachedPrimaryJson;
|
|
195
218
|
}
|
|
196
219
|
return function discoveryHandler(req, res, next) {
|
|
197
|
-
|
|
198
|
-
|
|
220
|
+
const isPrimaryPath = req.path === path;
|
|
221
|
+
const isLegacyPath = enableLegacyPath && req.path === legacyPath;
|
|
222
|
+
// Only handle requests to discovery paths
|
|
223
|
+
if (!isPrimaryPath && !isLegacyPath) {
|
|
199
224
|
next();
|
|
200
225
|
return;
|
|
201
226
|
}
|
|
227
|
+
// Log deprecation warning for legacy path
|
|
228
|
+
if (isLegacyPath) {
|
|
229
|
+
console.warn(`[DEPRECATION] Accessing legacy discovery endpoint ${legacyPath}. ` +
|
|
230
|
+
`Migrate to ${path} for A2A v1.0+ compliance. Legacy support will be removed in v2.0.0.`);
|
|
231
|
+
}
|
|
202
232
|
// Handle HEAD requests
|
|
203
233
|
if (req.method === "HEAD") {
|
|
204
234
|
if (!enableHead) {
|
|
@@ -208,23 +238,13 @@ export function discoveryMiddleware(config, options = {}) {
|
|
|
208
238
|
});
|
|
209
239
|
return;
|
|
210
240
|
}
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
const json = JSON.stringify(doc);
|
|
214
|
-
// Generate stable ETag from config hash (not request-dependent document)
|
|
215
|
-
const configHash = generateETag(JSON.stringify({
|
|
216
|
-
schemaUrl: config.schemaUrl,
|
|
217
|
-
lafsVersion: config.lafsVersion,
|
|
218
|
-
service: config.service,
|
|
219
|
-
capabilities: config.capabilities,
|
|
220
|
-
endpoints: config.endpoints,
|
|
221
|
-
cacheMaxAge: config.cacheMaxAge
|
|
222
|
-
}));
|
|
223
|
-
const etag = enableEtag ? configHash : undefined;
|
|
241
|
+
const json = getSerializedDoc(isLegacyPath, req);
|
|
242
|
+
const etag = enableEtag ? generateETag(json) : undefined;
|
|
224
243
|
res.set({
|
|
225
244
|
"Content-Type": "application/json",
|
|
226
245
|
"Cache-Control": `public, max-age=${cacheMaxAge}`,
|
|
227
246
|
...(etag && { "ETag": etag }),
|
|
247
|
+
...(isLegacyPath && { "Deprecation": "true", "Sunset": "Sat, 31 Dec 2025 23:59:59 GMT" }),
|
|
228
248
|
"Content-Length": Buffer.byteLength(json)
|
|
229
249
|
});
|
|
230
250
|
res.status(200).end();
|
|
@@ -239,23 +259,8 @@ export function discoveryMiddleware(config, options = {}) {
|
|
|
239
259
|
return;
|
|
240
260
|
}
|
|
241
261
|
try {
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
// Validate against schema
|
|
245
|
-
validateDocument(doc);
|
|
246
|
-
// Serialize document
|
|
247
|
-
const json = JSON.stringify(doc);
|
|
248
|
-
// Generate ETag from config hash (stable) rather than request-dependent document
|
|
249
|
-
// This ensures ETag is consistent across requests even when URLs are constructed from request
|
|
250
|
-
const configHash = generateETag(JSON.stringify({
|
|
251
|
-
schemaUrl: config.schemaUrl,
|
|
252
|
-
lafsVersion: config.lafsVersion,
|
|
253
|
-
service: config.service,
|
|
254
|
-
capabilities: config.capabilities,
|
|
255
|
-
endpoints: config.endpoints,
|
|
256
|
-
cacheMaxAge: config.cacheMaxAge
|
|
257
|
-
}));
|
|
258
|
-
const etag = enableEtag ? configHash : undefined;
|
|
262
|
+
const json = getSerializedDoc(isLegacyPath, req);
|
|
263
|
+
const etag = enableEtag ? generateETag(json) : undefined;
|
|
259
264
|
// Check If-None-Match for conditional request
|
|
260
265
|
if (enableEtag && req.headers["if-none-match"] === etag) {
|
|
261
266
|
res.status(304).end();
|
|
@@ -270,6 +275,12 @@ export function discoveryMiddleware(config, options = {}) {
|
|
|
270
275
|
if (etag) {
|
|
271
276
|
headers["ETag"] = etag;
|
|
272
277
|
}
|
|
278
|
+
// Add deprecation headers for legacy path
|
|
279
|
+
if (isLegacyPath) {
|
|
280
|
+
headers["Deprecation"] = "true";
|
|
281
|
+
headers["Sunset"] = "Sat, 31 Dec 2025 23:59:59 GMT";
|
|
282
|
+
headers["Link"] = `<${buildUrl(config.baseUrl, path, req)}>; rel="successor-version"`;
|
|
283
|
+
}
|
|
273
284
|
res.set(headers);
|
|
274
285
|
res.status(200).send(json);
|
|
275
286
|
}
|
|
@@ -279,18 +290,17 @@ export function discoveryMiddleware(config, options = {}) {
|
|
|
279
290
|
};
|
|
280
291
|
}
|
|
281
292
|
/**
|
|
282
|
-
* Fastify plugin for
|
|
293
|
+
* Fastify plugin for A2A Agent Card discovery
|
|
283
294
|
*
|
|
284
295
|
* @param fastify - Fastify instance
|
|
285
296
|
* @param options - Plugin options
|
|
286
297
|
*/
|
|
287
298
|
export async function discoveryFastifyPlugin(fastify, options) {
|
|
288
|
-
const path = options.path || "/.well-known/
|
|
299
|
+
const path = options.path || "/.well-known/agent-card.json";
|
|
289
300
|
const config = options.config;
|
|
290
301
|
const cacheMaxAge = config.cacheMaxAge || 3600;
|
|
291
302
|
const handler = async (request, reply) => {
|
|
292
|
-
const doc =
|
|
293
|
-
validateDocument(doc);
|
|
303
|
+
const doc = buildAgentCard(config, request.raw);
|
|
294
304
|
const json = JSON.stringify(doc);
|
|
295
305
|
const etag = generateETag(json);
|
|
296
306
|
reply.header("Content-Type", "application/json");
|
|
@@ -301,4 +311,32 @@ export async function discoveryFastifyPlugin(fastify, options) {
|
|
|
301
311
|
// Note: Actual route registration depends on Fastify's API
|
|
302
312
|
// This is a type-safe signature for the plugin
|
|
303
313
|
}
|
|
314
|
+
// ============================================================================
|
|
315
|
+
// Breaking Changes Documentation
|
|
316
|
+
// ============================================================================
|
|
317
|
+
/**
|
|
318
|
+
* BREAKING CHANGES v1.2.3 → v2.0.0:
|
|
319
|
+
*
|
|
320
|
+
* 1. Discovery Endpoint Path
|
|
321
|
+
* - OLD: /.well-known/lafs.json
|
|
322
|
+
* - NEW: /.well-known/agent-card.json
|
|
323
|
+
* - MIGRATION: Update client code to use new path
|
|
324
|
+
* - BACKWARD COMPAT: Legacy path still works but logs deprecation warning
|
|
325
|
+
*
|
|
326
|
+
* 2. Discovery Document Format
|
|
327
|
+
* - OLD: DiscoveryDocument interface (lafs_version, service, capabilities, endpoints)
|
|
328
|
+
* - NEW: AgentCard interface (A2A v1.0 compliant)
|
|
329
|
+
* - MIGRATION: Update config from 'service' to 'agent' format
|
|
330
|
+
* - BACKWARD COMPAT: Legacy config format automatically converted with warning
|
|
331
|
+
*
|
|
332
|
+
* 3. Type Names
|
|
333
|
+
* - Capability → AgentSkill (renamed to align with A2A spec)
|
|
334
|
+
* - ServiceConfig → AgentCard (renamed)
|
|
335
|
+
* - All old types marked as @deprecated
|
|
336
|
+
*
|
|
337
|
+
* 4. Removed in v2.0.0
|
|
338
|
+
* - Legacy path support will be removed
|
|
339
|
+
* - Old type definitions will be removed
|
|
340
|
+
* - Automatic config migration will be removed
|
|
341
|
+
*/
|
|
304
342
|
export default discoveryMiddleware;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -10,4 +10,6 @@ export * from "./discovery.js";
|
|
|
10
10
|
export * from "./health/index.js";
|
|
11
11
|
export * from "./shutdown/index.js";
|
|
12
12
|
export * from "./circuit-breaker/index.js";
|
|
13
|
-
export
|
|
13
|
+
export { LafsA2AResult, createLafsArtifact, createTextArtifact, createFileArtifact, isExtensionRequired, getExtensionParams, AGENT_CARD_PATH, HTTP_EXTENSION_HEADER, LAFS_EXTENSION_URI, A2A_EXTENSIONS_HEADER, parseExtensionsHeader, negotiateExtensions, formatExtensionsHeader, buildLafsExtension, ExtensionSupportRequiredError, extensionNegotiationMiddleware, TERMINAL_STATES, INTERRUPTED_STATES, VALID_TRANSITIONS, isValidTransition, isTerminalState, isInterruptedState, InvalidStateTransitionError, TaskImmutabilityError, TaskNotFoundError, TaskManager, attachLafsEnvelope, } from "./a2a/index.js";
|
|
14
|
+
export type { LafsA2AConfig, LafsSendMessageParams, LafsExtensionParams, ExtensionNegotiationResult, BuildLafsExtensionOptions, ExtensionNegotiationMiddlewareOptions, CreateTaskOptions, ListTasksOptions, ListTasksResult, } from "./a2a/index.js";
|
|
15
|
+
export type { Task, TaskState, TaskStatus, Artifact, Part, Message, PushNotificationConfig, MessageSendConfiguration, TaskStatusUpdateEvent, TaskArtifactUpdateEvent, SendMessageResponse, SendMessageSuccessResponse, JSONRPCErrorResponse, TextPart, DataPart, FilePart, } from "./a2a/index.js";
|
package/dist/src/index.js
CHANGED
|
@@ -12,4 +12,13 @@ export * from "./health/index.js";
|
|
|
12
12
|
export * from "./shutdown/index.js";
|
|
13
13
|
export * from "./circuit-breaker/index.js";
|
|
14
14
|
// A2A Integration
|
|
15
|
-
export
|
|
15
|
+
// Explicitly re-export to avoid naming conflicts with discovery types
|
|
16
|
+
// (AgentCard, AgentSkill, AgentCapabilities, AgentExtension).
|
|
17
|
+
// For full A2A types, import from '@cleocode/lafs-protocol/a2a'.
|
|
18
|
+
export {
|
|
19
|
+
// Bridge
|
|
20
|
+
LafsA2AResult, createLafsArtifact, createTextArtifact, createFileArtifact, isExtensionRequired, getExtensionParams,
|
|
21
|
+
// Extensions (T098)
|
|
22
|
+
LAFS_EXTENSION_URI, A2A_EXTENSIONS_HEADER, parseExtensionsHeader, negotiateExtensions, formatExtensionsHeader, buildLafsExtension, ExtensionSupportRequiredError, extensionNegotiationMiddleware,
|
|
23
|
+
// Task Lifecycle (T099)
|
|
24
|
+
TERMINAL_STATES, INTERRUPTED_STATES, VALID_TRANSITIONS, isValidTransition, isTerminalState, isInterruptedState, InvalidStateTransitionError, TaskImmutabilityError, TaskNotFoundError, TaskManager, attachLafsEnvelope, } from "./a2a/index.js";
|
package/lafs.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleocode/lafs-protocol",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "LLM-Agent-First Specification schemas and conformance tooling",
|
|
@@ -10,6 +10,18 @@
|
|
|
10
10
|
".": {
|
|
11
11
|
"import": "./dist/src/index.js",
|
|
12
12
|
"types": "./dist/src/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./discovery": {
|
|
15
|
+
"import": "./dist/src/discovery.js",
|
|
16
|
+
"types": "./dist/src/discovery.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"./a2a": {
|
|
19
|
+
"import": "./dist/src/a2a/index.js",
|
|
20
|
+
"types": "./dist/src/a2a/index.d.ts"
|
|
21
|
+
},
|
|
22
|
+
"./a2a/bindings": {
|
|
23
|
+
"import": "./dist/src/a2a/bindings/index.js",
|
|
24
|
+
"types": "./dist/src/a2a/bindings/index.d.ts"
|
|
13
25
|
}
|
|
14
26
|
},
|
|
15
27
|
"files": [
|
|
@@ -48,6 +60,7 @@
|
|
|
48
60
|
"schema",
|
|
49
61
|
"conformance"
|
|
50
62
|
],
|
|
63
|
+
"author": "CLEO Code <hello@cleo.co> (https://cleo.co)",
|
|
51
64
|
"license": "MIT",
|
|
52
65
|
"devDependencies": {
|
|
53
66
|
"@modelcontextprotocol/sdk": "^1.26.0",
|