feat: badge model display names, configurable via options.badge#43
Open
BenjaminDSmithy wants to merge 1 commit into
Open
feat: badge model display names, configurable via options.badge#43BenjaminDSmithy wants to merge 1 commit into
BenjaminDSmithy wants to merge 1 commit into
Conversation
bb235ee to
fcca9d1
Compare
fcca9d1 to
ab55c8e
Compare
LM Studio's native /api/v1/models reports params_string ("27B"),
quantization.name ("Q4_K_M"), size_bytes, and format ("mlx"|"gguf"),
but none of it reached the OpenCode model config. Builds that share a
display_name (an MLX quant and its GGUF sibling, or two quant levels of
one model) were indistinguishable in the picker.
Append a metadata badge to the display name, with the verbosity set by
a new provider.lmstudio.options.badge option:
off -> Devstral Small
format -> Devstral Small (MLX)
compact -> Devstral Small (Q4_K_M · 14.3 GB · MLX)
full -> Devstral Small (24B · Q4_K_M · 14.3 GB · MLX) [default]
Tiers are order-preserving subsets of [params, quant, size, format].
compact drops params because params is identical across the quant and
format siblings of one model, so it never disambiguates them. Within a
tier, each segment is still dropped when LM Studio does not report it,
so the badge degrades cleanly on older servers; a metadata-less record
keeps the bare display_name. Size uses LM Studio's decimal (base-1000)
convention. The separator is a single named constant (not a config
option). Only the display name changes; the model key (ID) is untouched.
The default is "full", so a user with no config sees the same output as
before this option existed — the change is purely additive. An
unrecognised badge value fails open to "full" rather than breaking
OpenCode startup, matching the plugin's discovery-error-degrades ethos.
The badge key is plugin-only and is deliberately NOT stripped from the
forwarded provider options: @ai-sdk/openai-compatible reads only named
fields and ignores unknown keys (never sent on the wire), and options is
the one config record that persists across repeated config-hook runs, so
stripping would revert the user's tier to the default on the next load
(the same reason apiKey is written back into options).
Adds params_string and size_bytes to the native model schema (both
optional for backward compatibility). Covered by tier, size-format,
resolve-fallback, and enhanceConfig idempotency tests (32 pass);
README, CHANGELOG, and the v1 contract doc updated.
ab55c8e to
e9807f2
Compare
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.
What
Append a metadata badge to each model's display name so otherwise-identical builds are distinguishable in the OpenCode model picker, with the verbosity controlled by a new
provider.lmstudio.options.badgeoption:offDevstral SmallformatDevstral Small (MLX)compactDevstral Small (Q4_K_M · 14.3 GB · MLX)fullDevstral Small (24B · Q4_K_M · 14.3 GB · MLX)Default is
full, so a user with no config sees exactly the same output as before this option existed — the change is purely additive. The option only dials the badge down.{ "provider": { "lmstudio": { "options": { "badge": "compact" } } } }Why
GET /api/v1/modelsalready carriesparams_string,quantization.name,size_bytes, andformat, but none of it reachedModelConfig. In practice an MLX quant and a GGUF quant of the same model share adisplay_name, and multiple quant levels collapse to the same row — the picker shows identical names with no way to tell them apart. This surfaces metadata the endpoint already returns; nolmsCLI or SDK dependency, so it stays remote-server-safe. Raised against the rc.2 feedback tracker (#34).How
src/plugin/enhance-config.ts:BADGE_TIERS/BadgeTier/DEFAULT_BADGE_TIER = "full", andresolveBadgeTier(raw)which fails open tofullfor any non-enum value (a typo must never break startup — matches the plugin's discovery-error-degrades ethos).modelBadge(model, tier)selects an order-preserving subset of[params, quant, size, format].compactdrops params on principle: params is identical across the quant/format siblings of one model, so it never disambiguates them — the tier keeps only the segments that do. (This avoids parsingdisplay_name, which would be exactly the kind of name heuristic the README rules out.) Within a tier, each segment is still dropped when unreported.enhanceConfigresolves the tier once fromexisting?.options?.badgeand threads it intotoModelConfig. Separator is a single named constant (not a config knob — kept minimal). Onlynamechanges; the modelkey/ID is untouched.src/types/index.ts: addsparams_stringandsize_bytestoLMStudioModelSchema, both.nullable().optional()so older servers and the existing fixture still validate.The
badgekey and forwarded optionsbadgeis a plugin-only option, and it is deliberately not stripped from the options forwarded to@ai-sdk/openai-compatible. Two reasons, both verified against the bundled versions:createOpenAICompatiblereads only named fields (baseURL,apiKey,headers,queryParams,fetch, …) and ignores unknown keys —badgeis never placed in a header/URL/body, never sent on the wire, never throws.optionsis the one config record that persists across repeatedconfig-hook invocations (it's howapiKeysurvives re-runs). Strippingbadgewould revert the user's tier to the default on the second config load. AnenhanceConfig-twice test asserts both the option's persistence and that the chosen tier is still applied on the second run.If you'd rather strip it anyway, I'm happy to add a persistent side-channel for the tier — but not-stripping is the simpler correct choice here.
Contract note
The rc.2 contract (#34) says "display names come directly from LM Studio metadata." This decorates the display name; the
keyis still verbatim, and every segment is LM Studio metadata.docs/v1-contract.mdis updated with the badge mapping row and the default. If you'd prefer the default to beoff(badge fully opt-in) rather thanfull, that's a one-line change — say the word.Tests
modelBadgeper-tier (incl. intra-tier segment drop) +formatSizeboundaries +resolveBadgeTierfallback.enhanceConfigintegration:offyields bare names,compactapplies + survives a second run (no-strip idempotency), invalid value falls back tofull.29→32pass;tsc --noEmitclean;eslintclean; lockfile untouched.README (
Model mapping),CHANGELOG.md(Unreleased → Added), anddocs/v1-contract.mdupdated.