Skip to content

Commit ead85ae

Browse files
committed
model names: shorten to post-slash portion for all display/selection
1 parent 477fae2 commit ead85ae

7 files changed

Lines changed: 120 additions & 151 deletions

File tree

‎src/threecode/api.nim‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,7 +1000,7 @@ proc emitUserSubmit*(line: string, echoRows = -1) =
10001000
proc callModel*(p: Profile, messages: JsonNode, usage: var Usage, lastPromptTokens: int): JsonNode =
10011001
ensureReasoningField(messages)
10021002
var body = %*{
1003-
"model": p.modelPrefix & p.model,
1003+
"model": p.model,
10041004
"messages": messages,
10051005
"stream": true,
10061006
"stream_options": {"include_usage": true}
@@ -1011,7 +1011,7 @@ proc callModel*(p: Profile, messages: JsonNode, usage: var Usage, lastPromptToke
10111011
# we hit (Cerebras + Groq document it; Fireworks/DeepInfra/NVIDIA NIM
10121012
# inherit it) lands there. Forcing "high" while we shake out behavior;
10131013
# drop back to "medium" once we're done testing.
1014-
if p.model == "gpt-oss":
1014+
if p.family == "gpt-oss":
10151015
body["reasoning_effort"] = %"high"
10161016
let bodyStr = $body
10171017
let t0 = epochTime()
@@ -1137,7 +1137,7 @@ proc callModel*(p: Profile, messages: JsonNode, usage: var Usage, lastPromptToke
11371137

11381138
proc verifyProfile*(p: Profile): (bool, string) =
11391139
let body = $(%*{
1140-
"model": p.modelPrefix & p.model,
1140+
"model": p.model,
11411141
"messages": [%*{"role": "user", "content": "ping"}],
11421142
"max_tokens": 1,
11431143
"stream": false

‎src/threecode/compact.nim‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ proc callSummarizer(p: Profile, messages: JsonNode): string =
200200
continue
201201
payload.add m
202202
let body = %*{
203-
"model": p.modelPrefix & p.model,
203+
"model": p.model,
204204
"messages": payload,
205205
"max_tokens": SummarizeMaxTokens,
206206
"stream": false

‎src/threecode/config.nim‎

Lines changed: 58 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,47 @@
1-
import std/[os, parsecfg, streams, strformat, strutils, terminal, uri]
1+
import std/[os, parsecfg, streams, strformat, strutils, tables, terminal, uri]
22
import types, prompts
33

44
type
55
ProviderRec* = object
66
## In-memory mirror of a [provider] section. `family` is the optional
77
## experimental override (broad name like "glm"/"qwen"/"gpt-oss") used
88
## to pick a system prompt; only honored when --experimental is on.
9-
## Known-good combos ignore it. `models` is the list of API model ids
10-
## this provider exposes (stored without `modelPrefix`, which carries
11-
## the shared path prefix so it doesn't repeat per model).
9+
## Known-good combos ignore it. `models` is the list of full API model
10+
## ids (e.g. "openai/gpt-oss-120b") as sent on the wire. `modelPrefix`
11+
## is only populated transiently when reading old config files that
12+
## stored a separate `model_prefix` key; it is expanded into the model
13+
## ids on load and never written back out.
1214
name*, url*, key*, modelPrefix*, family*: string
1315
models*: seq[string]
1416

17+
proc shortModel*(model: string): string =
18+
## Everything after the last `/` in a model id. This is the
19+
## user-visible short name: `gpt-oss-120b` for `openai/gpt-oss-120b`,
20+
## `glm-5p1` for `accounts/fireworks/models/glm-5p1`. When there is no
21+
## slash, the model id is already a bare name and is returned as-is.
22+
let slash = model.rfind('/')
23+
if slash < 0: model else: model[slash + 1 .. ^1]
24+
25+
proc shortToFull*(models: seq[string]): Table[string, string] =
26+
## Maps each short model name (after the last `/`) to the full model id.
27+
## When two full ids share the same short name — e.g. nvidia sometimes
28+
## lists a model both as `org/model-name` and bare `model-name` — only
29+
## the first occurrence is kept. This mirrors the display list: the
30+
## user sees both names, picks the short one, and gets the first match.
31+
## If genuine ambiguity arises in the future we can promote a conflict
32+
## notice here; for now silent first-wins is the right trade-off.
33+
for m in models:
34+
let s = shortModel(m)
35+
if s notin result:
36+
result[s] = m
37+
1538
proc findModel*(p: ProviderRec, name: string): int =
39+
## Matches by full model id or by short name (everything after the last
40+
## `/`). Short-name matching handles `:variant <name>` from users who
41+
## type the bare model name and old `current = provider.shortname` config
42+
## values that haven't been rewritten yet.
1643
for i, m in p.models:
17-
if m == name: return i
44+
if m == name or shortModel(m) == name: return i
1845
-1
1946

2047
var activeCurrent*: string
@@ -42,7 +69,7 @@ proc explainExperimentalGate*(p: Profile) =
4269

4370
proc hasKnownGoodModel*(prov: ProviderRec): bool =
4471
for m in prov.models:
45-
if knownGoodFamily(prov.name, prov.modelPrefix & m) != "": return true
72+
if knownGoodFamily(prov.name, m) != "": return true
4673
false
4774

4875
proc firstKnownGoodCombo*(providers: seq[ProviderRec]): string =
@@ -53,7 +80,7 @@ proc firstKnownGoodCombo*(providers: seq[ProviderRec]): string =
5380
for pr in providers:
5481
if pr.url == "" or pr.key == "": continue
5582
for m in pr.models:
56-
if knownGoodFamily(pr.name, pr.modelPrefix & m) != "":
83+
if knownGoodFamily(pr.name, m) != "":
5784
return pr.name & "." & m
5885
""
5986

@@ -96,6 +123,15 @@ proc parseConfigFile*(path: string): (string, seq[ProviderRec]) =
96123
p.open(stream, path)
97124
proc flush() =
98125
if inProvider:
126+
# Backward compat: old configs wrote `model_prefix = "openai/"` and
127+
# stored bare names like `"gpt-oss-120b"` in `models`. Expand them
128+
# to full ids here so the rest of the codebase only ever sees full
129+
# ids. The prefix is never written back out.
130+
if prov.modelPrefix != "":
131+
for i in 0 ..< prov.models.len:
132+
if not prov.models[i].startsWith(prov.modelPrefix):
133+
prov.models[i] = prov.modelPrefix & prov.models[i]
134+
prov.modelPrefix = ""
99135
providers.add prov
100136
prov = ProviderRec()
101137
inProvider = false
@@ -146,8 +182,6 @@ proc writeConfigFile*(path: string, current: string,
146182
buf.add "name = " & quoteVal(pr.name) & "\n"
147183
buf.add "url = " & quoteVal(pr.url) & "\n"
148184
buf.add "key = " & quoteVal(pr.key) & "\n"
149-
if pr.modelPrefix != "":
150-
buf.add "model_prefix = " & quoteVal(pr.modelPrefix) & "\n"
151185
if pr.family != "":
152186
buf.add "family = " & quoteVal(pr.family) & "\n"
153187
buf.add "models = " & quoteVal(formatModels(pr.models)) & "\n"
@@ -185,15 +219,14 @@ proc buildProfile*(current: string, providers: seq[ProviderRec],
185219
if pr.name == name:
186220
if pr.url == "" or pr.key == "" or pr.models.len == 0:
187221
return Profile()
188-
if model == "":
189-
model = pr.models[0]
190-
if pr.findModel(model) < 0:
222+
let idx = if model == "": 0 else: pr.findModel(model)
223+
if idx < 0:
191224
return Profile()
192-
var prof = Profile(name: pr.name & "." & model, url: pr.url,
193-
key: pr.key, modelPrefix: pr.modelPrefix,
194-
model: model)
225+
let fullModel = pr.models[idx]
226+
var prof = Profile(name: pr.name & "." & fullModel, url: pr.url,
227+
key: pr.key, model: fullModel)
195228
prof.family = resolveFamily(pr, prof)
196-
let (_, ver, vrt) = knownGoodTags(pr.name, pr.modelPrefix & model)
229+
let (_, ver, vrt) = knownGoodTags(pr.name, fullModel)
197230
prof.version = ver
198231
prof.variant = vrt
199232
return prof
@@ -231,14 +264,14 @@ proc loadProfile*(wanted: string): Profile =
231264
if prov.url == "": die &"provider '{name}': url not set in {path}", ExitConfig
232265
if prov.key == "": die &"provider '{name}': key not set in {path}", ExitConfig
233266
if prov.models.len == 0: die &"provider '{name}': models not set in {path}", ExitConfig
234-
if model == "":
235-
model = prov.models[0]
236-
if prov.findModel(model) < 0:
267+
let idx = if model == "": 0 else: prov.findModel(model)
268+
if idx < 0:
237269
die &"provider '{name}': model '{model}' not in models list ({prov.models.join(\", \")})", ExitConfig
238-
var prof = Profile(name: prov.name & "." & model, url: prov.url, key: prov.key,
239-
modelPrefix: prov.modelPrefix, model: model)
270+
let fullModel = prov.models[idx]
271+
var prof = Profile(name: prov.name & "." & fullModel, url: prov.url,
272+
key: prov.key, model: fullModel)
240273
prof.family = resolveFamily(prov, prof)
241-
let (_, ver, vrt) = knownGoodTags(prov.name, prov.modelPrefix & model)
274+
let (_, ver, vrt) = knownGoodTags(prov.name, fullModel)
242275
prof.version = ver
243276
prof.variant = vrt
244277
if wanted == "" and not experimentalEnabled and not isKnownGood(prof):
@@ -312,44 +345,8 @@ proc defaultNameFromUrl*(url: string): string =
312345
if labels.len >= 2: labels[^2]
313346
else: labels[0]
314347
315-
proc commonModelPrefix*(models: seq[string]): string =
316-
## Path-style prefix (everything up to and including the last `/`)
317-
## shared by every entry. For a single model, returns its own path
318-
## prefix so providers like `openai/gpt-oss-120b` get split into a
319-
## `model_prefix` of `openai/` and a bare `gpt-oss-120b`. When no
320-
## entries share a slashed prefix, returns "".
321-
if models.len == 0: return ""
322-
if models.len == 1:
323-
let slash = models[0].rfind('/')
324-
return if slash < 0: "" else: models[0][0 .. slash]
325-
var prefix = models[0]
326-
for m in models[1 .. ^1]:
327-
var i = 0
328-
while i < prefix.len and i < m.len and prefix[i] == m[i]:
329-
inc i
330-
prefix = prefix[0 ..< i]
331-
if prefix.len == 0: return ""
332-
let slash = prefix.rfind('/')
333-
if slash < 0: "" else: prefix[0 .. slash]
334-
335-
proc shortModel*(model: string): string =
336-
## The portion of a model id after the last `/`. Used in `:model`
337-
## listings and other places where the path-style provider namespace
338-
## is redundant. `gpt-oss-120b` for `openai/gpt-oss-120b`,
339-
## `glm-5p1` for `accounts/fireworks/models/glm-5p1`.
340-
let slash = model.rfind('/')
341-
if slash < 0: model else: model[slash + 1 .. ^1]
342-
343-
proc curatedFor*(provider: string): (string, seq[string]) =
344-
## Returns (commonPrefix, modelsWithoutPrefix) from KnownGoodCombos
345-
## for the given provider name.
346-
var fullIds: seq[string]
348+
proc curatedFor*(provider: string): seq[string] =
349+
## Full model ids from KnownGoodCombos for the given provider name.
347350
let p = provider.toLowerAscii
348351
for c in KnownGoodCombos:
349-
if c[0].toLowerAscii == p: fullIds.add c[1]
350-
let prefix = commonModelPrefix(fullIds)
351-
var stripped: seq[string]
352-
for m in fullIds:
353-
if prefix != "" and m.startsWith(prefix): stripped.add m[prefix.len .. ^1]
354-
else: stripped.add m
355-
(prefix, stripped)
352+
if c[0].toLowerAscii == p: result.add c[1]

‎src/threecode/display.nim‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import std/[critbits, exitprocs, json, os, strformat, strutils, terminal]
2-
import types, util, prompts, session, actions, minline
2+
import types, util, config, prompts, session, actions, minline
33

44
# Three visible tiers, designed to read on both light + dark terminal
55
# backgrounds:
@@ -394,7 +394,7 @@ proc showProfile*(p: Profile) =
394394
let dot = p.name.find('.')
395395
let provider = if dot < 0: p.name else: p.name[0 ..< dot]
396396
stdout.styledWriteLine fgCyan, styleBright, " provider ", resetStyle, provider
397-
stdout.styledWriteLine fgCyan, styleBright, " model ", resetStyle, p.model
397+
stdout.styledWriteLine fgCyan, styleBright, " model ", resetStyle, shortModel(p.model)
398398

399399
# Track up-navigation so "down past last" can return to blank line.
400400
var navigatedUp*: bool = false

‎src/threecode/prompts.nim‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -919,7 +919,7 @@ proc knownGoodFamily*(p: Profile): string =
919919
let dot = p.name.find('.')
920920
if dot < 0: return ""
921921
let provider = p.name[0 ..< dot].toLowerAscii
922-
let model = (p.modelPrefix & p.model).toLowerAscii
922+
let model = p.model.toLowerAscii
923923
for combo in KnownGoodCombos:
924924
if combo[0].toLowerAscii == provider and
925925
combo[1].toLowerAscii == model: return combo[2]
@@ -959,9 +959,8 @@ proc buildCredit*(p: Profile): string =
959959
## `:provider`/`:model` switch mid-session.
960960
let dot = p.name.find('.')
961961
let provider = if dot < 0: p.name else: p.name[0 ..< dot]
962-
let model = p.modelPrefix & p.model
963-
if provider != "" and model != "":
964-
"Credit where it's due: you're " & model & ", served via " & provider & "."
962+
if provider != "" and p.model != "":
963+
"Credit where it's due: you're " & p.model & ", served via " & provider & "."
965964
else:
966965
"Credit where it's due — to whoever trained the weights driving you and the lab serving them."
967966

‎src/threecode/types.nim‎

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,14 @@ type
1616
offset*: int
1717
limit*: int
1818
Profile* = object
19-
## `model` is the API model string sent on the wire (e.g.
20-
## "openai/gpt-oss-120b"). `modelPrefix` is the path-prefix that's
21-
## stored separately in the config so provider namespaces don't get
22-
## repeated for every model in the list; it is concatenated to
23-
## `model` when forming the wire value. The remaining fields are
24-
## metadata pulled out of the known-good table: `family` ("glm" /
25-
## "qwen" / "gpt-oss") drives the (prompt, tools) tuple selection;
26-
## `version` and `variant` (e.g. "3", "480b") are informational
27-
## tags. In experimental mode `family` may also come from the
28-
## per-provider `family = ...` config override.
29-
name*, url*, key*, modelPrefix*, model*: string
19+
## `model` is the full wire value sent in the API `model` field
20+
## (e.g. "openai/gpt-oss-120b"). Display code shortens it with
21+
## `shortModel(model)` (everything after the last `/`). `family`
22+
## ("glm" / "qwen" / "gpt-oss") drives (prompt, tools) tuple
23+
## selection. `version` and `variant` (e.g. "3", "480b") are
24+
## informational tags from KnownGoodCombos. In experimental mode
25+
## `family` may also come from the per-provider config override.
26+
name*, url*, key*, model*: string
3027
family*, version*, variant*: string
3128
Usage* = object
3229
promptTokens*, completionTokens*, totalTokens*, cachedTokens*: int

0 commit comments

Comments
 (0)