@agentcash/discovery 0.1.4 → 1.0.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/dist/cli.cjs +1032 -2170
- package/dist/cli.d.cts +2 -14
- package/dist/cli.d.ts +2 -14
- package/dist/cli.js +1032 -2170
- package/dist/index.cjs +1257 -2170
- package/dist/index.d.cts +263 -284
- package/dist/index.d.ts +263 -284
- package/dist/index.js +1244 -2162
- package/dist/schemas.cjs +78 -25
- package/dist/schemas.d.cts +584 -27
- package/dist/schemas.d.ts +584 -27
- package/dist/schemas.js +72 -19
- package/package.json +3 -3
package/dist/cli.cjs
CHANGED
|
@@ -24,625 +24,107 @@ __export(cli_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(cli_exports);
|
|
26
26
|
|
|
27
|
-
// src/
|
|
28
|
-
var
|
|
29
|
-
var STRICT_ESCALATION_CODES = [
|
|
30
|
-
"LEGACY_WELL_KNOWN_USED",
|
|
31
|
-
"LEGACY_DNS_USED",
|
|
32
|
-
"LEGACY_DNS_PLAIN_URL",
|
|
33
|
-
"LEGACY_MISSING_METHOD",
|
|
34
|
-
"LEGACY_INSTRUCTIONS_USED",
|
|
35
|
-
"LEGACY_OWNERSHIP_PROOFS_USED",
|
|
36
|
-
"INTEROP_MPP_USED"
|
|
37
|
-
];
|
|
27
|
+
// src/core/source/openapi/index.ts
|
|
28
|
+
var import_neverthrow2 = require("neverthrow");
|
|
38
29
|
|
|
39
|
-
// src/
|
|
40
|
-
var
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
function parseContextWindowTokens(value) {
|
|
61
|
-
const parsed = Number(value);
|
|
62
|
-
if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
return parsed;
|
|
66
|
-
}
|
|
67
|
-
function parseCliArgs(argv) {
|
|
68
|
-
let target;
|
|
69
|
-
let verbose = false;
|
|
70
|
-
let json = false;
|
|
71
|
-
let probe = false;
|
|
72
|
-
let timeoutMs = DEFAULT_TIMEOUT_MS;
|
|
73
|
-
let compatMode = DEFAULT_COMPAT_MODE;
|
|
74
|
-
let color = true;
|
|
75
|
-
let noTruncate = false;
|
|
76
|
-
let harness = false;
|
|
77
|
-
let client = "claude-code";
|
|
78
|
-
let contextWindowTokens;
|
|
79
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
80
|
-
const arg = argv[i];
|
|
81
|
-
if (arg === "--help" || arg === "-h") {
|
|
82
|
-
return { kind: "help" };
|
|
83
|
-
}
|
|
84
|
-
if (arg === "--verbose" || arg === "-v") {
|
|
85
|
-
verbose = true;
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
if (arg === "--json") {
|
|
89
|
-
json = true;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (arg === "--probe") {
|
|
93
|
-
probe = true;
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
if (arg === "--no-color") {
|
|
97
|
-
color = false;
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
if (arg === "--no-truncate") {
|
|
101
|
-
noTruncate = true;
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
if (arg === "--harness") {
|
|
105
|
-
harness = true;
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
if (arg === "--compat") {
|
|
109
|
-
const value = argv[i + 1];
|
|
110
|
-
if (!value) {
|
|
111
|
-
return { kind: "error", message: "Missing value for --compat." };
|
|
112
|
-
}
|
|
113
|
-
const parsed = parseCompatibilityMode(value);
|
|
114
|
-
if (!parsed) {
|
|
115
|
-
return {
|
|
116
|
-
kind: "error",
|
|
117
|
-
message: `Invalid --compat value '${value}'. Expected: on | off | strict.`
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
compatMode = parsed;
|
|
121
|
-
i += 1;
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
if (arg === "--timeout-ms") {
|
|
125
|
-
const value = argv[i + 1];
|
|
126
|
-
if (!value) {
|
|
127
|
-
return { kind: "error", message: "Missing value for --timeout-ms." };
|
|
128
|
-
}
|
|
129
|
-
const parsed = parseTimeoutMs(value);
|
|
130
|
-
if (!parsed) {
|
|
131
|
-
return {
|
|
132
|
-
kind: "error",
|
|
133
|
-
message: `Invalid --timeout-ms value '${value}'. Expected a positive integer.`
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
timeoutMs = parsed;
|
|
137
|
-
i += 1;
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
if (arg === "--client") {
|
|
141
|
-
const value = argv[i + 1];
|
|
142
|
-
if (!value) {
|
|
143
|
-
return { kind: "error", message: "Missing value for --client." };
|
|
144
|
-
}
|
|
145
|
-
const parsed = parseClient(value);
|
|
146
|
-
if (!parsed) {
|
|
147
|
-
return {
|
|
148
|
-
kind: "error",
|
|
149
|
-
message: "Invalid --client value '" + value + "'. Expected: claude-code | skill-cli | generic-mcp | generic."
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
client = parsed;
|
|
153
|
-
i += 1;
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
if (arg === "--context-window-tokens") {
|
|
157
|
-
const value = argv[i + 1];
|
|
158
|
-
if (!value) {
|
|
159
|
-
return { kind: "error", message: "Missing value for --context-window-tokens." };
|
|
160
|
-
}
|
|
161
|
-
const parsed = parseContextWindowTokens(value);
|
|
162
|
-
if (!parsed) {
|
|
163
|
-
return {
|
|
164
|
-
kind: "error",
|
|
165
|
-
message: `Invalid --context-window-tokens value '${value}'. Expected a positive integer.`
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
contextWindowTokens = parsed;
|
|
169
|
-
i += 1;
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
if (arg.startsWith("-")) {
|
|
173
|
-
return { kind: "error", message: `Unknown flag '${arg}'.` };
|
|
174
|
-
}
|
|
175
|
-
if (target) {
|
|
176
|
-
return {
|
|
177
|
-
kind: "error",
|
|
178
|
-
message: `Unexpected extra positional argument '${arg}'. Only one domain/URL is supported.`
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
target = arg;
|
|
182
|
-
}
|
|
183
|
-
if (!target) {
|
|
184
|
-
return { kind: "error", message: "Missing required <domain> argument." };
|
|
185
|
-
}
|
|
186
|
-
return {
|
|
187
|
-
kind: "ok",
|
|
188
|
-
options: {
|
|
189
|
-
target,
|
|
190
|
-
verbose,
|
|
191
|
-
json,
|
|
192
|
-
compatMode,
|
|
193
|
-
probe,
|
|
194
|
-
timeoutMs,
|
|
195
|
-
color,
|
|
196
|
-
noTruncate,
|
|
197
|
-
harness,
|
|
198
|
-
client,
|
|
199
|
-
...contextWindowTokens ? { contextWindowTokens } : {}
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
function renderHelp() {
|
|
204
|
-
return [
|
|
205
|
-
"Usage: npx @agentcash/discovery <domain-or-url> [options]",
|
|
206
|
-
"",
|
|
207
|
-
"Options:",
|
|
208
|
-
" -v, --verbose Show full compatibility matrix",
|
|
209
|
-
" --json Emit JSON output",
|
|
210
|
-
" --compat <mode> Compatibility mode: on | off | strict (default: on)",
|
|
211
|
-
" --probe Re-run with probe candidates from discovered paths",
|
|
212
|
-
" --harness Render L0-L5 context harness audit view",
|
|
213
|
-
" --client <id> Harness client: claude-code | skill-cli | generic-mcp | generic",
|
|
214
|
-
" --context-window-tokens <n>",
|
|
215
|
-
" Override client context window for harness budget checks",
|
|
216
|
-
" --timeout-ms <ms> Per-request timeout in milliseconds (default: 5000)",
|
|
217
|
-
" --no-color Disable ANSI colors",
|
|
218
|
-
" --no-truncate Disable output truncation in verbose tables",
|
|
219
|
-
" -h, --help Show help"
|
|
220
|
-
].join("\n");
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// src/cli/render.ts
|
|
224
|
-
var import_table = require("table");
|
|
225
|
-
function createPalette(enabled) {
|
|
226
|
-
const wrap = (code) => (value) => enabled ? `\x1B[${code}m${value}\x1B[0m` : value;
|
|
227
|
-
return {
|
|
228
|
-
dim: wrap(2),
|
|
229
|
-
bold: wrap(1),
|
|
230
|
-
red: wrap(31),
|
|
231
|
-
green: wrap(32),
|
|
232
|
-
yellow: wrap(33),
|
|
233
|
-
cyan: wrap(36)
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
function summarizeWarnings(warnings) {
|
|
237
|
-
const errors = warnings.filter((entry) => entry.severity === "error").length;
|
|
238
|
-
const warns = warnings.filter((entry) => entry.severity === "warn").length;
|
|
239
|
-
const infos = warnings.filter((entry) => entry.severity === "info").length;
|
|
240
|
-
const codeCounts = warnings.filter((entry) => entry.code !== "PROBE_STAGE_SKIPPED").reduce(
|
|
241
|
-
(acc, entry) => {
|
|
242
|
-
acc[entry.code] = (acc[entry.code] ?? 0) + 1;
|
|
243
|
-
return acc;
|
|
244
|
-
},
|
|
245
|
-
{}
|
|
246
|
-
);
|
|
247
|
-
const codes = Object.entries(codeCounts).sort((a, b) => b[1] - a[1]).map(([code, count]) => ({ code, count }));
|
|
248
|
-
return {
|
|
249
|
-
total: warnings.length,
|
|
250
|
-
errors,
|
|
251
|
-
warns,
|
|
252
|
-
infos,
|
|
253
|
-
codes
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
function asYesNo(value) {
|
|
257
|
-
return value ? "yes" : "no";
|
|
258
|
-
}
|
|
259
|
-
function truncate(value, max, noTruncate) {
|
|
260
|
-
if (noTruncate || value.length <= max) {
|
|
261
|
-
return value;
|
|
262
|
-
}
|
|
263
|
-
if (max <= 1) return value.slice(0, max);
|
|
264
|
-
return `${value.slice(0, Math.max(1, max - 1))}...`;
|
|
265
|
-
}
|
|
266
|
-
function formatPricing(resource) {
|
|
267
|
-
if (resource.authHint !== "paid") return "-";
|
|
268
|
-
if (resource.pricing) {
|
|
269
|
-
if (resource.pricing.pricingMode === "fixed") {
|
|
270
|
-
return resource.pricing.price ? `fixed ${resource.pricing.price}` : "fixed";
|
|
271
|
-
}
|
|
272
|
-
if (resource.pricing.pricingMode === "range") {
|
|
273
|
-
return `range ${resource.pricing.minPrice ?? "?"}-${resource.pricing.maxPrice ?? "?"}`;
|
|
274
|
-
}
|
|
275
|
-
return "quote";
|
|
276
|
-
}
|
|
277
|
-
return resource.priceHint ? `hint ${resource.priceHint}` : "-";
|
|
278
|
-
}
|
|
279
|
-
function statusBadge(ok, palette) {
|
|
280
|
-
return ok ? palette.green("[PASS]") : palette.red("[FAIL]");
|
|
281
|
-
}
|
|
282
|
-
function riskBadge(summary, palette) {
|
|
283
|
-
if (summary.errors > 0) return palette.red("HIGH");
|
|
284
|
-
if (summary.warns > 0) return palette.yellow("MEDIUM");
|
|
285
|
-
return palette.green("LOW");
|
|
286
|
-
}
|
|
287
|
-
function sectionHeader(title, palette) {
|
|
288
|
-
return `${palette.bold(title)}
|
|
289
|
-
${palette.dim("-".repeat(title.length))}`;
|
|
290
|
-
}
|
|
291
|
-
function introBlock(run, options, palette) {
|
|
292
|
-
const summary = summarizeWarnings(run.result.warnings);
|
|
293
|
-
const topCodes = summary.codes.slice(0, 3).map(({ code, count }) => `${code} (${count})`).join(", ");
|
|
294
|
-
const lines = [
|
|
295
|
-
`${palette.bold("AGENTCASH DISCOVERY AUDIT")} ${statusBadge(run.result.resources.length > 0, palette)}`,
|
|
296
|
-
`${palette.dim("=".repeat(74))}`,
|
|
297
|
-
`Target ${options.target}`,
|
|
298
|
-
`Origin ${run.result.origin}`,
|
|
299
|
-
`Stage ${run.result.selectedStage ?? "none"} Compat: ${run.result.compatMode}`,
|
|
300
|
-
`Resources ${run.result.resources.length} Duration: ${run.durationMs}ms`,
|
|
301
|
-
`Risk ${riskBadge(summary, palette)} Upgrade suggested: ${run.result.upgradeSuggested ? "yes" : "no"}`,
|
|
302
|
-
`Warnings error=${summary.errors} warn=${summary.warns} info=${summary.infos}`,
|
|
303
|
-
`Top signals ${topCodes || "-"}`
|
|
304
|
-
];
|
|
305
|
-
return lines.join("\n");
|
|
306
|
-
}
|
|
307
|
-
function outcomeLine(run) {
|
|
308
|
-
const selectedStage = run.result.selectedStage ?? "none";
|
|
309
|
-
if (run.result.resources.length === 0) {
|
|
310
|
-
return "No usable discovery metadata was found.";
|
|
311
|
-
}
|
|
312
|
-
if (selectedStage === "openapi" || selectedStage === "override") {
|
|
313
|
-
return `Canonical discovery succeeded via ${selectedStage}.`;
|
|
314
|
-
}
|
|
315
|
-
return `Discovery succeeded, but only via legacy stage ${selectedStage}.`;
|
|
316
|
-
}
|
|
317
|
-
function actionItems(run, options) {
|
|
318
|
-
const target = options.target;
|
|
319
|
-
if (run.result.resources.length === 0) {
|
|
320
|
-
return [
|
|
321
|
-
`Run deeper diagnostics: npx @agentcash/discovery ${target} -v --probe --compat on`,
|
|
322
|
-
`Check that ${target} serves /openapi.json or /.well-known/x402`,
|
|
323
|
-
"If endpoint exists, verify it returns parseable JSON and non-empty resources"
|
|
324
|
-
];
|
|
325
|
-
}
|
|
326
|
-
if (run.result.selectedStage === "openapi" || run.result.selectedStage === "override") {
|
|
327
|
-
return [
|
|
328
|
-
`Run strict mode hardening: npx @agentcash/discovery ${target} --compat strict -v`,
|
|
329
|
-
"Reduce warning count to near-zero and keep pricing/auth hints explicit"
|
|
330
|
-
];
|
|
331
|
-
}
|
|
332
|
-
return [
|
|
333
|
-
"Publish canonical OpenAPI discovery metadata with x-payment-info and security schemes",
|
|
334
|
-
`Validate migration path: npx @agentcash/discovery ${target} --compat strict -v --probe`,
|
|
335
|
-
"Keep legacy well-known path only during sunset window"
|
|
336
|
-
];
|
|
337
|
-
}
|
|
338
|
-
function buildTable(data, maxColWidths, noTruncate) {
|
|
339
|
-
const rendered = data.map(
|
|
340
|
-
(row) => row.map((cell, idx) => {
|
|
341
|
-
const width = maxColWidths[idx] ?? 120;
|
|
342
|
-
return noTruncate ? cell : truncate(cell, width, false);
|
|
30
|
+
// src/schemas.ts
|
|
31
|
+
var import_zod = require("zod");
|
|
32
|
+
var OpenApiPaymentInfoSchema = import_zod.z.object({
|
|
33
|
+
pricingMode: import_zod.z.enum(["fixed", "range", "quote"]),
|
|
34
|
+
price: import_zod.z.string().optional(),
|
|
35
|
+
minPrice: import_zod.z.string().optional(),
|
|
36
|
+
maxPrice: import_zod.z.string().optional(),
|
|
37
|
+
protocols: import_zod.z.array(import_zod.z.string()).optional()
|
|
38
|
+
});
|
|
39
|
+
var OpenApiOperationSchema = import_zod.z.object({
|
|
40
|
+
operationId: import_zod.z.string().optional(),
|
|
41
|
+
summary: import_zod.z.string().optional(),
|
|
42
|
+
description: import_zod.z.string().optional(),
|
|
43
|
+
tags: import_zod.z.array(import_zod.z.string()).optional(),
|
|
44
|
+
security: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.array(import_zod.z.string()))).optional(),
|
|
45
|
+
parameters: import_zod.z.array(
|
|
46
|
+
import_zod.z.object({
|
|
47
|
+
in: import_zod.z.string(),
|
|
48
|
+
name: import_zod.z.string(),
|
|
49
|
+
schema: import_zod.z.unknown().optional(),
|
|
50
|
+
required: import_zod.z.boolean().optional()
|
|
343
51
|
})
|
|
344
|
-
)
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
})
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
const header = ["severity", "code", "count", "hint/message"];
|
|
402
|
-
const rows = summary.codes.slice(0, 8).map(({ code, count }) => {
|
|
403
|
-
const sample = hintByCode.get(code) ?? "-";
|
|
404
|
-
const severity = code.includes("NO_DISCOVERY") ? "error" : code.includes("LEGACY") ? "warn" : "info";
|
|
405
|
-
return [severity, code, String(count), sample];
|
|
406
|
-
});
|
|
407
|
-
return [header, ...rows];
|
|
408
|
-
}
|
|
409
|
-
function harnessBudgetTable(report) {
|
|
410
|
-
return [
|
|
411
|
-
["metric", "value"],
|
|
412
|
-
["client", `${report.client.id} (${report.client.surface})`],
|
|
413
|
-
["context window tokens", String(report.budget.contextWindowTokens)],
|
|
414
|
-
["zero-hop budget tokens", String(report.budget.zeroHopBudgetTokens)],
|
|
415
|
-
["estimated L0+L1 tokens", String(report.budget.estimatedZeroHopTokens)],
|
|
416
|
-
["within budget", report.budget.withinBudget ? "yes" : "no"]
|
|
417
|
-
];
|
|
418
|
-
}
|
|
419
|
-
function harnessLayerSummaryRows(report) {
|
|
420
|
-
return [
|
|
421
|
-
["layer", "what", "primary command", "status"],
|
|
422
|
-
[
|
|
423
|
-
"L0",
|
|
424
|
-
"trigger layer",
|
|
425
|
-
report.levels.l0.installCommand,
|
|
426
|
-
`${report.levels.l0.intentTriggers.length} triggers`
|
|
427
|
-
],
|
|
428
|
-
[
|
|
429
|
-
"L1",
|
|
430
|
-
"installed domain index",
|
|
431
|
-
report.levels.l1.fanoutCommands[0] ?? "-",
|
|
432
|
-
`${report.levels.l1.domainClass}; stage=${report.levels.l1.selectedDiscoveryStage ?? "none"}`
|
|
433
|
-
],
|
|
434
|
-
[
|
|
435
|
-
"L2",
|
|
436
|
-
"domain resources",
|
|
437
|
-
report.levels.l2.command,
|
|
438
|
-
`${report.levels.l2.resourceCount} resources`
|
|
439
|
-
],
|
|
440
|
-
[
|
|
441
|
-
"L3",
|
|
442
|
-
"resource details",
|
|
443
|
-
report.levels.l3.command,
|
|
444
|
-
`${report.levels.l3.pricedResourceCount} priced`
|
|
445
|
-
],
|
|
446
|
-
["L4", "domain guidance", report.levels.l4.llmsTxtUrl ?? "-", report.levels.l4.guidanceStatus],
|
|
447
|
-
["L5", "cross-domain composition", "-", report.levels.l5.status]
|
|
448
|
-
];
|
|
449
|
-
}
|
|
450
|
-
function harnessL2PreviewRows(report, noTruncate) {
|
|
451
|
-
const header = ["method", "path", "auth", "source"];
|
|
452
|
-
const rows = report.levels.l2.resources.slice(0, 15).map((resource) => [
|
|
453
|
-
resource.method,
|
|
454
|
-
truncate(resource.path, 54, noTruncate),
|
|
455
|
-
resource.authMode ?? "-",
|
|
456
|
-
resource.source
|
|
457
|
-
]);
|
|
458
|
-
return [header, ...rows];
|
|
459
|
-
}
|
|
460
|
-
function renderHarnessSummary(report, options, palette) {
|
|
461
|
-
const lines = [];
|
|
462
|
-
lines.push(
|
|
463
|
-
`${palette.bold("L0-L5 Context Harness")} ${statusBadge(report.levels.l2.resourceCount > 0, palette)}`
|
|
464
|
-
);
|
|
465
|
-
lines.push(`${palette.dim("=".repeat(74))}`);
|
|
466
|
-
lines.push(`Client ${report.client.label}`);
|
|
467
|
-
lines.push(`Domain ${report.levels.l1.domain}`);
|
|
468
|
-
lines.push(`L2 resources ${report.levels.l2.resourceCount}`);
|
|
469
|
-
lines.push(
|
|
470
|
-
`Budget ${report.budget.estimatedZeroHopTokens}/${report.budget.zeroHopBudgetTokens} tokens (L0+L1)`
|
|
471
|
-
);
|
|
472
|
-
lines.push("");
|
|
473
|
-
lines.push("Layer map:");
|
|
474
|
-
lines.push("1. L0 trigger surface -> install and route to agentcash");
|
|
475
|
-
lines.push(`2. L1 domain index -> ${report.levels.l1.fanoutCommands[0]}`);
|
|
476
|
-
lines.push(`3. L2 resources -> ${report.levels.l2.command}`);
|
|
477
|
-
lines.push(`4. L3 details -> ${report.levels.l3.command}`);
|
|
478
|
-
lines.push(
|
|
479
|
-
`5. L4 guidance -> ${report.levels.l4.guidanceStatus}${report.levels.l4.llmsTxtUrl ? ` (${report.levels.l4.llmsTxtUrl})` : ""}`
|
|
480
|
-
);
|
|
481
|
-
lines.push("6. L5 cross-domain -> out of scope in v1");
|
|
482
|
-
lines.push("");
|
|
483
|
-
lines.push(
|
|
484
|
-
`Next: npx @agentcash/discovery ${options.target} --harness -v --client ${report.client.id}`
|
|
485
|
-
);
|
|
486
|
-
return lines.join("\n");
|
|
52
|
+
).optional(),
|
|
53
|
+
requestBody: import_zod.z.object({
|
|
54
|
+
required: import_zod.z.boolean().optional(),
|
|
55
|
+
content: import_zod.z.record(import_zod.z.string(), import_zod.z.object({ schema: import_zod.z.unknown().optional() }))
|
|
56
|
+
}).optional(),
|
|
57
|
+
responses: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
|
|
58
|
+
"x-payment-info": OpenApiPaymentInfoSchema.optional()
|
|
59
|
+
});
|
|
60
|
+
var OpenApiPathItemSchema = import_zod.z.object({
|
|
61
|
+
get: OpenApiOperationSchema.optional(),
|
|
62
|
+
post: OpenApiOperationSchema.optional(),
|
|
63
|
+
put: OpenApiOperationSchema.optional(),
|
|
64
|
+
delete: OpenApiOperationSchema.optional(),
|
|
65
|
+
patch: OpenApiOperationSchema.optional(),
|
|
66
|
+
head: OpenApiOperationSchema.optional(),
|
|
67
|
+
options: OpenApiOperationSchema.optional(),
|
|
68
|
+
trace: OpenApiOperationSchema.optional()
|
|
69
|
+
});
|
|
70
|
+
var OpenApiDocSchema = import_zod.z.object({
|
|
71
|
+
// TODO(zdql): We should inherit a canonical OpenAPI schema and then extend with our types.
|
|
72
|
+
openapi: import_zod.z.string(),
|
|
73
|
+
info: import_zod.z.object({
|
|
74
|
+
title: import_zod.z.string(),
|
|
75
|
+
version: import_zod.z.string(),
|
|
76
|
+
description: import_zod.z.string().optional(),
|
|
77
|
+
guidance: import_zod.z.string().optional()
|
|
78
|
+
}),
|
|
79
|
+
servers: import_zod.z.array(import_zod.z.object({ url: import_zod.z.string() })).optional(),
|
|
80
|
+
tags: import_zod.z.array(import_zod.z.object({ name: import_zod.z.string() })).optional(),
|
|
81
|
+
components: import_zod.z.object({ securitySchemes: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional() }).optional(),
|
|
82
|
+
"x-discovery": import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
|
|
83
|
+
paths: import_zod.z.record(import_zod.z.string(), OpenApiPathItemSchema)
|
|
84
|
+
});
|
|
85
|
+
var WellKnownDocSchema = import_zod.z.object({
|
|
86
|
+
version: import_zod.z.number().optional(),
|
|
87
|
+
resources: import_zod.z.array(import_zod.z.string()).default([]),
|
|
88
|
+
mppResources: import_zod.z.array(import_zod.z.string()).optional(),
|
|
89
|
+
// isMmmEnabled
|
|
90
|
+
description: import_zod.z.string().optional(),
|
|
91
|
+
ownershipProofs: import_zod.z.array(import_zod.z.string()).optional(),
|
|
92
|
+
instructions: import_zod.z.string().optional()
|
|
93
|
+
});
|
|
94
|
+
var WellKnownParsedSchema = import_zod.z.object({
|
|
95
|
+
routes: import_zod.z.array(
|
|
96
|
+
import_zod.z.object({
|
|
97
|
+
path: import_zod.z.string(),
|
|
98
|
+
method: import_zod.z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "TRACE"])
|
|
99
|
+
})
|
|
100
|
+
),
|
|
101
|
+
instructions: import_zod.z.string().optional()
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// src/core/source/openapi/utils.ts
|
|
105
|
+
function isRecord(value) {
|
|
106
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
487
107
|
}
|
|
488
|
-
function
|
|
489
|
-
|
|
490
|
-
lines.push(
|
|
491
|
-
`${palette.bold("L0-L5 Context Harness (Verbose)")} ${statusBadge(report.levels.l2.resourceCount > 0, palette)}`
|
|
492
|
-
);
|
|
493
|
-
lines.push(`${palette.dim("=".repeat(74))}`);
|
|
494
|
-
lines.push(`Target ${report.target}`);
|
|
495
|
-
lines.push(`Origin ${report.origin}`);
|
|
496
|
-
lines.push(`Client ${report.client.label}`);
|
|
497
|
-
lines.push("");
|
|
498
|
-
lines.push(sectionHeader("Budget check", palette));
|
|
499
|
-
lines.push(buildTable(harnessBudgetTable(report), [30, 42], options.noTruncate));
|
|
500
|
-
lines.push("");
|
|
501
|
-
lines.push(sectionHeader("Layer summary", palette));
|
|
502
|
-
lines.push(buildTable(harnessLayerSummaryRows(report), [8, 24, 44, 28], options.noTruncate));
|
|
503
|
-
lines.push("");
|
|
504
|
-
lines.push(sectionHeader("L2 resource preview", palette));
|
|
505
|
-
if (report.levels.l2.resources.length === 0) {
|
|
506
|
-
lines.push(palette.dim("(no discovered resources)"));
|
|
507
|
-
} else {
|
|
508
|
-
lines.push(
|
|
509
|
-
buildTable(
|
|
510
|
-
harnessL2PreviewRows(report, options.noTruncate),
|
|
511
|
-
[10, 54, 12, 20],
|
|
512
|
-
options.noTruncate
|
|
513
|
-
)
|
|
514
|
-
);
|
|
515
|
-
if (report.levels.l2.resources.length > 15) {
|
|
516
|
-
lines.push(
|
|
517
|
-
palette.dim(
|
|
518
|
-
`Showing 15/${report.levels.l2.resources.length} resources. Use --json to inspect full L2/L3 payload.`
|
|
519
|
-
)
|
|
520
|
-
);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
lines.push("");
|
|
524
|
-
lines.push(sectionHeader("L4 guidance", palette));
|
|
525
|
-
lines.push(`status: ${report.levels.l4.guidanceStatus}`);
|
|
526
|
-
lines.push(`llms.txt url: ${report.levels.l4.llmsTxtUrl ?? "-"}`);
|
|
527
|
-
lines.push(`llms.txt tokens: ${report.levels.l4.llmsTxtTokenEstimate}`);
|
|
528
|
-
lines.push(`preview: ${report.levels.l4.guidancePreview ?? "-"}`);
|
|
529
|
-
lines.push("");
|
|
530
|
-
lines.push(sectionHeader("Diagnostics", palette));
|
|
531
|
-
lines.push(
|
|
532
|
-
`warnings: ${report.diagnostics.warningCount} (errors=${report.diagnostics.errorWarningCount})`
|
|
533
|
-
);
|
|
534
|
-
lines.push(`selected stage: ${report.diagnostics.selectedStage ?? "none"}`);
|
|
535
|
-
lines.push(`upgrade suggested: ${report.diagnostics.upgradeSuggested ? "yes" : "no"}`);
|
|
536
|
-
lines.push(
|
|
537
|
-
`upgrade reasons: ${report.diagnostics.upgradeReasons.length > 0 ? report.diagnostics.upgradeReasons.join(", ") : "-"}`
|
|
538
|
-
);
|
|
539
|
-
return lines.join("\n");
|
|
108
|
+
function hasSecurity(operation, scheme) {
|
|
109
|
+
return operation.security?.some((s) => scheme in s) ?? false;
|
|
540
110
|
}
|
|
541
|
-
function
|
|
542
|
-
|
|
543
|
-
if (options.harness && run.harness) {
|
|
544
|
-
return renderHarnessSummary(run.harness, options, palette);
|
|
545
|
-
}
|
|
546
|
-
const body = [];
|
|
547
|
-
body.push(introBlock(run, options, palette));
|
|
548
|
-
body.push("");
|
|
549
|
-
body.push(`Outcome: ${outcomeLine(run)}`);
|
|
550
|
-
const items = actionItems(run, options);
|
|
551
|
-
body.push("Next:");
|
|
552
|
-
for (let i = 0; i < items.length; i += 1) {
|
|
553
|
-
body.push(`${i + 1}. ${items[i]}`);
|
|
554
|
-
}
|
|
555
|
-
if (options.probe) {
|
|
556
|
-
body.push(`Probe candidates considered: ${run.probeCandidateCount}`);
|
|
557
|
-
}
|
|
558
|
-
return body.join("\n");
|
|
111
|
+
function has402Response(operation) {
|
|
112
|
+
return Boolean(operation.responses?.["402"]);
|
|
559
113
|
}
|
|
560
|
-
function
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
body.push(outcomeLine(run));
|
|
572
|
-
body.push("");
|
|
573
|
-
body.push(sectionHeader("Recommended actions", palette));
|
|
574
|
-
const items = actionItems(run, options);
|
|
575
|
-
for (let i = 0; i < items.length; i += 1) {
|
|
576
|
-
body.push(`${i + 1}. ${items[i]}`);
|
|
577
|
-
}
|
|
578
|
-
body.push("");
|
|
579
|
-
body.push(sectionHeader("Compatibility matrix", palette));
|
|
580
|
-
body.push(buildTable(stageMatrixRows(run.result), [18, 8, 10, 10, 10, 56], noTruncate));
|
|
581
|
-
body.push("");
|
|
582
|
-
body.push(sectionHeader("Resource matrix", palette));
|
|
583
|
-
if (run.result.resources.length === 0) {
|
|
584
|
-
body.push(palette.dim("(no discovered resources)"));
|
|
585
|
-
} else {
|
|
586
|
-
const allRows = resourceMatrixRows(run.result);
|
|
587
|
-
const previewRows = allRows.slice(0, 16);
|
|
588
|
-
body.push(buildTable(previewRows, [8, 44, 10, 24, 18, 10, 10], noTruncate));
|
|
589
|
-
if (allRows.length > previewRows.length) {
|
|
590
|
-
body.push(
|
|
591
|
-
palette.dim(
|
|
592
|
-
`Showing ${previewRows.length - 1}/${allRows.length - 1} resources. Use --no-truncate or --json for complete output.`
|
|
593
|
-
)
|
|
594
|
-
);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
body.push("");
|
|
598
|
-
body.push(sectionHeader("Warning breakdown", palette));
|
|
599
|
-
if (summary.codes.length === 0) {
|
|
600
|
-
body.push(palette.dim("(no warnings)"));
|
|
601
|
-
} else {
|
|
602
|
-
body.push(buildTable(warningRows(summary, run.result), [10, 34, 8, 62], noTruncate));
|
|
603
|
-
}
|
|
604
|
-
if (run.result.upgradeReasons.length > 0) {
|
|
605
|
-
body.push("");
|
|
606
|
-
body.push(sectionHeader("Upgrade guidance", palette));
|
|
607
|
-
for (const reason of run.result.upgradeReasons) {
|
|
608
|
-
body.push(`- ${reason}`);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
return body.join("\n");
|
|
612
|
-
}
|
|
613
|
-
function renderJson(run, options) {
|
|
614
|
-
return JSON.stringify(
|
|
615
|
-
{
|
|
616
|
-
ok: run.result.resources.length > 0,
|
|
617
|
-
target: options.target,
|
|
618
|
-
origin: run.result.origin,
|
|
619
|
-
compatMode: run.result.compatMode,
|
|
620
|
-
selectedStage: run.result.selectedStage ?? null,
|
|
621
|
-
resources: run.result.resources,
|
|
622
|
-
trace: run.result.trace,
|
|
623
|
-
warnings: run.result.warnings,
|
|
624
|
-
upgradeSuggested: run.result.upgradeSuggested,
|
|
625
|
-
upgradeReasons: run.result.upgradeReasons,
|
|
626
|
-
meta: {
|
|
627
|
-
durationMs: run.durationMs,
|
|
628
|
-
probeEnabled: options.probe,
|
|
629
|
-
probeCandidateCount: run.probeCandidateCount,
|
|
630
|
-
timeoutMs: options.timeoutMs
|
|
631
|
-
},
|
|
632
|
-
...run.harness ? { harness: run.harness } : {}
|
|
633
|
-
},
|
|
634
|
-
null,
|
|
635
|
-
2
|
|
636
|
-
);
|
|
114
|
+
function inferAuthMode(operation) {
|
|
115
|
+
const hasXPaymentInfo = Boolean(operation["x-payment-info"]);
|
|
116
|
+
const hasPayment = hasXPaymentInfo || has402Response(operation);
|
|
117
|
+
const hasApiKey = hasSecurity(operation, "apiKey");
|
|
118
|
+
const hasSiwx = hasSecurity(operation, "siwx");
|
|
119
|
+
if (hasPayment && hasApiKey) return "apiKey+paid";
|
|
120
|
+
if (hasXPaymentInfo) return "paid";
|
|
121
|
+
if (hasPayment) return hasSiwx ? "siwx" : "paid";
|
|
122
|
+
if (hasApiKey) return "apiKey";
|
|
123
|
+
if (hasSiwx) return "siwx";
|
|
124
|
+
return void 0;
|
|
637
125
|
}
|
|
638
126
|
|
|
639
|
-
// src/
|
|
640
|
-
var isMmmEnabled = () => "0.1.4".includes("-mmm");
|
|
641
|
-
|
|
642
|
-
// src/core/constants.ts
|
|
643
|
-
var OPENAPI_PATH_CANDIDATES = ["/openapi.json", "/.well-known/openapi.json"];
|
|
644
|
-
var WELL_KNOWN_MPP_PATH = "/.well-known/mpp";
|
|
645
|
-
var LLMS_TOKEN_WARNING_THRESHOLD = 2500;
|
|
127
|
+
// src/core/lib/constants.ts
|
|
646
128
|
var HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
647
129
|
"GET",
|
|
648
130
|
"POST",
|
|
@@ -653,10 +135,9 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
|
653
135
|
"OPTIONS",
|
|
654
136
|
"TRACE"
|
|
655
137
|
]);
|
|
656
|
-
var DEFAULT_PROBE_METHODS = ["GET", "POST"];
|
|
657
138
|
var DEFAULT_MISSING_METHOD = "POST";
|
|
658
139
|
|
|
659
|
-
// src/core/url.ts
|
|
140
|
+
// src/core/lib/url.ts
|
|
660
141
|
function normalizeOrigin(target) {
|
|
661
142
|
const trimmed = target.trim();
|
|
662
143
|
const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
@@ -674,9 +155,6 @@ function normalizePath(pathname) {
|
|
|
674
155
|
const normalized = prefixed.replace(/\/+/g, "/");
|
|
675
156
|
return normalized !== "/" ? normalized.replace(/\/$/, "") : "/";
|
|
676
157
|
}
|
|
677
|
-
function toResourceKey(origin, method, path) {
|
|
678
|
-
return `${origin} ${method} ${normalizePath(path)}`;
|
|
679
|
-
}
|
|
680
158
|
function parseMethod(value) {
|
|
681
159
|
if (!value) return void 0;
|
|
682
160
|
const upper = value.toUpperCase();
|
|
@@ -691,901 +169,504 @@ function toAbsoluteUrl(origin, value) {
|
|
|
691
169
|
}
|
|
692
170
|
}
|
|
693
171
|
|
|
694
|
-
// src/core/
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
message,
|
|
700
|
-
...options?.hint ? { hint: options.hint } : {},
|
|
701
|
-
...options?.stage ? { stage: options.stage } : {},
|
|
702
|
-
...options?.resourceKey ? { resourceKey: options.resourceKey } : {}
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
function applyStrictEscalation(warnings, strict) {
|
|
706
|
-
if (!strict) return warnings;
|
|
707
|
-
return warnings.map((entry) => {
|
|
708
|
-
if (!STRICT_ESCALATION_CODES.includes(entry.code)) {
|
|
709
|
-
return entry;
|
|
710
|
-
}
|
|
711
|
-
if (entry.severity === "error") return entry;
|
|
712
|
-
return {
|
|
713
|
-
...entry,
|
|
714
|
-
severity: "error",
|
|
715
|
-
message: `${entry.message} (strict mode escalated)`
|
|
716
|
-
};
|
|
717
|
-
});
|
|
172
|
+
// src/core/source/fetch.ts
|
|
173
|
+
var import_neverthrow = require("neverthrow");
|
|
174
|
+
function toFetchError(err) {
|
|
175
|
+
const cause = err instanceof DOMException && (err.name === "TimeoutError" || err.name === "AbortError") ? "timeout" : "network";
|
|
176
|
+
return { cause, message: String(err) };
|
|
718
177
|
}
|
|
719
|
-
function
|
|
720
|
-
|
|
721
|
-
const output = [];
|
|
722
|
-
for (const item of warnings) {
|
|
723
|
-
const key = `${item.code}|${item.severity}|${item.stage ?? ""}|${item.resourceKey ?? ""}|${item.message}`;
|
|
724
|
-
if (seen.has(key)) continue;
|
|
725
|
-
seen.add(key);
|
|
726
|
-
output.push(item);
|
|
727
|
-
}
|
|
728
|
-
return output;
|
|
178
|
+
function fetchSafe(url, init) {
|
|
179
|
+
return import_neverthrow.ResultAsync.fromPromise(fetch(url, init), toFetchError);
|
|
729
180
|
}
|
|
730
181
|
|
|
731
|
-
// src/
|
|
732
|
-
|
|
733
|
-
const normalizedOrigin = normalizeOrigin(input.origin);
|
|
734
|
-
const normalizedPath = normalizePath(input.path);
|
|
735
|
-
return {
|
|
736
|
-
resourceKey: toResourceKey(normalizedOrigin, input.method, normalizedPath),
|
|
737
|
-
origin: normalizedOrigin,
|
|
738
|
-
method: input.method,
|
|
739
|
-
path: normalizedPath,
|
|
740
|
-
source: input.source,
|
|
741
|
-
verified: false,
|
|
742
|
-
...input.protocolHints?.length ? { protocolHints: [...new Set(input.protocolHints)] } : {},
|
|
743
|
-
...input.priceHint ? { priceHint: input.priceHint } : {},
|
|
744
|
-
...input.pricing ? { pricing: input.pricing } : {},
|
|
745
|
-
...input.authHint ? { authHint: input.authHint } : {},
|
|
746
|
-
...input.summary ? { summary: input.summary } : {},
|
|
747
|
-
confidence,
|
|
748
|
-
trustTier,
|
|
749
|
-
...input.links ? { links: input.links } : {}
|
|
750
|
-
};
|
|
751
|
-
}
|
|
182
|
+
// src/mmm-enabled.ts
|
|
183
|
+
var isMmmEnabled = () => "1.0.0".includes("-mmm");
|
|
752
184
|
|
|
753
|
-
// src/
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
return { method: maybeMethod, target };
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
if (trimmed.startsWith("/") || /^https?:\/\//i.test(trimmed)) {
|
|
769
|
-
return { target: trimmed };
|
|
770
|
-
}
|
|
771
|
-
return null;
|
|
772
|
-
}
|
|
773
|
-
function parseWellKnownPayload(payload, origin, sourceUrl) {
|
|
774
|
-
const warnings = [
|
|
775
|
-
warning(
|
|
776
|
-
"LEGACY_WELL_KNOWN_USED",
|
|
777
|
-
"warn",
|
|
778
|
-
"Using legacy /.well-known/x402 compatibility path. Migrate to OpenAPI-first.",
|
|
779
|
-
{
|
|
780
|
-
stage: "well-known/x402"
|
|
781
|
-
}
|
|
782
|
-
)
|
|
783
|
-
];
|
|
784
|
-
if (!isRecord(payload)) {
|
|
785
|
-
warnings.push(
|
|
786
|
-
warning("PARSE_FAILED", "error", "Legacy well-known payload is not an object", {
|
|
787
|
-
stage: "well-known/x402"
|
|
788
|
-
})
|
|
789
|
-
);
|
|
790
|
-
return { resources: [], warnings, raw: payload };
|
|
791
|
-
}
|
|
792
|
-
const resourcesRaw = Array.isArray(payload.resources) ? payload.resources.filter((entry) => typeof entry === "string") : [];
|
|
793
|
-
if (resourcesRaw.length === 0) {
|
|
794
|
-
warnings.push(
|
|
795
|
-
warning("STAGE_EMPTY", "warn", "Legacy well-known has no valid resources array", {
|
|
796
|
-
stage: "well-known/x402"
|
|
797
|
-
})
|
|
798
|
-
);
|
|
799
|
-
}
|
|
800
|
-
const instructions = typeof payload.instructions === "string" ? payload.instructions : void 0;
|
|
801
|
-
const ownershipProofs = Array.isArray(payload.ownershipProofs) ? payload.ownershipProofs.filter((entry) => typeof entry === "string") : [];
|
|
802
|
-
if (instructions) {
|
|
803
|
-
warnings.push(
|
|
804
|
-
warning(
|
|
805
|
-
"LEGACY_INSTRUCTIONS_USED",
|
|
806
|
-
"warn",
|
|
807
|
-
"Using /.well-known/x402.instructions as compatibility guidance fallback. Prefer llms.txt.",
|
|
808
|
-
{
|
|
809
|
-
stage: "well-known/x402",
|
|
810
|
-
hint: "Move guidance to llms.txt and reference via x-discovery.llmsTxtUrl in OpenAPI."
|
|
811
|
-
}
|
|
812
|
-
)
|
|
813
|
-
);
|
|
814
|
-
}
|
|
815
|
-
if (ownershipProofs.length > 0) {
|
|
816
|
-
warnings.push(
|
|
817
|
-
warning(
|
|
818
|
-
"LEGACY_OWNERSHIP_PROOFS_USED",
|
|
819
|
-
"warn",
|
|
820
|
-
"Using /.well-known/x402.ownershipProofs compatibility field. Prefer OpenAPI provenance extension.",
|
|
821
|
-
{
|
|
822
|
-
stage: "well-known/x402",
|
|
823
|
-
hint: "Move ownership proofs to x-discovery.ownershipProofs in OpenAPI."
|
|
824
|
-
}
|
|
825
|
-
)
|
|
826
|
-
);
|
|
827
|
-
}
|
|
828
|
-
const resources = [];
|
|
829
|
-
for (const rawEntry of resourcesRaw) {
|
|
830
|
-
const parsed = parseLegacyResourceEntry(rawEntry);
|
|
831
|
-
if (!parsed) {
|
|
832
|
-
warnings.push(
|
|
833
|
-
warning("PARSE_FAILED", "warn", `Invalid legacy resource entry: ${rawEntry}`, {
|
|
834
|
-
stage: "well-known/x402"
|
|
835
|
-
})
|
|
836
|
-
);
|
|
837
|
-
continue;
|
|
838
|
-
}
|
|
839
|
-
const absolute = toAbsoluteUrl(origin, parsed.target);
|
|
840
|
-
if (!absolute) {
|
|
841
|
-
warnings.push(
|
|
842
|
-
warning("PARSE_FAILED", "warn", `Invalid legacy resource URL: ${rawEntry}`, {
|
|
843
|
-
stage: "well-known/x402"
|
|
844
|
-
})
|
|
845
|
-
);
|
|
846
|
-
continue;
|
|
847
|
-
}
|
|
848
|
-
const method = parsed.method ?? DEFAULT_MISSING_METHOD;
|
|
849
|
-
if (!parsed.method) {
|
|
850
|
-
warnings.push(
|
|
851
|
-
warning(
|
|
852
|
-
"LEGACY_MISSING_METHOD",
|
|
853
|
-
"warn",
|
|
854
|
-
`Legacy resource '${rawEntry}' missing method. Defaulting to ${DEFAULT_MISSING_METHOD}.`,
|
|
855
|
-
{
|
|
856
|
-
stage: "well-known/x402"
|
|
857
|
-
}
|
|
858
|
-
)
|
|
185
|
+
// src/core/source/openapi/index.ts
|
|
186
|
+
var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
|
|
187
|
+
const routes = [];
|
|
188
|
+
for (const [rawPath, pathItem] of Object.entries(doc.paths)) {
|
|
189
|
+
for (const httpMethod of [...HTTP_METHODS]) {
|
|
190
|
+
const operation = pathItem[httpMethod.toLowerCase()];
|
|
191
|
+
if (!operation) continue;
|
|
192
|
+
const authMode = inferAuthMode(operation) ?? void 0;
|
|
193
|
+
if (!authMode) continue;
|
|
194
|
+
const p = operation["x-payment-info"];
|
|
195
|
+
const protocols = (p?.protocols ?? []).filter(
|
|
196
|
+
(proto) => proto !== "mpp" || isMmmEnabled()
|
|
859
197
|
);
|
|
198
|
+
if ((authMode === "paid" || authMode === "siwx") && !has402Response(operation)) continue;
|
|
199
|
+
if (authMode === "paid" && protocols.length === 0) continue;
|
|
200
|
+
const pricing = authMode === "paid" && p ? {
|
|
201
|
+
pricingMode: p.pricingMode,
|
|
202
|
+
...p.price ? { price: p.price } : {},
|
|
203
|
+
...p.minPrice ? { minPrice: p.minPrice } : {},
|
|
204
|
+
...p.maxPrice ? { maxPrice: p.maxPrice } : {}
|
|
205
|
+
} : void 0;
|
|
206
|
+
const summary = operation.summary ?? operation.description;
|
|
207
|
+
routes.push({
|
|
208
|
+
path: normalizePath(rawPath),
|
|
209
|
+
method: httpMethod.toUpperCase(),
|
|
210
|
+
...summary ? { summary } : {},
|
|
211
|
+
authMode,
|
|
212
|
+
...protocols.length ? { protocols } : {},
|
|
213
|
+
...pricing ? { pricing } : {}
|
|
214
|
+
});
|
|
860
215
|
}
|
|
861
|
-
const path = normalizePath(absolute.pathname);
|
|
862
|
-
resources.push(
|
|
863
|
-
createResource(
|
|
864
|
-
{
|
|
865
|
-
origin: absolute.origin,
|
|
866
|
-
method,
|
|
867
|
-
path,
|
|
868
|
-
source: "well-known/x402",
|
|
869
|
-
summary: `${method} ${path}`,
|
|
870
|
-
authHint: "paid",
|
|
871
|
-
protocolHints: ["x402"],
|
|
872
|
-
links: {
|
|
873
|
-
wellKnownUrl: sourceUrl,
|
|
874
|
-
discoveryUrl: sourceUrl
|
|
875
|
-
}
|
|
876
|
-
},
|
|
877
|
-
0.65,
|
|
878
|
-
ownershipProofs.length > 0 ? "ownership_verified" : "origin_hosted"
|
|
879
|
-
)
|
|
880
|
-
);
|
|
881
216
|
}
|
|
882
217
|
return {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
218
|
+
info: {
|
|
219
|
+
title: doc.info.title,
|
|
220
|
+
...doc.info.description ? { description: doc.info.description } : {},
|
|
221
|
+
version: doc.info.version
|
|
222
|
+
},
|
|
223
|
+
routes,
|
|
224
|
+
...doc.info.guidance ? { guidance: doc.info.guidance } : {}
|
|
886
225
|
};
|
|
887
|
-
}
|
|
888
|
-
async function
|
|
889
|
-
const stageUrl = options.url ?? `${options.origin}/.well-known/x402`;
|
|
226
|
+
});
|
|
227
|
+
async function parseBody(response, url) {
|
|
890
228
|
try {
|
|
891
|
-
const response = await options.fetcher(stageUrl, {
|
|
892
|
-
method: "GET",
|
|
893
|
-
headers: { Accept: "application/json", ...options.headers },
|
|
894
|
-
signal: options.signal
|
|
895
|
-
});
|
|
896
|
-
if (!response.ok) {
|
|
897
|
-
if (response.status === 404) {
|
|
898
|
-
return {
|
|
899
|
-
stage: "well-known/x402",
|
|
900
|
-
valid: false,
|
|
901
|
-
resources: [],
|
|
902
|
-
warnings: [],
|
|
903
|
-
links: { wellKnownUrl: stageUrl }
|
|
904
|
-
};
|
|
905
|
-
}
|
|
906
|
-
return {
|
|
907
|
-
stage: "well-known/x402",
|
|
908
|
-
valid: false,
|
|
909
|
-
resources: [],
|
|
910
|
-
warnings: [
|
|
911
|
-
warning("FETCH_FAILED", "warn", `Legacy well-known fetch failed (${response.status})`, {
|
|
912
|
-
stage: "well-known/x402"
|
|
913
|
-
})
|
|
914
|
-
],
|
|
915
|
-
links: { wellKnownUrl: stageUrl }
|
|
916
|
-
};
|
|
917
|
-
}
|
|
918
229
|
const payload = await response.json();
|
|
919
|
-
const parsed =
|
|
920
|
-
return
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
warnings: parsed.warnings,
|
|
925
|
-
links: { wellKnownUrl: stageUrl },
|
|
926
|
-
...options.includeRaw ? { raw: parsed.raw } : {}
|
|
927
|
-
};
|
|
928
|
-
} catch (error) {
|
|
929
|
-
return {
|
|
930
|
-
stage: "well-known/x402",
|
|
931
|
-
valid: false,
|
|
932
|
-
resources: [],
|
|
933
|
-
warnings: [
|
|
934
|
-
warning(
|
|
935
|
-
"FETCH_FAILED",
|
|
936
|
-
"warn",
|
|
937
|
-
`Legacy well-known fetch exception: ${error instanceof Error ? error.message : String(error)}`,
|
|
938
|
-
{
|
|
939
|
-
stage: "well-known/x402"
|
|
940
|
-
}
|
|
941
|
-
)
|
|
942
|
-
],
|
|
943
|
-
links: { wellKnownUrl: stageUrl }
|
|
944
|
-
};
|
|
230
|
+
const parsed = OpenApiParsedSchema.safeParse(payload);
|
|
231
|
+
if (!parsed.success) return null;
|
|
232
|
+
return { raw: payload, ...parsed.data, fetchedUrl: url };
|
|
233
|
+
} catch {
|
|
234
|
+
return null;
|
|
945
235
|
}
|
|
946
236
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
for (const part of parts) {
|
|
958
|
-
const separator = part.indexOf("=");
|
|
959
|
-
if (separator <= 0) continue;
|
|
960
|
-
const key = part.slice(0, separator).trim().toLowerCase();
|
|
961
|
-
const value = part.slice(separator + 1).trim();
|
|
962
|
-
if (key && value) keyValues.set(key, value);
|
|
963
|
-
}
|
|
964
|
-
if (keyValues.get("v") !== "x4021") return null;
|
|
965
|
-
const url = keyValues.get("url");
|
|
966
|
-
if (!url || !/^https?:\/\//i.test(url)) return null;
|
|
967
|
-
return { url, legacyPlainUrl: false };
|
|
237
|
+
function getOpenAPI(origin, headers, signal, specificationOverrideUrl) {
|
|
238
|
+
const url = specificationOverrideUrl ?? `${origin}/openapi.json`;
|
|
239
|
+
return fetchSafe(url, {
|
|
240
|
+
method: "GET",
|
|
241
|
+
headers: { Accept: "application/json", ...headers },
|
|
242
|
+
signal
|
|
243
|
+
}).andThen((response) => {
|
|
244
|
+
if (!response.ok) return (0, import_neverthrow2.okAsync)(null);
|
|
245
|
+
return import_neverthrow2.ResultAsync.fromSafePromise(parseBody(response, url));
|
|
246
|
+
});
|
|
968
247
|
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
try {
|
|
983
|
-
records = await options.txtResolver(fqdn);
|
|
984
|
-
} catch (error) {
|
|
985
|
-
return {
|
|
986
|
-
stage: "dns/_x402",
|
|
987
|
-
valid: false,
|
|
988
|
-
resources: [],
|
|
989
|
-
warnings: [
|
|
990
|
-
warning(
|
|
991
|
-
"FETCH_FAILED",
|
|
992
|
-
"warn",
|
|
993
|
-
`DNS TXT lookup failed for ${fqdn}: ${error instanceof Error ? error.message : String(error)}`,
|
|
994
|
-
{ stage: "dns/_x402" }
|
|
995
|
-
)
|
|
996
|
-
]
|
|
997
|
-
};
|
|
998
|
-
}
|
|
999
|
-
if (records.length === 0) {
|
|
1000
|
-
return {
|
|
1001
|
-
stage: "dns/_x402",
|
|
1002
|
-
valid: false,
|
|
1003
|
-
resources: [],
|
|
1004
|
-
warnings: []
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
const warnings = [
|
|
1008
|
-
warning(
|
|
1009
|
-
"LEGACY_DNS_USED",
|
|
1010
|
-
"warn",
|
|
1011
|
-
"Using DNS _x402 compatibility path. Migrate to OpenAPI-first discovery.",
|
|
248
|
+
|
|
249
|
+
// src/core/source/wellknown/index.ts
|
|
250
|
+
var import_neverthrow3 = require("neverthrow");
|
|
251
|
+
function toWellKnownParsed(origin, doc) {
|
|
252
|
+
const routes = doc.resources.flatMap((entry) => {
|
|
253
|
+
const trimmed = entry.trim();
|
|
254
|
+
if (!trimmed) return [];
|
|
255
|
+
const parts = trimmed.split(/\s+/);
|
|
256
|
+
const maybeMethod = parts.length >= 2 ? parseMethod(parts[0]) : void 0;
|
|
257
|
+
const target = maybeMethod ? parts.slice(1).join(" ") : trimmed;
|
|
258
|
+
const absolute = toAbsoluteUrl(origin, target);
|
|
259
|
+
if (!absolute) return [];
|
|
260
|
+
return [
|
|
1012
261
|
{
|
|
1013
|
-
|
|
262
|
+
path: normalizePath(absolute.pathname),
|
|
263
|
+
method: maybeMethod ?? DEFAULT_MISSING_METHOD
|
|
1014
264
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
for (const record of records) {
|
|
1019
|
-
const parsed = parseDnsRecord(record);
|
|
1020
|
-
if (!parsed) {
|
|
1021
|
-
warnings.push(
|
|
1022
|
-
warning("PARSE_FAILED", "warn", `Invalid DNS _x402 TXT record: ${record}`, {
|
|
1023
|
-
stage: "dns/_x402"
|
|
1024
|
-
})
|
|
1025
|
-
);
|
|
1026
|
-
continue;
|
|
1027
|
-
}
|
|
1028
|
-
urls.push(parsed.url);
|
|
1029
|
-
if (parsed.legacyPlainUrl) {
|
|
1030
|
-
warnings.push(
|
|
1031
|
-
warning("LEGACY_DNS_PLAIN_URL", "warn", `Legacy plain URL TXT format used: ${record}`, {
|
|
1032
|
-
stage: "dns/_x402",
|
|
1033
|
-
hint: "Use v=x4021;url=<https-url> format."
|
|
1034
|
-
})
|
|
1035
|
-
);
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1038
|
-
if (urls.length === 0) {
|
|
1039
|
-
return {
|
|
1040
|
-
stage: "dns/_x402",
|
|
1041
|
-
valid: false,
|
|
1042
|
-
resources: [],
|
|
1043
|
-
warnings,
|
|
1044
|
-
...options.includeRaw ? { raw: { dnsRecords: records } } : {}
|
|
1045
|
-
};
|
|
1046
|
-
}
|
|
1047
|
-
const mergedWarnings = [...warnings];
|
|
1048
|
-
const mergedResources = [];
|
|
1049
|
-
const rawDocuments = [];
|
|
1050
|
-
for (const url of urls) {
|
|
1051
|
-
const stageResult = await runWellKnownX402Stage({
|
|
1052
|
-
origin,
|
|
1053
|
-
url,
|
|
1054
|
-
fetcher: options.fetcher,
|
|
1055
|
-
headers: options.headers,
|
|
1056
|
-
signal: options.signal,
|
|
1057
|
-
includeRaw: options.includeRaw
|
|
1058
|
-
});
|
|
1059
|
-
mergedResources.push(...stageResult.resources);
|
|
1060
|
-
mergedWarnings.push(...stageResult.warnings);
|
|
1061
|
-
if (options.includeRaw && stageResult.raw !== void 0) {
|
|
1062
|
-
rawDocuments.push({ url, document: stageResult.raw });
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
const deduped = /* @__PURE__ */ new Map();
|
|
1066
|
-
for (const resource of mergedResources) {
|
|
1067
|
-
if (!deduped.has(resource.resourceKey)) {
|
|
1068
|
-
deduped.set(resource.resourceKey, {
|
|
1069
|
-
...resource,
|
|
1070
|
-
source: "dns/_x402",
|
|
1071
|
-
links: {
|
|
1072
|
-
...resource.links,
|
|
1073
|
-
discoveryUrl: resource.links?.discoveryUrl
|
|
1074
|
-
}
|
|
1075
|
-
});
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
return {
|
|
1079
|
-
stage: "dns/_x402",
|
|
1080
|
-
valid: deduped.size > 0,
|
|
1081
|
-
resources: [...deduped.values()],
|
|
1082
|
-
warnings: mergedWarnings,
|
|
1083
|
-
...options.includeRaw ? { raw: { dnsRecords: records, documents: rawDocuments } } : {}
|
|
1084
|
-
};
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
// src/compat/interop-mpp/wellKnownMpp.ts
|
|
1088
|
-
function isRecord2(value) {
|
|
1089
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
265
|
+
];
|
|
266
|
+
});
|
|
267
|
+
return { routes, ...doc.instructions ? { instructions: doc.instructions } : {} };
|
|
1090
268
|
}
|
|
1091
|
-
async function
|
|
1092
|
-
const url = `${options.origin}${WELL_KNOWN_MPP_PATH}`;
|
|
269
|
+
async function parseBody2(response, origin, url) {
|
|
1093
270
|
try {
|
|
1094
|
-
const response = await options.fetcher(url, {
|
|
1095
|
-
method: "GET",
|
|
1096
|
-
headers: { Accept: "application/json", ...options.headers },
|
|
1097
|
-
signal: options.signal
|
|
1098
|
-
});
|
|
1099
|
-
if (!response.ok) {
|
|
1100
|
-
return {
|
|
1101
|
-
stage: "interop/mpp",
|
|
1102
|
-
valid: false,
|
|
1103
|
-
resources: [],
|
|
1104
|
-
warnings: []
|
|
1105
|
-
};
|
|
1106
|
-
}
|
|
1107
271
|
const payload = await response.json();
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
stage: "interop/mpp"
|
|
1116
|
-
})
|
|
1117
|
-
],
|
|
1118
|
-
...options.includeRaw ? { raw: payload } : {}
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
const warnings = [
|
|
1122
|
-
warning(
|
|
1123
|
-
"INTEROP_MPP_USED",
|
|
1124
|
-
"info",
|
|
1125
|
-
"Using /.well-known/mpp interop adapter. This is additive and non-canonical.",
|
|
1126
|
-
{
|
|
1127
|
-
stage: "interop/mpp",
|
|
1128
|
-
hint: "Use for registry indexing only, not canonical runtime routing."
|
|
1129
|
-
}
|
|
1130
|
-
)
|
|
1131
|
-
];
|
|
1132
|
-
const resources = [];
|
|
1133
|
-
const fromStringResources = Array.isArray(payload.resources) ? payload.resources.filter((entry) => typeof entry === "string") : [];
|
|
1134
|
-
for (const entry of fromStringResources) {
|
|
1135
|
-
const parts = entry.trim().split(/\s+/);
|
|
1136
|
-
let method = parseMethod(parts[0]);
|
|
1137
|
-
let path = parts.join(" ");
|
|
1138
|
-
if (method) {
|
|
1139
|
-
path = parts.slice(1).join(" ");
|
|
1140
|
-
} else {
|
|
1141
|
-
method = "POST";
|
|
1142
|
-
}
|
|
1143
|
-
const normalizedPath = normalizePath(path);
|
|
1144
|
-
resources.push(
|
|
1145
|
-
createResource(
|
|
1146
|
-
{
|
|
1147
|
-
origin: options.origin,
|
|
1148
|
-
method,
|
|
1149
|
-
path: normalizedPath,
|
|
1150
|
-
source: "interop/mpp",
|
|
1151
|
-
summary: `${method} ${normalizedPath}`,
|
|
1152
|
-
authHint: "paid",
|
|
1153
|
-
protocolHints: ["mpp"],
|
|
1154
|
-
links: { discoveryUrl: url }
|
|
1155
|
-
},
|
|
1156
|
-
0.45,
|
|
1157
|
-
"unverified"
|
|
1158
|
-
)
|
|
1159
|
-
);
|
|
1160
|
-
}
|
|
1161
|
-
const fromServiceObjects = Array.isArray(payload.services) ? payload.services.filter((entry) => isRecord2(entry)) : [];
|
|
1162
|
-
for (const service of fromServiceObjects) {
|
|
1163
|
-
const path = typeof service.path === "string" ? service.path : void 0;
|
|
1164
|
-
const method = parseMethod(typeof service.method === "string" ? service.method : void 0) ?? "POST";
|
|
1165
|
-
if (!path) continue;
|
|
1166
|
-
const normalizedPath = normalizePath(path);
|
|
1167
|
-
resources.push(
|
|
1168
|
-
createResource(
|
|
1169
|
-
{
|
|
1170
|
-
origin: options.origin,
|
|
1171
|
-
method,
|
|
1172
|
-
path: normalizedPath,
|
|
1173
|
-
source: "interop/mpp",
|
|
1174
|
-
summary: typeof service.summary === "string" ? service.summary : `${method} ${normalizedPath}`,
|
|
1175
|
-
authHint: "paid",
|
|
1176
|
-
protocolHints: ["mpp"],
|
|
1177
|
-
links: { discoveryUrl: url }
|
|
1178
|
-
},
|
|
1179
|
-
0.45,
|
|
1180
|
-
"unverified"
|
|
1181
|
-
)
|
|
1182
|
-
);
|
|
1183
|
-
}
|
|
1184
|
-
const deduped = /* @__PURE__ */ new Map();
|
|
1185
|
-
for (const resource of resources) {
|
|
1186
|
-
if (!deduped.has(resource.resourceKey)) deduped.set(resource.resourceKey, resource);
|
|
1187
|
-
}
|
|
1188
|
-
return {
|
|
1189
|
-
stage: "interop/mpp",
|
|
1190
|
-
valid: deduped.size > 0,
|
|
1191
|
-
resources: [...deduped.values()],
|
|
1192
|
-
warnings,
|
|
1193
|
-
...options.includeRaw ? { raw: payload } : {}
|
|
1194
|
-
};
|
|
1195
|
-
} catch (error) {
|
|
1196
|
-
return {
|
|
1197
|
-
stage: "interop/mpp",
|
|
1198
|
-
valid: false,
|
|
1199
|
-
resources: [],
|
|
1200
|
-
warnings: [
|
|
1201
|
-
warning(
|
|
1202
|
-
"FETCH_FAILED",
|
|
1203
|
-
"warn",
|
|
1204
|
-
`Interop /.well-known/mpp fetch failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1205
|
-
{
|
|
1206
|
-
stage: "interop/mpp"
|
|
1207
|
-
}
|
|
1208
|
-
)
|
|
1209
|
-
]
|
|
1210
|
-
};
|
|
272
|
+
const doc = WellKnownDocSchema.safeParse(payload);
|
|
273
|
+
if (!doc.success) return null;
|
|
274
|
+
const parsed = WellKnownParsedSchema.safeParse(toWellKnownParsed(origin, doc.data));
|
|
275
|
+
if (!parsed.success) return null;
|
|
276
|
+
return { raw: payload, ...parsed.data, fetchedUrl: url };
|
|
277
|
+
} catch {
|
|
278
|
+
return null;
|
|
1211
279
|
}
|
|
1212
280
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
281
|
+
function getWellKnown(origin, headers, signal) {
|
|
282
|
+
const url = `${origin}/.well-known/x402`;
|
|
283
|
+
return fetchSafe(url, {
|
|
284
|
+
method: "GET",
|
|
285
|
+
headers: { Accept: "application/json", ...headers },
|
|
286
|
+
signal
|
|
287
|
+
}).andThen((response) => {
|
|
288
|
+
if (!response.ok) return (0, import_neverthrow3.okAsync)(null);
|
|
289
|
+
return import_neverthrow3.ResultAsync.fromSafePromise(parseBody2(response, origin, url));
|
|
290
|
+
});
|
|
1217
291
|
}
|
|
1218
292
|
|
|
1219
|
-
// src/core/
|
|
1220
|
-
function
|
|
1221
|
-
|
|
293
|
+
// src/core/layers/l2.ts
|
|
294
|
+
function formatPrice(pricing) {
|
|
295
|
+
if (pricing.pricingMode === "fixed") return `$${pricing.price}`;
|
|
296
|
+
if (pricing.pricingMode === "range") return `$${pricing.minPrice}-$${pricing.maxPrice}`;
|
|
297
|
+
return pricing.maxPrice ? `up to $${pricing.maxPrice}` : "quote";
|
|
298
|
+
}
|
|
299
|
+
function checkL2ForOpenAPI(openApi) {
|
|
300
|
+
const routes = openApi.routes.map((route) => ({
|
|
301
|
+
path: route.path,
|
|
302
|
+
method: route.method,
|
|
303
|
+
summary: route.summary ?? `${route.method} ${route.path}`,
|
|
304
|
+
...route.authMode ? { authMode: route.authMode } : {},
|
|
305
|
+
...route.pricing ? { price: formatPrice(route.pricing) } : {},
|
|
306
|
+
...route.protocols?.length ? { protocols: route.protocols } : {}
|
|
307
|
+
}));
|
|
308
|
+
return {
|
|
309
|
+
...openApi.info.title ? { title: openApi.info.title } : {},
|
|
310
|
+
...openApi.info.description ? { description: openApi.info.description } : {},
|
|
311
|
+
routes,
|
|
312
|
+
source: "openapi"
|
|
313
|
+
};
|
|
1222
314
|
}
|
|
1223
|
-
function
|
|
1224
|
-
const
|
|
1225
|
-
|
|
315
|
+
function checkL2ForWellknown(wellKnown) {
|
|
316
|
+
const routes = wellKnown.routes.map((route) => ({
|
|
317
|
+
path: route.path,
|
|
318
|
+
method: route.method,
|
|
319
|
+
summary: `${route.method} ${route.path}`,
|
|
320
|
+
authMode: "paid",
|
|
321
|
+
protocols: ["x402"]
|
|
322
|
+
}));
|
|
323
|
+
return { routes, source: "well-known/x402" };
|
|
1226
324
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
325
|
+
|
|
326
|
+
// src/core/layers/l4.ts
|
|
327
|
+
function checkL4ForOpenAPI(openApi) {
|
|
328
|
+
if (openApi.guidance) {
|
|
329
|
+
return { guidance: openApi.guidance, source: "openapi" };
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
1231
332
|
}
|
|
1232
|
-
function
|
|
1233
|
-
if (
|
|
1234
|
-
|
|
1235
|
-
if (hasSecurity(operation, "siwx")) return "siwx";
|
|
1236
|
-
return "paid";
|
|
333
|
+
function checkL4ForWellknown(wellKnown) {
|
|
334
|
+
if (wellKnown.instructions) {
|
|
335
|
+
return { guidance: wellKnown.instructions, source: "well-known/x402" };
|
|
1237
336
|
}
|
|
1238
|
-
|
|
1239
|
-
if (hasSecurity(operation, "siwx")) return "siwx";
|
|
1240
|
-
return void 0;
|
|
337
|
+
return null;
|
|
1241
338
|
}
|
|
1242
339
|
|
|
1243
|
-
// src/
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
340
|
+
// src/audit/codes.ts
|
|
341
|
+
var AUDIT_CODES = {
|
|
342
|
+
// ─── Source presence ─────────────────────────────────────────────────────────
|
|
343
|
+
OPENAPI_NOT_FOUND: "OPENAPI_NOT_FOUND",
|
|
344
|
+
WELLKNOWN_NOT_FOUND: "WELLKNOWN_NOT_FOUND",
|
|
345
|
+
// ─── OpenAPI quality ─────────────────────────────────────────────────────────
|
|
346
|
+
OPENAPI_NO_ROUTES: "OPENAPI_NO_ROUTES",
|
|
347
|
+
// ─── L2 route-list checks ────────────────────────────────────────────────────
|
|
348
|
+
L2_NO_ROUTES: "L2_NO_ROUTES",
|
|
349
|
+
L2_ROUTE_COUNT_HIGH: "L2_ROUTE_COUNT_HIGH",
|
|
350
|
+
L2_AUTH_MODE_MISSING: "L2_AUTH_MODE_MISSING",
|
|
351
|
+
L2_PRICE_MISSING_ON_PAID: "L2_PRICE_MISSING_ON_PAID",
|
|
352
|
+
L2_PROTOCOLS_MISSING_ON_PAID: "L2_PROTOCOLS_MISSING_ON_PAID",
|
|
353
|
+
// ─── L3 endpoint advisory checks ─────────────────────────────────────────────
|
|
354
|
+
L3_NOT_FOUND: "L3_NOT_FOUND",
|
|
355
|
+
L3_INPUT_SCHEMA_MISSING: "L3_INPUT_SCHEMA_MISSING",
|
|
356
|
+
L3_AUTH_MODE_MISSING: "L3_AUTH_MODE_MISSING",
|
|
357
|
+
L3_PROTOCOLS_MISSING_ON_PAID: "L3_PROTOCOLS_MISSING_ON_PAID",
|
|
358
|
+
// ─── L4 guidance checks ──────────────────────────────────────────────────────
|
|
359
|
+
L4_GUIDANCE_MISSING: "L4_GUIDANCE_MISSING",
|
|
360
|
+
L4_GUIDANCE_TOO_LONG: "L4_GUIDANCE_TOO_LONG"
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// src/audit/warnings.ts
|
|
364
|
+
var GUIDANCE_TOO_LONG_CHARS = 4e3;
|
|
365
|
+
var ROUTE_COUNT_HIGH = 40;
|
|
366
|
+
function getWarningsForOpenAPI(openApi) {
|
|
367
|
+
if (openApi === null) {
|
|
368
|
+
return [
|
|
369
|
+
{
|
|
370
|
+
code: AUDIT_CODES.OPENAPI_NOT_FOUND,
|
|
371
|
+
severity: "info",
|
|
372
|
+
message: "No OpenAPI specification found at this origin.",
|
|
373
|
+
hint: "Expose an OpenAPI spec (e.g. /openapi.json) for richer discovery."
|
|
374
|
+
}
|
|
375
|
+
];
|
|
376
|
+
}
|
|
377
|
+
const warnings = [];
|
|
378
|
+
if (openApi.routes.length === 0) {
|
|
379
|
+
warnings.push({
|
|
380
|
+
code: AUDIT_CODES.OPENAPI_NO_ROUTES,
|
|
381
|
+
severity: "warn",
|
|
382
|
+
message: "OpenAPI spec found but contains no route definitions.",
|
|
383
|
+
hint: "Add paths to your OpenAPI spec so agents can discover endpoints.",
|
|
384
|
+
path: "paths"
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
return warnings;
|
|
1251
388
|
}
|
|
1252
|
-
function
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
389
|
+
function getWarningsForWellKnown(wellKnown) {
|
|
390
|
+
if (wellKnown === null) {
|
|
391
|
+
return [
|
|
392
|
+
{
|
|
393
|
+
code: AUDIT_CODES.WELLKNOWN_NOT_FOUND,
|
|
394
|
+
severity: "info",
|
|
395
|
+
message: "No /.well-known/x402 resource found at this origin.",
|
|
396
|
+
hint: "Expose /.well-known/x402 as a fallback for agents that cannot read OpenAPI."
|
|
397
|
+
}
|
|
398
|
+
];
|
|
399
|
+
}
|
|
400
|
+
return [];
|
|
1258
401
|
}
|
|
1259
|
-
|
|
402
|
+
function getWarningsForL2(l2) {
|
|
1260
403
|
const warnings = [];
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
404
|
+
if (l2.routes.length === 0) {
|
|
405
|
+
warnings.push({
|
|
406
|
+
code: AUDIT_CODES.L2_NO_ROUTES,
|
|
407
|
+
severity: "warn",
|
|
408
|
+
message: "No routes found from any discovery source."
|
|
409
|
+
});
|
|
410
|
+
return warnings;
|
|
411
|
+
}
|
|
412
|
+
if (l2.routes.length > ROUTE_COUNT_HIGH) {
|
|
413
|
+
warnings.push({
|
|
414
|
+
code: AUDIT_CODES.L2_ROUTE_COUNT_HIGH,
|
|
415
|
+
severity: "warn",
|
|
416
|
+
message: `High route count (${l2.routes.length}) may exceed agent token budgets for zero-hop injection.`,
|
|
417
|
+
hint: "Consider grouping routes or reducing the advertised surface."
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
for (const route of l2.routes) {
|
|
421
|
+
const loc = `${route.method} ${route.path}`;
|
|
422
|
+
if (!route.authMode) {
|
|
423
|
+
warnings.push({
|
|
424
|
+
code: AUDIT_CODES.L2_AUTH_MODE_MISSING,
|
|
425
|
+
severity: "warn",
|
|
426
|
+
message: `Route ${loc} is missing an auth mode declaration.`,
|
|
427
|
+
hint: "Set x-payment-info.authMode (or securitySchemes) so agents know if payment is required.",
|
|
428
|
+
path: route.path
|
|
1271
429
|
});
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
430
|
+
}
|
|
431
|
+
if (route.authMode === "paid") {
|
|
432
|
+
if (!route.price) {
|
|
433
|
+
warnings.push({
|
|
434
|
+
code: AUDIT_CODES.L2_PRICE_MISSING_ON_PAID,
|
|
435
|
+
severity: "warn",
|
|
436
|
+
message: `Paid route ${loc} has no price hint.`,
|
|
437
|
+
hint: "Add x-payment-info.price (or minPrice/maxPrice) so agents can budget before calling.",
|
|
438
|
+
path: route.path
|
|
439
|
+
});
|
|
1281
440
|
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
441
|
+
if (!route.protocols?.length) {
|
|
442
|
+
warnings.push({
|
|
443
|
+
code: AUDIT_CODES.L2_PROTOCOLS_MISSING_ON_PAID,
|
|
444
|
+
severity: "info",
|
|
445
|
+
message: `Paid route ${loc} does not declare supported payment protocols.`,
|
|
446
|
+
hint: "Add x-payment-info.protocols (e.g. ['x402']) to signal which payment flows are accepted.",
|
|
447
|
+
path: route.path
|
|
448
|
+
});
|
|
1290
449
|
}
|
|
1291
|
-
document = payload;
|
|
1292
|
-
fetchedUrl = url;
|
|
1293
|
-
break;
|
|
1294
|
-
} catch (error) {
|
|
1295
|
-
warnings.push(
|
|
1296
|
-
warning(
|
|
1297
|
-
"FETCH_FAILED",
|
|
1298
|
-
"warn",
|
|
1299
|
-
`OpenAPI fetch exception at ${url}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1300
|
-
{ stage: "openapi" }
|
|
1301
|
-
)
|
|
1302
|
-
);
|
|
1303
450
|
}
|
|
1304
451
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
452
|
+
return warnings;
|
|
453
|
+
}
|
|
454
|
+
function getWarningsForL3(l3) {
|
|
455
|
+
if (l3 === null) {
|
|
456
|
+
return [
|
|
457
|
+
{
|
|
458
|
+
code: AUDIT_CODES.L3_NOT_FOUND,
|
|
459
|
+
severity: "info",
|
|
460
|
+
message: "No spec data found for this endpoint.",
|
|
461
|
+
hint: "Ensure the path is defined in your OpenAPI spec or reachable via probe."
|
|
462
|
+
}
|
|
463
|
+
];
|
|
1312
464
|
}
|
|
1313
|
-
const
|
|
1314
|
-
if (!
|
|
1315
|
-
warnings.push(
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
stage: "openapi",
|
|
1322
|
-
valid: false,
|
|
1323
|
-
resources: [],
|
|
1324
|
-
warnings,
|
|
1325
|
-
links: { openapiUrl: fetchedUrl },
|
|
1326
|
-
...options.includeRaw ? { raw: document } : {}
|
|
1327
|
-
};
|
|
465
|
+
const warnings = [];
|
|
466
|
+
if (!l3.authMode) {
|
|
467
|
+
warnings.push({
|
|
468
|
+
code: AUDIT_CODES.L3_AUTH_MODE_MISSING,
|
|
469
|
+
severity: "warn",
|
|
470
|
+
message: "Endpoint has no auth mode in the spec.",
|
|
471
|
+
hint: "Declare auth mode via security schemes or x-payment-info so agents can plan payment."
|
|
472
|
+
});
|
|
1328
473
|
}
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
stage: "openapi"
|
|
1337
|
-
})
|
|
1338
|
-
);
|
|
474
|
+
if (l3.authMode === "paid" && !l3.inputSchema) {
|
|
475
|
+
warnings.push({
|
|
476
|
+
code: AUDIT_CODES.L3_INPUT_SCHEMA_MISSING,
|
|
477
|
+
severity: "warn",
|
|
478
|
+
message: "Paid endpoint is missing an input schema.",
|
|
479
|
+
hint: "Add a requestBody or parameters schema so agents can construct valid payloads."
|
|
480
|
+
});
|
|
1339
481
|
}
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
{
|
|
1359
|
-
stage: "openapi"
|
|
1360
|
-
}
|
|
1361
|
-
)
|
|
1362
|
-
);
|
|
1363
|
-
continue;
|
|
1364
|
-
}
|
|
1365
|
-
const operation = rawOperation;
|
|
1366
|
-
const summary = asString(operation.summary) ?? asString(operation.description);
|
|
1367
|
-
if (!summary) {
|
|
1368
|
-
warnings.push(
|
|
1369
|
-
warning(
|
|
1370
|
-
"OPENAPI_SUMMARY_MISSING",
|
|
1371
|
-
"warn",
|
|
1372
|
-
`${method} ${rawPath} missing summary/description, using fallback summary`,
|
|
1373
|
-
{ stage: "openapi" }
|
|
1374
|
-
)
|
|
1375
|
-
);
|
|
1376
|
-
}
|
|
1377
|
-
const authMode = inferAuthMode(operation);
|
|
1378
|
-
if (!authMode) {
|
|
1379
|
-
warnings.push(
|
|
1380
|
-
warning(
|
|
1381
|
-
"OPENAPI_AUTH_MODE_MISSING",
|
|
1382
|
-
"error",
|
|
1383
|
-
`${method} ${rawPath} missing auth mode (no x-payment-info, 402 response, or security scheme)`,
|
|
1384
|
-
{
|
|
1385
|
-
stage: "openapi"
|
|
1386
|
-
}
|
|
1387
|
-
)
|
|
1388
|
-
);
|
|
1389
|
-
continue;
|
|
1390
|
-
}
|
|
1391
|
-
if ((authMode === "paid" || authMode === "siwx") && !has402Response(operation)) {
|
|
1392
|
-
warnings.push(
|
|
1393
|
-
warning(
|
|
1394
|
-
"OPENAPI_402_MISSING",
|
|
1395
|
-
"error",
|
|
1396
|
-
`${method} ${rawPath} requires 402 response for authMode=${authMode}`,
|
|
1397
|
-
{ stage: "openapi" }
|
|
1398
|
-
)
|
|
1399
|
-
);
|
|
1400
|
-
continue;
|
|
1401
|
-
}
|
|
1402
|
-
const paymentInfo = isRecord3(operation["x-payment-info"]) ? operation["x-payment-info"] : void 0;
|
|
1403
|
-
const protocols = paymentInfo ? parseProtocols(paymentInfo) : [];
|
|
1404
|
-
if (authMode === "paid" && protocols.length === 0) {
|
|
1405
|
-
warnings.push(
|
|
1406
|
-
warning(
|
|
1407
|
-
"OPENAPI_PAID_PROTOCOLS_MISSING",
|
|
1408
|
-
"error",
|
|
1409
|
-
`${method} ${rawPath} must define x-payment-info.protocols when authMode=paid`,
|
|
1410
|
-
{ stage: "openapi" }
|
|
1411
|
-
)
|
|
1412
|
-
);
|
|
1413
|
-
continue;
|
|
1414
|
-
}
|
|
1415
|
-
let pricing;
|
|
1416
|
-
let priceHint;
|
|
1417
|
-
if (paymentInfo && authMode === "paid") {
|
|
1418
|
-
const pricingModeRaw = asString(paymentInfo.pricingMode);
|
|
1419
|
-
const price = parsePriceValue(paymentInfo.price);
|
|
1420
|
-
const minPrice = parsePriceValue(paymentInfo.minPrice);
|
|
1421
|
-
const maxPrice = parsePriceValue(paymentInfo.maxPrice);
|
|
1422
|
-
const inferredPricingMode = pricingModeRaw ?? (price ? "fixed" : minPrice && maxPrice ? "range" : "quote");
|
|
1423
|
-
if (inferredPricingMode !== "fixed" && inferredPricingMode !== "range" && inferredPricingMode !== "quote") {
|
|
1424
|
-
warnings.push(
|
|
1425
|
-
warning(
|
|
1426
|
-
"OPENAPI_PRICING_INVALID",
|
|
1427
|
-
"error",
|
|
1428
|
-
`${method} ${rawPath} has invalid pricingMode`,
|
|
1429
|
-
{
|
|
1430
|
-
stage: "openapi"
|
|
1431
|
-
}
|
|
1432
|
-
)
|
|
1433
|
-
);
|
|
1434
|
-
continue;
|
|
1435
|
-
}
|
|
1436
|
-
if (inferredPricingMode === "fixed" && !price) {
|
|
1437
|
-
warnings.push(
|
|
1438
|
-
warning(
|
|
1439
|
-
"OPENAPI_PRICING_INVALID",
|
|
1440
|
-
"error",
|
|
1441
|
-
`${method} ${rawPath} fixed pricing requires price`,
|
|
1442
|
-
{
|
|
1443
|
-
stage: "openapi"
|
|
1444
|
-
}
|
|
1445
|
-
)
|
|
1446
|
-
);
|
|
1447
|
-
continue;
|
|
1448
|
-
}
|
|
1449
|
-
if (inferredPricingMode === "range") {
|
|
1450
|
-
if (!minPrice || !maxPrice) {
|
|
1451
|
-
warnings.push(
|
|
1452
|
-
warning(
|
|
1453
|
-
"OPENAPI_PRICING_INVALID",
|
|
1454
|
-
"error",
|
|
1455
|
-
`${method} ${rawPath} range pricing requires minPrice and maxPrice`,
|
|
1456
|
-
{ stage: "openapi" }
|
|
1457
|
-
)
|
|
1458
|
-
);
|
|
1459
|
-
continue;
|
|
1460
|
-
}
|
|
1461
|
-
const min = Number(minPrice);
|
|
1462
|
-
const max = Number(maxPrice);
|
|
1463
|
-
if (!Number.isFinite(min) || !Number.isFinite(max) || min > max) {
|
|
1464
|
-
warnings.push(
|
|
1465
|
-
warning(
|
|
1466
|
-
"OPENAPI_PRICING_INVALID",
|
|
1467
|
-
"error",
|
|
1468
|
-
`${method} ${rawPath} range pricing requires numeric minPrice <= maxPrice`,
|
|
1469
|
-
{ stage: "openapi" }
|
|
1470
|
-
)
|
|
1471
|
-
);
|
|
1472
|
-
continue;
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
pricing = {
|
|
1476
|
-
pricingMode: inferredPricingMode,
|
|
1477
|
-
...price ? { price } : {},
|
|
1478
|
-
...minPrice ? { minPrice } : {},
|
|
1479
|
-
...maxPrice ? { maxPrice } : {}
|
|
1480
|
-
};
|
|
1481
|
-
priceHint = inferredPricingMode === "fixed" ? price : inferredPricingMode === "range" ? `${minPrice}-${maxPrice}` : maxPrice;
|
|
482
|
+
if (l3.authMode === "paid" && !l3.protocols?.length) {
|
|
483
|
+
warnings.push({
|
|
484
|
+
code: AUDIT_CODES.L3_PROTOCOLS_MISSING_ON_PAID,
|
|
485
|
+
severity: "info",
|
|
486
|
+
message: "Paid endpoint does not declare supported payment protocols.",
|
|
487
|
+
hint: "Add x-payment-info.protocols (e.g. ['x402']) to the operation."
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return warnings;
|
|
491
|
+
}
|
|
492
|
+
function getWarningsForL4(l4) {
|
|
493
|
+
if (l4 === null) {
|
|
494
|
+
return [
|
|
495
|
+
{
|
|
496
|
+
code: AUDIT_CODES.L4_GUIDANCE_MISSING,
|
|
497
|
+
severity: "info",
|
|
498
|
+
message: "No guidance text found (llms.txt or OpenAPI info.guidance).",
|
|
499
|
+
hint: "Add an info.guidance field to your OpenAPI spec or expose /llms.txt for agent-readable instructions."
|
|
1482
500
|
}
|
|
1483
|
-
|
|
1484
|
-
{
|
|
1485
|
-
origin: options.origin,
|
|
1486
|
-
method,
|
|
1487
|
-
path: normalizePath(rawPath),
|
|
1488
|
-
source: "openapi",
|
|
1489
|
-
summary: summary ?? `${method} ${normalizePath(rawPath)}`,
|
|
1490
|
-
authHint: authMode,
|
|
1491
|
-
protocolHints: protocols,
|
|
1492
|
-
...priceHint ? { priceHint } : {},
|
|
1493
|
-
...pricing ? { pricing } : {},
|
|
1494
|
-
links: {
|
|
1495
|
-
openapiUrl: fetchedUrl,
|
|
1496
|
-
...llmsTxtUrl ? { llmsTxtUrl } : {}
|
|
1497
|
-
}
|
|
1498
|
-
},
|
|
1499
|
-
0.95,
|
|
1500
|
-
ownershipProofs.length > 0 ? "ownership_verified" : "origin_hosted"
|
|
1501
|
-
);
|
|
1502
|
-
resources.push(resource);
|
|
1503
|
-
}
|
|
501
|
+
];
|
|
1504
502
|
}
|
|
1505
|
-
if (
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
if (llmsResponse.ok) {
|
|
1513
|
-
const llmsText = await llmsResponse.text();
|
|
1514
|
-
const tokenCount = estimateTokenCount(llmsText);
|
|
1515
|
-
if (tokenCount > LLMS_TOKEN_WARNING_THRESHOLD) {
|
|
1516
|
-
warnings.push(
|
|
1517
|
-
warning(
|
|
1518
|
-
"LLMSTXT_TOO_LARGE",
|
|
1519
|
-
"warn",
|
|
1520
|
-
`llms.txt estimated ${tokenCount} tokens (threshold ${LLMS_TOKEN_WARNING_THRESHOLD})`,
|
|
1521
|
-
{
|
|
1522
|
-
stage: "openapi",
|
|
1523
|
-
hint: "Keep llms.txt concise and domain-level only."
|
|
1524
|
-
}
|
|
1525
|
-
)
|
|
1526
|
-
);
|
|
1527
|
-
}
|
|
1528
|
-
if (options.includeRaw) {
|
|
1529
|
-
return {
|
|
1530
|
-
stage: "openapi",
|
|
1531
|
-
valid: resources.length > 0,
|
|
1532
|
-
resources,
|
|
1533
|
-
warnings,
|
|
1534
|
-
links: { openapiUrl: fetchedUrl, llmsTxtUrl },
|
|
1535
|
-
raw: {
|
|
1536
|
-
openapi: document,
|
|
1537
|
-
llmsTxt: llmsText
|
|
1538
|
-
}
|
|
1539
|
-
};
|
|
1540
|
-
}
|
|
1541
|
-
} else {
|
|
1542
|
-
warnings.push(
|
|
1543
|
-
warning(
|
|
1544
|
-
"LLMSTXT_FETCH_FAILED",
|
|
1545
|
-
"warn",
|
|
1546
|
-
`llms.txt fetch failed (${llmsResponse.status})`,
|
|
1547
|
-
{
|
|
1548
|
-
stage: "openapi"
|
|
1549
|
-
}
|
|
1550
|
-
)
|
|
1551
|
-
);
|
|
503
|
+
if (l4.guidance.length > GUIDANCE_TOO_LONG_CHARS) {
|
|
504
|
+
return [
|
|
505
|
+
{
|
|
506
|
+
code: AUDIT_CODES.L4_GUIDANCE_TOO_LONG,
|
|
507
|
+
severity: "warn",
|
|
508
|
+
message: `Guidance text is ${l4.guidance.length} characters, which may exceed zero-hop token budgets.`,
|
|
509
|
+
hint: "Trim guidance to under ~4000 characters for reliable zero-hop context injection."
|
|
1552
510
|
}
|
|
1553
|
-
|
|
1554
|
-
warnings.push(
|
|
1555
|
-
warning(
|
|
1556
|
-
"LLMSTXT_FETCH_FAILED",
|
|
1557
|
-
"warn",
|
|
1558
|
-
`llms.txt fetch failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1559
|
-
{ stage: "openapi" }
|
|
1560
|
-
)
|
|
1561
|
-
);
|
|
1562
|
-
}
|
|
511
|
+
];
|
|
1563
512
|
}
|
|
513
|
+
return [];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// src/core/source/probe/index.ts
|
|
517
|
+
var import_neverthrow4 = require("neverthrow");
|
|
518
|
+
|
|
519
|
+
// src/core/protocols/x402/v1/schema.ts
|
|
520
|
+
function extractSchemas(accepts) {
|
|
521
|
+
const first = accepts[0];
|
|
522
|
+
if (!isRecord(first)) return {};
|
|
523
|
+
const outputSchema = isRecord(first.outputSchema) ? first.outputSchema : void 0;
|
|
1564
524
|
return {
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
resources,
|
|
1568
|
-
warnings,
|
|
1569
|
-
links: {
|
|
1570
|
-
openapiUrl: fetchedUrl,
|
|
1571
|
-
...llmsTxtUrl ? { llmsTxtUrl } : {}
|
|
1572
|
-
},
|
|
1573
|
-
...options.includeRaw ? { raw: { openapi: document } } : {}
|
|
525
|
+
...outputSchema && isRecord(outputSchema.input) ? { inputSchema: outputSchema.input } : {},
|
|
526
|
+
...outputSchema && isRecord(outputSchema.output) ? { outputSchema: outputSchema.output } : {}
|
|
1574
527
|
};
|
|
1575
528
|
}
|
|
1576
529
|
|
|
1577
|
-
// src/core/
|
|
1578
|
-
function
|
|
1579
|
-
if (
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
530
|
+
// src/core/protocols/x402/shared.ts
|
|
531
|
+
function asNonEmptyString(value) {
|
|
532
|
+
if (typeof value !== "string") return void 0;
|
|
533
|
+
const trimmed = value.trim();
|
|
534
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
535
|
+
}
|
|
536
|
+
function asPositiveNumber(value) {
|
|
537
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// src/core/protocols/x402/v1/payment-options.ts
|
|
541
|
+
function extractPaymentOptions(accepts) {
|
|
542
|
+
return accepts.flatMap((accept) => {
|
|
543
|
+
if (!isRecord(accept)) return [];
|
|
544
|
+
const network = asNonEmptyString(accept.network);
|
|
545
|
+
const asset = asNonEmptyString(accept.asset);
|
|
546
|
+
const maxAmountRequired = asNonEmptyString(accept.maxAmountRequired);
|
|
547
|
+
if (!network || !asset || !maxAmountRequired) return [];
|
|
548
|
+
const scheme = asNonEmptyString(accept.scheme);
|
|
549
|
+
const payTo = asNonEmptyString(accept.payTo);
|
|
550
|
+
const maxTimeoutSeconds = asPositiveNumber(accept.maxTimeoutSeconds);
|
|
551
|
+
return [
|
|
552
|
+
{
|
|
553
|
+
protocol: "x402",
|
|
554
|
+
version: 1,
|
|
555
|
+
network,
|
|
556
|
+
asset,
|
|
557
|
+
maxAmountRequired,
|
|
558
|
+
...scheme ? { scheme } : {},
|
|
559
|
+
...payTo ? { payTo } : {},
|
|
560
|
+
...maxTimeoutSeconds ? { maxTimeoutSeconds } : {}
|
|
561
|
+
}
|
|
562
|
+
];
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// src/core/protocols/x402/v2/schema.ts
|
|
567
|
+
function extractSchemas2(payload) {
|
|
568
|
+
if (!isRecord(payload)) return {};
|
|
569
|
+
const extensions = isRecord(payload.extensions) ? payload.extensions : void 0;
|
|
570
|
+
const bazaar = extensions && isRecord(extensions.bazaar) ? extensions.bazaar : void 0;
|
|
571
|
+
const schema = bazaar && isRecord(bazaar.schema) ? bazaar.schema : void 0;
|
|
572
|
+
if (!schema) return {};
|
|
573
|
+
const schemaProps = isRecord(schema.properties) ? schema.properties : void 0;
|
|
574
|
+
if (!schemaProps) return {};
|
|
575
|
+
const inputProps = isRecord(schemaProps.input) ? schemaProps.input : void 0;
|
|
576
|
+
const inputProperties = inputProps && isRecord(inputProps.properties) ? inputProps.properties : void 0;
|
|
577
|
+
const inputSchema = (inputProperties && isRecord(inputProperties.body) ? inputProperties.body : void 0) ?? (inputProperties && isRecord(inputProperties.queryParams) ? inputProperties.queryParams : void 0);
|
|
578
|
+
const outputProps = isRecord(schemaProps.output) ? schemaProps.output : void 0;
|
|
579
|
+
const outputProperties = outputProps && isRecord(outputProps.properties) ? outputProps.properties : void 0;
|
|
580
|
+
const outputSchema = outputProperties && isRecord(outputProperties.example) ? outputProperties.example : void 0;
|
|
581
|
+
return {
|
|
582
|
+
...inputSchema ? { inputSchema } : {},
|
|
583
|
+
...outputSchema ? { outputSchema } : {}
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/core/protocols/x402/v2/payment-options.ts
|
|
588
|
+
function extractPaymentOptions2(accepts) {
|
|
589
|
+
return accepts.flatMap((accept) => {
|
|
590
|
+
if (!isRecord(accept)) return [];
|
|
591
|
+
const network = asNonEmptyString(accept.network);
|
|
592
|
+
const asset = asNonEmptyString(accept.asset);
|
|
593
|
+
const amount = asNonEmptyString(accept.amount);
|
|
594
|
+
if (!network || !asset || !amount) return [];
|
|
595
|
+
const scheme = asNonEmptyString(accept.scheme);
|
|
596
|
+
const payTo = asNonEmptyString(accept.payTo);
|
|
597
|
+
const maxTimeoutSeconds = asPositiveNumber(accept.maxTimeoutSeconds);
|
|
598
|
+
return [
|
|
599
|
+
{
|
|
600
|
+
protocol: "x402",
|
|
601
|
+
version: 2,
|
|
602
|
+
network,
|
|
603
|
+
asset,
|
|
604
|
+
amount,
|
|
605
|
+
...scheme ? { scheme } : {},
|
|
606
|
+
...payTo ? { payTo } : {},
|
|
607
|
+
...maxTimeoutSeconds ? { maxTimeoutSeconds } : {}
|
|
608
|
+
}
|
|
609
|
+
];
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/core/protocols/x402/index.ts
|
|
614
|
+
function parseVersion(payload) {
|
|
615
|
+
if (!isRecord(payload)) return void 0;
|
|
616
|
+
const v = payload.x402Version;
|
|
617
|
+
if (v === 1 || v === 2) return v;
|
|
618
|
+
return void 0;
|
|
619
|
+
}
|
|
620
|
+
function detectAuthHint(payload) {
|
|
621
|
+
if (isRecord(payload) && isRecord(payload.extensions)) {
|
|
622
|
+
if (payload.extensions["api-key"]) return "apiKey+paid";
|
|
623
|
+
if (payload.extensions["sign-in-with-x"]) return "siwx";
|
|
1586
624
|
}
|
|
1587
625
|
return "paid";
|
|
1588
626
|
}
|
|
627
|
+
function extractPaymentOptions3(payload) {
|
|
628
|
+
if (!isRecord(payload)) return [];
|
|
629
|
+
const version = parseVersion(payload);
|
|
630
|
+
const accepts = Array.isArray(payload.accepts) ? payload.accepts : [];
|
|
631
|
+
if (version === 1) return extractPaymentOptions(accepts);
|
|
632
|
+
if (version === 2) return extractPaymentOptions2(accepts);
|
|
633
|
+
return [];
|
|
634
|
+
}
|
|
635
|
+
function extractSchemas3(payload) {
|
|
636
|
+
if (!isRecord(payload)) return {};
|
|
637
|
+
const version = parseVersion(payload);
|
|
638
|
+
if (version === 2) return extractSchemas2(payload);
|
|
639
|
+
if (version === 1) {
|
|
640
|
+
const accepts = Array.isArray(payload.accepts) ? payload.accepts : [];
|
|
641
|
+
return extractSchemas(accepts);
|
|
642
|
+
}
|
|
643
|
+
return {};
|
|
644
|
+
}
|
|
645
|
+
function parseInputSchema(payload) {
|
|
646
|
+
return extractSchemas3(payload).inputSchema;
|
|
647
|
+
}
|
|
648
|
+
function parseOutputSchema(payload) {
|
|
649
|
+
return extractSchemas3(payload).outputSchema;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/core/protocols/x402/v1/parse-payment-required.ts
|
|
653
|
+
async function parsePaymentRequiredBody(response) {
|
|
654
|
+
const payload = await response.clone().json();
|
|
655
|
+
if (!isRecord(payload) || payload.x402Version !== 1) return null;
|
|
656
|
+
return payload;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// src/core/protocols/x402/v2/parse-payment-required.ts
|
|
660
|
+
function parsePaymentRequiredBody2(headerValue) {
|
|
661
|
+
const payload = JSON.parse(atob(headerValue));
|
|
662
|
+
if (!isRecord(payload) || payload.x402Version !== 2) return null;
|
|
663
|
+
return payload;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// src/core/source/probe/index.ts
|
|
667
|
+
function isUsableStatus(status) {
|
|
668
|
+
return status === 402 || status >= 200 && status < 300;
|
|
669
|
+
}
|
|
1589
670
|
function detectProtocols(response) {
|
|
1590
671
|
const protocols = /* @__PURE__ */ new Set();
|
|
1591
672
|
const directHeader = response.headers.get("x-payment-protocol");
|
|
@@ -1600,767 +681,548 @@ function detectProtocols(response) {
|
|
|
1600
681
|
if (isMmmEnabled() && authHeader.includes("mpp")) protocols.add("mpp");
|
|
1601
682
|
return [...protocols];
|
|
1602
683
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
valid: false,
|
|
1609
|
-
resources: [],
|
|
1610
|
-
warnings: [
|
|
1611
|
-
warning(
|
|
1612
|
-
"PROBE_STAGE_SKIPPED",
|
|
1613
|
-
"info",
|
|
1614
|
-
"Probe stage skipped because no probe candidates were provided.",
|
|
1615
|
-
{ stage: "probe" }
|
|
1616
|
-
)
|
|
1617
|
-
]
|
|
1618
|
-
};
|
|
684
|
+
function buildProbeUrl(url, method, inputBody) {
|
|
685
|
+
if (!inputBody || method !== "GET" && method !== "HEAD") return url;
|
|
686
|
+
const u = new URL(url);
|
|
687
|
+
for (const [key, value] of Object.entries(inputBody)) {
|
|
688
|
+
u.searchParams.set(key, String(value));
|
|
1619
689
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
}
|
|
690
|
+
return u.toString();
|
|
691
|
+
}
|
|
692
|
+
function probeMethod(url, method, path, headers, signal, inputBody) {
|
|
693
|
+
const hasBody = inputBody !== void 0 && method !== "GET" && method !== "HEAD";
|
|
694
|
+
const probeUrl = buildProbeUrl(url, method, inputBody);
|
|
695
|
+
return fetchSafe(probeUrl, {
|
|
696
|
+
method,
|
|
697
|
+
headers: {
|
|
698
|
+
Accept: "application/json, text/plain;q=0.9, */*;q=0.8",
|
|
699
|
+
...hasBody ? { "Content-Type": "application/json" } : {},
|
|
700
|
+
...headers
|
|
701
|
+
},
|
|
702
|
+
...hasBody ? { body: JSON.stringify(inputBody) } : {},
|
|
703
|
+
signal
|
|
704
|
+
}).andThen((response) => {
|
|
705
|
+
if (!isUsableStatus(response.status)) return import_neverthrow4.ResultAsync.fromSafePromise(Promise.resolve(null));
|
|
706
|
+
return import_neverthrow4.ResultAsync.fromSafePromise(
|
|
707
|
+
(async () => {
|
|
1639
708
|
let authHint = response.status === 402 ? "paid" : "unprotected";
|
|
709
|
+
let paymentRequiredBody;
|
|
1640
710
|
if (response.status === 402) {
|
|
1641
711
|
try {
|
|
1642
|
-
const
|
|
1643
|
-
|
|
712
|
+
const headerValue = response.headers.get("payment-required");
|
|
713
|
+
const parsed = headerValue !== null ? parsePaymentRequiredBody2(headerValue) : await parsePaymentRequiredBody(response);
|
|
714
|
+
if (parsed !== null) {
|
|
715
|
+
paymentRequiredBody = parsed;
|
|
716
|
+
authHint = detectAuthHint(paymentRequiredBody);
|
|
717
|
+
}
|
|
1644
718
|
} catch {
|
|
1645
719
|
}
|
|
1646
720
|
}
|
|
1647
|
-
const
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
"runtime_verified"
|
|
1661
|
-
)
|
|
1662
|
-
);
|
|
1663
|
-
} catch (error) {
|
|
1664
|
-
warnings.push(
|
|
1665
|
-
warning(
|
|
1666
|
-
"FETCH_FAILED",
|
|
1667
|
-
"info",
|
|
1668
|
-
`Probe ${method} ${path} failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1669
|
-
{ stage: "probe" }
|
|
1670
|
-
)
|
|
1671
|
-
);
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
return {
|
|
1676
|
-
stage: "probe",
|
|
1677
|
-
valid: resources.length > 0,
|
|
1678
|
-
resources,
|
|
1679
|
-
warnings
|
|
1680
|
-
};
|
|
721
|
+
const protocols = detectProtocols(response);
|
|
722
|
+
const wwwAuthenticate = response.headers.get("www-authenticate") ?? void 0;
|
|
723
|
+
return {
|
|
724
|
+
path,
|
|
725
|
+
method,
|
|
726
|
+
authHint,
|
|
727
|
+
...protocols.length ? { protocols } : {},
|
|
728
|
+
...paymentRequiredBody !== void 0 ? { paymentRequiredBody } : {},
|
|
729
|
+
...wwwAuthenticate ? { wwwAuthenticate } : {}
|
|
730
|
+
};
|
|
731
|
+
})()
|
|
732
|
+
);
|
|
733
|
+
});
|
|
1681
734
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
function computeUpgradeSignal(warnings) {
|
|
1695
|
-
const reasons = warnings.map((entry) => entry.code).filter((code, index, list) => list.indexOf(code) === index).filter((code) => UPGRADE_WARNING_CODE_SET.has(code));
|
|
1696
|
-
return {
|
|
1697
|
-
upgradeSuggested: reasons.length > 0,
|
|
1698
|
-
upgradeReasons: reasons
|
|
1699
|
-
};
|
|
735
|
+
function getProbe(url, headers, signal, inputBody) {
|
|
736
|
+
const path = normalizePath(new URL(url).pathname || "/");
|
|
737
|
+
return import_neverthrow4.ResultAsync.fromSafePromise(
|
|
738
|
+
Promise.all(
|
|
739
|
+
[...HTTP_METHODS].map(
|
|
740
|
+
(method) => probeMethod(url, method, path, headers, signal, inputBody).match(
|
|
741
|
+
(result) => result,
|
|
742
|
+
() => null
|
|
743
|
+
)
|
|
744
|
+
)
|
|
745
|
+
).then((results) => results.filter((r) => r !== null))
|
|
746
|
+
);
|
|
1700
747
|
}
|
|
1701
748
|
|
|
1702
|
-
// src/core/
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
continue;
|
|
1727
|
-
}
|
|
1728
|
-
target.set(resource.resourceKey, {
|
|
1729
|
-
...existing,
|
|
1730
|
-
summary: existing.summary ?? resource.summary,
|
|
1731
|
-
links: { ...resource.links, ...existing.links },
|
|
1732
|
-
confidence: Math.max(existing.confidence ?? 0, resource.confidence ?? 0)
|
|
1733
|
-
});
|
|
1734
|
-
}
|
|
1735
|
-
return warnings;
|
|
1736
|
-
}
|
|
1737
|
-
function toTrace(stageResult, startedAt) {
|
|
1738
|
-
return {
|
|
1739
|
-
stage: stageResult.stage,
|
|
1740
|
-
attempted: true,
|
|
1741
|
-
valid: stageResult.valid,
|
|
1742
|
-
resourceCount: stageResult.resources.length,
|
|
1743
|
-
durationMs: Date.now() - startedAt,
|
|
1744
|
-
warnings: stageResult.warnings,
|
|
1745
|
-
...stageResult.links ? { links: stageResult.links } : {}
|
|
1746
|
-
};
|
|
1747
|
-
}
|
|
1748
|
-
async function runOverrideStage(options) {
|
|
1749
|
-
const warnings = [];
|
|
1750
|
-
for (const overrideUrl of options.overrideUrls) {
|
|
1751
|
-
const normalized = overrideUrl.trim();
|
|
1752
|
-
if (!normalized) continue;
|
|
749
|
+
// src/core/protocols/mpp/index.ts
|
|
750
|
+
var TEMPO_DEFAULT_CHAIN_ID = 4217;
|
|
751
|
+
function parseAuthParams(segment) {
|
|
752
|
+
const params = {};
|
|
753
|
+
const re = /(\w+)=(?:"([^"]*)"|'([^']*)')/g;
|
|
754
|
+
let match;
|
|
755
|
+
while ((match = re.exec(segment)) !== null) {
|
|
756
|
+
params[match[1]] = match[2] ?? match[3] ?? "";
|
|
757
|
+
}
|
|
758
|
+
return params;
|
|
759
|
+
}
|
|
760
|
+
function extractPaymentOptions4(wwwAuthenticate) {
|
|
761
|
+
if (!isMmmEnabled() || !wwwAuthenticate) return [];
|
|
762
|
+
const options = [];
|
|
763
|
+
for (const segment of wwwAuthenticate.split(/,\s*(?=Payment\s)/i)) {
|
|
764
|
+
const stripped = segment.replace(/^Payment\s+/i, "").trim();
|
|
765
|
+
const params = parseAuthParams(stripped);
|
|
766
|
+
const paymentMethod = params["method"];
|
|
767
|
+
const intent = params["intent"];
|
|
768
|
+
const realm = params["realm"];
|
|
769
|
+
const description = params["description"];
|
|
770
|
+
const requestStr = params["request"];
|
|
771
|
+
if (!paymentMethod || !intent || !realm || !requestStr) continue;
|
|
772
|
+
let request;
|
|
1753
773
|
try {
|
|
1754
|
-
const
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
});
|
|
1759
|
-
if (!response.ok) {
|
|
1760
|
-
warnings.push(
|
|
1761
|
-
warning(
|
|
1762
|
-
"FETCH_FAILED",
|
|
1763
|
-
"warn",
|
|
1764
|
-
`Override fetch failed (${response.status}) at ${normalized}`,
|
|
1765
|
-
{
|
|
1766
|
-
stage: "override"
|
|
1767
|
-
}
|
|
1768
|
-
)
|
|
1769
|
-
);
|
|
1770
|
-
continue;
|
|
1771
|
-
}
|
|
1772
|
-
const bodyText = await response.text();
|
|
1773
|
-
let parsedJson;
|
|
1774
|
-
try {
|
|
1775
|
-
parsedJson = JSON.parse(bodyText);
|
|
1776
|
-
} catch {
|
|
1777
|
-
parsedJson = void 0;
|
|
1778
|
-
}
|
|
1779
|
-
if (parsedJson && typeof parsedJson === "object" && !Array.isArray(parsedJson) && "openapi" in parsedJson && "paths" in parsedJson) {
|
|
1780
|
-
const openapiResult = await runOpenApiStage({
|
|
1781
|
-
origin: options.origin,
|
|
1782
|
-
fetcher: async (input, init) => {
|
|
1783
|
-
const inputString = String(input);
|
|
1784
|
-
if (inputString === `${options.origin}/openapi.json` || inputString === `${options.origin}/.well-known/openapi.json`) {
|
|
1785
|
-
return new Response(bodyText, {
|
|
1786
|
-
status: 200,
|
|
1787
|
-
headers: { "content-type": "application/json" }
|
|
1788
|
-
});
|
|
1789
|
-
}
|
|
1790
|
-
return options.fetcher(input, init);
|
|
1791
|
-
},
|
|
1792
|
-
headers: options.headers,
|
|
1793
|
-
signal: options.signal,
|
|
1794
|
-
includeRaw: options.includeRaw
|
|
1795
|
-
});
|
|
1796
|
-
return {
|
|
1797
|
-
...openapiResult,
|
|
1798
|
-
stage: "override",
|
|
1799
|
-
warnings: [
|
|
1800
|
-
warning("OVERRIDE_USED", "info", `Using explicit override source ${normalized}`, {
|
|
1801
|
-
stage: "override"
|
|
1802
|
-
}),
|
|
1803
|
-
...openapiResult.warnings
|
|
1804
|
-
]
|
|
1805
|
-
};
|
|
1806
|
-
}
|
|
1807
|
-
if (parsedJson && typeof parsedJson === "object" && !Array.isArray(parsedJson)) {
|
|
1808
|
-
const parsedWellKnown = parseWellKnownPayload(parsedJson, options.origin, normalized);
|
|
1809
|
-
if (parsedWellKnown.resources.length > 0) {
|
|
1810
|
-
return {
|
|
1811
|
-
stage: "override",
|
|
1812
|
-
valid: true,
|
|
1813
|
-
resources: parsedWellKnown.resources,
|
|
1814
|
-
warnings: [
|
|
1815
|
-
warning("OVERRIDE_USED", "info", `Using explicit override source ${normalized}`, {
|
|
1816
|
-
stage: "override"
|
|
1817
|
-
}),
|
|
1818
|
-
...parsedWellKnown.warnings
|
|
1819
|
-
],
|
|
1820
|
-
links: { discoveryUrl: normalized },
|
|
1821
|
-
...options.includeRaw ? { raw: parsedWellKnown.raw } : {}
|
|
1822
|
-
};
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
} catch (error) {
|
|
1826
|
-
warnings.push(
|
|
1827
|
-
warning(
|
|
1828
|
-
"FETCH_FAILED",
|
|
1829
|
-
"warn",
|
|
1830
|
-
`Override fetch exception at ${normalized}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1831
|
-
{ stage: "override" }
|
|
1832
|
-
)
|
|
1833
|
-
);
|
|
774
|
+
const parsed = JSON.parse(requestStr);
|
|
775
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) continue;
|
|
776
|
+
request = parsed;
|
|
777
|
+
} catch {
|
|
1834
778
|
continue;
|
|
1835
779
|
}
|
|
1836
|
-
const
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
780
|
+
const asset = typeof request["currency"] === "string" ? request["currency"] : void 0;
|
|
781
|
+
const amountRaw = request["amount"];
|
|
782
|
+
const amount = typeof amountRaw === "string" ? amountRaw : typeof amountRaw === "number" ? String(amountRaw) : void 0;
|
|
783
|
+
const decimals = typeof request["decimals"] === "number" ? request["decimals"] : void 0;
|
|
784
|
+
const payTo = typeof request["recipient"] === "string" ? request["recipient"] : void 0;
|
|
785
|
+
const methodDetails = request["methodDetails"];
|
|
786
|
+
const chainId = methodDetails !== null && typeof methodDetails === "object" && !Array.isArray(methodDetails) && typeof methodDetails["chainId"] === "number" ? methodDetails["chainId"] : TEMPO_DEFAULT_CHAIN_ID;
|
|
787
|
+
if (!asset || !amount) continue;
|
|
788
|
+
options.push({
|
|
789
|
+
protocol: "mpp",
|
|
790
|
+
// isMmmEnabled
|
|
791
|
+
paymentMethod,
|
|
792
|
+
intent,
|
|
793
|
+
realm,
|
|
794
|
+
network: `tempo:${String(chainId)}`,
|
|
795
|
+
// isMmmEnabled
|
|
796
|
+
asset,
|
|
797
|
+
amount,
|
|
798
|
+
...decimals != null ? { decimals } : {},
|
|
799
|
+
...payTo ? { payTo } : {},
|
|
800
|
+
...description ? { description } : {}
|
|
1843
801
|
});
|
|
1844
|
-
if (fallbackWellKnownResult.valid) {
|
|
1845
|
-
return {
|
|
1846
|
-
...fallbackWellKnownResult,
|
|
1847
|
-
stage: "override",
|
|
1848
|
-
warnings: [
|
|
1849
|
-
warning("OVERRIDE_USED", "info", `Using explicit override source ${normalized}`, {
|
|
1850
|
-
stage: "override"
|
|
1851
|
-
}),
|
|
1852
|
-
...fallbackWellKnownResult.warnings
|
|
1853
|
-
]
|
|
1854
|
-
};
|
|
1855
|
-
}
|
|
1856
|
-
warnings.push(...fallbackWellKnownResult.warnings);
|
|
1857
802
|
}
|
|
1858
|
-
return
|
|
1859
|
-
stage: "override",
|
|
1860
|
-
valid: false,
|
|
1861
|
-
resources: [],
|
|
1862
|
-
warnings
|
|
1863
|
-
};
|
|
803
|
+
return options;
|
|
1864
804
|
}
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
const
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
const warnings = [];
|
|
1877
|
-
const trace = [];
|
|
1878
|
-
const resourceWarnings = {};
|
|
1879
|
-
const rawSources = {};
|
|
1880
|
-
const merged = /* @__PURE__ */ new Map();
|
|
1881
|
-
if (compatMode !== "off") {
|
|
1882
|
-
warnings.push(
|
|
1883
|
-
warning(
|
|
1884
|
-
"COMPAT_MODE_ENABLED",
|
|
1885
|
-
compatMode === "strict" ? "warn" : "info",
|
|
1886
|
-
`Compatibility mode is '${compatMode}'. Legacy adapters are active.`
|
|
1887
|
-
)
|
|
1888
|
-
);
|
|
1889
|
-
}
|
|
1890
|
-
const stages = [];
|
|
1891
|
-
if (options.overrideUrls && options.overrideUrls.length > 0) {
|
|
1892
|
-
stages.push(
|
|
1893
|
-
() => runOverrideStage({
|
|
1894
|
-
origin,
|
|
1895
|
-
overrideUrls: options.overrideUrls ?? [],
|
|
1896
|
-
fetcher,
|
|
1897
|
-
headers: options.headers,
|
|
1898
|
-
signal: options.signal,
|
|
1899
|
-
includeRaw
|
|
1900
|
-
})
|
|
1901
|
-
);
|
|
1902
|
-
}
|
|
1903
|
-
stages.push(
|
|
1904
|
-
() => runOpenApiStage({
|
|
1905
|
-
origin,
|
|
1906
|
-
fetcher,
|
|
1907
|
-
headers: options.headers,
|
|
1908
|
-
signal: options.signal,
|
|
1909
|
-
includeRaw
|
|
1910
|
-
})
|
|
1911
|
-
);
|
|
1912
|
-
if (compatMode !== "off") {
|
|
1913
|
-
stages.push(
|
|
1914
|
-
() => runWellKnownX402Stage({
|
|
1915
|
-
origin,
|
|
1916
|
-
fetcher,
|
|
1917
|
-
headers: options.headers,
|
|
1918
|
-
signal: options.signal,
|
|
1919
|
-
includeRaw
|
|
1920
|
-
})
|
|
1921
|
-
);
|
|
1922
|
-
stages.push(
|
|
1923
|
-
() => runDnsStage({
|
|
1924
|
-
origin,
|
|
1925
|
-
fetcher,
|
|
1926
|
-
txtResolver: options.txtResolver,
|
|
1927
|
-
headers: options.headers,
|
|
1928
|
-
signal: options.signal,
|
|
1929
|
-
includeRaw
|
|
1930
|
-
})
|
|
1931
|
-
);
|
|
1932
|
-
if (includeInteropMpp && isMmmEnabled()) {
|
|
1933
|
-
stages.push(
|
|
1934
|
-
() => runInteropMppStage({
|
|
1935
|
-
origin,
|
|
1936
|
-
fetcher,
|
|
1937
|
-
headers: options.headers,
|
|
1938
|
-
signal: options.signal,
|
|
1939
|
-
includeRaw
|
|
1940
|
-
})
|
|
1941
|
-
);
|
|
805
|
+
|
|
806
|
+
// src/core/layers/l3.ts
|
|
807
|
+
function findMatchingOpenApiPath(paths, targetPath) {
|
|
808
|
+
const exact = paths[targetPath];
|
|
809
|
+
if (isRecord(exact)) return { matchedPath: targetPath, pathItem: exact };
|
|
810
|
+
for (const [specPath, entry] of Object.entries(paths)) {
|
|
811
|
+
if (!isRecord(entry)) continue;
|
|
812
|
+
const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
|
|
813
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
814
|
+
if (regex.test(targetPath)) {
|
|
815
|
+
return { matchedPath: specPath, pathItem: entry };
|
|
1942
816
|
}
|
|
1943
817
|
}
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
)
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
function resolveRef(document, ref, seen) {
|
|
821
|
+
if (!ref.startsWith("#/")) return void 0;
|
|
822
|
+
if (seen.has(ref)) return { $circular: ref };
|
|
823
|
+
seen.add(ref);
|
|
824
|
+
const parts = ref.slice(2).split("/");
|
|
825
|
+
let current = document;
|
|
826
|
+
for (const part of parts) {
|
|
827
|
+
if (!isRecord(current)) return void 0;
|
|
828
|
+
current = current[part];
|
|
829
|
+
if (current === void 0) return void 0;
|
|
830
|
+
}
|
|
831
|
+
if (isRecord(current)) return resolveRefs(document, current, seen);
|
|
832
|
+
return current;
|
|
833
|
+
}
|
|
834
|
+
function resolveRefs(document, obj, seen, depth = 0) {
|
|
835
|
+
if (depth > 4) return obj;
|
|
836
|
+
const resolved = {};
|
|
837
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
838
|
+
if (key === "$ref" && typeof value === "string") {
|
|
839
|
+
const deref = resolveRef(document, value, seen);
|
|
840
|
+
if (isRecord(deref)) {
|
|
841
|
+
Object.assign(resolved, deref);
|
|
842
|
+
} else {
|
|
843
|
+
resolved[key] = value;
|
|
1968
844
|
}
|
|
845
|
+
continue;
|
|
1969
846
|
}
|
|
1970
|
-
if (
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
if (rawObject.openapi !== void 0) rawSources.openapi = rawObject.openapi;
|
|
1974
|
-
if (typeof rawObject.llmsTxt === "string") rawSources.llmsTxt = rawObject.llmsTxt;
|
|
1975
|
-
}
|
|
1976
|
-
if (stageResult.stage === "well-known/x402" || stageResult.stage === "override") {
|
|
1977
|
-
rawSources.wellKnownX402 = [...rawSources.wellKnownX402 ?? [], stageResult.raw];
|
|
1978
|
-
}
|
|
1979
|
-
if (stageResult.stage === "dns/_x402") {
|
|
1980
|
-
const rawObject = stageResult.raw;
|
|
1981
|
-
if (Array.isArray(rawObject.dnsRecords)) {
|
|
1982
|
-
rawSources.dnsRecords = rawObject.dnsRecords;
|
|
1983
|
-
}
|
|
1984
|
-
}
|
|
1985
|
-
if (stageResult.stage === "interop/mpp") {
|
|
1986
|
-
rawSources.interopMpp = stageResult.raw;
|
|
1987
|
-
}
|
|
847
|
+
if (isRecord(value)) {
|
|
848
|
+
resolved[key] = resolveRefs(document, value, seen, depth + 1);
|
|
849
|
+
continue;
|
|
1988
850
|
}
|
|
1989
|
-
if (
|
|
851
|
+
if (Array.isArray(value)) {
|
|
852
|
+
resolved[key] = value.map(
|
|
853
|
+
(item) => isRecord(item) ? resolveRefs(document, item, seen, depth + 1) : item
|
|
854
|
+
);
|
|
1990
855
|
continue;
|
|
1991
856
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
857
|
+
resolved[key] = value;
|
|
858
|
+
}
|
|
859
|
+
return resolved;
|
|
860
|
+
}
|
|
861
|
+
function extractRequestBodySchema(operationSchema) {
|
|
862
|
+
const requestBody = operationSchema.requestBody;
|
|
863
|
+
if (!isRecord(requestBody)) return void 0;
|
|
864
|
+
const content = requestBody.content;
|
|
865
|
+
if (!isRecord(content)) return void 0;
|
|
866
|
+
const jsonMediaType = content["application/json"];
|
|
867
|
+
if (isRecord(jsonMediaType) && isRecord(jsonMediaType.schema)) {
|
|
868
|
+
return jsonMediaType.schema;
|
|
869
|
+
}
|
|
870
|
+
for (const mediaType of Object.values(content)) {
|
|
871
|
+
if (isRecord(mediaType) && isRecord(mediaType.schema)) {
|
|
872
|
+
return mediaType.schema;
|
|
1997
873
|
}
|
|
1998
874
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
const
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
if (!detailed) {
|
|
2012
|
-
return {
|
|
2013
|
-
origin,
|
|
2014
|
-
resources,
|
|
2015
|
-
warnings: dedupedWarnings,
|
|
2016
|
-
compatMode,
|
|
2017
|
-
...upgradeSignal,
|
|
2018
|
-
...selectedStage ? { selectedStage } : {}
|
|
2019
|
-
};
|
|
2020
|
-
}
|
|
875
|
+
return void 0;
|
|
876
|
+
}
|
|
877
|
+
function extractParameters(operationSchema) {
|
|
878
|
+
const params = operationSchema.parameters;
|
|
879
|
+
if (!Array.isArray(params)) return [];
|
|
880
|
+
return params.filter((value) => isRecord(value));
|
|
881
|
+
}
|
|
882
|
+
function extractInputSchema(operationSchema) {
|
|
883
|
+
const requestBody = extractRequestBodySchema(operationSchema);
|
|
884
|
+
const parameters = extractParameters(operationSchema);
|
|
885
|
+
if (!requestBody && parameters.length === 0) return void 0;
|
|
886
|
+
if (requestBody && parameters.length === 0) return requestBody;
|
|
2021
887
|
return {
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
warnings: dedupedWarnings,
|
|
2025
|
-
compatMode,
|
|
2026
|
-
...upgradeSignal,
|
|
2027
|
-
selectedStage: trace.find((entry) => entry.valid)?.stage,
|
|
2028
|
-
trace,
|
|
2029
|
-
resourceWarnings,
|
|
2030
|
-
...includeRaw ? { rawSources } : {}
|
|
888
|
+
...requestBody ? { requestBody } : {},
|
|
889
|
+
...parameters.length > 0 ? { parameters } : {}
|
|
2031
890
|
};
|
|
2032
891
|
}
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
surface: "mcp",
|
|
2044
|
-
defaultContextWindowTokens: 2e5,
|
|
2045
|
-
zeroHopBudgetPercent: 0.1,
|
|
2046
|
-
notes: "Targets environments where MCP context can use roughly 10% of total context."
|
|
2047
|
-
},
|
|
2048
|
-
"skill-cli": {
|
|
2049
|
-
id: "skill-cli",
|
|
2050
|
-
label: "Skill + CLI Harness",
|
|
2051
|
-
surface: "skill-cli",
|
|
2052
|
-
defaultContextWindowTokens: 2e5,
|
|
2053
|
-
zeroHopBudgetPercent: 0.02,
|
|
2054
|
-
notes: "Targets constrained skill contexts with title/description-heavy routing."
|
|
2055
|
-
},
|
|
2056
|
-
"generic-mcp": {
|
|
2057
|
-
id: "generic-mcp",
|
|
2058
|
-
label: "Generic MCP Harness",
|
|
2059
|
-
surface: "mcp",
|
|
2060
|
-
defaultContextWindowTokens: 128e3,
|
|
2061
|
-
zeroHopBudgetPercent: 0.05,
|
|
2062
|
-
notes: "Conservative MCP profile when specific client budgets are unknown."
|
|
2063
|
-
},
|
|
2064
|
-
generic: {
|
|
2065
|
-
id: "generic",
|
|
2066
|
-
label: "Generic Agent Harness",
|
|
2067
|
-
surface: "generic",
|
|
2068
|
-
defaultContextWindowTokens: 128e3,
|
|
2069
|
-
zeroHopBudgetPercent: 0.03,
|
|
2070
|
-
notes: "Fallback profile for unknown clients."
|
|
892
|
+
function parseOperationPrice(operation) {
|
|
893
|
+
const paymentInfo = operation["x-payment-info"];
|
|
894
|
+
if (!isRecord(paymentInfo)) return void 0;
|
|
895
|
+
function toFiniteNumber(value) {
|
|
896
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
897
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
898
|
+
const parsed = Number(value);
|
|
899
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
900
|
+
}
|
|
901
|
+
return void 0;
|
|
2071
902
|
}
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
const
|
|
2075
|
-
|
|
903
|
+
const fixed = toFiniteNumber(paymentInfo.price);
|
|
904
|
+
if (fixed != null) return `$${String(fixed)}`;
|
|
905
|
+
const min = toFiniteNumber(paymentInfo.minPrice);
|
|
906
|
+
const max = toFiniteNumber(paymentInfo.maxPrice);
|
|
907
|
+
if (min != null && max != null) return `$${String(min)}-$${String(max)}`;
|
|
908
|
+
return void 0;
|
|
2076
909
|
}
|
|
2077
|
-
function
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
910
|
+
function parseOperationProtocols(operation) {
|
|
911
|
+
const paymentInfo = operation["x-payment-info"];
|
|
912
|
+
if (!isRecord(paymentInfo) || !Array.isArray(paymentInfo.protocols)) return void 0;
|
|
913
|
+
const protocols = paymentInfo.protocols.filter(
|
|
914
|
+
(protocol) => typeof protocol === "string" && protocol.length > 0 && (protocol !== "mpp" || isMmmEnabled())
|
|
915
|
+
);
|
|
916
|
+
return protocols.length > 0 ? protocols : void 0;
|
|
917
|
+
}
|
|
918
|
+
function getL3ForOpenAPI(openApi, path, method) {
|
|
919
|
+
const document = openApi.raw;
|
|
920
|
+
const paths = isRecord(document.paths) ? document.paths : void 0;
|
|
921
|
+
if (!paths) return null;
|
|
922
|
+
const matched = findMatchingOpenApiPath(paths, path);
|
|
923
|
+
if (!matched) return null;
|
|
924
|
+
const operation = matched.pathItem[method.toLowerCase()];
|
|
925
|
+
if (!isRecord(operation)) return null;
|
|
926
|
+
const resolvedOperation = resolveRefs(document, operation, /* @__PURE__ */ new Set());
|
|
927
|
+
const summary = typeof resolvedOperation.summary === "string" ? resolvedOperation.summary : typeof resolvedOperation.description === "string" ? resolvedOperation.description : void 0;
|
|
928
|
+
return {
|
|
929
|
+
source: "openapi",
|
|
930
|
+
...summary ? { summary } : {},
|
|
931
|
+
authMode: inferAuthMode(resolvedOperation) ?? void 0,
|
|
932
|
+
estimatedPrice: parseOperationPrice(resolvedOperation),
|
|
933
|
+
protocols: parseOperationProtocols(resolvedOperation),
|
|
934
|
+
inputSchema: extractInputSchema(resolvedOperation),
|
|
935
|
+
outputSchema: resolvedOperation
|
|
936
|
+
};
|
|
2083
937
|
}
|
|
2084
|
-
function
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
938
|
+
function getL3ForProbe(probe, path, method) {
|
|
939
|
+
const probeResult = probe.find((r) => r.path === path && r.method === method);
|
|
940
|
+
if (!probeResult) return null;
|
|
941
|
+
const inputSchema = probeResult.paymentRequiredBody ? parseInputSchema(probeResult.paymentRequiredBody) : void 0;
|
|
942
|
+
const outputSchema = probeResult.paymentRequiredBody ? parseOutputSchema(probeResult.paymentRequiredBody) : void 0;
|
|
943
|
+
const paymentOptions = [
|
|
944
|
+
...probeResult.paymentRequiredBody ? extractPaymentOptions3(probeResult.paymentRequiredBody) : [],
|
|
945
|
+
...isMmmEnabled() ? extractPaymentOptions4(probeResult.wwwAuthenticate) : []
|
|
946
|
+
// isMmmEnabled
|
|
947
|
+
];
|
|
948
|
+
return {
|
|
949
|
+
source: "probe",
|
|
950
|
+
authMode: probeResult.authHint,
|
|
951
|
+
...probeResult.protocols?.length ? { protocols: probeResult.protocols } : {},
|
|
952
|
+
...inputSchema ? { inputSchema } : {},
|
|
953
|
+
...outputSchema ? { outputSchema } : {},
|
|
954
|
+
...paymentOptions.length ? { paymentOptions } : {}
|
|
2097
955
|
};
|
|
2098
|
-
for (const resource of resources) {
|
|
2099
|
-
const mode = resource.authHint;
|
|
2100
|
-
if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
|
|
2101
|
-
counts[mode] += 1;
|
|
2102
|
-
} else {
|
|
2103
|
-
counts.unknown += 1;
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
return counts;
|
|
2107
956
|
}
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
}
|
|
2114
|
-
|
|
2115
|
-
);
|
|
957
|
+
|
|
958
|
+
// src/runtime/check-endpoint.ts
|
|
959
|
+
function getAdvisoriesForOpenAPI(openApi, path) {
|
|
960
|
+
return [...HTTP_METHODS].flatMap((method) => {
|
|
961
|
+
const l3 = getL3ForOpenAPI(openApi, path, method);
|
|
962
|
+
return l3 ? [{ method, ...l3 }] : [];
|
|
963
|
+
});
|
|
2116
964
|
}
|
|
2117
|
-
function
|
|
2118
|
-
return [...
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
}).map((resource) => ({
|
|
2123
|
-
resourceKey: resource.resourceKey,
|
|
2124
|
-
method: resource.method,
|
|
2125
|
-
path: resource.path,
|
|
2126
|
-
source: resource.source,
|
|
2127
|
-
authMode: resource.authHint ?? null
|
|
2128
|
-
}));
|
|
965
|
+
function getAdvisoriesForProbe(probe, path) {
|
|
966
|
+
return [...HTTP_METHODS].flatMap((method) => {
|
|
967
|
+
const l3 = getL3ForProbe(probe, path, method);
|
|
968
|
+
return l3 ? [{ method, ...l3 }] : [];
|
|
969
|
+
});
|
|
2129
970
|
}
|
|
2130
|
-
function
|
|
2131
|
-
const
|
|
2132
|
-
const
|
|
2133
|
-
|
|
971
|
+
async function checkEndpointSchema(options) {
|
|
972
|
+
const endpoint = new URL(options.url);
|
|
973
|
+
const origin = normalizeOrigin(endpoint.origin);
|
|
974
|
+
const path = normalizePath(endpoint.pathname || "/");
|
|
975
|
+
if (options.sampleInputBody !== void 0) {
|
|
976
|
+
const probeResult2 = await getProbe(
|
|
977
|
+
options.url,
|
|
978
|
+
options.headers,
|
|
979
|
+
options.signal,
|
|
980
|
+
options.sampleInputBody
|
|
981
|
+
);
|
|
982
|
+
if (probeResult2.isErr()) {
|
|
983
|
+
return {
|
|
984
|
+
found: false,
|
|
985
|
+
origin,
|
|
986
|
+
path,
|
|
987
|
+
cause: probeResult2.error.cause,
|
|
988
|
+
message: probeResult2.error.message
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
const advisories2 = getAdvisoriesForProbe(probeResult2.value, path);
|
|
992
|
+
if (advisories2.length > 0) return { found: true, origin, path, advisories: advisories2 };
|
|
993
|
+
return { found: false, origin, path, cause: "not_found" };
|
|
994
|
+
}
|
|
995
|
+
const openApiResult = await getOpenAPI(origin, options.headers, options.signal);
|
|
996
|
+
const openApi = openApiResult.isOk() ? openApiResult.value : null;
|
|
997
|
+
if (openApi) {
|
|
998
|
+
const advisories2 = getAdvisoriesForOpenAPI(openApi, path);
|
|
999
|
+
if (advisories2.length > 0) return { found: true, origin, path, advisories: advisories2 };
|
|
1000
|
+
return { found: false, origin, path, cause: "not_found" };
|
|
1001
|
+
}
|
|
1002
|
+
const probeResult = await getProbe(options.url, options.headers, options.signal);
|
|
1003
|
+
if (probeResult.isErr()) {
|
|
2134
1004
|
return {
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
1005
|
+
found: false,
|
|
1006
|
+
origin,
|
|
1007
|
+
path,
|
|
1008
|
+
cause: probeResult.error.cause,
|
|
1009
|
+
message: probeResult.error.message
|
|
2139
1010
|
};
|
|
2140
1011
|
}
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
1012
|
+
const advisories = getAdvisoriesForProbe(probeResult.value, path);
|
|
1013
|
+
if (advisories.length > 0) return { found: true, origin, path, advisories };
|
|
1014
|
+
return { found: false, origin, path, cause: "not_found" };
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// src/cli.ts
|
|
1018
|
+
function parseArgs(args) {
|
|
1019
|
+
const flags = { json: false, verbose: false };
|
|
1020
|
+
const positional = [];
|
|
1021
|
+
for (const arg of args) {
|
|
1022
|
+
if (arg === "--json") flags.json = true;
|
|
1023
|
+
else if (arg === "-v" || arg === "--verbose") flags.verbose = true;
|
|
1024
|
+
else positional.push(arg);
|
|
2149
1025
|
}
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
};
|
|
1026
|
+
const [first, second] = positional;
|
|
1027
|
+
if (first && (first.startsWith("http://") || first.startsWith("https://"))) {
|
|
1028
|
+
return { command: "discover", target: first, flags };
|
|
1029
|
+
}
|
|
1030
|
+
return { command: first ?? null, target: second ?? null, flags };
|
|
2156
1031
|
}
|
|
2157
|
-
function
|
|
2158
|
-
return CLIENT_PROFILES[client] ?? CLIENT_PROFILES.generic;
|
|
2159
|
-
}
|
|
2160
|
-
function buildContextHarnessReport(options) {
|
|
2161
|
-
const client = getHarnessClientProfile(options.client);
|
|
2162
|
-
const contextWindowTokens = options.contextWindowTokens ?? client.defaultContextWindowTokens;
|
|
2163
|
-
const zeroHopBudgetTokens = Math.floor(contextWindowTokens * client.zeroHopBudgetPercent);
|
|
2164
|
-
const hostname = safeHostname(options.result.origin);
|
|
2165
|
-
const domainClass = isFirstPartyDomain(hostname) ? "first-party" : "ugc";
|
|
2166
|
-
const l0Summary = "Route payment-capable intents to agentcash and use install/discover commands for progressive disclosure.";
|
|
2167
|
-
const l1Summary = "Expose installed domain routing hints and trigger fan-out into L2/L3 commands when user intent matches domain capabilities.";
|
|
2168
|
-
const l0EstimatedTokens = estimateTokenCount(
|
|
2169
|
-
`${INTENT_TRIGGERS.join(", ")} npx agentcash install npx agentcash install-ext`
|
|
2170
|
-
);
|
|
2171
|
-
const l1EstimatedTokens = estimateTokenCount(
|
|
2172
|
-
`${hostname} ${l1Summary} npx agentcash discover ${hostname} npx agentcash discover ${hostname} --verbose`
|
|
2173
|
-
);
|
|
2174
|
-
const llmsInfo = getLlmsTxtInfo(options.result);
|
|
1032
|
+
function routeToResource(route) {
|
|
2175
1033
|
return {
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
withinBudget: l0EstimatedTokens + l1EstimatedTokens <= zeroHopBudgetTokens
|
|
2184
|
-
},
|
|
2185
|
-
levels: {
|
|
2186
|
-
l0: {
|
|
2187
|
-
layer: "L0",
|
|
2188
|
-
intentTriggers: INTENT_TRIGGERS,
|
|
2189
|
-
installCommand: "npx agentcash install",
|
|
2190
|
-
deliverySurfaces: ["MCP", "Skill+CLI"],
|
|
2191
|
-
summary: l0Summary,
|
|
2192
|
-
estimatedTokens: l0EstimatedTokens
|
|
2193
|
-
},
|
|
2194
|
-
l1: {
|
|
2195
|
-
layer: "L1",
|
|
2196
|
-
domain: hostname,
|
|
2197
|
-
domainClass,
|
|
2198
|
-
selectedDiscoveryStage: options.result.selectedStage ?? null,
|
|
2199
|
-
summary: l1Summary,
|
|
2200
|
-
fanoutCommands: [
|
|
2201
|
-
`npx agentcash discover ${hostname}`,
|
|
2202
|
-
`npx agentcash discover ${hostname} --verbose`
|
|
2203
|
-
],
|
|
2204
|
-
estimatedTokens: l1EstimatedTokens
|
|
2205
|
-
},
|
|
2206
|
-
l2: {
|
|
2207
|
-
layer: "L2",
|
|
2208
|
-
command: `npx agentcash discover ${hostname}`,
|
|
2209
|
-
tokenLight: true,
|
|
2210
|
-
resourceCount: options.result.resources.length,
|
|
2211
|
-
resources: toL2Entries(options.result.resources)
|
|
2212
|
-
},
|
|
2213
|
-
l3: {
|
|
2214
|
-
layer: "L3",
|
|
2215
|
-
command: `npx agentcash discover ${hostname} --verbose`,
|
|
2216
|
-
detailLevel: "endpoint-schema-and-metadata",
|
|
2217
|
-
countsByAuthMode: toAuthModeCount(options.result.resources),
|
|
2218
|
-
countsBySource: toSourceCount(options.result.resources),
|
|
2219
|
-
pricedResourceCount: options.result.resources.filter(
|
|
2220
|
-
(resource) => resource.authHint === "paid"
|
|
2221
|
-
).length
|
|
2222
|
-
},
|
|
2223
|
-
l4: {
|
|
2224
|
-
layer: "L4",
|
|
2225
|
-
guidanceSource: llmsInfo.llmsTxtUrl ? "llms.txt" : "none",
|
|
2226
|
-
llmsTxtUrl: llmsInfo.llmsTxtUrl,
|
|
2227
|
-
llmsTxtTokenEstimate: llmsInfo.llmsTxtTokenEstimate,
|
|
2228
|
-
guidancePreview: llmsInfo.guidancePreview,
|
|
2229
|
-
guidanceStatus: llmsInfo.guidanceStatus
|
|
2230
|
-
},
|
|
2231
|
-
l5: {
|
|
2232
|
-
layer: "L5",
|
|
2233
|
-
status: "out_of_scope",
|
|
2234
|
-
note: "Cross-domain composition is intentionally out of scope for discovery v1."
|
|
2235
|
-
}
|
|
2236
|
-
},
|
|
2237
|
-
diagnostics: {
|
|
2238
|
-
warningCount: options.result.warnings.length,
|
|
2239
|
-
errorWarningCount: options.result.warnings.filter((warning2) => warning2.severity === "error").length,
|
|
2240
|
-
selectedStage: options.result.selectedStage ?? null,
|
|
2241
|
-
upgradeSuggested: options.result.upgradeSuggested,
|
|
2242
|
-
upgradeReasons: options.result.upgradeReasons
|
|
2243
|
-
}
|
|
1034
|
+
resourceKey: `${route.method} ${route.path}`,
|
|
1035
|
+
method: route.method,
|
|
1036
|
+
path: route.path,
|
|
1037
|
+
summary: route.summary,
|
|
1038
|
+
...route.authMode ? { authHint: route.authMode } : {},
|
|
1039
|
+
...route.price ? { priceHint: route.price } : {},
|
|
1040
|
+
...route.protocols?.length ? { protocols: route.protocols } : {}
|
|
2244
1041
|
};
|
|
2245
1042
|
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
1043
|
+
async function main(args) {
|
|
1044
|
+
const { command, target, flags } = parseArgs(args);
|
|
1045
|
+
if (command === "discover" && target) return runDiscover(target, flags);
|
|
1046
|
+
if (command === "check" && target) return runCheck(target, flags);
|
|
1047
|
+
console.log("Usage:");
|
|
1048
|
+
console.log(" discovery <origin> Discover all endpoints at an origin");
|
|
1049
|
+
console.log(" discovery discover <origin> Discover all endpoints at an origin");
|
|
1050
|
+
console.log(" discovery check <url> Inspect a specific endpoint URL");
|
|
1051
|
+
console.log("");
|
|
1052
|
+
console.log("Flags:");
|
|
1053
|
+
console.log(" --json Machine-readable JSON output");
|
|
1054
|
+
console.log(" -v Verbose output (includes guidance text and warning hints)");
|
|
1055
|
+
return 1;
|
|
1056
|
+
}
|
|
1057
|
+
async function runDiscover(target, flags) {
|
|
1058
|
+
const origin = normalizeOrigin(target);
|
|
1059
|
+
if (!flags.json) console.log(`
|
|
1060
|
+
Discovering ${origin}...
|
|
1061
|
+
`);
|
|
1062
|
+
const [openApiResult, wellKnownResult] = await Promise.all([
|
|
1063
|
+
getOpenAPI(origin),
|
|
1064
|
+
getWellKnown(origin)
|
|
1065
|
+
]);
|
|
1066
|
+
const openApi = openApiResult.isOk() ? openApiResult.value : null;
|
|
1067
|
+
const wellKnown = wellKnownResult.isOk() ? wellKnownResult.value : null;
|
|
1068
|
+
const warnings = [
|
|
1069
|
+
...getWarningsForOpenAPI(openApi),
|
|
1070
|
+
...getWarningsForWellKnown(wellKnown)
|
|
1071
|
+
];
|
|
1072
|
+
if (openApi) {
|
|
1073
|
+
const l2 = checkL2ForOpenAPI(openApi);
|
|
1074
|
+
const l4 = checkL4ForOpenAPI(openApi);
|
|
1075
|
+
warnings.push(...getWarningsForL2(l2), ...getWarningsForL4(l4));
|
|
1076
|
+
if (flags.json) {
|
|
1077
|
+
const meta = {
|
|
1078
|
+
origin,
|
|
1079
|
+
specUrl: openApi.fetchedUrl,
|
|
1080
|
+
...l2.title ? { title: l2.title } : {},
|
|
1081
|
+
...l2.description ? { description: l2.description } : {}
|
|
1082
|
+
};
|
|
1083
|
+
if (l4) {
|
|
1084
|
+
meta.guidanceTokens = Math.ceil(l4.guidance.length / 4);
|
|
1085
|
+
if (flags.verbose) meta.guidance = l4.guidance;
|
|
2272
1086
|
}
|
|
2273
|
-
|
|
1087
|
+
console.log(
|
|
1088
|
+
JSON.stringify(
|
|
1089
|
+
{
|
|
1090
|
+
ok: true,
|
|
1091
|
+
selectedStage: "openapi",
|
|
1092
|
+
resources: l2.routes.map(routeToResource),
|
|
1093
|
+
warnings,
|
|
1094
|
+
trace: [],
|
|
1095
|
+
meta
|
|
1096
|
+
},
|
|
1097
|
+
null,
|
|
1098
|
+
2
|
|
1099
|
+
)
|
|
1100
|
+
);
|
|
1101
|
+
return 0;
|
|
2274
1102
|
}
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
1103
|
+
console.log(`Source: openapi`);
|
|
1104
|
+
console.log(`Spec: ${openApi.fetchedUrl}`);
|
|
1105
|
+
if (l2.title) console.log(`API: ${l2.title}`);
|
|
1106
|
+
console.log(`Routes: ${l2.routes.length}
|
|
1107
|
+
`);
|
|
1108
|
+
for (const route of l2.routes) {
|
|
1109
|
+
const auth = route.authMode ? ` ${route.authMode}` : "";
|
|
1110
|
+
const price = route.price ? ` ${route.price}` : "";
|
|
1111
|
+
const protocols = route.protocols?.length ? ` [${route.protocols.join(", ")}]` : "";
|
|
1112
|
+
console.log(` ${route.method.padEnd(7)} ${route.path}${auth}${price}${protocols}`);
|
|
2283
1113
|
}
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
const baseOptions = {
|
|
2291
|
-
target: options.target,
|
|
2292
|
-
compatMode: options.compatMode,
|
|
2293
|
-
fetcher,
|
|
2294
|
-
headers: {
|
|
2295
|
-
"user-agent": CLI_USER_AGENT
|
|
2296
|
-
},
|
|
2297
|
-
rawView: options.harness ? "full" : "none"
|
|
2298
|
-
};
|
|
2299
|
-
const startedAt = Date.now();
|
|
2300
|
-
let result = await discoverDetailedFn(baseOptions);
|
|
2301
|
-
let probeCandidateCount = 0;
|
|
2302
|
-
if (options.probe) {
|
|
2303
|
-
const probeCandidates = buildProbeCandidates(result.resources);
|
|
2304
|
-
probeCandidateCount = probeCandidates.length;
|
|
2305
|
-
if (probeCandidates.length > 0) {
|
|
2306
|
-
result = await discoverDetailedFn({
|
|
2307
|
-
...baseOptions,
|
|
2308
|
-
probeCandidates
|
|
2309
|
-
});
|
|
1114
|
+
if (l4) {
|
|
1115
|
+
const tokens = Math.ceil(l4.guidance.length / 4);
|
|
1116
|
+
console.log(`
|
|
1117
|
+
Guidance: ${tokens} tokens`);
|
|
1118
|
+
if (flags.verbose) console.log(`
|
|
1119
|
+
${l4.guidance}`);
|
|
2310
1120
|
}
|
|
2311
|
-
}
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
1121
|
+
} else if (wellKnown) {
|
|
1122
|
+
const l2 = checkL2ForWellknown(wellKnown);
|
|
1123
|
+
const l4 = checkL4ForWellknown(wellKnown);
|
|
1124
|
+
warnings.push(...getWarningsForL2(l2), ...getWarningsForL4(l4));
|
|
1125
|
+
if (flags.json) {
|
|
1126
|
+
const meta = { origin, specUrl: wellKnown.fetchedUrl };
|
|
1127
|
+
if (l4 && flags.verbose) meta.guidance = l4.guidance;
|
|
1128
|
+
console.log(
|
|
1129
|
+
JSON.stringify(
|
|
1130
|
+
{
|
|
1131
|
+
ok: true,
|
|
1132
|
+
selectedStage: "well-known/x402",
|
|
1133
|
+
resources: l2.routes.map(routeToResource),
|
|
1134
|
+
warnings,
|
|
1135
|
+
trace: [],
|
|
1136
|
+
meta
|
|
1137
|
+
},
|
|
1138
|
+
null,
|
|
1139
|
+
2
|
|
1140
|
+
)
|
|
1141
|
+
);
|
|
1142
|
+
return 0;
|
|
1143
|
+
}
|
|
1144
|
+
console.log(`Source: well-known/x402`);
|
|
1145
|
+
console.log(`Spec: ${wellKnown.fetchedUrl}`);
|
|
1146
|
+
console.log(`Routes: ${l2.routes.length}
|
|
2330
1147
|
`);
|
|
1148
|
+
for (const route of l2.routes) {
|
|
1149
|
+
console.log(` ${route.method.padEnd(7)} ${route.path} paid [x402]`);
|
|
1150
|
+
}
|
|
1151
|
+
} else {
|
|
1152
|
+
if (flags.json) {
|
|
1153
|
+
console.log(
|
|
1154
|
+
JSON.stringify(
|
|
1155
|
+
{ ok: false, selectedStage: null, resources: [], warnings, trace: [], meta: { origin } },
|
|
1156
|
+
null,
|
|
1157
|
+
2
|
|
1158
|
+
)
|
|
1159
|
+
);
|
|
1160
|
+
return 0;
|
|
1161
|
+
}
|
|
1162
|
+
console.log("Not found \u2014 no OpenAPI spec or /.well-known/x402 at this origin.");
|
|
1163
|
+
}
|
|
1164
|
+
printWarnings(warnings, flags.verbose);
|
|
1165
|
+
return 0;
|
|
2331
1166
|
}
|
|
2332
|
-
function
|
|
2333
|
-
|
|
1167
|
+
async function runCheck(url, flags) {
|
|
1168
|
+
if (!flags.json) console.log(`
|
|
1169
|
+
Checking ${url}...
|
|
2334
1170
|
`);
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
stderr(parsed.message);
|
|
2346
|
-
stderr("");
|
|
2347
|
-
stderr(renderHelp());
|
|
2348
|
-
return 2;
|
|
1171
|
+
const result = await checkEndpointSchema({ url });
|
|
1172
|
+
if (!result.found) {
|
|
1173
|
+
const warnings2 = getWarningsForL3(null);
|
|
1174
|
+
if (flags.json) {
|
|
1175
|
+
console.log(JSON.stringify({ url, found: false, warnings: warnings2 }, null, 2));
|
|
1176
|
+
return 1;
|
|
1177
|
+
}
|
|
1178
|
+
console.log("Not found \u2014 no spec data for this endpoint.");
|
|
1179
|
+
printWarnings(warnings2, flags.verbose);
|
|
1180
|
+
return 1;
|
|
2349
1181
|
}
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
} else if (parsed.options.verbose) {
|
|
2355
|
-
stdout(renderVerbose(run, parsed.options));
|
|
2356
|
-
} else {
|
|
2357
|
-
stdout(renderSummary(run, parsed.options));
|
|
1182
|
+
const warnings = [];
|
|
1183
|
+
if (flags.json) {
|
|
1184
|
+
for (const advisory of result.advisories) {
|
|
1185
|
+
warnings.push(...getWarningsForL3(advisory));
|
|
2358
1186
|
}
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
1187
|
+
console.log(
|
|
1188
|
+
JSON.stringify(
|
|
1189
|
+
{
|
|
1190
|
+
url,
|
|
1191
|
+
found: true,
|
|
1192
|
+
origin: result.origin,
|
|
1193
|
+
path: result.path,
|
|
1194
|
+
advisories: result.advisories,
|
|
1195
|
+
warnings
|
|
1196
|
+
},
|
|
1197
|
+
null,
|
|
1198
|
+
2
|
|
1199
|
+
)
|
|
1200
|
+
);
|
|
1201
|
+
return 0;
|
|
1202
|
+
}
|
|
1203
|
+
console.log(`Origin: ${result.origin}`);
|
|
1204
|
+
console.log(`Path: ${result.path}
|
|
1205
|
+
`);
|
|
1206
|
+
for (const advisory of result.advisories) {
|
|
1207
|
+
const auth = advisory.authMode ? ` ${advisory.authMode}` : "";
|
|
1208
|
+
const price = advisory.estimatedPrice ? ` ${advisory.estimatedPrice}` : "";
|
|
1209
|
+
const protocols = advisory.protocols?.length ? ` [${advisory.protocols.join(", ")}]` : "";
|
|
1210
|
+
console.log(` ${advisory.method.padEnd(7)}${auth}${price}${protocols}`);
|
|
1211
|
+
warnings.push(...getWarningsForL3(advisory));
|
|
1212
|
+
}
|
|
1213
|
+
printWarnings(warnings, flags.verbose);
|
|
1214
|
+
return 0;
|
|
1215
|
+
}
|
|
1216
|
+
function printWarnings(warnings, verbose) {
|
|
1217
|
+
if (warnings.length === 0) return;
|
|
1218
|
+
console.log(`
|
|
1219
|
+
Warnings (${warnings.length}):`);
|
|
1220
|
+
for (const w of warnings) {
|
|
1221
|
+
const loc = w.path ? ` (${w.path})` : "";
|
|
1222
|
+
const hint = verbose && w.hint ? `
|
|
1223
|
+
Hint: ${w.hint}` : "";
|
|
1224
|
+
console.log(` [${w.severity.padEnd(4)}] ${w.code}${loc}
|
|
1225
|
+
${w.message}${hint}`);
|
|
2364
1226
|
}
|
|
2365
1227
|
}
|
|
2366
1228
|
// Annotate the CommonJS export names for ESM import in node:
|