@agents-txt/core 0.1.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/dist/index.cjs +712 -0
- package/dist/index.d.cts +681 -0
- package/dist/index.d.ts +681 -0
- package/dist/index.js +668 -0
- package/package.json +57 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
// src/utils.ts
|
|
2
|
+
function sanitizeValue(value, maxLength = 500) {
|
|
3
|
+
const str = typeof value === "string" ? value : String(value ?? "");
|
|
4
|
+
return str.replace(/[\r\n]/g, " ").replace(/[\x00-\x1f]/g, "").trim().slice(0, maxLength);
|
|
5
|
+
}
|
|
6
|
+
function parseRateLimit(value) {
|
|
7
|
+
const match = value.match(/^(\d+)\/(second|minute|hour|day)$/);
|
|
8
|
+
if (!match) return null;
|
|
9
|
+
return { requests: parseInt(match[1], 10), window: match[2] };
|
|
10
|
+
}
|
|
11
|
+
function formatRateLimit(requests, window) {
|
|
12
|
+
return `${requests}/${window}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/parser.ts
|
|
16
|
+
var VALID_PROTOCOLS = /* @__PURE__ */ new Set(["REST", "MCP", "A2A", "GraphQL", "WebSocket"]);
|
|
17
|
+
var VALID_AUTH_TYPES = /* @__PURE__ */ new Set(["none", "api-key", "bearer-token", "oauth2", "hmac"]);
|
|
18
|
+
var VALID_DECLARATION_TYPES = /* @__PURE__ */ new Set(["platform", "agent"]);
|
|
19
|
+
function parse(input) {
|
|
20
|
+
const errors = [];
|
|
21
|
+
const warnings = [];
|
|
22
|
+
const lines = input.split(/\r?\n/);
|
|
23
|
+
let specVersion = "1.0";
|
|
24
|
+
let generatedAt;
|
|
25
|
+
let declarationType;
|
|
26
|
+
const operatesOn = [];
|
|
27
|
+
const site = {};
|
|
28
|
+
const capabilities = [];
|
|
29
|
+
const allowPaths = [];
|
|
30
|
+
const disallowPaths = [];
|
|
31
|
+
const agents = {};
|
|
32
|
+
const metadata = {};
|
|
33
|
+
let state = "TOP_LEVEL";
|
|
34
|
+
let currentCapability = null;
|
|
35
|
+
let currentAgentName = null;
|
|
36
|
+
let currentAgentPolicy = null;
|
|
37
|
+
function flushCapability() {
|
|
38
|
+
if (currentCapability && currentCapability.id) {
|
|
39
|
+
capabilities.push({
|
|
40
|
+
id: currentCapability.id,
|
|
41
|
+
description: currentCapability.description ?? "",
|
|
42
|
+
endpoint: currentCapability.endpoint ?? "",
|
|
43
|
+
method: currentCapability.method,
|
|
44
|
+
protocol: currentCapability.protocol ?? "REST",
|
|
45
|
+
auth: currentCapability.auth,
|
|
46
|
+
rateLimit: currentCapability.rateLimit,
|
|
47
|
+
openapi: currentCapability.openapi,
|
|
48
|
+
parameters: currentCapability.parameters,
|
|
49
|
+
scopes: currentCapability.scopes
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
currentCapability = null;
|
|
53
|
+
}
|
|
54
|
+
function flushAgent() {
|
|
55
|
+
if (currentAgentName !== null && currentAgentPolicy !== null) {
|
|
56
|
+
agents[currentAgentName] = currentAgentPolicy;
|
|
57
|
+
}
|
|
58
|
+
currentAgentName = null;
|
|
59
|
+
currentAgentPolicy = null;
|
|
60
|
+
}
|
|
61
|
+
for (let i = 0; i < lines.length; i++) {
|
|
62
|
+
const lineNum = i + 1;
|
|
63
|
+
const raw = lines[i];
|
|
64
|
+
const trimmed = raw.trim();
|
|
65
|
+
if (trimmed === "" || trimmed.startsWith("#")) {
|
|
66
|
+
const specMatch = trimmed.match(/^#\s*Spec-Version:\s*(.+)/i);
|
|
67
|
+
if (specMatch) specVersion = specMatch[1].trim();
|
|
68
|
+
const genMatch = trimmed.match(/^#\s*Generated:\s*(.+)/i);
|
|
69
|
+
if (genMatch) generatedAt = genMatch[1].trim();
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const isIndented = raw.startsWith(" ") || raw.startsWith(" ");
|
|
73
|
+
if (isIndented && state === "IN_CAPABILITY" && currentCapability) {
|
|
74
|
+
const colonIdx2 = trimmed.indexOf(":");
|
|
75
|
+
if (colonIdx2 === -1) {
|
|
76
|
+
warnings.push({ line: lineNum, message: `Unparseable indented line: "${trimmed}"` });
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const key2 = trimmed.slice(0, colonIdx2).trim();
|
|
80
|
+
const value2 = trimmed.slice(colonIdx2 + 1).trim();
|
|
81
|
+
switch (key2) {
|
|
82
|
+
case "Endpoint":
|
|
83
|
+
currentCapability.endpoint = value2;
|
|
84
|
+
break;
|
|
85
|
+
case "Method":
|
|
86
|
+
currentCapability.method = value2.toUpperCase();
|
|
87
|
+
break;
|
|
88
|
+
case "Protocol":
|
|
89
|
+
if (VALID_PROTOCOLS.has(value2)) {
|
|
90
|
+
currentCapability.protocol = value2;
|
|
91
|
+
} else {
|
|
92
|
+
warnings.push({ line: lineNum, field: "Protocol", message: `Unknown protocol: ${value2}` });
|
|
93
|
+
currentCapability.protocol = value2;
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
case "Auth":
|
|
97
|
+
if (!currentCapability.auth) currentCapability.auth = { type: "none" };
|
|
98
|
+
if (VALID_AUTH_TYPES.has(value2)) {
|
|
99
|
+
currentCapability.auth.type = value2;
|
|
100
|
+
} else {
|
|
101
|
+
warnings.push({ line: lineNum, field: "Auth", message: `Unknown auth type: ${value2}` });
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
104
|
+
case "Auth-Endpoint":
|
|
105
|
+
if (!currentCapability.auth) currentCapability.auth = { type: "bearer-token" };
|
|
106
|
+
currentCapability.auth.tokenEndpoint = value2;
|
|
107
|
+
break;
|
|
108
|
+
case "Auth-Docs":
|
|
109
|
+
if (!currentCapability.auth) currentCapability.auth = { type: "none" };
|
|
110
|
+
currentCapability.auth.docsUrl = value2;
|
|
111
|
+
break;
|
|
112
|
+
case "Scopes":
|
|
113
|
+
if (!currentCapability.auth) currentCapability.auth = { type: "oauth2" };
|
|
114
|
+
currentCapability.auth.scopes = value2.split(",").map((s) => s.trim());
|
|
115
|
+
break;
|
|
116
|
+
case "Rate-Limit": {
|
|
117
|
+
const rl = parseRateLimit(value2);
|
|
118
|
+
if (rl) {
|
|
119
|
+
currentCapability.rateLimit = { requests: rl.requests, window: rl.window };
|
|
120
|
+
} else {
|
|
121
|
+
warnings.push({ line: lineNum, field: "Rate-Limit", message: `Invalid rate limit: ${value2}` });
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case "Description":
|
|
126
|
+
currentCapability.description = value2;
|
|
127
|
+
break;
|
|
128
|
+
case "OpenAPI":
|
|
129
|
+
currentCapability.openapi = value2;
|
|
130
|
+
break;
|
|
131
|
+
case "Param": {
|
|
132
|
+
const parsed = parseParam(value2);
|
|
133
|
+
if (parsed) {
|
|
134
|
+
if (!currentCapability.parameters) currentCapability.parameters = [];
|
|
135
|
+
currentCapability.parameters.push(parsed);
|
|
136
|
+
} else {
|
|
137
|
+
warnings.push({ line: lineNum, field: "Param", message: `Invalid parameter: ${value2}` });
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
default:
|
|
142
|
+
warnings.push({ line: lineNum, message: `Unknown capability field: ${key2}` });
|
|
143
|
+
}
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (isIndented && state === "IN_AGENT" && currentAgentPolicy) {
|
|
147
|
+
const colonIdx2 = trimmed.indexOf(":");
|
|
148
|
+
if (colonIdx2 === -1) continue;
|
|
149
|
+
const key2 = trimmed.slice(0, colonIdx2).trim();
|
|
150
|
+
const value2 = trimmed.slice(colonIdx2 + 1).trim();
|
|
151
|
+
switch (key2) {
|
|
152
|
+
case "Rate-Limit": {
|
|
153
|
+
const rl = parseRateLimit(value2);
|
|
154
|
+
if (rl) {
|
|
155
|
+
currentAgentPolicy.rateLimit = { requests: rl.requests, window: rl.window };
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case "Capabilities":
|
|
160
|
+
currentAgentPolicy.capabilities = value2.split(",").map((s) => s.trim());
|
|
161
|
+
break;
|
|
162
|
+
case "Agent-Declaration":
|
|
163
|
+
currentAgentPolicy.agentDeclaration = value2;
|
|
164
|
+
break;
|
|
165
|
+
default:
|
|
166
|
+
warnings.push({ line: lineNum, message: `Unknown agent field: ${key2}` });
|
|
167
|
+
}
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (state === "IN_CAPABILITY") {
|
|
171
|
+
flushCapability();
|
|
172
|
+
state = "TOP_LEVEL";
|
|
173
|
+
}
|
|
174
|
+
if (state === "IN_AGENT") {
|
|
175
|
+
flushAgent();
|
|
176
|
+
state = "TOP_LEVEL";
|
|
177
|
+
}
|
|
178
|
+
const colonIdx = trimmed.indexOf(":");
|
|
179
|
+
if (colonIdx === -1) {
|
|
180
|
+
warnings.push({ line: lineNum, message: `Unparseable line: "${trimmed}"` });
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const key = trimmed.slice(0, colonIdx).trim();
|
|
184
|
+
const value = trimmed.slice(colonIdx + 1).trim();
|
|
185
|
+
switch (key) {
|
|
186
|
+
case "Site-Name":
|
|
187
|
+
site.name = value;
|
|
188
|
+
break;
|
|
189
|
+
case "Site-URL":
|
|
190
|
+
site.url = value;
|
|
191
|
+
break;
|
|
192
|
+
case "Description":
|
|
193
|
+
case "Site-Description":
|
|
194
|
+
site.description = value;
|
|
195
|
+
break;
|
|
196
|
+
case "Contact":
|
|
197
|
+
case "Site-Contact":
|
|
198
|
+
site.contact = value;
|
|
199
|
+
break;
|
|
200
|
+
case "Privacy-Policy":
|
|
201
|
+
case "Site-Privacy-Policy":
|
|
202
|
+
site.privacyPolicy = value;
|
|
203
|
+
break;
|
|
204
|
+
case "Declaration-Type":
|
|
205
|
+
if (VALID_DECLARATION_TYPES.has(value)) {
|
|
206
|
+
declarationType = value;
|
|
207
|
+
} else {
|
|
208
|
+
warnings.push({ line: lineNum, field: "Declaration-Type", message: `Unknown declaration type: ${value}` });
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
case "Operates-On":
|
|
212
|
+
operatesOn.push(value);
|
|
213
|
+
break;
|
|
214
|
+
case "Allow":
|
|
215
|
+
allowPaths.push(value);
|
|
216
|
+
break;
|
|
217
|
+
case "Disallow":
|
|
218
|
+
disallowPaths.push(value);
|
|
219
|
+
break;
|
|
220
|
+
case "Agents-JSON":
|
|
221
|
+
metadata["Agents-JSON"] = value;
|
|
222
|
+
break;
|
|
223
|
+
case "Capability":
|
|
224
|
+
currentCapability = { id: value };
|
|
225
|
+
state = "IN_CAPABILITY";
|
|
226
|
+
break;
|
|
227
|
+
case "Agent":
|
|
228
|
+
currentAgentName = value;
|
|
229
|
+
currentAgentPolicy = {};
|
|
230
|
+
state = "IN_AGENT";
|
|
231
|
+
break;
|
|
232
|
+
default:
|
|
233
|
+
metadata[key] = value;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (state === "IN_CAPABILITY") flushCapability();
|
|
237
|
+
if (state === "IN_AGENT") flushAgent();
|
|
238
|
+
if (!site.name) errors.push({ field: "Site-Name", message: "Site-Name is required" });
|
|
239
|
+
if (!site.url) errors.push({ field: "Site-URL", message: "Site-URL is required" });
|
|
240
|
+
if (errors.length > 0) {
|
|
241
|
+
return { success: false, errors, warnings };
|
|
242
|
+
}
|
|
243
|
+
const document = {
|
|
244
|
+
specVersion,
|
|
245
|
+
generatedAt,
|
|
246
|
+
declarationType,
|
|
247
|
+
operatesOn: operatesOn.length > 0 ? operatesOn : void 0,
|
|
248
|
+
site: {
|
|
249
|
+
name: site.name,
|
|
250
|
+
url: site.url,
|
|
251
|
+
description: site.description,
|
|
252
|
+
contact: site.contact,
|
|
253
|
+
privacyPolicy: site.privacyPolicy
|
|
254
|
+
},
|
|
255
|
+
capabilities,
|
|
256
|
+
access: {
|
|
257
|
+
allow: allowPaths.length > 0 ? allowPaths : ["*"],
|
|
258
|
+
disallow: disallowPaths
|
|
259
|
+
},
|
|
260
|
+
agents: Object.keys(agents).length > 0 ? agents : { "*": {} },
|
|
261
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : void 0
|
|
262
|
+
};
|
|
263
|
+
return { success: true, document, errors, warnings };
|
|
264
|
+
}
|
|
265
|
+
function parseParam(value) {
|
|
266
|
+
const match = value.match(
|
|
267
|
+
/^(\w+)\s*\(\s*(\w+)\s*,\s*(\w+)(?:\s*,\s*(required))?\s*\)(?:\s*—\s*(.+))?$/
|
|
268
|
+
);
|
|
269
|
+
if (!match) return null;
|
|
270
|
+
const [, name, location, type, req, description] = match;
|
|
271
|
+
const validLocations = /* @__PURE__ */ new Set(["query", "path", "header", "body"]);
|
|
272
|
+
if (!validLocations.has(location)) return null;
|
|
273
|
+
return {
|
|
274
|
+
name,
|
|
275
|
+
in: location,
|
|
276
|
+
type,
|
|
277
|
+
required: req === "required",
|
|
278
|
+
description: description?.trim()
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/schema.ts
|
|
283
|
+
import { z } from "zod";
|
|
284
|
+
var RateLimitWindowSchema = z.enum(["second", "minute", "hour", "day"]);
|
|
285
|
+
var RateLimitSchema = z.object({
|
|
286
|
+
requests: z.number().int().positive(),
|
|
287
|
+
window: RateLimitWindowSchema
|
|
288
|
+
});
|
|
289
|
+
var AuthTypeSchema = z.enum(["none", "api-key", "bearer-token", "oauth2", "hmac"]);
|
|
290
|
+
var AuthConfigSchema = z.object({
|
|
291
|
+
type: AuthTypeSchema,
|
|
292
|
+
tokenEndpoint: z.string().url().optional(),
|
|
293
|
+
docsUrl: z.string().url().optional(),
|
|
294
|
+
scopes: z.array(z.string()).optional()
|
|
295
|
+
});
|
|
296
|
+
var ProtocolSchema = z.enum(["REST", "MCP", "A2A", "GraphQL", "WebSocket"]);
|
|
297
|
+
var ParameterDefSchema = z.object({
|
|
298
|
+
name: z.string().min(1),
|
|
299
|
+
in: z.enum(["query", "path", "header", "body"]),
|
|
300
|
+
type: z.string().min(1),
|
|
301
|
+
required: z.boolean().optional(),
|
|
302
|
+
default: z.unknown().optional(),
|
|
303
|
+
description: z.string().optional(),
|
|
304
|
+
max: z.number().optional(),
|
|
305
|
+
min: z.number().optional()
|
|
306
|
+
});
|
|
307
|
+
var CapabilitySchema = z.object({
|
|
308
|
+
id: z.string().min(1).max(100).regex(/^[a-z0-9][a-z0-9-]*$/, "Must be lowercase alphanumeric with hyphens"),
|
|
309
|
+
description: z.string().min(1).max(500),
|
|
310
|
+
endpoint: z.string().url(),
|
|
311
|
+
method: z.string().optional(),
|
|
312
|
+
protocol: ProtocolSchema,
|
|
313
|
+
auth: AuthConfigSchema.optional(),
|
|
314
|
+
rateLimit: RateLimitSchema.optional(),
|
|
315
|
+
openapi: z.string().url().optional(),
|
|
316
|
+
parameters: z.array(ParameterDefSchema).optional(),
|
|
317
|
+
scopes: z.array(z.string()).optional()
|
|
318
|
+
});
|
|
319
|
+
var SiteInfoSchema = z.object({
|
|
320
|
+
name: z.string().min(1).max(200),
|
|
321
|
+
url: z.string().url(),
|
|
322
|
+
description: z.string().max(500).optional(),
|
|
323
|
+
contact: z.string().email().optional(),
|
|
324
|
+
privacyPolicy: z.string().url().optional()
|
|
325
|
+
});
|
|
326
|
+
var AccessControlSchema = z.object({
|
|
327
|
+
allow: z.array(z.string()),
|
|
328
|
+
disallow: z.array(z.string())
|
|
329
|
+
});
|
|
330
|
+
var AgentPolicySchema = z.object({
|
|
331
|
+
rateLimit: RateLimitSchema.optional(),
|
|
332
|
+
capabilities: z.array(z.string()).optional(),
|
|
333
|
+
agentDeclaration: z.string().url().optional()
|
|
334
|
+
});
|
|
335
|
+
var DeclarationTypeSchema = z.enum(["platform", "agent"]);
|
|
336
|
+
var AgentsTxtDocumentSchema = z.object({
|
|
337
|
+
specVersion: z.string().regex(/^\d+\.\d+$/, "Must be major.minor format"),
|
|
338
|
+
generatedAt: z.string().datetime().optional(),
|
|
339
|
+
declarationType: DeclarationTypeSchema.optional(),
|
|
340
|
+
operatesOn: z.array(z.string().url()).optional(),
|
|
341
|
+
site: SiteInfoSchema,
|
|
342
|
+
capabilities: z.array(CapabilitySchema),
|
|
343
|
+
access: AccessControlSchema,
|
|
344
|
+
agents: z.record(z.string(), AgentPolicySchema),
|
|
345
|
+
metadata: z.record(z.string(), z.string()).optional()
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// src/parser-json.ts
|
|
349
|
+
function parseJSON(input) {
|
|
350
|
+
let raw;
|
|
351
|
+
try {
|
|
352
|
+
raw = JSON.parse(input);
|
|
353
|
+
} catch (err) {
|
|
354
|
+
return {
|
|
355
|
+
success: false,
|
|
356
|
+
errors: [{ message: `Invalid JSON: ${err instanceof Error ? err.message : "parse error"}` }],
|
|
357
|
+
warnings: []
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
const result = AgentsTxtDocumentSchema.safeParse(raw);
|
|
361
|
+
if (!result.success) {
|
|
362
|
+
return {
|
|
363
|
+
success: false,
|
|
364
|
+
errors: result.error.issues.map((issue) => ({
|
|
365
|
+
field: issue.path.join("."),
|
|
366
|
+
message: issue.message
|
|
367
|
+
})),
|
|
368
|
+
warnings: []
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
return {
|
|
372
|
+
success: true,
|
|
373
|
+
document: result.data,
|
|
374
|
+
errors: [],
|
|
375
|
+
warnings: []
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/generator.ts
|
|
380
|
+
function generate(doc) {
|
|
381
|
+
const lines = [];
|
|
382
|
+
lines.push("# agents.txt \u2014 AI Agent Capability Declaration");
|
|
383
|
+
lines.push(`# Spec-Version: ${doc.specVersion}`);
|
|
384
|
+
if (doc.generatedAt) {
|
|
385
|
+
lines.push(`# Generated: ${doc.generatedAt}`);
|
|
386
|
+
}
|
|
387
|
+
lines.push("");
|
|
388
|
+
if (doc.declarationType) {
|
|
389
|
+
lines.push(`Declaration-Type: ${doc.declarationType}`);
|
|
390
|
+
}
|
|
391
|
+
if (doc.operatesOn) {
|
|
392
|
+
for (const url of doc.operatesOn) {
|
|
393
|
+
lines.push(`Operates-On: ${url}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (doc.declarationType || doc.operatesOn) {
|
|
397
|
+
lines.push("");
|
|
398
|
+
}
|
|
399
|
+
lines.push(`Site-Name: ${sanitizeValue(doc.site.name)}`);
|
|
400
|
+
lines.push(`Site-URL: ${sanitizeValue(doc.site.url)}`);
|
|
401
|
+
if (doc.site.description) {
|
|
402
|
+
lines.push(`Description: ${sanitizeValue(doc.site.description)}`);
|
|
403
|
+
}
|
|
404
|
+
if (doc.site.contact) {
|
|
405
|
+
lines.push(`Contact: ${sanitizeValue(doc.site.contact)}`);
|
|
406
|
+
}
|
|
407
|
+
if (doc.site.privacyPolicy) {
|
|
408
|
+
lines.push(`Privacy-Policy: ${sanitizeValue(doc.site.privacyPolicy)}`);
|
|
409
|
+
}
|
|
410
|
+
lines.push("");
|
|
411
|
+
for (const cap of doc.capabilities) {
|
|
412
|
+
lines.push(`Capability: ${cap.id}`);
|
|
413
|
+
lines.push(` Endpoint: ${cap.endpoint}`);
|
|
414
|
+
if (cap.method) {
|
|
415
|
+
lines.push(` Method: ${cap.method}`);
|
|
416
|
+
}
|
|
417
|
+
lines.push(` Protocol: ${cap.protocol}`);
|
|
418
|
+
if (cap.auth) {
|
|
419
|
+
lines.push(` Auth: ${cap.auth.type}`);
|
|
420
|
+
if (cap.auth.tokenEndpoint) {
|
|
421
|
+
lines.push(` Auth-Endpoint: ${cap.auth.tokenEndpoint}`);
|
|
422
|
+
}
|
|
423
|
+
if (cap.auth.docsUrl) {
|
|
424
|
+
lines.push(` Auth-Docs: ${cap.auth.docsUrl}`);
|
|
425
|
+
}
|
|
426
|
+
if (cap.auth.scopes && cap.auth.scopes.length > 0) {
|
|
427
|
+
lines.push(` Scopes: ${cap.auth.scopes.join(", ")}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (cap.rateLimit) {
|
|
431
|
+
lines.push(` Rate-Limit: ${formatRateLimit(cap.rateLimit.requests, cap.rateLimit.window)}`);
|
|
432
|
+
}
|
|
433
|
+
if (cap.description) {
|
|
434
|
+
lines.push(` Description: ${sanitizeValue(cap.description)}`);
|
|
435
|
+
}
|
|
436
|
+
if (cap.openapi) {
|
|
437
|
+
lines.push(` OpenAPI: ${cap.openapi}`);
|
|
438
|
+
}
|
|
439
|
+
if (cap.parameters) {
|
|
440
|
+
for (const param of cap.parameters) {
|
|
441
|
+
const parts = [param.in, param.type];
|
|
442
|
+
if (param.required) parts.push("required");
|
|
443
|
+
let line = ` Param: ${param.name} (${parts.join(", ")})`;
|
|
444
|
+
if (param.description) line += ` \u2014 ${sanitizeValue(param.description)}`;
|
|
445
|
+
lines.push(line);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
lines.push("");
|
|
449
|
+
}
|
|
450
|
+
for (const pattern of doc.access.allow) {
|
|
451
|
+
lines.push(`Allow: ${pattern}`);
|
|
452
|
+
}
|
|
453
|
+
for (const pattern of doc.access.disallow) {
|
|
454
|
+
lines.push(`Disallow: ${pattern}`);
|
|
455
|
+
}
|
|
456
|
+
lines.push("");
|
|
457
|
+
for (const [agent, policy] of Object.entries(doc.agents)) {
|
|
458
|
+
lines.push(`Agent: ${agent}`);
|
|
459
|
+
if (policy.rateLimit) {
|
|
460
|
+
lines.push(` Rate-Limit: ${formatRateLimit(policy.rateLimit.requests, policy.rateLimit.window)}`);
|
|
461
|
+
}
|
|
462
|
+
if (policy.capabilities && policy.capabilities.length > 0) {
|
|
463
|
+
lines.push(` Capabilities: ${policy.capabilities.join(", ")}`);
|
|
464
|
+
}
|
|
465
|
+
if (policy.agentDeclaration) {
|
|
466
|
+
lines.push(` Agent-Declaration: ${policy.agentDeclaration}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
lines.push("");
|
|
470
|
+
if (doc.metadata) {
|
|
471
|
+
for (const [key, value] of Object.entries(doc.metadata)) {
|
|
472
|
+
lines.push(`${key}: ${sanitizeValue(value)}`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return lines.join("\n") + "\n";
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/generator-json.ts
|
|
479
|
+
function generateJSON(doc) {
|
|
480
|
+
return JSON.stringify(doc, null, 2);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// src/validator.ts
|
|
484
|
+
function validate(doc) {
|
|
485
|
+
const errors = [];
|
|
486
|
+
const warnings = [];
|
|
487
|
+
const schemaResult = AgentsTxtDocumentSchema.safeParse(doc);
|
|
488
|
+
if (!schemaResult.success) {
|
|
489
|
+
for (const issue of schemaResult.error.issues) {
|
|
490
|
+
errors.push({
|
|
491
|
+
path: issue.path.join("."),
|
|
492
|
+
message: issue.message,
|
|
493
|
+
code: "SCHEMA_VIOLATION"
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
const capIds = /* @__PURE__ */ new Set();
|
|
498
|
+
for (const cap of doc.capabilities) {
|
|
499
|
+
if (capIds.has(cap.id)) {
|
|
500
|
+
errors.push({
|
|
501
|
+
path: `capabilities.${cap.id}`,
|
|
502
|
+
message: `Duplicate capability ID: "${cap.id}"`,
|
|
503
|
+
code: "DUPLICATE_CAPABILITY"
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
capIds.add(cap.id);
|
|
507
|
+
}
|
|
508
|
+
for (const [agentName, policy] of Object.entries(doc.agents)) {
|
|
509
|
+
if (policy.capabilities) {
|
|
510
|
+
for (const capId of policy.capabilities) {
|
|
511
|
+
if (!capIds.has(capId)) {
|
|
512
|
+
errors.push({
|
|
513
|
+
path: `agents.${agentName}.capabilities`,
|
|
514
|
+
message: `Agent "${agentName}" references unknown capability: "${capId}"`,
|
|
515
|
+
code: "UNKNOWN_CAPABILITY_REF"
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if (doc.declarationType === "agent" && (!doc.operatesOn || doc.operatesOn.length === 0)) {
|
|
522
|
+
warnings.push({
|
|
523
|
+
path: "operatesOn",
|
|
524
|
+
message: "Agent declarations should include at least one Operates-On URL",
|
|
525
|
+
code: "MISSING_OPERATES_ON"
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
if (doc.operatesOn && doc.operatesOn.length > 0 && doc.declarationType !== "agent") {
|
|
529
|
+
warnings.push({
|
|
530
|
+
path: "declarationType",
|
|
531
|
+
message: 'Operates-On is set but Declaration-Type is not "agent"',
|
|
532
|
+
code: "OPERATES_ON_WITHOUT_AGENT_TYPE"
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
if (doc.site.url && !doc.site.url.startsWith("https://")) {
|
|
536
|
+
warnings.push({
|
|
537
|
+
path: "site.url",
|
|
538
|
+
message: "Site URL should use HTTPS",
|
|
539
|
+
code: "INSECURE_URL"
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
for (const cap of doc.capabilities) {
|
|
543
|
+
if (cap.endpoint && !cap.endpoint.startsWith("https://")) {
|
|
544
|
+
warnings.push({
|
|
545
|
+
path: `capabilities.${cap.id}.endpoint`,
|
|
546
|
+
message: `Capability "${cap.id}" endpoint should use HTTPS`,
|
|
547
|
+
code: "INSECURE_ENDPOINT"
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return {
|
|
552
|
+
valid: errors.length === 0,
|
|
553
|
+
errors,
|
|
554
|
+
warnings
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
function validateText(text) {
|
|
558
|
+
const parseResult = parse(text);
|
|
559
|
+
if (!parseResult.success || !parseResult.document) {
|
|
560
|
+
return {
|
|
561
|
+
valid: false,
|
|
562
|
+
errors: parseResult.errors.map((e) => ({
|
|
563
|
+
path: e.field ?? "",
|
|
564
|
+
message: e.message,
|
|
565
|
+
code: "PARSE_ERROR"
|
|
566
|
+
})),
|
|
567
|
+
warnings: []
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
return validate(parseResult.document);
|
|
571
|
+
}
|
|
572
|
+
function validateJSON(json) {
|
|
573
|
+
const parseResult = parseJSON(json);
|
|
574
|
+
if (!parseResult.success || !parseResult.document) {
|
|
575
|
+
return {
|
|
576
|
+
valid: false,
|
|
577
|
+
errors: parseResult.errors.map((e) => ({
|
|
578
|
+
path: e.field ?? "",
|
|
579
|
+
message: e.message,
|
|
580
|
+
code: "PARSE_ERROR"
|
|
581
|
+
})),
|
|
582
|
+
warnings: []
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
return validate(parseResult.document);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// src/client.ts
|
|
589
|
+
var WELL_KNOWN_TXT = "/.well-known/agents.txt";
|
|
590
|
+
var FALLBACK_TXT = "/agents.txt";
|
|
591
|
+
var WELL_KNOWN_JSON = "/.well-known/agents.json";
|
|
592
|
+
var FALLBACK_JSON = "/agents.json";
|
|
593
|
+
var AgentsTxtClient = class {
|
|
594
|
+
timeout;
|
|
595
|
+
userAgent;
|
|
596
|
+
constructor(options = {}) {
|
|
597
|
+
this.timeout = options.timeout ?? 1e4;
|
|
598
|
+
this.userAgent = options.userAgent ?? "agents-txt-client/0.1";
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Discover agents.txt from a site. Tries /.well-known/agents.txt first,
|
|
602
|
+
* falls back to /agents.txt.
|
|
603
|
+
*/
|
|
604
|
+
async discover(baseUrl) {
|
|
605
|
+
const normalized = baseUrl.replace(/\/+$/, "");
|
|
606
|
+
const primary = await this.fetchText(`${normalized}${WELL_KNOWN_TXT}`);
|
|
607
|
+
if (primary) return parse(primary);
|
|
608
|
+
const fallback = await this.fetchText(`${normalized}${FALLBACK_TXT}`);
|
|
609
|
+
if (fallback) return parse(fallback);
|
|
610
|
+
return {
|
|
611
|
+
success: false,
|
|
612
|
+
errors: [{ message: `No agents.txt found at ${normalized}` }],
|
|
613
|
+
warnings: []
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Discover agents.json from a site. Tries /.well-known/agents.json first,
|
|
618
|
+
* falls back to /agents.json.
|
|
619
|
+
*/
|
|
620
|
+
async discoverJSON(baseUrl) {
|
|
621
|
+
const normalized = baseUrl.replace(/\/+$/, "");
|
|
622
|
+
const primary = await this.fetchText(`${normalized}${WELL_KNOWN_JSON}`);
|
|
623
|
+
if (primary) return parseJSON(primary);
|
|
624
|
+
const fallback = await this.fetchText(`${normalized}${FALLBACK_JSON}`);
|
|
625
|
+
if (fallback) return parseJSON(fallback);
|
|
626
|
+
return {
|
|
627
|
+
success: false,
|
|
628
|
+
errors: [{ message: `No agents.json found at ${normalized}` }],
|
|
629
|
+
warnings: []
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
async fetchText(url) {
|
|
633
|
+
const controller = new AbortController();
|
|
634
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
635
|
+
try {
|
|
636
|
+
const response = await fetch(url, {
|
|
637
|
+
headers: { "User-Agent": this.userAgent },
|
|
638
|
+
signal: controller.signal
|
|
639
|
+
});
|
|
640
|
+
if (!response.ok) return null;
|
|
641
|
+
return await response.text();
|
|
642
|
+
} catch {
|
|
643
|
+
return null;
|
|
644
|
+
} finally {
|
|
645
|
+
clearTimeout(timer);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
export {
|
|
650
|
+
AgentsTxtClient,
|
|
651
|
+
AgentsTxtDocumentSchema,
|
|
652
|
+
AuthConfigSchema,
|
|
653
|
+
CapabilitySchema,
|
|
654
|
+
DeclarationTypeSchema,
|
|
655
|
+
ProtocolSchema,
|
|
656
|
+
RateLimitSchema,
|
|
657
|
+
SiteInfoSchema,
|
|
658
|
+
formatRateLimit,
|
|
659
|
+
generate,
|
|
660
|
+
generateJSON,
|
|
661
|
+
parse,
|
|
662
|
+
parseJSON,
|
|
663
|
+
parseRateLimit,
|
|
664
|
+
sanitizeValue,
|
|
665
|
+
validate,
|
|
666
|
+
validateJSON,
|
|
667
|
+
validateText
|
|
668
|
+
};
|