@adcp/sdk 14.4.0 → 15.2.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/bin/adcp-grade.js +46 -2
- package/bin/adcp.js +13 -3
- package/dist/lib/auth/oauth/ClientCredentialsFlow.js +4 -1
- package/dist/lib/auth/oauth/ClientCredentialsFlow.mjs +4 -1
- package/dist/lib/auth/oauth/MCPOAuthProvider.d.mts +19 -6
- package/dist/lib/auth/oauth/MCPOAuthProvider.d.ts +19 -6
- package/dist/lib/auth/oauth/MCPOAuthProvider.js +95 -30
- package/dist/lib/auth/oauth/MCPOAuthProvider.mjs +96 -30
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.d.mts +1 -1
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.d.ts +1 -1
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.js +1 -1
- package/dist/lib/auth/oauth/NonInteractiveFlowHandler.mjs +1 -1
- package/dist/lib/auth/oauth/client-credentials-schema.d.mts +17 -0
- package/dist/lib/auth/oauth/client-credentials-schema.d.ts +17 -0
- package/dist/lib/auth/oauth/client-credentials-schema.js +37 -0
- package/dist/lib/auth/oauth/client-credentials-schema.mjs +13 -0
- package/dist/lib/auth/oauth/file-storage.d.mts +3 -3
- package/dist/lib/auth/oauth/file-storage.d.ts +3 -3
- package/dist/lib/auth/oauth/file-storage.js +12 -5
- package/dist/lib/auth/oauth/file-storage.mjs +12 -5
- package/dist/lib/auth/oauth/index.d.mts +10 -6
- package/dist/lib/auth/oauth/index.d.ts +10 -6
- package/dist/lib/auth/oauth/index.js +9 -3
- package/dist/lib/auth/oauth/index.mjs +8 -3
- package/dist/lib/auth/oauth/types.d.mts +9 -2
- package/dist/lib/auth/oauth/types.d.ts +9 -2
- package/dist/lib/auth/oauth/types.js +3 -2
- package/dist/lib/auth/oauth/types.mjs +3 -2
- package/dist/lib/auth/oauth/web-flow.d.mts +2 -0
- package/dist/lib/auth/oauth/web-flow.d.ts +2 -0
- package/dist/lib/auth/oauth/web-flow.js +42 -9
- package/dist/lib/auth/oauth/web-flow.mjs +42 -9
- package/dist/lib/canonical-references/index.d.mts +19 -1
- package/dist/lib/canonical-references/index.d.ts +19 -1
- package/dist/lib/canonical-references/index.js +81 -9
- package/dist/lib/canonical-references/index.mjs +80 -9
- package/dist/lib/client/core.d.mts +1 -0
- package/dist/lib/client/core.d.ts +1 -0
- package/dist/lib/core/AgentClient.d.mts +9 -1
- package/dist/lib/core/AgentClient.d.ts +9 -1
- package/dist/lib/core/AgentClient.js +10 -0
- package/dist/lib/core/AgentClient.mjs +10 -0
- package/dist/lib/core/SingleAgentClient.d.mts +13 -2
- package/dist/lib/core/SingleAgentClient.d.ts +13 -2
- package/dist/lib/core/SingleAgentClient.js +23 -5
- package/dist/lib/core/SingleAgentClient.mjs +23 -5
- package/dist/lib/index.d.mts +2 -2
- package/dist/lib/index.d.ts +2 -2
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.mjs +3 -1
- package/dist/lib/reporting/gcs/adapter.d.mts +31 -0
- package/dist/lib/reporting/gcs/adapter.d.ts +31 -0
- package/dist/lib/reporting/gcs/adapter.js +342 -0
- package/dist/lib/reporting/gcs/adapter.mjs +325 -0
- package/dist/lib/reporting/gcs/bucket-policy.d.mts +3 -0
- package/dist/lib/reporting/gcs/bucket-policy.d.ts +3 -0
- package/dist/lib/reporting/gcs/bucket-policy.js +31 -0
- package/dist/lib/reporting/gcs/bucket-policy.mjs +7 -0
- package/dist/lib/reporting/gcs/errors.d.mts +5 -0
- package/dist/lib/reporting/gcs/errors.d.ts +5 -0
- package/dist/lib/reporting/gcs/errors.js +41 -0
- package/dist/lib/reporting/gcs/errors.mjs +16 -0
- package/dist/lib/reporting/gcs/fence.d.mts +36 -0
- package/dist/lib/reporting/gcs/fence.d.ts +36 -0
- package/dist/lib/reporting/gcs/fence.js +250 -0
- package/dist/lib/reporting/gcs/fence.mjs +230 -0
- package/dist/lib/reporting/gcs/index.d.mts +7 -0
- package/dist/lib/reporting/gcs/index.d.ts +7 -0
- package/dist/lib/reporting/gcs/index.js +41 -0
- package/dist/lib/reporting/gcs/index.mjs +12 -0
- package/dist/lib/reporting/gcs/io.d.mts +7 -0
- package/dist/lib/reporting/gcs/io.d.ts +7 -0
- package/dist/lib/reporting/gcs/io.js +95 -0
- package/dist/lib/reporting/gcs/io.mjs +69 -0
- package/dist/lib/reporting/gcs/reader.d.mts +43 -0
- package/dist/lib/reporting/gcs/reader.d.ts +43 -0
- package/dist/lib/reporting/gcs/reader.js +193 -0
- package/dist/lib/reporting/gcs/reader.mjs +169 -0
- package/dist/lib/reporting/gcs/validation.d.mts +5 -0
- package/dist/lib/reporting/gcs/validation.d.ts +5 -0
- package/dist/lib/reporting/gcs/validation.js +49 -0
- package/dist/lib/reporting/gcs/validation.mjs +25 -0
- package/dist/lib/reporting/ledger/handler.d.mts +1 -0
- package/dist/lib/reporting/ledger/handler.d.ts +1 -0
- package/dist/lib/reporting/ledger/index.d.mts +1 -0
- package/dist/lib/reporting/ledger/index.d.ts +1 -0
- package/dist/lib/reporting/ledger/index.js +2 -0
- package/dist/lib/reporting/ledger/index.mjs +1 -0
- package/dist/lib/reporting/ledger/managed-postgres.d.mts +16 -1
- package/dist/lib/reporting/ledger/managed-postgres.d.ts +16 -1
- package/dist/lib/reporting/ledger/managed-postgres.js +243 -2
- package/dist/lib/reporting/ledger/managed-postgres.mjs +248 -2
- package/dist/lib/reporting/ledger/object-writes.d.mts +69 -0
- package/dist/lib/reporting/ledger/object-writes.d.ts +69 -0
- package/dist/lib/reporting/ledger/object-writes.js +147 -0
- package/dist/lib/reporting/ledger/object-writes.mjs +114 -0
- package/dist/lib/schemas/index.d.mts +2 -0
- package/dist/lib/schemas/index.d.ts +2 -0
- package/dist/lib/schemas/index.js +3 -0
- package/dist/lib/schemas/index.mjs +2 -0
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/a2a-adapter.d.mts +50 -1
- package/dist/lib/server/a2a-adapter.d.ts +50 -1
- package/dist/lib/server/a2a-adapter.js +230 -7
- package/dist/lib/server/a2a-adapter.mjs +237 -7
- package/dist/lib/server/auth-signature.d.mts +84 -46
- package/dist/lib/server/auth-signature.d.ts +84 -46
- package/dist/lib/server/auth-signature.js +48 -14
- package/dist/lib/server/auth-signature.mjs +58 -15
- package/dist/lib/server/create-adcp-server.js +5 -16
- package/dist/lib/server/create-adcp-server.mjs +5 -16
- package/dist/lib/server/index.d.mts +1 -1
- package/dist/lib/server/index.d.ts +1 -1
- package/dist/lib/server/index.js +2 -0
- package/dist/lib/server/index.mjs +3 -1
- package/dist/lib/signing/agent-fetch.js +3 -33
- package/dist/lib/signing/agent-fetch.mjs +3 -33
- package/dist/lib/signing/middleware.d.mts +9 -1
- package/dist/lib/signing/middleware.d.ts +9 -1
- package/dist/lib/signing/operation-resolution.d.mts +37 -0
- package/dist/lib/signing/operation-resolution.d.ts +37 -0
- package/dist/lib/signing/operation-resolution.js +154 -0
- package/dist/lib/signing/operation-resolution.mjs +125 -0
- package/dist/lib/signing/postgres-replay-store.d.mts +1 -1
- package/dist/lib/signing/postgres-replay-store.d.ts +1 -1
- package/dist/lib/signing/redis-replay-store.d.mts +1 -1
- package/dist/lib/signing/redis-replay-store.d.ts +1 -1
- package/dist/lib/signing/server.d.mts +1 -0
- package/dist/lib/signing/server.d.ts +1 -0
- package/dist/lib/signing/server.js +7 -0
- package/dist/lib/signing/server.mjs +8 -0
- package/dist/lib/signing/verifier.d.mts +36 -1
- package/dist/lib/signing/verifier.d.ts +36 -1
- package/dist/lib/signing/verifier.js +66 -34
- package/dist/lib/signing/verifier.mjs +61 -34
- package/dist/lib/testing/client.js +8 -3
- package/dist/lib/testing/client.mjs +8 -3
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.mts +12 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.ts +12 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.js +29 -2
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.mjs +27 -1
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.d.mts +61 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.d.ts +61 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.js +298 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.mjs +276 -0
- package/dist/lib/testing/storyboard/request-signing/grader.d.mts +31 -1
- package/dist/lib/testing/storyboard/request-signing/grader.d.ts +31 -1
- package/dist/lib/testing/storyboard/request-signing/grader.js +11 -0
- package/dist/lib/testing/storyboard/request-signing/grader.mjs +11 -0
- package/dist/lib/testing/storyboard/request-signing/index.d.mts +5 -4
- package/dist/lib/testing/storyboard/request-signing/index.d.ts +5 -4
- package/dist/lib/testing/storyboard/request-signing/index.js +13 -0
- package/dist/lib/testing/storyboard/request-signing/index.mjs +18 -2
- package/dist/lib/testing/storyboard/request-signing/types.d.mts +43 -0
- package/dist/lib/testing/storyboard/request-signing/types.d.ts +43 -0
- package/dist/lib/testing/storyboard/request-signing/types.js +3 -0
- package/dist/lib/testing/storyboard/request-signing/types.mjs +2 -0
- package/dist/lib/testing/storyboard/request-signing/vector-loader.d.mts +42 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.d.ts +42 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.js +100 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +104 -3
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/testing/types.d.mts +10 -1
- package/dist/lib/testing/types.d.ts +10 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/docs/TYPE-SUMMARY.md +2 -2
- package/docs/ZOD-SCHEMAS.md +19 -0
- package/docs/guides/BUILD-AN-AGENT.md +9 -4
- package/docs/guides/CREATIVE-DELIVERY.md +34 -0
- package/docs/guides/OAUTH-ISSUER-BINDING.md +46 -0
- package/docs/guides/REPORTING-GCS-FENCE.md +52 -0
- package/docs/guides/REPORTING-GCS-MANAGED.md +56 -0
- package/docs/guides/REPORTING-OPERATIONS.md +4 -0
- package/docs/guides/SIGNING-GUIDE.md +20 -6
- package/docs/guides/VALIDATE-YOUR-AGENT.md +4 -0
- package/docs/guides/WEB-OAUTH.md +58 -163
- package/docs/guides/account-resolution.md +1 -1
- package/docs/llms.txt +2 -2
- package/docs/migration-14.0-to-14.1.md +1 -1
- package/docs/migration-14.x-rc-worksheet.md +8 -7
- package/docs/migration-buyer-agent-registry.md +1 -1
- package/docs/migration-oauth-issuer-bindings.md +14 -79
- package/package.json +19 -1
- package/skills/build-seller-agent/specialisms/signed-requests.md +2 -0
|
@@ -13,8 +13,10 @@ export type { AgentConfig, AgentOAuthTokens, AgentOAuthClient };
|
|
|
13
13
|
* Agent config storage interface
|
|
14
14
|
*
|
|
15
15
|
* Implement this to persist OAuth tokens back to agent configuration.
|
|
16
|
-
* Round-trip pending `oauth_code_verifier` and `oauth_discovery_state` verbatim
|
|
17
|
-
*
|
|
16
|
+
* Round-trip pending `oauth_code_verifier` and `oauth_discovery_state` verbatim.
|
|
17
|
+
* For oauth_tokens, oauth_client, oauth_code_verifier and oauth_discovery_state,
|
|
18
|
+
* an own undefined property clears; omission preserves the saved field.
|
|
19
|
+
* Resource overrides and other fields retain the storage backend's semantics.
|
|
18
20
|
* This allows different storage backends (file, database, memory)
|
|
19
21
|
* while keeping tokens in the agent config structure.
|
|
20
22
|
*/
|
|
@@ -76,6 +78,11 @@ export interface OAuthProviderConfig {
|
|
|
76
78
|
storage?: OAuthConfigStorage;
|
|
77
79
|
/** Authorization flow handler */
|
|
78
80
|
flowHandler: OAuthFlowHandler;
|
|
81
|
+
/**
|
|
82
|
+
* Explicitly allow fresh registration, PKCE work, redirects and automatic
|
|
83
|
+
* credential invalidation. Defaults to false; CLI factories opt in.
|
|
84
|
+
*/
|
|
85
|
+
allowInteractiveAuthorization?: boolean;
|
|
79
86
|
/** OAuth client metadata (required - use DEFAULT_CLIENT_METADATA as base) */
|
|
80
87
|
clientMetadata: OAuthClientMetadata;
|
|
81
88
|
/**
|
|
@@ -13,8 +13,10 @@ export type { AgentConfig, AgentOAuthTokens, AgentOAuthClient };
|
|
|
13
13
|
* Agent config storage interface
|
|
14
14
|
*
|
|
15
15
|
* Implement this to persist OAuth tokens back to agent configuration.
|
|
16
|
-
* Round-trip pending `oauth_code_verifier` and `oauth_discovery_state` verbatim
|
|
17
|
-
*
|
|
16
|
+
* Round-trip pending `oauth_code_verifier` and `oauth_discovery_state` verbatim.
|
|
17
|
+
* For oauth_tokens, oauth_client, oauth_code_verifier and oauth_discovery_state,
|
|
18
|
+
* an own undefined property clears; omission preserves the saved field.
|
|
19
|
+
* Resource overrides and other fields retain the storage backend's semantics.
|
|
18
20
|
* This allows different storage backends (file, database, memory)
|
|
19
21
|
* while keeping tokens in the agent config structure.
|
|
20
22
|
*/
|
|
@@ -76,6 +78,11 @@ export interface OAuthProviderConfig {
|
|
|
76
78
|
storage?: OAuthConfigStorage;
|
|
77
79
|
/** Authorization flow handler */
|
|
78
80
|
flowHandler: OAuthFlowHandler;
|
|
81
|
+
/**
|
|
82
|
+
* Explicitly allow fresh registration, PKCE work, redirects and automatic
|
|
83
|
+
* credential invalidation. Defaults to false; CLI factories opt in.
|
|
84
|
+
*/
|
|
85
|
+
allowInteractiveAuthorization?: boolean;
|
|
79
86
|
/** OAuth client metadata (required - use DEFAULT_CLIENT_METADATA as base) */
|
|
80
87
|
clientMetadata: OAuthClientMetadata;
|
|
81
88
|
/**
|
|
@@ -103,10 +103,11 @@ function fromMCPClientInfo(info) {
|
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
function isOAuthIssuer(value) {
|
|
106
|
-
if (typeof value !== "string" || value
|
|
106
|
+
if (typeof value !== "string" || !value || /[\u0000-\u0020\u007f\\]/.test(value)) return false;
|
|
107
107
|
try {
|
|
108
108
|
const url = new URL(value);
|
|
109
|
-
|
|
109
|
+
const rawAuthority = value.match(/^[a-z][a-z0-9+.-]*:\/\/([^/?#]*)/i)?.[1];
|
|
110
|
+
return (url.protocol === "https:" || url.protocol === "http:") && !rawAuthority?.includes("@") && !url.username && !url.password && !url.search && !url.hash;
|
|
110
111
|
} catch {
|
|
111
112
|
return false;
|
|
112
113
|
}
|
|
@@ -71,10 +71,11 @@ function fromMCPClientInfo(info) {
|
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
function isOAuthIssuer(value) {
|
|
74
|
-
if (typeof value !== "string" || value
|
|
74
|
+
if (typeof value !== "string" || !value || /[\u0000-\u0020\u007f\\]/.test(value)) return false;
|
|
75
75
|
try {
|
|
76
76
|
const url = new URL(value);
|
|
77
|
-
|
|
77
|
+
const rawAuthority = value.match(/^[a-z][a-z0-9+.-]*:\/\/([^/?#]*)/i)?.[1];
|
|
78
|
+
return (url.protocol === "https:" || url.protocol === "http:") && !rawAuthority?.includes("@") && !url.username && !url.password && !url.search && !url.hash;
|
|
78
79
|
} catch {
|
|
79
80
|
return false;
|
|
80
81
|
}
|
|
@@ -66,6 +66,8 @@ export interface PendingWebFlow {
|
|
|
66
66
|
resourceOverrideSnapshot?: string | null;
|
|
67
67
|
scope?: string;
|
|
68
68
|
authorizationServerUrl: string;
|
|
69
|
+
/** Validated AS issuer frozen at start. Legacy rows without it cannot spend credentials. */
|
|
70
|
+
authorizationServerIssuer?: string;
|
|
69
71
|
/** Persist as JSON (e.g. Postgres `jsonb`); contents are MCP SDK-typed. */
|
|
70
72
|
clientInformation: OAuthClientInformation;
|
|
71
73
|
createdAt: Date;
|
|
@@ -66,6 +66,8 @@ export interface PendingWebFlow {
|
|
|
66
66
|
resourceOverrideSnapshot?: string | null;
|
|
67
67
|
scope?: string;
|
|
68
68
|
authorizationServerUrl: string;
|
|
69
|
+
/** Validated AS issuer frozen at start. Legacy rows without it cannot spend credentials. */
|
|
70
|
+
authorizationServerIssuer?: string;
|
|
69
71
|
/** Persist as JSON (e.g. Postgres `jsonb`); contents are MCP SDK-typed. */
|
|
70
72
|
clientInformation: OAuthClientInformation;
|
|
71
73
|
createdAt: Date;
|
|
@@ -145,6 +145,8 @@ async function startWebOAuthFlow(opts) {
|
|
|
145
145
|
throw new import_types.OAuthError(`No OAuth metadata at ${asUrl.toString()}`, "no_authorization_server_metadata", agent.id);
|
|
146
146
|
}
|
|
147
147
|
(0, import_types.assertOAuthServerIssuer)(asMetadata, asUrl.toString());
|
|
148
|
+
const authorizationServerIssuer = asUrl.toString();
|
|
149
|
+
assertSavedCredentialIssuers(agent, authorizationServerIssuer);
|
|
148
150
|
const resource = resourceOverride ? new URL(resourceOverride) : prm?.resource ? new URL(prm.resource) : (0, import_auth_utils.resourceUrlFromServerUrl)(new URL(agent.agent_uri));
|
|
149
151
|
const baseClientMetadata = {
|
|
150
152
|
...import_types.DEFAULT_CLIENT_METADATA,
|
|
@@ -159,7 +161,8 @@ async function startWebOAuthFlow(opts) {
|
|
|
159
161
|
asMetadata,
|
|
160
162
|
clientMetadata: baseClientMetadata,
|
|
161
163
|
fetchFn: guardedFetch,
|
|
162
|
-
allowConfidentialClient
|
|
164
|
+
allowConfidentialClient,
|
|
165
|
+
issuer: authorizationServerIssuer
|
|
163
166
|
});
|
|
164
167
|
const state = (generateState ?? defaultGenerateState)();
|
|
165
168
|
const { authorizationUrl, codeVerifier } = await (0, import_auth.startAuthorization)(asUrl.toString(), {
|
|
@@ -187,6 +190,7 @@ async function startWebOAuthFlow(opts) {
|
|
|
187
190
|
resourceOverrideSnapshot: agent.oauth_resource ?? null,
|
|
188
191
|
scope,
|
|
189
192
|
authorizationServerUrl: asUrl.toString(),
|
|
193
|
+
authorizationServerIssuer,
|
|
190
194
|
clientInformation,
|
|
191
195
|
createdAt: now,
|
|
192
196
|
expiresAt,
|
|
@@ -232,11 +236,16 @@ async function completeWebOAuthFlow(opts) {
|
|
|
232
236
|
fetchFn: guardedFetch
|
|
233
237
|
});
|
|
234
238
|
(0, import_types.assertOAuthServerIssuer)(asMetadata, flow.authorizationServerUrl);
|
|
239
|
+
assertFrozenIssuer(flow);
|
|
235
240
|
(0, import_types.assertOAuthCredentialIssuer)(
|
|
236
241
|
flow.clientInformation,
|
|
237
|
-
flow.
|
|
242
|
+
flow.authorizationServerIssuer,
|
|
238
243
|
!!flow.clientInformation.client_secret
|
|
239
244
|
);
|
|
245
|
+
if (agentStorage) {
|
|
246
|
+
agentForPersistence = await agentStorage.loadAgent(flow.agentId);
|
|
247
|
+
assertAgentUnchangedForFlow(agentForPersistence, flow);
|
|
248
|
+
}
|
|
240
249
|
let tokens;
|
|
241
250
|
try {
|
|
242
251
|
tokens = await (0, import_auth.exchangeAuthorization)(flow.authorizationServerUrl, {
|
|
@@ -248,7 +257,7 @@ async function completeWebOAuthFlow(opts) {
|
|
|
248
257
|
resource: flow.resource ? new URL(flow.resource) : void 0,
|
|
249
258
|
fetchFn: guardedFetch
|
|
250
259
|
});
|
|
251
|
-
tokens = { ...tokens, issuer: flow.
|
|
260
|
+
tokens = { ...tokens, issuer: flow.authorizationServerIssuer };
|
|
252
261
|
} catch (err) {
|
|
253
262
|
throw wrapTokenExchangeError(err);
|
|
254
263
|
}
|
|
@@ -262,7 +271,8 @@ async function completeWebOAuthFlow(opts) {
|
|
|
262
271
|
} else if (flow.resourceOverrideAction === "clear") {
|
|
263
272
|
delete agentForPersistence.oauth_resource;
|
|
264
273
|
}
|
|
265
|
-
|
|
274
|
+
agentForPersistence.oauth_code_verifier = void 0;
|
|
275
|
+
agentForPersistence.oauth_discovery_state = void 0;
|
|
266
276
|
await agentStorage.saveAgent(agentForPersistence);
|
|
267
277
|
persisted = true;
|
|
268
278
|
}
|
|
@@ -274,6 +284,20 @@ async function completeWebOAuthFlow(opts) {
|
|
|
274
284
|
persisted
|
|
275
285
|
};
|
|
276
286
|
}
|
|
287
|
+
function assertSavedCredentialIssuers(agent, issuer) {
|
|
288
|
+
if (agent.oauth_tokens) (0, import_types.assertOAuthCredentialIssuer)(agent.oauth_tokens, issuer, !!agent.oauth_tokens.refresh_token);
|
|
289
|
+
if (agent.oauth_client) (0, import_types.assertOAuthCredentialIssuer)(agent.oauth_client, issuer, !!agent.oauth_client.client_secret);
|
|
290
|
+
}
|
|
291
|
+
function assertFrozenIssuer(flow) {
|
|
292
|
+
if (!flow.authorizationServerIssuer) {
|
|
293
|
+
throw new import_types.OAuthError(
|
|
294
|
+
"Pending OAuth flow lacks a validated authorization-server issuer. Start a new owner sign-in.",
|
|
295
|
+
"oauth_issuer_required",
|
|
296
|
+
flow.agentId
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
(0, import_types.assertOAuthServerIssuer)({ issuer: flow.authorizationServerIssuer }, flow.authorizationServerUrl);
|
|
300
|
+
}
|
|
277
301
|
function assertAgentUnchangedForFlow(agent, flow) {
|
|
278
302
|
if (!agent) {
|
|
279
303
|
throw new AgentVanishedDuringFlowError(flow.agentId);
|
|
@@ -281,6 +305,13 @@ function assertAgentUnchangedForFlow(agent, flow) {
|
|
|
281
305
|
if (agent.agent_uri !== flow.agentUrl) {
|
|
282
306
|
throw new AgentChangedDuringFlowError(flow.agentId);
|
|
283
307
|
}
|
|
308
|
+
if (!agent.oauth_client) throw new AgentChangedDuringFlowError(flow.agentId);
|
|
309
|
+
const currentClient = (0, import_types.toMCPClientInfo)(agent.oauth_client);
|
|
310
|
+
for (const key of ["client_id", "client_secret", "client_secret_expires_at", "issuer"]) {
|
|
311
|
+
if (currentClient[key] !== flow.clientInformation[key]) throw new AgentChangedDuringFlowError(flow.agentId);
|
|
312
|
+
}
|
|
313
|
+
assertFrozenIssuer(flow);
|
|
314
|
+
assertSavedCredentialIssuers(agent, flow.authorizationServerIssuer);
|
|
284
315
|
if (Object.prototype.hasOwnProperty.call(flow, "resourceOverrideSnapshot") && (agent.oauth_resource ?? null) !== flow.resourceOverrideSnapshot) {
|
|
285
316
|
throw new AgentChangedDuringFlowError(flow.agentId);
|
|
286
317
|
}
|
|
@@ -416,9 +447,8 @@ function createSsrfSafeOAuthFetch(trustedFetchFn, allowHttp) {
|
|
|
416
447
|
});
|
|
417
448
|
}
|
|
418
449
|
async function resolveClientInformation(args) {
|
|
419
|
-
const { agent, agentStorage, asUrl, asMetadata, clientMetadata, fetchFn, allowConfidentialClient } = args;
|
|
450
|
+
const { agent, agentStorage, asUrl, asMetadata, clientMetadata, fetchFn, allowConfidentialClient, issuer } = args;
|
|
420
451
|
if (agent.oauth_client) {
|
|
421
|
-
(0, import_types.assertOAuthCredentialIssuer)(agent.oauth_client, asUrl.toString(), !!agent.oauth_client.client_secret);
|
|
422
452
|
return (0, import_types.toMCPClientInfo)(agent.oauth_client);
|
|
423
453
|
}
|
|
424
454
|
if (!asMetadata.registration_endpoint) {
|
|
@@ -428,17 +458,20 @@ async function resolveClientInformation(args) {
|
|
|
428
458
|
agent.id
|
|
429
459
|
);
|
|
430
460
|
}
|
|
431
|
-
const
|
|
461
|
+
const response = await (0, import_auth.registerClient)(asUrl.toString(), {
|
|
432
462
|
metadata: asMetadata,
|
|
433
463
|
clientMetadata,
|
|
434
464
|
fetchFn
|
|
435
465
|
});
|
|
436
|
-
const registered = { ...
|
|
466
|
+
const registered = { ...response, issuer };
|
|
437
467
|
if (registered.client_secret && !allowConfidentialClient) {
|
|
438
468
|
throw new ConfidentialClientNotAllowedError(agent.id);
|
|
439
469
|
}
|
|
440
470
|
if (agentStorage) {
|
|
441
|
-
const fresh = await agentStorage.loadAgent(agent.id)
|
|
471
|
+
const fresh = await agentStorage.loadAgent(agent.id);
|
|
472
|
+
if (!fresh) throw new AgentVanishedDuringFlowError(agent.id);
|
|
473
|
+
if (fresh.agent_uri !== agent.agent_uri || fresh.oauth_client) throw new AgentChangedDuringFlowError(agent.id);
|
|
474
|
+
assertSavedCredentialIssuers(fresh, issuer);
|
|
442
475
|
fresh.oauth_client = (0, import_types.fromMCPClientInfo)(registered);
|
|
443
476
|
await agentStorage.saveAgent(fresh);
|
|
444
477
|
}
|
|
@@ -124,6 +124,8 @@ async function startWebOAuthFlow(opts) {
|
|
|
124
124
|
throw new OAuthError(`No OAuth metadata at ${asUrl.toString()}`, "no_authorization_server_metadata", agent.id);
|
|
125
125
|
}
|
|
126
126
|
assertOAuthServerIssuer(asMetadata, asUrl.toString());
|
|
127
|
+
const authorizationServerIssuer = asUrl.toString();
|
|
128
|
+
assertSavedCredentialIssuers(agent, authorizationServerIssuer);
|
|
127
129
|
const resource = resourceOverride ? new URL(resourceOverride) : prm?.resource ? new URL(prm.resource) : resourceUrlFromServerUrl(new URL(agent.agent_uri));
|
|
128
130
|
const baseClientMetadata = {
|
|
129
131
|
...DEFAULT_CLIENT_METADATA,
|
|
@@ -138,7 +140,8 @@ async function startWebOAuthFlow(opts) {
|
|
|
138
140
|
asMetadata,
|
|
139
141
|
clientMetadata: baseClientMetadata,
|
|
140
142
|
fetchFn: guardedFetch,
|
|
141
|
-
allowConfidentialClient
|
|
143
|
+
allowConfidentialClient,
|
|
144
|
+
issuer: authorizationServerIssuer
|
|
142
145
|
});
|
|
143
146
|
const state = (generateState ?? defaultGenerateState)();
|
|
144
147
|
const { authorizationUrl, codeVerifier } = await startAuthorization(asUrl.toString(), {
|
|
@@ -166,6 +169,7 @@ async function startWebOAuthFlow(opts) {
|
|
|
166
169
|
resourceOverrideSnapshot: agent.oauth_resource ?? null,
|
|
167
170
|
scope,
|
|
168
171
|
authorizationServerUrl: asUrl.toString(),
|
|
172
|
+
authorizationServerIssuer,
|
|
169
173
|
clientInformation,
|
|
170
174
|
createdAt: now,
|
|
171
175
|
expiresAt,
|
|
@@ -211,11 +215,16 @@ async function completeWebOAuthFlow(opts) {
|
|
|
211
215
|
fetchFn: guardedFetch
|
|
212
216
|
});
|
|
213
217
|
assertOAuthServerIssuer(asMetadata, flow.authorizationServerUrl);
|
|
218
|
+
assertFrozenIssuer(flow);
|
|
214
219
|
assertOAuthCredentialIssuer(
|
|
215
220
|
flow.clientInformation,
|
|
216
|
-
flow.
|
|
221
|
+
flow.authorizationServerIssuer,
|
|
217
222
|
!!flow.clientInformation.client_secret
|
|
218
223
|
);
|
|
224
|
+
if (agentStorage) {
|
|
225
|
+
agentForPersistence = await agentStorage.loadAgent(flow.agentId);
|
|
226
|
+
assertAgentUnchangedForFlow(agentForPersistence, flow);
|
|
227
|
+
}
|
|
219
228
|
let tokens;
|
|
220
229
|
try {
|
|
221
230
|
tokens = await exchangeAuthorization(flow.authorizationServerUrl, {
|
|
@@ -227,7 +236,7 @@ async function completeWebOAuthFlow(opts) {
|
|
|
227
236
|
resource: flow.resource ? new URL(flow.resource) : void 0,
|
|
228
237
|
fetchFn: guardedFetch
|
|
229
238
|
});
|
|
230
|
-
tokens = { ...tokens, issuer: flow.
|
|
239
|
+
tokens = { ...tokens, issuer: flow.authorizationServerIssuer };
|
|
231
240
|
} catch (err) {
|
|
232
241
|
throw wrapTokenExchangeError(err);
|
|
233
242
|
}
|
|
@@ -241,7 +250,8 @@ async function completeWebOAuthFlow(opts) {
|
|
|
241
250
|
} else if (flow.resourceOverrideAction === "clear") {
|
|
242
251
|
delete agentForPersistence.oauth_resource;
|
|
243
252
|
}
|
|
244
|
-
|
|
253
|
+
agentForPersistence.oauth_code_verifier = void 0;
|
|
254
|
+
agentForPersistence.oauth_discovery_state = void 0;
|
|
245
255
|
await agentStorage.saveAgent(agentForPersistence);
|
|
246
256
|
persisted = true;
|
|
247
257
|
}
|
|
@@ -253,6 +263,20 @@ async function completeWebOAuthFlow(opts) {
|
|
|
253
263
|
persisted
|
|
254
264
|
};
|
|
255
265
|
}
|
|
266
|
+
function assertSavedCredentialIssuers(agent, issuer) {
|
|
267
|
+
if (agent.oauth_tokens) assertOAuthCredentialIssuer(agent.oauth_tokens, issuer, !!agent.oauth_tokens.refresh_token);
|
|
268
|
+
if (agent.oauth_client) assertOAuthCredentialIssuer(agent.oauth_client, issuer, !!agent.oauth_client.client_secret);
|
|
269
|
+
}
|
|
270
|
+
function assertFrozenIssuer(flow) {
|
|
271
|
+
if (!flow.authorizationServerIssuer) {
|
|
272
|
+
throw new OAuthError(
|
|
273
|
+
"Pending OAuth flow lacks a validated authorization-server issuer. Start a new owner sign-in.",
|
|
274
|
+
"oauth_issuer_required",
|
|
275
|
+
flow.agentId
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
assertOAuthServerIssuer({ issuer: flow.authorizationServerIssuer }, flow.authorizationServerUrl);
|
|
279
|
+
}
|
|
256
280
|
function assertAgentUnchangedForFlow(agent, flow) {
|
|
257
281
|
if (!agent) {
|
|
258
282
|
throw new AgentVanishedDuringFlowError(flow.agentId);
|
|
@@ -260,6 +284,13 @@ function assertAgentUnchangedForFlow(agent, flow) {
|
|
|
260
284
|
if (agent.agent_uri !== flow.agentUrl) {
|
|
261
285
|
throw new AgentChangedDuringFlowError(flow.agentId);
|
|
262
286
|
}
|
|
287
|
+
if (!agent.oauth_client) throw new AgentChangedDuringFlowError(flow.agentId);
|
|
288
|
+
const currentClient = toMCPClientInfo(agent.oauth_client);
|
|
289
|
+
for (const key of ["client_id", "client_secret", "client_secret_expires_at", "issuer"]) {
|
|
290
|
+
if (currentClient[key] !== flow.clientInformation[key]) throw new AgentChangedDuringFlowError(flow.agentId);
|
|
291
|
+
}
|
|
292
|
+
assertFrozenIssuer(flow);
|
|
293
|
+
assertSavedCredentialIssuers(agent, flow.authorizationServerIssuer);
|
|
263
294
|
if (Object.prototype.hasOwnProperty.call(flow, "resourceOverrideSnapshot") && (agent.oauth_resource ?? null) !== flow.resourceOverrideSnapshot) {
|
|
264
295
|
throw new AgentChangedDuringFlowError(flow.agentId);
|
|
265
296
|
}
|
|
@@ -395,9 +426,8 @@ function createSsrfSafeOAuthFetch(trustedFetchFn, allowHttp) {
|
|
|
395
426
|
});
|
|
396
427
|
}
|
|
397
428
|
async function resolveClientInformation(args) {
|
|
398
|
-
const { agent, agentStorage, asUrl, asMetadata, clientMetadata, fetchFn, allowConfidentialClient } = args;
|
|
429
|
+
const { agent, agentStorage, asUrl, asMetadata, clientMetadata, fetchFn, allowConfidentialClient, issuer } = args;
|
|
399
430
|
if (agent.oauth_client) {
|
|
400
|
-
assertOAuthCredentialIssuer(agent.oauth_client, asUrl.toString(), !!agent.oauth_client.client_secret);
|
|
401
431
|
return toMCPClientInfo(agent.oauth_client);
|
|
402
432
|
}
|
|
403
433
|
if (!asMetadata.registration_endpoint) {
|
|
@@ -407,17 +437,20 @@ async function resolveClientInformation(args) {
|
|
|
407
437
|
agent.id
|
|
408
438
|
);
|
|
409
439
|
}
|
|
410
|
-
const
|
|
440
|
+
const response = await registerClient(asUrl.toString(), {
|
|
411
441
|
metadata: asMetadata,
|
|
412
442
|
clientMetadata,
|
|
413
443
|
fetchFn
|
|
414
444
|
});
|
|
415
|
-
const registered = { ...
|
|
445
|
+
const registered = { ...response, issuer };
|
|
416
446
|
if (registered.client_secret && !allowConfidentialClient) {
|
|
417
447
|
throw new ConfidentialClientNotAllowedError(agent.id);
|
|
418
448
|
}
|
|
419
449
|
if (agentStorage) {
|
|
420
|
-
const fresh = await agentStorage.loadAgent(agent.id)
|
|
450
|
+
const fresh = await agentStorage.loadAgent(agent.id);
|
|
451
|
+
if (!fresh) throw new AgentVanishedDuringFlowError(agent.id);
|
|
452
|
+
if (fresh.agent_uri !== agent.agent_uri || fresh.oauth_client) throw new AgentChangedDuringFlowError(agent.id);
|
|
453
|
+
assertSavedCredentialIssuers(fresh, issuer);
|
|
421
454
|
fresh.oauth_client = fromMCPClientInfo(registered);
|
|
422
455
|
await agentStorage.saveAgent(fresh);
|
|
423
456
|
}
|
|
@@ -5,7 +5,7 @@ export interface CanonicalReference {
|
|
|
5
5
|
}
|
|
6
6
|
export type CanonicalReferenceKind = 'format_schema' | 'platform_extensions' | 'generic';
|
|
7
7
|
export type CanonicalReferenceStatus = 'resolved' | 'unresolvable' | 'invalid_document' | 'invalid_schema' | 'digest_mismatch' | 'blocked_unsafe_url' | 'invalid_ref';
|
|
8
|
-
export type CanonicalReferenceErrorCode = 'invalid_ref' | 'non_https_url' | 'unsafe_url' | 'redirect_blocked' | 'http_error' | 'network_error' | 'body_too_large' | 'invalid_json' | 'document_too_deep' | 'invalid_json_schema' | 'unsupported_schema_draft' | 'ref_sandbox_violation' | 'external_ref_unpinned' | 'keyword_limit_exceeded' | 'budget_exceeded' | 'digest_mismatch';
|
|
8
|
+
export type CanonicalReferenceErrorCode = 'invalid_ref' | 'non_https_url' | 'unsafe_url' | 'redirect_blocked' | 'http_error' | 'network_error' | 'body_too_large' | 'invalid_json' | 'document_too_deep' | 'invalid_json_schema' | 'unsupported_schema_draft' | 'ref_sandbox_violation' | 'external_ref_unpinned' | 'keyword_limit_exceeded' | 'budget_exceeded' | 'invalid_options' | 'aborted' | 'access_denied' | 'digest_mismatch';
|
|
9
9
|
export interface CanonicalReferenceError {
|
|
10
10
|
code: CanonicalReferenceErrorCode;
|
|
11
11
|
message: string;
|
|
@@ -63,7 +63,25 @@ export interface CanonicalReferenceCacheOptions {
|
|
|
63
63
|
/** Maximum estimated retained bytes. Default 32 MiB. Set to 0 to disable caching. */
|
|
64
64
|
maxBytes?: number;
|
|
65
65
|
}
|
|
66
|
+
/** Host-owned authenticated document transport. It must enforce destination scope before I/O. */
|
|
67
|
+
export interface CanonicalDocumentReader {
|
|
68
|
+
read(request: {
|
|
69
|
+
uri: string;
|
|
70
|
+
maxBytes: number;
|
|
71
|
+
signal: AbortSignal;
|
|
72
|
+
}): Promise<{
|
|
73
|
+
body: Uint8Array;
|
|
74
|
+
contentType?: string;
|
|
75
|
+
}>;
|
|
76
|
+
}
|
|
77
|
+
/** Stable, secret-free failure for an authenticated document transport. */
|
|
78
|
+
export declare class CanonicalDocumentReadError extends Error {
|
|
79
|
+
readonly code: 'access_denied' | 'unsafe_url' | 'body_too_large' | 'network_error' | 'invalid_options' | 'aborted';
|
|
80
|
+
constructor(code: 'access_denied' | 'unsafe_url' | 'body_too_large' | 'network_error' | 'invalid_options' | 'aborted');
|
|
81
|
+
}
|
|
66
82
|
export interface CanonicalReferenceResolverOptions {
|
|
83
|
+
/** Private transports bypass all caches so every resolution re-authorizes. No HTTP fallback. */
|
|
84
|
+
documentReader?: CanonicalDocumentReader;
|
|
67
85
|
/** Caller-owned cache. Defaults to a fresh bounded per-resolver LRU. */
|
|
68
86
|
cache?: CanonicalReferenceCache;
|
|
69
87
|
/** Default 5_000 ms. */
|
|
@@ -5,7 +5,7 @@ export interface CanonicalReference {
|
|
|
5
5
|
}
|
|
6
6
|
export type CanonicalReferenceKind = 'format_schema' | 'platform_extensions' | 'generic';
|
|
7
7
|
export type CanonicalReferenceStatus = 'resolved' | 'unresolvable' | 'invalid_document' | 'invalid_schema' | 'digest_mismatch' | 'blocked_unsafe_url' | 'invalid_ref';
|
|
8
|
-
export type CanonicalReferenceErrorCode = 'invalid_ref' | 'non_https_url' | 'unsafe_url' | 'redirect_blocked' | 'http_error' | 'network_error' | 'body_too_large' | 'invalid_json' | 'document_too_deep' | 'invalid_json_schema' | 'unsupported_schema_draft' | 'ref_sandbox_violation' | 'external_ref_unpinned' | 'keyword_limit_exceeded' | 'budget_exceeded' | 'digest_mismatch';
|
|
8
|
+
export type CanonicalReferenceErrorCode = 'invalid_ref' | 'non_https_url' | 'unsafe_url' | 'redirect_blocked' | 'http_error' | 'network_error' | 'body_too_large' | 'invalid_json' | 'document_too_deep' | 'invalid_json_schema' | 'unsupported_schema_draft' | 'ref_sandbox_violation' | 'external_ref_unpinned' | 'keyword_limit_exceeded' | 'budget_exceeded' | 'invalid_options' | 'aborted' | 'access_denied' | 'digest_mismatch';
|
|
9
9
|
export interface CanonicalReferenceError {
|
|
10
10
|
code: CanonicalReferenceErrorCode;
|
|
11
11
|
message: string;
|
|
@@ -63,7 +63,25 @@ export interface CanonicalReferenceCacheOptions {
|
|
|
63
63
|
/** Maximum estimated retained bytes. Default 32 MiB. Set to 0 to disable caching. */
|
|
64
64
|
maxBytes?: number;
|
|
65
65
|
}
|
|
66
|
+
/** Host-owned authenticated document transport. It must enforce destination scope before I/O. */
|
|
67
|
+
export interface CanonicalDocumentReader {
|
|
68
|
+
read(request: {
|
|
69
|
+
uri: string;
|
|
70
|
+
maxBytes: number;
|
|
71
|
+
signal: AbortSignal;
|
|
72
|
+
}): Promise<{
|
|
73
|
+
body: Uint8Array;
|
|
74
|
+
contentType?: string;
|
|
75
|
+
}>;
|
|
76
|
+
}
|
|
77
|
+
/** Stable, secret-free failure for an authenticated document transport. */
|
|
78
|
+
export declare class CanonicalDocumentReadError extends Error {
|
|
79
|
+
readonly code: 'access_denied' | 'unsafe_url' | 'body_too_large' | 'network_error' | 'invalid_options' | 'aborted';
|
|
80
|
+
constructor(code: 'access_denied' | 'unsafe_url' | 'body_too_large' | 'network_error' | 'invalid_options' | 'aborted');
|
|
81
|
+
}
|
|
66
82
|
export interface CanonicalReferenceResolverOptions {
|
|
83
|
+
/** Private transports bypass all caches so every resolution re-authorizes. No HTTP fallback. */
|
|
84
|
+
documentReader?: CanonicalDocumentReader;
|
|
67
85
|
/** Caller-owned cache. Defaults to a fresh bounded per-resolver LRU. */
|
|
68
86
|
cache?: CanonicalReferenceCache;
|
|
69
87
|
/** Default 5_000 ms. */
|
|
@@ -28,6 +28,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var canonical_references_exports = {};
|
|
30
30
|
__export(canonical_references_exports, {
|
|
31
|
+
CanonicalDocumentReadError: () => CanonicalDocumentReadError,
|
|
31
32
|
canonicalReferenceCacheKey: () => canonicalReferenceCacheKey,
|
|
32
33
|
createCanonicalReferenceCache: () => createCanonicalReferenceCache,
|
|
33
34
|
createCanonicalReferenceResolver: () => createCanonicalReferenceResolver,
|
|
@@ -44,6 +45,18 @@ var import_sandbox_refs = require('../v2/format-schema/sandbox-refs.js');
|
|
|
44
45
|
var import_regex_safety = require('../v2/format-schema/regex-safety.js');
|
|
45
46
|
var import_strict_json = require('../signing/agent-resolver/strict-json.js');
|
|
46
47
|
const Ajv2020 = require("ajv/dist/2020");
|
|
48
|
+
class CanonicalDocumentReadError extends Error {
|
|
49
|
+
constructor(code) {
|
|
50
|
+
super(`Canonical document read: ${code}`);
|
|
51
|
+
this.code = code;
|
|
52
|
+
this.name = "CanonicalDocumentReadError";
|
|
53
|
+
Object.defineProperty(this, /* @__PURE__ */ Symbol.for("adcp.canonicalDocumentReadError"), { value: true });
|
|
54
|
+
}
|
|
55
|
+
code;
|
|
56
|
+
}
|
|
57
|
+
function isCanonicalDocumentReadError(error) {
|
|
58
|
+
return !!error && typeof error === "object" && error[/* @__PURE__ */ Symbol.for("adcp.canonicalDocumentReadError")] === true;
|
|
59
|
+
}
|
|
47
60
|
const DEFAULT_TIMEOUT_MS = 5e3;
|
|
48
61
|
const DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
|
|
49
62
|
const DEFAULT_MAX_TOTAL_REF_BYTES = 8 * 1024 * 1024;
|
|
@@ -149,10 +162,13 @@ function exceedsJsonDocumentDepth(value) {
|
|
|
149
162
|
return false;
|
|
150
163
|
}
|
|
151
164
|
function createCanonicalReferenceResolver(defaults = {}) {
|
|
165
|
+
if (defaults.documentReader && (!Number.isSafeInteger(defaults.timeoutMs ?? DEFAULT_TIMEOUT_MS) || (defaults.timeoutMs ?? DEFAULT_TIMEOUT_MS) < 1 || (defaults.timeoutMs ?? DEFAULT_TIMEOUT_MS) > 6e4 || !Number.isSafeInteger(defaults.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES) || (defaults.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES) < 1 || (defaults.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES) > 8 * 1024 * 1024))
|
|
166
|
+
throw new RangeError("Private canonical document transport limits are invalid");
|
|
152
167
|
const cache = defaults.cache ?? createCanonicalReferenceCache();
|
|
153
168
|
const merge = (options = {}) => ({
|
|
154
169
|
...defaults,
|
|
155
170
|
...options,
|
|
171
|
+
documentReader: defaults.documentReader ?? options.documentReader,
|
|
156
172
|
cache
|
|
157
173
|
});
|
|
158
174
|
return {
|
|
@@ -205,7 +221,7 @@ async function fetchJsonReference(ref, options) {
|
|
|
205
221
|
const kind = options.kind ?? "generic";
|
|
206
222
|
const valid = validateRef(ref, kind, options);
|
|
207
223
|
if (!valid.ok) return valid.result;
|
|
208
|
-
const cache = options.cache;
|
|
224
|
+
const cache = options.documentReader ? void 0 : options.cache;
|
|
209
225
|
const cacheKey = canonicalReferenceCacheKey(ref, options);
|
|
210
226
|
const cached = cache?.get(cacheKey);
|
|
211
227
|
if (cached) {
|
|
@@ -223,15 +239,70 @@ async function fetchJsonReference(ref, options) {
|
|
|
223
239
|
}
|
|
224
240
|
let response;
|
|
225
241
|
try {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
242
|
+
if (options.documentReader) {
|
|
243
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
244
|
+
const maxBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
|
|
245
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 6e4 || !Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > 8 * 1024 * 1024)
|
|
246
|
+
throw new CanonicalDocumentReadError("invalid_options");
|
|
247
|
+
const controller = new AbortController();
|
|
248
|
+
const onAbort = () => controller.abort(new CanonicalDocumentReadError("aborted"));
|
|
249
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
250
|
+
if (options.signal?.aborted) onAbort();
|
|
251
|
+
let abortListener;
|
|
252
|
+
const timer = setTimeout(() => controller.abort(new CanonicalDocumentReadError("network_error")), timeoutMs);
|
|
253
|
+
try {
|
|
254
|
+
controller.signal.throwIfAborted();
|
|
255
|
+
const read = await Promise.race([
|
|
256
|
+
options.documentReader.read({ uri: ref.uri, maxBytes, signal: controller.signal }),
|
|
257
|
+
new Promise((_, reject) => {
|
|
258
|
+
abortListener = () => reject(controller.signal.reason);
|
|
259
|
+
controller.signal.addEventListener("abort", abortListener, { once: true });
|
|
260
|
+
if (controller.signal.aborted) abortListener();
|
|
261
|
+
})
|
|
262
|
+
]);
|
|
263
|
+
controller.signal.throwIfAborted();
|
|
264
|
+
if (!(read?.body instanceof Uint8Array) || read.body.byteLength > maxBytes)
|
|
265
|
+
throw new CanonicalDocumentReadError("body_too_large");
|
|
266
|
+
response = {
|
|
267
|
+
status: 200,
|
|
268
|
+
body: Uint8Array.from(read.body),
|
|
269
|
+
headers: { "content-type": read.contentType ?? "" }
|
|
270
|
+
};
|
|
271
|
+
} finally {
|
|
272
|
+
clearTimeout(timer);
|
|
273
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
274
|
+
if (abortListener) controller.signal.removeEventListener("abort", abortListener);
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
response = await (0, import_ssrf_fetch.ssrfSafeFetch)(ref.uri, {
|
|
278
|
+
method: "GET",
|
|
279
|
+
headers: { accept: "application/json, application/schema+json" },
|
|
280
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
281
|
+
maxBodyBytes: options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,
|
|
282
|
+
allowPrivateIp: options.allowPrivateNetwork === true,
|
|
283
|
+
signal: options.signal
|
|
284
|
+
});
|
|
285
|
+
}
|
|
234
286
|
} catch (err) {
|
|
287
|
+
if (isCanonicalDocumentReadError(err)) {
|
|
288
|
+
const code = [
|
|
289
|
+
"access_denied",
|
|
290
|
+
"unsafe_url",
|
|
291
|
+
"body_too_large",
|
|
292
|
+
"network_error",
|
|
293
|
+
"invalid_options",
|
|
294
|
+
"aborted"
|
|
295
|
+
].includes(err.code) ? err.code : "network_error";
|
|
296
|
+
return fail(
|
|
297
|
+
kind,
|
|
298
|
+
ref,
|
|
299
|
+
cacheKey,
|
|
300
|
+
code === "unsafe_url" ? "blocked_unsafe_url" : "unresolvable",
|
|
301
|
+
code,
|
|
302
|
+
`Canonical document read failed: ${code}`,
|
|
303
|
+
{ retryable: code === "network_error" }
|
|
304
|
+
);
|
|
305
|
+
}
|
|
235
306
|
if (err instanceof import_ssrf_fetch.SsrfRefusedError) {
|
|
236
307
|
if (err.code === "body_exceeds_limit") {
|
|
237
308
|
return fail(
|
|
@@ -753,6 +824,7 @@ function countSchemaKeywords(root) {
|
|
|
753
824
|
}
|
|
754
825
|
// Annotate the CommonJS export names for ESM import in node:
|
|
755
826
|
0 && (module.exports = {
|
|
827
|
+
CanonicalDocumentReadError,
|
|
756
828
|
canonicalReferenceCacheKey,
|
|
757
829
|
createCanonicalReferenceCache,
|
|
758
830
|
createCanonicalReferenceResolver,
|