@adcp/sdk 7.3.0 → 7.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.
Files changed (67) hide show
  1. package/dist/lib/core/AgentClient.d.ts.map +1 -1
  2. package/dist/lib/core/SingleAgentClient.d.ts +58 -14
  3. package/dist/lib/core/SingleAgentClient.d.ts.map +1 -1
  4. package/dist/lib/core/SingleAgentClient.js +68 -26
  5. package/dist/lib/core/SingleAgentClient.js.map +1 -1
  6. package/dist/lib/errors/index.d.ts +8 -3
  7. package/dist/lib/errors/index.d.ts.map +1 -1
  8. package/dist/lib/errors/index.js +1 -1
  9. package/dist/lib/errors/index.js.map +1 -1
  10. package/dist/lib/index.d.ts +2 -2
  11. package/dist/lib/index.d.ts.map +1 -1
  12. package/dist/lib/index.js.map +1 -1
  13. package/dist/lib/protocols/index.d.ts +16 -6
  14. package/dist/lib/protocols/index.d.ts.map +1 -1
  15. package/dist/lib/protocols/index.js.map +1 -1
  16. package/dist/lib/protocols/responseSizeLimit.js +7 -0
  17. package/dist/lib/protocols/responseSizeLimit.js.map +1 -1
  18. package/dist/lib/registry/index.d.ts +36 -3
  19. package/dist/lib/registry/index.d.ts.map +1 -1
  20. package/dist/lib/registry/index.js +41 -5
  21. package/dist/lib/registry/index.js.map +1 -1
  22. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  23. package/dist/lib/server/create-adcp-server.d.ts +95 -0
  24. package/dist/lib/server/create-adcp-server.d.ts.map +1 -1
  25. package/dist/lib/server/create-adcp-server.js +543 -35
  26. package/dist/lib/server/create-adcp-server.js.map +1 -1
  27. package/dist/lib/server/example-tld-guard.d.ts +9 -0
  28. package/dist/lib/server/example-tld-guard.d.ts.map +1 -0
  29. package/dist/lib/server/example-tld-guard.js +25 -0
  30. package/dist/lib/server/example-tld-guard.js.map +1 -0
  31. package/dist/lib/server/index.d.ts +5 -3
  32. package/dist/lib/server/index.d.ts.map +1 -1
  33. package/dist/lib/server/index.js +17 -4
  34. package/dist/lib/server/index.js.map +1 -1
  35. package/dist/lib/server/test-controller-bridge.d.ts +885 -1
  36. package/dist/lib/server/test-controller-bridge.d.ts.map +1 -1
  37. package/dist/lib/server/test-controller-bridge.js +1502 -2
  38. package/dist/lib/server/test-controller-bridge.js.map +1 -1
  39. package/dist/lib/testing/index.d.ts +2 -2
  40. package/dist/lib/testing/index.d.ts.map +1 -1
  41. package/dist/lib/testing/index.js +17 -3
  42. package/dist/lib/testing/index.js.map +1 -1
  43. package/dist/lib/types/core.generated.d.ts +1212 -20
  44. package/dist/lib/types/core.generated.d.ts.map +1 -1
  45. package/dist/lib/types/core.generated.js +1 -1
  46. package/dist/lib/types/inline-enums.generated.d.ts +6 -2
  47. package/dist/lib/types/inline-enums.generated.d.ts.map +1 -1
  48. package/dist/lib/types/inline-enums.generated.js +11 -5
  49. package/dist/lib/types/inline-enums.generated.js.map +1 -1
  50. package/dist/lib/types/schemas.generated.d.ts +26202 -26253
  51. package/dist/lib/types/schemas.generated.d.ts.map +1 -1
  52. package/dist/lib/types/schemas.generated.js +1353 -1300
  53. package/dist/lib/types/schemas.generated.js.map +1 -1
  54. package/dist/lib/types/tools.generated.d.ts +1082 -21
  55. package/dist/lib/types/tools.generated.d.ts.map +1 -1
  56. package/dist/lib/types/wellknown-schemas.generated.d.ts +2 -2
  57. package/dist/lib/validation/sync-creatives.d.ts +6 -6
  58. package/dist/lib/version.d.ts +3 -3
  59. package/dist/lib/version.js +3 -3
  60. package/examples/hello_creative_adapter_ad_server.ts +5 -0
  61. package/examples/hello_creative_adapter_template.ts +5 -0
  62. package/examples/hello_seller_adapter_guaranteed.ts +5 -0
  63. package/examples/hello_seller_adapter_non_guaranteed.ts +5 -0
  64. package/examples/hello_seller_adapter_social.ts +5 -0
  65. package/examples/hello_si_adapter_brand.ts +5 -0
  66. package/package.json +2 -2
  67. package/skills/call-adcp-agent/SKILL.md +1 -0
@@ -246,6 +246,42 @@ function stampReplayed(response) {
246
246
  }
247
247
  }
