@adcp/sdk 17.0.0-beta.0 → 17.0.0-beta.1

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 (70) hide show
  1. package/dist/lib/adapters/version/3.1/bidding.js +1 -1
  2. package/dist/lib/adapters/version/3.1/bidding.mjs +1 -1
  3. package/dist/lib/core/ResponseValidator.js +1 -1
  4. package/dist/lib/core/ResponseValidator.mjs +1 -1
  5. package/dist/lib/core/SingleAgentClient.d.mts +7 -5
  6. package/dist/lib/core/SingleAgentClient.d.ts +7 -5
  7. package/dist/lib/core/SingleAgentClient.js +57 -32
  8. package/dist/lib/core/SingleAgentClient.mjs +59 -33
  9. package/dist/lib/core/TaskExecutor.js +5 -3
  10. package/dist/lib/core/TaskExecutor.mjs +5 -3
  11. package/dist/lib/errors/index.d.mts +22 -0
  12. package/dist/lib/errors/index.d.ts +22 -0
  13. package/dist/lib/errors/index.js +43 -0
  14. package/dist/lib/errors/index.mjs +42 -0
  15. package/dist/lib/index.d.mts +1 -1
  16. package/dist/lib/index.d.ts +1 -1
  17. package/dist/lib/index.js +2 -0
  18. package/dist/lib/index.mjs +2 -0
  19. package/dist/lib/protocols/mcp-modern.d.mts +2 -0
  20. package/dist/lib/protocols/mcp-modern.d.ts +2 -0
  21. package/dist/lib/protocols/mcp-modern.js +31 -5
  22. package/dist/lib/protocols/mcp-modern.mjs +31 -5
  23. package/dist/lib/protocols/mcp-scope.d.mts +3 -1
  24. package/dist/lib/protocols/mcp-scope.d.ts +3 -1
  25. package/dist/lib/protocols/mcp-scope.js +19 -2
  26. package/dist/lib/protocols/mcp-scope.mjs +18 -2
  27. package/dist/lib/protocols/mcp.js +12 -6
  28. package/dist/lib/protocols/mcp.mjs +13 -6
  29. package/dist/lib/testing/client.d.mts +1 -0
  30. package/dist/lib/testing/client.d.ts +1 -0
  31. package/dist/lib/testing/client.js +6 -5
  32. package/dist/lib/testing/client.mjs +6 -5
  33. package/dist/lib/testing/storyboard/agent-routing.d.mts +5 -2
  34. package/dist/lib/testing/storyboard/agent-routing.d.ts +5 -2
  35. package/dist/lib/testing/storyboard/agent-routing.js +11 -4
  36. package/dist/lib/testing/storyboard/agent-routing.mjs +11 -4
  37. package/dist/lib/testing/storyboard/context.d.mts +13 -3
  38. package/dist/lib/testing/storyboard/context.d.ts +13 -3
  39. package/dist/lib/testing/storyboard/context.js +15 -10
  40. package/dist/lib/testing/storyboard/context.mjs +15 -10
  41. package/dist/lib/testing/storyboard/runner.js +141 -13
  42. package/dist/lib/testing/storyboard/runner.mjs +141 -13
  43. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  44. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  45. package/dist/lib/testing/storyboard/validations.js +4 -1
  46. package/dist/lib/testing/storyboard/validations.mjs +4 -1
  47. package/dist/lib/testing/test-controller.js +3 -0
  48. package/dist/lib/testing/test-controller.mjs +3 -0
  49. package/dist/lib/utils/envelope-status-compat.d.mts +18 -1
  50. package/dist/lib/utils/envelope-status-compat.d.ts +18 -1
  51. package/dist/lib/utils/envelope-status-compat.js +11 -5
  52. package/dist/lib/utils/envelope-status-compat.mjs +11 -5
  53. package/dist/lib/utils/response-unwrapper.js +12 -9
  54. package/dist/lib/utils/response-unwrapper.mjs +12 -9
  55. package/dist/lib/v2/projection/creative-delivery.js +56 -11
  56. package/dist/lib/v2/projection/creative-delivery.mjs +56 -11
  57. package/dist/lib/validation/schema-validator.js +1 -1
  58. package/dist/lib/validation/schema-validator.mjs +1 -1
  59. package/dist/lib/version.d.mts +3 -3
  60. package/dist/lib/version.d.ts +3 -3
  61. package/dist/lib/version.js +3 -3
  62. package/dist/lib/version.mjs +3 -3
  63. package/docs/TYPE-SUMMARY.md +2 -2
  64. package/docs/guides/COLLECTION-FIXTURE-FOUNDATION.md +11 -0
  65. package/docs/guides/DISCOVERY-CACHE-AND-SESSION-REUSE.md +12 -1
  66. package/docs/llms.txt +2 -2
  67. package/docs/migration-14.x-rc-worksheet.md +4 -4
  68. package/docs/migration-16-to-17.md +24 -1
  69. package/docs/migration-reporting-storage-package.md +1 -1
  70. package/package.json +1 -1
@@ -684,7 +684,7 @@ function remapTaskCompletionOutputs(outputs) {
684
684
  return o;
685
685
  });
686
686
  }
687
- async function resolveTaskCompletionOutputs(taskResult, outputs, client, webhookReceiver, originatingTaskName) {
687
+ async function resolveTaskCompletionOutputs(taskResult, outputs, client, webhookReceiver, originatingTaskName, responseAdcpVersion) {
688
688
  const hasTaskCompletionPath = outputs.some(
689
689
  (o) => typeof o.path === "string" && o.path.startsWith(TASK_COMPLETION_PATH_PREFIX)
690
690
  );
@@ -737,7 +737,10 @@ async function resolveTaskCompletionOutputs(taskResult, outputs, client, webhook
737
737
  if (winner.kind === "poll") {
738
738
  const polled = winner.result;
739
739
  if (polled.success === false) return { attempted: true, taskFailed: true };
740
- return { attempted: true, data: normalizeTaskCompletionData(polled.data, originatingTaskName) };
740
+ return {
741
+ attempted: true,
742
+ data: normalizeTaskCompletionData(polled.data, originatingTaskName, responseAdcpVersion)
743
+ };
741
744
  }
742
745
  const waitResult = winner.result;
743
746
  if (waitResult.timed_out) {
@@ -745,7 +748,10 @@ async function resolveTaskCompletionOutputs(taskResult, outputs, client, webhook
745
748
  }
746
749
  const webhookBody = waitResult.webhook.body;
747
750
  if (webhookBody?.status === "completed") {
748
- return { attempted: true, data: normalizeTaskCompletionData(webhookBody.result, originatingTaskName) };
751
+ return {
752
+ attempted: true,
753
+ data: normalizeTaskCompletionData(webhookBody.result, originatingTaskName, responseAdcpVersion)
754
+ };
749
755
  }
750
756
  return { attempted: true, taskFailed: true };
751
757
  } catch {
@@ -754,18 +760,19 @@ async function resolveTaskCompletionOutputs(taskResult, outputs, client, webhook
754
760
  if (timer) clearTimeout(timer);
755
761
  }
756
762
  }
757
- function normalizeTaskCompletionData(data, taskName) {
763
+ function normalizeTaskCompletionData(data, taskName, adcpVersion) {
758
764
  if (data == null || typeof data !== "object" || Array.isArray(data)) return data;
759
765
  const original = data;
760
- const compat = (0, import_envelope_status_compat.injectLegacyEnvelopeStatus)(original, { toolName: taskName });
766
+ const compatOptions = { toolName: taskName, adcpVersion };
767
+ const compat = (0, import_envelope_status_compat.injectLegacyEnvelopeStatus)(original, compatOptions);
761
768
  if (!("status" in original) && "status" in compat) {
762
769
  const { status: _status, ...rest } = compat;
763
- return (0, import_envelope_status_compat.normalizeLegacyMediaBuyStatusForReturn)(rest, { toolName: taskName });
770
+ return (0, import_envelope_status_compat.normalizeLegacyMediaBuyStatusForReturn)(rest, compatOptions);
764
771
  }
765
772
  if ((taskName === "create_media_buy" || taskName === "update_media_buy") && typeof original.status === "string" && compat.status === "completed" && typeof compat.media_buy_status === "string") {
766
- return (0, import_envelope_status_compat.normalizeLegacyMediaBuyStatusForReturn)({ ...compat, status: original.status }, { toolName: taskName });
773
+ return (0, import_envelope_status_compat.normalizeLegacyMediaBuyStatusForReturn)({ ...compat, status: original.status }, compatOptions);
767
774
  }
768
- return (0, import_envelope_status_compat.normalizeLegacyMediaBuyStatusForReturn)(compat, { toolName: taskName });
775
+ return (0, import_envelope_status_compat.normalizeLegacyMediaBuyStatusForReturn)(compat, compatOptions);
769
776
  }
770
777
  function readEnvIntOrDefault(value, fallback) {
771
778
  if (!value) return fallback;
@@ -1903,9 +1910,10 @@ async function prepareStoryboardFixturesBody(agentUrl, storyboard, options) {
1903
1910
  options = snapshotPreparationOptions(options);
1904
1911
  }
1905
1912
  const callerOwnsClients = (0, import_client.hasTestClientScope)();
1906
- const preparationOptions = resolvePreparationRouteOptions(agentUrls[0], options);
1913
+ const runOptions = options;
1914
+ const preparationOptions = resolvePreparationRouteOptions(agentUrls[0], runOptions);
1907
1915
  const resolution = (0, import_client.getOrCreateClientResolution)(agentUrls[0], preparationOptions);
1908
- const client = resolution.client;
1916
+ let client = resolution.client;
1909
1917
  const finishPreparation = (seeding, disposition) => {
1910
1918
  if (!scopeStillMatches()) {
1911
1919
  return refusePreparation(driftRefusal, seeding ?? void 0);
@@ -1915,6 +1923,45 @@ async function prepareStoryboardFixturesBody(agentUrl, storyboard, options) {
1915
1923
  options = preparationOptions;
1916
1924
  try {
1917
1925
  let profile = options._profile;
1926
+ let discoveryStep;
1927
+ let routingContext;
1928
+ const failDiscovery = (step, failedProfile) => {
1929
+ const failed = buildDiscoveryFailedResult(agentUrls, storyboard, step, failedProfile);
1930
+ return finishPreparation(
1931
+ { phase: failed.phases[0], allPassed: false, passedCount: 0, failedCount: 1 },
1932
+ { status: "failed", detail: step.error ?? "agent discovery failed before fixture preparation" }
1933
+ );
1934
+ };
1935
+ if (runOptions.agents && (storyboardCapabilityPredicates(storyboard).length > 0 || storyboard.required_tools?.length || storyboard.required_any_of_tools?.length)) {
1936
+ try {
1937
+ validateAgentsMap(agentUrl, storyboard, runOptions);
1938
+ routingContext = await (0, import_agent_routing.buildRoutingContext)(storyboard, { ...runOptions, discovery_resilient: false });
1939
+ } catch (error) {
1940
+ const failedStep = error instanceof import_agent_routing.DiscoveryFailure ? error.step : void 0;
1941
+ return await failDiscovery({
1942
+ ...failedStep,
1943
+ step: failedStep?.step ?? "Discover preparation topology",
1944
+ passed: false,
1945
+ duration_ms: failedStep?.duration_ms ?? 0,
1946
+ error: (0, import_oauth_metadata_graph.redactOAuthUrlsInText)(
1947
+ error instanceof import_agent_routing.DiscoveryFailure ? `Discovery failed for agent "${error.agentKey}" (${(0, import_oauth_metadata_graph.redactOAuthUrlForOutput)(error.url)}): ${error.underlying}` : error instanceof Error ? error.message : String(error)
1948
+ )
1949
+ });
1950
+ }
1951
+ for (const routeProfile of routingContext.profiles.values()) {
1952
+ const failure = (0, import_client.describeProfileDiscoveryFailure)(
1953
+ routeProfile,
1954
+ runOptions._requireValidCapabilityEvidence === true,
1955
+ runOptions._allowCapabilityDiagnostic === true
1956
+ );
1957
+ if (failure) return await failDiscovery(failure, routeProfile);
1958
+ }
1959
+ const firstKey = Object.keys(runOptions.agents)[0];
1960
+ profile = routingContext.profiles.get(firstKey);
1961
+ client = routingContext.clients.get(firstKey);
1962
+ discoveryStep = routingContext.discoverySteps?.get(firstKey);
1963
+ options = { ...options, _profile: profile, agentTools: (0, import_agent_routing.normalizeAgentToolNames)(profile.tools) };
1964
+ }
1918
1965
  const suppliedFailure = profile && (0, import_client.describeProfileDiscoveryFailure)(
1919
1966
  profile,
1920
1967
  options._requireValidCapabilityEvidence === true,
@@ -1927,8 +1974,9 @@ async function prepareStoryboardFixturesBody(agentUrl, storyboard, options) {
1927
1974
  { status: "failed", detail: suppliedFailure.error ?? "agent discovery failed" }
1928
1975
  );
1929
1976
  }
1930
- if (!profile && !resolution.reusedShared) {
1977
+ if (!profile) {
1931
1978
  const discovered = await (0, import_client.getOrDiscoverStoryboardProfile)(client, options);
1979
+ discoveryStep = discovered.evidence ?? discovered.step;
1932
1980
  if (discovered.step.passed === false) {
1933
1981
  const failed = buildDiscoveryFailedResult(agentUrls, storyboard, discovered.step, discovered.profile);
1934
1982
  return await finishPreparation(
@@ -1949,6 +1997,80 @@ async function prepareStoryboardFixturesBody(agentUrl, storyboard, options) {
1949
1997
  };
1950
1998
  }
1951
1999
  if (!scopeStillMatches()) return await refusePreparation(driftRefusal);
2000
+ let capabilityUnmet;
2001
+ const applicabilityTools = routingContext ? [...new Set([...routingContext.profiles.values()].flatMap((p) => (0, import_agent_routing.normalizeAgentToolNames)(p.tools) ?? []))] : options.agentTools;
2002
+ try {
2003
+ if (routingContext && runOptions.agents) {
2004
+ const dispatch = createRoutingDispatcher(routingContext, runOptions, runOptions.agents);
2005
+ const defaultProfile = runOptions.default_agent ? routingContext.profiles.get(runOptions.default_agent) : void 0;
2006
+ const rootDetails = storyboard.phases.flatMap(
2007
+ (phase) => phase.steps.filter((step) => step.task !== import_loader.VALIDATION_ONLY_TASK).map((step) => {
2008
+ const selected = dispatch.nextFor(step);
2009
+ const owner = runOptions.default_agent ? defaultProfile : selected.profile;
2010
+ return evaluateStoryboardCapabilityGates(
2011
+ storyboard,
2012
+ owner,
2013
+ (0, import_agent_routing.normalizeAgentToolNames)(owner?.tools),
2014
+ runOptions.adcpVersion
2015
+ );
2016
+ })
2017
+ );
2018
+ capabilityUnmet = rootDetails.length > 0 && rootDetails.every((detail) => detail !== null) ? rootDetails[0] : null;
2019
+ } else {
2020
+ capabilityUnmet = evaluateStoryboardCapabilityGates(
2021
+ storyboard,
2022
+ profile,
2023
+ options.agentTools,
2024
+ options.adcpVersion
2025
+ );
2026
+ }
2027
+ } catch (error) {
2028
+ return await failDiscovery({
2029
+ step: "Resolve preparation routes",
2030
+ passed: false,
2031
+ duration_ms: 0,
2032
+ error: (0, import_oauth_metadata_graph.redactOAuthUrlsInText)(error instanceof Error ? error.message : String(error))
2033
+ });
2034
+ }
2035
+ const missingFamily = applicabilityTools && storyboard.required_any_of_tools?.find((family) => !(0, import_agent_routing.hasAnyRequiredTool)(family.tools, applicabilityTools));
2036
+ const unmetDetail = capabilityUnmet ?? (missingFamily ? `missing_required_tool_family: needs ${missingFamily.tools.join(" or ")}` + (missingFamily.rationale ? ` (${missingFamily.rationale})` : "") : null) ?? (storyboard.required_tools?.length && applicabilityTools && !(0, import_agent_routing.hasAnyRequiredTool)(storyboard.required_tools, applicabilityTools) ? `agent does not advertise any of [${storyboard.required_tools.join(", ")}]` : null);
2037
+ if (unmetDetail !== null) {
2038
+ const observations = routingContext && runOptions.agents ? Object.entries(runOptions.agents).map(([key, entry]) => ({
2039
+ key,
2040
+ url: entry.url,
2041
+ step: routingContext.discoverySteps?.get(key)
2042
+ })) : [{ key: "discovery", url: agentUrls[0], step: discoveryStep }];
2043
+ const steps = observations.map(({ key, url, step }) => ({
2044
+ storyboard_id: storyboard.id,
2045
+ phase_id: "discovery",
2046
+ step_id: key,
2047
+ title: step ? "Agent capability discovery" : "Supplied agent capability profile",
2048
+ task: step?.task ?? "",
2049
+ passed: true,
2050
+ duration_ms: step?.duration_ms ?? 0,
2051
+ validations: [],
2052
+ context: {},
2053
+ ...routingContext && { agent_url: (0, import_oauth_metadata_graph.redactOAuthUrlForOutput)(url) },
2054
+ extraction: step?.extraction ?? { path: "none" },
2055
+ ...step?.request && { request: step.request },
2056
+ ...step?.response_record && { response_record: step.response_record }
2057
+ }));
2058
+ return await finishPreparation(
2059
+ {
2060
+ phase: {
2061
+ phase_id: "discovery",
2062
+ phase_title: "Agent capability discovery",
2063
+ passed: true,
2064
+ duration_ms: steps.reduce((total, step) => total + step.duration_ms, 0),
2065
+ steps
2066
+ },
2067
+ allPassed: true,
2068
+ passedCount: steps.length,
2069
+ failedCount: 0
2070
+ },
2071
+ { status: "unsupported", reason: "not_applicable", detail: unmetDetail }
2072
+ );
2073
+ }
1952
2074
  const context = createRunContext(storyboard, options).context;
1953
2075
  const seeding = await (0, import_seeding.runControllerSeeding)(client, storyboard, options, context);
1954
2076
  if (!seeding) {
@@ -5841,7 +5963,12 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
5841
5963
  };
5842
5964
  const hasData = taskResult?.data !== void 0 && taskResult?.data !== null;
5843
5965
  if (passed && hasData && taskResult && !(effectiveStep.task === "sync_accounts" && request.dry_run === true)) {
5844
- const extracted = (0, import_context.extractContextWithProvenance)(effectiveStep.task, taskResult.data, step.id);
5966
+ const extracted = (0, import_context.extractContextWithProvenance)(
5967
+ effectiveStep.task,
5968
+ taskResult.data,
5969
+ step.id,
5970
+ options._serverAdcpVersion ?? options.adcpVersion
5971
+ );
5845
5972
  const conventionUnset = [];
5846
5973
  for (const group of extracted.clearGroups ?? []) {
5847
5974
  if (group.when && !group.when.values.includes(updatedContext[group.when.key])) continue;
@@ -5872,7 +5999,8 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
5872
5999
  step.context_outputs,
5873
6000
  client,
5874
6001
  runState.webhookReceiver,
5875
- effectiveStep.task
6002
+ effectiveStep.task,
6003
+ options._serverAdcpVersion ?? options.adcpVersion
5876
6004
  );
5877
6005
  const extractionData = "data" in taskCompletionResolution ? taskCompletionResolution.data : hasData && taskResult ? taskResult.data : void 0;
5878
6006
  const remappedOutputs = remapTaskCompletionOutputs(step.context_outputs);
@@ -726,7 +726,7 @@ function remapTaskCompletionOutputs(outputs) {
726
726
  return o;
727
727
  });
728
728
  }
729
- async function resolveTaskCompletionOutputs(taskResult, outputs, client, webhookReceiver, originatingTaskName) {
729
+ async function resolveTaskCompletionOutputs(taskResult, outputs, client, webhookReceiver, originatingTaskName, responseAdcpVersion) {
730
730
  const hasTaskCompletionPath = outputs.some(
731
731
  (o) => typeof o.path === "string" && o.path.startsWith(TASK_COMPLETION_PATH_PREFIX)
732
732
  );
@@ -779,7 +779,10 @@ async function resolveTaskCompletionOutputs(taskResult, outputs, client, webhook
779
779
  if (winner.kind === "poll") {
780
780
  const polled = winner.result;
781
781
  if (polled.success === false) return { attempted: true, taskFailed: true };
782
- return { attempted: true, data: normalizeTaskCompletionData(polled.data, originatingTaskName) };
782
+ return {
783
+ attempted: true,
784
+ data: normalizeTaskCompletionData(polled.data, originatingTaskName, responseAdcpVersion)
785
+ };
783
786
  }
784
787
  const waitResult = winner.result;
785
788
  if (waitResult.timed_out) {
@@ -787,7 +790,10 @@ async function resolveTaskCompletionOutputs(taskResult, outputs, client, webhook
787
790
  }
788
791
  const webhookBody = waitResult.webhook.body;
789
792
  if (webhookBody?.status === "completed") {
790
- return { attempted: true, data: normalizeTaskCompletionData(webhookBody.result, originatingTaskName) };
793
+ return {
794
+ attempted: true,
795
+ data: normalizeTaskCompletionData(webhookBody.result, originatingTaskName, responseAdcpVersion)
796
+ };
791
797
  }
792
798
  return { attempted: true, taskFailed: true };
793
799
  } catch {
@@ -796,18 +802,19 @@ async function resolveTaskCompletionOutputs(taskResult, outputs, client, webhook
796
802
  if (timer) clearTimeout(timer);
797
803
  }
798
804
  }
799
- function normalizeTaskCompletionData(data, taskName) {
805
+ function normalizeTaskCompletionData(data, taskName, adcpVersion) {
800
806
  if (data == null || typeof data !== "object" || Array.isArray(data)) return data;
801
807
  const original = data;
802
- const compat = injectLegacyEnvelopeStatus(original, { toolName: taskName });
808
+ const compatOptions = { toolName: taskName, adcpVersion };
809
+ const compat = injectLegacyEnvelopeStatus(original, compatOptions);
803
810
  if (!("status" in original) && "status" in compat) {
804
811
  const { status: _status, ...rest } = compat;
805
- return normalizeLegacyMediaBuyStatusForReturn(rest, { toolName: taskName });
812
+ return normalizeLegacyMediaBuyStatusForReturn(rest, compatOptions);
806
813
  }
807
814
  if ((taskName === "create_media_buy" || taskName === "update_media_buy") && typeof original.status === "string" && compat.status === "completed" && typeof compat.media_buy_status === "string") {
808
- return normalizeLegacyMediaBuyStatusForReturn({ ...compat, status: original.status }, { toolName: taskName });
815
+ return normalizeLegacyMediaBuyStatusForReturn({ ...compat, status: original.status }, compatOptions);
809
816
  }
810
- return normalizeLegacyMediaBuyStatusForReturn(compat, { toolName: taskName });
817
+ return normalizeLegacyMediaBuyStatusForReturn(compat, compatOptions);
811
818
  }
812
819
  function readEnvIntOrDefault(value, fallback) {
813
820
  if (!value) return fallback;
@@ -1945,9 +1952,10 @@ async function prepareStoryboardFixturesBody(agentUrl, storyboard, options) {
1945
1952
  options = snapshotPreparationOptions(options);
1946
1953
  }
1947
1954
  const callerOwnsClients = hasTestClientScope();
1948
- const preparationOptions = resolvePreparationRouteOptions(agentUrls[0], options);
1955
+ const runOptions = options;
1956
+ const preparationOptions = resolvePreparationRouteOptions(agentUrls[0], runOptions);
1949
1957
  const resolution = getOrCreateClientResolution(agentUrls[0], preparationOptions);
1950
- const client = resolution.client;
1958
+ let client = resolution.client;
1951
1959
  const finishPreparation = (seeding, disposition) => {
1952
1960
  if (!scopeStillMatches()) {
1953
1961
  return refusePreparation(driftRefusal, seeding ?? void 0);
@@ -1957,6 +1965,45 @@ async function prepareStoryboardFixturesBody(agentUrl, storyboard, options) {
1957
1965
  options = preparationOptions;
1958
1966
  try {
1959
1967
  let profile = options._profile;
1968
+ let discoveryStep;
1969
+ let routingContext;
1970
+ const failDiscovery = (step, failedProfile) => {
1971
+ const failed = buildDiscoveryFailedResult(agentUrls, storyboard, step, failedProfile);
1972
+ return finishPreparation(
1973
+ { phase: failed.phases[0], allPassed: false, passedCount: 0, failedCount: 1 },
1974
+ { status: "failed", detail: step.error ?? "agent discovery failed before fixture preparation" }
1975
+ );
1976
+ };
1977
+ if (runOptions.agents && (storyboardCapabilityPredicates(storyboard).length > 0 || storyboard.required_tools?.length || storyboard.required_any_of_tools?.length)) {
1978
+ try {
1979
+ validateAgentsMap(agentUrl, storyboard, runOptions);
1980
+ routingContext = await buildRoutingContext(storyboard, { ...runOptions, discovery_resilient: false });
1981
+ } catch (error) {
1982
+ const failedStep = error instanceof DiscoveryFailure ? error.step : void 0;
1983
+ return await failDiscovery({
1984
+ ...failedStep,
1985
+ step: failedStep?.step ?? "Discover preparation topology",
1986
+ passed: false,
1987
+ duration_ms: failedStep?.duration_ms ?? 0,
1988
+ error: redactOAuthUrlsInText(
1989
+ error instanceof DiscoveryFailure ? `Discovery failed for agent "${error.agentKey}" (${redactOAuthUrlForOutput(error.url)}): ${error.underlying}` : error instanceof Error ? error.message : String(error)
1990
+ )
1991
+ });
1992
+ }
1993
+ for (const routeProfile of routingContext.profiles.values()) {
1994
+ const failure = describeProfileDiscoveryFailure(
1995
+ routeProfile,
1996
+ runOptions._requireValidCapabilityEvidence === true,
1997
+ runOptions._allowCapabilityDiagnostic === true
1998
+ );
1999
+ if (failure) return await failDiscovery(failure, routeProfile);
2000
+ }
2001
+ const firstKey = Object.keys(runOptions.agents)[0];
2002
+ profile = routingContext.profiles.get(firstKey);
2003
+ client = routingContext.clients.get(firstKey);
2004
+ discoveryStep = routingContext.discoverySteps?.get(firstKey);
2005
+ options = { ...options, _profile: profile, agentTools: normalizeAgentToolNames(profile.tools) };
2006
+ }
1960
2007
  const suppliedFailure = profile && describeProfileDiscoveryFailure(
1961
2008
  profile,
1962
2009
  options._requireValidCapabilityEvidence === true,
@@ -1969,8 +2016,9 @@ async function prepareStoryboardFixturesBody(agentUrl, storyboard, options) {
1969
2016
  { status: "failed", detail: suppliedFailure.error ?? "agent discovery failed" }
1970
2017
  );
1971
2018
  }
1972
- if (!profile && !resolution.reusedShared) {
2019
+ if (!profile) {
1973
2020
  const discovered = await getOrDiscoverStoryboardProfile(client, options);
2021
+ discoveryStep = discovered.evidence ?? discovered.step;
1974
2022
  if (discovered.step.passed === false) {
1975
2023
  const failed = buildDiscoveryFailedResult(agentUrls, storyboard, discovered.step, discovered.profile);
1976
2024
  return await finishPreparation(
@@ -1991,6 +2039,80 @@ async function prepareStoryboardFixturesBody(agentUrl, storyboard, options) {
1991
2039
  };
1992
2040
  }
1993
2041
  if (!scopeStillMatches()) return await refusePreparation(driftRefusal);
2042
+ let capabilityUnmet;
2043
+ const applicabilityTools = routingContext ? [...new Set([...routingContext.profiles.values()].flatMap((p) => normalizeAgentToolNames(p.tools) ?? []))] : options.agentTools;
2044
+ try {
2045
+ if (routingContext && runOptions.agents) {
2046
+ const dispatch = createRoutingDispatcher(routingContext, runOptions, runOptions.agents);
2047
+ const defaultProfile = runOptions.default_agent ? routingContext.profiles.get(runOptions.default_agent) : void 0;
2048
+ const rootDetails = storyboard.phases.flatMap(
2049
+ (phase) => phase.steps.filter((step) => step.task !== VALIDATION_ONLY_TASK).map((step) => {
2050
+ const selected = dispatch.nextFor(step);
2051
+ const owner = runOptions.default_agent ? defaultProfile : selected.profile;
2052
+ return evaluateStoryboardCapabilityGates(
2053
+ storyboard,
2054
+ owner,
2055
+ normalizeAgentToolNames(owner?.tools),
2056
+ runOptions.adcpVersion
2057
+ );
2058
+ })
2059
+ );
2060
+ capabilityUnmet = rootDetails.length > 0 && rootDetails.every((detail) => detail !== null) ? rootDetails[0] : null;
2061
+ } else {
2062
+ capabilityUnmet = evaluateStoryboardCapabilityGates(
2063
+ storyboard,
2064
+ profile,
2065
+ options.agentTools,
2066
+ options.adcpVersion
2067
+ );
2068
+ }
2069
+ } catch (error) {
2070
+ return await failDiscovery({
2071
+ step: "Resolve preparation routes",
2072
+ passed: false,
2073
+ duration_ms: 0,
2074
+ error: redactOAuthUrlsInText(error instanceof Error ? error.message : String(error))
2075
+ });
2076
+ }
2077
+ const missingFamily = applicabilityTools && storyboard.required_any_of_tools?.find((family) => !hasAnyRequiredTool(family.tools, applicabilityTools));
2078
+ const unmetDetail = capabilityUnmet ?? (missingFamily ? `missing_required_tool_family: needs ${missingFamily.tools.join(" or ")}` + (missingFamily.rationale ? ` (${missingFamily.rationale})` : "") : null) ?? (storyboard.required_tools?.length && applicabilityTools && !hasAnyRequiredTool(storyboard.required_tools, applicabilityTools) ? `agent does not advertise any of [${storyboard.required_tools.join(", ")}]` : null);
2079
+ if (unmetDetail !== null) {
2080
+ const observations = routingContext && runOptions.agents ? Object.entries(runOptions.agents).map(([key, entry]) => ({
2081
+ key,
2082
+ url: entry.url,
2083
+ step: routingContext.discoverySteps?.get(key)
2084
+ })) : [{ key: "discovery", url: agentUrls[0], step: discoveryStep }];
2085
+ const steps = observations.map(({ key, url, step }) => ({
2086
+ storyboard_id: storyboard.id,
2087
+ phase_id: "discovery",
2088
+ step_id: key,
2089
+ title: step ? "Agent capability discovery" : "Supplied agent capability profile",
2090
+ task: step?.task ?? "",
2091
+ passed: true,
2092
+ duration_ms: step?.duration_ms ?? 0,
2093
+ validations: [],
2094
+ context: {},
2095
+ ...routingContext && { agent_url: redactOAuthUrlForOutput(url) },
2096
+ extraction: step?.extraction ?? { path: "none" },
2097
+ ...step?.request && { request: step.request },
2098
+ ...step?.response_record && { response_record: step.response_record }
2099
+ }));
2100
+ return await finishPreparation(
2101
+ {
2102
+ phase: {
2103
+ phase_id: "discovery",
2104
+ phase_title: "Agent capability discovery",
2105
+ passed: true,
2106
+ duration_ms: steps.reduce((total, step) => total + step.duration_ms, 0),
2107
+ steps
2108
+ },
2109
+ allPassed: true,
2110
+ passedCount: steps.length,
2111
+ failedCount: 0
2112
+ },
2113
+ { status: "unsupported", reason: "not_applicable", detail: unmetDetail }
2114
+ );
2115
+ }
1994
2116
  const context = createRunContext(storyboard, options).context;
1995
2117
  const seeding = await runControllerSeeding(client, storyboard, options, context);
1996
2118
  if (!seeding) {
@@ -5883,7 +6005,12 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
5883
6005
  };
5884
6006
  const hasData = taskResult?.data !== void 0 && taskResult?.data !== null;
5885
6007
  if (passed && hasData && taskResult && !(effectiveStep.task === "sync_accounts" && request.dry_run === true)) {
5886
- const extracted = extractContextWithProvenance(effectiveStep.task, taskResult.data, step.id);
6008
+ const extracted = extractContextWithProvenance(
6009
+ effectiveStep.task,
6010
+ taskResult.data,
6011
+ step.id,
6012
+ options._serverAdcpVersion ?? options.adcpVersion
6013
+ );
5887
6014
  const conventionUnset = [];
5888
6015
  for (const group of extracted.clearGroups ?? []) {
5889
6016
  if (group.when && !group.when.values.includes(updatedContext[group.when.key])) continue;
@@ -5914,7 +6041,8 @@ async function executeStep(client, step, storyboardId, phaseId, context, allStep
5914
6041
  step.context_outputs,
5915
6042
  client,
5916
6043
  runState.webhookReceiver,
5917
- effectiveStep.task
6044
+ effectiveStep.task,
6045
+ options._serverAdcpVersion ?? options.adcpVersion
5918
6046
  );
5919
6047
  const extractionData = "data" in taskCompletionResolution ? taskCompletionResolution.data : hasData && taskResult ? taskResult.data : void 0;
5920
6048
  const remappedOutputs = remapTaskCompletionOutputs(step.context_outputs);
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
197
197
  */
198
198
  export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
199
199
  /** Capability semver emitted in run summaries and used for advisory expiry. */
200
- export declare const RUNNER_CAPABILITY_VERSION = "17.0.0-beta.0";
200
+ export declare const RUNNER_CAPABILITY_VERSION = "17.0.0-beta.1";
201
201
  /** True when a failed validation contributes to the owning step's grade. */
202
202
  export declare function validationFailsStep(result: ValidationResult): boolean;
203
203
  /**
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
197
197
  */
198
198
  export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
199
199
  /** Capability semver emitted in run summaries and used for advisory expiry. */
200
- export declare const RUNNER_CAPABILITY_VERSION = "17.0.0-beta.0";
200
+ export declare const RUNNER_CAPABILITY_VERSION = "17.0.0-beta.1";
201
201
  /** True when a failed validation contributes to the owning step's grade. */
202
202
  export declare function validationFailsStep(result: ValidationResult): boolean;
203
203
  /**
@@ -295,8 +295,11 @@ function validateResponseSchemaForVersion(validation, ctx, taskResult) {
295
295
  return rest;
296
296
  })();
297
297
  const schema = import_response_schemas.TOOL_RESPONSE_SCHEMAS[taskName];
298
- const dataForValidation = Array.isArray(dataWithoutMessage) ? dataWithoutMessage : (0, import_envelope_status_compat.injectLegacyEnvelopeStatus)(dataWithoutMessage, { toolName: taskName });
299
298
  const responseAdcpVersion = ctx.responseAdcpVersion ?? ctx.adcpVersion;
299
+ const dataForValidation = Array.isArray(dataWithoutMessage) ? dataWithoutMessage : (0, import_envelope_status_compat.injectLegacyEnvelopeStatus)(dataWithoutMessage, {
300
+ toolName: taskName,
301
+ adcpVersion: responseAdcpVersion
302
+ });
300
303
  const parseResult = schema?.safeParse(
301
304
  (0, import_response_schemas.prepareResponseForSchemaValidation)(taskName, dataForValidation, responseAdcpVersion)
302
305
  );
@@ -276,8 +276,11 @@ function validateResponseSchemaForVersion(validation, ctx, taskResult) {
276
276
  return rest;
277
277
  })();
278
278
  const schema = TOOL_RESPONSE_SCHEMAS[taskName];
279
- const dataForValidation = Array.isArray(dataWithoutMessage) ? dataWithoutMessage : injectLegacyEnvelopeStatus(dataWithoutMessage, { toolName: taskName });
280
279
  const responseAdcpVersion = ctx.responseAdcpVersion ?? ctx.adcpVersion;
280
+ const dataForValidation = Array.isArray(dataWithoutMessage) ? dataWithoutMessage : injectLegacyEnvelopeStatus(dataWithoutMessage, {
281
+ toolName: taskName,
282
+ adcpVersion: responseAdcpVersion
283
+ });
281
284
  const parseResult = schema?.safeParse(
282
285
  prepareResponseForSchemaValidation(taskName, dataForValidation, responseAdcpVersion)
283
286
  );
@@ -31,6 +31,7 @@ __export(test_controller_exports, {
31
31
  });
32
32
  module.exports = __toCommonJS(test_controller_exports);
33
33
  var import_client = require('./client.js');
34
+ var import_errors = require('../errors/index.js');
34
35
  const TOOL_NAME = "comply_test_controller";
35
36
  const DEFAULT_SANDBOX_ACCOUNT = { brand: { domain: "test.example" }, operator: "test.example", sandbox: true };
36
37
  function buildAccount(options) {
@@ -84,6 +85,8 @@ async function detectController(client, profile, options) {
84
85
  }
85
86
  return { detected: false };
86
87
  } catch (error) {
88
+ if (error instanceof import_errors.ProtocolFeatureUnsupportedError && error.adcpError.details?.feature === "adcp_version")
89
+ throw error;
87
90
  (0, import_client.getLogger)().warn(
88
91
  { tool: TOOL_NAME, error: error instanceof Error ? error.message : String(error) },
89
92
  "comply_test_controller list_scenarios failed \u2014 treating as unavailable"
@@ -1,4 +1,5 @@
1
1
  import { getLogger, resolveAccount } from "./client.mjs";
2
+ import { ProtocolFeatureUnsupportedError } from "../errors/index.mjs";
2
3
  const TOOL_NAME = "comply_test_controller";
3
4
  const DEFAULT_SANDBOX_ACCOUNT = { brand: { domain: "test.example" }, operator: "test.example", sandbox: true };
4
5
  function buildAccount(options) {
@@ -52,6 +53,8 @@ async function detectController(client, profile, options) {
52
53
  }
53
54
  return { detected: false };
54
55
  } catch (error) {
56
+ if (error instanceof ProtocolFeatureUnsupportedError && error.adcpError.details?.feature === "adcp_version")
57
+ throw error;
55
58
  getLogger().warn(
56
59
  { tool: TOOL_NAME, error: error instanceof Error ? error.message : String(error) },
57
60
  "comply_test_controller list_scenarios failed \u2014 treating as unavailable"
@@ -14,7 +14,10 @@
14
14
  * enum during validation. The same translation applies to the documented 3.1
15
15
  * deprecation window for top-level `status: MediaBuyStatus`. Other 3.1+
16
16
  * responses are returned unchanged so the strict validator still rejects a
17
- * peer that omits or misuses `status`.
17
+ * peer that omits or misuses `status`. A bare `completed` / `rejected` /
18
+ * `canceled` is never treated as a lifecycle when the payload, or failing
19
+ * that the caller's requested release (`options.adcpVersion`), is 3.1+:
20
+ * there it is the task envelope status.
18
21
  *
19
22
  * The leniency is a back-compat affordance, not a permanent loosening.
20
23
  */
@@ -26,6 +29,20 @@ export interface LegacyEnvelopeStatusOptions {
26
29
  * the protocol task envelope.
27
30
  */
28
31
  toolName?: string;
32
+ /**
33
+ * AdCP release the caller requested or negotiated for this exchange (for
34
+ * example the client's `adcpVersion` pin, or the release negotiated down
35
+ * from it). The response `adcp_version` echo is optional, so a seller on
36
+ * 3.1+ may answer a create with only `{ status: 'completed', media_buy_id }`.
37
+ * `completed`, `rejected` and `canceled` are valid both as TaskStatus and as
38
+ * MediaBuyStatus. When the payload carries no version echo, this release
39
+ * decides which one a bare top-level `status` is: on 3.1+ it is the task
40
+ * envelope status and is never copied into `media_buy_status`. An explicit
41
+ * `adcp_version` / `adcp_major_version` on the payload always wins. When
42
+ * neither the payload nor the caller knows the release, the legacy 3.0
43
+ * reading still applies.
44
+ */
45
+ adcpVersion?: string;
29
46
  }
30
47
  /** 3.2 synchronous response arms that intentionally forbid task-envelope status. */
31
48
  export declare const STATUS_FREE_SYNC_RESPONSE_TOOLS: ReadonlySet<string>;
@@ -14,7 +14,10 @@
14
14
  * enum during validation. The same translation applies to the documented 3.1
15
15
  * deprecation window for top-level `status: MediaBuyStatus`. Other 3.1+
16
16
  * responses are returned unchanged so the strict validator still rejects a
17
- * peer that omits or misuses `status`.
17
+ * peer that omits or misuses `status`. A bare `completed` / `rejected` /
18
+ * `canceled` is never treated as a lifecycle when the payload, or failing
19
+ * that the caller's requested release (`options.adcpVersion`), is 3.1+:
20
+ * there it is the task envelope status.
18
21
  *
19
22
  * The leniency is a back-compat affordance, not a permanent loosening.
20
23
  */
@@ -26,6 +29,20 @@ export interface LegacyEnvelopeStatusOptions {
26
29
  * the protocol task envelope.
27
30
  */
28
31
  toolName?: string;
32
+ /**
33
+ * AdCP release the caller requested or negotiated for this exchange (for
34
+ * example the client's `adcpVersion` pin, or the release negotiated down
35
+ * from it). The response `adcp_version` echo is optional, so a seller on
36
+ * 3.1+ may answer a create with only `{ status: 'completed', media_buy_id }`.
37
+ * `completed`, `rejected` and `canceled` are valid both as TaskStatus and as
38
+ * MediaBuyStatus. When the payload carries no version echo, this release
39
+ * decides which one a bare top-level `status` is: on 3.1+ it is the task
40
+ * envelope status and is never copied into `media_buy_status`. An explicit
41
+ * `adcp_version` / `adcp_major_version` on the payload always wins. When
42
+ * neither the payload nor the caller knows the release, the legacy 3.0
43
+ * reading still applies.
44
+ */
45
+ adcpVersion?: string;
29
46
  }
30
47
  /** 3.2 synchronous response arms that intentionally forbid task-envelope status. */
31
48
  export declare const STATUS_FREE_SYNC_RESPONSE_TOOLS: ReadonlySet<string>;
@@ -51,7 +51,7 @@ function injectLegacyEnvelopeStatus(response, options) {
51
51
  return response;
52
52
  }
53
53
  if (options?.toolName && STATUS_FREE_SYNC_RESPONSE_TOOLS.has(options.toolName)) return response;
54
- if (options?.toolName && MEDIA_BUY_STATUS_RESPONSE_TOOLS.has(options.toolName) && allowsDeprecatedMediaBuyStatus(response) && isMediaBuySuccessStatusForCompat(response)) {
54
+ if (options?.toolName && MEDIA_BUY_STATUS_RESPONSE_TOOLS.has(options.toolName) && allowsDeprecatedMediaBuyStatus(response) && isMediaBuySuccessStatusForCompat(response, options)) {
55
55
  const mediaBuyStatus = response.status;
56
56
  return {
57
57
  ...response,
@@ -76,16 +76,16 @@ function normalizeLegacyMediaBuyStatusForReturn(response, options) {
76
76
  if (!response || typeof response !== "object" || Array.isArray(response)) {
77
77
  return response;
78
78
  }
79
- if (!options?.toolName || !MEDIA_BUY_STATUS_RESPONSE_TOOLS.has(options.toolName) || !allowsDeprecatedMediaBuyStatus(response) || !isMediaBuySuccessStatusForCompat(response)) {
79
+ if (!options?.toolName || !MEDIA_BUY_STATUS_RESPONSE_TOOLS.has(options.toolName) || !allowsDeprecatedMediaBuyStatus(response) || !isMediaBuySuccessStatusForCompat(response, options)) {
80
80
  return response;
81
81
  }
82
- const status = (0, import_media_buy_status.getAuthoritativeMediaBuyStatus)(response);
82
+ const status = (0, import_media_buy_status.getAuthoritativeMediaBuyStatus)(withRequestedVersion(response, options));
83
83
  if (!status || response.media_buy_status === status) return response;
84
84
  return { ...response, media_buy_status: status };
85
85
  }
86
- function isMediaBuySuccessStatusForCompat(response) {
86
+ function isMediaBuySuccessStatusForCompat(response, options) {
87
87
  const status = response.status;
88
- if ((0, import_media_buy_status.getAuthoritativeMediaBuyStatus)(response) !== status) return false;
88
+ if ((0, import_media_buy_status.getAuthoritativeMediaBuyStatus)(withRequestedVersion(response, options)) !== status) return false;
89
89
  if (typeof response.media_buy_status === "string" && response.media_buy_status !== status) {
90
90
  return false;
91
91
  }
@@ -100,6 +100,12 @@ function allowsDeprecatedMediaBuyStatus(payload) {
100
100
  if (typeof major === "number") return major === 3;
101
101
  return true;
102
102
  }
103
+ function withRequestedVersion(payload, options) {
104
+ const requested = options?.adcpVersion;
105
+ if (typeof requested !== "string" || requested.length === 0) return payload;
106
+ if (typeof payload.adcp_version === "string" || typeof payload.adcp_major_version === "number") return payload;
107
+ return { ...payload, adcp_version: requested };
108
+ }
103
109
  // Annotate the CommonJS export names for ESM import in node:
104
110
  0 && (module.exports = {
105
111
  STATUS_FREE_SYNC_RESPONSE_TOOLS,