@cleocode/lafs-protocol 1.2.3 → 1.3.2
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 +211 -165
- package/dist/src/health/index.js +10 -1
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +10 -1
- package/dist/src/types.d.ts +2 -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,242 @@
|
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
}
|
|
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');
|
|
79
20
|
}
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Utility Functions
|
|
23
|
+
// ============================================================================
|
|
80
24
|
/**
|
|
81
25
|
* Build absolute URL from base and path
|
|
82
26
|
*/
|
|
83
27
|
function buildUrl(base, path, req) {
|
|
84
|
-
// If path is already absolute, return it
|
|
85
28
|
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
86
29
|
return path;
|
|
87
30
|
}
|
|
88
|
-
// If base is provided, use it
|
|
89
31
|
if (base) {
|
|
90
32
|
const separator = base.endsWith("/") || path.startsWith("/") ? "" : "/";
|
|
91
33
|
return `${base}${separator}${path}`;
|
|
92
34
|
}
|
|
93
|
-
// Otherwise try to construct from request
|
|
94
35
|
if (req) {
|
|
95
36
|
const protocol = req.headers["x-forwarded-proto"] || req.protocol || "http";
|
|
96
37
|
const host = req.headers.host || "localhost";
|
|
97
38
|
const separator = path.startsWith("/") ? "" : "/";
|
|
98
39
|
return `${protocol}://${host}${separator}${path}`;
|
|
99
40
|
}
|
|
100
|
-
// Fallback to relative path
|
|
101
41
|
return path.startsWith("/") ? path : `/${path}`;
|
|
102
42
|
}
|
|
103
43
|
/**
|
|
104
|
-
* Generate ETag from
|
|
44
|
+
* Generate ETag from content
|
|
105
45
|
*/
|
|
106
46
|
function generateETag(content) {
|
|
107
47
|
return `"${createHash("sha256").update(content).digest("hex").slice(0, 32)}"`;
|
|
108
48
|
}
|
|
109
49
|
/**
|
|
110
|
-
* Build
|
|
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
|
|
111
110
|
*/
|
|
112
|
-
function
|
|
111
|
+
function buildLegacyDiscoveryDocument(config, req) {
|
|
113
112
|
const schemaUrl = config.schemaUrl || "https://lafs.dev/schemas/v1/discovery.schema.json";
|
|
114
|
-
const lafsVersion = config.lafsVersion ||
|
|
113
|
+
const lafsVersion = config.lafsVersion || pkg.version;
|
|
115
114
|
return {
|
|
116
115
|
$schema: schemaUrl,
|
|
117
116
|
lafs_version: lafsVersion,
|
|
118
|
-
service: config.service
|
|
119
|
-
|
|
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
|
+
})),
|
|
120
129
|
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)
|
|
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)
|
|
128
133
|
}
|
|
129
134
|
};
|
|
130
135
|
}
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// Middleware
|
|
138
|
+
// ============================================================================
|
|
131
139
|
/**
|
|
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
|
|
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
|
|
148
144
|
*
|
|
149
|
-
* @param config - Discovery configuration
|
|
145
|
+
* @param config - Discovery configuration (A2A v1.0 format)
|
|
150
146
|
* @param options - Middleware options
|
|
151
147
|
* @returns Express RequestHandler
|
|
152
148
|
*
|
|
153
149
|
* @example
|
|
154
150
|
* ```typescript
|
|
155
151
|
* import express from "express";
|
|
156
|
-
* import { discoveryMiddleware } from "
|
|
152
|
+
* import { discoveryMiddleware } from "@cleocode/lafs-protocol/discovery";
|
|
157
153
|
*
|
|
158
154
|
* const app = express();
|
|
159
155
|
*
|
|
160
156
|
* app.use(discoveryMiddleware({
|
|
161
|
-
*
|
|
162
|
-
* name: "my-lafs-
|
|
157
|
+
* agent: {
|
|
158
|
+
* name: "my-lafs-agent",
|
|
159
|
+
* description: "A LAFS-compliant agent with A2A support",
|
|
163
160
|
* version: "1.0.0",
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
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
|
+
* ]
|
|
177
178
|
* }
|
|
178
179
|
* }));
|
|
179
180
|
* ```
|
|
180
181
|
*/
|
|
181
182
|
export function discoveryMiddleware(config, options = {}) {
|
|
182
|
-
const path = options.path || "/.well-known/
|
|
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;
|
|
183
187
|
const enableHead = options.enableHead !== false;
|
|
184
188
|
const enableEtag = options.enableEtag !== false;
|
|
185
189
|
const cacheMaxAge = config.cacheMaxAge || 3600;
|
|
186
190
|
// Validate configuration
|
|
187
|
-
if (!config.
|
|
188
|
-
throw new Error("Discovery config requires
|
|
191
|
+
if (!config.agent && !config.service) {
|
|
192
|
+
throw new Error("Discovery config requires 'agent' (A2A v1.0) or 'service' (legacy) configuration");
|
|
189
193
|
}
|
|
190
|
-
|
|
191
|
-
|
|
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
|
+
}
|
|
192
202
|
}
|
|
193
|
-
|
|
194
|
-
|
|
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;
|
|
195
226
|
}
|
|
196
227
|
return function discoveryHandler(req, res, next) {
|
|
197
|
-
|
|
198
|
-
|
|
228
|
+
const isPrimaryPath = req.path === path;
|
|
229
|
+
const isLegacyPath = enableLegacyPath && req.path === legacyPath;
|
|
230
|
+
// Only handle requests to discovery paths
|
|
231
|
+
if (!isPrimaryPath && !isLegacyPath) {
|
|
199
232
|
next();
|
|
200
233
|
return;
|
|
201
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
|
+
}
|
|
202
240
|
// Handle HEAD requests
|
|
203
241
|
if (req.method === "HEAD") {
|
|
204
242
|
if (!enableHead) {
|
|
@@ -208,23 +246,13 @@ export function discoveryMiddleware(config, options = {}) {
|
|
|
208
246
|
});
|
|
209
247
|
return;
|
|
210
248
|
}
|
|
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;
|
|
249
|
+
const json = getSerializedDoc(isLegacyPath, req);
|
|
250
|
+
const etag = enableEtag ? generateETag(json) : undefined;
|
|
224
251
|
res.set({
|
|
225
252
|
"Content-Type": "application/json",
|
|
226
253
|
"Cache-Control": `public, max-age=${cacheMaxAge}`,
|
|
227
254
|
...(etag && { "ETag": etag }),
|
|
255
|
+
...(isLegacyPath && { "Deprecation": "true", "Sunset": "Sat, 31 Dec 2025 23:59:59 GMT" }),
|
|
228
256
|
"Content-Length": Buffer.byteLength(json)
|
|
229
257
|
});
|
|
230
258
|
res.status(200).end();
|
|
@@ -239,23 +267,8 @@ export function discoveryMiddleware(config, options = {}) {
|
|
|
239
267
|
return;
|
|
240
268
|
}
|
|
241
269
|
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;
|
|
270
|
+
const json = getSerializedDoc(isLegacyPath, req);
|
|
271
|
+
const etag = enableEtag ? generateETag(json) : undefined;
|
|
259
272
|
// Check If-None-Match for conditional request
|
|
260
273
|
if (enableEtag && req.headers["if-none-match"] === etag) {
|
|
261
274
|
res.status(304).end();
|
|
@@ -270,6 +283,12 @@ export function discoveryMiddleware(config, options = {}) {
|
|
|
270
283
|
if (etag) {
|
|
271
284
|
headers["ETag"] = etag;
|
|
272
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
|
+
}
|
|
273
292
|
res.set(headers);
|
|
274
293
|
res.status(200).send(json);
|
|
275
294
|
}
|
|
@@ -279,18 +298,17 @@ export function discoveryMiddleware(config, options = {}) {
|
|
|
279
298
|
};
|
|
280
299
|
}
|
|
281
300
|
/**
|
|
282
|
-
* Fastify plugin for
|
|
301
|
+
* Fastify plugin for A2A Agent Card discovery
|
|
283
302
|
*
|
|
284
303
|
* @param fastify - Fastify instance
|
|
285
304
|
* @param options - Plugin options
|
|
286
305
|
*/
|
|
287
306
|
export async function discoveryFastifyPlugin(fastify, options) {
|
|
288
|
-
const path = options.path || "/.well-known/
|
|
307
|
+
const path = options.path || "/.well-known/agent-card.json";
|
|
289
308
|
const config = options.config;
|
|
290
309
|
const cacheMaxAge = config.cacheMaxAge || 3600;
|
|
291
310
|
const handler = async (request, reply) => {
|
|
292
|
-
const doc =
|
|
293
|
-
validateDocument(doc);
|
|
311
|
+
const doc = buildAgentCard(config, request.raw);
|
|
294
312
|
const json = JSON.stringify(doc);
|
|
295
313
|
const etag = generateETag(json);
|
|
296
314
|
reply.header("Content-Type", "application/json");
|
|
@@ -301,4 +319,32 @@ export async function discoveryFastifyPlugin(fastify, options) {
|
|
|
301
319
|
// Note: Actual route registration depends on Fastify's API
|
|
302
320
|
// This is a type-safe signature for the plugin
|
|
303
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
|
+
*/
|
|
304
350
|
export default discoveryMiddleware;
|
package/dist/src/health/index.js
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Provides health check endpoints for monitoring and orchestration
|
|
5
5
|
*/
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
let pkg;
|
|
9
|
+
try {
|
|
10
|
+
pkg = require('../../package.json');
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
pkg = require('../../../package.json');
|
|
14
|
+
}
|
|
6
15
|
/**
|
|
7
16
|
* Health check middleware for Express applications
|
|
8
17
|
*
|
|
@@ -70,7 +79,7 @@ export function healthCheck(config = {}) {
|
|
|
70
79
|
const health = {
|
|
71
80
|
status,
|
|
72
81
|
timestamp,
|
|
73
|
-
version:
|
|
82
|
+
version: pkg.version,
|
|
74
83
|
uptime: Math.floor((Date.now() - startTime) / 1000),
|
|
75
84
|
checks: checkResults
|
|
76
85
|
};
|
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/dist/src/types.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface Warning {
|
|
|
7
7
|
replacement?: string;
|
|
8
8
|
removeBy?: string;
|
|
9
9
|
}
|
|
10
|
+
export type MVILevel = 'minimal' | 'standard' | 'full' | 'custom';
|
|
10
11
|
export interface LAFSMeta {
|
|
11
12
|
specVersion: string;
|
|
12
13
|
schemaVersion: string;
|
|
@@ -15,7 +16,7 @@ export interface LAFSMeta {
|
|
|
15
16
|
requestId: string;
|
|
16
17
|
transport: LAFSTransport;
|
|
17
18
|
strict: boolean;
|
|
18
|
-
mvi:
|
|
19
|
+
mvi: MVILevel;
|
|
19
20
|
contextVersion: number;
|
|
20
21
|
/** Session identifier for correlating multi-step agent workflows */
|
|
21
22
|
sessionId?: string;
|
package/lafs.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleocode/lafs-protocol",
|
|
3
|
-
"version": "1.2
|
|
3
|
+
"version": "1.3.2",
|
|
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": [
|
|
@@ -34,6 +46,7 @@
|
|
|
34
46
|
"lafs-conformance": "dist/src/cli.js"
|
|
35
47
|
},
|
|
36
48
|
"scripts": {
|
|
49
|
+
"version": "node scripts/sync-version.mjs",
|
|
37
50
|
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|
|
38
51
|
"prepack": "npm run build",
|
|
39
52
|
"typecheck": "tsc --noEmit",
|