248
248
  }
249
+ /**
250
+ * Stamp a non-normative `_bridge` marker on a response that the
251
+ * `testController` bridge augmented with seeded fixtures.
252
+ *
253
+ * Mirrors {@link stampReplayed} — sets `_bridge` on `structuredContent`
254
+ * AND on the parsed JSON in `content[0].text`, so A2A/REST adapters that
255
+ * consume the text body see the same envelope MCP does.
256
+ *
257
+ * Only call this AFTER a successful merge — for singleton-replace tools
258
+ * (`get_account_financials`, `get_brand_identity`, `si_get_offering`,
259
+ * `get_property_list`, `get_collection_list`, `get_content_standards`)
260
+ * gate on `merged !== sc` so the marker only fires when the seeded fixture
261
+ * actually replaced the handler payload.
262
+ */
263
+ function stampBridge(response, callback, tool, mergedCount) {
264
+ if (!response.structuredContent || typeof response.structuredContent !== 'object')
265
+ return;
266
+ const marker = { callback, tool, merged_count: mergedCount };
267
+ const sc = response.structuredContent;
268
+ sc._bridge = marker;
269
+ if (Array.isArray(response.content)) {
270
+ const first = response.content[0];
271
+ if (first && first.type === 'text' && typeof first.text === 'string') {
272
+ try {
273
+ const parsed = JSON.parse(first.text);
274
+ if (parsed && typeof parsed === 'object') {
275
+ parsed._bridge = marker;
276
+ first.text = JSON.stringify(parsed);
277
+ }
278
+ }
279
+ catch {
280
+ // Text isn't JSON — leave it alone (implausible for AdCP responses).
281
+ }
282
+ }
283
+ }
284
+ }
249
285
  /**
250
286
  * Remove per-request echo fields (`context`) from a formatted MCP response
251
287
  * before caching. The buyer's `correlation_id` is scoped to the individual
@@ -1204,6 +1240,29 @@ function buildSupportedVersionsList(capConfig, serverPin) {
1204
1240
  */
1205
1241
  function createAdcpServer(config) {
1206
1242
  const { name, version, adcpVersion: configuredAdcpVersion, resolveAccount, resolveAccountFromAuth, resolveSessionKey, agentRegistry, exposeErrorDetails = process.env.NODE_ENV !== 'production', stateStore = DEFAULT_STATE_STORE, logger = noopLogger, capabilities: capConfig, idempotency: idempotencyConfig, resolveIdempotencyPrincipal, instructions: instructionsOption, onInstructionsError = 'skip', taskStore, taskMessageQueue, webhooks, signedRequests, validation: validationConfig, credentialPolicy, testController: testControllerBridge, } = config;
1243
+ // One-shot construction-time warn when `testController` is wired without
1244
+ // any account resolver. The dispatch-time sandbox gate admits requests
1245
+ // where `ctx.account === undefined`, so without a resolver the only
1246
+ // remaining check is buyer-supplied `account.sandbox` / `context.sandbox`
1247
+ // on the request — caller-controlled, not a trust boundary. Storyboard
1248
+ // runners legitimately have no account scoping and should ignore this
1249
+ // warning; production bindings need to wire `resolveAccount` (or
1250
+ // `resolveAccountFromAuth` for OAuth-passthrough setups) so the gate's
1251
+ // account-side check has teeth.
1252
+ //
1253
+ // Dual-emit: `process.emitWarning` writes to stderr by default so the
1254
+ // signal is visible even when `logger` is the default `noopLogger`
1255
+ // (the day-one case where the misconfig is most likely). `logger.warn`
1256
+ // also fires so adopters with configured logging pipelines see it in
1257
+ // their normal channel. The `code` lets adopters silence it via
1258
+ // `--no-warnings=ADCP_BRIDGE_NO_RESOLVER` if they're knowingly running
1259
+ // a storyboard-runner config. See `AdcpServerConfig.testController`
1260
+ // JSDoc § "Security — trust boundary" and #1784.
1261
+ if (testControllerBridge != null && resolveAccount === undefined && resolveAccountFromAuth === undefined) {
1262
+ const message = '[adcp/createAdcpServer] testController is wired but no account resolver — configure resolveAccount (or resolveAccountFromAuth) for production. Storyboard runners without account scoping can ignore. Details: https://github.com/adcontextprotocol/adcp-client/blob/main/docs/guides/VALIDATE-YOUR-AGENT.md';
1263
+ process.emitWarning(message, { type: 'AdcpServerConfigWarning', code: 'ADCP_BRIDGE_NO_RESOLVER' });
1264
+ logger.warn(message);
1265
+ }
1207
1266
  // Pre-resolve credential-policy patterns once. The patterns config is
1208
1267
  // a stable property of `CredentialPolicyConfig`; pulling it out here
1209
1268
  // keeps the per-call hot path (`scanArgsForCredentials`) free of the
@@ -2202,50 +2261,499 @@ function createAdcpServer(config) {
2202
2261
  else {
2203
2262
  formatted = wrap(result);
2204
2263
  }
2205
- // --- Test-controller bridge: augment get_products with seeded fixtures. ---
2206
- // Only runs when the seller opted in via `testController.getSeededProducts`
2207
- // AND the request carries a sandbox marker (account.sandbox === true or
2208
- // context.sandbox === true). When `resolveAccount` returned a concrete
2209
- // account, we additionally require `ctx.account.sandbox === true` so a
2210
- // request that happens to include `account.sandbox: true` can't leak
2211
- // fixtures into a non-sandbox resolved account (belt-and-suspenders).
2212
- // Seeded products append to whatever the handler returned; `product_id`
2213
- // collisions resolve with the seeded entry winning, so storyboards that
2214
- // override default inventory see their fixture.
2215
- if (toolName === 'get_products' &&
2216
- testControllerBridge?.getSeededProducts &&
2264
+ // --- Test-controller bridge: augment read-side tools with seeded fixtures. ---
2265
+ // Each per-tool callback (`getSeededProducts`, `getSeededCreatives`, ...)
2266
+ // is opt-in by presence on the bridge interface. All callbacks share the
2267
+ // same triply-gated contract:
2268
+ // 1. The bridge is registered AND has the matching callback;
2269
+ // 2. The handler returned a success envelope (not an `adcp_error`);
2270
+ // 3. The request carries a sandbox marker (account.sandbox === true or
2271
+ // context.sandbox === true) AND, if `resolveAccount` produced a
2272
+ // record, that record is flagged `sandbox: true` too.
2273
+ // For array-collection tools, seeded entries append to the handler's
2274
+ // response with seeded winning on id collision (same as `getSeededProducts`).
2275
+ // `get_account_financials` is the exception — singleton response, so the
2276
+ // seeded entry REPLACES the handler payload when its `account.account_id`
2277
+ // matches the request's `account.account_id`.
2278
+ // Diagnostic for adopters chasing "why aren't my fixtures showing":
2279
+ // when the request carries a sandbox marker but the resolved account
2280
+ // is explicitly non-sandbox, the gate rejects silently. Emit a
2281
+ // `debug` line so the rejection is observable in dev logs without
2282
+ // adding noise to production traffic (where the gate's first check
2283
+ // — `isSandboxRequestForSeeding` — fails first and never reaches
2284
+ // this branch).
2285
+ if (testControllerBridge &&
2286
+ !isErrorResponse(formatted) &&
2287
+ (0, test_controller_bridge_1.isSandboxRequest)(params) &&
2288
+ ctx.account !== undefined &&
2289
+ !(typeof ctx.account === 'object' &&
2290
+ ctx.account !== null &&
2291
+ ctx.account.sandbox === true)) {
2292
+ // Include the resolved account_id so the log line is self-
2293
+ // diagnostic — an adopter chasing "why aren't my fixtures
2294
+ // showing" can match the rejected account against their
2295
+ // resolveAccount source without correlating across log lines.
2296
+ // account_ids appear in normal request logs already; no new
2297
+ // PII surface.
2298
+ const resolvedAccountId = typeof ctx.account === 'object' &&
2299
+ ctx.account !== null &&
2300
+ typeof ctx.account.account_id === 'string'
2301
+ ? ctx.account.account_id
2302
+ : undefined;
2303
+ logger.debug('test-controller bridge: request is sandbox-flagged but resolved account is not sandbox; skipping merge', { tool: toolName, resolved_account_id: resolvedAccountId });
2304
+ }
2305
+ if (testControllerBridge &&
2217
2306
  !isErrorResponse(formatted) &&
2218
2307
  (0, test_controller_bridge_1.isSandboxRequest)(params) &&
2219
- // If resolveAccount produced a record, require it to be flagged
2220
- // sandbox too. If no account was resolved, the request-signal
2221
- // check above is the only line of defense — keep that contract.
2222
2308
  (ctx.account === undefined ||
2223
2309
  (typeof ctx.account === 'object' &&
2224
2310
  ctx.account !== null &&
2225
2311
  ctx.account.sandbox === true))) {
2226
- try {
2227
- const bridgeCtx = { input: params };
2228
- if (ctx.account !== undefined)
2229
- bridgeCtx.account = ctx.account;
2230
- const rawSeeded = await testControllerBridge.getSeededProducts(bridgeCtx);
2231
- const seeded = (0, test_controller_bridge_1.filterValidSeededProducts)(rawSeeded, logger);
2232
- if (seeded.length > 0) {
2233
- const sc = formatted.structuredContent;
2234
- if (sc && typeof sc === 'object') {
2235
- const merged = (0, test_controller_bridge_1.mergeSeededProductsIntoResponse)(sc, seeded);
2236
- formatted = wrap(merged);
2312
+ const bridgeCtx = { input: params };
2313
+ if (ctx.account !== undefined)
2314
+ bridgeCtx.account = ctx.account;
2315
+ // get_products
2316
+ if (toolName === 'get_products' && testControllerBridge.getSeededProducts) {
2317
+ try {
2318
+ const rawSeeded = await testControllerBridge.getSeededProducts(bridgeCtx);
2319
+ const seeded = (0, test_controller_bridge_1.filterValidSeededProducts)(rawSeeded, logger);
2320
+ if (seeded.length > 0) {
2321
+ const sc = formatted.structuredContent;
2322
+ if (sc && typeof sc === 'object') {
2323
+ const merged = (0, test_controller_bridge_1.mergeSeededProductsIntoResponse)(sc, seeded);
2324
+ formatted = wrap(merged);
2325
+ stampBridge(formatted, 'getSeededProducts', toolName, seeded.length);
2326
+ }
2237
2327
  }
2238
2328
  }
2329
+ catch (err) {
2330
+ // Bridge failures are sandbox-only by construction, so logging +
2331
+ // returning the handler's response is the right default — a broken
2332
+ // test fixture shouldn't tank the request under test.
2333
+ const reason = err instanceof Error ? err.message : String(err);
2334
+ logger.warn('testController.getSeededProducts failed; returning handler response unchanged', {
2335
+ tool: toolName,
2336
+ error: reason,
2337
+ });
2338
+ }
2239
2339
  }
2240
- catch (err) {
2241
- // Bridge failures are sandbox-only by construction, so logging +
2242
- // returning the handler's response is the right default — a broken
2243
- // test fixture shouldn't tank the request under test.
2244
- const reason = err instanceof Error ? err.message : String(err);
2245
- logger.warn('testController.getSeededProducts failed; returning handler response unchanged', {
2246
- tool: toolName,
2247
- error: reason,
2248
- });
2340
+ // list_creatives
2341
+ else if (toolName === 'list_creatives' && testControllerBridge.getSeededCreatives) {
2342
+ try {
2343
+ const rawSeeded = await testControllerBridge.getSeededCreatives(bridgeCtx);
2344
+ const seeded = (0, test_controller_bridge_1.filterValidSeededCreatives)(rawSeeded, logger);
2345
+ if (seeded.length > 0) {
2346
+ const sc = formatted.structuredContent;
2347
+ if (sc && typeof sc === 'object') {
2348
+ const merged = (0, test_controller_bridge_1.mergeSeededCreativesIntoResponse)(sc, seeded);
2349
+ formatted = wrap(merged);
2350
+ stampBridge(formatted, 'getSeededCreatives', toolName, seeded.length);
2351
+ }
2352
+ }
2353
+ }
2354
+ catch (err) {
2355
+ const reason = err instanceof Error ? err.message : String(err);
2356
+ logger.warn('testController.getSeededCreatives failed; returning handler response unchanged', {
2357
+ tool: toolName,
2358
+ error: reason,
2359
+ });
2360
+ }
2361
+ }
2362
+ // get_media_buys
2363
+ else if (toolName === 'get_media_buys' && testControllerBridge.getSeededMediaBuys) {
2364
+ try {
2365
+ const rawSeeded = await testControllerBridge.getSeededMediaBuys(bridgeCtx);
2366
+ const seeded = (0, test_controller_bridge_1.filterValidSeededMediaBuys)(rawSeeded, logger);
2367
+ if (seeded.length > 0) {
2368
+ const sc = formatted.structuredContent;
2369
+ if (sc && typeof sc === 'object') {
2370
+ const merged = (0, test_controller_bridge_1.mergeSeededMediaBuysIntoResponse)(sc, seeded);
2371
+ formatted = wrap(merged);
2372
+ stampBridge(formatted, 'getSeededMediaBuys', toolName, seeded.length);
2373
+ }
2374
+ }
2375
+ }
2376
+ catch (err) {
2377
+ const reason = err instanceof Error ? err.message : String(err);
2378
+ logger.warn('testController.getSeededMediaBuys failed; returning handler response unchanged', {
2379
+ tool: toolName,
2380
+ error: reason,
2381
+ });
2382
+ }
2383
+ }
2384
+ // get_media_buy_delivery
2385
+ else if (toolName === 'get_media_buy_delivery' && testControllerBridge.getSeededMediaBuyDelivery) {
2386
+ try {
2387
+ const rawSeeded = await testControllerBridge.getSeededMediaBuyDelivery(bridgeCtx);
2388
+ const seeded = (0, test_controller_bridge_1.filterValidSeededMediaBuyDeliveries)(rawSeeded, logger);
2389
+ if (seeded.length > 0) {
2390
+ const sc = formatted.structuredContent;
2391
+ if (sc && typeof sc === 'object') {
2392
+ const merged = (0, test_controller_bridge_1.mergeSeededMediaBuyDeliveryIntoResponse)(sc, seeded);
2393
+ formatted = wrap(merged);
2394
+ stampBridge(formatted, 'getSeededMediaBuyDelivery', toolName, seeded.length);
2395
+ }
2396
+ }
2397
+ }
2398
+ catch (err) {
2399
+ const reason = err instanceof Error ? err.message : String(err);
2400
+ logger.warn('testController.getSeededMediaBuyDelivery failed; returning handler response unchanged', {
2401
+ tool: toolName,
2402
+ error: reason,
2403
+ });
2404
+ }
2405
+ }
2406
+ // list_accounts
2407
+ else if (toolName === 'list_accounts' && testControllerBridge.getSeededAccounts) {
2408
+ try {
2409
+ const rawSeeded = await testControllerBridge.getSeededAccounts(bridgeCtx);
2410
+ const seeded = (0, test_controller_bridge_1.filterValidSeededAccounts)(rawSeeded, logger);
2411
+ if (seeded.length > 0) {
2412
+ const sc = formatted.structuredContent;
2413
+ if (sc && typeof sc === 'object') {
2414
+ const merged = (0, test_controller_bridge_1.mergeSeededAccountsIntoResponse)(sc, seeded);
2415
+ formatted = wrap(merged);
2416
+ stampBridge(formatted, 'getSeededAccounts', toolName, seeded.length);
2417
+ }
2418
+ }
2419
+ }
2420
+ catch (err) {
2421
+ const reason = err instanceof Error ? err.message : String(err);
2422
+ logger.warn('testController.getSeededAccounts failed; returning handler response unchanged', {
2423
+ tool: toolName,
2424
+ error: reason,
2425
+ });
2426
+ }
2427
+ }
2428
+ // get_account_financials (singleton — replace, not append)
2429
+ else if (toolName === 'get_account_financials' && testControllerBridge.getSeededAccountFinancials) {
2430
+ try {
2431
+ const rawSeeded = await testControllerBridge.getSeededAccountFinancials(bridgeCtx);
2432
+ const seeded = (0, test_controller_bridge_1.filterValidSeededAccountFinancials)(rawSeeded, logger);
2433
+ if (seeded.length > 0) {
2434
+ const sc = formatted.structuredContent;
2435
+ if (sc && typeof sc === 'object') {
2436
+ // Brand+operator `AccountReference` variants don't carry
2437
+ // `account_id` on the wire — the framework's resolved
2438
+ // account does, so prefer it for the match key.
2439
+ const resolvedAccountId = ctx.account && typeof ctx.account === 'object' && !Array.isArray(ctx.account)
2440
+ ? ctx.account.account_id
2441
+ : undefined;
2442
+ const merged = (0, test_controller_bridge_1.replaceAccountFinancialsIfSeeded)(params, sc, seeded, typeof resolvedAccountId === 'string' && resolvedAccountId.length > 0
2443
+ ? resolvedAccountId
2444
+ : undefined);
2445
+ if (merged !== sc) {
2446
+ formatted = wrap(merged);
2447
+ stampBridge(formatted, 'getSeededAccountFinancials', toolName, seeded.length);
2448
+ }
2449
+ }
2450
+ }
2451
+ }
2452
+ catch (err) {
2453
+ const reason = err instanceof Error ? err.message : String(err);
2454
+ logger.warn('testController.getSeededAccountFinancials failed; returning handler response unchanged', {
2455
+ tool: toolName,
2456
+ error: reason,
2457
+ });
2458
+ }
2459
+ }
2460
+ // list_creative_formats
2461
+ else if (toolName === 'list_creative_formats' && testControllerBridge.getSeededCreativeFormats) {
2462
+ try {
2463
+ const rawSeeded = await testControllerBridge.getSeededCreativeFormats(bridgeCtx);
2464
+ const seeded = (0, test_controller_bridge_1.filterValidSeededCreativeFormats)(rawSeeded, logger);
2465
+ if (seeded.length > 0) {
2466
+ const sc = formatted.structuredContent;
2467
+ if (sc && typeof sc === 'object') {
2468
+ const merged = (0, test_controller_bridge_1.mergeSeededCreativeFormatsIntoResponse)(sc, seeded);
2469
+ formatted = wrap(merged);
2470
+ stampBridge(formatted, 'getSeededCreativeFormats', toolName, seeded.length);
2471
+ }
2472
+ }
2473
+ }
2474
+ catch (err) {
2475
+ const reason = err instanceof Error ? err.message : String(err);
2476
+ logger.warn('testController.getSeededCreativeFormats failed; returning handler response unchanged', {
2477
+ tool: toolName,
2478
+ error: reason,
2479
+ });
2480
+ }
2481
+ }
2482
+ // list_property_lists / get_property_list — one seeded fixture array
2483
+ // feeds both. List path: append-merge with seeded-wins on `list_id`
2484
+ // collision. Get path: pick by request.list_id and replace the
2485
+ // response's `list` field, preserving handler's resolved data
2486
+ // (`identifiers` / `pagination` / `resolved_at` / `cache_valid_until`
2487
+ // / `coverage_gaps` / `context` / `ext`).
2488
+ else if ((toolName === 'list_property_lists' || toolName === 'get_property_list') &&
2489
+ testControllerBridge.getSeededPropertyLists) {
2490
+ try {
2491
+ const rawSeeded = await testControllerBridge.getSeededPropertyLists(bridgeCtx);
2492
+ const seeded = (0, test_controller_bridge_1.filterValidSeededPropertyLists)(rawSeeded, logger);
2493
+ if (seeded.length > 0) {
2494
+ if (toolName === 'list_property_lists') {
2495
+ const sc = formatted.structuredContent;
2496
+ if (sc && typeof sc === 'object') {
2497
+ const merged = (0, test_controller_bridge_1.mergeSeededPropertyListsIntoResponse)(sc, seeded);
2498
+ formatted = wrap(merged);
2499
+ stampBridge(formatted, 'getSeededPropertyLists', toolName, seeded.length);
2500
+ }
2501
+ }
2502
+ else {
2503
+ const sc = formatted.structuredContent;
2504
+ if (sc && typeof sc === 'object') {
2505
+ const merged = (0, test_controller_bridge_1.replacePropertyListIfSeeded)(params, sc, seeded);
2506
+ if (merged !== sc) {
2507
+ formatted = wrap(merged);
2508
+ stampBridge(formatted, 'getSeededPropertyLists', toolName, seeded.length);
2509
+ }
2510
+ }
2511
+ }
2512
+ }
2513
+ }
2514
+ catch (err) {
2515
+ const reason = err instanceof Error ? err.message : String(err);
2516
+ logger.warn('testController.getSeededPropertyLists failed; returning handler response unchanged', {
2517
+ tool: toolName,
2518
+ error: reason,
2519
+ });
2520
+ }
2521
+ }
2522
+ // list_collection_lists / get_collection_list — symmetric with
2523
+ // property lists.
2524
+ else if ((toolName === 'list_collection_lists' || toolName === 'get_collection_list') &&
2525
+ testControllerBridge.getSeededCollectionLists) {
2526
+ try {
2527
+ const rawSeeded = await testControllerBridge.getSeededCollectionLists(bridgeCtx);
2528
+ const seeded = (0, test_controller_bridge_1.filterValidSeededCollectionLists)(rawSeeded, logger);
2529
+ if (seeded.length > 0) {
2530
+ if (toolName === 'list_collection_lists') {
2531
+ const sc = formatted.structuredContent;
2532
+ if (sc && typeof sc === 'object') {
2533
+ const merged = (0, test_controller_bridge_1.mergeSeededCollectionListsIntoResponse)(sc, seeded);
2534
+ formatted = wrap(merged);
2535
+ stampBridge(formatted, 'getSeededCollectionLists', toolName, seeded.length);
2536
+ }
2537
+ }
2538
+ else {
2539
+ const sc = formatted.structuredContent;
2540
+ if (sc && typeof sc === 'object') {
2541
+ const merged = (0, test_controller_bridge_1.replaceCollectionListIfSeeded)(params, sc, seeded);
2542
+ if (merged !== sc) {
2543
+ formatted = wrap(merged);
2544
+ stampBridge(formatted, 'getSeededCollectionLists', toolName, seeded.length);
2545
+ }
2546
+ }
2547
+ }
2548
+ }
2549
+ }
2550
+ catch (err) {
2551
+ const reason = err instanceof Error ? err.message : String(err);
2552
+ logger.warn('testController.getSeededCollectionLists failed; returning handler response unchanged', {
2553
+ tool: toolName,
2554
+ error: reason,
2555
+ });
2556
+ }
2557
+ }
2558
+ // get_signals — append-merge by signal_id. Works uniformly across
2559
+ // signal-marketplace and signal-owned specialisms (one bridge, both
2560
+ // dispatched on the same tool; the per-signal `signal_type` field
2561
+ // is the marketplace-vs-owned discriminator).
2562
+ else if (toolName === 'get_signals' && testControllerBridge.getSeededSignals) {
2563
+ try {
2564
+ const rawSeeded = await testControllerBridge.getSeededSignals(bridgeCtx);
2565
+ const seeded = (0, test_controller_bridge_1.filterValidSeededSignals)(rawSeeded, logger);
2566
+ if (seeded.length > 0) {
2567
+ const sc = formatted.structuredContent;
2568
+ if (sc && typeof sc === 'object') {
2569
+ const merged = (0, test_controller_bridge_1.mergeSeededSignalsIntoResponse)(sc, seeded);
2570
+ formatted = wrap(merged);
2571
+ stampBridge(formatted, 'getSeededSignals', toolName, seeded.length);
2572
+ }
2573
+ }
2574
+ }
2575
+ catch (err) {
2576
+ const reason = err instanceof Error ? err.message : String(err);
2577
+ logger.warn('testController.getSeededSignals failed; returning handler response unchanged', {
2578
+ tool: toolName,
2579
+ error: reason,
2580
+ });
2581
+ }
2582
+ }
2583
+ // get_creative_delivery — append-merge by creative_id;
2584
+ // `pagination.total` (when set by the handler) updates by the
2585
+ // count of new non-colliding seeded entries. No aggregated-totals
2586
+ // recomputation — this response has no top-level totals envelope.
2587
+ else if (toolName === 'get_creative_delivery' && testControllerBridge.getSeededCreativeDelivery) {
2588
+ try {
2589
+ const rawSeeded = await testControllerBridge.getSeededCreativeDelivery(bridgeCtx);
2590
+ const seeded = (0, test_controller_bridge_1.filterValidSeededCreativeDelivery)(rawSeeded, logger);
2591
+ if (seeded.length > 0) {
2592
+ const sc = formatted.structuredContent;
2593
+ if (sc && typeof sc === 'object') {
2594
+ const merged = (0, test_controller_bridge_1.mergeSeededCreativeDeliveryIntoResponse)(sc, seeded);
2595
+ formatted = wrap(merged);
2596
+ stampBridge(formatted, 'getSeededCreativeDelivery', toolName, seeded.length);
2597
+ }
2598
+ }
2599
+ }
2600
+ catch (err) {
2601
+ const reason = err instanceof Error ? err.message : String(err);
2602
+ logger.warn('testController.getSeededCreativeDelivery failed; returning handler response unchanged', {
2603
+ tool: toolName,
2604
+ error: reason,
2605
+ });
2606
+ }
2607
+ }
2608
+ // get_creative_features — `oneOf` envelope. Success arm: merge
2609
+ // seeded `CreativeFeatureResult[]` into `results` (dedup by
2610
+ // `feature_id`, seeded wins). Error arm: no-op (the helper
2611
+ // discriminates by presence of `results: []`).
2612
+ else if (toolName === 'get_creative_features' && testControllerBridge.getSeededCreativeFeatures) {
2613
+ try {
2614
+ const rawSeeded = await testControllerBridge.getSeededCreativeFeatures(bridgeCtx);
2615
+ const seeded = (0, test_controller_bridge_1.filterValidSeededCreativeFeatures)(rawSeeded, logger);
2616
+ if (seeded.length > 0) {
2617
+ const sc = formatted.structuredContent;
2618
+ if (sc && typeof sc === 'object') {
2619
+ const merged = (0, test_controller_bridge_1.mergeSeededCreativeFeaturesIntoResponse)(sc, seeded);
2620
+ if (merged !== sc) {
2621
+ formatted = wrap(merged);
2622
+ stampBridge(formatted, 'getSeededCreativeFeatures', toolName, seeded.length);
2623
+ }
2624
+ }
2625
+ }
2626
+ }
2627
+ catch (err) {
2628
+ const reason = err instanceof Error ? err.message : String(err);
2629
+ logger.warn('testController.getSeededCreativeFeatures failed; returning handler response unchanged', {
2630
+ tool: toolName,
2631
+ error: reason,
2632
+ });
2633
+ }
2634
+ }
2635
+ // get_brand_identity — singleton replace keyed by `brand_id`.
2636
+ // Seeded fixture is authoritative on the GetBrandIdentitySuccess
2637
+ // body; handler's `context` / `ext` round-trip. The response is
2638
+ // a union (success | error) — the dispatcher already gated on
2639
+ // `!isErrorResponse`, so we narrow defensively when reading.
2640
+ else if (toolName === 'get_brand_identity' && testControllerBridge.getSeededBrandIdentity) {
2641
+ try {
2642
+ const rawSeeded = await testControllerBridge.getSeededBrandIdentity(bridgeCtx);
2643
+ const seeded = (0, test_controller_bridge_1.filterValidSeededBrandIdentity)(rawSeeded, logger);
2644
+ if (seeded.length > 0) {
2645
+ const sc = formatted.structuredContent;
2646
+ if (sc && typeof sc === 'object') {
2647
+ const merged = (0, test_controller_bridge_1.replaceBrandIdentityIfSeeded)(params, sc, seeded);
2648
+ if (merged !== sc) {
2649
+ formatted = wrap(merged);
2650
+ stampBridge(formatted, 'getSeededBrandIdentity', toolName, seeded.length);
2651
+ }
2652
+ }
2653
+ }
2654
+ }
2655
+ catch (err) {
2656
+ const reason = err instanceof Error ? err.message : String(err);
2657
+ logger.warn('testController.getSeededBrandIdentity failed; returning handler response unchanged', {
2658
+ tool: toolName,
2659
+ error: reason,
2660
+ });
2661
+ }
2662
+ }
2663
+ // get_rights — append-merge keyed by `rights_id`. Discovery /
2664
+ // search tool (NL `query`); the response carries `rights[]`.
2665
+ // Drops to a no-op on the error arm of the response union.
2666
+ else if (toolName === 'get_rights' && testControllerBridge.getSeededRights) {
2667
+ try {
2668
+ const rawSeeded = await testControllerBridge.getSeededRights(bridgeCtx);
2669
+ const seeded = (0, test_controller_bridge_1.filterValidSeededRights)(rawSeeded, logger);
2670
+ if (seeded.length > 0) {
2671
+ const sc = formatted.structuredContent;
2672
+ if (sc && typeof sc === 'object') {
2673
+ const merged = (0, test_controller_bridge_1.mergeSeededRightsIntoResponse)(sc, seeded);
2674
+ if (merged !== sc) {
2675
+ formatted = wrap(merged);
2676
+ stampBridge(formatted, 'getSeededRights', toolName, seeded.length);
2677
+ }
2678
+ }
2679
+ }
2680
+ }
2681
+ catch (err) {
2682
+ const reason = err instanceof Error ? err.message : String(err);
2683
+ logger.warn('testController.getSeededRights failed; returning handler response unchanged', {
2684
+ tool: toolName,
2685
+ error: reason,
2686
+ });
2687
+ }
2688
+ }
2689
+ // si_get_offering — singleton replace keyed by `offering_id`.
2690
+ // Stateless catalog lookup; the response's `offering_token` is
2691
+ // produced for a future session but the lookup itself does not
2692
+ // consume one. Handler's `context` / `ext` round-trip.
2693
+ else if (toolName === 'si_get_offering' && testControllerBridge.getSeededSiOffering) {
2694
+ try {
2695
+ const rawSeeded = await testControllerBridge.getSeededSiOffering(bridgeCtx);
2696
+ const seeded = (0, test_controller_bridge_1.filterValidSeededSiOffering)(rawSeeded, logger);
2697
+ if (seeded.length > 0) {
2698
+ const sc = formatted.structuredContent;
2699
+ if (sc && typeof sc === 'object') {
2700
+ const merged = (0, test_controller_bridge_1.replaceSiOfferingIfSeeded)(params, sc, seeded);
2701
+ if (merged !== sc) {
2702
+ formatted = wrap(merged);
2703
+ stampBridge(formatted, 'getSeededSiOffering', toolName, seeded.length);
2704
+ }
2705
+ }
2706
+ }
2707
+ }
2708
+ catch (err) {
2709
+ const reason = err instanceof Error ? err.message : String(err);
2710
+ logger.warn('testController.getSeededSiOffering failed; returning handler response unchanged', {
2711
+ tool: toolName,
2712
+ error: reason,
2713
+ });
2714
+ }
2715
+ }
2716
+ // list_content_standards / get_content_standards — list returns
2717
+ // `{ standards: ContentStandards[] }` (success arm of a union; the
2718
+ // error arm is gated out upstream). Get returns `ContentStandards`
2719
+ // directly (success arm); replace the ContentStandards body and
2720
+ // round-trip handler's `context` / `ext` (both are framework-
2721
+ // managed envelope fields per the spec).
2722
+ else if ((toolName === 'list_content_standards' || toolName === 'get_content_standards') &&
2723
+ testControllerBridge.getSeededContentStandards) {
2724
+ try {
2725
+ const rawSeeded = await testControllerBridge.getSeededContentStandards(bridgeCtx);
2726
+ const seeded = (0, test_controller_bridge_1.filterValidSeededContentStandards)(rawSeeded, logger);
2727
+ if (seeded.length > 0) {
2728
+ if (toolName === 'list_content_standards') {
2729
+ const sc = formatted.structuredContent;
2730
+ if (sc && typeof sc === 'object') {
2731
+ const merged = (0, test_controller_bridge_1.mergeSeededContentStandardsIntoResponse)(sc, seeded);
2732
+ if (merged !== sc) {
2733
+ formatted = wrap(merged);
2734
+ stampBridge(formatted, 'getSeededContentStandards', toolName, seeded.length);
2735
+ }
2736
+ }
2737
+ }
2738
+ else {
2739
+ const sc = formatted.structuredContent;
2740
+ if (sc && typeof sc === 'object') {
2741
+ const merged = (0, test_controller_bridge_1.replaceContentStandardsIfSeeded)(params, sc, seeded);
2742
+ if (merged !== sc) {
2743
+ formatted = wrap(merged);
2744
+ stampBridge(formatted, 'getSeededContentStandards', toolName, seeded.length);
2745
+ }
2746
+ }
2747
+ }
2748
+ }
2749
+ }
2750
+ catch (err) {
2751
+ const reason = err instanceof Error ? err.message : String(err);
2752
+ logger.warn('testController.getSeededContentStandards failed; returning handler response unchanged', {
2753
+ tool: toolName,
2754
+ error: reason,
2755
+ });
2756
+ }
2249
2757
  }
2250
2758
  }
2251
2759
  // --- Response schema validation (opt-in) ---