Help us improve
Share bugs, ideas, or general feedback.
From oagen
Creates a wire-level HTTP smoke test script for a new SDK language, verifying request/response parity against OpenAPI specs and live API baselines.
npx claudepluginhub workos/oagenHow this skill is triggered — by the user, by Claude, or both
Slash command
/oagen:generate-smoke-testThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Create a self-contained smoke test script for a new SDK language that captures wire-level HTTP request/response pairs and compares them against the raw API baseline or spec-only baseline.
Runs a generate-verify loop to iteratively fix an emitter until smoke tests pass. Use after smoke tests are created or when debugging smoke test failures.
Generates type-safe client SDKs in TypeScript, Python, Go, Java from OpenAPI specs with auth, retries, pagination, and tests.
Initializes new SDK projects from OpenAPI specs using speakeasy quickstart command. Targets TypeScript, Python, Go, Java, C#, PHP, Ruby, Kotlin, Terraform.
Share bugs, ideas, or general feedback.
Create a self-contained smoke test script for a new SDK language that captures wire-level HTTP request/response pairs and compares them against the raw API baseline or spec-only baseline.
Each language's smoke test is a single file: smoke/sdk-{lang}.ts in the emitter project. It uses the target language's native HTTP interception to capture what the SDK actually sends over the wire, then outputs SmokeResults JSON. The diff tool compares this against a baseline and reports mismatches by severity.
It imports shared infrastructure from @workos/oagen/smoke and implements language-specific parts inline.
Emitter project: Use the project argument if provided, otherwise use AskUserQuestion.
oagen core: Check for node_modules/@workos/oagen/, or src/engine/types.ts in the current directory, otherwise ask.
Read and understand these files before writing any code:
@workos/oagen/smoke (exported from {oagen}/scripts/smoke/shared.ts) — parseSpec(), planOperations(), generatePayload() / generateCamelPayload(), generateQueryParams() / generateCamelQueryParams(), IdRegistry, getExpectedStatusCodes() / isUnexpectedStatus(), resolvePath(), CapturedExchange, SmokeResults
Existing smoke scripts (read via subagent) — Use the Agent tool with subagent_type: Explore to study the closest existing smoke script. This keeps ~630+ lines of reference source out of the main context:
Read the smoke scripts in
{emitterProject}/smoke/(e.g.,sdk-node.ts,sdk-ruby.ts). For the closest one to {language}, return: purpose, exported function signatures, structural pattern (setup → intercept → iterate → capture → cleanup), key abstractions (MethodResolution, SERVICE_MAP, interceptFetch, buildArgs), and which parts are language-specific vs. reusable. Be concise.
The Node script is the canonical reference (~630 lines) covering:
MethodResolution interface and 4-tier resolution: manifest, exact match, CRUD prefix, keyword fuzzySERVICE_MAP — IR service names to SDK property namesinterceptFetch() — HTTP interception with provenance capturebuildArgs() — argument construction from IR operations{oagen}/scripts/smoke/raw.ts — raw baseline script
{oagen}/scripts/smoke/diff.ts — diff tool severity levels (CRITICAL/WARNING/INFO)
docs/sdk-architecture/{language}.md (in emitter project) — target language patterns and HTTP client
Determine the location of the OpenAPI spec before doing anything:
spec argument was provided, use that.AskUserQuestion: "Where is your OpenAPI spec located? (absolute or relative path, e.g. ../openapi.yaml)"Store it as spec.
Choose the interception mechanism for the target language. It must capture the raw request (method, path, query, body) and response (status, body), storing both in a currentCapture variable (~20-30 lines):
| Language | Mechanism |
|---|---|
| Node | Patch globalThis.fetch |
| Ruby | WebMock stub_request or monkey-patch Net::HTTP |
| Python | responses, respx (httpx), or unittest.mock.patch |
| Go | Custom http.RoundTripper |
| Java/Kotlin | OkHttp Interceptor |
Map IR service names to SDK resource accessors. There are two sources of truth depending on the SDK:
For emitter-generated SDKs, the operations field in .oagen-manifest.json (produced by the emitter's buildOperationsMap hook) contains the authoritative service field for every operation. The smoke test should load this manifest and use its service mappings. The SERVICE_MAP only needs fallback entries for services not covered by the manifest.
If no operations map exists, warn the user: the emitter should implement buildOperationsMap. Without it, most operations will be skipped because heuristic method resolution fails on disambiguated names.
If sdk_path points to a hand-written SDK (not emitter-generated), delegate the exploration to a subagent to discover accessor names:
Use the Agent tool with subagent_type: Explore and a prompt like:
Explore the SDK at
{sdk_path}. Focus specifically on: the main client class, its resource accessor properties/methods, and how they map to domain names. Return a mapping of resource names to accessor names (e.g., Organizations → "organizations", Connections → "sso"). Only report what you actually find.
Then build the mapping:
const SERVICE_MAP: Record<string, string> = {
Organizations: "organizations",
Connections: "sso",
};
Each language's SDK will have different accessor names — discover them by reading the SDK's client class.
Adapt the 4-tier resolution to the target language's naming conventions (Ruby/Python: snake_case, Go: PascalCase, Node: camelCase):
Each resolution records provenance metadata (ExchangeProvenance) so findings can be traced back to the resolution path.
Build SDK call arguments from IR operations. Reference buildArgs() in existing smoke scripts and adapt to the target language's calling convention. See references/implementation-patterns.md for the concrete branching template covering all argument patterns (positional, payload-only, query-only, complex, idempotent POST).
smoke/sdk-{lang}.tsCreate the script in the emitter project. See references/implementation-patterns.md for the full structural template. The script follows this flow:
@workos/oagen/smokemain(): parse spec → load operations map → init SDK → iterate planOperations() groups → resolve method (Step 3) → build args (Step 4) → call SDK → capture exchange → extract IDs via ids.extractAndStore() → track POST creates for cleanupsmoke-results-sdk-{lang}.jsonAdd the smoke runner path to the plugin bundle export (e.g., src/plugin.ts):
// src/plugin.ts
export const workosEmittersPlugin = {
smokeRunners: {
// existing runners...
{language}: path.join(smokeDir, 'sdk-{language}.ts'),
},
// ...
};
The consumer project's oagen.config.ts imports the plugin bundle, so the new smoke runner is automatically available.
# Offline validation against spec baseline (no API key needed)
oagen verify --lang {lang} --output {sdk-path} --spec <spec>
# Live validation against real API (requires API key and raw baseline)
oagen verify --lang {lang} --output {sdk-path} --raw-results smoke-results-raw.json
During initial setup, run oagen generate then the smoke test until skips are minimized:
oagen generate --lang {lang} --output {sdk-path} --spec {spec} --namespace {ns}
oagen verify --lang {lang} --output {sdk-path} --spec {spec}
If many operations are skipped with "No matching SDK method", verify the operations map is present (see Step 2).
| Exit | Meaning | Output | Action |
|---|---|---|---|
| 0 | Clean | — | Done |
| 1 | Findings | smoke-diff-findings.json | Read findings, fix emitter/smoke script |
| 2 | Compile error | smoke-compile-errors.json | Read errors, fix emitter |
See scripts/smoke/README.md (in oagen core) for the full remediation guide. See Workflows for the overall workflow diagram.
This skill produces, in the emitter project:
smoke/sdk-{language}.ts — self-contained smoke test scriptsrc/plugin.ts) with the smoke runner path registered