chore: 🐝 Update SDK - Generate 0.10.1#357
Conversation
There was a problem hiding this comment.
Perry's Review
Speakeasy SDK regeneration (0.10.0 → 0.10.1) consolidating the two legacy benchmark endpoints into a single unified benchmarks.get_benchmarks(), adding images and classifications sub-SDKs, and renaming a large swath of public component types.
Verdict: 🔁 Needs changes
Details
Risk: 🟡 Medium — generated client SDK; failure is reversible (re-generate / re-publish), but one confirmed silent response-parsing bug + a public-API break shipped under a patch bump.
CI: ⏳ no checks reported yet on bb204dc (head is the empty commit to trigger [run-tests] workflow; combined status pending). Not a verdict gate.
Findings (see inline comments):
- 🔴
src/openrouter/components/anthropicusageiteration.py:39— net-newAnthropicUsageIterationis a plain (undiscriminated)Union; a normal{"type":"message", …}payload without the optionalmodelfield deserializes asAnthropicUnknownUsageIteration, notAnthropicMessageUsageIteration(empirically confirmed with pydantic 2.13). Root cause is upstream: the spec'sAnthropicUsageIterationanyOfhas nodiscriminator. - 🟡
pyproject.toml:3— breaking public-API removals/renames shipped under a patch bump (0.10.0 → 0.10.1): theget_benchmarks_artificial_analysis()/get_benchmarks_design_arena()methods on the datasets SDK are removed; ~38 public exports removed/renamed (BenchmarksAA*/BenchmarksDA*→Unified*, top-levelError,CompletionTokensDetails,PromptTokensDetails,Quality,Resolution, …). Carry-forward of the same concern raised on #352. - 🟡
src/openrouter/components/capabilitydescriptor.py:22— discriminated unions (CapabilityDescriptor, alsoUnifiedBenchmarksResponseData) have no catch-all variant; an unknowntype/sourceraisesValidationError. The spec'sx-speakeasy-unknown-values: allowis misplaced inside the discriminatormappingblock (.speakeasy/out.openapi.yaml:4073), so the intended fallback never generates.
Codex (openai/gpt-5.5): 2 findings surfaced — the AnthropicUsageIteration union-resolution blocker (independently confirmed by me) and the misplaced x-speakeasy-unknown-values discriminator-mapping suggestion. Both folded into the inline findings above.
Research: Speakeasy SDK versioning — Speakeasy bumps off info.version + document checksum, NOT schema-content diffing, so removed/renamed types register only as a checksum→patch bump unless info.version is bumped or a major/minor label is applied; pre-1.0.0 SDKs additionally auto-downgrade major→minor. Pydantic v2 unions — smart-mode union resolution scores by field-count then exactness and is explicitly not order-guaranteed; for reliable variant + fallback selection a Discriminator (or left_to_right with the catch-all last) is required. That is exactly what the anyOf is missing.
Security: no concerns — secret scan clean; no auth/key-handling/streaming-validation changes in the diff. The new image and classification endpoints are additive request/response models only.
Test coverage: none added — this repo ships generated code with no SDK-level unit tests; the union-resolution bug in particular would be caught by a single round-trip test over a type:"message" usage iteration without model.
Unresolved threads: none (first review).
Review metadata
Scope: first review (full)
Review: tier=large · model=claude-opus-latest · score=109.9
| "AnthropicUsageIteration", | ||
| Union[ | ||
| AnthropicCompactionUsageIteration, | ||
| AnthropicUnknownUsageIteration, |
There was a problem hiding this comment.
[blocker][codex] AnthropicUsageIteration is an undiscriminated Union — a normal type:"message" usage iteration without the optional model field deserializes as AnthropicUnknownUsageIteration, silently losing the typed variant.
Details
Why: This is a plain Union[Compaction, Unknown, Message, AdvisorMessage] with no Discriminator. AnthropicUnknownUsageIteration.type is str (accepts anything) while AnthropicMessageUsageIteration.type is Literal["message"]. Under pydantic v2 smart-mode, when a minimal {"type":"message","input_tokens":10,"output_tokens":5} payload is validated, the Unknown and Message variants set the same number of fields (the model field is optional), so the field-count metric ties and the broad str-typed Unknown variant wins. Confirmed empirically against the worktree with pydantic 2.13.4:
message payload (no model) -> AnthropicUnknownUsageIteration # ❌ wrong
message payload (with model) -> AnthropicMessageUsageIteration # ✅
compaction payload -> AnthropicCompactionUsageIteration # ✅
This type is reachable: AnthropicUsageIteration is embedded in ImageGenerationUsage.iterations and the anthropic usage block, so any image-gen / anthropic usage response carrying a model-less message iteration parses into the wrong type.
Fix (at the source — these files are DO NOT EDIT generated output): add a discriminator to the AnthropicUsageIteration schema in the source OpenAPI doc (rendered to .speakeasy/out.openapi.yaml:1948). Either give it a real discriminator: { propertyName: type } with a mapping, or — since Unknown is meant as the catch-all — make the generated union order Unknown LAST and use a discriminated-union-with-fallback shape. The generated anyOf currently has no discriminator at all.
Ref: Pydantic v2 — Unions (smart mode is not order-guaranteed; use a Discriminator)
Prompt for agents
The source OpenAPI schema `AnthropicUsageIteration` (rendered to .speakeasy/out.openapi.yaml:1948) is an anyOf with no discriminator, so Speakeasy generates src/openrouter/components/anthropicusageiteration.py as a plain pydantic Union. A {"type":"message"} payload without the optional `model` field mis-resolves to AnthropicUnknownUsageIteration instead of AnthropicMessageUsageIteration. Fix in the source spec (not the generated Python): add a discriminator with propertyName `type` mapping message->AnthropicMessageUsageIteration, compaction->AnthropicCompactionUsageIteration, etc., and keep AnthropicUnknownUsageIteration as the unknown-values fallback. Regenerate the SDK and add a round-trip test asserting a model-less message iteration parses as AnthropicMessageUsageIteration.
Reviewed at bb204dc
| [project] | ||
| name = "openrouter" | ||
| version = "0.10.0" | ||
| version = "0.10.1" |
There was a problem hiding this comment.
[suggestion] Breaking public-API removals/renames are shipping under a patch bump (0.10.0 → 0.10.1) — downstream installs that pin ~=0.10.0 will silently pull a breaking release.
Details
Why: This regen removes and renames a large public surface, not just additive changes:
datasets.get_benchmarks_artificial_analysis()anddatasets.get_benchmarks_design_arena()(+ their_asyncvariants) are removed, replaced bybenchmarks.get_benchmarks(). Endpoint paths moved from/datasets/benchmarks/*to/benchmarks.- ~38 public exports removed/renamed in
components/__init__.py:BenchmarksAA*/BenchmarksDA*→Unified*, plus top-levelError,CompletionTokensDetails,PromptTokensDetails,Metadata,Options,Quality,Resolution, etc.
Any consumer importing those names or calling the old methods breaks on upgrade. Per SemVer this warrants a minor bump (the SDK is pre-1.0.0, where Speakeasy auto-downgrades a major to minor — so the correct floor here is 0.11.0, not 0.10.1). This is the same concern raised on #352.
Root cause: Speakeasy derives the bump from info.version + a document checksum, not by diffing schema content — so a breaking spec change with an unchanged info.version only triggers a checksum→patch bump. The fix is to bump info.version in the source spec (or apply a minor label / SPEAKEASY_BUMP_OVERRIDE=minor) so the SDK lands as 0.11.0.
Prompt for agents
This Speakeasy regen removes public methods (datasets.get_benchmarks_artificial_analysis/design_arena) and renames ~38 public component exports, but bumps only the patch version (0.10.0 -> 0.10.1). Bump to a minor instead (0.11.0): set info.version in the source OpenAPI spec to a new minor, or apply the `minor` PR label / SPEAKEASY_BUMP_OVERRIDE=minor before regeneration, so downstream `~=0.10.0` pins are not silently broken. This is the second regen (after #352) to ship breaking public-API changes under a patch.
Reviewed at bb204dc
| r"""A typed descriptor for one supported request parameter.""" | ||
|
|
||
|
|
||
| CapabilityDescriptor = Annotated[ |
There was a problem hiding this comment.
[suggestion][codex] Discriminated unions have no catch-all variant — an unknown type (here) or source (UnifiedBenchmarksResponseData) raises ValidationError instead of degrading gracefully.
Details
Why: CapabilityDescriptor is a Discriminator-tagged union over exactly enum/range/boolean. get_discriminator() (src/openrouter/utils/annotations.py:79) raises ValueError when the discriminator value isn't one of the tags, so a future capability type the server adds will hard-fail client-side parsing. Confirmed against the worktree:
CapabilityDescriptor unknown type -> ValidationError
UnifiedBenchmarksResponseData unknown src -> ValidationError
The spec intended a fallback — .speakeasy/out.openapi.yaml:4073 has x-speakeasy-unknown-values: allow, but it is nested inside the discriminator.mapping block, so Speakeasy reads it as a bogus discriminator entry ("x-speakeasy-unknown-values" -> "allow") rather than an unknown-values directive on the schema. The fallback therefore never generates.
Fix (source spec): move x-speakeasy-unknown-values: allow out of discriminator.mapping and onto the CapabilityDescriptor schema itself (sibling of oneOf/discriminator), then regenerate. Apply the same check to UnifiedBenchmarksResponseData / the benchmark item unions.
Ref: Speakeasy — unknown union members / x-speakeasy-unknown-values
Prompt for agents
In the source OpenAPI spec, CapabilityDescriptor (rendered .speakeasy/out.openapi.yaml:4067) places `x-speakeasy-unknown-values: allow` inside discriminator.mapping, which makes it a bogus discriminator key instead of an unknown-values fallback directive. Move that extension to be a sibling of `oneOf`/`discriminator` on the CapabilityDescriptor schema. Audit UnifiedBenchmarksResponseData and the benchmark item unions for the same misplacement. Regenerate the SDK; verify an unknown capability `type` / benchmark `source` no longer raises pydantic ValidationError.
Reviewed at bb204dc
bb204dc to
ae0a378
Compare
Superseded by updated Perry review
There was a problem hiding this comment.
Perry's Review
Speakeasy SDK regen 0.10.1 — adds the new image-generation/benchmarks/classifications surface, removes the old datasets benchmark methods, and (since my last pass) adds the Sakana AI provider to the provider enums and per-provider option maps.
Verdict: 🔁 Needs changes
Details
Scope: incremental re-review. The only net-new change since my prior review (at bb204dc) is the addition of one provider (Sakana AI) across the provider-slug enum, display-name enums, and the image/video/byok option maps, plus the regenerated lockfiles and spec. I verified that addition is internally consistent at all seven sites (slug, two display-name enums, three option dicts with the sakana-ai alias, and the byok provider list) — no findings on the new code.
Why this is still "Needs changes": all three findings from my previous review remain open and unaddressed on this head, and the headline one is a correctness blocker I re-confirmed empirically at this SHA (pydantic 2.13.4):
- 🔴 blocker (carry-forward): the AnthropicUsageIteration union is an undiscriminated pydantic Union, so a model-less message usage iteration silently deserializes into the unknown/catch-all variant instead of the message variant. Re-reproduced against the worktree at this head — a minimal message payload without the optional model field resolves to the wrong type. See the existing open thread on the generated component for the full repro + the source-spec fix (add a discriminator on the anyOf rendered in the spec).
- 🟡 suggestion (carry-forward): breaking public-API removals/renames are shipping under a patch bump (0.10.0 → 0.10.1). Confirmed against Speakeasy's versioning docs: for a pre-1.0.0 SDK a breaking change should be a minor (0.11.0), and the patch bump happens only because the source spec's info.version is unchanged. Downstream pins of the compatible form will silently pull a breaking release. See the existing open thread.
- 🟡 suggestion (carry-forward): the CapabilityDescriptor discriminated union has no catch-all variant — an unknown discriminator value raises ValidationError client-side. The source spec intended a fallback but the x-speakeasy-unknown-values directive is misplaced inside the discriminator mapping. See the existing open thread.
These three threads are still unresolved, so I'm holding the prior Needs changes verdict rather than re-posting duplicate review threads on code I already flagged.
CI: pending — the head commit is an empty "trigger run-tests" commit and no checks have reported yet. Not a verdict gate (branch protection handles required checks).
Codex (second opinion): corroborated the version-bump finding and independently flagged the dropped legacy exports (Options/Resolution/AspectRatio) — both already covered by the open breaking-change thread. No net-new findings.
Analysis lanes: 75 candidate findings, all referencing nonexistent paths (hallucinated module layout) — 0 survived source verification.
Research: Speakeasy SDK versioning docs (pre-1.0.0 breaking → minor, not patch; bump derived from info.version + checksum) and pydantic v2 union resolution (smart-mode is not order-guaranteed; a Discriminator is required) — both reconfirm the carry-forward findings.
Security: no concerns. The only new surface is an additive provider enum value + optional option dict; no credential handling, auth, or streaming-path change.
Test coverage: generated SDK — no hand-written tests in scope.
Unresolved threads: 3 — all mine, all carry-forward (see above), none re-posted this pass.
Scope: incremental — net-new since prior review is the Sakana AI provider addition only
Review: tier=large · model=claude-opus-latest · score=110.2
e9155a4 to
a6a9d48
Compare
Superseded by updated Perry review
3863960 to
60cbeab
Compare
2e52e15 to
049b807
Compare
* `open_router.beta.responses.send()`: * `request` **Changed** * `response` **Changed** **Breaking**⚠️ * `open_router.tts.create_speech()`: * `request.provider.options` **Changed** * `open_router.chat.send()`: `response` **Changed** * `open_router.images.list_models()`: **Added** * `open_router.images.list_model_endpoints()`: **Added** * `open_router.datasets.get_benchmarks_artificial_analysis()`: **Deleted** **Breaking**⚠️ * `open_router.datasets.get_benchmarks_design_arena()`: **Deleted** **Breaking**⚠️ * `open_router.classifications.get_task_classifications()`: **Added** * `open_router.benchmarks.get_benchmarks()`: **Added** * `open_router.images.generate()`: **Added** * `open_router.stt.create_transcription()`: * `request.provider.options` **Changed** * `open_router.byok.list()`: `request.provider` **Changed** * `open_router.embeddings.generate()`: `response.usage` **Changed** * `open_router.embeddings.list_models()`: `response.data.[].reasoning` **Added** * `open_router.models.get()`: `response.data.reasoning` **Added** * `open_router.models.list()`: * `request.sort` **Changed** * `response.data.[].reasoning` **Added** * `open_router.models.list_for_user()`: `response.data.[].reasoning` **Added** * `open_router.presets.create_presets_responses()`: `request` **Changed** * `open_router.video_generation.generate()`: * `request.provider.options` **Changed**
d45da4e to
d7628fa
Compare
There was a problem hiding this comment.
Perry's Review
Speakeasy SDK regen to 0.10.1 — adds image-generation, benchmarks, and task-classification surfaces; renames the benchmark components/operations; and introduces the Anthropic usage-iteration union.
Verdict: 💬 Comments / questions
Details
Re-review scope: the only change since my last review (d45da4e → 9af8656) is generator-metadata churn — a regenerated gen-lock generation id and a RELEASES.md timestamp. The substantive SDK output is byte-identical to what I already reviewed, so I have not regenerated nits on unchanged code.
Risk: 🟡 Medium
CI: all passing ✅
Findings: no net-new findings on this push. My three prior findings remain open and unresolved as existing review threads (not re-posted here to avoid duplication):
- 🔴 blocker — the Anthropic usage-iteration union is undiscriminated; a model-less message iteration mis-deserializes into the unknown variant. Re-confirmed empirically against the worktree on pydantic 2.13.4 (a minimal message payload resolves to the unknown variant, not the message variant).
- 🟡 suggestion — breaking public-API removals and renames are shipping under a patch bump (0.10.0 to 0.10.1); per SemVer this warrants a minor (0.11.0). Speakeasy derives the bump from info.version plus a document checksum rather than a content diff, so the breaking surface is invisible to its versioner.
- 🟡 suggestion — the discriminated unions (capability descriptor, unified benchmarks response) have no catch-all variant, so an unknown discriminator value raises a validation error instead of degrading gracefully; the intended unknown-values directive is misplaced inside the discriminator mapping in the source spec.
Codex (heavy second opinion): independently re-surfaced the union-ordering blocker (same root cause as my prior blocker) — no additional net-new findings.
Research: Pydantic v2 union docs confirm smart-mode resolves by field-set count then exactness (not declaration order, and explicitly subject to change) — a discriminated union keyed on the type field is the recommended fix. Speakeasy versioning docs confirm the bump is info.version plus checksum derived and that pre-1.0.0 majors auto-downgrade to minor, so the bump-override env var (or a minor label) is the correct remedy.
Security: no concerns — generated client models only; no secrets in the diff, no auth, streaming, or logging logic touched.
Test coverage: the regen adds no round-trip tests for the new union; a test asserting a model-less message iteration parses as the message variant would catch the blocker.
Unresolved threads: 3 open (the blocker plus 2 suggestions above), all carried forward from prior pushes.
Scope: incremental — only generator-metadata lines changed since prior review
Review: tier=large · model=claude-opus-latest · score=112.0
SDK update
Versioning
Version Bump Type: [patch] - 🤖 (automated)
Tip
If updates to your OpenAPI document introduce breaking changes, be sure to update the
info.versionfield to trigger the correct version bump.Speakeasy supports manual control of SDK versioning through multiple methods.
Python SDK Changes:
open_router.beta.responses.send():requestChangedresponseChanged Breakingopen_router.tts.create_speech():request.provider.optionsChangedopen_router.chat.send():responseChangedopen_router.images.list_models(): Addedopen_router.images.list_model_endpoints(): Addedopen_router.datasets.get_benchmarks_artificial_analysis(): Deleted Breakingopen_router.datasets.get_benchmarks_design_arena(): Deleted Breakingopen_router.classifications.get_task_classifications(): Addedopen_router.benchmarks.get_benchmarks(): Addedopen_router.images.generate(): Addedopen_router.stt.create_transcription():request.provider.optionsChangedopen_router.byok.list():request.providerChangedopen_router.embeddings.generate():response.usageChangedopen_router.embeddings.list_models():response.data.[].reasoningAddedopen_router.models.get():response.data.reasoningAddedopen_router.models.list():request.sortChangedresponse.data.[].reasoningAddedopen_router.models.list_for_user():response.data.[].reasoningAddedopen_router.presets.create_presets_responses():requestChangedopen_router.video_generation.generate():request.provider.optionsChangedOpenAPI Change Summary
PYTHON CHANGELOG
No relevant generator changes
Based on Speakeasy CLI 1.680.0
Last updated by Speakeasy workflow