Skip to content

fix(providers): keep protocol types narrow and unwrap PEP 695 aliases - #20

Merged
zhanghanduo merged 1 commit into
mainfrom
fix/provider-protocol-narrow-types
Sep 2, 2026
Merged

zhanghanduo merged 1 commit into
mainfrom
fix/provider-protocol-narrow-types

Conversation

@zhanghanduo

Copy link
Copy Markdown
Collaborator

Summary

Two silent type-erasure defects found while a strict downstream host consumed the provider substrate (FrontierAgent #104).

protocol_of / thinking_format_for_protocol were widened to str during extraction. Their outputs feed ModelProfile.protocol / .thinking_format, which are Literal unions — so a value this module had just validated with is_wire_protocol was no longer assignable to the very field it was validated for. protocol_client.py is checked at basic level, so nothing here caught it; the strict host did, with 4 pyright errors in code it had not touched. Restores the narrow aliases the host declared before extraction. Signatures only, no body changes.

_schema_for_type mapped a PEP 695 type Alias = ... to {"type": "string"}. A TypeAliasType is not a primitive, has no origin, and is not a Pydantic model, so it fell through to the Any floor. That floor is a deliberate choice for under-annotated parameters — an alias is the opposite: a tool author who names a union to describe a parameter precisely got a schema claiming it was a string, with the annotation looking correct in the source. Now unwraps __value__. agent_core uses type statements itself (runtime/pause_check.py, providers/openai_chat.py), so this was live rather than hypothetical.

Validation

  • uv run ruff check agent_core tests
  • uv run pyright agent_core — 0 errors
  • uv run pytest -q — 1054 passed
  • both new alias tests fail with the unwrap removed (verified)
  • FrontierAgent #104 pyright goes 4 errors → 0 against this branch

Two type-erasure defects found while a strict downstream host consumed the
provider substrate. Both are silent: the annotation reads correctly at the
call site and the wrong type only shows up further downstream.

`protocol_of` and `thinking_format_for_protocol` were extracted with `-> str`
and `-> str | None`, widening what the host they came from declared. Their
outputs feed `ModelProfile.protocol` / `.thinking_format`, which are `Literal`
unions, so a value this module had just validated with `is_wire_protocol` was
no longer assignable to the very field it was validated for. This file is
checked at `basic` level, so nothing here caught it; a strict host did, with
four errors in code it had not touched.

`_schema_for_type` mapped a PEP 695 `type Alias = ...` straight to
`{"type": "string"}`: a TypeAliasType is not a primitive, has no origin, and is
not a Pydantic model, so it reached the `Any` floor. That floor is a deliberate
choice for under-annotated parameters — an alias is the opposite, and a tool
author who names a union to describe a parameter precisely got a schema
claiming it was a string. Unwrap `__value__` instead. `agent_core` uses `type`
statements itself (pause_check, openai_chat), so this was live.

Validation:
- uv run ruff check agent_core tests
- uv run pyright agent_core (0 errors)
- uv run pytest -q (1054 passed)
- both new alias tests fail with the unwrap removed

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@zhanghanduo
zhanghanduo merged commit 5b6be4c into main Sep 2, 2026
1 check passed
@zhanghanduo
zhanghanduo deleted the fix/provider-protocol-narrow-types branch September 2, 2026 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant