Uh oh!
There was an error while loading. Please reload this page.
feat(requesty): sync models from the Requesty catalog - #1
feat(requesty): sync models from the Requesty catalog#1devin-ai-integration[bot] wants to merge 2 commits into
Conversation
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
aa90a39 to
3fd9fe1Compare| // prefixes (an `openai/` chat route becoming an `openai-responses/` route) | ||
| // without being retired, so local models absent from a run are reported for | ||
| // manual lifecycle review instead of deleted. | ||
| deleteMissing: false, |
There was a problem hiding this comment.
deleteMissing true please
There was a problem hiding this comment.
Done — the flag is gone, so missing routes are deleted. Heads-up on what that removed: 14 files whose routes moved prefix, e.g. openai/gpt-5-codex → openai-responses/gpt-5-codex (recreated under the new ID), plus anthropic/claude-opus-4 and anthropic/claude-sonnet-4, which the endpoint no longer lists at all.
| import type { ExistingModel, SyncProvider, SyncedFullModel, SyncedModel } from "../index.js"; | ||
| import { factorBaseModel } from "./openrouter.js"; | ||
| // Requesty is an OpenAI-compatible LLM gateway. Its public model catalog carries |
There was a problem hiding this comment.
Remove all these extra comments
There was a problem hiding this comment.
Stripped — the file is down to a few one-liners where the behaviour isn't obvious from the code.
| // documented for OpenAI, Anthropic, and Google/Vertex routes only, so those are | ||
| // the vendors whose reasoning controls we can assert on a new model. | ||
| // https://docs.requesty.ai/features/reasoning | ||
| const REASONING_EFFORT_VENDORS = new Set(["anthropic", "google", "openai"]); |
There was a problem hiding this comment.
everyone is a reasoning effort vendor, we convert them all, shouldn't I see more models here?
There was a problem hiding this comment.
You were right — the allowlist is gone, every reasoning model gets the effort option now. Reasoning coverage went from the 3 first-party vendors to every route the API flags (or whose base metadata asserts reasoning).
| input_price: z.number(), | ||
| output_price: z.number(), | ||
| cached_price: z.number().optional(), | ||
| caching_price: z.number().optional(), |
There was a problem hiding this comment.
you should use the other Devin PR (which introduced pricing to have an array here actually showing all the bands we have, so we can potentially map it to different tiers
There was a problem hiding this comment.
Wired up: the schema takes the optional pricing array from requestyai/backend#2349 (prompt_tokens_threshold, input_price, output_price, cached_price, caching_price) and maps every band above threshold 0 to a cost.tiers entry, with the threshold-0 band staying the flat cost. The live endpoint doesn't return pricing yet, so today's run keeps the hand-authored bands (e.g. the 200K Anthropic tier); the moment anomalyco#2349 ships they come from the API instead.
| // `models/` metadata entry is enough to bring them into the catalog. | ||
| return vendorMetadata(model.id) === undefined ? undefined : model.id; |
There was a problem hiding this comment.
Fair — that was doing two things at once. sourceID is just model.id now; it only tells the runner which remote ID to name in the skipped-models notice.
| return value !== undefined && value > 0 ? value : undefined; | ||
| } | ||
| type Modality = "text" | "audio" | "image" | "video" | "pdf"; |
There was a problem hiding this comment.
/v1/models only currently returns text models (so we don't need to do this stuff)
There was a problem hiding this comment.
Dropped — modalities come from the base metadata now, so the Modality type and both helpers are gone.
| }; | ||
| } | ||
| function isModality(value: Modality | undefined): value is Modality { |
There was a problem hiding this comment.
can remove this then
| return value !== undefined; | ||
| } | ||
| function reasoningOptions( |
There was a problem hiding this comment.
Let's remove the task_budgets, and have reasoning efforts only, that is the same as all the other providers
There was a problem hiding this comment.
Done — a single effort option, no budget_tokens.
| // `deepseek/deepseek-reasoner`) report `supports_reasoning: false`, and some | ||
| // multimodal routes report `supports_vision: false`. A flag may raise a | ||
| // capability, never clear one that is authored or inherited. | ||
| const capabilities = { |
There was a problem hiding this comment.
this is an ugly object, construct this better
There was a problem hiding this comment.
Gone — capabilities are supported_parameters entries on the shaped model now, so the shared builder derives attachment / reasoning / tool_call / structured_output.
| // Region-pinned (`@us-east5`) and service-tier (`:flex`, `:high`) routes are | ||
| // variants of a catalog model rather than models of their own, so they are never | ||
| // created from the catalog alone. | ||
| export function resolveRequestyBaseModel(modelID: string): string | undefined { |
There was a problem hiding this comment.
We should create the region models, otherwise how would the user use them?
There was a problem hiding this comment.
Agreed and fixed. Base metadata is now resolved by model slug with the @region / :tier suffix stripped, so vertex/claude-opus-4-5@us-east5 inherits models/anthropic/claude-opus-4-5.toml and gets its own file at the route ID callers actually pass. That took the catalog from 84 files to ~500 (409 created), covering vertex/, bedrock/, azure/, deepinfra/, coding/, :flex, and the region pins.
requesty-JohnCosta27
left a comment
There was a problem hiding this comment.
const built = buildOpenRouterModel(toOpenRouterShape(model), existing);
you can import from the open router shape, so let's do that and keep this file tiny. We want this file to be TINY
3fd9fe1 to
df3807eCompareRefactored onto the OpenRouter shape as asked — What changed behaviourally:
Still skipped: 95 routes with no Two commits as before: script then catalog. |
| name: "Requesty", | ||
| modelsDir: "providers/requesty/models", | ||
| sourceID: (model) => model.id, | ||
| skippedNotice, |
There was a problem hiding this comment.
nevermind, lets inline the skippedNotice, but make it small
There was a problem hiding this comment.
Inlined, two lines.
| // Requesty routes carry gateway facts (price, limits, capabilities) but no | ||
| // release dates, weights, or family. Without a `models/` entry to inherit | ||
| // those from, an authored file is left as it is and the route is reported. | ||
| if (baseModel === undefined) { |
There was a problem hiding this comment.
what does this mean?
There was a problem hiding this comment.
Sorry, that comment was doing too much work. It's the routes we have no models/<lab>/<slug>.toml entry for (95 of them today: novita/qwen/qwen3-235b-a22b-fp8, xai/grok-4, …). There's nothing to inherit name / release_date / open_weights from, and the API doesn't give them, so the route can't be written from scratch. If someone already hand-wrote the file, it's left alone (otherwise deleteMissing would delete it); otherwise it's listed in the skipped notice. Comment is one line now: "A route with no metadata to inherit keeps whatever is authored for it."
| /** Context-length pricing bands, cheapest threshold first, excluding the base band. */ | ||
| function pricingTiers(model: RequestyModel): NonNullable<SyncedFullModel["cost"]>["tiers"] { | ||
| const tiers = (model.pricing ?? []) | ||
| .filter((band) => band.prompt_tokens_threshold > 0) |
There was a problem hiding this comment.
remove the filter and the sort, not needed because we return them this way already
There was a problem hiding this comment.
Done — no filter, no sort, just .slice(1) to skip the base band (which stays the flat cost).
| const rest = ids.length - listed.length; | ||
| return [ | ||
| `${ids.length} Requesty models were skipped because no \`models/\` metadata entry matches them yet.`, | ||
| `Skipped remote IDs: ${listed.map((id) => `\`${id}\``).join(", ")}${rest > 0 ? `, and ${rest} more` : ""}`, |
There was a problem hiding this comment.
lets not have all these strange joins
There was a problem hiding this comment.
Trimmed to one line and a single join: ${ids.length} Requesty routes have no \models/` metadata entry yet: ${ids.join(", ")}`.
| * (`vertex/claude-opus-4@us-east5`) or a service tier (`openai/gpt-5.4:flex`), so | ||
| * the metadata entry is looked up by model slug rather than by route prefix. | ||
| */ | ||
| export function resolveRequestyBaseModel(modelID: string): string | undefined { |
There was a problem hiding this comment.
Because the route prefix is the host, not the lab: vertex/claude-opus-4-5@us-east5, bedrock/claude-opus-4-5, and anthropic/claude-opus-4-5 all need to inherit models/anthropic/claude-opus-4-5.toml, so the lookup is on the slug with @region/:tier stripped rather than on the prefix. That's the only reason the function exists — docstring is one line now.
Co-Authored-By: john <john@requesty.ai>
Co-Authored-By: john <john@requesty.ai>
eca596c to
d2187b4Compare
Summary
Adds a sync module for Requesty (
packages/core/src/sync/providers/requesty.ts), registered insync/index.tsunderaggregators, so the hand-maintainedproviders/requestycatalog tracks the gateway's own routes, prices, and limits. This run: 65 created, 19 updated, 0 deleted.Source:
https://router.requesty.ai/v1/models(public; fetched without a key deliberately — a key only adds the account's ownpolicy/aliases, which are user-configured routes rather than catalog models). Reasoning surface: https://docs.requesty.ai/features/reasoningNon-obvious decisions, all documented in the new
sync.mdsection:xai/grok-4anddeepseek/deepseek-reasonerreportsupports_reasoning: false,xai/grok-4-fastreportssupports_vision: false. So each field isflag === true ? true : existing?.field, and factored models never emitmodalities(a single vision flag cannot express the pdf/audio inputs base metadata already records).models/entry, so every created file is abase_modelfile and curated facts stay inherited.sourceIDreturnsundefinedfor other prefixes, andresolveRequestyBaseModelmaps the vendor prefix onto metadata (zai/glm-5.2→zhipuai/glm-5.2,moonshot/kimi-k3→moonshotai/kimi-k3) while rejecting variant routes (openai/gpt-5.4:flex,vertex/claude-sonnet-5@us-east5). Hosting routes (vertex/,bedrock/,deepinfra/, …) belong to the provider catalogs that already cover them; unmatched first-party routes are listed in the sync notice instead of opening issues.reasoning_optionsis authored-first, then effort + decimal budget for OpenAI/Anthropic/Google routes (the only vendors whosereasoning_efforttranslation Requesty documents); every other new reasoning model falls back to[]rather than inventing a control.cost.tiersbands are preserved because the catalog exposes no tier thresholds. The catalog is authoritative for per-routelimit.context/limit.output, withmax_output_tokens: 0falling back to the authored output limit.deleteMissing: false: a model can move between prefixes (anopenai/chat route becomingopenai-responses/) without being retired, so absent locals are retained and reported.Verification:
bun validateclean,bun test packages/core/test/sync.test.ts60 pass (5 new Requesty tests),bun models:sync requesty --dry-runidempotent after the write.bun testhas 3 failures ondevunrelated to this change (open-weight metadata + sdk snapshot tests).Note: opened against the
requestyai/models.devfork (whosedevwas fast-forwarded to upstreamsst/models.dev@52d5045) because Devin has no write access tosst/models.dev— the branch is ready to be sent upstream as-is.Link to Devin session: https://app.devin.ai/sessions/8dfb3465f4b34a83a0451742cce7aac0
Requested by: @requesty-JohnCosta27