@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.
- package/LICENSE +21 -0
- package/README.md +235 -0
- package/dist/schemas/v1/conformance-profiles.json +39 -0
- package/dist/schemas/v1/envelope.schema.json +306 -0
- package/dist/schemas/v1/error-registry.json +162 -0
- 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 +102 -0
- package/dist/src/a2a/bindings/http.js +120 -0
- package/dist/src/a2a/bindings/index.d.ts +35 -0
- package/dist/src/a2a/bindings/index.js +79 -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 +175 -0
- package/dist/src/a2a/bridge.js +286 -0
- package/dist/src/a2a/extensions.d.ts +121 -0
- package/dist/src/a2a/extensions.js +205 -0
- package/dist/src/a2a/index.d.ts +40 -0
- package/dist/src/a2a/index.js +76 -0
- package/dist/src/a2a/streaming.d.ts +74 -0
- package/dist/src/a2a/streaming.js +265 -0
- package/dist/src/a2a/task-lifecycle.d.ts +109 -0
- package/dist/src/a2a/task-lifecycle.js +313 -0
- package/dist/src/budgetEnforcement.d.ts +84 -0
- package/dist/src/budgetEnforcement.js +328 -0
- package/dist/src/circuit-breaker/index.d.ts +121 -0
- package/dist/src/circuit-breaker/index.js +249 -0
- package/dist/src/cli.d.ts +16 -0
- package/dist/src/cli.js +63 -0
- package/dist/src/compliance.d.ts +31 -0
- package/dist/src/compliance.js +89 -0
- package/dist/src/conformance.d.ts +7 -0
- package/dist/src/conformance.js +248 -0
- package/dist/src/conformanceProfiles.d.ts +11 -0
- package/dist/src/conformanceProfiles.js +34 -0
- package/dist/src/deprecationRegistry.d.ts +13 -0
- package/dist/src/deprecationRegistry.js +39 -0
- package/dist/src/discovery.d.ts +286 -0
- package/dist/src/discovery.js +350 -0
- package/dist/src/envelope.d.ts +60 -0
- package/dist/src/envelope.js +136 -0
- package/dist/src/errorRegistry.d.ts +28 -0
- package/dist/src/errorRegistry.js +36 -0
- package/dist/src/fieldExtraction.d.ts +67 -0
- package/dist/src/fieldExtraction.js +133 -0
- package/dist/src/flagResolver.d.ts +46 -0
- package/dist/src/flagResolver.js +47 -0
- package/dist/src/flagSemantics.d.ts +16 -0
- package/dist/src/flagSemantics.js +45 -0
- package/dist/src/health/index.d.ts +105 -0
- package/dist/src/health/index.js +220 -0
- package/dist/src/index.d.ts +24 -0
- package/dist/src/index.js +34 -0
- package/dist/src/mcpAdapter.d.ts +28 -0
- package/dist/src/mcpAdapter.js +281 -0
- package/dist/src/mviProjection.d.ts +19 -0
- package/dist/src/mviProjection.js +116 -0
- package/dist/src/problemDetails.d.ts +34 -0
- package/dist/src/problemDetails.js +45 -0
- package/dist/src/shutdown/index.d.ts +69 -0
- package/dist/src/shutdown/index.js +160 -0
- package/dist/src/tokenEstimator.d.ts +87 -0
- package/dist/src/tokenEstimator.js +238 -0
- package/dist/src/types.d.ts +135 -0
- package/dist/src/types.js +12 -0
- package/dist/src/validateEnvelope.d.ts +15 -0
- package/dist/src/validateEnvelope.js +31 -0
- package/lafs.md +819 -0
- package/package.json +88 -0
- package/schemas/v1/agent-card.schema.json +230 -0
- package/schemas/v1/conformance-profiles.json +39 -0
- package/schemas/v1/context-ledger.schema.json +70 -0
- package/schemas/v1/discovery.schema.json +132 -0
- package/schemas/v1/envelope.schema.json +306 -0
- 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;
|