You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(llm-pi-ai): read input modalities from gateway model listings (patch ready on a fork branch)
#8873
discoverModels reads capacities from a provider's model listing and stops there. A gateway that publishes input_modalities — or OpenRouter, which nests them under architecture.input_modalities — is therefore reported as having no declared input modalities, so the Models page adopts the model as text-only and image attachments are refused for a vision-capable model behind that route.
The catalog path is already complete: discoverModels returns inputModalities: [...model.input] for a route the installed catalog describes, and LlmDiscoveredModel.inputModalities, the wire schema and the client's adopt() all carry it. Only the wire path reads capacity fields alone, so the gap is one mapping.
Change
ListingEntry gains input_modalities, inputModalities and a nested architecture.input_modalities (new interface beside the existing limit/top-provider ones).
New modalities(...candidates) helper, in the shape of capacity/label: the first candidate that yields a known modality wins, values outside the pi-ai vocabulary are dropped, and duplicates collapse.
readListing includes inputModalities when the entry declares any, and omits the field otherwise, so an endpoint that states nothing still says nothing.
Dropping unknown values is deliberate: a modality this build cannot name would either be ignored downstream or read as a capability the adapter cannot carry. Omitting the field preserves the current "unknown" meaning for every listing that has no modality field.
Tests
tests/discovery.spec.ts gains one case in the existing style (same listingServer helper, exact-equality expectations): flat gateway spelling, nested OpenRouter spelling, camel-case extension, an unknown modality that must be dropped, and an entry with no modality field that must keep the field absent.
The recorded OpenRouter fixture expectation gained the modalities its recorded reply declares, after dropping values outside the pi-ai vocabulary (file/audio/video dropped, text/image kept).
Verification performed
The mapping logic was executed against nine fixtures with the patched code extracted verbatim from the patched source (run with Node 24+ type stripping): gateway flat, OpenRouter nested, camel-case, unknown-modality drop, absent stays absent, duplicate collapse and vocabulary order, empty-first-candidate fall-through, enriched models map path, non-array modality field ignored. All nine pass.
The patch applies cleanly to a pristine copy of master; the applied files are byte-identical to the files that were executed.
pnpm exec vitest run packages/llm/llm-pi-ai on the branch: 14 files, 346 tests passed (the added case and the updated recorded expectation included).
Not verified here
The workspace typecheck script cannot run on this host: it starts with build:lib:host, which fails on a clean detached master (before this change, commit 5badb1500) with four Could not resolve '@deepseek-ai/dsh-agent-loop/invariant' in lib/types/index.js errors — the agent-loop package's exports define only . and ./package.json. That looks like a pre-existing artifact-plane issue on master, unrelated to this change; CI will confirm.
The package's own vitest suite was run through the workspace vitest filter because the package declares no test script.
Repository process this change owes
Agent Note: included at .agents/notes/implemented/bug-fix/2026-10-03-wire-input-modalities.md. The Chinese sibling and pairing sidecar still need the repository's own tooling (pnpm run verify-translation-pairing --write), which the pairing gate reads — not runnable in the environment this change was prepared in.
Model-visible output: this change adds none (the discovery reply is configuration metadata), so the package suite plus the added case is the coverage per the testing policy.
Where the code is
Since issues and pull requests are closed on this repository, the change sits ready on a public fork branch:
Happy to open a proper pull request the moment one is possible on this repository.
Prepared on a deployment that hit the gap in practice: a guardian-style gateway behind input_modalities, with 46 image-capable models that arrived text-only.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Problem
discoverModelsreads capacities from a provider's model listing and stops there. A gateway that publishesinput_modalities— or OpenRouter, which nests them underarchitecture.input_modalities— is therefore reported as having no declared input modalities, so the Models page adopts the model as text-only and image attachments are refused for a vision-capable model behind that route.The catalog path is already complete:
discoverModelsreturnsinputModalities: [...model.input]for a route the installed catalog describes, andLlmDiscoveredModel.inputModalities, the wire schema and the client'sadopt()all carry it. Only the wire path reads capacity fields alone, so the gap is one mapping.Change
ListingEntrygainsinput_modalities,inputModalitiesand a nestedarchitecture.input_modalities(new interface beside the existing limit/top-provider ones).modalities(...candidates)helper, in the shape ofcapacity/label: the first candidate that yields a known modality wins, values outside the pi-ai vocabulary are dropped, and duplicates collapse.readListingincludesinputModalitieswhen the entry declares any, and omits the field otherwise, so an endpoint that states nothing still says nothing.Dropping unknown values is deliberate: a modality this build cannot name would either be ignored downstream or read as a capability the adapter cannot carry. Omitting the field preserves the current "unknown" meaning for every listing that has no modality field.
Tests
tests/discovery.spec.tsgains one case in the existing style (samelistingServerhelper, exact-equality expectations): flat gateway spelling, nested OpenRouter spelling, camel-case extension, an unknown modality that must be dropped, and an entry with no modality field that must keep the field absent.The recorded OpenRouter fixture expectation gained the modalities its recorded reply declares, after dropping values outside the pi-ai vocabulary (
file/audio/videodropped,text/imagekept).Verification performed
modelsmap path, non-array modality field ignored. All nine pass.master; the applied files are byte-identical to the files that were executed.pnpm exec vitest run packages/llm/llm-pi-aion the branch: 14 files, 346 tests passed (the added case and the updated recorded expectation included).Not verified here
typecheckscript cannot run on this host: it starts withbuild:lib:host, which fails on a clean detachedmaster(before this change, commit5badb1500) with fourCould not resolve '@deepseek-ai/dsh-agent-loop/invariant' in lib/types/index.jserrors — the agent-loop package's exports define only.and./package.json. That looks like a pre-existing artifact-plane issue onmaster, unrelated to this change; CI will confirm.testscript.Repository process this change owes
.agents/notes/implemented/bug-fix/2026-10-03-wire-input-modalities.md. The Chinese sibling and pairing sidecar still need the repository's own tooling (pnpm run verify-translation-pairing --write), which the pairing gate reads — not runnable in the environment this change was prepared in.Where the code is
Since issues and pull requests are closed on this repository, the change sits ready on a public fork branch:
packages/llm/llm-pi-ai/src/discovery.ts,packages/llm/llm-pi-ai/tests/discovery.spec.ts,.agents/notes/implemented/bug-fix/2026-10-03-wire-input-modalities.mdHappy to open a proper pull request the moment one is possible on this repository.
Prepared on a deployment that hit the gap in practice: a guardian-style gateway behind
input_modalities, with 46 image-capable models that arrived text-only.All reactions