Skip to content

feat(client-generator): agent-friendly generators — python, go, php, cli + neutral authoring toolkit and eject workflow - #3016

Open
Marshevskyy wants to merge 193 commits into
mainfrom
feat/agent-friendly
Open

feat(client-generator): agent-friendly generators — python, go, php, cli + neutral authoring toolkit and eject workflow#3016
Marshevskyy wants to merge 193 commits into
mainfrom
feat/agent-friendly

Conversation

@Marshevskyy

@Marshevskyy Marshevskyy commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What this adds

Client generation for languages beyond TypeScript, built so that both we and our users extend it with AI agents driven by per-generator design skills.

New built-in generators

  • python — self-contained client over httpx: typed dataclasses, sync + async clients, pagination iterators, SSE, multipart, retries, discriminated-union decode via a DISCRIMINATORS registry.
  • go — stdlib-only single file: structs with json tags, typed-const enums, (T, error) methods, context.Context, range-over-func pagination, SSE. goPackage sets the package clause.
  • php — PHP ≥ 8.1 over the curl extension, zero Composer dependencies: promoted-constructor models, native enums, named-argument methods, Generator-based pagination, SSE over a curl_multi pump.
  • cli — a bin-ready command-line interface over the sdk. It validates requests by default: selecting cli pulls in the generators it needs (sdk and zod), so nothing extra has to be listed. binName names the command.
  • cli-docs — the Markdown reference for that CLI (<stem>.cli.md): usage, global flags, credential variables, exit codes, and every command with its arguments and flags. It renders from the same command table the CLI dispatches on, so the page cannot drift from the tool.

All three SDK languages have parity on auth (bearer/basic/apiKey with token providers), retries with Retry-After + jittered backoff, timeouts, idempotency keys, middleware, pagination, SSE, multipart, binary downloads, templated-server helpers (Servers class / <Name>URL functions), and response-header envelopes (<op>WithHeaders variants mirroring the TS { envelope: true } option from #3002).

The authoring model

  • One way to author a generator: print text with the language-neutral toolkit exported from the package root — Printer (indentation-aware source-text builder), naming (casing, identifierFor, RESERVED_WORDS), schema semantics (flattenAllOf, discriminatorCases, nullability, enumValues, headerCoerceType, schemaAtPointer), paginationRuleFor, and NotSupportedError for rejecting an option the generator can't honor. A dogfooding guard test pins that the built-in language generators use nothing else.
  • No AST toolkit and no typescript dependency in the authoring path. The TypeScript emitters render source text like everything else; the ts.factory exports are gone. typescript remains an optional peer needed only to bake a --setup module, the one place we parse TypeScript.
  • Generators declare their own options as a JSON Schema subset, validated before run (unknown key, wrong type, value outside an enum, missing required key) with defaults applied. Publishers set them under client.options.<generator>.
  • Skill-first development: each generator lives in its own folder with a design skill; changing a generator means changing the skill first, then making the code match (guard tests enforce presence and pin the ejected copies).

Eject workflow

redocly eject-generator <name> vendors any built-in generator into the repo as an editable .mjs. A language generator ships as its own source; a TypeScript generator ships bundled with the emitters it uses (unminified, one comment per source module), so sdk, zod, mock, swr, tanstack-query, transformers, cli, and cli-docs are ejectable too. An ejected-unmodified generator produces byte-identical output, proven in e2e.

Eject also:

  • writes the design as an agent skill (.claude/skills/<name>-generator/SKILL.md) plus the shared authoring skill, where agents auto-load them, and leaves a short pointer beside the code;
  • wires itself up — records @redocly/client-generator in devDependencies and adds the entry to client.generators, editing the config text so comments survive;
  • supports --update three-way merges with no committed snapshot: the merge base is the version recorded in the ejected file's own header, fetched from the registry when it differs from the installed one.

Compatibility

Generator compatibility is the package version under semver, not an invented number: a generator declares requiresGenerator (^1.2.0, ~1.2.0, >=1.2.0, or an exact version), and a CLI outside that range says which version it ships, which the generator needs, and how to reconcile them. Ejected generators record it automatically. Custom generators also run behind a validated contract: load-time shape validation, output-path containment (no writes outside --output), and run() result validation. An IR-shape snapshot test forces the "additive or breaking?" question on any model change. Failures are attributable (Generator "<name>" failed: …) and categorized in telemetry along with eject/update outcomes (coarse categories only — never file contents, paths, or user-chosen names).

Verification

  • Real-world bars in e2e: every generator's output from the Rebilly and pinned-SHA GitHub descriptions passes strict tsc / py_compile+import / go build+go vet+gofmt / php -l+require (tests/e2e/generate-client/large-descriptions.test.ts).
  • Client generation has its own test suite and CI job: npm run generators runs the client-generator unit tests plus tests/e2e/generate-client, sharded two ways, so a growing set of compiled-language bars cannot slow the job everything else shares. npm run e2e covers everything else.
  • Runnable examples per language, plus ejected-generator and CLI examples under tests/e2e/generate-client/examples/.
  • The PHP and Python outputs were gap-checked against a real production SDK migration target, and the whole set was re-run against Replay Admin and Rebilly Core after each round of review feedback.

Docs

New command page (eject-generator), a customize-client-generation guide, per-language usage notes with one table for the differences each language forces, and configuration reference updates (goPackage, binName, options, codeSamples).

Note for reviewers

The per-operation pagination extension is now x-redoclyPagination (camelCase, like every other Redocly extension). A description that still declares x-redocly-pagination silently loses its pagination rule — called out in the changeset.

Check yourself

  • This PR follows the contributing guide
  • All new/updated code is covered by tests
  • Core code changed? - Tested with other Redocly products (internal contributions only)
  • New package installed? - Tested in different environments (browser/node)
  • Documentation update has been considered

Security

  • The security impact of the change has been considered
  • Code follows company security practices and guidelines

Note

High Risk
Large experimental surface with breaking config/extension renames and new generated runtime behavior; misconfigured pagination or generator names can fail silently or at generation time across languages.

Overview
Expands client generation beyond TypeScript with built-in python, go, php, cli, cli-docs, and sdk-docs generators, a language-neutral authoring toolkit (per-generator JSON Schema options, requiresGenerator compatibility), and redocly eject-generator to vendor any built-in generator plus agent skills into the repo with config wiring and --update three-way merges.

Breaking: the default TypeScript generator is now typescript (not sdk); pagination operations must use x-redoclyPagination — the old x-redocly-pagination name is no longer read.

Adds client.cliOutput / binName / goPackage config, codeSamples overlays, and extended docs for CLI composition and SDK usage. CI splits npm run client-generators (unit + generate-client e2e with Python/Go/PHP toolchains, sharded) from the main e2e job; CLI build copies eject-assets. Telemetry records coarse generate-client / eject-generator usage without paths or user-chosen names.

Reviewed by Cursor Bugbot for commit 2a46d21. Bugbot is set up for automated code reviews on this repo. Configure here.

@changeset-bot

changeset-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2a46d21

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@redocly/client-generator Minor
@redocly/cli Minor
@redocly/openapi-core Minor
@redocly/respect-core Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment thread packages/client-generator/src/emitters/mock.ts Fixed
Comment thread packages/client-generator/src/emitters/mock.ts Fixed
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 78.12% (🎯 77%) 13502 / 17282
🔵 Statements 77.96% (🎯 77%) 14512 / 18613
🔵 Functions 81.37% (🎯 81%) 2717 / 3339
🔵 Branches 71.13% (🎯 71%) 9935 / 13966
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/cli/src/types.ts 100% 100% 100% 100%
packages/cli/src/wrapper.ts 84.21% 56.81% 100% 83.92% 69, 89-91, 123-130
packages/cli/src/commands/eject-generator.ts 47.13% 39.39% 50% 48.19% 74-122, 159-162, 178, 197, 211, 212-295, 316, 337, 352-354, 366, 368, 374, 380, 418-489, 529-579
packages/cli/src/commands/generate-client.ts 24.29% 17.24% 8.33% 23.76% 52-290
packages/cli/src/utils/client-generator-telemetry.ts 100% 100% 100% 100%
packages/cli/src/utils/telemetry.ts 93.28% 72.51% 94.44% 93.93% 74, 79, 302, 338, 351-352, 437, 442
packages/client-generator/src/generate.ts 100% 100% 100% 100%
packages/client-generator/src/index.ts 100% 100% 100% 100%
packages/client-generator/src/pipeline.ts 95.45% 84.44% 100% 96.87% 73-74, 130
packages/client-generator/src/plugin.ts 100% 100% 100% 100%
packages/client-generator/src/authoring/index.ts 100% 100% 100% 100%
packages/client-generator/src/authoring/naming.ts 100% 63.63% 100% 100%
packages/client-generator/src/authoring/pagination.ts 100% 90% 100% 100%
packages/client-generator/src/authoring/printer.ts 100% 100% 100% 100%
packages/client-generator/src/authoring/schema.ts 76.74% 72% 80.95% 82.02% 39, 65, 97, 115, 160-164, 167-175, 180, 184, 188-191
packages/client-generator/src/emitters/cli-docs.ts 92.45% 78.26% 87.5% 92.78% 36, 37, 77-82, 152
packages/client-generator/src/emitters/cli.ts 100% 94.02% 100% 100%
packages/client-generator/src/emitters/client-assembly.ts 100% 98.86% 100% 100%
packages/client-generator/src/emitters/descriptor.ts 100% 97.77% 100% 100%
packages/client-generator/src/emitters/emit-options.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/faker.ts 96.42% 93.9% 86.66% 98.55% 104, 109
packages/client-generator/src/emitters/go-runtime-sources.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/identifier.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/inline-runtime.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/jsdoc.ts 73.91% 73.68% 100% 96.87% 50, 57, 58, 59, 60, 61, 62, 63, 64, 66, 73, 74
packages/client-generator/src/emitters/mock-value.ts 92.85% 83.33% 100% 100% 40, 49
packages/client-generator/src/emitters/mock.ts 98.79% 98.33% 100% 98.59% 138
packages/client-generator/src/emitters/operation-types.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/pagination.ts 98.36% 97.87% 100% 99.04% 267, 296
packages/client-generator/src/emitters/php-runtime-sources.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/python-runtime-sources.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/render-client.ts 84.79% 81.87% 86.84% 86.7% 76, 146, 158, 170, 178-183, 243-245, 309-311, 314, 351, 360-361, 386, 413-429, 439, 441, 445, 448, 451
packages/client-generator/src/emitters/reserved-names.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/response-headers.ts 73.33% 42.85% 71.42% 66.66% 54-58
packages/client-generator/src/emitters/runtime-sources.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/sdk-docs.ts 0% 0% 0% 0% 42-209
packages/client-generator/src/emitters/setup-bake.ts 100% 92.1% 100% 100%
packages/client-generator/src/emitters/sse.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/swr.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/tanstack-query.ts 98.8% 89.7% 100% 100% 297
packages/client-generator/src/emitters/transformers.ts 96.02% 91.96% 97.22% 97.63% 65-67, 69, 150, 168
packages/client-generator/src/emitters/ts-literal.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/ts-type.ts 96.72% 95% 100% 96.22% 58, 85
packages/client-generator/src/emitters/type-guards.ts 100% 100% 100% 100%
packages/client-generator/src/emitters/wrapper-support.ts 97.91% 96.66% 100% 100% 91
packages/client-generator/src/emitters/zod.ts 96.82% 93.63% 100% 97.97% 169, 171, 178-181
packages/client-generator/src/generators/compatibility.ts 95.45% 96.29% 100% 93.33% 43
packages/client-generator/src/generators/index.ts 100% 100% 100% 100%
packages/client-generator/src/generators/meta.ts 67.27% 100% 35.71% 78.04% 21-23, 55-73, 96-136
packages/client-generator/src/generators/options.ts 100% 92.1% 100% 100%
packages/client-generator/src/generators/resolve.ts 100% 97.87% 100% 100%
packages/client-generator/src/generators/cli/index.ts 78.57% 75% 71.42% 91.66% 44, 50
packages/client-generator/src/generators/cli-docs/index.ts 100% 83.33% 100% 100%
packages/client-generator/src/generators/go/index.ts 93.9% 76.2% 94.89% 95.43% 83-96, 277-279, 295, 300-313, 329, 344, 346-348, 484, 706-710, 714, 759, 898, 903, 914, 1107-1121
packages/client-generator/src/generators/php/index.ts 93.08% 75.65% 96.59% 95.16% 55, 58, 67, 113-120, 148, 163, 169, 171, 187, 193-194, 202-204, 225, 235-236, 286, 486, 491-504, 605, 758, 860, 865, 886, 1037-1049
packages/client-generator/src/generators/python/index.ts 92.13% 78.57% 95% 93.87% 65-68, 72-76, 91-93, 211, 216, 235, 278, 282-288, 296, 297, 298, 299, 317, 359, 752-770
packages/client-generator/src/generators/sdk-docs/index.ts 23.07% 0% 0% 25% 56-78
packages/client-generator/src/generators/typescript/index.ts 68.42% 56.25% 33.33% 76.47% 35, 38-39, 42, 47-51
packages/client-generator/src/intermediate-representation/build.ts 100% 99.45% 100% 100%
packages/client-generator/src/intermediate-representation/model.ts 100% 100% 100% 100%
packages/client-generator/src/intermediate-representation/sanitize-identifiers.ts 100% 100% 100% 100%
packages/client-generator/src/runtime/cli.ts 96.1% 85.75% 90.56% 95.48% 212, 247, 264, 293, 401, 408-411, 482-484, 545-548, 583-585
packages/core/src/types/redocly-yaml.ts 89.32% 77.35% 92.85% 89% 490, 522, 528, 572-579, 581, 719-729, 739-755
Generated in workflow #11353 for commit 2522929 by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Performance Benchmark (Lower is Faster)

CLI Version Bundle Lint Check Config
cli-latest ▓ 1.00x (Fastest) ▓ 1.00x (Fastest) ▓ 1.00x (Fastest)
cli-next ▓ 1.01x ± 0.01 ▓ 1.00x ± 0.02 ▓ 1.01x ± 0.02

@Marshevskyy Marshevskyy added the snapshot Create experimental release PR label Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📦 A new experimental 🧪 version v0.0.0-snapshot.1785856098 of Redocly CLI has been published for testing.

Install with NPM:

npm install @redocly/cli@0.0.0-snapshot.1785856098

⚠️ Note: This is a development build and may contain unstable features.

Comment thread packages/client-generator/src/emitters/mock.ts Fixed
Comment thread packages/client-generator/src/emitters/mock.ts Fixed
@adamaltman

Copy link
Copy Markdown
Member

Feedback from Rebilly (Replay Admin + Rebilly Core)

Tried @redocly/cli@0.0.0-snapshot.1785856098 against:

  • Replay Admin OpenAPI (openapi: 3.2.0) — generators: sdk, zod, mock, php
  • Rebilly Core OpenAPI — generators: sdk, php

Overall: generation succeeds for both; TypeScript still works when co-selected with php; PHP DTOs / enums / discriminators / pagination / Servers / middleware look solid. Below are specific, actionable gaps we hit.

1. Core list one-shot vs *Items() typing is inconsistent

For bare-array collection responses (Rebilly Core style, pagination.items: ''):

  • getCustomerCollection(): array returns raw decoded JSON
  • getCustomerCollectionItems() yields hydrated Customer DTOs

Ask: hydrate the one-shot the same way (e.g. return Customer[] / document a typed collection), or clearly document why one-shot stays untyped arrays while *Items() hydrates.

2. \Generator has no item type for static analysis

Generated methods are typed as \Generator with no @return \Generator<int, Tag> (or equivalent).

Ask: emit PHPDoc (or a PHPStan/Psalm-friendly iterable annotation) so listTagsItems() is known to yield Tag, getCustomerCollectionItems() yields Customer, etc.

3. Middleware is not PSR-7 / PSR-15 / PSR-18 — document that

Middleware works as an onion of:

fn (array $request, callable $next): array

That’s fine for a zero-Composer-deps runtime, but easy to misread as “PSR middleware.”

Ask:

  • Document the request/response array shape (operationId, method, url, headers, query, optional body / contentType / idempotencyKey; response status / headers / body / url / timedOut).
  • Add a short PSR-3 logging middleware example in the use-generated-client / PHP guide (wrapping Psr\Log\LoggerInterface in the callable).

4. Single-file output size hurts large APIs

Rebilly Core PHP came out ~4.7MB / ~112k lines in one file (Admin was fine at ~372KB). Load time was acceptable (~0.2s), but IDE indexing and Psalm/PHPStan over the whole file will be painful.

Ask: consider a PHP split mode (models vs client vs runtime), and/or a runtime: package-style option that keeps the curl runtime out of the generated blob.

5. Formatting noise: blank line after every declaration header

Emitted style is consistently:

final class Page

{

(same for enums / methods). Harmless, but noisy diffs and less idiomatic.

Ask: emit final class Page\n{ (no blank line between name and {).

6. Collision renames are ugly in call sites

Examples we saw:

  • schema Error → PHP Error2 (TS Error_2)
  • duplicate Core operationIds → methods like patchCreditMemo2

Ask: improve rename strategy and/or document how publishers can control names (e.g. prefer operationId disambiguation that stays readable; allow overlay / x- rename hints). Method suffixes are especially costly because they become the public API.

7. argsStyle: grouped is TS-only — say so

Config had argsStyle: grouped; TS correctly uses { params: … }, PHP always uses flat named arguments (which is idiomatic PHP).

Ask: document that argsStyle applies to TypeScript only (or explicitly no-ops for PHP) so multi-language configs aren’t misleading.

8. Rich query params degrade to mixed

Admin list filters that are oneOf / array-capable in the OpenAPI often become mixed in PHP method signatures (e.g. $name = null, mixed $type = null on list ops), while simple enums/scalars stay typed.

Ask: where feasible, preserve union/enum/array types on query params (or emit overloads / documented union PHPDoc) so rich list filters don’t lose type info.

9. CLI --generator help omits new languages

redocly generate-client --help still describes built-ins as sdk, zod, tanstack-query, swr, transformers, mock even though php (and presumably python / go) work.

Ask: update the help text / choices blurb to include the new built-in generators.

10. Stricter pagination validation (positive note + docs)

Invalid per-operation cursorParam now fails generation (our old Admin overrides using after broke against current Admin OpenAPI, which uses cursor / itemsPerPage). That’s a good guard.

Ask: keep the strictness; maybe add a one-line tip in the error (“param must be declared on the operation”) if it isn’t already obvious.


Happy to share the generated artifacts or the small offline smokes we used (Admin cursor + Core offset + PHP php -l / DTO load) if useful.

@adamaltman

Copy link
Copy Markdown
Member

Follow-up: Python generator (same snapshot)

Tried --generator python (with sdk, and alongside php) on the same two descriptions:

  • Replay Admin → openapi.client.py (~436KB)
  • Rebilly Core → rebilly-core.client.py (~4.4MB)

Overall: generation succeeds; sync + async clients work; offline smokes pass for Admin cursor pagination and Core offset pagination + *_with_headers() envelopes. TypeScript smokes still pass when python is co-selected.

Python is in good shape relative to PHP on several points we flagged earlier — calling those out as already-good, then listing Python-specific asks.

Already better than PHP (positive)

  1. Core bare-array collections hydrate on the one-shotget_customer_collection() -> List[Customer] (not a raw list/dict). PHP still returns untyped array for the one-shot while hydrating only in *Items().
  2. Iterator item types are reallist_tags_items() -> Iterator[Tag], get_customer_collection_items() -> Iterator[Customer] (plus async AsyncIterator[...]). Addresses the PHP \Generator PHPDoc gap.
  3. Rich query unions often stay typed — e.g. Optional[Union[PromotionType, List[PromotionType]]] instead of collapsing to mixed.
  4. Injectable http_client — easy to pass httpx.Client(transport=httpx.MockTransport(...)) for tests. Nice seam; PHP has no equivalent transport hook today.

Python-specific asks

1. Output filename is not a normal import path

Emitted names follow the TS stem:

  • openapi.client.py
  • rebilly-core.client.py

Neither is a valid/idiomatic Python module name (import openapi.client / hyphens). Loading requires importlib.util.spec_from_file_location(...), and on Python 3.13 you must register the module in sys.modules before @dataclass runs or import fails with AttributeError: 'NoneType' object has no attribute '__dict__'.

Ask: emit an importable name by default, e.g. openapi_client.py / rebilly_core_client.py, or a small package directory (openapi_client/__init__.py). If the stem must track the TS output path, document a clientOutput / naming convention for Python (or a python-specific output option).

2. Cross-language auth config key mismatch

Python resolves API keys from auth["api_key"][scheme], while the PHP runtime uses auth['apiKey'] and TS tends to document apiKey.

Ask: document the per-language auth dict shape next to each other (or normalize to one key with a documented alias). Easy footgun when someone copies a TS/PHP snippet into Python.

3. Reserved-word fields (typetype_) — document with an example

Works correctly via _field_map (type_ in Python, "type" on the wire), including encode/decode round-trips. Same pattern on method kwargs (type_: Optional[TagType] = None).

Ask: add a short example in the Python usage guide so consumers aren’t surprised by tag.type_ / list_tags(type_=...).

4. Middleware shape differs from PHP — document both

Python middleware is hook-style:

{"on_request": fn(context), "on_response": fn(response, context) -> optional replacement}
# or objects with the same attributes

PHP is an onion of fn(array $request, callable $next): array.

Ask: document the Python middleware contract (context keys, when on_response may replace the response) and include a logging example (stdlib logging is enough; optional note that it’s not Starlette/HTTPX event hooks).

5. Large single-file Core output (same theme as PHP)

Core Python is ~4.4MB / large line count in one module (load ~0.5s here). Same request as for PHP: split models/client/runtime, or a shared runtime package so generated output isn’t dominated by embedded httpx glue.

6. CLI --generator help still omits python / php / go

Same as prior comment — help text still lists only the TS add-ons.


Happy to share the Python offline smokes (httpx.MockTransport + py_compile) if useful.

@adamaltman

Copy link
Copy Markdown
Member

Clarification on single-file output

One more product note on the earlier “large single-file” asks (PHP ~4.7MB / Python ~4.4MB for Rebilly Core):

We like the single-file default and would keep it. It’s a great “Download client” artifact from Redoc/API docs — one .py / .php / .ts link, no zip or package layout, easy for consumers to grab and drop into a project.

Please treat the size/split comments as optional escape hatches for huge descriptions, not a request to change the default:

  • Default stays single-file (docs download story).
  • Optional later: split models/client/runtime, or a shared runtime package, for publishers who hit IDE / type-checker / review limits on Core-scale APIs.

The other asks still stand, especially for Python: importable filename (openapi_client.py / package dir) matters even more if the single file is what people download from the docs.

@adamaltman

Copy link
Copy Markdown
Member

Follow-up: Go generator (same snapshot)

Tried --generator go (with sdk, alongside php / python) on the same two descriptions:

  • Replay Admin → openapi.client.go (~239KB)
  • Rebilly Core → rebilly-core.client.go (~2.7MB)

Overall: generation succeeds; go vet is clean; offline smokes pass for Admin cursor pagination (ListTagsItems) and Core offset pagination + GetCustomerCollectionWithHeaders. TypeScript smokes still pass when go is co-selected. Stdlib-only / injectable HTTPClient is a nice story for tests (http.RoundTripper mocks).

Same single-file preference as before: keep the one-file default for Redoc/API docs downloads. Core Go is notably smaller than Core PHP (~4.7MB) / Python (~4.4MB), which helps that story.

Already in good shape (positive)

  1. Core bare-array collections hydrate on the one-shotGetCustomerCollection(...) ([]Customer, error).
  2. Typed header envelopesGetCustomerCollectionWithHeaders(...) ([]Customer, GetCustomerCollectionHeaders, error) with pointer fields (PaginationTotal *int64, etc.).
  3. Range-over-func paginationListTagsItems / GetCustomerCollectionItems work with for x, err := range ... on Go 1.23+ (we used 1.26).
  4. Injectable HTTPClient + hook-style Middleware (OnRequest / OnResponse on *http.Request / *http.Response) — easy to test and log without a custom transport stack.
  5. Server URL helpers — e.g. ReplayPlatformApiSandboxURL(organizationId string).

Go-specific asks

1. Emit gofmt-clean output

The skill says the runtime/output is gofmt-clean, but generated Admin/Core clients are rewritten by gofmt (const alignment, struct field alignment — dozens of hunks on Admin alone). go vet still passes.

Ask: run gofmt (or equivalent alignment) as the last step of the Go emitter so downloaded single files are idiomatic out of the box.

2. Configurable package name (default client is fine)

Every file emits package client. That fits a downloadable single file dropped into a client/ directory, which we like.

It does mean Admin + Core cannot live in the same Go package/directory without renaming, and publishers can’t match an existing module path (package rebilly, etc.) without a post-edit.

Ask: keep client as the default; add a config/flag (e.g. client.goPackage / --go-package) for publishers who need a different name.

3. Auth providers are func() string only — document it

type Auth struct {
    Bearer func() string
    Basic  *BasicAuth
    APIKey map[string]func() string
}

Unlike PHP/Python (string or callable), Go requires a function even for a static token:

Auth: client.Auth{
    Bearer: func() string { return "…" },
    APIKey: map[string]func() string{
        "SecretApiKey": func() string { return "…" },
    },
},

Ask: document this in the Go usage guide with a static-token example (and optionally note why there’s no string overload — Go’s lack of union types).

4. Reserved-word fields (typeType_) — same docs ask as Python

Works correctly via json:"type" tags (Type_ TagType \json:"type"`onTag` / params).

Ask: short example in the Go guide so tag.Type_ / ListTagsParams{Type_: …} isn’t surprising.

5. CLI --generator help still omits go / php / python

Same as prior comments.


Happy to share the Go offline tests (go vet + httptest.RoundTripper smokes in a temp module) if useful.

@adamaltman

Copy link
Copy Markdown
Member

Follow-up: cli generator (same snapshot)

Tried --generator cli (with sdk, and also with sdk+zod) on Replay Admin and Rebilly Core OpenAPI descriptions.

Emits a sibling *.cli.ts Node shebang entry over the generated TS SDK (commands grouped by OpenAPI tag). --help, group help, command help, schema <command>, and --dry-run work when run with node --experimental-strip-types and --import-ext ts (so the CLI can import ./….client.ts).

Same single-file preference as before: a downloadable *.cli.ts next to the SDK is a nice docs artifact.

What works well

  1. Tag-grouped commands with summaries — e.g. Tags listTags, Tags getTag <id> / Core Customers GetCustomerCollection.
  2. --dry-run prints method/url/headers without calling the network.
  3. schema <operationId> dumps request/response schema refs as JSON.
  4. Body input via --json '<json>' | @file | @- on create/update ops.
  5. Exit-code contract in the engine comments (0 success, 1 API, 2 auth, 3 validation, 4 usage) — usage errors do return 4.
  6. When zod is co-selected, the CLI auto-use(zodValidation()) — good idea (see caveat below).

Asks

1. Document global flags in --help

These flags exist in the parser but never appear in top-level or command help:

  • --server-url
  • --format json|ndjson
  • --dry-run
  • --page-all
  • --output
  • --token
  • --json

Ask: add a Global flags: section to top-level --help (and/or mention relevant globals on command help). Also document auth env vars derived from the bin name (e.g. stem openapi.clientOPENAPI_CLIENT_TOKEN for bearer).

2. Help text says <command> --help but grouped APIs need <group> <command>

Top-level footer:

Run <bin> <command> --help for command details

For tagged APIs, bare listTags --help fails (exit 4 / unknown command). The working form is Tags listTags --help.

Ask: fix the footer to something like Run <bin> <group> <command> --help when commands are grouped, and/or accept ungrouped operationIds as aliases (listTagsTags listTags).

3. OpenAPI tag names with spaces are awkward as CLI groups

Several tags in large descriptions are multi-word (spaces). Those groups only resolve when quoted as a single argv:

<bin> "Some multi-word tag" --help

Unquoted multi-word tokens do not resolve the group.

Ask: kebab-case (or otherwise shell-friendly) group slugs for the CLI surface, keeping the human title in help text — e.g. invoke some-multi-word-tag, display the original tag name.

4. Multiline parameter descriptions break help formatting

Flag help concatenates OpenAPI descriptions that contain newlines, so output looks like:

  --cursor <string>  Cursor value for pagination.
Returns items in the collection starting at this cursor position.
…

Ask: collapse description whitespace to a single line in help (or indent continuation lines).

5. Bin name follows the TS stem, including dots

With clientOutput: …/openapi.client.ts, the CLI bin name becomes openapi.client, which yields env prefix OPENAPI_CLIENT_* and a usage line that looks like a filename.

Ask: allow an explicit binName / --bin-name (default can stay stem-based), and/or sanitize dots the same way other non-identifier characters are normalized for the env prefix.

6. zod + CLI + Node type-stripping

When cli and zod are co-selected, the CLI imports zodValidation from the zod sibling. Under Node’s strip-only mode that fails on TypeScript parameter properties in the generated zod helper (readonly operationId: string in ZodValidationError’s constructor). CLI without zod runs fine with node --experimental-strip-types.

Ask: either emit strip-friendly zod error classes (no parameter properties), or document that the CLI+zod combo needs tsx / a compile step — and make zod wiring opt-in for the CLI if publishers want a zero-build Node runner.

7. CLI --generator help still omits cli

Same theme as php/python/go — generate-client --help doesn’t list cli among built-ins.


Happy to share the exact help transcripts / dry-run JSON we captured if useful.

Comment thread .changeset/agent-friendly-generators.md Outdated

Added agent-friendly client generation: built-in `python`, `go`, `php`, and `cli` generators, a language-neutral authoring toolkit with a per-generator `AGENTS.md` skill, an `eject-generator` command, `x-codeSamples` output, and verification against large real-world descriptions — with every generator now emitting through source-text templates.

**Note:** the AST exports (`ts`, `printStatements`, `schemaToTypeNode`, …) were removed from `@redocly/client-generator/generate` in favor of the text toolkit (`tsType`, `tsJsdoc`, `codeLiteral`).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this from changelog

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

Comment on lines +56 to +65
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.12'
- name: Install httpx (the large-descriptions Python import bar needs it)
run: pip install httpx
- name: Cache the pinned GitHub REST description
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: tests/e2e/generate-client/.cache
key: large-descriptions-${{ hashFiles('tests/e2e/generate-client/large-descriptions.test.ts') }}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need a separate test suite for generator.
I think it will be very big and slow eventually when we start testing compiled languages.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and it's already the biggest suite. Splitting generator tests (client-generator unit +
tests/e2e/generate-client, including the large-description compile bars) into their own vitest
suite and CI job so compiled-language testing can grow without slowing the main e2e job.

Comment thread docs/@v2/commands/eject-generator.md Outdated
Your agent (or you) edits the generator, `redocly generate-client` rebuilds the client, and next week's spec change regenerates with the customization intact.

Ejectable generators: `python`, `go`, `php` — the language generators built on the language-neutral authoring toolkit.
The TypeScript `sdk` and its satellite generators are customized through `client.setup`, middleware, and configuration instead; running `eject-generator sdk` prints that guidance.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think typescript and satellites should be ejectable too as separate generators

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Docs now say every built-in is ejectable. Mechanically the language generators are one
self-contained file, while the TypeScript ones are thin entries over shared emitters, so eject
will bundle each generator with the emitters it uses into a single .mjs, keeping the
@redocly/client-generator imports external. sdk.mjs will be large, but it's one file you own,
which is the point.

Comment thread docs/@v2/commands/eject-generator.md Outdated
| ---------- | ------- | ---------------------------------------------------------------------------------------------------- |
| generator | string | Built-in generator to eject: `python`, `go`, or `php`. |
| `--dir` | string | Directory to eject into. Default `./generators`. |
| `--update` | boolean | Three-way merge a newer generator version into your customized copy; conflicts get standard markers. |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good

Comment thread docs/@v2/commands/eject-generator.md Outdated
Ejecting writes four things:

- `<dir>/<name>.mjs` — the generator, the exact code the built-in runs, readable plain ESM.
- `<dir>/.pristine/<name>.mjs` — a pristine snapshot (commit it); `--update` uses it as the merge base.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't need it. can update compare with the latest upstream version?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropping .pristine/ entirely. --update now uses the version recorded in the ejected file's
header as the merge base, so nothing extra is committed and there's nothing to keep in sync.

### Code samples for docs

A generator that knows how to call an operation can also document it: implement the optional `sample(operation, ctx)` hook to return one idiomatic snippet (`{ lang, label, source }`) per operation.
With `codeSamples: true` in the `client` block, generation collects every selected generator's samples into `<output stem>.code-samples.yaml` — an [OpenAPI Overlay](https://spec.openapis.org/overlay/latest.html) adding `x-codeSamples` per operation, ready for docs tooling to apply.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't have support for Overlays. We need to either add support for overlays in bundle or figure out some other format

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we will think about it in separate PR

Comment thread docs/@v2/guides/use-generated-client.md Outdated

The `cli` generator emits `<stem>.cli.ts` — a zero-dependency, bin-ready command-line interface over the generated client.
Path params are positional, query params become typed `--kebab-name` flags (enums list their choices in `--help`, array params repeat the flag), and JSON request bodies arrive via `--json '<json>'`, `--json @file.json`, or `--json @-` (stdin).
When `zod` is co-selected, requests are validated before they are sent.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it shoud just do it by default without the need to coselect zod

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. cli declares requires: ['sdk', 'zod'] and the resolver now pulls prerequisites in, so
--generator cli alone emits all three files and validates (exit 3) with nothing extra to
select. Same for the wrappers: --generator tanstack-query brings the sdk it wraps. The
trade-off is a zod runtime dependency for the CLI

Comment thread docs/@v2/guides/use-generated-client.md Outdated
Comment on lines +38 to +41
npx tsx src/client.cli.ts orders listOrders --status open --limit 10
npx tsx src/client.cli.ts orders createOrder --json @order.json
npx tsx src/client.cli.ts orders listOrders --page-all # one JSON page per line
npx tsx src/client.cli.ts schema createOrder # request/response schemas

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it

`tanstack-query`, `swr`, and `cli` wrap the throw-mode `sdk` client, so they require `--error-mode throw`; `transformers` requires `--date-type Date`.
See the [`zod`](https://github.com/Redocly/redocly-cli/tree/main/tests/e2e/generate-client/examples/zod), [`tanstack-query`](https://github.com/Redocly/redocly-cli/tree/main/tests/e2e/generate-client/examples/tanstack-query), and [`mock`](https://github.com/Redocly/redocly-cli/tree/main/tests/e2e/generate-client/examples/mock) examples.

### Generated CLI

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should also be able to generate documentation for the CLI (markdown file)
But the same applies for other clients so it can be the next stage.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may need some documentation templates that can be ejected then too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Built it — there's now a cli-docs generator that writes .cli.md beside the generated CLI: usage, global flags, credential environment variables, the exit-code table, and one section per command with its positional arguments and flags (type, required, enum choices, description). --generator cli-docs is the whole selection, since it pulls in the CLI it documents.

On ejectable templates: the renderer is the template. Every built-in generator is ejectable now, so redocly eject-generator cli-docs hands you the page layout as code you own — same language and toolkit as everything else, no template syntax and no extra dependency to learn. Light customization stays declarative through the generator's own options (title, frontmatter) under client.options.cli-docs; anything structural is an eject. We deliberately didn't add a template engine, because that would be a second customization mechanism sitting next to eject.

One property worth calling out: the page renders from the same command table the CLI dispatches on, and the same functions the runtime uses to address groups and name credential variables — so the docs can't drift from the tool. An e2e walks the generated CLI's own --help at both levels and fails if any command it lists is missing a section.

Comment thread docs/@v2/guides/use-generated-client.md Outdated
Comment on lines +61 to +62
The `python` generator emits a self-contained `<stem>.py` next to the configured output — a full Python SDK over [httpx](https://www.python-httpx.org/) (`pip install httpx`, Python ≥ 3.9):
typed dataclass models (allOf flattened, enums, discriminated unions decoded by their discriminator), a `Client` and an `AsyncClient` with one method per operation, auth, retries with `Retry-After` and jittered backoff, timeouts, idempotency keys, middleware hooks, pagination iterators (`<op>_pages()` / `<op>_items()`, `async for` variants), SSE streaming, multipart bodies, `<op>_with_headers()` envelope variants for operations that declare response headers, and a `Servers` class for templated server URLs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we need all of this details. It should be same as typescript one

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it doesn't support any of options we support for typescript? why?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You were right on both counts, and one of them was a bug: serverUrl was silently ignored by the
language generators (fixed, 795f58ca9). dateType: Date is now implemented for all three
(ea3795e47). Options a language genuinely can't apply no longer vanish — they warn with the
reason, or fail fast where the output would be wrong (d6cf2ba4e). The docs no longer enumerate
per-language caveats: the language sections state that these are the TypeScript client in another
language, with one table for the differences the language forces (error idiom, date type, header
envelope, auth shape, reserved-word suffix, file layout).

A generator adds artifacts _next to_ the client — it doesn't change the generated client's behavior; for that, use [publisher defaults](#publisher-defaults) or let the consumer compose [middleware](./use-generated-client.md#middleware).

A generator is `{ name, run }` (plus optional compatibility metadata); author it with `defineGenerator` from the package root, and build real TypeScript with the emit toolkit from `@redocly/client-generator/generate` — the same `ts.factory` + printer the built-in generators use, so the schema→type mapping matches the sdk's exactly:
A generator is `{ name, run }` (plus optional compatibility metadata); author it with `defineGenerator` from the package root.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it should also export additional options this generator may support? (e.g. as json schema)

We can support them via config only for example.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, a generator declares options as a JSON Schema, validated before run, and publishers set them under client.options.<generator> in config.

… helpers — slice-2 lessons landed, python items typed
Comment thread docs/@v2/guides/use-generated-client.md Outdated
Co-authored-by: Jacek Łękawa <164185257+JLekawa@users.noreply.github.com>
Comment thread docs/@v2/configuration/reference/client.md Outdated
Comment thread docs/@v2/guides/customize-client-generation.md Outdated
Comment thread docs/@v2/guides/customize-client-generation.md Outdated
Co-authored-by: Jacek Łękawa <164185257+JLekawa@users.noreply.github.com>
Comment thread packages/client-generator/src/authoring/pagination.ts
@Marshevskyy
Marshevskyy requested a review from tatomyr August 14, 2026 10:15
Comment thread .changeset/agent-friendly-generators.md Outdated
'@redocly/cli': minor
---

Added agent-friendly client generation: `python`, `go`, `php`, `cli`, and `cli-docs` generators in addition to TypeScript generators.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think changelog should be simpler

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll correct it right before merge.

Comment thread docs/@v2/guides/use-generated-client.md Outdated
process.exit(await runCli([{ commands: [login] }, ...SOURCES], process.argv.slice(2)));
```

The CLI resolves credentials from `wiring.env`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is wiring.env?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wiring.env is the environment map the CLI reads credentials from. The generated entry sets it to process.env, so in normal use it is the environment. It is injected instead of read globally so the engine stays testable and a host can run the CLI in-process without writing secrets into the global environment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

corrected a bit docs


#### CLI reference docs

The `cli-docs` generator writes `<output>.cli.md`, a Markdown reference.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think cli generator can generate docs as part of the generator itself, (same about other generators)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed on the other generators, but docs are a separate generator on purpose, mostly because of eject

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added SDK docs

Comment thread docs/@v2/guides/use-generated-client.md Outdated
}
```

#### Where the languages genuinely differ

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is extra info

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deleted


Auth accepts a static credential, or a provider function that the client resolves for each request:

```python

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have code tabs markdoc tag for those

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before I was adding markdoc tags, but @tatomyr preferred native markdown format.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be in the runtime/go
same runtime/php and runtime/python
and runtime/typescript

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like duplicate, this file already exists in assets (same for each generator)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generator AGENTS.md — not a duplicate: it is the source the shipped asset is compiled from. npm run prepare runs scripts/ejected-skill.mjs over src/generators//AGENTS.md and writes eject-assets/skills/-generator/SKILL.md, keeping the design sections verbatim and rewriting the intro and the modify loop for a user's repo (their file is generators/.mjs, not our index.ts, and they have no prepare or vitest). A test pins the asset to the source, so they cannot drift. Nothing said this in the file, which is a fair complaint — every source skill now states it, and the note sits in the paragraph the transform replaces, so the shipped skills are byte-identical.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why it is not in the generators/cli

Some generators are implemented in their generator folders and some are somehow in the core
I think it's confusing

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI:

emitters/cli.ts — the rule was real but unwritten, so this looked arbitrary. generators//index.ts is the entry: options, output paths, and a call to a renderer. The renderer lives in emitters/ because that is where the shared pieces are — operation-signature.ts, ts-type.ts, pagination.ts, sse.ts. cli.ts has three consumers: the cli generator, cli-docs (which renders its page from the same command table, which is what stops the docs from drifting), and the package entry, which exports its composed-entry renderer. Moving it into generators/cli/ would make one generator import another generator's internals. The exception you spotted is real: python, go, and php compose with nothing in emitters/, so each is one self-contained file, which is also what lets eject-generator hand a user its source instead of a bundle. I wrote all of this into ARCHITECTURE.md.

* const-objects, type guards; skipped when the document declares no schemas) and
* `<stem>.ts` (everything else, which `export *`s the schemas module).
*/
export const typescriptGenerator: Generator = ({ model, outputPath, outputMode, emit }) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can't be the reference implementation. I imports some code from core.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated guide

The runtime cli tests used a product-specific credential prefix. The example
API in this repository is the cafe, so the fixtures now use CAFE_SHOP and
CAFE_SYNCER.
sdk-docs writes one Markdown reference for each SDK selected in the same run
(<stem>.python.md, <stem>.go.md, and so on). A page carries the security
schemes and one section per operation: the method and path, the parameters,
the request body, the response type, and a call sample in that language.

The call samples come from each SDK generator's own `sample` hook, which the
pipeline passes to `run` as the new `samples` input. Because of this, a page
cannot drift from the SDK beside it, and the ejected copy does not carry the
SDK generators. Selecting sdk-docs without an SDK fails with the fix in the
message: the generator never pulls a language in on its own.

Also clarifies `wiring.env` in the generated-CLI guide. The field is the
injected environment map, and a wrapper that stores a token writes the token
to `process.env` before it runs a command.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

There are 7 total unresolved issues (including 5 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 381a54b. Configure here.

// One section per tag, in the order the description declares them, then the untagged
// operations — the same grouping the CLI and the split output modes use.
const operations = model.services.flatMap((service) => service.operations);
const paginated = resolveModelPagination(model, options.pagination);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs use wrong pagination resolver

Medium Severity

sdk-docs marks pagination via resolveModelPagination, the TypeScript verifier that throws on explicit rules that fail schema fit. Language SDKs use paginationRuleFor, which does not. Co-selecting sdk-docs with python/go/php can fail the whole run on a pagination config those SDKs would accept, and can also disagree with which operations those SDKs actually paginate.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 381a54b. Configure here.

);
if (paginated) {
printer.line('This operation is paginated, so the SDK gives it page and item iterators.');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing SSE and binary notes

Low Severity

The sdk-docs design requires per-operation behavior notes for paginated, SSE, and binary responses, but the renderer only emits the paginated line. SSE and binary operations are documented like ordinary JSON calls, so the reference omits how those SDKs actually behave.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 381a54b. Configure here.

The table restated the errorMode, dateType, outputMode, and runtime rows of
the client reference, and the response-header, auth, and reserved-name text
of the sections around it. The namespacing facts and the warning for an
option a language cannot apply were the only parts stated nowhere else, so
they move into the Language SDKs prose.
The hand-written SDK runtimes sat in three sibling folders at the package
root: go-runtime, php-runtime, and python-runtime. They are now runtime/go,
runtime/php, and runtime/python, so one folder holds every language runtime.

The TypeScript runtime stays in src/runtime, because it is compiled source
that ships in lib and that a `runtime: package` client imports. The other
three are never compiled: the prepare script embeds them as strings.
…he shipped asset

`src/generators/<name>/AGENTS.md` and
`eject-assets/skills/<name>-generator/SKILL.md` read as two copies of one
document. The second is generated from the first by `npm run prepare`, which
rewrites the intro and the modify loop for a user's repository. Each source
skill now says so, so nobody edits the generated asset.

The note sits in the intro paragraph, which the transform replaces, so the
shipped skills are byte-identical.
The split between generators/ and emitters/ was implicit, so `emitters/cli.ts`
looked misplaced. The module map now gives the rule: a generator folder holds
the entry, emitters/ holds the renderer that composes with the shared pieces,
and the three self-contained language SDKs are the stated exception.
…ementation

In the custom-generator guide that reads as "copy this generator", but it is a
short entry over renderers that are internal to the package, so an author
cannot import what it uses. The guide now says the generator implements the
sample hook, and points authors at the runnable examples, which use only the
public toolkit.
Two findings from the review bot on the new page renderer.

The pagination note came from `resolveModelPagination`, the TypeScript
verifier, which throws on a rule it cannot check against the response schema.
The language SDKs resolve pagination with `paginationRuleFor`, which does not,
so a python-only run could fail over a page, and the page could mark a
different set of operations than the SDK beside it. The note now comes from
the same helper the SDKs use.

The skill promised a note for paginated, streaming, and binary operations, and
only the paginated note existed. A streaming or binary operation now says so,
decided by the success content type, which needs no import of the TypeScript
emitters.
The benchmark job's pnpm pin moved on both sides; main's 11.22.0 wins.
The pagination bullet added to the source skill belongs to the design
sections, which ship, so the generated asset needed regenerating with it.
…ra auth setters

Review found the composed-CLI section reads as a contradiction: an earlier
section says the word after the bin name is a tag slug, while the composed
example puts an api alias there. Both are true and they never met. The guide
now compares the two setups, says the tag groups of an api nest inside its
alias, and says why the example's shorter form works: a bare operationId
resolves when it is unambiguous. An e2e now covers that shorter form, so the
documented spelling is verified rather than asserted.

`binName` led with what it is not, so it now leads with what it is.

The auth section read as three interfaces in TypeScript against one elsewhere.
Every language gives credentials to an instance through its constructor;
TypeScript adds two setters only because it also exports a module-level client.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

snapshot Create experimental release PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants