Skip to content

Refactor: consolidate case-conversion into one Zod-backed boundary serializer (keep camelCase-internal / snake-wire) #1564

Description

@christso

Summary

Keep agentv's documented architecture — camelCase TypeScript internals, snake_case persisted wire (YAML/JSONL) — but eliminate duplication and edge-case drift by consolidating the conversion layer into a single, well-tested, Zod-backed boundary serializer.

Decision reversal: NOT snake_case-native

An earlier idea was to drop the converter and make types snake_case-native (the OpenAI / Stripe / Anthropic-SDK pattern). That pattern fits single-provider SDKs whose TS types are their HTTP API surface (Stainless code-gens snake_case interface keys; type-safety is compile-time only, no runtime conversion).

agentv is different: it's a multi-provider abstraction (claude, codex, copilot, pi, vscode, agentv, cli, function, replay, mock) that normalizes many provider shapes into one canonical internal model. That's the Vercel AI SDK case, where a canonical camelCase internal model + conversion at the wire is the correct architecture. The snake_case wire exists for Python-ecosystem / JSONL portability. The split is already a documented contract (packages/core/src/evaluation/trace.ts:15-17), and Zod is already in use.

Actual problem: 4 duplicated converters that can drift

  • packages/sdk/src/case-conversion.ts
  • packages/core/src/evaluation/case-conversion.ts
  • apps/cli/src/utils/case-conversion.ts
  • inline copy in packages/core/src/evaluation/run-artifacts.ts (~line 1818)

Each can diverge on edge cases: acronyms (HTTPStatus), numeric boundaries (top_ptopP), and the uppercase proper-noun tool-name exception (Read, Edit).

Goal

  1. One shared, exhaustively-tested converter (single source of truth) imported by SDK, core, and CLI; delete the duplicates and the inline run-artifacts copy.
  2. Zod-backed wire boundary: canonical camelCase schemas + a typed snake transform on serialize/parse, so conversion is validated rather than a stringly-typed deep walk.
  3. Preserve byte-compatible JSONL/artifact output and the camelCase-internal / snake-wire contract.

Non-goals

  • Changing grader-author-facing field casing (stays camelCase internal view).
  • Removing the conversion boundary (it's required for multi-provider normalization + Python wire).

Tracking

Beads epic av-wwu5 (children av-wwu5.1 ADR/inventory → .2 SDK → .3 Core+Zod → .4 CLI → .5 validation).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions