Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions SCHEMA_DELTAS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# Generated-types delta

## Field changes

- `extensions/extension_meta.py`
- `AdcpExtensionFileSchema`: `-field_id`
_No field-shape changes detected._
2 changes: 1 addition & 1 deletion examples/sales_proposal_mode_seller/src/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def create_media_buy(
"media_buy_id": media_buy_id,
"buyer_ref": getattr(req, "buyer_ref", None),
"status": "active",
"confirmed_at": datetime.now(timezone.utc).isoformat(),
"confirmed_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
"proposal_id": str(proposal_id) if proposal_id else None,
"packages": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ async def finalize_proposal(
if pid in firm_cpm:
entry["firm_cpm"] = firm_cpm[pid]
expires_at = datetime.now(timezone.utc) + timedelta(hours=24)
committed_payload["expires_at"] = expires_at.isoformat()
committed_payload["expires_at"] = expires_at.isoformat().replace("+00:00", "Z")
return FinalizeProposalSuccess(
proposal=committed_payload,
expires_at=expires_at,
Expand Down
2 changes: 1 addition & 1 deletion src/adcp/server/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ def cancel_media_buy_response(
"media_buy_id": media_buy_id,
"status": "canceled",
"canceled_by": canceled_by,
"canceled_at": canceled_at or datetime.now(timezone.utc).isoformat(),
"canceled_at": canceled_at or datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
"valid_actions": [],
"sandbox": sandbox,
}
Expand Down
2 changes: 1 addition & 1 deletion src/adcp/server/proposal.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ def build(self) -> dict[str, Any]:
if self._brief_alignment:
proposal["brief_alignment"] = self._brief_alignment
if self._expires_at:
proposal["expires_at"] = self._expires_at.isoformat()
proposal["expires_at"] = self._expires_at.isoformat().replace("+00:00", "Z")
if self._budget_guidance:
proposal["total_budget_guidance"] = self._budget_guidance
if self._ext:
Expand Down
32 changes: 23 additions & 9 deletions src/adcp/server/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,20 @@ async def get_products():

from adcp.server.helpers import valid_actions_for_status


def _rfc3339_now() -> str:
"""Current UTC time as an RFC 3339 timestamp with ``Z`` suffix.

Python's :meth:`datetime.isoformat` emits ``+00:00`` for UTC, but
several strict schema validators in the AdCP ecosystem — notably
the ``zod.string().datetime()`` check that the AdCP storyboard
runner uses — reject the offset form by default. Normalizing to
the Zulu form (``...Z``) keeps response timestamps acceptable to
every common validator without losing precision.
"""
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")


_logger = logging.getLogger("adcp.server")


Expand Down Expand Up @@ -312,7 +326,7 @@ def media_buy_response(
"media_buy_id": media_buy_id,
"packages": _serialize(packages),
"revision": revision if revision is not None else 1,
"confirmed_at": confirmed_at or datetime.now(timezone.utc).isoformat(),
"confirmed_at": confirmed_at or _rfc3339_now(),
"sandbox": sandbox,
}
if buyer_ref is not None:
Expand Down Expand Up @@ -407,7 +421,7 @@ def delivery_response(
currency: ISO 4217 currency code.
sandbox: Whether this is simulated data.
"""
now = datetime.now(timezone.utc).isoformat()
now = _rfc3339_now()
return {
"reporting_period": reporting_period or {"start": now, "end": now},
"media_buy_deliveries": media_buy_deliveries,
Expand Down Expand Up @@ -465,14 +479,14 @@ def list_creatives_response(
Timestamp defaults: every Creative item in the spec requires
``created_date`` and ``updated_date`` (ISO 8601 UTC). For any dict
item that omits either field, this helper fills it with the current
UTC timestamp (``datetime.now(timezone.utc).isoformat()``). Both
fields default to the same value when neither is provided, which
matches the intuitive meaning for a freshly-listed item. Explicit
caller-provided values are always preserved. Pydantic model items
are passed through ``_serialize`` unchanged — callers using typed
Creative models should set timestamps on the model.
UTC timestamp via :func:`_rfc3339_now` (Zulu suffix, RFC 3339).
Both fields default to the same value when neither is provided,
which matches the intuitive meaning for a freshly-listed item.
Explicit caller-provided values are always preserved. Pydantic
model items are passed through ``_serialize`` unchanged — callers
using typed Creative models should set timestamps on the model.
"""
now = datetime.now(timezone.utc).isoformat()
now = _rfc3339_now()
filled: list[Any] = []
for item in creatives:
if isinstance(item, dict):
Expand Down
6 changes: 3 additions & 3 deletions src/adcp/types/_ergonomic.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ def _apply_coercion() -> None:
PackageRequest,
"creatives",
Annotated[
list[CreativeAsset] | None,
Sequence[CreativeAsset] | None,
BeforeValidator(coerce_subclass_list(CreativeAsset)),
],
)
Expand All @@ -281,7 +281,7 @@ def _apply_coercion() -> None:
CreateMediaBuyRequest,
"packages",
Annotated[
list[PackageRequest] | None,
Sequence[PackageRequest] | None,
BeforeValidator(coerce_subclass_list(PackageRequest)),
],
)
Expand Down Expand Up @@ -383,7 +383,7 @@ def _apply_coercion() -> None:
ListCreativesResponse,
"creatives",
Annotated[
list[Creative],
Sequence[Creative],
BeforeValidator(coerce_subclass_list(Creative)),
],
)
Expand Down
2 changes: 1 addition & 1 deletion src/adcp/types/_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
DO NOT EDIT MANUALLY.

Generated from: https://github.com/adcontextprotocol/adcp/tree/main/schemas
Generation date: 2026-05-08 18:05:19 UTC
Generation date: 2026-05-19 21:40:28 UTC
"""

# ruff: noqa: E501, I001
Expand Down
57 changes: 40 additions & 17 deletions src/adcp/types/capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@

from __future__ import annotations

from typing import get_args as _get_args

from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import (
A2ui,
Adcp,
Expand Down Expand Up @@ -88,6 +90,9 @@
from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import (
Account as CapabilitiesAccount,
)
from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import (
Adcp as _Adcp,
)

# ``Capabilities`` (line 580 of the generated module) is the SI-block's
# inner ``capabilities`` field type — modalities / components / commerce
Expand All @@ -109,31 +114,49 @@
from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import (
Creative as CapabilitiesCreative,
)

# ``Features2`` is the codegen name for the ``Signals.features`` type
# (numbered because ``Features`` already names the media_buy features
# block at line 142 of the generated module). Surface under a stable
# adopter-facing name so signals declarations read cleanly.
from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import (
Features2 as SignalsFeatures,
)

# ``Idempotency`` ships as a ``oneOf`` on the wire (``IdempotencySupported``
# vs ``IdempotencyUnsupported``) — the codegen names them ``Idempotency``
# and ``Idempotency3`` (with the numbered variant covering the
# ``supported: false`` arm). Surface the union halves under stable
# semantic names so adopters can construct either side without remembering
# which numbered variant is which.
from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import (
Idempotency as IdempotencySupported,
)
from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import (
Idempotency3 as IdempotencyUnsupported,
MediaBuy as CapabilitiesMediaBuy,
)
from adcp.types.generated_poc.bundled.protocol.get_adcp_capabilities_response import (
MediaBuy as CapabilitiesMediaBuy,
Signals as _Signals,
)

# ``Signals.features`` and the unsupported arm of the ``Adcp.idempotency``
# discriminated union are inline schemas the codegen materializes under
# numbered class names (``Features<N>`` / ``Idempotency<N>``). Those
# numbers are not stable across regens: ``datamodel-code-generator``
# 0.56.1 assigns them from a global counter whose traversal order shifts
# with both upstream schema layout AND filesystem-iteration order
# (APFS-on-macOS vs ext4-on-Linux), so the same pinned generator produces
# ``Features1`` in one environment and ``Features2`` in another. Reach
# the classes via their parents' field annotation — both ``Signals`` and
# ``Adcp`` are stable wire-spec class names, and the field annotations
# carry the union arm types directly.
_signals_features_arms = [
arm for arm in _get_args(_Signals.model_fields["features"].annotation) if arm is not type(None)
]
if len(_signals_features_arms) != 1:
raise RuntimeError(
"capabilities: Signals.features annotation lost its concrete type "
f"(got {_signals_features_arms!r})"
)
SignalsFeatures: type = _signals_features_arms[0]

_idempotency_arms = [
arm
for arm in _get_args(_Adcp.model_fields["idempotency"].annotation)
if arm is not IdempotencySupported and arm is not type(None)
]
if len(_idempotency_arms) != 1:
raise RuntimeError(
"capabilities: expected exactly one non-supported Idempotency arm, "
f"got {_idempotency_arms!r}"
)
IdempotencyUnsupported: type = _idempotency_arms[0]

__all__ = [
"A2ui",
"Adcp",
Expand Down
2 changes: 1 addition & 1 deletion src/adcp/types/generated_poc/enums/daast_tracking_event.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# generated by datamodel-codegen:
# filename: enums/daast_tracking_event.json
# timestamp: 2026-05-02T19:36:29+00:00
# timestamp: 2026-05-19T21:40:22+00:00

from __future__ import annotations

Expand Down
2 changes: 1 addition & 1 deletion src/adcp/types/generated_poc/enums/daast_version.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# generated by datamodel-codegen:
# filename: enums/daast_version.json
# timestamp: 2026-05-02T19:36:29+00:00
# timestamp: 2026-05-19T21:40:22+00:00

from __future__ import annotations

Expand Down
2 changes: 1 addition & 1 deletion src/adcp/types/generated_poc/enums/forecastable_metric.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# generated by datamodel-codegen:
# filename: enums/forecastable_metric.json
# timestamp: 2026-05-02T19:36:29+00:00
# timestamp: 2026-05-19T21:40:22+00:00

from __future__ import annotations

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# generated by datamodel-codegen:
# filename: media_buy/get_media_buy_delivery_response.json
# timestamp: 2026-05-02T19:36:29+00:00
# timestamp: 2026-05-19T21:40:22+00:00

from __future__ import annotations

from collections.abc import Sequence
from enum import Enum
from typing import Annotated, Any
from collections.abc import Sequence

from adcp.types.base import AdCPBaseModel
from pydantic import AwareDatetime, ConfigDict, Field
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# generated by datamodel-codegen:
# filename: media_buy/get_media_buys_response.json
# timestamp: 2026-05-02T19:36:29+00:00
# timestamp: 2026-05-19T21:40:22+00:00

from __future__ import annotations

from collections.abc import Sequence
from enum import Enum
from typing import Annotated
from collections.abc import Sequence

from adcp.types.base import AdCPBaseModel
from pydantic import AwareDatetime, ConfigDict, Field
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
# generated by datamodel-codegen:
# filename: media_buy/update_media_buy_response.json
# timestamp: 2026-05-02T19:36:29+00:00
# timestamp: 2026-05-19T21:40:22+00:00

from __future__ import annotations

from collections.abc import Sequence
from typing import Annotated
from collections.abc import Sequence

from adcp.types.base import AdCPBaseModel
from pydantic import AwareDatetime, ConfigDict, Field
Expand Down
Loading