[FEATURE]: JSON Schema as Source of Truth for API Contracts
Feature hasn't been suggested before.
- [x] I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request
Proposal: JSON Schema as Source of Truth for API Contracts
Summary
Move API schema definitions from inline TypeScript/Zod code to formal JSON Schema files. Generate TypeScript types and Zod validators from these schemas. This improves API discoverability, enables client SDK generation, and provides versioning for API contracts.
Problem Statement
Currently, OpenCode's API contracts are defined inline within TypeScript source code using Zod:
// packages/opencode/src/provider/provider.ts
export const Info = z.object({
id: z.string(),
name: z.string(),
source: z.enum(["env", "config", "custom", "api"]),
// ... scattered across 500+ lines
})
This creates several issues:
- No single source of truth - Schema definitions are scattered across multiple files
- Hard to discover - External clients must read TypeScript source to understand the API
- No versioning - Breaking changes have no semantic versioning
- No client generation - Each client (desktop, SDK, integration) reverse-engineers the API
- Poor documentation - Schema docs are inline comments, not structured
- Difficult to validate - No way to validate JSON payloads against schemas at build time
Real-World Impact
I'm building a Tauri desktop client for OpenCode and had to:
- Read through 1000+ lines of TypeScript to understand
Provider.Infoschema - Document commit hashes and line numbers as "source of truth" references
- Manually translate Zod schemas to Protocol Buffers for type safety
- Risk breaking changes with every OpenCode update
This shouldn't be necessary for a modern API.
Proposed Solution
1. Create schema/ Directory with JSON Schema Definitions
opencode/
├── schema/
│ ├── provider.schema.json # Source of truth
│ ├── model.schema.json
│ ├── session.schema.json
│ ├── message.schema.json
│ ├── tool.schema.json
│ ├── agent.schema.json
│ └── auth.schema.json
2. Example: Provider Schema
schema/provider.schema.json:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://opencode.ai/schemas/v1/provider.json",
"title": "Provider",
"description": "AI model provider configuration and metadata",
"type": "object",
"required": ["id", "name", "source", "models"],
"properties": {
"id": {
"type": "string",
"description": "Unique provider identifier",
"examples": ["anthropic", "openai", "google"]
},
"name": {
"type": "string",
"description": "Human-readable provider name",
"examples": ["Anthropic", "OpenAI"]
},
"source": {
"type": "string",
"enum": ["env", "config", "custom", "api"],
"description": "How the provider was configured"
},
"env": {
"type": "array",
"items": { "type": "string" },
"description": "Environment variables used for authentication",
"examples": [["ANTHROPIC_API_KEY"]]
},
"options": {
"$ref": "#/definitions/ProviderOptions"
},
"models": {
"type": "object",
"additionalProperties": {
"$ref": "./model.schema.json"
}
}
},
"definitions": {
"ProviderOptions": {
"type": "object",
"properties": {
"baseURL": {
"type": "string",
"format": "uri",
"description": "API endpoint override"
},
"apiKey": {
"type": "string",
"description": "API authentication key"
},
"headers": {
"type": "object",
"additionalProperties": { "type": "string" },
"description": "Custom HTTP headers"
},
"timeout": {
"type": "integer",
"minimum": 0,
"description": "Request timeout in milliseconds"
}
}
}
}
}
3. Generate TypeScript Types and Zod Validators
Build process:
# Generate TypeScript interfaces from JSON Schema
json-schema-to-typescript schema/*.json --output src/generated/types/
# Generate Zod validators from JSON Schema
json-schema-to-zod schema/*.json --output src/generated/validators/
Auto-generated types (src/generated/types/provider.ts):
export interface Provider {
id: string;
name: string;
source: "env" | "config" | "custom" | "api";
env?: string[];
options?: ProviderOptions;
models: Record<string, Model>;
}
export interface ProviderOptions {
baseURL?: string;
apiKey?: string;
headers?: Record<string, string>;
timeout?: number;
}
Auto-generated validators (src/generated/validators/provider.ts):
import { z } from "zod";
export const ProviderOptionsSchema = z.object({
baseURL: z.string().url().optional(),
apiKey: z.string().optional(),
headers: z.record(z.string()).optional(),
timeout: z.number().int().min(0).optional(),
});
export const ProviderSchema = z.object({
id: z.string(),
name: z.string(),
source: z.enum(["env", "config", "custom", "api"]),
env: z.array(z.string()).optional(),
options: ProviderOptionsSchema.optional(),
models: z.record(ModelSchema),
});
4. Update Source Code to Use Generated Types
Before:
// packages/opencode/src/provider/provider.ts
export const Info = z.object({
id: z.string(),
name: z.string(),
// ... 50 lines of schema definition
})
export type Info = z.infer<typeof Info>
After:
// packages/opencode/src/provider/provider.ts
import type { Provider } from "../generated/types/provider"
import { ProviderSchema } from "../generated/validators/provider"
// Business logic uses generated types
export async function list(): Promise<Record<string, Provider>> {
// ... implementation
}
// API validation uses generated validators
export function validate(data: unknown): Provider {
return ProviderSchema.parse(data)
}
Zero runtime behavior changes. Pure refactor.
Benefits
For OpenCode Maintainers
-
Single source of truth - All schemas in one place (
schema/directory) - Better documentation - Structured, versionable schema docs
- Automatic validation - Catch schema bugs at build time
- VSCode integration - Auto-complete and validation in JSON Schema files
- No runtime changes - Generated code works exactly like hand-written code
- Industry standard - JSON Schema is used by OpenAPI, VSCode, npm, etc.
For External Developers
- API discovery - Read schemas without diving into TypeScript source
- Type-safe clients - Generate SDKs in any language (TypeScript, Python, Rust, C#)
- Version tracking - Know when breaking changes occur
- Better docs - Auto-generate API documentation from schemas
- Validation tools - Validate payloads against published schemas
For the Ecosystem
- Desktop clients - Can generate type-safe gRPC/REST clients
-
SDK packages - Auto-generate
@opencode/sdk-python, etc. - IDE extensions - Better autocomplete and validation
- Testing tools - Schema-based fuzz testing and validation
- Documentation sites - Auto-generated API reference docs
Implementation Plan
Phase 1: Proof of Concept (Week 1)
- [ ] Create
schema/provider.schema.json - [ ] Set up build tooling (
json-schema-to-typescript,json-schema-to-zod) - [ ] Generate types and validators
- [ ] Update
packages/opencode/src/provider/provider.tsto use generated code - [ ] Verify all existing tests pass (zero behavior changes)
- [ ] Create PR with PoC
Phase 2: Core Schemas (Weeks 2-4)
Migrate one schema per PR:
- [ ]
model.schema.json- Model metadata, capabilities, options - [ ]
session.schema.json- Session/conversation management - [ ]
message.schema.json- User/assistant messages - [ ]
tool.schema.json- Tool execution state - [ ]
agent.schema.json- Agent definitions - [ ]
auth.schema.json- Authentication
Phase 3: Tooling & Versioning (Week 5)
- [ ] Add semantic versioning to schemas (
v1.0.0,v1.1.0) - [ ] Set up schema validation in CI (catch breaking changes)
- [ ] Document schema contribution guidelines
- [ ] Add
npm run schema:validatecommand
Phase 4: Documentation & SDKs (Week 6+)
- [ ] Generate OpenAPI spec from JSON Schemas
- [ ] Auto-generate API documentation site
- [ ] Publish
@opencode/schemaspackage to npm - [ ] Create SDK generation guide for external developers
Migration Strategy
Gradual, Non-Breaking
- Add schemas alongside existing code - No immediate changes required
- Generate code during build - Transparent to contributors
- Migrate one domain at a time - Provider → Model → Session → etc.
- Keep Zod validators working - Auto-generated, zero behavior changes
- Tests prove equivalence - All existing tests must pass
Backward Compatibility
- Existing code continues to work during migration
- Generated Zod schemas are functionally identical to hand-written ones
- No API changes, no runtime changes
- Contributors can still modify schemas (just edit JSON instead of TypeScript)
Tooling
Required Dependencies (dev only)
{
"devDependencies": {
"json-schema-to-typescript": "^13.1.0",
"json-schema-to-zod": "^2.0.0",
"ajv": "^8.12.0",
"ajv-cli": "^5.0.0"
}
}
Build Scripts
{
"scripts": {
"schema:types": "json-schema-to-typescript schema/*.json -o src/generated/types/",
"schema:zod": "json-schema-to-zod schema/*.json -o src/generated/validators/",
"schema:validate": "ajv validate -s schema/**/*.json",
"schema:docs": "generate-schema-doc schema/ -o docs/api/",
"prebuild": "npm run schema:types && npm run schema:zod"
}
}
Auto-runs before every build. No manual intervention needed.
Examples of This Pattern in the Wild
Projects successfully using JSON Schema as source of truth:
- VSCode - Extension API schemas
-
npm -
package.jsonschema - GitHub Actions - Workflow schema
-
Docker Compose -
docker-compose.ymlschema - Kubernetes - CRD schemas
- OpenAPI/Swagger - Built on JSON Schema
This is an industry-standard approach, not experimental.
FAQ
Q: Won't this make contributions harder?
A: No. Contributors edit JSON instead of TypeScript. Both are human-readable. JSON Schema has better VSCode support (auto-complete, validation).
Q: What about runtime performance?
A: Zero impact. Generated Zod validators are identical to hand-written ones. No additional runtime overhead.
Q: Do we need to rewrite everything at once?
A: No. Migrate incrementally. Old code and new code coexist during transition.
Q: What if we need to change a schema?
A: Edit the JSON Schema file, run build, generated code updates automatically. Simpler than editing scattered TypeScript files.
Q: Why not just use TypeScript types?
A: TypeScript types don't exist at runtime. We need runtime validation (Zod). Also, TypeScript types aren't consumable by other languages (Python, Rust, C# clients).
Q: Why not Protocol Buffers?
A: Protobuf has a steep learning curve and "not JavaScript-native" stigma. JSON Schema is widely understood, used by OpenAPI, and has mature JS tooling.
Alternatives Considered
Alternative 1: Keep Current Approach
Pros:
- No migration effort
- Works today
Cons:
- External clients struggle
- No API versioning
- Poor discoverability
- Doesn't scale to ecosystem
Alternative 2: Protocol Buffers
Pros:
- Strong typing
- Efficient serialization
- Multi-language support
Cons:
- Steep learning curve
- Not JavaScript-native
- Resistance from JS community
- Adds build complexity (protoc)
Alternative 3: OpenAPI Only
Pros:
- Good for HTTP APIs
- Auto-generates docs
Cons:
- Doesn't cover internal models
- Still need runtime validators
- Harder to version individual schemas
Decision: JSON Schema is the best balance of power, simplicity, and JavaScript-native tooling.
Call to Action
I'm volunteering to do the implementation work:
- Create PoC PR - Provider schema migration (1-2 days)
- Gather feedback - Address concerns, iterate on approach
- Incremental migration - One schema per PR (1-2 weeks total)
- Documentation - Contribution guide, schema versioning docs
I believe this will make OpenCode's API more accessible and enable a healthier ecosystem of clients, SDKs, and integrations.
Related Work
I've already done significant schema reverse-engineering for a Tauri desktop client:
- Documented all schemas with commit hashes and line numbers
- Created Protocol Buffer definitions for type safety
- Identified discrepancies and undocumented fields
This proposal builds on that work and makes it available to the entire community.
Would the maintainers be open to this approach? I'm happy to answer questions and create a PoC PR if there's interest.