@adcp/sdk 14.1.0 → 14.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/core/SingleAgentClient.d.mts +4 -1
- package/dist/lib/core/SingleAgentClient.d.ts +4 -1
- package/dist/lib/core/SingleAgentClient.js +25 -4
- package/dist/lib/core/SingleAgentClient.mjs +25 -4
- package/dist/lib/core/TaskExecutor.d.mts +3 -1
- package/dist/lib/core/TaskExecutor.d.ts +3 -1
- package/dist/lib/core/TaskExecutor.js +15 -11
- package/dist/lib/core/TaskExecutor.mjs +15 -11
- package/dist/lib/core/buyer-account-registry.d.mts +30 -2
- package/dist/lib/core/buyer-account-registry.d.ts +30 -2
- package/dist/lib/core/buyer-account-registry.js +126 -42
- package/dist/lib/core/buyer-account-registry.mjs +127 -43
- package/dist/lib/index.d.mts +3 -2
- package/dist/lib/index.d.ts +3 -2
- package/dist/lib/index.js +9 -0
- package/dist/lib/index.mjs +12 -1
- package/dist/lib/net/agent-transport-fetch.d.mts +4 -0
- package/dist/lib/net/agent-transport-fetch.d.ts +4 -0
- package/dist/lib/net/agent-transport-fetch.js +18 -5
- package/dist/lib/net/agent-transport-fetch.mjs +17 -5
- 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/agent-resolver/errors.d.mts +2 -0
- package/dist/lib/signing/agent-resolver/errors.d.ts +2 -0
- package/dist/lib/signing/agent-resolver/errors.js +6 -0
- package/dist/lib/signing/agent-resolver/errors.mjs +6 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.mts +9 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.ts +9 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.js +35 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +38 -2
- package/dist/lib/signing/agent-resolver/legacy-brand.js +1 -1
- package/dist/lib/signing/agent-resolver/legacy-brand.mjs +2 -2
- package/dist/lib/signing/agent-resolver/resolve-agent.d.mts +1 -1
- package/dist/lib/signing/agent-resolver/resolve-agent.d.ts +1 -1
- package/dist/lib/signing/agent-resolver/resolve-agent.js +47 -5
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs +55 -6
- package/dist/lib/signing/brand-jwks.d.mts +2 -0
- package/dist/lib/signing/brand-jwks.d.ts +2 -0
- package/dist/lib/signing/brand-jwks.js +10 -2
- package/dist/lib/signing/brand-jwks.mjs +10 -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/errors.d.mts +4 -2
- package/dist/lib/signing/errors.d.ts +4 -2
- package/dist/lib/signing/errors.js +3 -1
- package/dist/lib/signing/errors.mjs +3 -1
- package/dist/lib/signing/verifier.js +2 -2
- package/dist/lib/signing/verifier.mjs +2 -2
- package/dist/lib/signing/webhook-verifier.js +2 -2
- package/dist/lib/signing/webhook-verifier.mjs +3 -3
- 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/accept-proposal.d.ts +19 -1
- package/dist/lib/types/buy-products.d.ts +19 -1
- package/dist/lib/types/check-governance.d.ts +19 -1
- package/dist/lib/types/comply-test-controller.d.ts +19 -1
- package/dist/lib/types/control-media-buy.d.ts +19 -1
- package/dist/lib/types/core.generated.d.mts +14 -1
- package/dist/lib/types/core.generated.d.ts +14 -1
- package/dist/lib/types/create-media-buy.d.ts +14 -1
- package/dist/lib/types/get-media-buys.d.ts +14 -1
- package/dist/lib/types/get-products.d.ts +19 -1
- package/dist/lib/types/list-products.d.ts +19 -1
- package/dist/lib/types/refine-proposals.d.ts +19 -1
- package/dist/lib/types/request-proposals.d.ts +19 -1
- package/dist/lib/types/schemas.generated.d.ts +12 -3
- package/dist/lib/types/schemas.generated.js +4 -1
- package/dist/lib/types/schemas.generated.mjs +4 -1
- package/dist/lib/types/tools.generated.d.mts +14 -1
- package/dist/lib/types/tools.generated.d.ts +14 -1
- package/dist/lib/types/update-media-buy.d.ts +14 -1
- 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/dist/lib/webhooks/index.d.mts +24 -0
- package/dist/lib/webhooks/index.d.ts +24 -0
- package/dist/lib/webhooks/index.js +50 -24
- package/dist/lib/webhooks/index.mjs +49 -24
- package/dist/lib/wholesale-feed-sync/index.d.mts +2 -0
- package/dist/lib/wholesale-feed-sync/index.d.ts +2 -0
- package/dist/lib/wholesale-feed-sync/index.js +7 -0
- package/dist/lib/wholesale-feed-sync/index.mjs +4 -0
- package/dist/lib/wholesale-feed-sync/mirror.d.mts +97 -0
- package/dist/lib/wholesale-feed-sync/mirror.d.ts +97 -0
- package/dist/lib/wholesale-feed-sync/mirror.js +350 -0
- package/dist/lib/wholesale-feed-sync/mirror.mjs +322 -0
- package/dist/lib/wholesale-feed-sync/sync.d.mts +12 -29
- package/dist/lib/wholesale-feed-sync/sync.d.ts +12 -29
- package/dist/lib/wholesale-feed-sync/sync.js +153 -297
- package/dist/lib/wholesale-feed-sync/sync.mjs +158 -297
- package/docs/README.md +6 -0
- package/docs/TYPE-SUMMARY.md +2 -2
- package/docs/guides/BUYER-STORAGE.md +3 -0
- package/docs/guides/FIRST-CALL-TO-A-SELLER.md +3 -1
- package/docs/guides/account-resolution.md +46 -10
- package/docs/llms.txt +2 -2
- package/docs/migration-14.0-to-14.1.md +85 -0
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/docs/migration-agent-resolution-3.3.md +5 -3
- package/docs/recipes/verifying-inbound-webhooks.md +4 -0
- package/package.json +3 -2
- package/skills/call-adcp-agent/SKILL.md +4 -1
- package/skills/adcp-brand.previous/SKILL.md +0 -200
- package/skills/adcp-creative.previous/SKILL.md +0 -305
- package/skills/adcp-governance.previous/SKILL.md +0 -566
- package/skills/adcp-measurement.previous/SKILL.md +0 -136
- package/skills/adcp-media-buy.previous/SKILL.md +0 -556
- package/skills/adcp-si.previous/SKILL.md +0 -206
- package/skills/adcp-signals.previous/SKILL.md +0 -204
|
@@ -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) {
|
|
@@ -46,6 +46,8 @@ export interface AgentResolverErrorDetail {
|
|
|
46
46
|
export declare class AgentResolverError extends Error {
|
|
47
47
|
readonly code: AgentResolverErrorCode;
|
|
48
48
|
readonly detail: AgentResolverErrorDetail;
|
|
49
|
+
/** Local, cause-aware recovery. Wire codes retain the protocol taxonomy. */
|
|
50
|
+
readonly recovery: 'transient' | 'correctable' | 'terminal';
|
|
49
51
|
readonly [ATTACKER_INFLUENCED]: ReadonlyArray<keyof AgentResolverErrorDetail>;
|
|
50
52
|
constructor(code: AgentResolverErrorCode, message: string, detail?: AgentResolverErrorDetail, attackerInfluencedFields?: ReadonlyArray<keyof AgentResolverErrorDetail>);
|
|
51
53
|
}
|
|
@@ -46,6 +46,8 @@ export interface AgentResolverErrorDetail {
|
|
|
46
46
|
export declare class AgentResolverError extends Error {
|
|
47
47
|
readonly code: AgentResolverErrorCode;
|
|
48
48
|
readonly detail: AgentResolverErrorDetail;
|
|
49
|
+
/** Local, cause-aware recovery. Wire codes retain the protocol taxonomy. */
|
|
50
|
+
readonly recovery: 'transient' | 'correctable' | 'terminal';
|
|
49
51
|
readonly [ATTACKER_INFLUENCED]: ReadonlyArray<keyof AgentResolverErrorDetail>;
|
|
50
52
|
constructor(code: AgentResolverErrorCode, message: string, detail?: AgentResolverErrorDetail, attackerInfluencedFields?: ReadonlyArray<keyof AgentResolverErrorDetail>);
|
|
51
53
|
}
|
|
@@ -23,16 +23,22 @@ __export(errors_exports, {
|
|
|
23
23
|
attackerInfluencedFields: () => attackerInfluencedFields
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(errors_exports);
|
|
26
|
+
var import_fetch_helpers = require('./fetch-helpers.js');
|
|
27
|
+
var import_enums = require('../../types/enums.generated.js');
|
|
26
28
|
const ATTACKER_INFLUENCED = /* @__PURE__ */ Symbol.for("adcp.attacker-influenced");
|
|
27
29
|
class AgentResolverError extends Error {
|
|
28
30
|
code;
|
|
29
31
|
detail;
|
|
32
|
+
/** Local, cause-aware recovery. Wire codes retain the protocol taxonomy. */
|
|
33
|
+
recovery;
|
|
30
34
|
[ATTACKER_INFLUENCED];
|
|
31
35
|
constructor(code, message, detail = {}, attackerInfluencedFields2 = []) {
|
|
32
36
|
super(message);
|
|
33
37
|
this.name = "AgentResolverError";
|
|
34
38
|
this.code = code;
|
|
35
39
|
this.detail = detail;
|
|
40
|
+
const discovery = code === "request_signature_capabilities_unreachable" || code === "request_signature_brand_json_unreachable";
|
|
41
|
+
this.recovery = discovery && (0, import_fetch_helpers.isPermanentDiscoveryFailure)(detail) ? "terminal" : import_enums.RequestSigningErrorCodeMetadata[code].recovery;
|
|
36
42
|
this[ATTACKER_INFLUENCED] = attackerInfluencedFields2;
|
|
37
43
|
}
|
|
38
44
|
}
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
+
import { isPermanentDiscoveryFailure } from "./fetch-helpers.mjs";
|
|
2
|
+
import { RequestSigningErrorCodeMetadata } from "../../types/enums.generated.mjs";
|
|
1
3
|
const ATTACKER_INFLUENCED = /* @__PURE__ */ Symbol.for("adcp.attacker-influenced");
|
|
2
4
|
class AgentResolverError extends Error {
|
|
3
5
|
code;
|
|
4
6
|
detail;
|
|
7
|
+
/** Local, cause-aware recovery. Wire codes retain the protocol taxonomy. */
|
|
8
|
+
recovery;
|
|
5
9
|
[ATTACKER_INFLUENCED];
|
|
6
10
|
constructor(code, message, detail = {}, attackerInfluencedFields2 = []) {
|
|
7
11
|
super(message);
|
|
8
12
|
this.name = "AgentResolverError";
|
|
9
13
|
this.code = code;
|
|
10
14
|
this.detail = detail;
|
|
15
|
+
const discovery = code === "request_signature_capabilities_unreachable" || code === "request_signature_brand_json_unreachable";
|
|
16
|
+
this.recovery = discovery && isPermanentDiscoveryFailure(detail) ? "terminal" : RequestSigningErrorCodeMetadata[code].recovery;
|
|
11
17
|
this[ATTACKER_INFLUENCED] = attackerInfluencedFields2;
|
|
12
18
|
}
|
|
13
19
|
}
|
|
@@ -40,3 +40,12 @@ export declare function safeFetchJson(url: string, kind: FetchKind, options: {
|
|
|
40
40
|
maxBodyBytes: number;
|
|
41
41
|
lookup?: SsrfDnsLookup;
|
|
42
42
|
}): Promise<SafeFetchJsonResult>;
|
|
43
|
+
/** Coarse causes only: never expose messages, resolved addresses, or topology. */
|
|
44
|
+
export declare function classifyDiscoveryFailure(error: unknown): {
|
|
45
|
+
dns_error: SafeFetchError['transport'];
|
|
46
|
+
http_status?: number;
|
|
47
|
+
};
|
|
48
|
+
export declare function isPermanentDiscoveryFailure(detail: {
|
|
49
|
+
dns_error?: string;
|
|
50
|
+
http_status?: number;
|
|
51
|
+
}): boolean;
|
|
@@ -40,3 +40,12 @@ export declare function safeFetchJson(url: string, kind: FetchKind, options: {
|
|
|
40
40
|
maxBodyBytes: number;
|
|
41
41
|
lookup?: SsrfDnsLookup;
|
|
42
42
|
}): Promise<SafeFetchJsonResult>;
|
|
43
|
+
/** Coarse causes only: never expose messages, resolved addresses, or topology. */
|
|
44
|
+
export declare function classifyDiscoveryFailure(error: unknown): {
|
|
45
|
+
dns_error: SafeFetchError['transport'];
|
|
46
|
+
http_status?: number;
|
|
47
|
+
};
|
|
48
|
+
export declare function isPermanentDiscoveryFailure(detail: {
|
|
49
|
+
dns_error?: string;
|
|
50
|
+
http_status?: number;
|
|
51
|
+
}): boolean;
|
|
@@ -24,10 +24,13 @@ __export(fetch_helpers_exports, {
|
|
|
24
24
|
MAX_CAPABILITIES_BYTES: () => MAX_CAPABILITIES_BYTES,
|
|
25
25
|
MAX_JWKS_BYTES: () => MAX_JWKS_BYTES,
|
|
26
26
|
SafeFetchError: () => SafeFetchError,
|
|
27
|
+
classifyDiscoveryFailure: () => classifyDiscoveryFailure,
|
|
28
|
+
isPermanentDiscoveryFailure: () => isPermanentDiscoveryFailure,
|
|
27
29
|
safeFetchJson: () => safeFetchJson
|
|
28
30
|
});
|
|
29
31
|
module.exports = __toCommonJS(fetch_helpers_exports);
|
|
30
32
|
var import_net = require('../../net/index.js');
|
|
33
|
+
var import_agent_transport_fetch = require('../../net/agent-transport-fetch.js');
|
|
31
34
|
var import_strict_json = require('./strict-json.js');
|
|
32
35
|
const MAX_CAPABILITIES_BYTES = 65536;
|
|
33
36
|
const MAX_BRAND_JSON_BYTES = 262144;
|
|
@@ -60,7 +63,11 @@ async function safeFetchJson(url, kind, options) {
|
|
|
60
63
|
res = await (0, import_net.ssrfSafeFetch)(url, fetchOpts);
|
|
61
64
|
} catch (err) {
|
|
62
65
|
if (err instanceof import_net.SsrfRefusedError) {
|
|
63
|
-
throw new SafeFetchError(
|
|
66
|
+
throw new SafeFetchError(
|
|
67
|
+
kind,
|
|
68
|
+
import_net.SSRF_TRANSIENT_CODES.has(err.code) ? err.code === "body_exceeds_limit" ? "body_cap" : "dns_error" : "ssrf_refused",
|
|
69
|
+
`${kind} fetch refused: ${err.code}`
|
|
70
|
+
);
|
|
64
71
|
}
|
|
65
72
|
const message = err instanceof Error ? err.message : String(err);
|
|
66
73
|
if (/abort|timed?\s*out/i.test(message)) {
|
|
@@ -88,6 +95,31 @@ async function safeFetchJson(url, kind, options) {
|
|
|
88
95
|
fetchedAt: Math.floor(Date.now() / 1e3)
|
|
89
96
|
};
|
|
90
97
|
}
|
|
98
|
+
function classifyDiscoveryFailure(error) {
|
|
99
|
+
const seen = /* @__PURE__ */ new Set();
|
|
100
|
+
for (let depth = 0; error && typeof error === "object" && depth < 8 && !seen.has(error); depth++) {
|
|
101
|
+
seen.add(error);
|
|
102
|
+
if (error instanceof SafeFetchError)
|
|
103
|
+
return { dns_error: error.transport, ...error.httpStatus !== void 0 && { http_status: error.httpStatus } };
|
|
104
|
+
if (error instanceof import_agent_transport_fetch.AgentTransportPolicyError) return { dns_error: "ssrf_refused" };
|
|
105
|
+
if (error instanceof import_net.SsrfRefusedError)
|
|
106
|
+
return {
|
|
107
|
+
dns_error: import_net.SSRF_TRANSIENT_CODES.has(error.code) ? error.code === "body_exceeds_limit" ? "body_cap" : "dns_error" : "ssrf_refused"
|
|
108
|
+
};
|
|
109
|
+
const obj = error;
|
|
110
|
+
const status = obj.httpStatus ?? obj.status ?? obj.statusCode;
|
|
111
|
+
if (typeof status === "number" && status >= 400 && status <= 599)
|
|
112
|
+
return { dns_error: "fetch_failed", http_status: status };
|
|
113
|
+
if (obj.code === "ENOTFOUND" || obj.code === "EAI_AGAIN") return { dns_error: "dns_error" };
|
|
114
|
+
if (obj.name === "AbortError" || obj.name === "TimeoutError" || obj.code === "ETIMEDOUT" || typeof obj.code === "string" && obj.code.startsWith("UND_ERR_") && obj.code.endsWith("_TIMEOUT"))
|
|
115
|
+
return { dns_error: "timeout" };
|
|
116
|
+
error = obj.cause;
|
|
117
|
+
}
|
|
118
|
+
return { dns_error: "fetch_failed" };
|
|
119
|
+
}
|
|
120
|
+
function isPermanentDiscoveryFailure(detail) {
|
|
121
|
+
return detail.dns_error === "ssrf_refused" || detail.http_status !== void 0 && detail.http_status >= 400 && detail.http_status < 500;
|
|
122
|
+
}
|
|
91
123
|
// Annotate the CommonJS export names for ESM import in node:
|
|
92
124
|
0 && (module.exports = {
|
|
93
125
|
DEFAULT_CONNECT_TIMEOUT_MS,
|
|
@@ -96,5 +128,7 @@ async function safeFetchJson(url, kind, options) {
|
|
|
96
128
|
MAX_CAPABILITIES_BYTES,
|
|
97
129
|
MAX_JWKS_BYTES,
|
|
98
130
|
SafeFetchError,
|
|
131
|
+
classifyDiscoveryFailure,
|
|
132
|
+
isPermanentDiscoveryFailure,
|
|
99
133
|
safeFetchJson
|
|
100
134
|
});
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ssrfSafeFetch,
|
|
3
|
+
SsrfRefusedError,
|
|
4
|
+
SSRF_TRANSIENT_CODES
|
|
5
|
+
} from "../../net/index.mjs";
|
|
6
|
+
import { AgentTransportPolicyError } from "../../net/agent-transport-fetch.mjs";
|
|
2
7
|
import { parseStrictJson, StrictJsonError } from "./strict-json.mjs";
|
|
3
8
|
const MAX_CAPABILITIES_BYTES = 65536;
|
|
4
9
|
const MAX_BRAND_JSON_BYTES = 262144;
|
|
@@ -31,7 +36,11 @@ async function safeFetchJson(url, kind, options) {
|
|
|
31
36
|
res = await ssrfSafeFetch(url, fetchOpts);
|
|
32
37
|
} catch (err) {
|
|
33
38
|
if (err instanceof SsrfRefusedError) {
|
|
34
|
-
throw new SafeFetchError(
|
|
39
|
+
throw new SafeFetchError(
|
|
40
|
+
kind,
|
|
41
|
+
SSRF_TRANSIENT_CODES.has(err.code) ? err.code === "body_exceeds_limit" ? "body_cap" : "dns_error" : "ssrf_refused",
|
|
42
|
+
`${kind} fetch refused: ${err.code}`
|
|
43
|
+
);
|
|
35
44
|
}
|
|
36
45
|
const message = err instanceof Error ? err.message : String(err);
|
|
37
46
|
if (/abort|timed?\s*out/i.test(message)) {
|
|
@@ -59,6 +68,31 @@ async function safeFetchJson(url, kind, options) {
|
|
|
59
68
|
fetchedAt: Math.floor(Date.now() / 1e3)
|
|
60
69
|
};
|
|
61
70
|
}
|
|
71
|
+
function classifyDiscoveryFailure(error) {
|
|
72
|
+
const seen = /* @__PURE__ */ new Set();
|
|
73
|
+
for (let depth = 0; error && typeof error === "object" && depth < 8 && !seen.has(error); depth++) {
|
|
74
|
+
seen.add(error);
|
|
75
|
+
if (error instanceof SafeFetchError)
|
|
76
|
+
return { dns_error: error.transport, ...error.httpStatus !== void 0 && { http_status: error.httpStatus } };
|
|
77
|
+
if (error instanceof AgentTransportPolicyError) return { dns_error: "ssrf_refused" };
|
|
78
|
+
if (error instanceof SsrfRefusedError)
|
|
79
|
+
return {
|
|
80
|
+
dns_error: SSRF_TRANSIENT_CODES.has(error.code) ? error.code === "body_exceeds_limit" ? "body_cap" : "dns_error" : "ssrf_refused"
|
|
81
|
+
};
|
|
82
|
+
const obj = error;
|
|
83
|
+
const status = obj.httpStatus ?? obj.status ?? obj.statusCode;
|
|
84
|
+
if (typeof status === "number" && status >= 400 && status <= 599)
|
|
85
|
+
return { dns_error: "fetch_failed", http_status: status };
|
|
86
|
+
if (obj.code === "ENOTFOUND" || obj.code === "EAI_AGAIN") return { dns_error: "dns_error" };
|
|
87
|
+
if (obj.name === "AbortError" || obj.name === "TimeoutError" || obj.code === "ETIMEDOUT" || typeof obj.code === "string" && obj.code.startsWith("UND_ERR_") && obj.code.endsWith("_TIMEOUT"))
|
|
88
|
+
return { dns_error: "timeout" };
|
|
89
|
+
error = obj.cause;
|
|
90
|
+
}
|
|
91
|
+
return { dns_error: "fetch_failed" };
|
|
92
|
+
}
|
|
93
|
+
function isPermanentDiscoveryFailure(detail) {
|
|
94
|
+
return detail.dns_error === "ssrf_refused" || detail.http_status !== void 0 && detail.http_status >= 400 && detail.http_status < 500;
|
|
95
|
+
}
|
|
62
96
|
export {
|
|
63
97
|
DEFAULT_CONNECT_TIMEOUT_MS,
|
|
64
98
|
DEFAULT_TOTAL_TIMEOUT_MS,
|
|
@@ -66,5 +100,7 @@ export {
|
|
|
66
100
|
MAX_CAPABILITIES_BYTES,
|
|
67
101
|
MAX_JWKS_BYTES,
|
|
68
102
|
SafeFetchError,
|
|
103
|
+
classifyDiscoveryFailure,
|
|
104
|
+
isPermanentDiscoveryFailure,
|
|
69
105
|
safeFetchJson
|
|
70
106
|
};
|
|
@@ -42,7 +42,7 @@ async function fetchLegacyBrandJson(agentUrl, options) {
|
|
|
42
42
|
} catch (err) {
|
|
43
43
|
throw new import_fetch_helpers.SafeFetchError(
|
|
44
44
|
"brand.json",
|
|
45
|
-
|
|
45
|
+
(0, import_fetch_helpers.classifyDiscoveryFailure)(err).dns_error,
|
|
46
46
|
err instanceof import_brand_jwks.BrandJsonResolverError && err.code === "invalid_body" ? "brand.json body failed strict-JSON parse" : "Legacy brand.json discovery failed",
|
|
47
47
|
err instanceof import_brand_jwks.BrandJsonResolverError ? err.httpStatus : void 0
|
|
48
48
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fetchBrandJson, BrandJsonResolverError } from "../brand-jwks.mjs";
|
|
2
2
|
import { eTldPlusOne } from "./etld.mjs";
|
|
3
|
-
import { SafeFetchError } from "./fetch-helpers.mjs";
|
|
3
|
+
import { SafeFetchError, classifyDiscoveryFailure } from "./fetch-helpers.mjs";
|
|
4
4
|
async function fetchLegacyBrandJson(agentUrl, options) {
|
|
5
5
|
const agent = new URL(agentUrl);
|
|
6
6
|
let startUrl = `${agent.origin}/.well-known/brand.json`;
|
|
@@ -19,7 +19,7 @@ async function fetchLegacyBrandJson(agentUrl, options) {
|
|
|
19
19
|
} catch (err) {
|
|
20
20
|
throw new SafeFetchError(
|
|
21
21
|
"brand.json",
|
|
22
|
-
|
|
22
|
+
classifyDiscoveryFailure(err).dns_error,
|
|
23
23
|
err instanceof BrandJsonResolverError && err.code === "invalid_body" ? "brand.json body failed strict-JSON parse" : "Legacy brand.json discovery failed",
|
|
24
24
|
err instanceof BrandJsonResolverError ? err.httpStatus : void 0
|
|
25
25
|
);
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* `SsrfRefusedError` carries.
|
|
29
29
|
*/
|
|
30
30
|
import type { IdentityKeyOriginPurpose, IdentityPosture } from './capabilities-types.mjs';
|
|
31
|
-
import type
|
|
31
|
+
import { type SsrfDnsLookup } from '../../net/index.mjs';
|
|
32
32
|
import { type ConsistencyResult } from './consistency.mjs';
|
|
33
33
|
import { type AgentResolverErrorDetail } from './errors.mjs';
|
|
34
34
|
import { type AgentEntry } from './select-agent.mjs';
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* `SsrfRefusedError` carries.
|
|
29
29
|
*/
|
|
30
30
|
import type { IdentityKeyOriginPurpose, IdentityPosture } from './capabilities-types';
|
|
31
|
-
import type
|
|
31
|
+
import { type SsrfDnsLookup } from '../../net';
|
|
32
32
|
import { type ConsistencyResult } from './consistency';
|
|
33
33
|
import { type AgentResolverErrorDetail } from './errors';
|
|
34
34
|
import { type AgentEntry } from './select-agent';
|
|
@@ -22,9 +22,12 @@ __export(resolve_agent_exports, {
|
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(resolve_agent_exports);
|
|
24
24
|
var import_tldts = require("tldts");
|
|
25
|
+
var import_node_net = require("node:net");
|
|
25
26
|
var import_protocols = require('../../protocols/index.js');
|
|
26
27
|
var import_domain = require('../../brand/domain.js');
|
|
27
28
|
var import_capabilities_types = require('./capabilities-types.js');
|
|
29
|
+
var import_net = require('../../net/index.js');
|
|
30
|
+
var import_agent_transport_fetch = require('../../net/agent-transport-fetch.js');
|
|
28
31
|
var import_operator_authorization = require('./operator-authorization.js');
|
|
29
32
|
var import_legacy_brand = require('./legacy-brand.js');
|
|
30
33
|
var import_consistency = require('./consistency.js');
|
|
@@ -42,7 +45,7 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
42
45
|
const caps = options.bodyCaps ?? {};
|
|
43
46
|
const brandJsonCap = caps.brandJsonBytes ?? import_fetch_helpers.MAX_BRAND_JSON_BYTES;
|
|
44
47
|
const jwksCap = caps.jwksBytes ?? import_fetch_helpers.MAX_JWKS_BYTES;
|
|
45
|
-
const fetchCapabilities = options.fetchCapabilities ?? defaultFetchCapabilities(agentUrl, options.protocol ?? "mcp");
|
|
48
|
+
const fetchCapabilities = options.fetchCapabilities ?? defaultFetchCapabilities(agentUrl, options.protocol ?? "mcp", allowPrivateIp, timeoutMs);
|
|
46
49
|
let capabilitiesPayload;
|
|
47
50
|
let capabilitiesFetchedAt;
|
|
48
51
|
try {
|
|
@@ -59,7 +62,7 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
59
62
|
} catch (err) {
|
|
60
63
|
const detail = {
|
|
61
64
|
agent_url: agentUrl,
|
|
62
|
-
|
|
65
|
+
...(0, import_fetch_helpers.classifyDiscoveryFailure)(err),
|
|
63
66
|
last_attempt_at: now()
|
|
64
67
|
};
|
|
65
68
|
pushTrace(trace, { step: 1, name: "fetch_capabilities", ok: false, url: agentUrl, detail });
|
|
@@ -368,10 +371,49 @@ function checkAllowPrivateIp(requested) {
|
|
|
368
371
|
"resolveAgent: allowPrivateIp=true refused outside {NODE_ENV=test, NODE_ENV=development}. Set NODE_ENV appropriately for dev/test, or set ADCP_RESOLVER_ALLOW_PRIVATE_IP=1 as an explicit ops acknowledgment if you genuinely need private-IP discovery in this process."
|
|
369
372
|
);
|
|
370
373
|
}
|
|
371
|
-
function defaultFetchCapabilities(agentUrl, protocol) {
|
|
374
|
+
function defaultFetchCapabilities(agentUrl, protocol, allowPrivateIp, timeoutMs) {
|
|
372
375
|
return async () => {
|
|
373
|
-
const
|
|
374
|
-
|
|
376
|
+
const url = new URL(agentUrl);
|
|
377
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, "");
|
|
378
|
+
if (!allowPrivateIp && url.protocol !== "https:" || (0, import_node_net.isIP)(hostname) && ((0, import_net.isAlwaysBlocked)(hostname) || !allowPrivateIp && (0, import_net.isPrivateIp)(hostname)))
|
|
379
|
+
throw new import_agent_transport_fetch.AgentTransportPolicyError("Signing discovery URL refused by address or HTTPS policy.");
|
|
380
|
+
let failure;
|
|
381
|
+
const upstream = (0, import_agent_transport_fetch.createAgentTransportFetch)(agentUrl, {
|
|
382
|
+
allowPrivateIp,
|
|
383
|
+
allowPrivateInitialOrigin: false,
|
|
384
|
+
requireHttps: true
|
|
385
|
+
});
|
|
386
|
+
let postSucceeded = false;
|
|
387
|
+
const trustedFetchFn = async (input, init) => {
|
|
388
|
+
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
389
|
+
try {
|
|
390
|
+
const response = await upstream(input, init);
|
|
391
|
+
const isGet = method.toUpperCase() === "GET";
|
|
392
|
+
const observed = response.status >= 400 ? { dns_error: "fetch_failed", http_status: response.status } : void 0;
|
|
393
|
+
if (protocol === "mcp" && method.toUpperCase() === "POST" && response.ok) postSucceeded = true;
|
|
394
|
+
if (!(protocol === "mcp" && isGet && (postSucceeded || response.status === 405)) && !(isGet && observed && failure && !(0, import_fetch_helpers.isPermanentDiscoveryFailure)(failure) && (0, import_fetch_helpers.isPermanentDiscoveryFailure)(observed)))
|
|
395
|
+
failure = observed;
|
|
396
|
+
return response;
|
|
397
|
+
} catch (error) {
|
|
398
|
+
if (!(protocol === "mcp" && method.toUpperCase() === "GET" && postSucceeded))
|
|
399
|
+
failure = (0, import_fetch_helpers.classifyDiscoveryFailure)(error);
|
|
400
|
+
throw error;
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
const transport = { trustedFetchFn, requestTimeoutMs: timeoutMs };
|
|
404
|
+
const client = protocol === "a2a" ? (0, import_protocols.createA2AClient)(agentUrl, void 0, void 0, void 0, void 0, transport) : (0, import_protocols.createMCPClient)(agentUrl, void 0, void 0, void 0, void 0, transport);
|
|
405
|
+
try {
|
|
406
|
+
return await client.callTool("get_adcp_capabilities", {});
|
|
407
|
+
} catch (error) {
|
|
408
|
+
if (failure)
|
|
409
|
+
throw new import_fetch_helpers.SafeFetchError(
|
|
410
|
+
"capabilities",
|
|
411
|
+
failure.dns_error,
|
|
412
|
+
"Capabilities fetch failed",
|
|
413
|
+
failure.http_status
|
|
414
|
+
);
|
|
415
|
+
throw error;
|
|
416
|
+
}
|
|
375
417
|
};
|
|
376
418
|
}
|
|
377
419
|
function findAuthorizedOperator(brandJson, agentEtld1, _now, _options) {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { parse as parseTld } from "tldts";
|
|
2
|
+
import { isIP } from "node:net";
|
|
2
3
|
import { createA2AClient, createMCPClient } from "../../protocols/index.mjs";
|
|
3
4
|
import { isDevelopmentBrandDomain } from "../../brand/domain.mjs";
|
|
4
5
|
import { readBrandJsonUrl, readIdentityPosture } from "./capabilities-types.mjs";
|
|
6
|
+
import { isAlwaysBlocked, isPrivateIp } from "../../net/index.mjs";
|
|
7
|
+
import { AgentTransportPolicyError, createAgentTransportFetch } from "../../net/agent-transport-fetch.mjs";
|
|
5
8
|
import { checkDelegatedOperatorAuthorization } from "./operator-authorization.mjs";
|
|
6
9
|
import { fetchLegacyBrandJson } from "./legacy-brand.mjs";
|
|
7
10
|
import {
|
|
@@ -11,7 +14,14 @@ import {
|
|
|
11
14
|
} from "./consistency.mjs";
|
|
12
15
|
import { AgentResolverError } from "./errors.mjs";
|
|
13
16
|
import { eTldPlusOne, sameEtldPlusOne } from "./etld.mjs";
|
|
14
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
MAX_BRAND_JSON_BYTES,
|
|
19
|
+
MAX_JWKS_BYTES,
|
|
20
|
+
safeFetchJson,
|
|
21
|
+
SafeFetchError,
|
|
22
|
+
classifyDiscoveryFailure,
|
|
23
|
+
isPermanentDiscoveryFailure
|
|
24
|
+
} from "./fetch-helpers.mjs";
|
|
15
25
|
import { unwrapProtocolResponse } from "../protocol-response.mjs";
|
|
16
26
|
import { selectAgentByUrl, agentJwksUri, AgentSelectorError, canonicalAgentUrl } from "./select-agent.mjs";
|
|
17
27
|
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
@@ -23,7 +33,7 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
23
33
|
const caps = options.bodyCaps ?? {};
|
|
24
34
|
const brandJsonCap = caps.brandJsonBytes ?? MAX_BRAND_JSON_BYTES;
|
|
25
35
|
const jwksCap = caps.jwksBytes ?? MAX_JWKS_BYTES;
|
|
26
|
-
const fetchCapabilities = options.fetchCapabilities ?? defaultFetchCapabilities(agentUrl, options.protocol ?? "mcp");
|
|
36
|
+
const fetchCapabilities = options.fetchCapabilities ?? defaultFetchCapabilities(agentUrl, options.protocol ?? "mcp", allowPrivateIp, timeoutMs);
|
|
27
37
|
let capabilitiesPayload;
|
|
28
38
|
let capabilitiesFetchedAt;
|
|
29
39
|
try {
|
|
@@ -40,7 +50,7 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
40
50
|
} catch (err) {
|
|
41
51
|
const detail = {
|
|
42
52
|
agent_url: agentUrl,
|
|
43
|
-
|
|
53
|
+
...classifyDiscoveryFailure(err),
|
|
44
54
|
last_attempt_at: now()
|
|
45
55
|
};
|
|
46
56
|
pushTrace(trace, { step: 1, name: "fetch_capabilities", ok: false, url: agentUrl, detail });
|
|
@@ -349,10 +359,49 @@ function checkAllowPrivateIp(requested) {
|
|
|
349
359
|
"resolveAgent: allowPrivateIp=true refused outside {NODE_ENV=test, NODE_ENV=development}. Set NODE_ENV appropriately for dev/test, or set ADCP_RESOLVER_ALLOW_PRIVATE_IP=1 as an explicit ops acknowledgment if you genuinely need private-IP discovery in this process."
|
|
350
360
|
);
|
|
351
361
|
}
|
|
352
|
-
function defaultFetchCapabilities(agentUrl, protocol) {
|
|
362
|
+
function defaultFetchCapabilities(agentUrl, protocol, allowPrivateIp, timeoutMs) {
|
|
353
363
|
return async () => {
|
|
354
|
-
const
|
|
355
|
-
|
|
364
|
+
const url = new URL(agentUrl);
|
|
365
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, "");
|
|
366
|
+
if (!allowPrivateIp && url.protocol !== "https:" || isIP(hostname) && (isAlwaysBlocked(hostname) || !allowPrivateIp && isPrivateIp(hostname)))
|
|
367
|
+
throw new AgentTransportPolicyError("Signing discovery URL refused by address or HTTPS policy.");
|
|
368
|
+
let failure;
|
|
369
|
+
const upstream = createAgentTransportFetch(agentUrl, {
|
|
370
|
+
allowPrivateIp,
|
|
371
|
+
allowPrivateInitialOrigin: false,
|
|
372
|
+
requireHttps: true
|
|
373
|
+
});
|
|
374
|
+
let postSucceeded = false;
|
|
375
|
+
const trustedFetchFn = async (input, init) => {
|
|
376
|
+
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
377
|
+
try {
|
|
378
|
+
const response = await upstream(input, init);
|
|
379
|
+
const isGet = method.toUpperCase() === "GET";
|
|
380
|
+
const observed = response.status >= 400 ? { dns_error: "fetch_failed", http_status: response.status } : void 0;
|
|
381
|
+
if (protocol === "mcp" && method.toUpperCase() === "POST" && response.ok) postSucceeded = true;
|
|
382
|
+
if (!(protocol === "mcp" && isGet && (postSucceeded || response.status === 405)) && !(isGet && observed && failure && !isPermanentDiscoveryFailure(failure) && isPermanentDiscoveryFailure(observed)))
|
|
383
|
+
failure = observed;
|
|
384
|
+
return response;
|
|
385
|
+
} catch (error) {
|
|
386
|
+
if (!(protocol === "mcp" && method.toUpperCase() === "GET" && postSucceeded))
|
|
387
|
+
failure = classifyDiscoveryFailure(error);
|
|
388
|
+
throw error;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
const transport = { trustedFetchFn, requestTimeoutMs: timeoutMs };
|
|
392
|
+
const client = protocol === "a2a" ? createA2AClient(agentUrl, void 0, void 0, void 0, void 0, transport) : createMCPClient(agentUrl, void 0, void 0, void 0, void 0, transport);
|
|
393
|
+
try {
|
|
394
|
+
return await client.callTool("get_adcp_capabilities", {});
|
|
395
|
+
} catch (error) {
|
|
396
|
+
if (failure)
|
|
397
|
+
throw new SafeFetchError(
|
|
398
|
+
"capabilities",
|
|
399
|
+
failure.dns_error,
|
|
400
|
+
"Capabilities fetch failed",
|
|
401
|
+
failure.http_status
|
|
402
|
+
);
|
|
403
|
+
throw error;
|
|
404
|
+
}
|
|
356
405
|
};
|
|
357
406
|
}
|
|
358
407
|
function findAuthorizedOperator(brandJson, agentEtld1, _now, _options) {
|
|
@@ -14,6 +14,7 @@ export type BrandJsonResolverErrorCode = 'invalid_url' | 'invalid_house' | 'redi
|
|
|
14
14
|
*/
|
|
15
15
|
export declare class BrandJsonResolverError extends Error {
|
|
16
16
|
readonly code: BrandJsonResolverErrorCode;
|
|
17
|
+
readonly recovery: 'terminal' | 'transient';
|
|
17
18
|
readonly cause?: unknown;
|
|
18
19
|
/** HTTP status for `fetch_failed` responses, when a response was received. */
|
|
19
20
|
readonly httpStatus?: number;
|
|
@@ -79,6 +80,7 @@ export declare class BrandJsonJwksResolver implements JwksResolver {
|
|
|
79
80
|
private resolver?;
|
|
80
81
|
private onboarding?;
|
|
81
82
|
private lastOnboardingAttempt;
|
|
83
|
+
private lastOnboardingError?;
|
|
82
84
|
constructor(brandJsonUrl: string, options: BrandJsonJwksResolverOptions);
|
|
83
85
|
private createResolver;
|
|
84
86
|
private confirmedResolver;
|
|
@@ -14,6 +14,7 @@ export type BrandJsonResolverErrorCode = 'invalid_url' | 'invalid_house' | 'redi
|
|
|
14
14
|
*/
|
|
15
15
|
export declare class BrandJsonResolverError extends Error {
|
|
16
16
|
readonly code: BrandJsonResolverErrorCode;
|
|
17
|
+
readonly recovery: 'terminal' | 'transient';
|
|
17
18
|
readonly cause?: unknown;
|
|
18
19
|
/** HTTP status for `fetch_failed` responses, when a response was received. */
|
|
19
20
|
readonly httpStatus?: number;
|
|
@@ -79,6 +80,7 @@ export declare class BrandJsonJwksResolver implements JwksResolver {
|
|
|
79
80
|
private resolver?;
|
|
80
81
|
private onboarding?;
|
|
81
82
|
private lastOnboardingAttempt;
|
|
83
|
+
private lastOnboardingError?;
|
|
82
84
|
constructor(brandJsonUrl: string, options: BrandJsonJwksResolverOptions);
|
|
83
85
|
private createResolver;
|
|
84
86
|
private confirmedResolver;
|
|
@@ -23,12 +23,14 @@ __export(brand_jwks_exports, {
|
|
|
23
23
|
fetchBrandJson: () => fetchBrandJson
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(brand_jwks_exports);
|
|
26
|
+
var import_fetch_helpers = require('./agent-resolver/fetch-helpers.js');
|
|
26
27
|
var import_net = require('../net/index.js');
|
|
27
28
|
var import_resolved_agent_jwks = require('./agent-resolver/resolved-agent-jwks.js');
|
|
28
29
|
var import_strict_json = require('./agent-resolver/strict-json.js');
|
|
29
30
|
var import_select_agent = require('./agent-resolver/select-agent.js');
|
|
30
31
|
class BrandJsonResolverError extends Error {
|
|
31
32
|
code;
|
|
33
|
+
recovery;
|
|
32
34
|
cause;
|
|
33
35
|
/** HTTP status for `fetch_failed` responses, when a response was received. */
|
|
34
36
|
httpStatus;
|
|
@@ -38,6 +40,7 @@ class BrandJsonResolverError extends Error {
|
|
|
38
40
|
this.code = code;
|
|
39
41
|
this.httpStatus = details.httpStatus;
|
|
40
42
|
this.cause = details.cause;
|
|
43
|
+
this.recovery = code !== "fetch_failed" || (0, import_fetch_helpers.isPermanentDiscoveryFailure)((0, import_fetch_helpers.classifyDiscoveryFailure)(this)) ? "terminal" : "transient";
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
const DEFAULT_MIN_COOLDOWN_SECONDS = 30;
|
|
@@ -63,6 +66,7 @@ class BrandJsonJwksResolver {
|
|
|
63
66
|
resolver;
|
|
64
67
|
onboarding;
|
|
65
68
|
lastOnboardingAttempt = Number.NEGATIVE_INFINITY;
|
|
69
|
+
lastOnboardingError;
|
|
66
70
|
createResolver(agentUrl, operatorUrl) {
|
|
67
71
|
const options = this.options;
|
|
68
72
|
return new import_resolved_agent_jwks.ResolvedAgentJwksResolver(agentUrl, options.protocol ?? "mcp", {
|
|
@@ -86,7 +90,7 @@ class BrandJsonJwksResolver {
|
|
|
86
90
|
if (this.onboarding) return this.onboarding;
|
|
87
91
|
const now = this.options.now?.() ?? Date.now() / 1e3;
|
|
88
92
|
if (now - this.lastOnboardingAttempt < 30)
|
|
89
|
-
throw new BrandJsonResolverError("fetch_failed", "Operator onboarding discovery is in its retry cooldown.");
|
|
93
|
+
throw this.lastOnboardingError ?? new BrandJsonResolverError("fetch_failed", "Operator onboarding discovery is in its retry cooldown.");
|
|
90
94
|
this.lastOnboardingAttempt = now;
|
|
91
95
|
this.onboarding = (async () => {
|
|
92
96
|
const record = await fetchBrandJson({
|
|
@@ -99,8 +103,12 @@ class BrandJsonJwksResolver {
|
|
|
99
103
|
const resolver = this.createResolver(agentUrl, record.finalUrl);
|
|
100
104
|
await resolver.forceRefresh();
|
|
101
105
|
this.resolver = resolver;
|
|
106
|
+
this.lastOnboardingError = void 0;
|
|
102
107
|
return resolver;
|
|
103
|
-
})().
|
|
108
|
+
})().catch((error) => {
|
|
109
|
+
this.lastOnboardingError = error;
|
|
110
|
+
throw error;
|
|
111
|
+
}).finally(() => {
|
|
104
112
|
this.onboarding = void 0;
|
|
105
113
|
});
|
|
106
114
|
return this.onboarding;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { classifyDiscoveryFailure, isPermanentDiscoveryFailure } from "./agent-resolver/fetch-helpers.mjs";
|
|
1
2
|
import { ssrfSafeFetch } from "../net/index.mjs";
|
|
2
3
|
import { ResolvedAgentJwksResolver } from "./agent-resolver/resolved-agent-jwks.mjs";
|
|
3
4
|
import { parseStrictJson } from "./agent-resolver/strict-json.mjs";
|
|
4
5
|
import { canonicalAgentUrl } from "./agent-resolver/select-agent.mjs";
|
|
5
6
|
class BrandJsonResolverError extends Error {
|
|
6
7
|
code;
|
|
8
|
+
recovery;
|
|
7
9
|
cause;
|
|
8
10
|
/** HTTP status for `fetch_failed` responses, when a response was received. */
|
|
9
11
|
httpStatus;
|
|
@@ -13,6 +15,7 @@ class BrandJsonResolverError extends Error {
|
|
|
13
15
|
this.code = code;
|
|
14
16
|
this.httpStatus = details.httpStatus;
|
|
15
17
|
this.cause = details.cause;
|
|
18
|
+
this.recovery = code !== "fetch_failed" || isPermanentDiscoveryFailure(classifyDiscoveryFailure(this)) ? "terminal" : "transient";
|
|
16
19
|
}
|
|
17
20
|
}
|
|
18
21
|
const DEFAULT_MIN_COOLDOWN_SECONDS = 30;
|
|
@@ -38,6 +41,7 @@ class BrandJsonJwksResolver {
|
|
|
38
41
|
resolver;
|
|
39
42
|
onboarding;
|
|
40
43
|
lastOnboardingAttempt = Number.NEGATIVE_INFINITY;
|
|
44
|
+
lastOnboardingError;
|
|
41
45
|
createResolver(agentUrl, operatorUrl) {
|
|
42
46
|
const options = this.options;
|
|
43
47
|
return new ResolvedAgentJwksResolver(agentUrl, options.protocol ?? "mcp", {
|
|
@@ -61,7 +65,7 @@ class BrandJsonJwksResolver {
|
|
|
61
65
|
if (this.onboarding) return this.onboarding;
|
|
62
66
|
const now = this.options.now?.() ?? Date.now() / 1e3;
|
|
63
67
|
if (now - this.lastOnboardingAttempt < 30)
|
|
64
|
-
throw new BrandJsonResolverError("fetch_failed", "Operator onboarding discovery is in its retry cooldown.");
|
|
68
|
+
throw this.lastOnboardingError ?? new BrandJsonResolverError("fetch_failed", "Operator onboarding discovery is in its retry cooldown.");
|
|
65
69
|
this.lastOnboardingAttempt = now;
|
|
66
70
|
this.onboarding = (async () => {
|
|
67
71
|
const record = await fetchBrandJson({
|
|
@@ -74,8 +78,12 @@ class BrandJsonJwksResolver {
|
|
|
74
78
|
const resolver = this.createResolver(agentUrl, record.finalUrl);
|
|
75
79
|
await resolver.forceRefresh();
|
|
76
80
|
this.resolver = resolver;
|
|
81
|
+
this.lastOnboardingError = void 0;
|
|
77
82
|
return resolver;
|
|
78
|
-
})().
|
|
83
|
+
})().catch((error) => {
|
|
84
|
+
this.lastOnboardingError = error;
|
|
85
|
+
throw error;
|
|
86
|
+
}).finally(() => {
|
|
79
87
|
this.onboarding = void 0;
|
|
80
88
|
});
|
|
81
89
|
return this.onboarding;
|