@adcp/sdk 14.2.0 → 14.4.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.js +2 -4
- package/dist/lib/auth/oauth/MCPOAuthProvider.d.mts +6 -2
- package/dist/lib/auth/oauth/MCPOAuthProvider.d.ts +6 -2
- package/dist/lib/auth/oauth/MCPOAuthProvider.js +63 -3
- package/dist/lib/auth/oauth/MCPOAuthProvider.mjs +75 -3
- package/dist/lib/auth/oauth/diagnose.js +14 -0
- package/dist/lib/auth/oauth/diagnose.mjs +14 -0
- package/dist/lib/auth/oauth/file-storage.d.mts +4 -2
- package/dist/lib/auth/oauth/file-storage.d.ts +4 -2
- package/dist/lib/auth/oauth/file-storage.js +7 -1
- package/dist/lib/auth/oauth/file-storage.mjs +7 -1
- package/dist/lib/auth/oauth/types.d.mts +10 -0
- package/dist/lib/auth/oauth/types.d.ts +10 -0
- package/dist/lib/auth/oauth/types.js +39 -0
- package/dist/lib/auth/oauth/types.mjs +37 -0
- package/dist/lib/auth/oauth/web-flow.js +11 -1
- package/dist/lib/auth/oauth/web-flow.mjs +20 -2
- package/dist/lib/protocols/mcp.js +2 -0
- package/dist/lib/protocols/mcp.mjs +2 -0
- package/dist/lib/reporting/inspection.d.mts +3 -3
- package/dist/lib/reporting/inspection.d.ts +3 -3
- package/dist/lib/reporting/inspection.js +229 -84
- package/dist/lib/reporting/inspection.mjs +229 -84
- package/dist/lib/reporting/ledger/status-ingest.js +3 -19
- package/dist/lib/reporting/ledger/status-ingest.mjs +2 -18
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/create-adcp-server.js +5 -2
- package/dist/lib/server/create-adcp-server.mjs +5 -2
- package/dist/lib/signing/canonicalize.d.mts +2 -0
- package/dist/lib/signing/canonicalize.d.ts +2 -0
- package/dist/lib/signing/canonicalize.js +1 -1
- package/dist/lib/signing/canonicalize.mjs +1 -1
- package/dist/lib/signing/verifier.js +1 -1
- package/dist/lib/signing/verifier.mjs +1 -1
- package/dist/lib/testing/storyboard/request-signing/builder.js +13 -1
- package/dist/lib/testing/storyboard/request-signing/builder.mjs +13 -1
- package/dist/lib/testing/storyboard/request-signing/grader.js +1 -0
- package/dist/lib/testing/storyboard/request-signing/grader.mjs +1 -0
- package/dist/lib/testing/storyboard/request-signing/vector-loader.js +1 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +1 -1
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/types/adcp.d.mts +6 -0
- package/dist/lib/types/adcp.d.ts +6 -0
- package/dist/lib/utils/pointer-utils.d.mts +2 -0
- package/dist/lib/utils/pointer-utils.d.ts +2 -0
- package/dist/lib/utils/pointer-utils.js +43 -0
- package/dist/lib/utils/pointer-utils.mjs +19 -0
- package/dist/lib/validation/diagnostic-limits.d.mts +4 -0
- package/dist/lib/validation/diagnostic-limits.d.ts +4 -0
- package/dist/lib/validation/diagnostic-limits.js +57 -0
- package/dist/lib/validation/diagnostic-limits.mjs +32 -0
- package/dist/lib/validation/schema-errors.d.mts +3 -1
- package/dist/lib/validation/schema-errors.d.ts +3 -1
- package/dist/lib/validation/schema-errors.js +18 -3
- package/dist/lib/validation/schema-errors.mjs +18 -3
- package/dist/lib/validation/schema-validator.js +56 -4
- package/dist/lib/validation/schema-validator.mjs +56 -4
- 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/llms.txt +2 -2
- package/docs/migration-14.x-rc-worksheet.md +5 -5
- package/docs/migration-oauth-issuer-bindings.md +82 -0
- package/package.json +6 -6
- package/skills/call-adcp-agent/SKILL.md +4 -1
|
@@ -65,6 +65,7 @@ var import_envelope_status_compat = require('../utils/envelope-status-compat.js'
|
|
|
65
65
|
var import_schema_validator = require('../validation/schema-validator.js');
|
|
66
66
|
var import_sync_reporting_status_envelope = require('../validation/sync-reporting-status-envelope.js');
|
|
67
67
|
var import_schema_errors = require('../validation/schema-errors.js');
|
|
68
|
+
var import_diagnostic_limits = require('../validation/diagnostic-limits.js');
|
|
68
69
|
var import_idempotency2 = require('./idempotency/index.js');
|
|
69
70
|
var import_webhook_emitter = require('./webhook-emitter.js');
|
|
70
71
|
var import_middleware = require('../signing/middleware.js');
|
|
@@ -2287,7 +2288,7 @@ function createAdcpServer(config) {
|
|
|
2287
2288
|
const validationMode = effectiveRequestValidationMode(extra);
|
|
2288
2289
|
const accountIssues = protocolTaskAccountExtensionIssues(params);
|
|
2289
2290
|
const outcome = validationMode === "off" ? { valid: true, issues: [], schemaId: void 0 } : (0, import_schema_validator.validateRequest)(toolName, params, requestServedRelease(params)?.validationVersion ?? adcpVersion);
|
|
2290
|
-
const issues = [...outcome.valid ? [] : outcome.issues
|
|
2291
|
+
const issues = [...accountIssues, ...outcome.valid ? [] : outcome.issues];
|
|
2291
2292
|
if (issues.length === 0) return void 0;
|
|
2292
2293
|
if (validationMode === "strict" || accountIssues.length > 0) {
|
|
2293
2294
|
return (0, import_errors2.adcpError)(
|
|
@@ -2838,7 +2839,9 @@ function createAdcpServer(config) {
|
|
|
2838
2839
|
})
|
|
2839
2840
|
);
|
|
2840
2841
|
}
|
|
2841
|
-
let issues = idempotencyDisabled && requestIsStateChanging ? outcome.issues.filter(
|
|
2842
|
+
let issues = idempotencyDisabled && requestIsStateChanging ? outcome.issues.filter(
|
|
2843
|
+
(i) => !(i.keyword === "required" && i.pointer === "/idempotency_key" && !(0, import_diagnostic_limits.validationDiagnosticsTruncated)([i]))
|
|
2844
|
+
) : outcome.issues;
|
|
2842
2845
|
if (toolName === "sync_accounts" && effectiveFrameworkRequestValidationMode === "strict") {
|
|
2843
2846
|
const eventIssues = issues.filter(isAccountNotificationEventIssue);
|
|
2844
2847
|
if (eventIssues.length > 0 && eventIssues.length === issues.length) {
|
|
@@ -98,6 +98,7 @@ import { STATUS_FREE_SYNC_RESPONSE_TOOLS } from "../utils/envelope-status-compat
|
|
|
98
98
|
import { validateRequest, validateResponse, formatIssues } from "../validation/schema-validator.mjs";
|
|
99
99
|
import { validateSyncReportingStatusEnvelope } from "../validation/sync-reporting-status-envelope.mjs";
|
|
100
100
|
import { buildAdcpValidationErrorPayload } from "../validation/schema-errors.mjs";
|
|
101
|
+
import { validationDiagnosticsTruncated } from "../validation/diagnostic-limits.mjs";
|
|
101
102
|
import { hashPayload, IdempotencyClaimOwnershipError } from "./idempotency/index.mjs";
|
|
102
103
|
import {
|
|
103
104
|
createWebhookEmitter
|
|
@@ -2359,7 +2360,7 @@ function createAdcpServer(config) {
|
|
|
2359
2360
|
const validationMode = effectiveRequestValidationMode(extra);
|
|
2360
2361
|
const accountIssues = protocolTaskAccountExtensionIssues(params);
|
|
2361
2362
|
const outcome = validationMode === "off" ? { valid: true, issues: [], schemaId: void 0 } : validateRequest(toolName, params, requestServedRelease(params)?.validationVersion ?? adcpVersion);
|
|
2362
|
-
const issues = [...outcome.valid ? [] : outcome.issues
|
|
2363
|
+
const issues = [...accountIssues, ...outcome.valid ? [] : outcome.issues];
|
|
2363
2364
|
if (issues.length === 0) return void 0;
|
|
2364
2365
|
if (validationMode === "strict" || accountIssues.length > 0) {
|
|
2365
2366
|
return adcpError(
|
|
@@ -2910,7 +2911,9 @@ function createAdcpServer(config) {
|
|
|
2910
2911
|
})
|
|
2911
2912
|
);
|
|
2912
2913
|
}
|
|
2913
|
-
let issues = idempotencyDisabled && requestIsStateChanging ? outcome.issues.filter(
|
|
2914
|
+
let issues = idempotencyDisabled && requestIsStateChanging ? outcome.issues.filter(
|
|
2915
|
+
(i) => !(i.keyword === "required" && i.pointer === "/idempotency_key" && !validationDiagnosticsTruncated([i]))
|
|
2916
|
+
) : outcome.issues;
|
|
2914
2917
|
if (toolName === "sync_accounts" && effectiveFrameworkRequestValidationMode === "strict") {
|
|
2915
2918
|
const eventIssues = issues.filter(isAccountNotificationEventIssue);
|
|
2916
2919
|
if (eventIssues.length > 0 && eventIssues.length === issues.length) {
|
|
@@ -68,5 +68,7 @@ export declare function formatSignatureParams(components: ReadonlyArray<string>,
|
|
|
68
68
|
* non-transitional produce different A-labels for the same input, which
|
|
69
69
|
* would open a signer/verifier canonicalization differential. Accepts
|
|
70
70
|
* both absolute (`scheme://…`) and scheme-relative (`//…`) URL shapes.
|
|
71
|
+
* The transport taxonomy classifies the received URI as malformed. Diagnostic
|
|
72
|
+
* step 1 is SDK-local parsing, not the checklist's signature-header parser.
|
|
71
73
|
*/
|
|
72
74
|
export declare function rejectNonAsciiHost(rawUrl: string): void;
|
|
@@ -68,5 +68,7 @@ export declare function formatSignatureParams(components: ReadonlyArray<string>,
|
|
|
68
68
|
* non-transitional produce different A-labels for the same input, which
|
|
69
69
|
* would open a signer/verifier canonicalization differential. Accepts
|
|
70
70
|
* both absolute (`scheme://…`) and scheme-relative (`//…`) URL shapes.
|
|
71
|
+
* The transport taxonomy classifies the received URI as malformed. Diagnostic
|
|
72
|
+
* step 1 is SDK-local parsing, not the checklist's signature-header parser.
|
|
71
73
|
*/
|
|
72
74
|
export declare function rejectNonAsciiHost(rawUrl: string): void;
|
|
@@ -269,7 +269,7 @@ function rejectNonAsciiHost(rawUrl) {
|
|
|
269
269
|
for (let i = 0; i < authority.length; i++) {
|
|
270
270
|
if (authority.charCodeAt(i) > 127) {
|
|
271
271
|
throw new import_errors.RequestSignatureError(
|
|
272
|
-
"
|
|
272
|
+
"request_target_uri_malformed",
|
|
273
273
|
1,
|
|
274
274
|
"URL authority contains non-ASCII bytes; use the A-label (Punycode) form"
|
|
275
275
|
);
|
|
@@ -239,7 +239,7 @@ function rejectNonAsciiHost(rawUrl) {
|
|
|
239
239
|
for (let i = 0; i < authority.length; i++) {
|
|
240
240
|
if (authority.charCodeAt(i) > 127) {
|
|
241
241
|
throw new RequestSignatureError(
|
|
242
|
-
"
|
|
242
|
+
"request_target_uri_malformed",
|
|
243
243
|
1,
|
|
244
244
|
"URL authority contains non-ASCII bytes; use the A-label (Punycode) form"
|
|
245
245
|
);
|
|
@@ -99,7 +99,7 @@ async function verifyRequestSignature(request, options) {
|
|
|
99
99
|
if (!parsedSig) throw parseError;
|
|
100
100
|
const effectiveEncoding = pinnedBinaryEncoding ?? parsedEncoding;
|
|
101
101
|
const canonicalizationProfile = effectiveEncoding === "rfc8941-base64" ? "3.2" : "legacy";
|
|
102
|
-
|
|
102
|
+
(0, import_canonicalize.rejectNonAsciiHost)(request.url);
|
|
103
103
|
validateSingleValuedCoveredHeaders(parsedInput.components, request);
|
|
104
104
|
requireParams(parsedInput);
|
|
105
105
|
if (parsedInput.params.tag !== import_types.REQUEST_SIGNING_TAG) {
|
|
@@ -92,7 +92,7 @@ async function verifyRequestSignature(request, options) {
|
|
|
92
92
|
if (!parsedSig) throw parseError;
|
|
93
93
|
const effectiveEncoding = pinnedBinaryEncoding ?? parsedEncoding;
|
|
94
94
|
const canonicalizationProfile = effectiveEncoding === "rfc8941-base64" ? "3.2" : "legacy";
|
|
95
|
-
|
|
95
|
+
rejectNonAsciiHost(request.url);
|
|
96
96
|
validateSingleValuedCoveredHeaders(parsedInput.components, request);
|
|
97
97
|
requireParams(parsedInput);
|
|
98
98
|
if (parsedInput.params.tag !== REQUEST_SIGNING_TAG) {
|
|
@@ -192,6 +192,7 @@ const MUTATIONS = {
|
|
|
192
192
|
// transport-ungradable by the live probe because no HTTP client can route
|
|
193
193
|
// to that intentionally invalid host spelling.
|
|
194
194
|
"profile-3.2/negative/001-base64url-sf-binary": (vector, _keys, options) => passthrough(vector, options),
|
|
195
|
+
"profile-3.2/negative/001-no-signature-header": delegateToRoot("001-no-signature-header"),
|
|
195
196
|
"profile-3.2/negative/002-multiple-trailing-dots": (vector, _keys, options) => passthrough(vector, options),
|
|
196
197
|
"profile-3.2/negative/002-wrong-tag": delegateToRoot("002-wrong-tag"),
|
|
197
198
|
"profile-3.2/negative/003-expired-signature": delegateToRoot("003-expired-signature"),
|
|
@@ -208,6 +209,7 @@ const MUTATIONS = {
|
|
|
208
209
|
"profile-3.2/negative/008-unknown-keyid": delegateToRoot("008-unknown-keyid"),
|
|
209
210
|
"profile-3.2/negative/009-key-ops-missing-verify": delegateToRoot("009-key-ops-missing-verify"),
|
|
210
211
|
"profile-3.2/negative/010-content-digest-mismatch": delegateToRoot("010-content-digest-mismatch"),
|
|
212
|
+
"profile-3.2/negative/011-malformed-header": delegateToRoot("011-malformed-header"),
|
|
211
213
|
"profile-3.2/negative/012-missing-expires-param": delegateToRoot("012-missing-expires-param"),
|
|
212
214
|
"profile-3.2/negative/013-expires-le-created": delegateToRoot("013-expires-le-created"),
|
|
213
215
|
"profile-3.2/negative/014-missing-nonce-param": delegateToRoot("014-missing-nonce-param"),
|
|
@@ -220,8 +222,18 @@ const MUTATIONS = {
|
|
|
220
222
|
},
|
|
221
223
|
"profile-3.2/negative/016-replayed-nonce": delegateToRoot("016-replayed-nonce"),
|
|
222
224
|
"profile-3.2/negative/017-key-revoked": delegateToRoot("017-key-revoked"),
|
|
225
|
+
"profile-3.2/negative/019-signature-without-signature-input": delegateToRoot("019-signature-without-signature-input"),
|
|
223
226
|
"profile-3.2/negative/020-rate-abuse": delegateToRoot("020-rate-abuse"),
|
|
224
|
-
"profile-3.2/negative/
|
|
227
|
+
"profile-3.2/negative/021-duplicate-signature-input-label": delegateToRoot("021-duplicate-signature-input-label"),
|
|
228
|
+
"profile-3.2/negative/022-multi-valued-content-type": delegateToRoot("022-multi-valued-content-type"),
|
|
229
|
+
"profile-3.2/negative/023-multi-valued-content-digest": delegateToRoot("023-multi-valued-content-digest"),
|
|
230
|
+
"profile-3.2/negative/024-unquoted-string-param": delegateToRoot("024-unquoted-string-param"),
|
|
231
|
+
"profile-3.2/negative/025-jwk-alg-crv-mismatch": delegateToRoot("025-jwk-alg-crv-mismatch"),
|
|
232
|
+
"profile-3.2/negative/026-non-ascii-host": delegateToRoot("026-non-ascii-host"),
|
|
233
|
+
"profile-3.2/negative/027-webhook-registration-authentication-unsigned": delegateToRoot(
|
|
234
|
+
"027-webhook-registration-authentication-unsigned"
|
|
235
|
+
),
|
|
236
|
+
"profile-3.2/negative/028-unsigned-protocol-method-required": delegateToRoot("028-unsigned-protocol-method-required")
|
|
225
237
|
};
|
|
226
238
|
function delegateToRoot(rootId) {
|
|
227
239
|
return (vector, keys, options) => {
|
|
@@ -175,6 +175,7 @@ const MUTATIONS = {
|
|
|
175
175
|
// transport-ungradable by the live probe because no HTTP client can route
|
|
176
176
|
// to that intentionally invalid host spelling.
|
|
177
177
|
"profile-3.2/negative/001-base64url-sf-binary": (vector, _keys, options) => passthrough(vector, options),
|
|
178
|
+
"profile-3.2/negative/001-no-signature-header": delegateToRoot("001-no-signature-header"),
|
|
178
179
|
"profile-3.2/negative/002-multiple-trailing-dots": (vector, _keys, options) => passthrough(vector, options),
|
|
179
180
|
"profile-3.2/negative/002-wrong-tag": delegateToRoot("002-wrong-tag"),
|
|
180
181
|
"profile-3.2/negative/003-expired-signature": delegateToRoot("003-expired-signature"),
|
|
@@ -191,6 +192,7 @@ const MUTATIONS = {
|
|
|
191
192
|
"profile-3.2/negative/008-unknown-keyid": delegateToRoot("008-unknown-keyid"),
|
|
192
193
|
"profile-3.2/negative/009-key-ops-missing-verify": delegateToRoot("009-key-ops-missing-verify"),
|
|
193
194
|
"profile-3.2/negative/010-content-digest-mismatch": delegateToRoot("010-content-digest-mismatch"),
|
|
195
|
+
"profile-3.2/negative/011-malformed-header": delegateToRoot("011-malformed-header"),
|
|
194
196
|
"profile-3.2/negative/012-missing-expires-param": delegateToRoot("012-missing-expires-param"),
|
|
195
197
|
"profile-3.2/negative/013-expires-le-created": delegateToRoot("013-expires-le-created"),
|
|
196
198
|
"profile-3.2/negative/014-missing-nonce-param": delegateToRoot("014-missing-nonce-param"),
|
|
@@ -203,8 +205,18 @@ const MUTATIONS = {
|
|
|
203
205
|
},
|
|
204
206
|
"profile-3.2/negative/016-replayed-nonce": delegateToRoot("016-replayed-nonce"),
|
|
205
207
|
"profile-3.2/negative/017-key-revoked": delegateToRoot("017-key-revoked"),
|
|
208
|
+
"profile-3.2/negative/019-signature-without-signature-input": delegateToRoot("019-signature-without-signature-input"),
|
|
206
209
|
"profile-3.2/negative/020-rate-abuse": delegateToRoot("020-rate-abuse"),
|
|
207
|
-
"profile-3.2/negative/
|
|
210
|
+
"profile-3.2/negative/021-duplicate-signature-input-label": delegateToRoot("021-duplicate-signature-input-label"),
|
|
211
|
+
"profile-3.2/negative/022-multi-valued-content-type": delegateToRoot("022-multi-valued-content-type"),
|
|
212
|
+
"profile-3.2/negative/023-multi-valued-content-digest": delegateToRoot("023-multi-valued-content-digest"),
|
|
213
|
+
"profile-3.2/negative/024-unquoted-string-param": delegateToRoot("024-unquoted-string-param"),
|
|
214
|
+
"profile-3.2/negative/025-jwk-alg-crv-mismatch": delegateToRoot("025-jwk-alg-crv-mismatch"),
|
|
215
|
+
"profile-3.2/negative/026-non-ascii-host": delegateToRoot("026-non-ascii-host"),
|
|
216
|
+
"profile-3.2/negative/027-webhook-registration-authentication-unsigned": delegateToRoot(
|
|
217
|
+
"027-webhook-registration-authentication-unsigned"
|
|
218
|
+
),
|
|
219
|
+
"profile-3.2/negative/028-unsigned-protocol-method-required": delegateToRoot("028-unsigned-protocol-method-required")
|
|
208
220
|
};
|
|
209
221
|
function delegateToRoot(rootId) {
|
|
210
222
|
return (vector, keys, options) => {
|
|
@@ -134,6 +134,7 @@ const TRANSPORT_UNGRADABLE = {
|
|
|
134
134
|
// agent as Punycode, defeating the parse-time check. Direct verifier
|
|
135
135
|
// tests exercise this edge.
|
|
136
136
|
"026-non-ascii-host": "HTTP transport punycodes U-labels before the request leaves the client; verified at the library level.",
|
|
137
|
+
"profile-3.2/negative/026-non-ascii-host": "HTTP transport punycodes U-labels before the request leaves the client; verified at the library level.",
|
|
137
138
|
"profile-3.2/negative/002-multiple-trailing-dots": "HTTP transport cannot route to an intentionally malformed DNS authority; verified at the library level."
|
|
138
139
|
};
|
|
139
140
|
function semanticVectorExclusion(vector, declaredCapability) {
|
|
@@ -113,6 +113,7 @@ const TRANSPORT_UNGRADABLE = {
|
|
|
113
113
|
// agent as Punycode, defeating the parse-time check. Direct verifier
|
|
114
114
|
// tests exercise this edge.
|
|
115
115
|
"026-non-ascii-host": "HTTP transport punycodes U-labels before the request leaves the client; verified at the library level.",
|
|
116
|
+
"profile-3.2/negative/026-non-ascii-host": "HTTP transport punycodes U-labels before the request leaves the client; verified at the library level.",
|
|
116
117
|
"profile-3.2/negative/002-multiple-trailing-dots": "HTTP transport cannot route to an intentionally malformed DNS authority; verified at the library level."
|
|
117
118
|
};
|
|
118
119
|
function semanticVectorExclusion(vector, declaredCapability) {
|
|
@@ -39,7 +39,7 @@ function selectRequestSigningVectors(loaded, signingProfileVersion) {
|
|
|
39
39
|
}
|
|
40
40
|
function signingProfileForAdcpVersion(version) {
|
|
41
41
|
const match = /^(\d+)\.(\d+)/.exec(version ?? "");
|
|
42
|
-
return match && Number(match[1]) === 3 && Number(match[2])
|
|
42
|
+
return match && (Number(match[1]) > 3 || Number(match[1]) === 3 && Number(match[2]) >= 2) ? "3.2" : void 0;
|
|
43
43
|
}
|
|
44
44
|
const ERROR_CODES = /* @__PURE__ */ new Set([
|
|
45
45
|
"request_signature_required",
|
|
@@ -11,7 +11,7 @@ function selectRequestSigningVectors(loaded, signingProfileVersion) {
|
|
|
11
11
|
}
|
|
12
12
|
function signingProfileForAdcpVersion(version) {
|
|
13
13
|
const match = /^(\d+)\.(\d+)/.exec(version ?? "");
|
|
14
|
-
return match && Number(match[1]) === 3 && Number(match[2])
|
|
14
|
+
return match && (Number(match[1]) > 3 || Number(match[1]) === 3 && Number(match[2]) >= 2) ? "3.2" : void 0;
|
|
15
15
|
}
|
|
16
16
|
const ERROR_CODES = /* @__PURE__ */ new Set([
|
|
17
17
|
"request_signature_required",
|
|
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
|
|
|
197
197
|
*/
|
|
198
198
|
export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
|
|
199
199
|
/** Capability semver emitted in run summaries and used for advisory expiry. */
|
|
200
|
-
export declare const RUNNER_CAPABILITY_VERSION = "14.
|
|
200
|
+
export declare const RUNNER_CAPABILITY_VERSION = "14.4.0";
|
|
201
201
|
/** True when a failed validation contributes to the owning step's grade. */
|
|
202
202
|
export declare function validationFailsStep(result: ValidationResult): boolean;
|
|
203
203
|
/**
|
|
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
|
|
|
197
197
|
*/
|
|
198
198
|
export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
|
|
199
199
|
/** Capability semver emitted in run summaries and used for advisory expiry. */
|
|
200
|
-
export declare const RUNNER_CAPABILITY_VERSION = "14.
|
|
200
|
+
export declare const RUNNER_CAPABILITY_VERSION = "14.4.0";
|
|
201
201
|
/** True when a failed validation contributes to the owning step's grade. */
|
|
202
202
|
export declare function validationFailsStep(result: ValidationResult): boolean;
|
|
203
203
|
/**
|
|
@@ -145,6 +145,8 @@ export interface DayParting {
|
|
|
145
145
|
* OAuth tokens for agent authentication
|
|
146
146
|
*/
|
|
147
147
|
export interface AgentOAuthTokens {
|
|
148
|
+
/** Authorization server that issued these tokens. Required when reusing a refresh token. */
|
|
149
|
+
issuer?: string;
|
|
148
150
|
/** OAuth access token */
|
|
149
151
|
access_token: string;
|
|
150
152
|
/** OAuth refresh token (for token renewal) */
|
|
@@ -162,6 +164,8 @@ export interface AgentOAuthTokens {
|
|
|
162
164
|
* OAuth client information (from dynamic registration)
|
|
163
165
|
*/
|
|
164
166
|
export interface AgentOAuthClient {
|
|
167
|
+
/** Authorization server where this client was registered. Required when reusing a client secret. */
|
|
168
|
+
issuer?: string;
|
|
165
169
|
/** OAuth client ID */
|
|
166
170
|
client_id: string;
|
|
167
171
|
/** OAuth client secret (for confidential clients) */
|
|
@@ -401,6 +405,8 @@ export interface AgentConfig {
|
|
|
401
405
|
* authorization flow.
|
|
402
406
|
*/
|
|
403
407
|
oauth_client_credentials?: AgentOAuthClientCredentials;
|
|
408
|
+
/** Opaque discovery state paired with the pending PKCE verifier; persist and return verbatim. */
|
|
409
|
+
oauth_discovery_state?: import('@modelcontextprotocol/sdk/client/auth.js').OAuthDiscoveryState;
|
|
404
410
|
/**
|
|
405
411
|
* Additional HTTP headers to include in every request to this agent.
|
|
406
412
|
* Useful for sending API keys, org IDs, or other vendor-specific headers
|
package/dist/lib/types/adcp.d.ts
CHANGED
|
@@ -145,6 +145,8 @@ export interface DayParting {
|
|
|
145
145
|
* OAuth tokens for agent authentication
|
|
146
146
|
*/
|
|
147
147
|
export interface AgentOAuthTokens {
|
|
148
|
+
/** Authorization server that issued these tokens. Required when reusing a refresh token. */
|
|
149
|
+
issuer?: string;
|
|
148
150
|
/** OAuth access token */
|
|
149
151
|
access_token: string;
|
|
150
152
|
/** OAuth refresh token (for token renewal) */
|
|
@@ -162,6 +164,8 @@ export interface AgentOAuthTokens {
|
|
|
162
164
|
* OAuth client information (from dynamic registration)
|
|
163
165
|
*/
|
|
164
166
|
export interface AgentOAuthClient {
|
|
167
|
+
/** Authorization server where this client was registered. Required when reusing a client secret. */
|
|
168
|
+
issuer?: string;
|
|
165
169
|
/** OAuth client ID */
|
|
166
170
|
client_id: string;
|
|
167
171
|
/** OAuth client secret (for confidential clients) */
|
|
@@ -401,6 +405,8 @@ export interface AgentConfig {
|
|
|
401
405
|
* authorization flow.
|
|
402
406
|
*/
|
|
403
407
|
oauth_client_credentials?: AgentOAuthClientCredentials;
|
|
408
|
+
/** Opaque discovery state paired with the pending PKCE verifier; persist and return verbatim. */
|
|
409
|
+
oauth_discovery_state?: import('@modelcontextprotocol/sdk/client/auth.js').OAuthDiscoveryState;
|
|
404
410
|
/**
|
|
405
411
|
* Additional HTTP headers to include in every request to this agent.
|
|
406
412
|
* Useful for sending API keys, org IDs, or other vendor-specific headers
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var pointer_utils_exports = {};
|
|
20
|
+
__export(pointer_utils_exports, {
|
|
21
|
+
jsonPointerToJsonPathLite: () => jsonPointerToJsonPathLite
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(pointer_utils_exports);
|
|
24
|
+
function jsonPointerToJsonPathLite(pointer) {
|
|
25
|
+
if (pointer === "" || pointer === "/") return "$";
|
|
26
|
+
if (!pointer.startsWith("/")) return void 0;
|
|
27
|
+
let field = "";
|
|
28
|
+
for (const encoded of pointer.slice(1).split("/")) {
|
|
29
|
+
const segment = encoded.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
30
|
+
if (/^(0|[1-9][0-9]*)$/.test(segment)) {
|
|
31
|
+
field += `${field ? "" : "$"}[${segment}]`;
|
|
32
|
+
} else if (/^[A-Za-z_][A-Za-z0-9_-]*$/.test(segment)) {
|
|
33
|
+
field += field ? `.${segment}` : segment;
|
|
34
|
+
} else {
|
|
35
|
+
field += `${field ? "" : "$"}[${JSON.stringify(segment)}]`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return field;
|
|
39
|
+
}
|
|
40
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
41
|
+
0 && (module.exports = {
|
|
42
|
+
jsonPointerToJsonPathLite
|
|
43
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function jsonPointerToJsonPathLite(pointer) {
|
|
2
|
+
if (pointer === "" || pointer === "/") return "$";
|
|
3
|
+
if (!pointer.startsWith("/")) return void 0;
|
|
4
|
+
let field = "";
|
|
5
|
+
for (const encoded of pointer.slice(1).split("/")) {
|
|
6
|
+
const segment = encoded.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
7
|
+
if (/^(0|[1-9][0-9]*)$/.test(segment)) {
|
|
8
|
+
field += `${field ? "" : "$"}[${segment}]`;
|
|
9
|
+
} else if (/^[A-Za-z_][A-Za-z0-9_-]*$/.test(segment)) {
|
|
10
|
+
field += field ? `.${segment}` : segment;
|
|
11
|
+
} else {
|
|
12
|
+
field += `${field ? "" : "$"}[${JSON.stringify(segment)}]`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return field;
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
jsonPointerToJsonPathLite
|
|
19
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ValidationIssue } from './schema-validator.mjs';
|
|
2
|
+
export declare function validationDiagnosticsTruncated(issues: readonly ValidationIssue[]): boolean;
|
|
3
|
+
/** Retain actionable diagnostics with a visible marker when count/bytes overflow. */
|
|
4
|
+
export declare function boundValidationIssues(issues: Iterable<ValidationIssue>): ValidationIssue[];
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ValidationIssue } from './schema-validator';
|
|
2
|
+
export declare function validationDiagnosticsTruncated(issues: readonly ValidationIssue[]): boolean;
|
|
3
|
+
/** Retain actionable diagnostics with a visible marker when count/bytes overflow. */
|
|
4
|
+
export declare function boundValidationIssues(issues: Iterable<ValidationIssue>): ValidationIssue[];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var diagnostic_limits_exports = {};
|
|
20
|
+
__export(diagnostic_limits_exports, {
|
|
21
|
+
boundValidationIssues: () => boundValidationIssues,
|
|
22
|
+
validationDiagnosticsTruncated: () => validationDiagnosticsTruncated
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(diagnostic_limits_exports);
|
|
25
|
+
const MAX_ISSUES = 100;
|
|
26
|
+
const MAX_ISSUE_BYTES = 64 * 1024;
|
|
27
|
+
const MAX_SINGLE_ISSUE_BYTES = 8 * 1024;
|
|
28
|
+
const OMITTED_MESSAGE = "Validation failed; additional diagnostics omitted because the diagnostic limit was reached.";
|
|
29
|
+
const OMITTED_BYTES = 8 * 1024;
|
|
30
|
+
function validationDiagnosticsTruncated(issues) {
|
|
31
|
+
return issues.some((issue) => issue.message === OMITTED_MESSAGE);
|
|
32
|
+
}
|
|
33
|
+
function boundValidationIssues(issues) {
|
|
34
|
+
const bounded = [];
|
|
35
|
+
let bytes = 2;
|
|
36
|
+
for (const issue of issues) {
|
|
37
|
+
const issueBytes = Buffer.byteLength(JSON.stringify(issue), "utf8") + 1;
|
|
38
|
+
if (bounded.length >= MAX_ISSUES || issueBytes > MAX_SINGLE_ISSUE_BYTES || bytes + issueBytes + OMITTED_BYTES > MAX_ISSUE_BYTES) {
|
|
39
|
+
const omitted = bounded.length === MAX_ISSUES ? bounded.pop() : issue;
|
|
40
|
+
bounded.push({
|
|
41
|
+
pointer: Buffer.byteLength(omitted.pointer, "utf8") <= 1024 ? omitted.pointer : "/",
|
|
42
|
+
keyword: Buffer.byteLength(omitted.keyword, "utf8") <= 128 ? omitted.keyword : "type",
|
|
43
|
+
message: OMITTED_MESSAGE,
|
|
44
|
+
schemaPath: ""
|
|
45
|
+
});
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
bounded.push(issue);
|
|
49
|
+
bytes += issueBytes;
|
|
50
|
+
}
|
|
51
|
+
return bounded;
|
|
52
|
+
}
|
|
53
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
54
|
+
0 && (module.exports = {
|
|
55
|
+
boundValidationIssues,
|
|
56
|
+
validationDiagnosticsTruncated
|
|
57
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const MAX_ISSUES = 100;
|
|
2
|
+
const MAX_ISSUE_BYTES = 64 * 1024;
|
|
3
|
+
const MAX_SINGLE_ISSUE_BYTES = 8 * 1024;
|
|
4
|
+
const OMITTED_MESSAGE = "Validation failed; additional diagnostics omitted because the diagnostic limit was reached.";
|
|
5
|
+
const OMITTED_BYTES = 8 * 1024;
|
|
6
|
+
function validationDiagnosticsTruncated(issues) {
|
|
7
|
+
return issues.some((issue) => issue.message === OMITTED_MESSAGE);
|
|
8
|
+
}
|
|
9
|
+
function boundValidationIssues(issues) {
|
|
10
|
+
const bounded = [];
|
|
11
|
+
let bytes = 2;
|
|
12
|
+
for (const issue of issues) {
|
|
13
|
+
const issueBytes = Buffer.byteLength(JSON.stringify(issue), "utf8") + 1;
|
|
14
|
+
if (bounded.length >= MAX_ISSUES || issueBytes > MAX_SINGLE_ISSUE_BYTES || bytes + issueBytes + OMITTED_BYTES > MAX_ISSUE_BYTES) {
|
|
15
|
+
const omitted = bounded.length === MAX_ISSUES ? bounded.pop() : issue;
|
|
16
|
+
bounded.push({
|
|
17
|
+
pointer: Buffer.byteLength(omitted.pointer, "utf8") <= 1024 ? omitted.pointer : "/",
|
|
18
|
+
keyword: Buffer.byteLength(omitted.keyword, "utf8") <= 128 ? omitted.keyword : "type",
|
|
19
|
+
message: OMITTED_MESSAGE,
|
|
20
|
+
schemaPath: ""
|
|
21
|
+
});
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
bounded.push(issue);
|
|
25
|
+
bytes += issueBytes;
|
|
26
|
+
}
|
|
27
|
+
return bounded;
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
boundValidationIssues,
|
|
31
|
+
validationDiagnosticsTruncated
|
|
32
|
+
};
|
|
@@ -9,8 +9,9 @@ export interface ValidationErrorDetails {
|
|
|
9
9
|
tool: string;
|
|
10
10
|
/** Which side of the exchange — request (outgoing) or response (incoming). */
|
|
11
11
|
side: 'request' | 'response';
|
|
12
|
-
/**
|
|
12
|
+
/** Bounded failures, each with a JSON Pointer to the bad field. */
|
|
13
13
|
issues: ValidationIssue[];
|
|
14
|
+
issues_truncated?: boolean;
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
17
|
* Build a `ValidationError` thrown by strict-mode client hooks. Keeps the
|
|
@@ -34,6 +35,7 @@ export interface AdcpValidationErrorDetails {
|
|
|
34
35
|
issues: Array<Omit<ValidationIssue, 'schemaPath'> & {
|
|
35
36
|
schemaPath?: string;
|
|
36
37
|
}>;
|
|
38
|
+
issues_truncated?: boolean;
|
|
37
39
|
}
|
|
38
40
|
/**
|
|
39
41
|
* Serialize issues for the server-side `adcpError('VALIDATION_ERROR', ...)` call.
|
|
@@ -9,8 +9,9 @@ export interface ValidationErrorDetails {
|
|
|
9
9
|
tool: string;
|
|
10
10
|
/** Which side of the exchange — request (outgoing) or response (incoming). */
|
|
11
11
|
side: 'request' | 'response';
|
|
12
|
-
/**
|
|
12
|
+
/** Bounded failures, each with a JSON Pointer to the bad field. */
|
|
13
13
|
issues: ValidationIssue[];
|
|
14
|
+
issues_truncated?: boolean;
|
|
14
15
|
}
|
|
15
16
|
/**
|
|
16
17
|
* Build a `ValidationError` thrown by strict-mode client hooks. Keeps the
|
|
@@ -34,6 +35,7 @@ export interface AdcpValidationErrorDetails {
|
|
|
34
35
|
issues: Array<Omit<ValidationIssue, 'schemaPath'> & {
|
|
35
36
|
schemaPath?: string;
|
|
36
37
|
}>;
|
|
38
|
+
issues_truncated?: boolean;
|
|
37
39
|
}
|
|
38
40
|
/**
|
|
39
41
|
* Serialize issues for the server-side `adcpError('VALIDATION_ERROR', ...)` call.
|
|
@@ -24,6 +24,8 @@ __export(schema_errors_exports, {
|
|
|
24
24
|
module.exports = __toCommonJS(schema_errors_exports);
|
|
25
25
|
var import_errors = require('../errors/index.js');
|
|
26
26
|
var import_schema_validator = require('./schema-validator.js');
|
|
27
|
+
var import_pointer_utils = require('../utils/pointer-utils.js');
|
|
28
|
+
var import_diagnostic_limits = require('./diagnostic-limits.js');
|
|
27
29
|
function buildIssueProseSuffix(issue, rootSchemaId) {
|
|
28
30
|
const parts = [];
|
|
29
31
|
if (issue.schemaId && issue.schemaId !== rootSchemaId) parts.push(`schema: ${issue.schemaId}`);
|
|
@@ -33,14 +35,21 @@ function buildIssueProseSuffix(issue, rootSchemaId) {
|
|
|
33
35
|
return parts.length > 0 ? ` (${parts.join("; ")})` : "";
|
|
34
36
|
}
|
|
35
37
|
function buildValidationError(tool, side, issues) {
|
|
38
|
+
issues = (0, import_diagnostic_limits.boundValidationIssues)(issues);
|
|
36
39
|
const first = issues[0];
|
|
37
40
|
const field = first?.pointer ?? "/";
|
|
38
41
|
const constraint = first ? `${first.keyword}: ${first.message}${buildIssueProseSuffix(first, void 0)}` : "schema validation failed";
|
|
39
42
|
const err = new import_errors.ValidationError(field, void 0, `${tool} ${side}: ${constraint}`);
|
|
40
|
-
err.details = {
|
|
43
|
+
err.details = {
|
|
44
|
+
tool,
|
|
45
|
+
side,
|
|
46
|
+
issues,
|
|
47
|
+
...(0, import_diagnostic_limits.validationDiagnosticsTruncated)(issues) && { issues_truncated: true }
|
|
48
|
+
};
|
|
41
49
|
return err;
|
|
42
50
|
}
|
|
43
51
|
function buildAdcpValidationErrorPayload(tool, side, issues, options = {}) {
|
|
52
|
+
issues = (0, import_diagnostic_limits.boundValidationIssues)(issues);
|
|
44
53
|
const first = issues[0];
|
|
45
54
|
let message;
|
|
46
55
|
if (first != null) {
|
|
@@ -52,9 +61,15 @@ function buildAdcpValidationErrorPayload(tool, side, issues, options = {}) {
|
|
|
52
61
|
const payload = {
|
|
53
62
|
message,
|
|
54
63
|
issues: emittedIssues,
|
|
55
|
-
details: {
|
|
64
|
+
details: {
|
|
65
|
+
tool,
|
|
66
|
+
side,
|
|
67
|
+
issues: emittedIssues,
|
|
68
|
+
...(0, import_diagnostic_limits.validationDiagnosticsTruncated)(issues) && { issues_truncated: true }
|
|
69
|
+
}
|
|
56
70
|
};
|
|
57
|
-
|
|
71
|
+
const field = first && (0, import_pointer_utils.jsonPointerToJsonPathLite)(first.pointer);
|
|
72
|
+
if (field !== void 0) payload.field = field;
|
|
58
73
|
return payload;
|
|
59
74
|
}
|
|
60
75
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ValidationError } from "../errors/index.mjs";
|
|
2
2
|
import { formatDiscriminator } from "./schema-validator.mjs";
|
|
3
|
+
import { jsonPointerToJsonPathLite } from "../utils/pointer-utils.mjs";
|
|
4
|
+
import { boundValidationIssues, validationDiagnosticsTruncated } from "./diagnostic-limits.mjs";
|
|
3
5
|
function buildIssueProseSuffix(issue, rootSchemaId) {
|
|
4
6
|
const parts = [];
|
|
5
7
|
if (issue.schemaId && issue.schemaId !== rootSchemaId) parts.push(`schema: ${issue.schemaId}`);
|
|
@@ -9,14 +11,21 @@ function buildIssueProseSuffix(issue, rootSchemaId) {
|
|
|
9
11
|
return parts.length > 0 ? ` (${parts.join("; ")})` : "";
|
|
10
12
|
}
|
|
11
13
|
function buildValidationError(tool, side, issues) {
|
|
14
|
+
issues = boundValidationIssues(issues);
|
|
12
15
|
const first = issues[0];
|
|
13
16
|
const field = first?.pointer ?? "/";
|
|
14
17
|
const constraint = first ? `${first.keyword}: ${first.message}${buildIssueProseSuffix(first, void 0)}` : "schema validation failed";
|
|
15
18
|
const err = new ValidationError(field, void 0, `${tool} ${side}: ${constraint}`);
|
|
16
|
-
err.details = {
|
|
19
|
+
err.details = {
|
|
20
|
+
tool,
|
|
21
|
+
side,
|
|
22
|
+
issues,
|
|
23
|
+
...validationDiagnosticsTruncated(issues) && { issues_truncated: true }
|
|
24
|
+
};
|
|
17
25
|
return err;
|
|
18
26
|
}
|
|
19
27
|
function buildAdcpValidationErrorPayload(tool, side, issues, options = {}) {
|
|
28
|
+
issues = boundValidationIssues(issues);
|
|
20
29
|
const first = issues[0];
|
|
21
30
|
let message;
|
|
22
31
|
if (first != null) {
|
|
@@ -28,9 +37,15 @@ function buildAdcpValidationErrorPayload(tool, side, issues, options = {}) {
|
|
|
28
37
|
const payload = {
|
|
29
38
|
message,
|
|
30
39
|
issues: emittedIssues,
|
|
31
|
-
details: {
|
|
40
|
+
details: {
|
|
41
|
+
tool,
|
|
42
|
+
side,
|
|
43
|
+
issues: emittedIssues,
|
|
44
|
+
...validationDiagnosticsTruncated(issues) && { issues_truncated: true }
|
|
45
|
+
}
|
|
32
46
|
};
|
|
33
|
-
|
|
47
|
+
const field = first && jsonPointerToJsonPathLite(first.pointer);
|
|
48
|
+
if (field !== void 0) payload.field = field;
|
|
34
49
|
return payload;
|
|
35
50
|
}
|
|
36
51
|
export {
|