fix(types): eliminate as-any gaps for serverTool mixing and fromChatMessages input#32
Open
mattapperson wants to merge 4 commits intomainfrom
Open
fix(types): eliminate as-any gaps for serverTool mixing and fromChatMessages input#32mattapperson wants to merge 4 commits intomainfrom
mattapperson wants to merge 4 commits intomainfrom
Conversation
…essages input
Two consumer-facing type gaps in v0.4.0 required `as any`:
1. Mixing `tool()` + `serverTool()` results in a single array typed as
`Array<ClientTool | ServerTool>` failed because `ServerTool<T>`'s
`config: Extract<..., {type: T}>` is invariant over `T`, so a
`ServerTool<'openrouter:datetime'>` did not assign to the bare
`ServerTool` (= `ServerTool<ServerToolType>`). Split into:
- `ServerToolBase` — structural base (also kept as the union member
of `Tool`).
- `ServerToolNarrow<T>` — narrow form, returned by `serverTool<T>()`,
extends `ServerToolBase` via interface inheritance.
- `ServerTool` — now a type alias for `ServerToolBase`, so
`Array<ClientTool | ServerTool>` accepts any narrow variant.
2. `fromChatMessages()` returns `InputsUnion`, but `callModel`'s
`request.input` was typed `FieldOrAsyncFunction<Item[]> | string`,
which is a narrower union. Widen `input` to also accept
`FieldOrAsyncFunction<InputsUnion>` so the converter's output
assigns directly without a cast — matching the docstring.
Adds a type-level regression test (`consumer-type-ergonomics.test-d.ts`)
covering both scenarios, and migrates the existing narrowing fixtures
to `ServerToolNarrow<T>`.
mattapperson
commented
Apr 21, 2026
Collaborator
Author
mattapperson
left a comment
There was a problem hiding this comment.
One consideration on the changeset classification for a type-level break.
Restores ServerTool as a conditional generic with a `never` default so bare `ServerTool` collapses to `ServerToolBase` — preserving backward compatibility for callers that used `ServerTool<T>` while still allowing mixed `Array<ClientTool | ServerTool>` to accept narrow variants via intersection subtyping. Infers the stream output's type from `config.type` directly so narrowing works through both the intersection form and the bare base.
Reclassifies the changeset as `minor` and spells out the type-level breaking change so downstream release notes surface the migration from `ServerTool<T>` to `ServerToolNarrow<T>`. Also applies biome formatting required by the pre-push lint hook.
Revert classification to `patch` and rewrite the body to explain that both fixes are purely additive — `ServerTool` and `ServerTool<T>` still compile exactly as before, so no consumer migration is required.
mattapperson
commented
Apr 21, 2026
Collaborator
Author
mattapperson
left a comment
There was a problem hiding this comment.
reviewed, no issues found
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes two type gaps reported against v0.4.0 (context: Slack thread) where consumers mixing
tool()+serverTool()or usingfromChatMessages()were forced to useas any.Gap 1:
Array<ClientTool | ServerTool>rejected narrowserverTool()resultsServerTool<T>definedconfig: Extract<ServerToolConfig, { type: T }>, which is invariant overT. SoServerTool<'openrouter:datetime'>did not assign to bareServerTool(=ServerTool<ServerToolType>). Splitting the public surface:ServerToolBase— structural base, used as the union member ofTool.ServerToolNarrow<T>— narrow form, returned byserverTool<T>().extends ServerToolBasevia interface inheritance so narrow → base is nominal.ServerTool— now a type alias forServerToolBase. Mixed arrays likeArray<ClientTool | ServerTool>andTool[]accept anyserverTool()result directly.Code that only used
ServerToolwithout a type argument is unaffected. Callers that referencedServerTool<T>(rare — was only useful for narrowingconfig) should migrate toServerToolNarrow<T>or simplyReturnType<typeof serverTool<T>>.Gap 2:
fromChatMessages()output not assignable torequest.inputfromChatMessages()returnsmodels.InputsUnion, butcallModel'srequest.inputwas typedFieldOrAsyncFunction<Item[]> | string— a narrower union that excludedEasyInputMessage(with a wide role union). ThefromChatMessagesdocstring literally claims its output "can be passed directly tocallModel()"; now the types match. Widenedinputto also acceptFieldOrAsyncFunction<InputsUnion>.Before / after
Test plan
pnpm --filter @openrouter/agent run typecheck— cleanpnpm --filter @openrouter/agent exec vitest run --project=unit— 246/246, 21 files (20 runtime + 3 type-only)pnpm --filter @openrouter/agent exec biome check src tests— cleantests/unit/consumer-type-ergonomics.test-d.tslocks in both fixes at type-levelserver-tool-stream-narrowing.test-d.tsfixtures fromServerTool<T>toServerToolNarrow<T>serverToolmix +fromChatMessages) — both scenarios compile withoutas any