Skip to content

2026.10.5: model aliases and specified models in routing rules (core v0.62.0) - #284

Merged
fylorn merged 24 commits into
devfrom
model-aliases-2026.10.5
Oct 5, 2026
Merged

fylorn merged 24 commits into
devfrom
model-aliases-2026.10.5

Conversation

@fylorn

@fylorn fylorn commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Model aliases, specified models in routing rules and the Claude Desktop takeover without a Claude upstream, on core v0.62.0 (control-plane protocol 38), released as 2026.10.5. Answers ThinkWatchProject/ThinkWatch-Core#283.

Model aliases

  • Upstreams has an Aliases tab: the alias, the names it lists in order, and which upstream receives which name. A dot on the tab and a banner at its top offer to group the names one Claude model has across upstreams (official, Bedrock, Vertex, OpenRouter); the new-alias dialog offers the same.
  • The dialog previews the draft through POST /alias-preview: the name each upstream receives, upstream models of the same name it takes over, and connected clients that treat the name specially (Claude Desktop's model-name rule; Pi, oh-my-pi and Grok Build, whose API format follows the name).
  • Renaming goes through PUT /aliases/{name}, which updates the keys' scopes and the rules that name it; deleting shows what still refers to it (GET /aliases/{name}/usage).
  • An upstream's model list can start an alias from a model, and shows the aliases that list each model.
  • Key scope and model inputs show aliases with a mark, and allowing an upstream model shows that its aliases are allowed too.

Routing

  • A rule can forward to specified models: a list of upstream + model, tried in order, sent as written.
  • Model conditions mark the aliases a real model name also matches.
  • Traffic marks requests sent under an alias or to a specified model, and each attempt shows the model it sent and why; a candidate skipped because the key may not use its model reads "Model not allowed for this key".

Claude Desktop

  • Takeover follows the current model-name rule, 1M spellings included.
  • With no upstream offering a Claude model, takeover asks for an upstream model and adds a rule with that specified model on Claude Desktop's key; cancelling the takeover removes the rule.

Core v0.62.0

  • Generated types, the control-plane allowlist (/aliases, /alias-preview, the moved /provider-test, /provider-preview, /proxy-test, /plugin-* paths) and the Chinese translations of the new message codes.

2026.10.5

  • Version in the four files and release-notes/2026.10.5.md.
  • A 0.61.0 initial configuration and the current local configuration both pass twcore check on 0.62.0; request history is kept (schema unchanged).

Checked locally: typecheck, 834 unit tests, build, cargo fmt, clippy and the Rust tests.

🤖 Generated with Claude Code

fylorn and others added 24 commits October 5, 2026 09:47
The key dialog's visible-models table now lists aliases from GET /models
with an "alias" mark. An alias whose listed model is in scope (picked or
matched by a pattern) is shown checked and locked with "allowed with
<model>"; picking or matching only the alias leaves its models hidden.
Pattern hit counts stay name-based (aliases included), the visible count
includes inherited aliases, and a note under the table states the rule.
Inheritance is computed, never written: allow keeps the user's entries.

ModelInput accepts KnownModel-shaped options besides plain names; each
suggestion then carries a right-aligned muted note (providers, or
"alias · providers"), and the input marks a value that names an alias.

Types come from src/aliases/api.provisional.ts until core's bindings land.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s model

The traffic table's model column keeps the client's name. The upstream cell
now marks "Alias" when an alias made the serving attempt send another name,
and "Pinned" when the deciding rule pins upstream + model. The request
drawer's Routing tab adds an "Alias" row (resolved per upstream) or a
"Pinned model" row naming the rule, and the attempt chain lists each hop's
sent model when anything was renamed, with a tooltip worded by cause:
alias, rule rewrite (existing wording), pinned model, plugin, or a plain
statement when the cause no longer matches the current config.

The record has no "why": the cause is worked out from the client model,
each attempt's sent model, the route / rule / rewritten_by, and the
current alias table (GET /models alias items) and routes (overview).
When those disagree, nothing is claimed. Rows now carry route, rule,
rewritten_by and the serving hop's sent model from events and history.

src/aliases/api.provisional.ts holds the contract's provisional types.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The rule dialog's "Forward to" is now a choice between "Upstream or
  group" (today's select) and "Specified models": an ordered list of
  upstream + model rows (the model picked from that upstream's own list,
  with its context window), sent in order and as written, not through the
  alias table. With specified models, "Change model to" is hidden and not
  written to the config.
- RuleDraft carries the target kind and the pinned list; draftFromView /
  draftToInput round-trip both shapes of `to`, and validation asks for at
  least one row with an upstream and a model.
- The model condition's hint says that an alias matches only requests that
  use it, and that an upstream model name also matches the aliases that
  point to it (keeping the "matches N known models" count).
- The route dialog's rule table shows pinned targets as "upstream · model"
  with backups on the second line, marks an alias condition, and names the
  aliases a model name also matches. The route list and the route map
  handle pinned targets.
- The dry run shows the model each candidate is sent and where it came
  from (alias, rule rewrite, specified model).
- Provisional types until core's model aliases land: the contract's
  src/aliases/api.provisional.ts, plus src/routing/provisional.ts for the
  widened RuleView/RuleInput/DryRunResult shapes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… aliases and providers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nts, and aliases in the upstream models popover

- src/aliases/AliasDialog.tsx: name (red = problems from core that block saving,
  yellow = warnings), upstream models as a list input with the upstreams offering
  each name and unserved names flagged, the same model on other upstreams as
  checkboxes, and a read-only preview of the name each upstream receives
  (including upstreams that no longer receive the name). Previews through
  POST /aliases/preview as the draft changes; a name equal to a real model is
  listed first when creating; renames report which keys and rules changed.
- src-tauri/src/aliases.rs: `alias_hints` command. Hint codes with params (no
  sentences) for clients detected on this machine: Claude Code's own tier names,
  a name that looks like one vendor family listing another family's models, and
  whether a taken-over Claude Desktop shows the name; plus the taken-over clients
  whose model lists will be offered an update. tw-adopt's `cloud::is_alias` is now pub.
- Upstream models popover: an "alias x" mark on rows listed in an alias, and an
  "Add alias…" button on hover that opens the dialog with that model listed.
- The /aliases endpoints are called through `invoke("call")` with the provisional
  types until core ships them and the allowlists can name them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts key

When the gateway lists no model Claude Desktop accepts, the takeover
dialog now asks which upstream model Claude Desktop should use instead of
telling the user to hand-write a rewrite rule. Connecting writes
claude-sonnet-5 into its configuration as before and adds a pinned-model
rule on Claude Desktop's own key, first in the route that key uses:

  { name: Claude Desktop, when: { client: <its key> },
    to: [{ provider, model }, ...] }   # every upstream offering the model

Other clients are unaffected and no global alias is created. The rule is
written after the plan is checked and before the files, and is put back
if the files cannot be written. Restoring (one client, restore all,
uninstall) deletes it afterwards; when it cannot be deleted, the restore
still succeeds and says so. Connecting again changes the rule in place,
and deletes it once the gateway offers Claude models. A rule is ours only
while it keeps its name, its single key condition and a pinned target with
no deny or rewrite; rules the user changed are left alone, and a
same-named rule of the user's in that route stops the takeover.

- tw-adopt: Plan.pick_model (desktop::model_pick) replaces the
  adopt.plan.claude_desktop.no_claude_model note
- clients::desktop_rule: the rule's view, change, write, revert and
  removal, against the model-alias contract's JSON (to: string or
  [{provider, model}]) with provisional types until tw-api has them;
  tested against a fake control plane
- PlanView.desktop_rule; adopt_client takes the chosen model; new codes
  adopt.plan.gateway_changed, adopt.plan.claude_desktop.rule_name_taken,
  adopt.claude_desktop.rule_left / rule_unchecked / rule_not_reverted
- PlanDialog: the model choice and a "Gateway configuration" summary
- src/aliases/api.provisional.ts from the model-alias contract

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new tab between Upstreams and Proxies lists the aliases from
GET /aliases: each alias with the name every upstream receives for it,
its context window and 24-hour requests and cost, plus amber lines when
no upstream serves it or when an upstream's same-named model is shadowed.

- Suggestions for one model under different names show as a banner
  (one: "Create alias…"; several: a count and a dialog), and are listed
  directly in the empty state. "Dismiss" is remembered per suggestion with
  the guidance hints, so Settings' "show hidden hints" brings them back.
- The tab label carries the alias count and a dot while undismissed
  suggestions exist and the tab is not active.
- Row menu (also on right-click): Edit…, Copy name, View traffic, Delete….
- The delete dialog lists where the name is used (GET /aliases/{name}/usage
  plus connected clients whose config lists it) with links to each place.
- DetectedClient now carries the model list written in the client's
  config, so the delete dialog can name the clients that list the alias.

The create/edit dialog is mounted at integration (marked in
UpstreamsPage). The six alias endpoints are called through the `call`
command with provisional types until they are in the generated contract
and the webview allow-lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Points the six core dependencies at ThinkWatch-Core 4a1775d on
feat/model-aliases until v0.62.0 is tagged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s endpoints

Regenerates src/generated against protocol 38 and replaces every
provisional type with the generated one: the alias views, RuleTarget and
PinnedModel, KnownModel.alias/aliases, ModelRow.aliases and the dry-run
candidate_models. The six /aliases endpoints join WEBVIEW_ENDPOINTS and
ALLOWED, so src/aliases/api.ts calls them through the typed call() and
absorbs the dialog's separate API module.

The dry run now reads candidate_models directly; core sends sent_model for
every candidate, so the attempt list only names it when model_via says it
differs. The screenshot fixtures are regenerated against the same core and
the screenshot mocks answer the alias endpoints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chinese for the eight config.alias_* and six engine.pinned_* checks, the
four gw.model admission errors, control.replay_alias_unserved and
gw.plugin.alias_unserved, and 'alias' in the kind vocabulary used by
config.edit.* and control.name_*.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
desktop_rule.rs drops its own copies of the route, rule, key and model
types and calls ep::Overview, ep::KnownModels and ep::UpdateRoute
directly; rules read as RuleView are written back as RuleInput. The
pinned models shown in the confirmation are tw_api::PinnedModel, so
lite-api.ts keeps importing that type from tw-api.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/aliases/AliasMark.tsx replaces the five marks the lanes drew on their
own (key scope, model input, upstream models popover, rule conditions,
traffic upstream cell): a small secondary badge in the label size. The
traffic cell's Pinned mark shares it, since both explain why the model
sent differs from the one the client asked for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…code makes

The 'name looks like another family' hint listed clients from memory
(Claude Code, Codex, Qwen Code, opencode deciding parameters by model
name), which nothing in tw-adopt backs. It now names the adopted clients
whose config the adoption writes with a request format picked by model
name, Pi and oh-my-pi (pi::api_for) and Grok Build (grok::backend_for),
and only when the alias name and the upstream model get different
formats. The sentence says what follows: the request goes out in that
family's format and the gateway converts it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uncated

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es, moved plugin path

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fylorn
fylorn merged commit 9711256 into dev Oct 5, 2026
4 checks passed
@fylorn
fylorn deleted the model-aliases-2026.10.5 branch October 5, 2026 05:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant