Step-by-step guide for adding support for a new LLM in Dust. Use when adding a new model, or updating a previous one.
This skill guides you through adding a newly released LLM to the model_constructors +
llms stack (the endpoint-class router). It replaces the legacy lib/api/llm/clients/*
router, which no longer exists.
Adding a model is usually only half the task: the model it supersedes has to be retired in the same PR, and a model the provider has switched off needs its agents repointed. See Deprecating or removing an old model.
A model reaches production through three stacked layers. Add the new model to each:
front/types/assistant/models/*) โ the legacy ModelConfigurationType
describing the model (context, vision, reasoning efforts, pricing tiers). Still the source
of truth consumed by the UI, pricing, and the dust layer.model_constructors (front/lib/model_constructors/*) โ provider-agnostic endpoint
classes, one per (provider, model, region, provider-api). Each class mixes a shared
provider base client with a per-model config mixin (input schema, context size,
token pricing). This is where the real request/response shape and the narrowed input
config live.llms (dust layer) (front/lib/llms/*) โ thin Dust-specific wrappers around the
model_constructors classes that add Dust concerns (display name, byok, endpoint
filters, and any caps โ e.g. exposing 250k context on a model that natively supports
1M). Registered into DUST_STREAM_ENDPOINTS.Endpoints are named and filed as:
{provider}_{model}_{region}_{provider_api}.ts
e.g. google_gemini_3_6_flash_global_agent_platform.ts. The class name is the
PascalCase of the same, with numbers spelled out:
GoogleGeminiThreeDotSixFlashGlobalAgentPlatformStream.
The fastest, most reliable way to add a model is to copy the most recent model in the same family across all layers and rename. Grep every reference to that model and mirror each one. This skill lists the reference points; the sibling model is your template.
You MUST confirm every value below against the provider's official documentation and leave a URL + date in a code comment next to it. Do not carry values over from memory.
https://platform.openai.com/docs/modelshttps://docs.anthropic.com/en/docs/about-claude/models/overviewhttps://ai.google.dev/gemini-api/docs/modelshttps://docs.mistral.ai/getting-started/models/models_overview/https://openai.com/api/pricing/https://www.anthropic.com/pricing#anthropic-apihttps://ai.google.dev/gemini-api/docs/pricinghttps://mistral.ai/technology/#pricingWebSearch/WebFetch the docs first. If a value can't be confirmed, surface it โ don't guess.
Pick the newest sibling (e.g. for "Gemini 3.6 Flash" the sibling is "Gemini 3.5 Flash") and
grep -rln its id / const / class-name / model-id string. You will touch, roughly:
| File | What to add |
|---|---|
front/types/assistant/models/{provider}.ts |
X_MODEL_ID const + X_MODEL_CONFIG. Set isLatest: false on the previous model in the same family and drop "latest" from its description. Carry over the predecessor's availableIfOneOf / unavailableIfOneOf (see below). |
front/types/assistant/models/models.ts |
Add id to STATIC_MODEL_IDS and config to SUPPORTED_MODEL_CONFIGS (imports in both alpha blocks). |
front/types/assistant/models/auto.ts |
If the model should participate in auto/auto_fast/auto_complex routing, add a ModelStreamCandidate. |
front/lib/model_constructors/types/models.ts |
Add export const X = "model-id" and include it in the MODELS array (this is the model_constructors id type). |
StaticModelIdType)Adding the id to STATIC_MODEL_IDS makes these fail to compile until updated:
| File | What to add |
|---|---|
front/lib/api/assistant/token_pricing/global.ts |
CURRENT_MODEL_PRICING entry (input/output/cache_read_input_tokens per 1M) + doc URL comment. |
front/types/assistant/models/static_model_reasoning_efforts.ts |
{ none, light, medium, high } support map (satisfies Record<StaticModelIdType, ReasoningEffortSupport>). Must match the config's supportedReasoningEfforts (enforced by model_tiers.test.ts). |
front/types/assistant/models/model_tiers.ts |
STATIC_MODEL_TIERS entry mapping each supported effort โ tier name. |
And one that is not compile-forced, so nothing turns red if you skip it:
| File | What to add |
|---|---|
front/lib/api/assistant/token_pricing/eu.ts |
Add the id to EU_UPLIFT_MODEL_IDS if you register a non-global endpoint that prices above its global sibling. |
EU pricing is a second, silent list. Any endpoint with
region = EUROPEbills throughinferenceRegion: "eu"(inferenceRegionForEndpointRegioninfront/lib/api/llm/transitionLLM.ts), andcomputeTokensCostForUsageInMicroUsdthen looks the model up inEU_MODEL_PRICINGโ falling back to the global rate when it is absent.EU_UPLIFT_MODEL_IDSissatisfies readonly StaticModelIdType[], which validates the ids present but does not force completeness, so a missing entry undercharges EU traffic forever with nothing failing.The uplift is per provider and per endpoint, not per model โ compare the two endpoint classes'
tokenPricingrather than assuming. Regional agent-platform (Vertex) endpoints charge 10% over global for both Anthropic and Google, so a new Gemini registered oneu/agent-platformbelongs in the list just as much as a Claude does. OpenAI uplifts only the models whose pricing page lists a data-residency premium (gpt-5.4/5.5/5.6/6 yes, gpt-5/5.1/5.2 no). Mistral's EU endpoints are its native region with no global sibling, so nothing to add.
EU_MODEL_PRICINGderives every field by multiplying the global entry byEU_PRICING_MULTIPLIER, so it is only correct when the EU endpoint is a flat 1.1ร of global. A non-uniform regional price needs an explicit entry, not the multiplier.
Gating is inherited, and lives in two unlinked places. A new version of a gated model stays gated โ being newer is not a reason to release it. Copy the predecessor's
availableIfOneOf/unavailableIfOneOfonto the newX_MODEL_CONFIG(gates the picker, viaisModelAvailable) and declare the same flag on every endpoint you add (gates the router, viaisEndpointAvailable):static readonly endpointFilter = { featureFlags: { contains: "fireworks_new_model_feature" as const }, };Half-gating fails silently either way: hidden but reachable, or pickable but unroutable โ and
resolveModelswaps in a fallback model instead of erroring. Releasing a gated family is a separate, deliberate change.
model_constructors โ the endpoint classes (stream)| File | What to add |
|---|---|
front/lib/model_constructors/providers/{provider}/models/{model}.ts |
Config mixin WithXConfig(Base) exposing static model, static configSchema, static contextSize, static maxOutputTokens. Reuse the provider's shared inputConfig/reasoning_efforts/shared helpers. contextSize/maxOutputTokens are the REAL provider values โ caps belong in the dust layer. |
front/lib/model_constructors/stream/endpoints/{provider}_{model}_{region}_{api}.ts |
One class per available (region, provider-api), extending WithXConfig(BaseClient). Set static tokenPricing (per-endpoint, region-adjusted), region, regionalEndpoint, and static id = this.buildId(). Base clients live in stream/clients/*. |
front/lib/model_constructors/stream/index.ts |
Import + register each available endpoint in STREAM_ENDPOINTS. |
model_constructors โ tests (TDD, see below)| File | What to add |
|---|---|
front/lib/model_constructors/test/endpoints/{...}.test.ts |
One StreamSetup per endpoint. Copy the sibling's key set, but start every case at null โ never copy its expected values (see the TDD loop). |
front/lib/model_constructors/test/endpoints/setups.ts |
Import + register each registered endpoint's setup (satisfies Record<StreamEndpointId, StreamSetup> forces completeness). |
llms โ the dust layer (stream)| File | What to add |
|---|---|
front/lib/llms/providers/{provider}/models/{model}.ts |
Dust config mixin WithDustXConfig(Base) โ Object.assignes the legacy X_MODEL_CONFIG onto the class and overrides displayName/description/byok (and any caps). |
front/lib/llms/stream/endpoints/{...}.ts |
One thin dust wrapper per endpoint extending the model_constructors class via the dust mixin; call defineDustStreamEndpoint(...). |
front/lib/llms/stream/index.ts |
Register each available dust endpoint in DUST_STREAM_ENDPOINTS (satisfies Record<StreamEndpointId, ...>). |
| File | What to add |
|---|---|
sdks/js/src/types.ts |
Add the id to the KnownModelLLMId union. Then rebuild the SDK types (cd sdks/js && npm run build:types) โ front's sdk_drift.test.ts type-imports the built @dust-tt/client, so tsgo reads stale declarations until you do. |
front/components/providers/model_configs.ts |
Add config to USED_MODEL_CONFIGS so it shows in the UI, and evict the family's older versions down to two (see below). |
At most two versions of a family in
USED_MODEL_CONFIGS. The picker groups by maker, so every version left in the list is another near-identical row a user has to read past ("Gemini 3.5 Flash / 3.6 Flash / 3.7 Flash / 3.8 Flash"). When you add a model, keep only it and its immediate predecessor; drop the rest of the family fromUSED_MODEL_CONFIGS. Count families by product line, not by provider โ Gemini Flash, Gemini Flash Lite and Gemini Pro are three families, each allowed two.Everything a dropped model needs to stay callable lives elsewhere (
SUPPORTED_MODEL_CONFIGS, the endpoint classes, pricing), so the eviction only removes it from the picker and the agent builder. Then finish the deprecation properly, or the model rots into a stale default years later:
- Set
isLegacy: true+isLatest: falseon each evicted config.isLegacyis also what drops it from the public credits page, so an evicted-but-not-flagged model keeps being advertised while being unpickable.- Repoint every hardcoded reference to it.
grep -rn X_MODEL_CONFIG front front-apiand fix the ladders and defaults that name it:ORDERED_FAST_MODEL_CONFIGS/ORDERED_SMALL_MODEL_CONFIGS/ORDERED_LARGE_MODEL_CONFIGSinfront/lib/api/assistant/models.ts,getFastModelConfiginfront/lib/api/assistant/conversation/title.ts,preferredModelConfigurationon thedust-*global agents, andMODEL_STREAMScandidates infront/types/assistant/models/auto.ts. These are hand-maintained lists that no type checks โ nothing goes red when they point at a legacy model.A legacy model still referenced by one of those lists is the failure mode this rule exists for: conversation titles ran on Gemini 3.5 Flash for three releases after 3.6/3.7/3.8 shipped, purely because
getFastModelConfigwas never revisited.
No marketing mirror. The public credits page fetches
/api/marketing/model-credits, whichfront/lib/api/marketing/model_credits.tsderives at request time fromSUPPORTED_MODEL_CONFIGS+MODEL_PRICING. Nothing to copy intomarketing/โ but the model only appears there once it has aMODEL_PRICINGentry, is notisLegacy, and is released (noavailableIfOneOf.featureFlag).
Batch endpoints (
.../batch/...) are a curated subset โ only add them if the model needs batch. They are NOT completeness-enforced. SetsupportsBatchProcessingto the real capability regardless.
The endpoint classes derive their behavior from a shared integration test harness. Let the live API tell you the input contract โ never infer it from the sibling model. Sibling expectations are the single biggest source of wrong config: two models in the same family routinely differ on temperature, reasoning efforts, and forced tool use.
The config schema must ALWAYS mirror the API's real behavior as closely as possible. It
describes what the provider accepts โ not what Dust happens to send today, and not what would
be convenient. If the API accepts a value, the schema accepts it; if the API rejects a value,
the schema rejects it. Never narrow past the API because an upstream layer already strips the
field (the dropTemperature / dropTemperatureWhenReasoning config parsers in lib/llms are
a product policy and belong there, not in the endpoint schema), and never widen past it to
avoid a union. Concretely: Anthropic reasoning models accept exactly temperature: 1, so the
field is z.literal(1).optional().default(1) โ not z.undefined(), even though the Dust layer
drops it before the endpoint ever sees it.
When a divergence from the API is genuinely wanted (exposing a narrower effort set to control cost, say), it is a policy choice โ write it as a comment stating that the API allows more and why Dust doesn't, so the next reader doesn't mistake it for a provider constraint.
Reasoning efforts must ALWAYS mirror the model's official documentation, not merely whatever the endpoint happens to accept. This is the one place where "what the API tolerates" is the wrong source of truth, because gateways are routinely looser than the models they serve:
reasoning_effort against low/medium/high/xhigh/max/none
for every model it hosts, so a live run "passes" on efforts the model never defined.medium works through Fireworks but is not
a K3 effort.low produced more reasoning than medium).minimal and xhigh, which xAI documents only for other models.So: find the model author's doc (not just the host's), expose exactly the efforts it lists, and link it in a comment. Where host and author docs disagree, follow the author unless the host documents a model-specific override โ generic host guidance is not a contradiction. Then confirm each documented effort actually works on the live endpoint, and record any effort the endpoint accepts but the docs omit, with a note that undocumented efforts can change without notice.
When the product still offers an effort the model does not have, map it in the llms layer with
a configParsers entry (mapReasoningNoneToMinimal, mapNonNoneReasoningToHigh,
mapReasoningEffortToLowHighMax, forceHighReasoningEffort) โ never with a schema .transform(), and never by widening the
endpoint schema to swallow it.
Write the config mixin with configSchema set to the broad inputConfigSchema
(front/lib/model_constructors/types/input/configuration.ts), marked // TDD SCAFFOLD. Every
case must reach the API instead of being short-circuited by a guessed schema.
nullCopy the sibling's key set (so coverage matches) but not its expected values. null
runs the case with its default checkers. Starting from the sibling's
INPUT_CONFIGURATION_ERROR markers hides exactly the differences you are trying to find, and
lets stale expectations survive โ a suite whose expectations were never run green will happily
assert things the schema makes impossible.
--bailYou want every failure at once in order to characterize the contract:
cd front
NODE_ENV=test RUN_LLM_TEST=true DUST_MANAGED_{PROVIDER}_API_KEY=... \
npm run test -- --config lib/model_constructors/test/vite.config.js \
lib/model_constructors/test/endpoints/{...}.test.ts
Env-var names live in the sibling's createInstance (DUST_MANAGED_ANTHROPIC_API_KEY,
DUST_MANAGED_GOOGLE_AI_STUDIO_API_KEY, โฆ). Agent-platform/Vertex endpoints need
VERTEX_AI_PROJECT_ID plus GCP credentials โ a GOOGLE_APPLICATION_CREDENTIALS service-account
key works and needs no gcloud auth application-default login. Add --bail 1 or
-t "<substring>" only later, when iterating on a single case.
The bucket decides the fix:
| Last event | Meaning | What to do |
|---|---|---|
error carrying a provider message (invalid_request_error, โฆ) |
Real API constraint | The schema must encode it |
error of type input_configuration_error |
Our own zod rejected it before any request | With the widest schema this means a converter or base client still rejects it |
| The case passes | The API accepts this input | Whether to allow it is a policy choice โ match the sibling unless there's a reason to diverge, and state which you chose and why |
A passing case is evidence. It disproves any assumption that the model rejects that input โ
including assumptions already written down. Do not keep an INPUT_CONFIGURATION_ERROR because
a code comment says the model doesn't support something: the run outranks the comment.
Rewrite configSchema to the real contract, with a doc URL + date in a comment next to each
value. Three things to pin deliberately, not by inheritance:
reasoning default effort โ read it off the official doc, every time. The .default(...)
is load-bearing: an absent reasoning sends no thinking config, so the provider's own default
applies, and that differs per model (adaptive-on for Fable 5 / Opus 5 / Sonnet 5; thinking-off
for Opus 4.8/4.7/4.6 and Sonnet 4.6; no thinking for Haiku 4.5; max for Kimi K3 and GLM-5.2).
Never carry over a sibling's default or invent one for cost reasons โ Kimi K3 sat at low
when Moonshot documents max. Mirror the documented default and cite the page; if the product
wants a cheaper default, that belongs in defaultReasoningEffort on the llms model config, not
in the endpoint schema.temperature handling. Sweep actual values against the API rather than assuming โ the
rule is per-model. Anthropic reasoning models accept only 1 while thinking is on and any
value while thinking is off; some reject the field outright.forceTool compatibility. Which efforts are genuinely accepted, and
whether a forced tool_choice may coexist with reasoning.Mark the genuinely-rejected cases INPUT_CONFIGURATION_ERROR, re-run the full suite until
every case passes, then delete the // TDD SCAFFOLD comment.
A config mixin is shared across regions and provider APIs (e.g. global/anthropic +
eu/agent-platform), so narrowing it changes all of them. Run each one.
Shared configs are per family โ Opus, Sonnet, Haiku each have their own; a family with a single member (Fable 5) just keeps a standalone config. A family's shared config should track the latest member of that family, because the next model in it is far likelier to repeat the newest behavior than the oldest. So when characterizing a model reveals that its family's shared config was wrong, fix the shared config and put the override on the older models โ never special-case the newest one.
The reflex to resist is the opposite: leaving the shared config alone and giving the new model
a bespoke schema. That makes every future model in the family inherit stale behavior, and it
is how a restriction that only ever applied to one old model ends up applied to all of them.
(Worked example: forceTool: z.undefined() sat in the shared Opus config because extended
thinking forbids a forced tool_choice. Opus 4.7, 4.8 and 5 all use adaptive thinking and
all accept it โ verified live โ so the fix was to drop it from the shared config, not to
override it on Opus 5.)
Do not merge families that happen to agree today. Fable 5 and Opus 5 share every value except one (Fable 5 cannot disable thinking), but they are different families, so they keep separate configs and the coincidence is allowed to drift.
Then re-run the suites of every model in the family (ยง7), since they all moved.
If you cannot run the live suite (no key / non-interactive), narrow the config from the sibling model in the same family and say so explicitly โ flag every expectation as unverified; the live run must still happen before merge.
Without NODE_ENV=test+RUN_LLM_TEST, the test file loads but its cases are skipped; that
still validates it compiles and is registered.
cd front
npx tsgo --noEmit # whole-project type check
NODE_ENV=test npm run test -- \
types/assistant/models/sdk_drift.test.ts \
types/assistant/models/types.test.ts \
types/assistant/models/model_tiers.test.ts
tsgo clean over the files you touched (the satisfies Record<...> maps and STREAM_ENDPOINT_SETUPS are your completeness guardrails).sdk_drift.test.ts is a compile-time guard: its it() body always passes, and the
Exclude<StaticModelIdType, KnownModelLLMId> assertion only fails under tsgo. So a green
vitest run proves nothing here โ tsgo is what enforces front โ SDK. Rebuild the SDK types
first, or tsgo reads a stale @dust-tt/client and passes on a drifted id.model_tiers.test.ts green โ reasoning-effort maps and tier maps in sync with the configs.Adding a model is normally paired with retiring the one it supersedes. There are two distinct paths โ pick by whether the provider still serves the old model.
Hide it from new work and leave everything else standing, so agents already pinned to it
keep working and historical token accounting stays exact. Worked example: Kimi K2.6
added / K2.5 deprecated (f2824da5c5e, #28834).
| File | What to change |
|---|---|
front/types/assistant/models/{provider}.ts |
Set isLegacy: true + isLatest: false on the old config, and strip "flagship"/"latest" from its description. |
front/components/providers/model_configs.ts |
Remove it from USED_MODEL_CONFIGS โ that is what drops it from the model picker, the workspace model-providers page, and workspace_capabilities. |
front/types/assistant/models/auto.ts |
Replace it in any MODEL_STREAMS candidate list with the new model. |
front/lib/api/assistant/models.ts |
Replace it in ORDERED_FAST_MODEL_CONFIGS / ORDERED_SMALL_MODEL_CONFIGS / ORDERED_LARGE_MODEL_CONFIGS โ the whitelisted-model ladders behind getFastestWhitelistedModel & co. |
front/lib/api/assistant/conversation/title.ts |
Replace it in getFastModelConfig, the per-provider ladder picking the model that names conversations. |
front/lib/api/assistant/global_agents/configurations/dust/dust.ts |
Repoint every preferredModelConfiguration naming it (e.g. the dust-kimi* family). |
front/lib/api/assistant/global_agents/global_agent_metadata.ts |
Update the agent description strings that name the old model version. |
front/lib/api/assistant/global_agents/global_agents.ts |
If the old model had its own global agent (rather than a dust-* agent you just repointed), add its GLOBAL_AGENTS_SID to RETIRED_GLOBAL_AGENTS_SID. |
Retiring a global agent that way keeps it resolvable so past conversations still render,
while getGlobalAgents filters it out of list views and isRetiredGlobalAgent gates it out
of new conversations. Do not delete the GLOBAL_AGENTS_SID member โ the enum values are
the sIds persisted in historical messages.
Keep the id in STATIC_MODEL_IDS, SUPPORTED_MODEL_CONFIGS, CURRENT_MODEL_PRICING,
STATIC_MODEL_TIERS, STATIC_MODEL_SUPPORTED_REASONING_EFFORTS, and keep its endpoint
classes registered. Nothing to do for marketing: isLegacy is what excludes it from the
public credits list (front/lib/api/marketing/model_credits.ts).
A deprecated model needs no agent-config migration โ that is the point of the path.
Nothing can run on the model any more, so it comes out of the codebase entirely and every
agent still pinned to it must be repointed. Worked example: DeepSeek R1 removal
(3ca8d834527, #26958). Do Path 1's picker/global-agent repointing first, then:
X_MODEL_ID const and X_MODEL_CONFIG from
front/types/assistant/models/{provider}.ts, and their entries in
front/types/assistant/models/models.ts (STATIC_MODEL_IDS, SUPPORTED_MODEL_CONFIGS).static_model_reasoning_efforts.ts and model_tiers.ts. For pricing, move the entry
from CURRENT_MODEL_PRICING into LEGACY_MODEL_PRICING in the same
token_pricing/global.ts (a Record<string, PricingEntry>, so it survives the id leaving
StaticModelIdType); that block exists precisely so historical runs still cost out.
Deleting the pricing outright silently zeroes past usage.model_constructors + llms: delete the endpoint classes, config mixins, and
test/endpoints/*.test.ts; unregister from stream/index.ts, setups.ts and
llms/stream/index.ts; drop the id from the MODELS array in
front/lib/model_constructors/types/models.ts.RETIRED_GLOBAL_AGENTS_SID as in Path 1
โ that is the normal answer even here, and it keeps historical conversations rendering.
Only tear the agent out completely when it must stop resolving at all: delete the factory
(global_agents/configurations/{provider}.ts) and the GLOBAL_AGENTS_SID member in
front/types/assistant/assistant.ts, which turns every exhaustive reference red โ the
getGlobalAgent switch and the flag filters in global_agents.ts,
global_agent_metadata.ts, prompt_context.ts (Record<GLOBAL_AGENTS_SID, โฆ>) and
getGlobalAgentAuthorName. Deleting the member abandons the sIds stored in past
messages, so justify it explicitly. (DeepSeek R1 did this in 3ca8d834527, before
RETIRED_GLOBAL_AGENTS_SID existed โ prefer retirement now.)front/types/shared/feature_flags.ts once
nothing else references it.KnownModelLLMId in sdks/js/src/types.ts narrows a
public API type โ a breaking change. Get explicit sign-off first (see the
dust-breaking-changes skill), then rebuild (cd sdks/js && npm run build:types).A removal orphans every AgentConfiguration row still pinned to the dead model, so ship a
repoint script in the same PR: front/migrations/YYYYMMDD_migrate_<model>_models.ts, built
on makeScript. Template: front/migrations/20260608_migrate_deepseek_r1_models.ts.
20260810_migrate_sonnet46_medium_to_auto.ts โ can import the consts.)AgentConfigurationModel.findAll({ where: { modelId, status: "active" } })
through a ModelStaticWorkspaceAware alias, with
dangerouslyBypassWorkspaceIsolationSecurity: true plus the WORKSPACE_ISOLATION_BYPASS
comment and biome-ignore lint/plugin/noUnverifiedWorkspaceBypass the linter requires โ
migrations run across all workspaces.sId, version, workspaceId, from โ to) on the dry run, and
gate all writes on execute.update({ providerId, modelId }, { where: { id: agents.map((a) => a.id) } }) over the ids
already gathered โ not agent.update() per row. Scoped to those ids, the update needs no
isolation bypass of its own (the cross-workspace scan already happened in the findAll).providerId alongside modelId โ the replacement often sits on a different provider.
Reset reasoningEffort too if the target does not support the effort the agent was on.| Property | Notes |
|---|---|
contextSize / generationTokensCount |
Real provider values (legacy config). Caps go in the dust layer. |
supportsVision |
Can process images. |
supportsResponseFormat |
Structured output (JSON). Often incompatible with tool use โ verify. |
supportedReasoningEfforts |
{ none, light, medium, high }. Must match static_model_reasoning_efforts.ts. |
defaultReasoningEffort |
Default effort. |
isLatest / isLegacy |
Exactly one isLatest per family; flip the previous one to false. |
regionalAvailability |
{ "us-central1", "europe-west1" } โ reflect real availability. |
tokenizer |
Tokenizer for token counting. |
isLatest: falseSTATIC_MODEL_IDS + SUPPORTED_MODEL_CONFIGS + model_constructors/types/models.tsEU_UPLIFT_MODEL_IDS updated if a registered EU endpoint prices above its global sibling
(NOT compile-forced โ a miss silently bills EU traffic at global rates)model_constructors: config mixin + endpoint class(es) + stream/index.ts.test.ts per endpoint + setups.tsnull โ full red run โ narrowed schema
with reasoning-default and temperature confirmed against docs โ green run โ scaffold removedllms dust layer: dust mixin + endpoint(s) + llms/stream/index.tsmodel_configs.ts; SDK union updated and types rebuilt before tsgoUSED_MODEL_CONFIGS holds at most two versions of the family; every model evicted by
that rule is isLegacy: true + isLatest: false and no longer named by any hardcoded
ladder (ORDERED_*_MODEL_CONFIGS, getFastModelConfig, dust-* global agents,
MODEL_STREAMS)tsgo clean; types / model_tiers tests greenRetiring the superseded model (same PR):
isLegacy: true + isLatest: false, dropped from USED_MODEL_CONFIGSMODEL_STREAMS candidates and dust-* global agents repointed to the new model,
global-agent descriptions updatedLEGACY_MODEL_PRICING, not deletedKnownModelLLMId narrowing signed off as a breaking change before removal--executetsgo fails on sdk_drift.test.ts naming your id โ add it to KnownModelLLMId in sdks/js/src/types.ts, then cd sdks/js && npm run build:types (it type-imports the built @dust-tt/client declarations, so the rebuild must come first).tsgo on setups.ts / index files โ you added an endpoint to STREAM_ENDPOINTS without a matching setup, or vice-versa. Register both.model_tiers.test.ts fails โ static_model_reasoning_efforts.ts disagrees with the config's supportedReasoningEfforts, or STATIC_MODEL_TIERS is missing an effort the config supports.USED_MODEL_CONFIGS.invalid_request_error means narrow configSchema and mark the case INPUT_CONFIGURATION_ERROR; an input_configuration_error under the widened scaffold means a converter or base client is rejecting it, not the API.DUST_MANAGED_*_API_KEY twice; the last export wins interactively, so grepping for the first match can hand you a stale key.