Skip to content

Codegen: Zod schemas for FormatAssetSlot, and tighten Format.assets typing #1652

@bokelley

Description

@bokelley

Summary

Two related codegen gaps around format asset slots, both surfacing as friction for SDK consumers that want to validate or type-narrow Format.assets from creative agents:

  1. Zod schemas for slot shapes are missing. The codegen produces *AssetRequirementsSchema (image, video, text, …) at dist/lib/types/schemas.generated.d.ts, and TS types for the wrapping slot shapes (IndividualAssetSlot, RepeatableGroupSlot, FormatAssetSlot) at dist/lib/types/format-asset-slots.d.ts. But it doesn't generate Zod for the slot shapes — there's no IndividualAssetSlotSchema, RepeatableGroupSlotSchema, or FormatAssetSlotSchema.

  2. Format.assets is typed loosely. Per dist/lib/types/format-asset-slots.d.ts:

    export interface BaseIndividualAssetSlot {
      item_type: 'individual';
      asset_id: string;
      asset_role?: string;
      required: boolean;
      overlays?: Overlay[];
    }

    Whereas the wire format (and the proper IndividualAssetSlot discriminated union in the same file, line 77) carries asset_type plus a discriminated requirements. If Format.assets were typed as FormatAssetSlot[], consumers would get proper narrowing.

Why this matters

Today, consumers reading agent.listCreativeFormats() get back Format objects whose assets[] are typed as the narrow base. The fields are present in JSON but not in TS types. We work around this with casts:

// from a downstream consumer, paraphrased
const assetList = f.assets ?? (rawFormat.assets_required as typeof f.assets)
return assetList.map((a) => {
  const raw = a as unknown as Record<string, unknown>
  return {
    asset_type: String(raw.asset_type ?? raw.type ?? ''),
    description: String(raw.description ?? ''),
    requirements: raw.requirements as Record<string, unknown>,
    // ...
  }
})

Two costs:

  1. No runtime validation of slot shape. Without a slot-level Zod schema, consumers either build their own (forking the type definitions) or trust the wire format. We'd build our own; we'd rather not.
  2. Lost type narrowing. A consumer that reads slot.asset_type === "text" should get slot.requirements: TextAssetRequirements | undefined for free. Today they have to cast.

Proposed fix

(A) Generate Zod for slot shapes

Extend the JSON-Schema → Zod codegen (or whatever step produces schemas.generated.ts) to cover:

  • BaseIndividualAssetSlotSchema, BaseGroupAssetSlotSchema
  • IndividualImageAssetSlotSchema, IndividualVideoAssetSlotSchema, … (all 14 individual variants)
  • IndividualAssetSlotSchema — the discriminated union over asset_type
  • GroupAssetSlotSchema, RepeatableGroupSlotSchema
  • FormatAssetSlotSchema = IndividualAssetSlotSchema | RepeatableGroupSlotSchema

These shapes are already declared as TS interfaces in format-asset-slots.d.ts; the Zod codegen just needs to follow.

(B) Tighten Format.assets typing

Change Format.assets (in tools.generated) from the narrow base shape to FormatAssetSlot[]. Same wire format, more honest type. Eliminates the cast pattern shown above.

Workarounds in the meantime

We'll likely hand-roll a Zod discriminated union locally that satisfies IndividualAssetSlot from the SDK types (using satisfies z.ZodType<IndividualAssetSlot> to anchor compile-time alignment). When (A) lands, we delete ours and import yours.

Cross-links

Versions

@adcp/sdk@6.5.0. Reproducible by inspecting dist/lib/types/format-asset-slots.d.ts (TS-only types) vs dist/lib/types/schemas.generated.d.ts (no slot Zod schemas).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions