@agentcash/discovery 1.0.1 → 1.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/README.md +86 -154
- package/dist/cli.cjs +87 -60
- package/dist/cli.js +77 -60
- package/dist/index.cjs +89 -62
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +79 -62
- package/dist/schemas.cjs +1 -0
- package/dist/schemas.d.cts +1 -0
- package/dist/schemas.d.ts +1 -0
- package/dist/schemas.js +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Canonical discovery runtime for the agentcash ecosystem.
|
|
4
4
|
|
|
5
|
-
Use one library for CLI,
|
|
5
|
+
Use one library for MCP, CLI, router, and audit so discovery behavior is identical everywhere.
|
|
6
6
|
|
|
7
7
|
## Why One Library
|
|
8
8
|
|
|
9
9
|
- Same parsing logic across surfaces: no CLI/server/client drift.
|
|
10
|
+
- Shared Zod schema that the router can test against at compile-time.
|
|
10
11
|
- Same warning codes and precedence rules: fewer integration surprises.
|
|
11
12
|
- Same compatibility adapters in one place: legacy behavior is isolated and removable.
|
|
12
|
-
- Same L0-L5 harness model: easier context-budget auditing and rollout decisions.
|
|
13
13
|
|
|
14
14
|
## L0-L5 Mental Model
|
|
15
15
|
|
|
@@ -22,196 +22,128 @@ Use one library for CLI, server, and client so discovery behavior is identical e
|
|
|
22
22
|
|
|
23
23
|
Design rule: `L0` + `L1` are zero-hop critical. `L2+` should be fetched on demand.
|
|
24
24
|
|
|
25
|
+
In practice, each layer should guide the agent to discover the next:
|
|
26
|
+
|
|
27
|
+
| Layer | Surface | What the agent gets |
|
|
28
|
+
| ------ | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
29
|
+
| **L0** | MCP tool description / `agentcash --help` | First impression of agentcash. Should encourage the agent to use it and explicitly explain `discoverOriginSchema` and `checkEndpointSchema`. |
|
|
30
|
+
| **L1** | Same location as L0 | List of domains available to the agent. Each entry should be descriptive enough for the agent to understand what it does at a high level. |
|
|
31
|
+
| **L2** | `discoverOriginSchema` result | Detailed description of the origin and its supported endpoints. |
|
|
32
|
+
| **L3** | `checkEndpointSchema` result | Specific guidance for a single endpoint: input/output schema, auth mode, and a detailed description of what the endpoint does. |
|
|
33
|
+
| **L4** | `discoverOriginSchema` with `includeGuidance: true` | Composition guidance for 2+ resources at an origin. Sourced from the `guidance` field in OpenAPI. |
|
|
34
|
+
|
|
25
35
|
## Install
|
|
26
36
|
|
|
27
37
|
```bash
|
|
28
38
|
pnpm add @agentcash/discovery
|
|
29
39
|
```
|
|
30
40
|
|
|
31
|
-
## CLI
|
|
41
|
+
## CLI
|
|
32
42
|
|
|
33
|
-
|
|
43
|
+
Two commands: `discover` (list endpoints at an origin) and `check` (inspect a specific URL).
|
|
34
44
|
|
|
35
45
|
```bash
|
|
46
|
+
# Discover all endpoints at an origin
|
|
36
47
|
npx @agentcash/discovery stabletravel.dev
|
|
37
|
-
|
|
48
|
+
npx @agentcash/discovery discover stabletravel.dev
|
|
38
49
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
npx @agentcash/discovery stabletravel.dev -v
|
|
50
|
+
# Inspect a specific endpoint URL
|
|
51
|
+
npx @agentcash/discovery check https://stabletravel.dev/search
|
|
43
52
|
```
|
|
44
53
|
|
|
45
|
-
|
|
54
|
+
Flags:
|
|
46
55
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
| Flag | Description |
|
|
57
|
+
| -------- | -------------------------------------------------- |
|
|
58
|
+
| `--json` | Machine-readable JSON output |
|
|
59
|
+
| `-v` | Verbose — includes guidance text and warning hints |
|
|
50
60
|
|
|
51
|
-
|
|
61
|
+
JSON output shape (`discover`):
|
|
52
62
|
|
|
53
|
-
```
|
|
54
|
-
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"ok": true,
|
|
66
|
+
"selectedStage": "openapi",
|
|
67
|
+
"resources": [{ "resourceKey": "GET /search", "method": "GET", "path": "/search" }],
|
|
68
|
+
"warnings": [],
|
|
69
|
+
"meta": { "origin": "https://stabletravel.dev", "specUrl": "..." }
|
|
70
|
+
}
|
|
55
71
|
```
|
|
56
72
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
|
|
73
|
+
JSON output shape (`check`):
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"url": "https://stabletravel.dev/search",
|
|
78
|
+
"found": true,
|
|
79
|
+
"origin": "https://stabletravel.dev",
|
|
80
|
+
"path": "/search",
|
|
81
|
+
"advisories": [{ "method": "GET", "authMode": "bearer", "estimatedPrice": "$0.01" }],
|
|
82
|
+
"warnings": []
|
|
83
|
+
}
|
|
61
84
|
```
|
|
62
85
|
|
|
63
|
-
Useful flags:
|
|
64
|
-
|
|
65
|
-
- `--compat on|off|strict`
|
|
66
|
-
- `--probe`
|
|
67
|
-
- `--timeout-ms <ms>`
|
|
68
|
-
- `--no-truncate`
|
|
69
|
-
- `--no-color`
|
|
70
|
-
|
|
71
86
|
## Programmatic Usage
|
|
72
87
|
|
|
73
88
|
```ts
|
|
74
|
-
import {
|
|
75
|
-
auditContextHarness,
|
|
76
|
-
discover,
|
|
77
|
-
discoverDetailed,
|
|
78
|
-
validatePaymentRequiredDetailed,
|
|
79
|
-
type HarnessClientId,
|
|
80
|
-
} from '@agentcash/discovery';
|
|
81
|
-
|
|
82
|
-
const progressive = await discover({
|
|
83
|
-
target: 'stabletravel.dev',
|
|
84
|
-
compatMode: 'on',
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const detailed = await discoverDetailed({
|
|
88
|
-
target: 'stabletravel.dev',
|
|
89
|
-
compatMode: 'strict',
|
|
90
|
-
rawView: 'full',
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
const client: HarnessClientId = 'claude-code';
|
|
94
|
-
const harness = await auditContextHarness({
|
|
95
|
-
target: 'stabletravel.dev',
|
|
96
|
-
client,
|
|
97
|
-
contextWindowTokens: 200000,
|
|
98
|
-
compatMode: 'on',
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const validation = validatePaymentRequiredDetailed(payload, {
|
|
102
|
-
compatMode: 'strict',
|
|
103
|
-
metadata: {
|
|
104
|
-
title: 'Example API',
|
|
105
|
-
description: 'Sample description',
|
|
106
|
-
favicon: 'https://example.com/favicon.ico',
|
|
107
|
-
ogImages: [{ url: 'https://example.com/og.png' }],
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## MCP Adapter Contract
|
|
113
|
-
|
|
114
|
-
This section is the canonical contract for MCP-facing discovery adapters:
|
|
115
|
-
|
|
116
|
-
- `discoverForMcp(options)` for L2 resources + L4 guidance policy projection.
|
|
117
|
-
- `inspectEndpointForMcp(options)` for L3 OpenAPI advisory projection.
|
|
118
|
-
|
|
119
|
-
`discoverForMcp` guarantees:
|
|
120
|
-
|
|
121
|
-
- `guidanceAvailable` is always present.
|
|
122
|
-
- `guidanceTokens` is present when guidance exists.
|
|
123
|
-
- `guidance` is included when `includeGuidance=true`, excluded when
|
|
124
|
-
`includeGuidance=false`, and auto-included under the token threshold when not
|
|
125
|
-
specified.
|
|
126
|
-
|
|
127
|
-
`inspectEndpointForMcp` guarantees:
|
|
128
|
-
|
|
129
|
-
- Spec-derived HTTP methods for the selected endpoint path.
|
|
130
|
-
- Per-method advisory data: summary, estimated price, protocols, auth mode, and
|
|
131
|
-
input schema.
|
|
132
|
-
|
|
133
|
-
Ownership boundary:
|
|
134
|
-
|
|
135
|
-
- `@agentcash/discovery` owns discovery/advisory contracts.
|
|
136
|
-
- Runtime probe truth (live 402 parsing/payment option extraction/divergence)
|
|
137
|
-
belongs to the `agentcash` MCP package.
|
|
138
|
-
|
|
139
|
-
Reference integration boundary:
|
|
140
|
-
|
|
141
|
-
- `https://github.com/Merit-Systems/agentcash/blob/main/packages/external/mcp/docs/discovery-boundary.md`
|
|
89
|
+
import { discoverOriginSchema, checkEndpointSchema } from '@agentcash/discovery';
|
|
142
90
|
|
|
143
|
-
|
|
91
|
+
// Discover all endpoints at an origin
|
|
92
|
+
const result = await discoverOriginSchema({ target: 'stabletravel.dev' });
|
|
93
|
+
// result.found === true → result.endpoints (L2Route[]), result.guidance?, result.guidanceTokens?
|
|
144
94
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
3. `/.well-known/x402` (compat)
|
|
150
|
-
4. DNS TXT `_x402` pointers (compat)
|
|
151
|
-
5. Probe fallback (only when probe candidates are provided)
|
|
95
|
+
// Inspect a specific endpoint URL
|
|
96
|
+
const check = await checkEndpointSchema({ url: 'https://stabletravel.dev/search' });
|
|
97
|
+
// check.found === true → check.advisories (per-method: authMode, estimatedPrice, protocols, inputSchema)
|
|
98
|
+
```
|
|
152
99
|
|
|
153
|
-
|
|
100
|
+
## Exported API
|
|
154
101
|
|
|
155
|
-
|
|
156
|
-
- `discoverDetailed(...)` runs full waterfall and merges deterministically.
|
|
102
|
+
### Core discovery
|
|
157
103
|
|
|
158
|
-
|
|
104
|
+
| Export | Description |
|
|
105
|
+
| ------------------------ | --------------------------------------------------------- |
|
|
106
|
+
| `discoverOriginSchema()` | Progressive discovery — returns endpoints + advisory data |
|
|
107
|
+
| `checkEndpointSchema()` | Per-endpoint inspection — returns per-method advisories |
|
|
159
108
|
|
|
160
|
-
|
|
161
|
-
- Product policy diagnostics (network/schema/metadata) are layered on top with stable issue codes.
|
|
109
|
+
### Layer fetchers (low-level)
|
|
162
110
|
|
|
163
|
-
|
|
111
|
+
| Export | Layer | Description |
|
|
112
|
+
| -------------------------- | ----- | ------------------------------------- |
|
|
113
|
+
| `getOpenAPI(origin)` | — | Fetch OpenAPI spec from origin |
|
|
114
|
+
| `getWellKnown(origin)` | — | Fetch `/.well-known/x402` document |
|
|
115
|
+
| `getProbe(url, body?)` | — | Live endpoint probe |
|
|
116
|
+
| `checkL2ForOpenAPI(spec)` | L2 | Extract route list from OpenAPI |
|
|
117
|
+
| `checkL2ForWellknown(doc)` | L2 | Extract route list from well-known |
|
|
118
|
+
| `getL3(origin, path)` | L3 | Get detailed metadata for an endpoint |
|
|
119
|
+
| `checkL4ForOpenAPI(spec)` | L4 | Extract guidance from OpenAPI |
|
|
120
|
+
| `checkL4ForWellknown(doc)` | L4 | Extract guidance from well-known |
|
|
164
121
|
|
|
165
|
-
|
|
166
|
-
- `off`: canonical-only behavior.
|
|
167
|
-
- `strict`: legacy adapters enabled, selected warnings escalated.
|
|
122
|
+
### Validation
|
|
168
123
|
|
|
169
|
-
|
|
124
|
+
| Export | Description |
|
|
125
|
+
| ----------------------------------- | -------------------------------------------- |
|
|
126
|
+
| `validatePaymentRequiredDetailed()` | Full 402 payload validation with diagnostics |
|
|
127
|
+
| `evaluateMetadataCompleteness()` | Metadata quality score |
|
|
128
|
+
| `VALIDATION_CODES` | Stable issue code constants |
|
|
170
129
|
|
|
171
|
-
|
|
130
|
+
### Audit
|
|
172
131
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
132
|
+
| Export | Description |
|
|
133
|
+
| --------------------------- | ------------------------------ |
|
|
134
|
+
| `getWarningsForOpenAPI()` | Warnings for OpenAPI source |
|
|
135
|
+
| `getWarningsForWellKnown()` | Warnings for well-known source |
|
|
136
|
+
| `getWarningsForL2()` | Warnings for route list |
|
|
137
|
+
| `getWarningsForL3()` | Warnings for endpoint metadata |
|
|
138
|
+
| `getWarningsForL4()` | Warnings for guidance layer |
|
|
139
|
+
| `AUDIT_CODES` | Stable audit code constants |
|
|
176
140
|
|
|
177
|
-
|
|
141
|
+
Ownership boundary:
|
|
178
142
|
|
|
179
|
-
- `
|
|
180
|
-
- `
|
|
181
|
-
- `method`
|
|
182
|
-
- `path`
|
|
183
|
-
- `source`
|
|
184
|
-
- `verified` (default `false`)
|
|
143
|
+
- `@agentcash/discovery` owns discovery/advisory contracts.
|
|
144
|
+
- `@agentcash` should own all signing logic, but should be composable with the methods for probing built in this package.
|
|
185
145
|
|
|
186
146
|
Philosophy boundary:
|
|
187
147
|
|
|
188
148
|
- Machine-parsable discovery metadata belongs in OpenAPI.
|
|
189
|
-
- `llms.txt` is optional, unstructured guidance.
|
|
190
149
|
- Discovery is advisory. Runtime payment challenge/probe is authoritative.
|
|
191
|
-
|
|
192
|
-
## Internal Registry Audit Harness
|
|
193
|
-
|
|
194
|
-
For x402scan registry benchmarking:
|
|
195
|
-
|
|
196
|
-
```bash
|
|
197
|
-
SCAN_DATABASE_URL='postgresql://...' pnpm audit:registry
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
Quick sample:
|
|
201
|
-
|
|
202
|
-
```bash
|
|
203
|
-
SCAN_DATABASE_URL='postgresql://...' pnpm audit:registry:quick
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
Output:
|
|
207
|
-
|
|
208
|
-
- `audit/registry-audit-<timestamp>.json`
|
|
209
|
-
- `audit/registry-audit-latest.json`
|
|
210
|
-
|
|
211
|
-
## Deeper Docs
|
|
212
|
-
|
|
213
|
-
Architecture and planning artifacts live in `.claude/`.
|
|
214
|
-
|
|
215
|
-
Validation design doc:
|
|
216
|
-
|
|
217
|
-
- `docs/VALIDATION_DIAGNOSTICS_DESIGN_2026-03-03.md`
|
package/dist/cli.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/cli.ts
|
|
@@ -76,6 +86,7 @@ var OpenApiDocSchema = import_zod.z.object({
|
|
|
76
86
|
description: import_zod.z.string().optional(),
|
|
77
87
|
guidance: import_zod.z.string().optional()
|
|
78
88
|
}),
|
|
89
|
+
security: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.array(import_zod.z.string()))).optional(),
|
|
79
90
|
servers: import_zod.z.array(import_zod.z.object({ url: import_zod.z.string() })).optional(),
|
|
80
91
|
tags: import_zod.z.array(import_zod.z.object({ name: import_zod.z.string() })).optional(),
|
|
81
92
|
components: import_zod.z.object({ securitySchemes: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional() }).optional(),
|
|
@@ -105,20 +116,31 @@ var WellKnownParsedSchema = import_zod.z.object({
|
|
|
105
116
|
function isRecord(value) {
|
|
106
117
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
107
118
|
}
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
function resolveSecurityFlags(requirements, securitySchemes) {
|
|
120
|
+
let hasApiKey = false;
|
|
121
|
+
let hasSiwx = false;
|
|
122
|
+
for (const requirement of requirements) {
|
|
123
|
+
for (const schemeName of Object.keys(requirement)) {
|
|
124
|
+
if (schemeName === "siwx") {
|
|
125
|
+
hasSiwx = true;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (schemeName === "apiKey") {
|
|
129
|
+
hasApiKey = true;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const def = securitySchemes[schemeName];
|
|
133
|
+
if (isRecord(def) && def["type"] === "apiKey") hasApiKey = true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { hasApiKey, hasSiwx };
|
|
113
137
|
}
|
|
114
|
-
function inferAuthMode(operation) {
|
|
138
|
+
function inferAuthMode(operation, globalSecurity, securitySchemes) {
|
|
115
139
|
const hasXPaymentInfo = Boolean(operation["x-payment-info"]);
|
|
116
|
-
const
|
|
117
|
-
const hasApiKey =
|
|
118
|
-
|
|
119
|
-
if (hasPayment && hasApiKey) return "apiKey+paid";
|
|
140
|
+
const effectiveSecurity = operation.security !== void 0 && operation.security.length > 0 ? operation.security : globalSecurity ?? [];
|
|
141
|
+
const { hasApiKey, hasSiwx } = resolveSecurityFlags(effectiveSecurity, securitySchemes ?? {});
|
|
142
|
+
if (hasXPaymentInfo && hasApiKey) return "apiKey+paid";
|
|
120
143
|
if (hasXPaymentInfo) return "paid";
|
|
121
|
-
if (hasPayment) return hasSiwx ? "siwx" : "paid";
|
|
122
144
|
if (hasApiKey) return "apiKey";
|
|
123
145
|
if (hasSiwx) return "siwx";
|
|
124
146
|
return void 0;
|
|
@@ -138,10 +160,12 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
|
|
|
138
160
|
var DEFAULT_MISSING_METHOD = "POST";
|
|
139
161
|
|
|
140
162
|
// src/core/lib/url.ts
|
|
141
|
-
function
|
|
163
|
+
function ensureProtocol(target) {
|
|
142
164
|
const trimmed = target.trim();
|
|
143
|
-
|
|
144
|
-
|
|
165
|
+
return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
166
|
+
}
|
|
167
|
+
function normalizeOrigin(target) {
|
|
168
|
+
const url = new URL(ensureProtocol(target));
|
|
145
169
|
url.pathname = "";
|
|
146
170
|
url.search = "";
|
|
147
171
|
url.hash = "";
|
|
@@ -180,7 +204,7 @@ function fetchSafe(url, init) {
|
|
|
180
204
|
}
|
|
181
205
|
|
|
182
206
|
// src/mmm-enabled.ts
|
|
183
|
-
var isMmmEnabled = () => "1.0
|
|
207
|
+
var isMmmEnabled = () => "1.1.0".includes("-mmm");
|
|
184
208
|
|
|
185
209
|
// src/core/source/openapi/index.ts
|
|
186
210
|
var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
|
|
@@ -189,15 +213,13 @@ var OpenApiParsedSchema = OpenApiDocSchema.transform((doc) => {
|
|
|
189
213
|
for (const httpMethod of [...HTTP_METHODS]) {
|
|
190
214
|
const operation = pathItem[httpMethod.toLowerCase()];
|
|
191
215
|
if (!operation) continue;
|
|
192
|
-
const authMode = inferAuthMode(operation) ?? void 0;
|
|
216
|
+
const authMode = inferAuthMode(operation, doc.security, doc.components?.securitySchemes) ?? void 0;
|
|
193
217
|
if (!authMode) continue;
|
|
194
218
|
const p = operation["x-payment-info"];
|
|
195
219
|
const protocols = (p?.protocols ?? []).filter(
|
|
196
220
|
(proto) => proto !== "mpp" || isMmmEnabled()
|
|
197
221
|
);
|
|
198
|
-
|
|
199
|
-
if (authMode === "paid" && protocols.length === 0) continue;
|
|
200
|
-
const pricing = authMode === "paid" && p ? {
|
|
222
|
+
const pricing = (authMode === "paid" || authMode === "apiKey+paid") && p ? {
|
|
201
223
|
pricingMode: p.pricingMode,
|
|
202
224
|
...p.price ? { price: p.price } : {},
|
|
203
225
|
...p.minPrice ? { minPrice: p.minPrice } : {},
|
|
@@ -348,6 +370,7 @@ var AUDIT_CODES = {
|
|
|
348
370
|
L2_NO_ROUTES: "L2_NO_ROUTES",
|
|
349
371
|
L2_ROUTE_COUNT_HIGH: "L2_ROUTE_COUNT_HIGH",
|
|
350
372
|
L2_AUTH_MODE_MISSING: "L2_AUTH_MODE_MISSING",
|
|
373
|
+
L2_NO_PAID_ROUTES: "L2_NO_PAID_ROUTES",
|
|
351
374
|
L2_PRICE_MISSING_ON_PAID: "L2_PRICE_MISSING_ON_PAID",
|
|
352
375
|
L2_PROTOCOLS_MISSING_ON_PAID: "L2_PROTOCOLS_MISSING_ON_PAID",
|
|
353
376
|
// ─── L3 endpoint advisory checks ─────────────────────────────────────────────
|
|
@@ -409,6 +432,15 @@ function getWarningsForL2(l2) {
|
|
|
409
432
|
});
|
|
410
433
|
return warnings;
|
|
411
434
|
}
|
|
435
|
+
const hasPaidRoute = l2.routes.some((r) => r.authMode === "paid" || r.authMode === "apiKey+paid");
|
|
436
|
+
if (!hasPaidRoute) {
|
|
437
|
+
warnings.push({
|
|
438
|
+
code: AUDIT_CODES.L2_NO_PAID_ROUTES,
|
|
439
|
+
severity: "info",
|
|
440
|
+
message: "No endpoints are marked as paid or apiKey+paid.",
|
|
441
|
+
hint: "Add x-payment-info to operations that require payment so agents know which endpoints are monetized."
|
|
442
|
+
});
|
|
443
|
+
}
|
|
412
444
|
if (l2.routes.length > ROUTE_COUNT_HIGH) {
|
|
413
445
|
warnings.push({
|
|
414
446
|
code: AUDIT_CODES.L2_ROUTE_COUNT_HIGH,
|
|
@@ -495,8 +527,8 @@ function getWarningsForL4(l4) {
|
|
|
495
527
|
{
|
|
496
528
|
code: AUDIT_CODES.L4_GUIDANCE_MISSING,
|
|
497
529
|
severity: "info",
|
|
498
|
-
message: "No guidance text found (
|
|
499
|
-
hint: "Add an info.guidance field to your OpenAPI spec
|
|
530
|
+
message: "No guidance text found (OpenAPI info.guidance).",
|
|
531
|
+
hint: "Add an info.guidance field to your OpenAPI spec for agent-readable instructions."
|
|
500
532
|
}
|
|
501
533
|
];
|
|
502
534
|
}
|
|
@@ -805,54 +837,31 @@ function extractPaymentOptions4(wwwAuthenticate) {
|
|
|
805
837
|
return options;
|
|
806
838
|
}
|
|
807
839
|
|
|
808
|
-
// src/core/
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
if (!isRecord(entry)) continue;
|
|
814
|
-
const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
|
|
815
|
-
const regex = new RegExp(`^${pattern}$`);
|
|
816
|
-
if (regex.test(targetPath)) {
|
|
817
|
-
return { matchedPath: specPath, pathItem: entry };
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
return null;
|
|
821
|
-
}
|
|
822
|
-
function resolveRef(document, ref, seen) {
|
|
823
|
-
if (!ref.startsWith("#/")) return void 0;
|
|
824
|
-
if (seen.has(ref)) return { $circular: ref };
|
|
825
|
-
seen.add(ref);
|
|
826
|
-
const parts = ref.slice(2).split("/");
|
|
827
|
-
let current = document;
|
|
828
|
-
for (const part of parts) {
|
|
829
|
-
if (!isRecord(current)) return void 0;
|
|
830
|
-
current = current[part];
|
|
831
|
-
if (current === void 0) return void 0;
|
|
832
|
-
}
|
|
833
|
-
if (isRecord(current)) return resolveRefs(document, current, seen);
|
|
834
|
-
return current;
|
|
840
|
+
// src/core/lib/resolve-ref.ts
|
|
841
|
+
var import_dereference_json_schema = __toESM(require("dereference-json-schema"), 1);
|
|
842
|
+
var { resolveRefSync } = import_dereference_json_schema.default;
|
|
843
|
+
function isRecord2(value) {
|
|
844
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
835
845
|
}
|
|
836
|
-
function
|
|
837
|
-
if (depth > 4) return obj;
|
|
846
|
+
function deepResolveRefs(document, obj) {
|
|
838
847
|
const resolved = {};
|
|
839
848
|
for (const [key, value] of Object.entries(obj)) {
|
|
840
849
|
if (key === "$ref" && typeof value === "string") {
|
|
841
|
-
const deref =
|
|
842
|
-
if (
|
|
843
|
-
Object.assign(resolved, deref);
|
|
850
|
+
const deref = resolveRefSync(document, value);
|
|
851
|
+
if (isRecord2(deref)) {
|
|
852
|
+
Object.assign(resolved, deepResolveRefs(document, deref));
|
|
844
853
|
} else {
|
|
845
854
|
resolved[key] = value;
|
|
846
855
|
}
|
|
847
856
|
continue;
|
|
848
857
|
}
|
|
849
|
-
if (
|
|
850
|
-
resolved[key] =
|
|
858
|
+
if (isRecord2(value)) {
|
|
859
|
+
resolved[key] = deepResolveRefs(document, value);
|
|
851
860
|
continue;
|
|
852
861
|
}
|
|
853
862
|
if (Array.isArray(value)) {
|
|
854
863
|
resolved[key] = value.map(
|
|
855
|
-
(item) =>
|
|
864
|
+
(item) => isRecord2(item) ? deepResolveRefs(document, item) : item
|
|
856
865
|
);
|
|
857
866
|
continue;
|
|
858
867
|
}
|
|
@@ -860,6 +869,24 @@ function resolveRefs(document, obj, seen, depth = 0) {
|
|
|
860
869
|
}
|
|
861
870
|
return resolved;
|
|
862
871
|
}
|
|
872
|
+
function resolveRefs(obj, document) {
|
|
873
|
+
return deepResolveRefs(document, obj);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// src/core/layers/l3.ts
|
|
877
|
+
function findMatchingOpenApiPath(paths, targetPath) {
|
|
878
|
+
const exact = paths[targetPath];
|
|
879
|
+
if (isRecord(exact)) return { matchedPath: targetPath, pathItem: exact };
|
|
880
|
+
for (const [specPath, entry] of Object.entries(paths)) {
|
|
881
|
+
if (!isRecord(entry)) continue;
|
|
882
|
+
const pattern = specPath.replace(/\{[^}]+\}/g, "[^/]+");
|
|
883
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
884
|
+
if (regex.test(targetPath)) {
|
|
885
|
+
return { matchedPath: specPath, pathItem: entry };
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
863
890
|
function extractRequestBodySchema(operationSchema) {
|
|
864
891
|
const requestBody = operationSchema.requestBody;
|
|
865
892
|
if (!isRecord(requestBody)) return void 0;
|
|
@@ -939,7 +966,7 @@ function getL3ForOpenAPI(openApi, path, method) {
|
|
|
939
966
|
if (!matched) return null;
|
|
940
967
|
const operation = matched.pathItem[method.toLowerCase()];
|
|
941
968
|
if (!isRecord(operation)) return null;
|
|
942
|
-
const resolvedOperation = resolveRefs(
|
|
969
|
+
const resolvedOperation = resolveRefs(operation, document);
|
|
943
970
|
const summary = typeof resolvedOperation.summary === "string" ? resolvedOperation.summary : typeof resolvedOperation.description === "string" ? resolvedOperation.description : void 0;
|
|
944
971
|
return {
|
|
945
972
|
source: "openapi",
|
|
@@ -985,12 +1012,12 @@ function getAdvisoriesForProbe(probe, path) {
|
|
|
985
1012
|
});
|
|
986
1013
|
}
|
|
987
1014
|
async function checkEndpointSchema(options) {
|
|
988
|
-
const endpoint = new URL(options.url);
|
|
1015
|
+
const endpoint = new URL(ensureProtocol(options.url));
|
|
989
1016
|
const origin = normalizeOrigin(endpoint.origin);
|
|
990
1017
|
const path = normalizePath(endpoint.pathname || "/");
|
|
991
1018
|
if (options.sampleInputBody !== void 0) {
|
|
992
1019
|
const probeResult2 = await getProbe(
|
|
993
|
-
|
|
1020
|
+
endpoint.href,
|
|
994
1021
|
options.headers,
|
|
995
1022
|
options.signal,
|
|
996
1023
|
options.sampleInputBody
|
|
@@ -1015,7 +1042,7 @@ async function checkEndpointSchema(options) {
|
|
|
1015
1042
|
if (advisories2.length > 0) return { found: true, origin, path, advisories: advisories2 };
|
|
1016
1043
|
return { found: false, origin, path, cause: "not_found" };
|
|
1017
1044
|
}
|
|
1018
|
-
const probeResult = await getProbe(
|
|
1045
|
+
const probeResult = await getProbe(endpoint.href, options.headers, options.signal);
|
|
1019
1046
|
if (probeResult.isErr()) {
|
|
1020
1047
|
return {
|
|
1021
1048
|
found: false,
|