@adcp/sdk 14.0.0-rc.50 → 14.0.0-rc.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- 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 +4 -1
- package/dist/lib/core/AgentClient.d.ts +4 -1
- package/dist/lib/core/AgentClient.js +8 -0
- package/dist/lib/core/AgentClient.mjs +8 -0
- package/dist/lib/core/SingleAgentClient.d.mts +10 -1
- package/dist/lib/core/SingleAgentClient.d.ts +10 -1
- package/dist/lib/core/SingleAgentClient.js +238 -34
- package/dist/lib/core/SingleAgentClient.mjs +240 -35
- package/dist/lib/core/TaskExecutor.js +1 -1
- package/dist/lib/core/TaskExecutor.mjs +1 -1
- package/dist/lib/core/account-resolution.d.mts +24 -0
- package/dist/lib/core/account-resolution.d.ts +24 -0
- package/dist/lib/core/account-resolution.js +79 -0
- package/dist/lib/core/account-resolution.mjs +54 -0
- package/dist/lib/errors/index.d.mts +27 -0
- package/dist/lib/errors/index.d.ts +27 -0
- package/dist/lib/errors/index.js +56 -0
- package/dist/lib/errors/index.mjs +52 -0
- package/dist/lib/index.d.mts +4 -3
- package/dist/lib/index.d.ts +4 -3
- package/dist/lib/index.js +10 -0
- package/dist/lib/index.mjs +10 -0
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/testing/scenarios/media-buy.d.mts +2 -1
- package/dist/lib/testing/scenarios/media-buy.d.ts +2 -1
- package/dist/lib/testing/scenarios/media-buy.js +31 -3
- package/dist/lib/testing/scenarios/media-buy.mjs +31 -3
- package/dist/lib/testing/storyboard/runner.d.mts +2 -1
- package/dist/lib/testing/storyboard/runner.d.ts +2 -1
- package/dist/lib/testing/storyboard/runner.js +144 -15
- package/dist/lib/testing/storyboard/runner.mjs +146 -16
- package/dist/lib/testing/storyboard/task-map.d.mts +1 -0
- package/dist/lib/testing/storyboard/task-map.d.ts +1 -0
- package/dist/lib/testing/storyboard/task-map.js +8 -2
- package/dist/lib/testing/storyboard/task-map.mjs +8 -2
- package/dist/lib/testing/storyboard/types.d.mts +10 -3
- package/dist/lib/testing/storyboard/types.d.ts +10 -3
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/utils/capabilities.d.mts +13 -0
- package/dist/lib/utils/capabilities.d.ts +13 -0
- package/dist/lib/utils/capabilities.js +17 -0
- package/dist/lib/utils/capabilities.mjs +16 -0
- package/dist/lib/utils/request-normalizer.js +1 -1
- package/dist/lib/utils/request-normalizer.mjs +1 -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/guides/BUYER-QUICKSTART-3.2.md +28 -1
- package/docs/llms.txt +2 -2
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/package.json +1 -1
|
@@ -119,8 +119,11 @@ import {
|
|
|
119
119
|
resolveFeature,
|
|
120
120
|
listDeclaredFeatures,
|
|
121
121
|
TASK_FEATURE_MAP,
|
|
122
|
-
assertValidIdempotencyReplayTtlSeconds
|
|
122
|
+
assertValidIdempotencyReplayTtlSeconds,
|
|
123
|
+
supportsBuyingMode
|
|
123
124
|
} from "../utils/capabilities.mjs";
|
|
125
|
+
import { AccountPendingApprovalError, AccountRequiredError, UnsupportedBuyingModeError } from "../errors/index.mjs";
|
|
126
|
+
import { sameCountrySet, selectListedAccount } from "./account-resolution.mjs";
|
|
124
127
|
import { normalizeRequestParams } from "../utils/request-normalizer.mjs";
|
|
125
128
|
import { globalAsyncLocalStorage } from "../utils/global-async-local-storage.mjs";
|
|
126
129
|
import { validateUserAgent } from "../utils/validate-user-agent.mjs";
|
|
@@ -310,12 +313,18 @@ function canonicalAccountRoutingSnapshot(value) {
|
|
|
310
313
|
if (typeof sourceBrand.domain !== "string") return void 0;
|
|
311
314
|
const brand = Object.freeze({
|
|
312
315
|
domain: sourceBrand.domain,
|
|
313
|
-
...typeof sourceBrand.brand_id === "string" ? { brand_id: sourceBrand.brand_id } : {}
|
|
316
|
+
...typeof sourceBrand.brand_id === "string" ? { brand_id: sourceBrand.brand_id } : {},
|
|
317
|
+
...Array.isArray(sourceBrand.countries) && sourceBrand.countries.every((country) => typeof country === "string") ? { countries: Object.freeze([...sourceBrand.countries].sort()) } : {}
|
|
314
318
|
});
|
|
319
|
+
const operatorUnit = account.operator_unit;
|
|
320
|
+
const unitId = operatorUnit && typeof operatorUnit === "object" && !Array.isArray(operatorUnit) ? operatorUnit.id : void 0;
|
|
315
321
|
return Object.freeze({
|
|
316
322
|
brand,
|
|
317
323
|
operator: account.operator,
|
|
318
|
-
...typeof
|
|
324
|
+
...typeof unitId === "string" ? { operator_unit: Object.freeze({ id: unitId }) } : {},
|
|
325
|
+
...typeof account.currency === "string" ? { currency: account.currency } : {},
|
|
326
|
+
...typeof account.timezone === "string" ? { timezone: account.timezone } : {},
|
|
327
|
+
sandbox: account.sandbox === true
|
|
319
328
|
});
|
|
320
329
|
}
|
|
321
330
|
function canonicalOptionRefRoutingSnapshot(value) {
|
|
@@ -575,6 +584,13 @@ function snapshotTaskOptions(options) {
|
|
|
575
584
|
}
|
|
576
585
|
};
|
|
577
586
|
}
|
|
587
|
+
function assertRawMethodForGovernedPayload(taskType, options) {
|
|
588
|
+
if (options?.preserveGovernedPayload) {
|
|
589
|
+
throw new ConfigurationError(
|
|
590
|
+
`Governed ${taskType} requires the raw legacy task method; canonical creative projection can reshape approved arguments.`
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
578
594
|
const PRIMARY_ADCP_TASK_NAMES = {
|
|
579
595
|
get_products: true,
|
|
580
596
|
list_products: true,
|
|
@@ -2956,17 +2972,17 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
2956
2972
|
async executeAndHandleWithinDeadline(taskType, handlerName, params, inputHandler, options, transformCompletedResponse, legacyFormatConverter, canonicalRequest, projectionCatalogs) {
|
|
2957
2973
|
throwIfAborted(options?.signal);
|
|
2958
2974
|
const canonicalCreativeInvocation = CANONICAL_CREATIVE_ACTIVITY_TASKS.has(taskType) || canonicalRequest !== void 0;
|
|
2959
|
-
let normalizedParams = normalizeRequestParams(taskType, params, {
|
|
2975
|
+
let normalizedParams = options?.preserveGovernedPayload ? params : normalizeRequestParams(taskType, params, {
|
|
2960
2976
|
skipIdempotencyAutoInject: options?.skipIdempotencyAutoInject,
|
|
2961
2977
|
skipAccountValidation: options?.skipAccountValidation
|
|
2962
2978
|
});
|
|
2963
2979
|
if (!options?.skipRequestValidation) assertNoPrincipalIdentityInput(taskType, normalizedParams);
|
|
2964
2980
|
this.assertRequestSupportedByConfiguredVersion(taskType, normalizedParams, options, canonicalCreativeInvocation);
|
|
2965
2981
|
this.assertDurablePropertyListCredentialSupported(taskType, normalizedParams);
|
|
2966
|
-
if (!options?.skipIdempotencyAutoInject && requestUsesIdempotency(taskType, normalizedParams) && normalizedParams && typeof normalizedParams === "object" && !normalizedParams.idempotency_key) {
|
|
2982
|
+
if (!options?.skipIdempotencyAutoInject && !options?.preserveGovernedPayload && requestUsesIdempotency(taskType, normalizedParams) && normalizedParams && typeof normalizedParams === "object" && !normalizedParams.idempotency_key) {
|
|
2967
2983
|
normalizedParams = { ...normalizedParams, idempotency_key: generateIdempotencyKey() };
|
|
2968
2984
|
}
|
|
2969
|
-
if (!options?.skipIdempotencyAutoInject && !options?.skipAccountValidation && !options?.skipRequestValidation && !isExternalSchemaRootActive(this.resolvedAdcpVersion)) {
|
|
2985
|
+
if (!options?.skipIdempotencyAutoInject && !options?.skipAccountValidation && !options?.skipRequestValidation && !options?.preserveGovernedPayload && !isExternalSchemaRootActive(this.resolvedAdcpVersion)) {
|
|
2970
2986
|
this.validateRequest(taskType, normalizedParams);
|
|
2971
2987
|
}
|
|
2972
2988
|
await this.validateTaskFeatures(taskType, options);
|
|
@@ -2982,7 +2998,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
2982
2998
|
}
|
|
2983
2999
|
const agent = await this.ensureEndpointDiscovered(options);
|
|
2984
3000
|
throwIfAborted(options?.signal);
|
|
2985
|
-
if (!options?.skipIdempotencyAutoInject && !options?.skipAccountValidation && !options?.skipRequestValidation) {
|
|
3001
|
+
if (!options?.skipIdempotencyAutoInject && !options?.skipAccountValidation && !options?.skipRequestValidation && !options?.preserveGovernedPayload) {
|
|
2986
3002
|
this.executor.validateRequest(taskType, normalizedParams);
|
|
2987
3003
|
}
|
|
2988
3004
|
const capabilityDiscoveryContext = {};
|
|
@@ -3005,7 +3021,12 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
3005
3021
|
capabilityDiscoveryContext.capabilities
|
|
3006
3022
|
);
|
|
3007
3023
|
const inputSchemaStripLogs = [];
|
|
3008
|
-
|
|
3024
|
+
if (options?.preserveGovernedPayload && serverVersion !== "v3") {
|
|
3025
|
+
throw new ConfigurationError(
|
|
3026
|
+
"Governed storyboard requests require a v3 seller to preserve the approved payload."
|
|
3027
|
+
);
|
|
3028
|
+
}
|
|
3029
|
+
const { params: adaptedParams, driftLogs: adaptDriftLogs } = options?.preserveGovernedPayload ? { params: normalizedParams, driftLogs: [] } : this.adaptRequest(
|
|
3009
3030
|
taskType,
|
|
3010
3031
|
normalizedParams,
|
|
3011
3032
|
serverVersion,
|
|
@@ -4115,28 +4136,67 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
4115
4136
|
legacyFormatConverter,
|
|
4116
4137
|
projectionCatalogs ?? this.config.projectionCatalogs
|
|
4117
4138
|
);
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
const
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4139
|
+
return withTaskDeadline(snapshotTaskOptions(taskOptions), async (effectiveOptions) => {
|
|
4140
|
+
this.assertRequestSupportedByConfiguredVersion("get_products", params, effectiveOptions);
|
|
4141
|
+
let request = params;
|
|
4142
|
+
const legacyAccountId = request.account_id;
|
|
4143
|
+
const accountReference = request.account ?? (typeof legacyAccountId === "string" && legacyAccountId ? { account_id: legacyAccountId } : void 0);
|
|
4144
|
+
if (!request.account && typeof legacyAccountId === "string" && legacyAccountId) {
|
|
4145
|
+
const { account_id: _legacyAccountId, ...rest } = request;
|
|
4146
|
+
void _legacyAccountId;
|
|
4147
|
+
request = { ...rest, account: { account_id: legacyAccountId } };
|
|
4148
|
+
}
|
|
4149
|
+
if (!effectiveOptions.skipRequestValidation && this.config.validateFeatures !== false) {
|
|
4150
|
+
const capabilities = await this.getCapabilities(effectiveOptions);
|
|
4151
|
+
const sellerDeclares31 = capabilities.servedVersion !== void 0 ? !isPre31AdcpVersion(capabilities.servedVersion) : capabilities.supportedVersions?.some((version) => !isPre31AdcpVersion(version)) === true;
|
|
4152
|
+
const sellerDeclaresBuyingModes = Array.isArray(
|
|
4153
|
+
capabilities._raw?.media_buy?.buying_modes
|
|
4154
|
+
);
|
|
4155
|
+
if (!capabilities._synthetic) {
|
|
4156
|
+
const effectiveMode = request.buying_mode ?? (request.brief ? "brief" : void 0);
|
|
4157
|
+
if (effectiveMode && (sellerDeclares31 || sellerDeclaresBuyingModes) && !supportsBuyingMode(capabilities, effectiveMode)) {
|
|
4158
|
+
throw new UnsupportedBuyingModeError(effectiveMode, capabilities.buyingModes ?? ["brief"]);
|
|
4159
|
+
}
|
|
4160
|
+
if (!effectiveOptions.skipAccountValidation && !accountReference && capabilities.account?.requiredForProducts) {
|
|
4161
|
+
throw new AccountRequiredError(
|
|
4162
|
+
capabilities.account.requireOperatorAuth ? "explicit" : "implicit",
|
|
4163
|
+
"get_products"
|
|
4164
|
+
);
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
if (!request.buying_mode && !request.brief) {
|
|
4168
|
+
const legacySeller = capabilities._synthetic || !sellerDeclares31 && !sellerDeclaresBuyingModes;
|
|
4169
|
+
if (!legacySeller && !supportsBuyingMode(capabilities, "wholesale")) {
|
|
4170
|
+
throw new UnsupportedBuyingModeError(void 0, capabilities.buyingModes ?? ["brief"]);
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
if (!request.buying_mode && !request.brief) {
|
|
4175
|
+
request = { ...request, buying_mode: "wholesale" };
|
|
4176
|
+
}
|
|
4177
|
+
const account = canonicalAccountRoutingSnapshot(accountReference);
|
|
4178
|
+
return this.executeAndHandle(
|
|
4179
|
+
"get_products",
|
|
4180
|
+
"onGetProductsStatusChange",
|
|
4181
|
+
request,
|
|
4182
|
+
inputHandler,
|
|
4183
|
+
effectiveOptions,
|
|
4184
|
+
(data) => {
|
|
4185
|
+
const { response, diagnostics } = toCanonicalOnlyResponse(data, {
|
|
4186
|
+
legacyFormatConverter: effectiveLegacyFormatConverter,
|
|
4187
|
+
projectionCatalogs: projectionCatalogs ?? this.config.projectionCatalogs
|
|
4188
|
+
});
|
|
4189
|
+
const authoritativeProducts = Array.isArray(data.products) ? data.products : response.products;
|
|
4190
|
+
this.rememberCanonicalProductRoutes(response.products, account, authoritativeProducts);
|
|
4191
|
+
const { _message: _dropLegacyMessage, ...canonical } = response;
|
|
4192
|
+
void _dropLegacyMessage;
|
|
4193
|
+
return { ...canonical, projection: { diagnostics } };
|
|
4194
|
+
},
|
|
4195
|
+
effectiveLegacyFormatConverter,
|
|
4196
|
+
void 0,
|
|
4197
|
+
projectionCatalogs
|
|
4198
|
+
);
|
|
4199
|
+
});
|
|
4140
4200
|
}
|
|
4141
4201
|
/** Discover products through the compact AdCP 3.2 catalog task. */
|
|
4142
4202
|
async listProducts(params, inputHandler, options) {
|
|
@@ -4150,11 +4210,14 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
4150
4210
|
}
|
|
4151
4211
|
/** @deprecated Explicit raw-wire escape hatch for migration tooling. */
|
|
4152
4212
|
async getProductsLegacy(params, inputHandler, options) {
|
|
4153
|
-
|
|
4213
|
+
const request = params.buying_mode || params.brief ? params : { ...params, buying_mode: "wholesale" };
|
|
4214
|
+
return this.executeTaskUnprojected("get_products", request, inputHandler, options);
|
|
4154
4215
|
}
|
|
4155
4216
|
/** @internal Run final legacy get_products adaptation before an application-owned atomic mutation claim. */
|
|
4156
4217
|
async getProductsLegacyWithPreDispatch(params, beforeDispatch, inputHandler, options) {
|
|
4157
|
-
const requestSnapshot = structuredClone(
|
|
4218
|
+
const requestSnapshot = structuredClone(
|
|
4219
|
+
params.buying_mode || params.brief ? params : { ...params, buying_mode: "wholesale" }
|
|
4220
|
+
);
|
|
4158
4221
|
return this.executeTaskUnprojected(
|
|
4159
4222
|
"get_products",
|
|
4160
4223
|
requestSnapshot,
|
|
@@ -4193,6 +4256,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
4193
4256
|
* @param options - Task execution options
|
|
4194
4257
|
*/
|
|
4195
4258
|
async createMediaBuy(params, inputHandler, options) {
|
|
4259
|
+
assertRawMethodForGovernedPayload("create_media_buy", options);
|
|
4196
4260
|
this.assertDurableProjectionOverrideSupported(options?.legacyFormatConverter);
|
|
4197
4261
|
const requestSnapshot = structuredCloneWithLegacyCreativeMetadata(params);
|
|
4198
4262
|
const projectionCatalogs = options?.projectionCatalogs ? structuredClone(options.projectionCatalogs) : void 0;
|
|
@@ -4322,6 +4386,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
4322
4386
|
* @param options - Task execution options
|
|
4323
4387
|
*/
|
|
4324
4388
|
async updateMediaBuy(params, inputHandler, options) {
|
|
4389
|
+
assertRawMethodForGovernedPayload("update_media_buy", options);
|
|
4325
4390
|
this.assertDurableProjectionOverrideSupported(options?.legacyFormatConverter);
|
|
4326
4391
|
const requestSnapshot = structuredCloneWithLegacyCreativeMetadata(params);
|
|
4327
4392
|
const projectionCatalogs = options?.projectionCatalogs ? structuredClone(options.projectionCatalogs) : void 0;
|
|
@@ -4390,6 +4455,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
4390
4455
|
* @param options - Task execution options
|
|
4391
4456
|
*/
|
|
4392
4457
|
async syncCreatives(params, inputHandler, options) {
|
|
4458
|
+
assertRawMethodForGovernedPayload("sync_creatives", options);
|
|
4393
4459
|
this.assertDurableProjectionOverrideSupported(
|
|
4394
4460
|
options?.creativeFormatProjection?.legacyFormatConverter ?? options?.legacyFormatConverter
|
|
4395
4461
|
);
|
|
@@ -4848,6 +4914,140 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
4848
4914
|
options
|
|
4849
4915
|
);
|
|
4850
4916
|
}
|
|
4917
|
+
/**
|
|
4918
|
+
* Resolve an account for the authenticated caller. Explicit-account sellers
|
|
4919
|
+
* are discovered with list_accounts; implicit-account sellers are registered
|
|
4920
|
+
* with sync_accounts and addressed by their natural key.
|
|
4921
|
+
*/
|
|
4922
|
+
async resolveAccount(hints = {}, options, onTaskResult) {
|
|
4923
|
+
return withTaskDeadline(snapshotTaskOptions(options), async (effectiveOptions) => {
|
|
4924
|
+
const capabilities = await this.getCapabilities(effectiveOptions);
|
|
4925
|
+
if (capabilities._synthetic || !capabilities.account) {
|
|
4926
|
+
throw new AccountRequiredError(
|
|
4927
|
+
"explicit",
|
|
4928
|
+
"resolveAccount",
|
|
4929
|
+
"The seller did not provide an account contract. Probe get_adcp_capabilities or pass an account reference explicitly."
|
|
4930
|
+
);
|
|
4931
|
+
}
|
|
4932
|
+
if (capabilities.account.requireOperatorAuth) {
|
|
4933
|
+
const accounts = [];
|
|
4934
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
4935
|
+
let cursor;
|
|
4936
|
+
for (let page = 0; page < 100; page++) {
|
|
4937
|
+
const result2 = await this.listAccounts(
|
|
4938
|
+
{
|
|
4939
|
+
status: "active",
|
|
4940
|
+
sandbox: hints.sandbox ?? false,
|
|
4941
|
+
...cursor !== void 0 && { pagination: { cursor } }
|
|
4942
|
+
},
|
|
4943
|
+
void 0,
|
|
4944
|
+
effectiveOptions
|
|
4945
|
+
);
|
|
4946
|
+
onTaskResult?.(result2);
|
|
4947
|
+
if (!result2.success || result2.status !== "completed" || !result2.data || !("accounts" in result2.data) || !Array.isArray(result2.data.accounts)) {
|
|
4948
|
+
throw new Error("list_accounts did not return a completed account list.");
|
|
4949
|
+
}
|
|
4950
|
+
accounts.push(...result2.data.accounts);
|
|
4951
|
+
if (!result2.data.pagination?.has_more) return selectListedAccount(accounts, hints);
|
|
4952
|
+
const nextCursor = result2.data.pagination.cursor;
|
|
4953
|
+
if (!nextCursor || seenCursors.has(nextCursor)) break;
|
|
4954
|
+
seenCursors.add(nextCursor);
|
|
4955
|
+
cursor = nextCursor;
|
|
4956
|
+
}
|
|
4957
|
+
throw new AccountRequiredError(
|
|
4958
|
+
"explicit",
|
|
4959
|
+
"resolveAccount",
|
|
4960
|
+
"Could not complete list_accounts pagination. Select an account_id explicitly after listing accounts."
|
|
4961
|
+
);
|
|
4962
|
+
}
|
|
4963
|
+
if (!hints.brand?.domain || !hints.operator) {
|
|
4964
|
+
throw new AccountRequiredError(
|
|
4965
|
+
"implicit",
|
|
4966
|
+
"resolveAccount",
|
|
4967
|
+
"Implicit-account sellers require both brand and operator to sync an account."
|
|
4968
|
+
);
|
|
4969
|
+
}
|
|
4970
|
+
const uses32NaturalKey = hints.operatorUnit !== void 0 || hints.currency !== void 0 || hints.timezone !== void 0;
|
|
4971
|
+
const sellerServes32 = capabilities.servedVersion ? !isPre32AdcpVersion(capabilities.servedVersion) : capabilities.supportedVersions?.some((version) => !isPre32AdcpVersion(version)) === true;
|
|
4972
|
+
if (uses32NaturalKey && (isPre32AdcpVersion(this.resolvedAdcpVersion) || !sellerServes32)) {
|
|
4973
|
+
throw new AccountRequiredError(
|
|
4974
|
+
"implicit",
|
|
4975
|
+
"resolveAccount",
|
|
4976
|
+
"Operator unit, account currency, and account timezone require AdCP 3.2 on both buyer and seller."
|
|
4977
|
+
);
|
|
4978
|
+
}
|
|
4979
|
+
const timezone = capabilities.account.timezone;
|
|
4980
|
+
if (timezone?.accountSelection === "buyer_selected") {
|
|
4981
|
+
if (!hints.timezone || !timezone.supportedTimezones?.includes(hints.timezone)) {
|
|
4982
|
+
throw new AccountRequiredError(
|
|
4983
|
+
"implicit",
|
|
4984
|
+
"resolveAccount",
|
|
4985
|
+
"Choose a supported account timezone before sync_accounts."
|
|
4986
|
+
);
|
|
4987
|
+
}
|
|
4988
|
+
} else if (hints.timezone && timezone) {
|
|
4989
|
+
throw new AccountRequiredError(
|
|
4990
|
+
"implicit",
|
|
4991
|
+
"resolveAccount",
|
|
4992
|
+
"This seller does not accept a buyer-selected account timezone."
|
|
4993
|
+
);
|
|
4994
|
+
}
|
|
4995
|
+
const currencyModes = capabilities.account.supportedAccountCurrencyModes;
|
|
4996
|
+
if (currencyModes?.length === 1 && currencyModes[0] === "fixed" && !hints.currency) {
|
|
4997
|
+
throw new AccountRequiredError("implicit", "resolveAccount", "This seller requires a fixed account currency.");
|
|
4998
|
+
}
|
|
4999
|
+
if (hints.currency && currencyModes?.length === 1 && currencyModes[0] === "per_media_buy") {
|
|
5000
|
+
throw new AccountRequiredError("implicit", "resolveAccount", "This seller selects currency per media buy.");
|
|
5001
|
+
}
|
|
5002
|
+
const account = {
|
|
5003
|
+
brand: hints.brand,
|
|
5004
|
+
operator: hints.operator,
|
|
5005
|
+
...hints.operatorUnit !== void 0 && { operator_unit: hints.operatorUnit },
|
|
5006
|
+
...hints.currency !== void 0 && { currency: hints.currency },
|
|
5007
|
+
...hints.timezone !== void 0 && { timezone: hints.timezone },
|
|
5008
|
+
...hints.sandbox !== void 0 && { sandbox: hints.sandbox }
|
|
5009
|
+
};
|
|
5010
|
+
const supportedBilling = capabilities.account.supportedBilling;
|
|
5011
|
+
const billing = hints.billing ?? capabilities.account.defaultBilling ?? (supportedBilling.length === 1 ? supportedBilling[0] : void 0);
|
|
5012
|
+
if (!billing || supportedBilling.length > 0 && !supportedBilling.includes(billing)) {
|
|
5013
|
+
throw new AccountRequiredError(
|
|
5014
|
+
"implicit",
|
|
5015
|
+
"resolveAccount",
|
|
5016
|
+
`Choose a supported billing party for sync_accounts (seller supports: ${supportedBilling.join(", ") || "(none)"}).`
|
|
5017
|
+
);
|
|
5018
|
+
}
|
|
5019
|
+
const result = await this.syncAccounts(
|
|
5020
|
+
{
|
|
5021
|
+
accounts: [
|
|
5022
|
+
{
|
|
5023
|
+
...account,
|
|
5024
|
+
billing
|
|
5025
|
+
}
|
|
5026
|
+
]
|
|
5027
|
+
},
|
|
5028
|
+
void 0,
|
|
5029
|
+
effectiveOptions
|
|
5030
|
+
);
|
|
5031
|
+
onTaskResult?.(result);
|
|
5032
|
+
if (!result.success || result.status !== "completed" || !result.data || !("accounts" in result.data) || !Array.isArray(result.data.accounts)) {
|
|
5033
|
+
throw new Error("sync_accounts did not return a completed account result.");
|
|
5034
|
+
}
|
|
5035
|
+
const synced = result.data.accounts.find(
|
|
5036
|
+
(row) => row.brand?.domain === hints.brand.domain && row.operator === hints.operator && (!hints.brand.brand_id || row.brand.brand_id === hints.brand.brand_id) && sameCountrySet(hints.brand.countries, row.brand.countries) && (!hints.operatorUnit || row.operator_unit?.id === hints.operatorUnit.id) && (!hints.currency || row.currency === hints.currency) && (!hints.timezone || row.timezone === hints.timezone) && (row.sandbox === void 0 || row.sandbox === true === (hints.sandbox === true))
|
|
5037
|
+
);
|
|
5038
|
+
if (synced?.status === "pending_approval") {
|
|
5039
|
+
throw new AccountPendingApprovalError(account, synced.account_id);
|
|
5040
|
+
}
|
|
5041
|
+
if (!synced || synced.action === "failed" || synced.status !== "active") {
|
|
5042
|
+
throw new AccountRequiredError(
|
|
5043
|
+
"implicit",
|
|
5044
|
+
"resolveAccount",
|
|
5045
|
+
"sync_accounts did not establish an active account for the requested brand and operator."
|
|
5046
|
+
);
|
|
5047
|
+
}
|
|
5048
|
+
return account;
|
|
5049
|
+
});
|
|
5050
|
+
}
|
|
4851
5051
|
/**
|
|
4852
5052
|
* Sync accounts
|
|
4853
5053
|
*/
|
|
@@ -5116,7 +5316,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
5116
5316
|
let detectedServerVersion;
|
|
5117
5317
|
let detectedServerVersionSynthetic;
|
|
5118
5318
|
try {
|
|
5119
|
-
const normalizedParams = normalizeRequestParams(taskName, params, {
|
|
5319
|
+
const normalizedParams = options?.preserveGovernedPayload ? params : normalizeRequestParams(taskName, params, {
|
|
5120
5320
|
skipIdempotencyAutoInject: options?.skipIdempotencyAutoInject,
|
|
5121
5321
|
skipAccountValidation: options?.skipAccountValidation
|
|
5122
5322
|
});
|
|
@@ -5128,7 +5328,7 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
5128
5328
|
await this.requireSupportedMajor(taskName, options);
|
|
5129
5329
|
}
|
|
5130
5330
|
const agent = await this.ensureEndpointDiscovered(options);
|
|
5131
|
-
if (!options?.skipIdempotencyAutoInject && !options?.skipAccountValidation && !options?.skipRequestValidation) {
|
|
5331
|
+
if (!options?.skipIdempotencyAutoInject && !options?.skipAccountValidation && !options?.skipRequestValidation && !options?.preserveGovernedPayload) {
|
|
5132
5332
|
this.executor.validateRequest(taskName, normalizedParams);
|
|
5133
5333
|
}
|
|
5134
5334
|
const capabilityDiscoveryContext = {};
|
|
@@ -5152,7 +5352,12 @@ Hint: this usually means agent_uri does not include the MCP endpoint path. The S
|
|
|
5152
5352
|
capabilityDiscoveryContext.capabilities
|
|
5153
5353
|
);
|
|
5154
5354
|
const inputSchemaStripLogs = [];
|
|
5155
|
-
|
|
5355
|
+
if (options?.preserveGovernedPayload && serverVersion !== "v3") {
|
|
5356
|
+
throw new ConfigurationError(
|
|
5357
|
+
"Governed storyboard requests require a v3 seller to preserve the approved payload."
|
|
5358
|
+
);
|
|
5359
|
+
}
|
|
5360
|
+
const { params: adaptedParams, driftLogs: adaptDriftLogs } = options?.preserveGovernedPayload ? { params: normalizedParams, driftLogs: [] } : this.adaptRequest(
|
|
5156
5361
|
taskName,
|
|
5157
5362
|
normalizedParams,
|
|
5158
5363
|
serverVersion,
|
|
@@ -789,7 +789,7 @@ class TaskExecutor {
|
|
|
789
789
|
const workingTimeout = this.config.workingTimeout || 12e4;
|
|
790
790
|
const wireAdcpVersion = options.wireAdcpVersion ?? this.config.wireAdcpVersion;
|
|
791
791
|
const versionEnvelope = options.versionEnvelope ?? this.config.versionEnvelope;
|
|
792
|
-
const idempotencyKey = options.skipIdempotencyAutoInject ? void 0 : resolveIdempotencyKey(taskName, params, serverVersion);
|
|
792
|
+
const idempotencyKey = options.skipIdempotencyAutoInject || options.preserveGovernedPayload ? void 0 : resolveIdempotencyKey(taskName, params, serverVersion);
|
|
793
793
|
if (idempotencyKey) (0, import_task_deadline.attachTaskDeadlineIdempotencyKey)(options, idempotencyKey);
|
|
794
794
|
if (serverVersion !== "v2" && idempotencyKey && params && typeof params === "object" && !params.idempotency_key) {
|
|
795
795
|
params = { ...params, idempotency_key: idempotencyKey };
|
|
@@ -762,7 +762,7 @@ class TaskExecutor {
|
|
|
762
762
|
const workingTimeout = this.config.workingTimeout || 12e4;
|
|
763
763
|
const wireAdcpVersion = options.wireAdcpVersion ?? this.config.wireAdcpVersion;
|
|
764
764
|
const versionEnvelope = options.versionEnvelope ?? this.config.versionEnvelope;
|
|
765
|
-
const idempotencyKey = options.skipIdempotencyAutoInject ? void 0 : resolveIdempotencyKey(taskName, params, serverVersion);
|
|
765
|
+
const idempotencyKey = options.skipIdempotencyAutoInject || options.preserveGovernedPayload ? void 0 : resolveIdempotencyKey(taskName, params, serverVersion);
|
|
766
766
|
if (idempotencyKey) attachTaskDeadlineIdempotencyKey(options, idempotencyKey);
|
|
767
767
|
if (serverVersion !== "v2" && idempotencyKey && params && typeof params === "object" && !params.idempotency_key) {
|
|
768
768
|
params = { ...params, idempotency_key: idempotencyKey };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AccountReference, ListAccountsResponse } from '../types/index.mjs';
|
|
2
|
+
export type ListedAccount = ListAccountsResponse['accounts'][number];
|
|
3
|
+
type NaturalAccountReference = Extract<AccountReference, {
|
|
4
|
+
brand: unknown;
|
|
5
|
+
}>;
|
|
6
|
+
export declare function sameCountrySet(expected?: readonly string[], actual?: readonly string[]): boolean;
|
|
7
|
+
/** Hints for resolving a seller account against the caller's own credentials. */
|
|
8
|
+
export interface ResolveAccountOptions {
|
|
9
|
+
brand?: NaturalAccountReference['brand'];
|
|
10
|
+
operator?: string;
|
|
11
|
+
operatorUnit?: NaturalAccountReference['operator_unit'];
|
|
12
|
+
currency?: NaturalAccountReference['currency'];
|
|
13
|
+
timezone?: NaturalAccountReference['timezone'];
|
|
14
|
+
sandbox?: boolean;
|
|
15
|
+
/** Require introspected access to this task when the seller publishes authorization. */
|
|
16
|
+
forTask?: string;
|
|
17
|
+
/** Billing party for buyer-declared accounts when the seller has no default. */
|
|
18
|
+
billing?: 'operator' | 'agent' | 'advertiser';
|
|
19
|
+
/** An account_id or a callback choosing one of the eligible list_accounts rows. */
|
|
20
|
+
select?: string | ((accounts: readonly ListedAccount[]) => string);
|
|
21
|
+
}
|
|
22
|
+
/** Select only from active rows returned for the authenticated caller. */
|
|
23
|
+
export declare function selectListedAccount(accounts: readonly ListedAccount[], hints: ResolveAccountOptions): AccountReference;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AccountReference, ListAccountsResponse } from '../types';
|
|
2
|
+
export type ListedAccount = ListAccountsResponse['accounts'][number];
|
|
3
|
+
type NaturalAccountReference = Extract<AccountReference, {
|
|
4
|
+
brand: unknown;
|
|
5
|
+
}>;
|
|
6
|
+
export declare function sameCountrySet(expected?: readonly string[], actual?: readonly string[]): boolean;
|
|
7
|
+
/** Hints for resolving a seller account against the caller's own credentials. */
|
|
8
|
+
export interface ResolveAccountOptions {
|
|
9
|
+
brand?: NaturalAccountReference['brand'];
|
|
10
|
+
operator?: string;
|
|
11
|
+
operatorUnit?: NaturalAccountReference['operator_unit'];
|
|
12
|
+
currency?: NaturalAccountReference['currency'];
|
|
13
|
+
timezone?: NaturalAccountReference['timezone'];
|
|
14
|
+
sandbox?: boolean;
|
|
15
|
+
/** Require introspected access to this task when the seller publishes authorization. */
|
|
16
|
+
forTask?: string;
|
|
17
|
+
/** Billing party for buyer-declared accounts when the seller has no default. */
|
|
18
|
+
billing?: 'operator' | 'agent' | 'advertiser';
|
|
19
|
+
/** An account_id or a callback choosing one of the eligible list_accounts rows. */
|
|
20
|
+
select?: string | ((accounts: readonly ListedAccount[]) => string);
|
|
21
|
+
}
|
|
22
|
+
/** Select only from active rows returned for the authenticated caller. */
|
|
23
|
+
export declare function selectListedAccount(accounts: readonly ListedAccount[], hints: ResolveAccountOptions): AccountReference;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
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 account_resolution_exports = {};
|
|
20
|
+
__export(account_resolution_exports, {
|
|
21
|
+
sameCountrySet: () => sameCountrySet,
|
|
22
|
+
selectListedAccount: () => selectListedAccount
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(account_resolution_exports);
|
|
25
|
+
var import_errors = require('../errors/index.js');
|
|
26
|
+
function sameCountrySet(expected, actual) {
|
|
27
|
+
if (expected === void 0) return true;
|
|
28
|
+
if (!actual || expected.length !== actual.length) return false;
|
|
29
|
+
const sortedExpected = [...expected].sort();
|
|
30
|
+
const sortedActual = [...actual].sort();
|
|
31
|
+
return sortedExpected.every((country, index) => country === sortedActual[index]);
|
|
32
|
+
}
|
|
33
|
+
function selectListedAccount(accounts, hints) {
|
|
34
|
+
const candidates = accounts.filter((account) => {
|
|
35
|
+
if (typeof account.account_id !== "string" || !account.account_id) return false;
|
|
36
|
+
if (account.status !== "active") return false;
|
|
37
|
+
if (hints.forTask && account.authorization && !account.authorization.allowed_tasks.includes(hints.forTask)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
if (hints.brand) {
|
|
41
|
+
if (account.brand?.domain !== hints.brand.domain) return false;
|
|
42
|
+
if (hints.brand.brand_id && account.brand.brand_id !== hints.brand.brand_id) return false;
|
|
43
|
+
if (!sameCountrySet(hints.brand.countries, account.brand.countries)) return false;
|
|
44
|
+
}
|
|
45
|
+
if (hints.operator && account.operator !== hints.operator) return false;
|
|
46
|
+
if (hints.operatorUnit && account.operator_unit?.id !== hints.operatorUnit.id) return false;
|
|
47
|
+
if (hints.currency && account.currency !== hints.currency) return false;
|
|
48
|
+
if (hints.timezone && account.timezone !== hints.timezone) return false;
|
|
49
|
+
if (account.sandbox === true !== (hints.sandbox === true)) return false;
|
|
50
|
+
return true;
|
|
51
|
+
});
|
|
52
|
+
if (candidates.length === 0) {
|
|
53
|
+
throw new import_errors.AccountRequiredError(
|
|
54
|
+
"explicit",
|
|
55
|
+
"resolveAccount",
|
|
56
|
+
"list_accounts returned no active account matching the supplied brand, operator, and sandbox hints."
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
const selectedId = typeof hints.select === "function" ? hints.select(candidates) : hints.select;
|
|
60
|
+
if (selectedId !== void 0) {
|
|
61
|
+
if (!candidates.some((account) => account.account_id === selectedId)) {
|
|
62
|
+
throw new import_errors.AccountRequiredError(
|
|
63
|
+
"explicit",
|
|
64
|
+
"resolveAccount",
|
|
65
|
+
"The selected account_id is not among the active accounts returned for this caller."
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return { account_id: selectedId };
|
|
69
|
+
}
|
|
70
|
+
if (candidates.length > 1) {
|
|
71
|
+
throw new import_errors.AccountAmbiguousError(candidates.map((account) => account.account_id));
|
|
72
|
+
}
|
|
73
|
+
return { account_id: candidates[0].account_id };
|
|
74
|
+
}
|
|
75
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
76
|
+
0 && (module.exports = {
|
|
77
|
+
sameCountrySet,
|
|
78
|
+
selectListedAccount
|
|
79
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { AccountAmbiguousError, AccountRequiredError } from "../errors/index.mjs";
|
|
2
|
+
function sameCountrySet(expected, actual) {
|
|
3
|
+
if (expected === void 0) return true;
|
|
4
|
+
if (!actual || expected.length !== actual.length) return false;
|
|
5
|
+
const sortedExpected = [...expected].sort();
|
|
6
|
+
const sortedActual = [...actual].sort();
|
|
7
|
+
return sortedExpected.every((country, index) => country === sortedActual[index]);
|
|
8
|
+
}
|
|
9
|
+
function selectListedAccount(accounts, hints) {
|
|
10
|
+
const candidates = accounts.filter((account) => {
|
|
11
|
+
if (typeof account.account_id !== "string" || !account.account_id) return false;
|
|
12
|
+
if (account.status !== "active") return false;
|
|
13
|
+
if (hints.forTask && account.authorization && !account.authorization.allowed_tasks.includes(hints.forTask)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
if (hints.brand) {
|
|
17
|
+
if (account.brand?.domain !== hints.brand.domain) return false;
|
|
18
|
+
if (hints.brand.brand_id && account.brand.brand_id !== hints.brand.brand_id) return false;
|
|
19
|
+
if (!sameCountrySet(hints.brand.countries, account.brand.countries)) return false;
|
|
20
|
+
}
|
|
21
|
+
if (hints.operator && account.operator !== hints.operator) return false;
|
|
22
|
+
if (hints.operatorUnit && account.operator_unit?.id !== hints.operatorUnit.id) return false;
|
|
23
|
+
if (hints.currency && account.currency !== hints.currency) return false;
|
|
24
|
+
if (hints.timezone && account.timezone !== hints.timezone) return false;
|
|
25
|
+
if (account.sandbox === true !== (hints.sandbox === true)) return false;
|
|
26
|
+
return true;
|
|
27
|
+
});
|
|
28
|
+
if (candidates.length === 0) {
|
|
29
|
+
throw new AccountRequiredError(
|
|
30
|
+
"explicit",
|
|
31
|
+
"resolveAccount",
|
|
32
|
+
"list_accounts returned no active account matching the supplied brand, operator, and sandbox hints."
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const selectedId = typeof hints.select === "function" ? hints.select(candidates) : hints.select;
|
|
36
|
+
if (selectedId !== void 0) {
|
|
37
|
+
if (!candidates.some((account) => account.account_id === selectedId)) {
|
|
38
|
+
throw new AccountRequiredError(
|
|
39
|
+
"explicit",
|
|
40
|
+
"resolveAccount",
|
|
41
|
+
"The selected account_id is not among the active accounts returned for this caller."
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return { account_id: selectedId };
|
|
45
|
+
}
|
|
46
|
+
if (candidates.length > 1) {
|
|
47
|
+
throw new AccountAmbiguousError(candidates.map((account) => account.account_id));
|
|
48
|
+
}
|
|
49
|
+
return { account_id: candidates[0].account_id };
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
sameCountrySet,
|
|
53
|
+
selectListedAccount
|
|
54
|
+
};
|
|
@@ -72,6 +72,33 @@ export declare class UnsupportedTaskError extends ADCPError {
|
|
|
72
72
|
readonly code = "UNSUPPORTED_TASK";
|
|
73
73
|
constructor(agentId: string, taskName: string, supportedTasks?: string[] | undefined);
|
|
74
74
|
}
|
|
75
|
+
/** Seller did not declare the requested get_products buying mode. */
|
|
76
|
+
export declare class UnsupportedBuyingModeError extends ADCPError {
|
|
77
|
+
readonly requestedMode: string | undefined;
|
|
78
|
+
readonly declaredModes: readonly string[];
|
|
79
|
+
readonly code = "UNSUPPORTED_BUYING_MODE";
|
|
80
|
+
constructor(requestedMode: string | undefined, declaredModes: readonly string[]);
|
|
81
|
+
}
|
|
82
|
+
/** A seller requires an account reference before an account-scoped request. */
|
|
83
|
+
export declare class AccountRequiredError extends ADCPError {
|
|
84
|
+
readonly accountModel: 'explicit' | 'implicit';
|
|
85
|
+
readonly taskName: string;
|
|
86
|
+
readonly code = "ACCOUNT_REQUIRED";
|
|
87
|
+
constructor(accountModel: 'explicit' | 'implicit', taskName: string, reason?: string);
|
|
88
|
+
}
|
|
89
|
+
/** Account provisioning succeeded but the seller has not approved its use. */
|
|
90
|
+
export declare class AccountPendingApprovalError extends ADCPError {
|
|
91
|
+
readonly account: import('../types/index.mjs').AccountReference;
|
|
92
|
+
readonly accountId?: string | undefined;
|
|
93
|
+
readonly code = "ACCOUNT_PENDING_APPROVAL";
|
|
94
|
+
constructor(account: import('../types/index.mjs').AccountReference, accountId?: string | undefined);
|
|
95
|
+
}
|
|
96
|
+
/** More than one active account matches the caller's selection hints. */
|
|
97
|
+
export declare class AccountAmbiguousError extends ADCPError {
|
|
98
|
+
readonly candidates: readonly string[];
|
|
99
|
+
readonly code = "ACCOUNT_AMBIGUOUS";
|
|
100
|
+
constructor(candidates: readonly string[]);
|
|
101
|
+
}
|
|
75
102
|
/**
|
|
76
103
|
* Error thrown when protocol communication fails
|
|
77
104
|
*/
|