@agentcash/discovery 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/CLAUDE.md +24 -0
- package/AGENTS.md +23 -0
- package/LICENSE +21 -0
- package/README.md +115 -0
- package/bin/discovery.js +6 -0
- package/dist/cli.cjs +1825 -0
- package/dist/cli.d.cts +15 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +1798 -0
- package/dist/flags.cjs +44 -0
- package/dist/flags.d.cts +6 -0
- package/dist/flags.d.ts +6 -0
- package/dist/flags.js +17 -0
- package/dist/index.cjs +1448 -0
- package/dist/index.d.cts +106 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.js +1415 -0
- package/dist/schemas.cjs +58 -0
- package/dist/schemas.d.cts +40 -0
- package/dist/schemas.d.ts +40 -0
- package/dist/schemas.js +28 -0
- package/package.json +95 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1415 @@
|
|
|
1
|
+
// src/flags.ts
|
|
2
|
+
var LEGACY_SUNSET_DATE = "2026-03-24";
|
|
3
|
+
var DEFAULT_COMPAT_MODE = "on";
|
|
4
|
+
var STRICT_ESCALATION_CODES = [
|
|
5
|
+
"LEGACY_WELL_KNOWN_USED",
|
|
6
|
+
"LEGACY_DNS_USED",
|
|
7
|
+
"LEGACY_DNS_PLAIN_URL",
|
|
8
|
+
"LEGACY_MISSING_METHOD",
|
|
9
|
+
"LEGACY_INSTRUCTIONS_USED",
|
|
10
|
+
"LEGACY_OWNERSHIP_PROOFS_USED",
|
|
11
|
+
"INTEROP_MPP_USED"
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
// src/core/constants.ts
|
|
15
|
+
var OPENAPI_PATH_CANDIDATES = ["/openapi.json", "/.well-known/openapi.json"];
|
|
16
|
+
var WELL_KNOWN_MPP_PATH = "/.well-known/mpp";
|
|
17
|
+
var LLMS_TOKEN_WARNING_THRESHOLD = 2500;
|
|
18
|
+
var HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
19
|
+
"GET",
|
|
20
|
+
"POST",
|
|
21
|
+
"PUT",
|
|
22
|
+
"DELETE",
|
|
23
|
+
"PATCH",
|
|
24
|
+
"HEAD",
|
|
25
|
+
"OPTIONS",
|
|
26
|
+
"TRACE"
|
|
27
|
+
]);
|
|
28
|
+
var DEFAULT_PROBE_METHODS = ["GET", "POST"];
|
|
29
|
+
var DEFAULT_MISSING_METHOD = "POST";
|
|
30
|
+
|
|
31
|
+
// src/core/url.ts
|
|
32
|
+
function normalizeOrigin(target) {
|
|
33
|
+
const trimmed = target.trim();
|
|
34
|
+
const withProtocol = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
35
|
+
const url = new URL(withProtocol);
|
|
36
|
+
url.pathname = "";
|
|
37
|
+
url.search = "";
|
|
38
|
+
url.hash = "";
|
|
39
|
+
return url.toString().replace(/\/$/, "");
|
|
40
|
+
}
|
|
41
|
+
function normalizePath(pathname) {
|
|
42
|
+
const parsed = pathname.trim();
|
|
43
|
+
if (parsed.length === 0 || parsed === "/") return "/";
|
|
44
|
+
const pathOnly = parsed.split("?")[0]?.split("#")[0] ?? "/";
|
|
45
|
+
const prefixed = pathOnly.startsWith("/") ? pathOnly : `/${pathOnly}`;
|
|
46
|
+
const normalized = prefixed.replace(/\/+/g, "/");
|
|
47
|
+
return normalized !== "/" ? normalized.replace(/\/$/, "") : "/";
|
|
48
|
+
}
|
|
49
|
+
function toResourceKey(origin, method, path) {
|
|
50
|
+
return `${origin} ${method} ${normalizePath(path)}`;
|
|
51
|
+
}
|
|
52
|
+
function parseMethod(value) {
|
|
53
|
+
if (!value) return void 0;
|
|
54
|
+
const upper = value.toUpperCase();
|
|
55
|
+
return HTTP_METHODS.has(upper) ? upper : void 0;
|
|
56
|
+
}
|
|
57
|
+
function toAbsoluteUrl(origin, value) {
|
|
58
|
+
try {
|
|
59
|
+
if (/^https?:\/\//i.test(value)) return new URL(value);
|
|
60
|
+
return new URL(normalizePath(value), `${origin}/`);
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/core/warnings.ts
|
|
67
|
+
function warning(code, severity, message, options) {
|
|
68
|
+
return {
|
|
69
|
+
code,
|
|
70
|
+
severity,
|
|
71
|
+
message,
|
|
72
|
+
...options?.hint ? { hint: options.hint } : {},
|
|
73
|
+
...options?.stage ? { stage: options.stage } : {},
|
|
74
|
+
...options?.resourceKey ? { resourceKey: options.resourceKey } : {}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function applyStrictEscalation(warnings, strict) {
|
|
78
|
+
if (!strict) return warnings;
|
|
79
|
+
return warnings.map((entry) => {
|
|
80
|
+
if (!STRICT_ESCALATION_CODES.includes(entry.code)) {
|
|
81
|
+
return entry;
|
|
82
|
+
}
|
|
83
|
+
if (entry.severity === "error") return entry;
|
|
84
|
+
return {
|
|
85
|
+
...entry,
|
|
86
|
+
severity: "error",
|
|
87
|
+
message: `${entry.message} (strict mode escalated)`
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function dedupeWarnings(warnings) {
|
|
92
|
+
const seen = /* @__PURE__ */ new Set();
|
|
93
|
+
const output = [];
|
|
94
|
+
for (const item of warnings) {
|
|
95
|
+
const key = `${item.code}|${item.severity}|${item.stage ?? ""}|${item.resourceKey ?? ""}|${item.message}`;
|
|
96
|
+
if (seen.has(key)) continue;
|
|
97
|
+
seen.add(key);
|
|
98
|
+
output.push(item);
|
|
99
|
+
}
|
|
100
|
+
return output;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/core/normalize.ts
|
|
104
|
+
function createResource(input, confidence, trustTier) {
|
|
105
|
+
const normalizedOrigin = normalizeOrigin(input.origin);
|
|
106
|
+
const normalizedPath = normalizePath(input.path);
|
|
107
|
+
return {
|
|
108
|
+
resourceKey: toResourceKey(normalizedOrigin, input.method, normalizedPath),
|
|
109
|
+
origin: normalizedOrigin,
|
|
110
|
+
method: input.method,
|
|
111
|
+
path: normalizedPath,
|
|
112
|
+
source: input.source,
|
|
113
|
+
verified: false,
|
|
114
|
+
...input.protocolHints?.length ? { protocolHints: [...new Set(input.protocolHints)] } : {},
|
|
115
|
+
...input.priceHint ? { priceHint: input.priceHint } : {},
|
|
116
|
+
...input.pricing ? { pricing: input.pricing } : {},
|
|
117
|
+
...input.authHint ? { authHint: input.authHint } : {},
|
|
118
|
+
...input.summary ? { summary: input.summary } : {},
|
|
119
|
+
confidence,
|
|
120
|
+
trustTier,
|
|
121
|
+
...input.links ? { links: input.links } : {}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/compat/legacy-x402scan/wellKnown.ts
|
|
126
|
+
function isRecord(value) {
|
|
127
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
128
|
+
}
|
|
129
|
+
function parseLegacyResourceEntry(entry) {
|
|
130
|
+
const trimmed = entry.trim();
|
|
131
|
+
if (!trimmed) return null;
|
|
132
|
+
const parts = trimmed.split(/\s+/);
|
|
133
|
+
if (parts.length >= 2) {
|
|
134
|
+
const maybeMethod = parseMethod(parts[0]);
|
|
135
|
+
if (maybeMethod) {
|
|
136
|
+
const target = parts.slice(1).join(" ");
|
|
137
|
+
return { method: maybeMethod, target };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (trimmed.startsWith("/") || /^https?:\/\//i.test(trimmed)) {
|
|
141
|
+
return { target: trimmed };
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
function parseWellKnownPayload(payload, origin, sourceUrl) {
|
|
146
|
+
const warnings = [
|
|
147
|
+
warning(
|
|
148
|
+
"LEGACY_WELL_KNOWN_USED",
|
|
149
|
+
"warn",
|
|
150
|
+
"Using legacy /.well-known/x402 compatibility path. Migrate to OpenAPI-first.",
|
|
151
|
+
{
|
|
152
|
+
stage: "well-known/x402"
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
];
|
|
156
|
+
if (!isRecord(payload)) {
|
|
157
|
+
warnings.push(
|
|
158
|
+
warning("PARSE_FAILED", "error", "Legacy well-known payload is not an object", {
|
|
159
|
+
stage: "well-known/x402"
|
|
160
|
+
})
|
|
161
|
+
);
|
|
162
|
+
return { resources: [], warnings, raw: payload };
|
|
163
|
+
}
|
|
164
|
+
const resourcesRaw = Array.isArray(payload.resources) ? payload.resources.filter((entry) => typeof entry === "string") : [];
|
|
165
|
+
if (resourcesRaw.length === 0) {
|
|
166
|
+
warnings.push(
|
|
167
|
+
warning("STAGE_EMPTY", "warn", "Legacy well-known has no valid resources array", {
|
|
168
|
+
stage: "well-known/x402"
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
const instructions = typeof payload.instructions === "string" ? payload.instructions : void 0;
|
|
173
|
+
const ownershipProofs = Array.isArray(payload.ownershipProofs) ? payload.ownershipProofs.filter((entry) => typeof entry === "string") : [];
|
|
174
|
+
if (instructions) {
|
|
175
|
+
warnings.push(
|
|
176
|
+
warning(
|
|
177
|
+
"LEGACY_INSTRUCTIONS_USED",
|
|
178
|
+
"warn",
|
|
179
|
+
"Using /.well-known/x402.instructions as compatibility guidance fallback. Prefer llms.txt.",
|
|
180
|
+
{
|
|
181
|
+
stage: "well-known/x402",
|
|
182
|
+
hint: "Move guidance to llms.txt and reference via x-agentcash-guidance.llmsTxtUrl."
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
if (ownershipProofs.length > 0) {
|
|
188
|
+
warnings.push(
|
|
189
|
+
warning(
|
|
190
|
+
"LEGACY_OWNERSHIP_PROOFS_USED",
|
|
191
|
+
"warn",
|
|
192
|
+
"Using /.well-known/x402.ownershipProofs compatibility field. Prefer OpenAPI provenance extension.",
|
|
193
|
+
{
|
|
194
|
+
stage: "well-known/x402",
|
|
195
|
+
hint: "Move ownership proofs to x-agentcash-provenance.ownershipProofs in OpenAPI."
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
const resources = [];
|
|
201
|
+
for (const rawEntry of resourcesRaw) {
|
|
202
|
+
const parsed = parseLegacyResourceEntry(rawEntry);
|
|
203
|
+
if (!parsed) {
|
|
204
|
+
warnings.push(
|
|
205
|
+
warning("PARSE_FAILED", "warn", `Invalid legacy resource entry: ${rawEntry}`, {
|
|
206
|
+
stage: "well-known/x402"
|
|
207
|
+
})
|
|
208
|
+
);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const absolute = toAbsoluteUrl(origin, parsed.target);
|
|
212
|
+
if (!absolute) {
|
|
213
|
+
warnings.push(
|
|
214
|
+
warning("PARSE_FAILED", "warn", `Invalid legacy resource URL: ${rawEntry}`, {
|
|
215
|
+
stage: "well-known/x402"
|
|
216
|
+
})
|
|
217
|
+
);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const method = parsed.method ?? DEFAULT_MISSING_METHOD;
|
|
221
|
+
if (!parsed.method) {
|
|
222
|
+
warnings.push(
|
|
223
|
+
warning(
|
|
224
|
+
"LEGACY_MISSING_METHOD",
|
|
225
|
+
"warn",
|
|
226
|
+
`Legacy resource '${rawEntry}' missing method. Defaulting to ${DEFAULT_MISSING_METHOD}.`,
|
|
227
|
+
{
|
|
228
|
+
stage: "well-known/x402"
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
const path = normalizePath(absolute.pathname);
|
|
234
|
+
resources.push(
|
|
235
|
+
createResource(
|
|
236
|
+
{
|
|
237
|
+
origin: absolute.origin,
|
|
238
|
+
method,
|
|
239
|
+
path,
|
|
240
|
+
source: "well-known/x402",
|
|
241
|
+
summary: `${method} ${path}`,
|
|
242
|
+
authHint: "paid",
|
|
243
|
+
protocolHints: ["x402"],
|
|
244
|
+
links: {
|
|
245
|
+
wellKnownUrl: sourceUrl,
|
|
246
|
+
discoveryUrl: sourceUrl
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
0.65,
|
|
250
|
+
ownershipProofs.length > 0 ? "ownership_verified" : "origin_hosted"
|
|
251
|
+
)
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
resources,
|
|
256
|
+
warnings,
|
|
257
|
+
raw: payload
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
async function runWellKnownX402Stage(options) {
|
|
261
|
+
const stageUrl = options.url ?? `${options.origin}/.well-known/x402`;
|
|
262
|
+
try {
|
|
263
|
+
const response = await options.fetcher(stageUrl, {
|
|
264
|
+
method: "GET",
|
|
265
|
+
headers: { Accept: "application/json", ...options.headers },
|
|
266
|
+
signal: options.signal
|
|
267
|
+
});
|
|
268
|
+
if (!response.ok) {
|
|
269
|
+
if (response.status === 404) {
|
|
270
|
+
return {
|
|
271
|
+
stage: "well-known/x402",
|
|
272
|
+
valid: false,
|
|
273
|
+
resources: [],
|
|
274
|
+
warnings: [],
|
|
275
|
+
links: { wellKnownUrl: stageUrl }
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
stage: "well-known/x402",
|
|
280
|
+
valid: false,
|
|
281
|
+
resources: [],
|
|
282
|
+
warnings: [
|
|
283
|
+
warning("FETCH_FAILED", "warn", `Legacy well-known fetch failed (${response.status})`, {
|
|
284
|
+
stage: "well-known/x402"
|
|
285
|
+
})
|
|
286
|
+
],
|
|
287
|
+
links: { wellKnownUrl: stageUrl }
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
const payload = await response.json();
|
|
291
|
+
const parsed = parseWellKnownPayload(payload, options.origin, stageUrl);
|
|
292
|
+
return {
|
|
293
|
+
stage: "well-known/x402",
|
|
294
|
+
valid: parsed.resources.length > 0,
|
|
295
|
+
resources: parsed.resources,
|
|
296
|
+
warnings: parsed.warnings,
|
|
297
|
+
links: { wellKnownUrl: stageUrl },
|
|
298
|
+
...options.includeRaw ? { raw: parsed.raw } : {}
|
|
299
|
+
};
|
|
300
|
+
} catch (error) {
|
|
301
|
+
return {
|
|
302
|
+
stage: "well-known/x402",
|
|
303
|
+
valid: false,
|
|
304
|
+
resources: [],
|
|
305
|
+
warnings: [
|
|
306
|
+
warning(
|
|
307
|
+
"FETCH_FAILED",
|
|
308
|
+
"warn",
|
|
309
|
+
`Legacy well-known fetch exception: ${error instanceof Error ? error.message : String(error)}`,
|
|
310
|
+
{
|
|
311
|
+
stage: "well-known/x402"
|
|
312
|
+
}
|
|
313
|
+
)
|
|
314
|
+
],
|
|
315
|
+
links: { wellKnownUrl: stageUrl }
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/compat/legacy-x402scan/dns.ts
|
|
321
|
+
function parseDnsRecord(record) {
|
|
322
|
+
const trimmed = record.trim();
|
|
323
|
+
if (!trimmed) return null;
|
|
324
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
325
|
+
return { url: trimmed, legacyPlainUrl: true };
|
|
326
|
+
}
|
|
327
|
+
const parts = trimmed.split(";").map((entry) => entry.trim()).filter(Boolean);
|
|
328
|
+
const keyValues = /* @__PURE__ */ new Map();
|
|
329
|
+
for (const part of parts) {
|
|
330
|
+
const separator = part.indexOf("=");
|
|
331
|
+
if (separator <= 0) continue;
|
|
332
|
+
const key = part.slice(0, separator).trim().toLowerCase();
|
|
333
|
+
const value = part.slice(separator + 1).trim();
|
|
334
|
+
if (key && value) keyValues.set(key, value);
|
|
335
|
+
}
|
|
336
|
+
if (keyValues.get("v") !== "x4021") return null;
|
|
337
|
+
const url = keyValues.get("url");
|
|
338
|
+
if (!url || !/^https?:\/\//i.test(url)) return null;
|
|
339
|
+
return { url, legacyPlainUrl: false };
|
|
340
|
+
}
|
|
341
|
+
async function runDnsStage(options) {
|
|
342
|
+
if (!options.txtResolver) {
|
|
343
|
+
return {
|
|
344
|
+
stage: "dns/_x402",
|
|
345
|
+
valid: false,
|
|
346
|
+
resources: [],
|
|
347
|
+
warnings: []
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
const origin = normalizeOrigin(options.origin);
|
|
351
|
+
const hostname = new URL(origin).hostname;
|
|
352
|
+
const fqdn = `_x402.${hostname}`;
|
|
353
|
+
let records;
|
|
354
|
+
try {
|
|
355
|
+
records = await options.txtResolver(fqdn);
|
|
356
|
+
} catch (error) {
|
|
357
|
+
return {
|
|
358
|
+
stage: "dns/_x402",
|
|
359
|
+
valid: false,
|
|
360
|
+
resources: [],
|
|
361
|
+
warnings: [
|
|
362
|
+
warning(
|
|
363
|
+
"FETCH_FAILED",
|
|
364
|
+
"warn",
|
|
365
|
+
`DNS TXT lookup failed for ${fqdn}: ${error instanceof Error ? error.message : String(error)}`,
|
|
366
|
+
{ stage: "dns/_x402" }
|
|
367
|
+
)
|
|
368
|
+
]
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
if (records.length === 0) {
|
|
372
|
+
return {
|
|
373
|
+
stage: "dns/_x402",
|
|
374
|
+
valid: false,
|
|
375
|
+
resources: [],
|
|
376
|
+
warnings: []
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
const warnings = [
|
|
380
|
+
warning(
|
|
381
|
+
"LEGACY_DNS_USED",
|
|
382
|
+
"warn",
|
|
383
|
+
"Using DNS _x402 compatibility path. Migrate to OpenAPI-first discovery.",
|
|
384
|
+
{
|
|
385
|
+
stage: "dns/_x402"
|
|
386
|
+
}
|
|
387
|
+
)
|
|
388
|
+
];
|
|
389
|
+
const urls = [];
|
|
390
|
+
for (const record of records) {
|
|
391
|
+
const parsed = parseDnsRecord(record);
|
|
392
|
+
if (!parsed) {
|
|
393
|
+
warnings.push(
|
|
394
|
+
warning("PARSE_FAILED", "warn", `Invalid DNS _x402 TXT record: ${record}`, {
|
|
395
|
+
stage: "dns/_x402"
|
|
396
|
+
})
|
|
397
|
+
);
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
urls.push(parsed.url);
|
|
401
|
+
if (parsed.legacyPlainUrl) {
|
|
402
|
+
warnings.push(
|
|
403
|
+
warning("LEGACY_DNS_PLAIN_URL", "warn", `Legacy plain URL TXT format used: ${record}`, {
|
|
404
|
+
stage: "dns/_x402",
|
|
405
|
+
hint: "Use v=x4021;url=<https-url> format."
|
|
406
|
+
})
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (urls.length === 0) {
|
|
411
|
+
return {
|
|
412
|
+
stage: "dns/_x402",
|
|
413
|
+
valid: false,
|
|
414
|
+
resources: [],
|
|
415
|
+
warnings,
|
|
416
|
+
...options.includeRaw ? { raw: { dnsRecords: records } } : {}
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
const mergedWarnings = [...warnings];
|
|
420
|
+
const mergedResources = [];
|
|
421
|
+
const rawDocuments = [];
|
|
422
|
+
for (const url of urls) {
|
|
423
|
+
const stageResult = await runWellKnownX402Stage({
|
|
424
|
+
origin,
|
|
425
|
+
url,
|
|
426
|
+
fetcher: options.fetcher,
|
|
427
|
+
headers: options.headers,
|
|
428
|
+
signal: options.signal,
|
|
429
|
+
includeRaw: options.includeRaw
|
|
430
|
+
});
|
|
431
|
+
mergedResources.push(...stageResult.resources);
|
|
432
|
+
mergedWarnings.push(...stageResult.warnings);
|
|
433
|
+
if (options.includeRaw && stageResult.raw !== void 0) {
|
|
434
|
+
rawDocuments.push({ url, document: stageResult.raw });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
438
|
+
for (const resource of mergedResources) {
|
|
439
|
+
if (!deduped.has(resource.resourceKey)) {
|
|
440
|
+
deduped.set(resource.resourceKey, {
|
|
441
|
+
...resource,
|
|
442
|
+
source: "dns/_x402",
|
|
443
|
+
links: {
|
|
444
|
+
...resource.links,
|
|
445
|
+
discoveryUrl: resource.links?.discoveryUrl
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
stage: "dns/_x402",
|
|
452
|
+
valid: deduped.size > 0,
|
|
453
|
+
resources: [...deduped.values()],
|
|
454
|
+
warnings: mergedWarnings,
|
|
455
|
+
...options.includeRaw ? { raw: { dnsRecords: records, documents: rawDocuments } } : {}
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/compat/interop-mpp/wellKnownMpp.ts
|
|
460
|
+
function isRecord2(value) {
|
|
461
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
462
|
+
}
|
|
463
|
+
async function runInteropMppStage(options) {
|
|
464
|
+
const url = `${options.origin}${WELL_KNOWN_MPP_PATH}`;
|
|
465
|
+
try {
|
|
466
|
+
const response = await options.fetcher(url, {
|
|
467
|
+
method: "GET",
|
|
468
|
+
headers: { Accept: "application/json", ...options.headers },
|
|
469
|
+
signal: options.signal
|
|
470
|
+
});
|
|
471
|
+
if (!response.ok) {
|
|
472
|
+
return {
|
|
473
|
+
stage: "interop/mpp",
|
|
474
|
+
valid: false,
|
|
475
|
+
resources: [],
|
|
476
|
+
warnings: []
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
const payload = await response.json();
|
|
480
|
+
if (!isRecord2(payload)) {
|
|
481
|
+
return {
|
|
482
|
+
stage: "interop/mpp",
|
|
483
|
+
valid: false,
|
|
484
|
+
resources: [],
|
|
485
|
+
warnings: [
|
|
486
|
+
warning("PARSE_FAILED", "warn", "Interop /.well-known/mpp payload is not an object", {
|
|
487
|
+
stage: "interop/mpp"
|
|
488
|
+
})
|
|
489
|
+
],
|
|
490
|
+
...options.includeRaw ? { raw: payload } : {}
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
const warnings = [
|
|
494
|
+
warning(
|
|
495
|
+
"INTEROP_MPP_USED",
|
|
496
|
+
"info",
|
|
497
|
+
"Using /.well-known/mpp interop adapter. This is additive and non-canonical.",
|
|
498
|
+
{
|
|
499
|
+
stage: "interop/mpp",
|
|
500
|
+
hint: "Use for registry indexing only, not canonical runtime routing."
|
|
501
|
+
}
|
|
502
|
+
)
|
|
503
|
+
];
|
|
504
|
+
const resources = [];
|
|
505
|
+
const fromStringResources = Array.isArray(payload.resources) ? payload.resources.filter((entry) => typeof entry === "string") : [];
|
|
506
|
+
for (const entry of fromStringResources) {
|
|
507
|
+
const parts = entry.trim().split(/\s+/);
|
|
508
|
+
let method = parseMethod(parts[0]);
|
|
509
|
+
let path = parts.join(" ");
|
|
510
|
+
if (method) {
|
|
511
|
+
path = parts.slice(1).join(" ");
|
|
512
|
+
} else {
|
|
513
|
+
method = "POST";
|
|
514
|
+
}
|
|
515
|
+
const normalizedPath = normalizePath(path);
|
|
516
|
+
resources.push(
|
|
517
|
+
createResource(
|
|
518
|
+
{
|
|
519
|
+
origin: options.origin,
|
|
520
|
+
method,
|
|
521
|
+
path: normalizedPath,
|
|
522
|
+
source: "interop/mpp",
|
|
523
|
+
summary: `${method} ${normalizedPath}`,
|
|
524
|
+
authHint: "paid",
|
|
525
|
+
protocolHints: ["mpp"],
|
|
526
|
+
links: { discoveryUrl: url }
|
|
527
|
+
},
|
|
528
|
+
0.45,
|
|
529
|
+
"unverified"
|
|
530
|
+
)
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
const fromServiceObjects = Array.isArray(payload.services) ? payload.services.filter((entry) => isRecord2(entry)) : [];
|
|
534
|
+
for (const service of fromServiceObjects) {
|
|
535
|
+
const path = typeof service.path === "string" ? service.path : void 0;
|
|
536
|
+
const method = parseMethod(typeof service.method === "string" ? service.method : void 0) ?? "POST";
|
|
537
|
+
if (!path) continue;
|
|
538
|
+
const normalizedPath = normalizePath(path);
|
|
539
|
+
resources.push(
|
|
540
|
+
createResource(
|
|
541
|
+
{
|
|
542
|
+
origin: options.origin,
|
|
543
|
+
method,
|
|
544
|
+
path: normalizedPath,
|
|
545
|
+
source: "interop/mpp",
|
|
546
|
+
summary: typeof service.summary === "string" ? service.summary : `${method} ${normalizedPath}`,
|
|
547
|
+
authHint: "paid",
|
|
548
|
+
protocolHints: ["mpp"],
|
|
549
|
+
links: { discoveryUrl: url }
|
|
550
|
+
},
|
|
551
|
+
0.45,
|
|
552
|
+
"unverified"
|
|
553
|
+
)
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
557
|
+
for (const resource of resources) {
|
|
558
|
+
if (!deduped.has(resource.resourceKey)) deduped.set(resource.resourceKey, resource);
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
stage: "interop/mpp",
|
|
562
|
+
valid: deduped.size > 0,
|
|
563
|
+
resources: [...deduped.values()],
|
|
564
|
+
warnings,
|
|
565
|
+
...options.includeRaw ? { raw: payload } : {}
|
|
566
|
+
};
|
|
567
|
+
} catch (error) {
|
|
568
|
+
return {
|
|
569
|
+
stage: "interop/mpp",
|
|
570
|
+
valid: false,
|
|
571
|
+
resources: [],
|
|
572
|
+
warnings: [
|
|
573
|
+
warning(
|
|
574
|
+
"FETCH_FAILED",
|
|
575
|
+
"warn",
|
|
576
|
+
`Interop /.well-known/mpp fetch failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
577
|
+
{
|
|
578
|
+
stage: "interop/mpp"
|
|
579
|
+
}
|
|
580
|
+
)
|
|
581
|
+
]
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/core/token.ts
|
|
587
|
+
function estimateTokenCount(text) {
|
|
588
|
+
return Math.ceil(text.length / 4);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/core/openapi.ts
|
|
592
|
+
function isRecord3(value) {
|
|
593
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
594
|
+
}
|
|
595
|
+
function asString(value) {
|
|
596
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
597
|
+
}
|
|
598
|
+
function parsePriceValue(value) {
|
|
599
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
600
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
601
|
+
return void 0;
|
|
602
|
+
}
|
|
603
|
+
function require402Response(operation) {
|
|
604
|
+
const responses = operation.responses;
|
|
605
|
+
if (!isRecord3(responses)) return false;
|
|
606
|
+
return Boolean(responses["402"]);
|
|
607
|
+
}
|
|
608
|
+
function parseProtocols(paymentInfo) {
|
|
609
|
+
const protocols = paymentInfo.protocols;
|
|
610
|
+
if (!Array.isArray(protocols)) return [];
|
|
611
|
+
return protocols.filter(
|
|
612
|
+
(entry) => typeof entry === "string" && entry.length > 0
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
function parseAuthMode(operation) {
|
|
616
|
+
const auth = operation["x-agentcash-auth"];
|
|
617
|
+
if (!isRecord3(auth)) return void 0;
|
|
618
|
+
const mode = auth.mode;
|
|
619
|
+
if (mode === "paid" || mode === "siwx" || mode === "apiKey" || mode === "unprotected") {
|
|
620
|
+
return mode;
|
|
621
|
+
}
|
|
622
|
+
return void 0;
|
|
623
|
+
}
|
|
624
|
+
async function runOpenApiStage(options) {
|
|
625
|
+
const warnings = [];
|
|
626
|
+
const resources = [];
|
|
627
|
+
let fetchedUrl;
|
|
628
|
+
let document;
|
|
629
|
+
for (const path of OPENAPI_PATH_CANDIDATES) {
|
|
630
|
+
const url = `${options.origin}${path}`;
|
|
631
|
+
try {
|
|
632
|
+
const response = await options.fetcher(url, {
|
|
633
|
+
method: "GET",
|
|
634
|
+
headers: { Accept: "application/json", ...options.headers },
|
|
635
|
+
signal: options.signal
|
|
636
|
+
});
|
|
637
|
+
if (!response.ok) {
|
|
638
|
+
if (response.status !== 404) {
|
|
639
|
+
warnings.push(
|
|
640
|
+
warning("FETCH_FAILED", "warn", `OpenAPI fetch failed (${response.status}) at ${url}`, {
|
|
641
|
+
stage: "openapi"
|
|
642
|
+
})
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
const payload = await response.json();
|
|
648
|
+
if (!isRecord3(payload)) {
|
|
649
|
+
warnings.push(
|
|
650
|
+
warning("PARSE_FAILED", "error", `OpenAPI payload at ${url} is not a JSON object`, {
|
|
651
|
+
stage: "openapi"
|
|
652
|
+
})
|
|
653
|
+
);
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
document = payload;
|
|
657
|
+
fetchedUrl = url;
|
|
658
|
+
break;
|
|
659
|
+
} catch (error) {
|
|
660
|
+
warnings.push(
|
|
661
|
+
warning(
|
|
662
|
+
"FETCH_FAILED",
|
|
663
|
+
"warn",
|
|
664
|
+
`OpenAPI fetch exception at ${url}: ${error instanceof Error ? error.message : String(error)}`,
|
|
665
|
+
{ stage: "openapi" }
|
|
666
|
+
)
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
if (!document || !fetchedUrl) {
|
|
671
|
+
return {
|
|
672
|
+
stage: "openapi",
|
|
673
|
+
valid: false,
|
|
674
|
+
resources: [],
|
|
675
|
+
warnings
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
const hasTopLevel = typeof document.openapi === "string" && isRecord3(document.info) && typeof document.info.title === "string" && typeof document.info.version === "string" && isRecord3(document.paths);
|
|
679
|
+
if (!hasTopLevel) {
|
|
680
|
+
warnings.push(
|
|
681
|
+
warning("OPENAPI_TOP_LEVEL_INVALID", "error", "OpenAPI required fields are missing", {
|
|
682
|
+
stage: "openapi"
|
|
683
|
+
})
|
|
684
|
+
);
|
|
685
|
+
return {
|
|
686
|
+
stage: "openapi",
|
|
687
|
+
valid: false,
|
|
688
|
+
resources: [],
|
|
689
|
+
warnings,
|
|
690
|
+
links: { openapiUrl: fetchedUrl },
|
|
691
|
+
...options.includeRaw ? { raw: document } : {}
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
const paths = document.paths;
|
|
695
|
+
const provenance = isRecord3(document["x-agentcash-provenance"]) ? document["x-agentcash-provenance"] : void 0;
|
|
696
|
+
const ownershipProofs = Array.isArray(provenance?.ownershipProofs) ? provenance.ownershipProofs.filter((entry) => typeof entry === "string") : [];
|
|
697
|
+
const guidance = isRecord3(document["x-agentcash-guidance"]) ? document["x-agentcash-guidance"] : void 0;
|
|
698
|
+
const llmsTxtUrl = asString(guidance?.llmsTxtUrl);
|
|
699
|
+
if (ownershipProofs.length > 0) {
|
|
700
|
+
warnings.push(
|
|
701
|
+
warning("OPENAPI_OWNERSHIP_PROOFS_PRESENT", "info", "OpenAPI ownership proofs detected", {
|
|
702
|
+
stage: "openapi"
|
|
703
|
+
})
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
for (const [rawPath, rawPathItem] of Object.entries(paths)) {
|
|
707
|
+
if (!isRecord3(rawPathItem)) {
|
|
708
|
+
warnings.push(
|
|
709
|
+
warning("OPENAPI_OPERATION_INVALID", "warn", `Path item ${rawPath} is not an object`, {
|
|
710
|
+
stage: "openapi"
|
|
711
|
+
})
|
|
712
|
+
);
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
for (const [rawMethod, rawOperation] of Object.entries(rawPathItem)) {
|
|
716
|
+
const method = parseMethod(rawMethod);
|
|
717
|
+
if (!method) continue;
|
|
718
|
+
if (!isRecord3(rawOperation)) {
|
|
719
|
+
warnings.push(
|
|
720
|
+
warning(
|
|
721
|
+
"OPENAPI_OPERATION_INVALID",
|
|
722
|
+
"warn",
|
|
723
|
+
`${rawMethod.toUpperCase()} ${rawPath} is not an object`,
|
|
724
|
+
{
|
|
725
|
+
stage: "openapi"
|
|
726
|
+
}
|
|
727
|
+
)
|
|
728
|
+
);
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
const operation = rawOperation;
|
|
732
|
+
const summary = asString(operation.summary) ?? asString(operation.description);
|
|
733
|
+
if (!summary) {
|
|
734
|
+
warnings.push(
|
|
735
|
+
warning(
|
|
736
|
+
"OPENAPI_SUMMARY_MISSING",
|
|
737
|
+
"warn",
|
|
738
|
+
`${method} ${rawPath} missing summary/description, using fallback summary`,
|
|
739
|
+
{ stage: "openapi" }
|
|
740
|
+
)
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
const authMode = parseAuthMode(operation);
|
|
744
|
+
if (!authMode) {
|
|
745
|
+
warnings.push(
|
|
746
|
+
warning(
|
|
747
|
+
"OPENAPI_AUTH_MODE_MISSING",
|
|
748
|
+
"error",
|
|
749
|
+
`${method} ${rawPath} missing x-agentcash-auth.mode`,
|
|
750
|
+
{
|
|
751
|
+
stage: "openapi"
|
|
752
|
+
}
|
|
753
|
+
)
|
|
754
|
+
);
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
if ((authMode === "paid" || authMode === "siwx") && !require402Response(operation)) {
|
|
758
|
+
warnings.push(
|
|
759
|
+
warning(
|
|
760
|
+
"OPENAPI_402_MISSING",
|
|
761
|
+
"error",
|
|
762
|
+
`${method} ${rawPath} requires 402 response for authMode=${authMode}`,
|
|
763
|
+
{ stage: "openapi" }
|
|
764
|
+
)
|
|
765
|
+
);
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
const paymentInfo = isRecord3(operation["x-payment-info"]) ? operation["x-payment-info"] : void 0;
|
|
769
|
+
const protocols = paymentInfo ? parseProtocols(paymentInfo) : [];
|
|
770
|
+
if (authMode === "paid" && protocols.length === 0) {
|
|
771
|
+
warnings.push(
|
|
772
|
+
warning(
|
|
773
|
+
"OPENAPI_PAID_PROTOCOLS_MISSING",
|
|
774
|
+
"error",
|
|
775
|
+
`${method} ${rawPath} must define x-payment-info.protocols when authMode=paid`,
|
|
776
|
+
{ stage: "openapi" }
|
|
777
|
+
)
|
|
778
|
+
);
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
let pricing;
|
|
782
|
+
let priceHint;
|
|
783
|
+
if (paymentInfo && authMode === "paid") {
|
|
784
|
+
const pricingModeRaw = asString(paymentInfo.pricingMode);
|
|
785
|
+
const price = parsePriceValue(paymentInfo.price);
|
|
786
|
+
const minPrice = parsePriceValue(paymentInfo.minPrice);
|
|
787
|
+
const maxPrice = parsePriceValue(paymentInfo.maxPrice);
|
|
788
|
+
const inferredPricingMode = pricingModeRaw ?? (price ? "fixed" : minPrice && maxPrice ? "range" : "quote");
|
|
789
|
+
if (inferredPricingMode !== "fixed" && inferredPricingMode !== "range" && inferredPricingMode !== "quote") {
|
|
790
|
+
warnings.push(
|
|
791
|
+
warning(
|
|
792
|
+
"OPENAPI_PRICING_INVALID",
|
|
793
|
+
"error",
|
|
794
|
+
`${method} ${rawPath} has invalid pricingMode`,
|
|
795
|
+
{
|
|
796
|
+
stage: "openapi"
|
|
797
|
+
}
|
|
798
|
+
)
|
|
799
|
+
);
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
if (inferredPricingMode === "fixed" && !price) {
|
|
803
|
+
warnings.push(
|
|
804
|
+
warning(
|
|
805
|
+
"OPENAPI_PRICING_INVALID",
|
|
806
|
+
"error",
|
|
807
|
+
`${method} ${rawPath} fixed pricing requires price`,
|
|
808
|
+
{
|
|
809
|
+
stage: "openapi"
|
|
810
|
+
}
|
|
811
|
+
)
|
|
812
|
+
);
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (inferredPricingMode === "range") {
|
|
816
|
+
if (!minPrice || !maxPrice) {
|
|
817
|
+
warnings.push(
|
|
818
|
+
warning(
|
|
819
|
+
"OPENAPI_PRICING_INVALID",
|
|
820
|
+
"error",
|
|
821
|
+
`${method} ${rawPath} range pricing requires minPrice and maxPrice`,
|
|
822
|
+
{ stage: "openapi" }
|
|
823
|
+
)
|
|
824
|
+
);
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
const min = Number(minPrice);
|
|
828
|
+
const max = Number(maxPrice);
|
|
829
|
+
if (!Number.isFinite(min) || !Number.isFinite(max) || min > max) {
|
|
830
|
+
warnings.push(
|
|
831
|
+
warning(
|
|
832
|
+
"OPENAPI_PRICING_INVALID",
|
|
833
|
+
"error",
|
|
834
|
+
`${method} ${rawPath} range pricing requires numeric minPrice <= maxPrice`,
|
|
835
|
+
{ stage: "openapi" }
|
|
836
|
+
)
|
|
837
|
+
);
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
pricing = {
|
|
842
|
+
pricingMode: inferredPricingMode,
|
|
843
|
+
...price ? { price } : {},
|
|
844
|
+
...minPrice ? { minPrice } : {},
|
|
845
|
+
...maxPrice ? { maxPrice } : {}
|
|
846
|
+
};
|
|
847
|
+
priceHint = inferredPricingMode === "fixed" ? price : inferredPricingMode === "range" ? `${minPrice}-${maxPrice}` : maxPrice;
|
|
848
|
+
}
|
|
849
|
+
const resource = createResource(
|
|
850
|
+
{
|
|
851
|
+
origin: options.origin,
|
|
852
|
+
method,
|
|
853
|
+
path: normalizePath(rawPath),
|
|
854
|
+
source: "openapi",
|
|
855
|
+
summary: summary ?? `${method} ${normalizePath(rawPath)}`,
|
|
856
|
+
authHint: authMode,
|
|
857
|
+
protocolHints: protocols,
|
|
858
|
+
...priceHint ? { priceHint } : {},
|
|
859
|
+
...pricing ? { pricing } : {},
|
|
860
|
+
links: {
|
|
861
|
+
openapiUrl: fetchedUrl,
|
|
862
|
+
...llmsTxtUrl ? { llmsTxtUrl } : {}
|
|
863
|
+
}
|
|
864
|
+
},
|
|
865
|
+
0.95,
|
|
866
|
+
ownershipProofs.length > 0 ? "ownership_verified" : "origin_hosted"
|
|
867
|
+
);
|
|
868
|
+
resources.push(resource);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
if (llmsTxtUrl) {
|
|
872
|
+
try {
|
|
873
|
+
const llmsResponse = await options.fetcher(llmsTxtUrl, {
|
|
874
|
+
method: "GET",
|
|
875
|
+
headers: { Accept: "text/plain", ...options.headers },
|
|
876
|
+
signal: options.signal
|
|
877
|
+
});
|
|
878
|
+
if (llmsResponse.ok) {
|
|
879
|
+
const llmsText = await llmsResponse.text();
|
|
880
|
+
const tokenCount = estimateTokenCount(llmsText);
|
|
881
|
+
if (tokenCount > LLMS_TOKEN_WARNING_THRESHOLD) {
|
|
882
|
+
warnings.push(
|
|
883
|
+
warning(
|
|
884
|
+
"LLMSTXT_TOO_LARGE",
|
|
885
|
+
"warn",
|
|
886
|
+
`llms.txt estimated ${tokenCount} tokens (threshold ${LLMS_TOKEN_WARNING_THRESHOLD})`,
|
|
887
|
+
{
|
|
888
|
+
stage: "openapi",
|
|
889
|
+
hint: "Keep llms.txt concise and domain-level only."
|
|
890
|
+
}
|
|
891
|
+
)
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
if (options.includeRaw) {
|
|
895
|
+
return {
|
|
896
|
+
stage: "openapi",
|
|
897
|
+
valid: resources.length > 0,
|
|
898
|
+
resources,
|
|
899
|
+
warnings,
|
|
900
|
+
links: { openapiUrl: fetchedUrl, llmsTxtUrl },
|
|
901
|
+
raw: {
|
|
902
|
+
openapi: document,
|
|
903
|
+
llmsTxt: llmsText
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
} else {
|
|
908
|
+
warnings.push(
|
|
909
|
+
warning(
|
|
910
|
+
"LLMSTXT_FETCH_FAILED",
|
|
911
|
+
"warn",
|
|
912
|
+
`llms.txt fetch failed (${llmsResponse.status})`,
|
|
913
|
+
{
|
|
914
|
+
stage: "openapi"
|
|
915
|
+
}
|
|
916
|
+
)
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
} catch (error) {
|
|
920
|
+
warnings.push(
|
|
921
|
+
warning(
|
|
922
|
+
"LLMSTXT_FETCH_FAILED",
|
|
923
|
+
"warn",
|
|
924
|
+
`llms.txt fetch failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
925
|
+
{ stage: "openapi" }
|
|
926
|
+
)
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return {
|
|
931
|
+
stage: "openapi",
|
|
932
|
+
valid: resources.length > 0,
|
|
933
|
+
resources,
|
|
934
|
+
warnings,
|
|
935
|
+
links: {
|
|
936
|
+
openapiUrl: fetchedUrl,
|
|
937
|
+
...llmsTxtUrl ? { llmsTxtUrl } : {}
|
|
938
|
+
},
|
|
939
|
+
...options.includeRaw ? { raw: { openapi: document } } : {}
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// src/core/probe.ts
|
|
944
|
+
function detectAuthHintFrom402Payload(payload) {
|
|
945
|
+
if (payload && typeof payload === "object") {
|
|
946
|
+
const asRecord = payload;
|
|
947
|
+
const extensions = asRecord.extensions;
|
|
948
|
+
if (extensions && typeof extensions === "object") {
|
|
949
|
+
const extRecord = extensions;
|
|
950
|
+
if (extRecord["sign-in-with-x"]) return "siwx";
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return "paid";
|
|
954
|
+
}
|
|
955
|
+
function detectProtocols(response) {
|
|
956
|
+
const protocols = /* @__PURE__ */ new Set();
|
|
957
|
+
const directHeader = response.headers.get("x-payment-protocol");
|
|
958
|
+
if (directHeader) {
|
|
959
|
+
for (const part of directHeader.split(",")) {
|
|
960
|
+
const protocol = part.trim().toLowerCase();
|
|
961
|
+
if (protocol) protocols.add(protocol);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
const authHeader = response.headers.get("www-authenticate")?.toLowerCase() ?? "";
|
|
965
|
+
if (authHeader.includes("x402")) protocols.add("x402");
|
|
966
|
+
if (authHeader.includes("mpp")) protocols.add("mpp");
|
|
967
|
+
return [...protocols];
|
|
968
|
+
}
|
|
969
|
+
async function runProbeStage(options) {
|
|
970
|
+
const candidates = options.probeCandidates ?? [];
|
|
971
|
+
if (candidates.length === 0) {
|
|
972
|
+
return {
|
|
973
|
+
stage: "probe",
|
|
974
|
+
valid: false,
|
|
975
|
+
resources: [],
|
|
976
|
+
warnings: [
|
|
977
|
+
warning(
|
|
978
|
+
"PROBE_STAGE_SKIPPED",
|
|
979
|
+
"info",
|
|
980
|
+
"Probe stage skipped because no probe candidates were provided.",
|
|
981
|
+
{ stage: "probe" }
|
|
982
|
+
)
|
|
983
|
+
]
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
const resources = [];
|
|
987
|
+
const warnings = [];
|
|
988
|
+
for (const candidate of candidates) {
|
|
989
|
+
const path = normalizePath(candidate.path);
|
|
990
|
+
const methods = candidate.methods?.length ? candidate.methods : DEFAULT_PROBE_METHODS;
|
|
991
|
+
for (const method of methods) {
|
|
992
|
+
const url = `${options.origin}${path}`;
|
|
993
|
+
try {
|
|
994
|
+
const response = await options.fetcher(url, {
|
|
995
|
+
method,
|
|
996
|
+
headers: {
|
|
997
|
+
Accept: "application/json, text/plain;q=0.9, */*;q=0.8",
|
|
998
|
+
...options.headers
|
|
999
|
+
},
|
|
1000
|
+
signal: options.signal
|
|
1001
|
+
});
|
|
1002
|
+
if (response.status !== 402 && (response.status < 200 || response.status >= 300)) {
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
let authHint = response.status === 402 ? "paid" : "unprotected";
|
|
1006
|
+
if (response.status === 402) {
|
|
1007
|
+
try {
|
|
1008
|
+
const payload = await response.clone().json();
|
|
1009
|
+
authHint = detectAuthHintFrom402Payload(payload);
|
|
1010
|
+
} catch {
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
const protocolHints = detectProtocols(response);
|
|
1014
|
+
resources.push(
|
|
1015
|
+
createResource(
|
|
1016
|
+
{
|
|
1017
|
+
origin: options.origin,
|
|
1018
|
+
method,
|
|
1019
|
+
path,
|
|
1020
|
+
source: "probe",
|
|
1021
|
+
summary: `${method} ${path}`,
|
|
1022
|
+
authHint,
|
|
1023
|
+
...protocolHints.length ? { protocolHints } : {}
|
|
1024
|
+
},
|
|
1025
|
+
0.6,
|
|
1026
|
+
"runtime_verified"
|
|
1027
|
+
)
|
|
1028
|
+
);
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
warnings.push(
|
|
1031
|
+
warning(
|
|
1032
|
+
"FETCH_FAILED",
|
|
1033
|
+
"info",
|
|
1034
|
+
`Probe ${method} ${path} failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1035
|
+
{ stage: "probe" }
|
|
1036
|
+
)
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
return {
|
|
1042
|
+
stage: "probe",
|
|
1043
|
+
valid: resources.length > 0,
|
|
1044
|
+
resources,
|
|
1045
|
+
warnings
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// src/core/upgrade.ts
|
|
1050
|
+
var UPGRADE_WARNING_CODES = [
|
|
1051
|
+
"LEGACY_WELL_KNOWN_USED",
|
|
1052
|
+
"LEGACY_DNS_USED",
|
|
1053
|
+
"LEGACY_DNS_PLAIN_URL",
|
|
1054
|
+
"LEGACY_INSTRUCTIONS_USED",
|
|
1055
|
+
"LEGACY_OWNERSHIP_PROOFS_USED",
|
|
1056
|
+
"OPENAPI_AUTH_MODE_MISSING",
|
|
1057
|
+
"OPENAPI_TOP_LEVEL_INVALID"
|
|
1058
|
+
];
|
|
1059
|
+
var UPGRADE_WARNING_CODE_SET = new Set(UPGRADE_WARNING_CODES);
|
|
1060
|
+
function computeUpgradeSignal(warnings) {
|
|
1061
|
+
const reasons = warnings.map((entry) => entry.code).filter((code, index, list) => list.indexOf(code) === index).filter((code) => UPGRADE_WARNING_CODE_SET.has(code));
|
|
1062
|
+
return {
|
|
1063
|
+
upgradeSuggested: reasons.length > 0,
|
|
1064
|
+
upgradeReasons: reasons
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// src/core/discovery.ts
|
|
1069
|
+
function mergeResources(target, incoming, resourceWarnings) {
|
|
1070
|
+
const warnings = [];
|
|
1071
|
+
for (const resource of incoming) {
|
|
1072
|
+
const existing = target.get(resource.resourceKey);
|
|
1073
|
+
if (!existing) {
|
|
1074
|
+
target.set(resource.resourceKey, resource);
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
const conflict = existing.authHint !== resource.authHint || existing.priceHint !== resource.priceHint || JSON.stringify(existing.protocolHints ?? []) !== JSON.stringify(resource.protocolHints ?? []);
|
|
1078
|
+
if (conflict) {
|
|
1079
|
+
const conflictWarning = warning(
|
|
1080
|
+
"CROSS_SOURCE_CONFLICT",
|
|
1081
|
+
"warn",
|
|
1082
|
+
`Resource conflict for ${resource.resourceKey}; keeping higher-precedence source ${existing.source} over ${resource.source}.`,
|
|
1083
|
+
{
|
|
1084
|
+
resourceKey: resource.resourceKey
|
|
1085
|
+
}
|
|
1086
|
+
);
|
|
1087
|
+
warnings.push(conflictWarning);
|
|
1088
|
+
resourceWarnings[resource.resourceKey] = [
|
|
1089
|
+
...resourceWarnings[resource.resourceKey] ?? [],
|
|
1090
|
+
conflictWarning
|
|
1091
|
+
];
|
|
1092
|
+
continue;
|
|
1093
|
+
}
|
|
1094
|
+
target.set(resource.resourceKey, {
|
|
1095
|
+
...existing,
|
|
1096
|
+
summary: existing.summary ?? resource.summary,
|
|
1097
|
+
links: { ...resource.links, ...existing.links },
|
|
1098
|
+
confidence: Math.max(existing.confidence ?? 0, resource.confidence ?? 0)
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
return warnings;
|
|
1102
|
+
}
|
|
1103
|
+
function toTrace(stageResult, startedAt) {
|
|
1104
|
+
return {
|
|
1105
|
+
stage: stageResult.stage,
|
|
1106
|
+
attempted: true,
|
|
1107
|
+
valid: stageResult.valid,
|
|
1108
|
+
resourceCount: stageResult.resources.length,
|
|
1109
|
+
durationMs: Date.now() - startedAt,
|
|
1110
|
+
warnings: stageResult.warnings,
|
|
1111
|
+
...stageResult.links ? { links: stageResult.links } : {}
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
async function runOverrideStage(options) {
|
|
1115
|
+
const warnings = [];
|
|
1116
|
+
for (const overrideUrl of options.overrideUrls) {
|
|
1117
|
+
const normalized = overrideUrl.trim();
|
|
1118
|
+
if (!normalized) continue;
|
|
1119
|
+
try {
|
|
1120
|
+
const response = await options.fetcher(normalized, {
|
|
1121
|
+
method: "GET",
|
|
1122
|
+
headers: { Accept: "application/json, text/plain;q=0.9", ...options.headers },
|
|
1123
|
+
signal: options.signal
|
|
1124
|
+
});
|
|
1125
|
+
if (!response.ok) {
|
|
1126
|
+
warnings.push(
|
|
1127
|
+
warning(
|
|
1128
|
+
"FETCH_FAILED",
|
|
1129
|
+
"warn",
|
|
1130
|
+
`Override fetch failed (${response.status}) at ${normalized}`,
|
|
1131
|
+
{
|
|
1132
|
+
stage: "override"
|
|
1133
|
+
}
|
|
1134
|
+
)
|
|
1135
|
+
);
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1138
|
+
const bodyText = await response.text();
|
|
1139
|
+
let parsedJson;
|
|
1140
|
+
try {
|
|
1141
|
+
parsedJson = JSON.parse(bodyText);
|
|
1142
|
+
} catch {
|
|
1143
|
+
parsedJson = void 0;
|
|
1144
|
+
}
|
|
1145
|
+
if (parsedJson && typeof parsedJson === "object" && !Array.isArray(parsedJson) && "openapi" in parsedJson && "paths" in parsedJson) {
|
|
1146
|
+
const openapiResult = await runOpenApiStage({
|
|
1147
|
+
origin: options.origin,
|
|
1148
|
+
fetcher: async (input, init) => {
|
|
1149
|
+
const inputString = String(input);
|
|
1150
|
+
if (inputString === `${options.origin}/openapi.json` || inputString === `${options.origin}/.well-known/openapi.json`) {
|
|
1151
|
+
return new Response(bodyText, {
|
|
1152
|
+
status: 200,
|
|
1153
|
+
headers: { "content-type": "application/json" }
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
return options.fetcher(input, init);
|
|
1157
|
+
},
|
|
1158
|
+
headers: options.headers,
|
|
1159
|
+
signal: options.signal,
|
|
1160
|
+
includeRaw: options.includeRaw
|
|
1161
|
+
});
|
|
1162
|
+
return {
|
|
1163
|
+
...openapiResult,
|
|
1164
|
+
stage: "override",
|
|
1165
|
+
warnings: [
|
|
1166
|
+
warning("OVERRIDE_USED", "info", `Using explicit override source ${normalized}`, {
|
|
1167
|
+
stage: "override"
|
|
1168
|
+
}),
|
|
1169
|
+
...openapiResult.warnings
|
|
1170
|
+
]
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
if (parsedJson && typeof parsedJson === "object" && !Array.isArray(parsedJson)) {
|
|
1174
|
+
const parsedWellKnown = parseWellKnownPayload(parsedJson, options.origin, normalized);
|
|
1175
|
+
if (parsedWellKnown.resources.length > 0) {
|
|
1176
|
+
return {
|
|
1177
|
+
stage: "override",
|
|
1178
|
+
valid: true,
|
|
1179
|
+
resources: parsedWellKnown.resources,
|
|
1180
|
+
warnings: [
|
|
1181
|
+
warning("OVERRIDE_USED", "info", `Using explicit override source ${normalized}`, {
|
|
1182
|
+
stage: "override"
|
|
1183
|
+
}),
|
|
1184
|
+
...parsedWellKnown.warnings
|
|
1185
|
+
],
|
|
1186
|
+
links: { discoveryUrl: normalized },
|
|
1187
|
+
...options.includeRaw ? { raw: parsedWellKnown.raw } : {}
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
} catch (error) {
|
|
1192
|
+
warnings.push(
|
|
1193
|
+
warning(
|
|
1194
|
+
"FETCH_FAILED",
|
|
1195
|
+
"warn",
|
|
1196
|
+
`Override fetch exception at ${normalized}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1197
|
+
{ stage: "override" }
|
|
1198
|
+
)
|
|
1199
|
+
);
|
|
1200
|
+
continue;
|
|
1201
|
+
}
|
|
1202
|
+
const fallbackWellKnownResult = await runWellKnownX402Stage({
|
|
1203
|
+
origin: options.origin,
|
|
1204
|
+
url: normalized,
|
|
1205
|
+
fetcher: options.fetcher,
|
|
1206
|
+
headers: options.headers,
|
|
1207
|
+
signal: options.signal,
|
|
1208
|
+
includeRaw: options.includeRaw
|
|
1209
|
+
});
|
|
1210
|
+
if (fallbackWellKnownResult.valid) {
|
|
1211
|
+
return {
|
|
1212
|
+
...fallbackWellKnownResult,
|
|
1213
|
+
stage: "override",
|
|
1214
|
+
warnings: [
|
|
1215
|
+
warning("OVERRIDE_USED", "info", `Using explicit override source ${normalized}`, {
|
|
1216
|
+
stage: "override"
|
|
1217
|
+
}),
|
|
1218
|
+
...fallbackWellKnownResult.warnings
|
|
1219
|
+
]
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
warnings.push(...fallbackWellKnownResult.warnings);
|
|
1223
|
+
}
|
|
1224
|
+
return {
|
|
1225
|
+
stage: "override",
|
|
1226
|
+
valid: false,
|
|
1227
|
+
resources: [],
|
|
1228
|
+
warnings
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
async function runDiscovery({
|
|
1232
|
+
detailed,
|
|
1233
|
+
options
|
|
1234
|
+
}) {
|
|
1235
|
+
const fetcher = options.fetcher ?? fetch;
|
|
1236
|
+
const compatMode = options.compatMode ?? DEFAULT_COMPAT_MODE;
|
|
1237
|
+
const strictCompat = compatMode === "strict";
|
|
1238
|
+
const rawView = detailed ? options.rawView ?? "none" : "none";
|
|
1239
|
+
const includeRaw = rawView === "full";
|
|
1240
|
+
const includeInteropMpp = detailed && Boolean(options.includeInteropMpp);
|
|
1241
|
+
const origin = normalizeOrigin(options.target);
|
|
1242
|
+
const warnings = [];
|
|
1243
|
+
const trace = [];
|
|
1244
|
+
const resourceWarnings = {};
|
|
1245
|
+
const rawSources = {};
|
|
1246
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1247
|
+
if (compatMode !== "off") {
|
|
1248
|
+
warnings.push(
|
|
1249
|
+
warning(
|
|
1250
|
+
"COMPAT_MODE_ENABLED",
|
|
1251
|
+
compatMode === "strict" ? "warn" : "info",
|
|
1252
|
+
`Compatibility mode is '${compatMode}'. Legacy adapters are active.`
|
|
1253
|
+
)
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
const stages = [];
|
|
1257
|
+
if (options.overrideUrls && options.overrideUrls.length > 0) {
|
|
1258
|
+
stages.push(
|
|
1259
|
+
() => runOverrideStage({
|
|
1260
|
+
origin,
|
|
1261
|
+
overrideUrls: options.overrideUrls ?? [],
|
|
1262
|
+
fetcher,
|
|
1263
|
+
headers: options.headers,
|
|
1264
|
+
signal: options.signal,
|
|
1265
|
+
includeRaw
|
|
1266
|
+
})
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
stages.push(
|
|
1270
|
+
() => runOpenApiStage({
|
|
1271
|
+
origin,
|
|
1272
|
+
fetcher,
|
|
1273
|
+
headers: options.headers,
|
|
1274
|
+
signal: options.signal,
|
|
1275
|
+
includeRaw
|
|
1276
|
+
})
|
|
1277
|
+
);
|
|
1278
|
+
if (compatMode !== "off") {
|
|
1279
|
+
stages.push(
|
|
1280
|
+
() => runWellKnownX402Stage({
|
|
1281
|
+
origin,
|
|
1282
|
+
fetcher,
|
|
1283
|
+
headers: options.headers,
|
|
1284
|
+
signal: options.signal,
|
|
1285
|
+
includeRaw
|
|
1286
|
+
})
|
|
1287
|
+
);
|
|
1288
|
+
stages.push(
|
|
1289
|
+
() => runDnsStage({
|
|
1290
|
+
origin,
|
|
1291
|
+
fetcher,
|
|
1292
|
+
txtResolver: options.txtResolver,
|
|
1293
|
+
headers: options.headers,
|
|
1294
|
+
signal: options.signal,
|
|
1295
|
+
includeRaw
|
|
1296
|
+
})
|
|
1297
|
+
);
|
|
1298
|
+
if (includeInteropMpp) {
|
|
1299
|
+
stages.push(
|
|
1300
|
+
() => runInteropMppStage({
|
|
1301
|
+
origin,
|
|
1302
|
+
fetcher,
|
|
1303
|
+
headers: options.headers,
|
|
1304
|
+
signal: options.signal,
|
|
1305
|
+
includeRaw
|
|
1306
|
+
})
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
stages.push(
|
|
1311
|
+
() => runProbeStage({
|
|
1312
|
+
origin,
|
|
1313
|
+
fetcher,
|
|
1314
|
+
headers: options.headers,
|
|
1315
|
+
signal: options.signal,
|
|
1316
|
+
probeCandidates: options.probeCandidates
|
|
1317
|
+
})
|
|
1318
|
+
);
|
|
1319
|
+
let selectedStage;
|
|
1320
|
+
for (const runStage of stages) {
|
|
1321
|
+
const startedAt = Date.now();
|
|
1322
|
+
const stageResult = await runStage();
|
|
1323
|
+
stageResult.warnings = applyStrictEscalation(stageResult.warnings, strictCompat);
|
|
1324
|
+
trace.push(toTrace(stageResult, startedAt));
|
|
1325
|
+
warnings.push(...stageResult.warnings);
|
|
1326
|
+
if (stageResult.resources.length > 0) {
|
|
1327
|
+
for (const stageWarning of stageResult.warnings) {
|
|
1328
|
+
if (stageWarning.resourceKey) {
|
|
1329
|
+
resourceWarnings[stageWarning.resourceKey] = [
|
|
1330
|
+
...resourceWarnings[stageWarning.resourceKey] ?? [],
|
|
1331
|
+
stageWarning
|
|
1332
|
+
];
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
if (!stageResult.valid) {
|
|
1337
|
+
continue;
|
|
1338
|
+
}
|
|
1339
|
+
const mergeWarnings = mergeResources(merged, stageResult.resources, resourceWarnings);
|
|
1340
|
+
warnings.push(...mergeWarnings);
|
|
1341
|
+
if (includeRaw && stageResult.raw !== void 0) {
|
|
1342
|
+
if (stageResult.stage === "openapi" || stageResult.stage === "override") {
|
|
1343
|
+
const rawObject = stageResult.raw;
|
|
1344
|
+
if (rawObject.openapi !== void 0) rawSources.openapi = rawObject.openapi;
|
|
1345
|
+
if (typeof rawObject.llmsTxt === "string") rawSources.llmsTxt = rawObject.llmsTxt;
|
|
1346
|
+
}
|
|
1347
|
+
if (stageResult.stage === "well-known/x402" || stageResult.stage === "override") {
|
|
1348
|
+
rawSources.wellKnownX402 = [...rawSources.wellKnownX402 ?? [], stageResult.raw];
|
|
1349
|
+
}
|
|
1350
|
+
if (stageResult.stage === "dns/_x402") {
|
|
1351
|
+
const rawObject = stageResult.raw;
|
|
1352
|
+
if (Array.isArray(rawObject.dnsRecords)) {
|
|
1353
|
+
rawSources.dnsRecords = rawObject.dnsRecords;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
if (stageResult.stage === "interop/mpp") {
|
|
1357
|
+
rawSources.interopMpp = stageResult.raw;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
if (!detailed) {
|
|
1361
|
+
selectedStage = selectedStage ?? stageResult.stage;
|
|
1362
|
+
break;
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
if (merged.size === 0) {
|
|
1366
|
+
warnings.push(
|
|
1367
|
+
warning(
|
|
1368
|
+
"NO_DISCOVERY_SOURCES",
|
|
1369
|
+
"error",
|
|
1370
|
+
"No discovery stage returned first-valid-non-empty results."
|
|
1371
|
+
)
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
const dedupedWarnings = dedupeWarnings(warnings);
|
|
1375
|
+
const upgradeSignal = computeUpgradeSignal(dedupedWarnings);
|
|
1376
|
+
const resources = [...merged.values()];
|
|
1377
|
+
if (!detailed) {
|
|
1378
|
+
return {
|
|
1379
|
+
origin,
|
|
1380
|
+
resources,
|
|
1381
|
+
warnings: dedupedWarnings,
|
|
1382
|
+
compatMode,
|
|
1383
|
+
...upgradeSignal,
|
|
1384
|
+
...selectedStage ? { selectedStage } : {}
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
return {
|
|
1388
|
+
origin,
|
|
1389
|
+
resources,
|
|
1390
|
+
warnings: dedupedWarnings,
|
|
1391
|
+
compatMode,
|
|
1392
|
+
...upgradeSignal,
|
|
1393
|
+
selectedStage: trace.find((entry) => entry.valid)?.stage,
|
|
1394
|
+
trace,
|
|
1395
|
+
resourceWarnings,
|
|
1396
|
+
...includeRaw ? { rawSources } : {}
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// src/index.ts
|
|
1401
|
+
async function discover(options) {
|
|
1402
|
+
return await runDiscovery({ detailed: false, options });
|
|
1403
|
+
}
|
|
1404
|
+
async function discoverDetailed(options) {
|
|
1405
|
+
return await runDiscovery({ detailed: true, options });
|
|
1406
|
+
}
|
|
1407
|
+
export {
|
|
1408
|
+
DEFAULT_COMPAT_MODE,
|
|
1409
|
+
LEGACY_SUNSET_DATE,
|
|
1410
|
+
STRICT_ESCALATION_CODES,
|
|
1411
|
+
UPGRADE_WARNING_CODES,
|
|
1412
|
+
computeUpgradeSignal,
|
|
1413
|
+
discover,
|
|
1414
|
+
discoverDetailed
|
|
1415
|
+
};
|