Skip to content

feat(contracts,api,upload,content): generate /openapi.json from Zod schemas#15

Merged
isuttell merged 3 commits into
mainfrom
agents/openapi-zod
May 22, 2026
Merged

feat(contracts,api,upload,content): generate /openapi.json from Zod schemas#15
isuttell merged 3 commits into
mainfrom
agents/openapi-zod

Conversation

@isuttell

@isuttell isuttell commented May 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Generate /openapi.json for api, upload, and content Workers from the Zod contracts in packages/contracts using @asteasolutions/zod-to-openapi (the peer that backs @hono/zod-openapi; the Workers ship plain Hono routes so the generator is used directly).
  • New pnpm openapi:check task (wired into pnpm verify and Turbo) diffs every served document against goldens under packages/contracts/openapi/; pnpm openapi:write refreshes them.
  • Forced-drift smoke confirmed: mutating any golden flips openapi:check to exit 1.

Implementation notes

  • packages/contracts/src/zod.ts extends Zod with .openapi(...) before any schema is constructed (Zod 4 requirement). Every schema module routes through it so the order is deterministic.
  • One generator file per Worker under packages/contracts/src/openapi/ keeps each file under 300 LOC.
  • The hand-written openApiDocument() helpers in all three Workers (~200 LOC each) are deleted in favor of a one-line call into the contract builder.
  • 429 responses now use a dedicated RateLimitErrorEnvelope whose error.code enum is narrowed to rate_limited_actor / rate_limited_workspace only (CodeRabbit major).
  • The public Agent View route no longer advertises 401 (CodeRabbit major).

CodeRabbit triage

coderabbit review --agent returned 8 findings against this branch.

# Severity Disposition
1 trivial Fixed: dropped the zod-setup re-export; consumers import z from ../zod.js directly.
2 trivial Fixed: inlined params and fileBytesResponse helpers in content.ts.
3 trivial Fixed: inlined params / fileBytesResponse (same finding as #2).
4 major Deferred: page_info.next_cursor exists in the existing contract but list routes don't accept cursor yet. Adding pagination is a route-layer change, out of scope for OpenAPI generation.
5 major Fixed: 429 now uses RateLimitErrorEnvelope with a narrowed error.code enum.
6 major Fixed: removed the bogus 401 response from the unauthenticated agentView.public route.
7 major Deferred: CreateApiKeyRequest requires workspace_id in body even when the path provides it. Pre-existing contract used by packages/api-client; changing it is a contract migration, not an OpenAPI generation task.
8 trivial Acknowledged: kept the as unknown as Record<string, unknown> cast. Workers return JSON via c.json(...), which wants Record<string, unknown>. Importing OpenAPIObject from openapi3-ts/oas31 would still require a cast at the call site, so the indirection isn't worth it.

Test plan

  • pnpm verify (60/60 Turbo tasks)
  • Forced-drift smoke: mutate packages/contracts/openapi/content.json then pnpm openapi:check exits 1; restore then exit 0.
  • CodeRabbit findings triaged.

Stacked on worktree-agent-a14b512aa4365cfc5 (PR #10) for the X-Request-Id + request_id plumbing.

Summary by CodeRabbit

Release Notes

  • New Features

    • API endpoints (/openapi.json) now serve auto-generated, up-to-date documentation for the API, Content, and Upload services.
  • Documentation

    • API specifications are now automatically generated from source and validated in CI to ensure documentation always reflects the actual API.
  • Chores

    • Enhanced verification workflow to include OpenAPI documentation checks.

Review Change Stack

@isuttell isuttell temporarily deployed to pr-preview-15 May 22, 2026 04:46 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

This PR refactors OpenAPI document generation across Agent Paste's API, Content, and Upload worker applications. Instead of maintaining hand-written OpenAPI schemas in each worker, the PR centralizes generation using Zod schemas from a shared contracts package. A new Zod wrapper enables OpenAPI metadata annotations on schemas. Three document builder functions generate OpenAPI 3.1 specifications dynamically, accepting environment-based server URLs. Golden JSON files serve as reference contracts. A CLI script validates that generated documents match golden files in CI. Each worker app now imports and calls the appropriate builder when serving /openapi.json, eliminating ~400 lines of duplicated schema definition code.

Sequence Diagram

sequenceDiagram
  participant Client
  participant Worker as API/Content/Upload Worker
  participant Handler as /openapi.json Handler
  participant Builder as buildApiOpenApiDocument
  participant Registry as OpenAPIRegistry
  participant Generator as OpenApiGeneratorV31
  Client->>Worker: GET /openapi.json
  Handler->>Builder: Call buildXxxOpenApiDocument(options)
  Builder->>Registry: Create registry
  Builder->>Registry: Register schemas, security, endpoints
  Builder->>Generator: Generate from registry
  Generator->>Builder: OpenAPI 3.1 Document
  Builder->>Handler: Return Record<string, unknown>
  Handler->>Client: 200 + OpenAPI JSON
Loading

Possibly related PRs

  • zaks-io/agent-paste#5: Overlaps with OpenAPI refactoring for apps/api and its 429 rate-limit response schema assertions, including Retry-After header and rate-limited actor details.

Poem

🐰 Schemas once scattered, now gathered as one,
Zod whispers OpenAPI—golden specs run,
Three workers align where golden files lead,
CI validates the truth that we need.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly summarizes the main change: OpenAPI documents are now generated from Zod schemas for three services (api, upload, content packages).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agents/openapi-zod

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

@isuttell isuttell force-pushed the worktree-agent-a14b512aa4365cfc5 branch from 3bad1b6 to 3afb7db Compare May 22, 2026 05:40
@isuttell isuttell force-pushed the agents/openapi-zod branch from 82a8182 to 7e9d4a5 Compare May 22, 2026 05:51
@isuttell isuttell temporarily deployed to pr-preview-15 May 22, 2026 05:51 — with GitHub Actions Inactive
@isuttell isuttell force-pushed the worktree-agent-a14b512aa4365cfc5 branch from 3afb7db to 1807c4e Compare May 22, 2026 05:53
@isuttell isuttell changed the base branch from worktree-agent-a14b512aa4365cfc5 to main May 22, 2026 05:56
isuttell and others added 3 commits May 21, 2026 22:56
…chemas

Replace hand-written OpenAPI documents in api/upload/content with
documents generated from the Zod schemas in `packages/contracts` using
`@asteasolutions/zod-to-openapi` (the peer that powers `@hono/zod-openapi`).

The generators live in `packages/contracts/src/openapi/` and emit one
document per Worker. `pnpm openapi:check` diffs every served document
against a checked-in golden under `packages/contracts/openapi/`; a
forced-drift smoke confirms the check fails on any mismatch.
`pnpm verify` now runs `openapi:check` so CI catches contract drift.

A central `packages/contracts/src/zod.ts` extends Zod with `.openapi()`
before any schema is constructed, which Zod 4 requires.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Drop the 401 response from the public Agent View route, which has no
  security requirement.
- Introduce a dedicated `RateLimitErrorEnvelope` schema so 429 responses
  only advertise `rate_limited_actor`/`rate_limited_workspace` codes
  instead of the full error code enum.
- Inline single-use helpers in the content generator and drop the
  pointless `zod-setup` re-export.

Deferred (recorded for the follow-up SDK work, not in scope for this PR):
list operations still surface `page_info.next_cursor` without a matching
`cursor` query parameter, and the admin api-key create body still mirrors
`workspace_id` from the path. Both are pre-existing contract shapes used
by `packages/api-client`; changing them is a contract migration, not an
OpenAPI generation task.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@isuttell isuttell force-pushed the agents/openapi-zod branch from 7e9d4a5 to dc7742f Compare May 22, 2026 05:57
@isuttell isuttell temporarily deployed to pr-preview-15 May 22, 2026 05:57 — with GitHub Actions Inactive
@isuttell isuttell merged commit d3c61e4 into main May 22, 2026
2 of 4 checks passed
@github-actions

Copy link
Copy Markdown

agent-paste PR preview resources were cleaned up. The pr-preview-${context.issue.number} environment is left in place; remove it from the GitHub UI if desired.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant