# oagen

oagen ([oʊweɪdd͡ʒɛn](https://ipa-reader.com/?text=o%CA%8Awe%C9%AAdd%CD%A1%CA%92%C9%9Bn)) is a framework for building custom SDK generators from OpenAPI 3.x specifications.

Its core job is narrow:

- parse an OpenAPI spec into a typed intermediate representation (IR)
- let language emitters turn that IR into files
- regenerate the SDK when the spec changes

More advanced workflows, such as preserving the public API of an existing SDK during a migration to generation, is also supported.

## Who This Is For

oagen is a fit if you:

- need more control than off-the-shelf generators give you
- want to build or maintain a custom emitter for one or more languages
- care about generated output being idiomatic for a specific SDK style

oagen is probably not a fit if you:

- just want a turnkey SDK generator with batteries included
- do not want to maintain emitter code
- do not need a reusable IR or generation framework

## Core Concept

oagen has a small core:

- **Parser**: OpenAPI 3.x -> `ApiSpec` IR
- **Emitter runtime**: `ApiSpec` -> `GeneratedFile[]`
- **Diffing**: compare spec versions and map changes to generated output

Advanced features such as API-surface extraction, compatibility overlays, smoke verification, and live-SDK integration are available, but they are optional. You can ignore them until you need them.

## Quickstart

Install the package:

```bash
npm install @workos/oagen
```

Inspect a spec:

```bash
oagen parse --spec openapi.yml
```

Create an emitter project:

```bash
oagen init --lang ruby --project ./my-emitter
cd ./my-emitter
```

Generate files with your emitter:

```bash
npm run sdk:generate -- --spec ../openapi.yml --namespace MyService
```

For the shortest end-to-end setup, see [Minimal Quickstart](docs/core/quickstart.md).

## The Core API

The default `@workos/oagen` entrypoint is intentionally focused on the framework core:

```ts
import {
  defaultSdkBehavior,
  mergeSdkBehavior,
  diffSpecs,
  generate,
  generateFiles,
  getEmitter,
  parseSpec,
  planOperation,
  registerEmitter,
  toCamelCase,
  toPascalCase,
  toSnakeCase,
} from "@workos/oagen";
import type {
  ApiSpec,
  SdkBehavior,
  Emitter,
  EmitterContext,
  GeneratedFile,
  Model,
  Enum,
  Service,
  OperationPlan,
} from "@workos/oagen";
```

Advanced compat and verification APIs are available through explicit subpaths:

```ts
import {
  buildOverlayLookup,
  patchOverlay,
  registerExtractor,
} from "@workos/oagen/compat";
import { runCompatCheck, runOverlayRetryLoop } from "@workos/oagen/verify";
```

## Building an Emitter

Emitters are pure functions over the IR. They receive typed IR nodes and return `GeneratedFile[]`.

```ts
import type { Emitter } from "@workos/oagen";

const myEmitter: Emitter = {
  language: "go",
  generateModels: (models, ctx) => [
    /* ... */
  ],
  generateEnums: (enums, ctx) => [
    /* ... */
  ],
  generateResources: (services, ctx) => [
    /* ... */
  ],
  generateClient: (spec, ctx) => [
    /* ... */
  ],
  generateErrors: () => [],
  generateTests: () => [],
  fileHeader: () => "// Auto-generated by oagen. Do not edit.",
};
```

Start with:

- [Reference Emitter](examples/reference-emitter/) — a working TypeScript emitter with tests against a GitHub-flavored fixture spec
- [Minimal Quickstart](docs/core/quickstart.md)
- [Emitter Contract](docs/architecture/emitter-contract.md)
- [IR Type System Reference](docs/architecture/ir-types.md)

## SDK Behavior

`ApiSpec.sdk` contains language-agnostic runtime policies — retry logic, error mapping, telemetry, pagination delays, User-Agent construction, and more. It is always populated (via `defaultSdkBehavior()` during parsing).

Emitters read policy from `ctx.spec.sdk` instead of hardcoding values:

```ts
function generateHttpClient(ctx: EmitterContext) {
  const sdk = ctx.spec.sdk;
  const retryCodes = sdk.retry.retryableStatusCodes; // [429, 500, 502, 503, 504]
  const maxRetries = sdk.retry.maxRetries; // 3
  const backoff = sdk.retry.backoff; // { initialDelay: 1, multiplier: 2, maxDelay: 30, jitterFactor: 0.5 }
  // ...generate code using these values
}
```

Override defaults per-SDK via `oagen.config.ts`:

```ts
// oagen.config.ts — Python SDK overrides
export default {
  sdkBehavior: {
    retry: { backoff: { initialDelay: 0.5, maxDelay: 8.0 } },
    timeout: {
      defaultTimeoutSeconds: 30,
      timeoutEnvVar: "SDK_REQUEST_TIMEOUT",
    },
    pagination: { autoPageDelayMs: 0 },
  },
};
```

See [`src/ir/sdk-behavior.ts`](src/ir/sdk-behavior.ts) for all interfaces and default values.

## Operation Resolution

`resolveOperations(spec, hints?, mountRules?)` derives method names and mount targets for every operation in the spec. The algorithm produces a snake_case name from the HTTP method and path, then applies optional overrides from a hint map.

Emitters consume `ctx.resolvedOperations` instead of computing names independently, ensuring all SDKs use the same method names (converted to each language's convention).

Configure hints and mount rules in `oagen.config.ts`:

```ts
export default {
  operationHints: {
    'GET /sso/authorize': { name: 'get_authorization_url' },
    'POST /user_management/authenticate': {
      split: [
        { name: 'authenticate_with_password', targetVariant: 'PasswordRequest', ... },
      ],
    },
  },
  mountRules: {
    Connections: 'SSO',           // All Connections ops mount on SSO
    DirectoryGroups: 'DirectorySync',
  },
  modelHints: {
    // Pin a model's placement when "first service to reference it wins"
    // would otherwise drift as the spec evolves.
    User: 'UserManagementUsers',
  },
};
```

Review resolved names with `oagen resolve`:

```bash
oagen resolve --spec openapi.yml --format table   # Markdown review table
oagen resolve --spec openapi.yml --format json     # JSON for programmatic use
```

See [`src/ir/operation-hints.ts`](src/ir/operation-hints.ts) for types and [`docs/architecture/ir-types.md`](docs/architecture/ir-types.md) for the full reference.

## Commands

| Command          | Purpose                                             |
| ---------------- | --------------------------------------------------- |
| `oagen parse`    | Parse a spec and print IR JSON                      |
| `oagen init`     | Scaffold an emitter project                         |
| `oagen generate` | Run a registered emitter                            |
| `oagen resolve`  | Review resolved operation names (table or JSON)     |
| `oagen diff`     | Compare two specs and output a diff report          |
| `oagen extract`  | Advanced: extract an SDK API surface for compat use |
| `oagen verify`   | Advanced: smoke-test output and run compat checks   |

See [CLI Reference](docs/cli.md).

## Documentation

- [Docs Overview](docs/index.md)
- [Core Docs](docs/core/index.md)
- [Advanced Docs](docs/advanced/index.md)
- [Contributor Docs](docs/contributor/index.md)
- [Public API Policy](docs/contributor/public-api.md)
- [Versioning and Migration](docs/contributor/versioning.md)

## Advanced Workflows

oagen also includes tooling for a more opinionated migration workflow:

- extract the public API of an existing SDK
- generate a replacement while preserving names and exports
- verify the generated SDK against smoke tests and compatibility checks
- integrate generated files into a live SDK tree

Those workflows are documented separately because they are not required to use the core framework:

- [Workflows](docs/architecture/workflows.md)
- [Extractor Contract](docs/architecture/extractor-contract.md)
- [CLI Reference](docs/cli.md)

## AI / Plugin Tooling

This repo ships with Claude Code plugin assets and skills for scaffold-and-verify workflows. The framework is usable without any agent tooling.

If you need them, start here:

- [Agent Docs](docs/agents/architecture.md)
- [Emitter Agent Docs](docs/agents/emitters.md)
- [Testing Agent Docs](docs/agents/testing.md)

## Development

```bash
npm install
npm run build
npm test
npm run typecheck
```
