Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ad5c33a
Key scope and model input: show model aliases, inherit from real names
fylorn Oct 5, 2026
d7b6b71
Traffic: alias and pinned-model markers, and why each attempt sent it…
fylorn Oct 5, 2026
e469368
Routing rules: forward to specified models, alias-aware model conditions
fylorn Oct 5, 2026
a02eed1
Merge ma-ui-scope
fylorn Oct 5, 2026
e667ed9
Merge ma-ui-rules
fylorn Oct 5, 2026
32579fb
Merge ma-ui-traffic
fylorn Oct 5, 2026
54a276d
Routing dialogs pass model objects to ModelInput, so suggestions show…
fylorn Oct 5, 2026
ae51af9
Model aliases: the new/edit alias dialog, name hints about local clie…
fylorn Oct 5, 2026
eaca4d3
Claude Desktop takeover: choose its model and pin it with a rule on i…
fylorn Oct 5, 2026
98417e7
Merge ma-ui-dialog
fylorn Oct 5, 2026
e5d9017
Merge ma-ui-desktop
fylorn Oct 5, 2026
1984bbd
Model aliases: the Aliases tab on the upstreams page
fylorn Oct 5, 2026
387dfa0
Merge ma-ui-tab
fylorn Oct 5, 2026
3ee5da4
Upstreams page mounts the alias dialog for create and edit
fylorn Oct 5, 2026
7e5d05c
Pin core to the model-aliases commit (protocol 38)
fylorn Oct 5, 2026
7b0927b
Model aliases UI: use the generated core types and allowlist the alia…
fylorn Oct 5, 2026
b7111d0
Translate the model-alias messages from core
fylorn Oct 5, 2026
90fac2b
Claude Desktop rule: read and write routes with the core types
fylorn Oct 5, 2026
44522b3
One alias mark for every place that shows it
fylorn Oct 5, 2026
b7a8483
Alias name hints: name only clients whose format choice the adoption …
fylorn Oct 5, 2026
0672098
Route dialog keeps the alias mark visible when a long condition is tr…
fylorn Oct 5, 2026
c040fe4
Follow the final core: not_allowed skip label, WebSocket alias messag…
fylorn Oct 5, 2026
1a883cd
2026.10.5
fylorn Oct 5, 2026
720b97e
Use core v0.62.0
fylorn Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "thinkwatch-lite",
"private": true,
"description": "Desktop app for a local AI API gateway on macOS, Windows and Linux",
"version": "2026.10.4",
"version": "2026.10.5",
"type": "module",
"packageManager": "pnpm@11.13.0",
"scripts": {
Expand Down
26 changes: 26 additions & 0 deletions release-notes/2026.10.5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
**Upgrade notes:**
- The bundled core is now 0.62.0, which speaks control-plane protocol 38. A remote server has to run core 0.62.0 too: this version does not connect to 0.61.0, and 2026.10.4 and earlier do not connect to 0.62.0.
- Request history is kept.
- A key's model scope now also applies when no upstream reports a model list, on WebSocket connections such as Codex's, and to every backup in failover. A client that used a model outside its key's scope through one of these gaps is now refused.

**Model aliases:**
- Upstreams has a new Aliases tab. An alias is another name for the same model: list the names the model has on each upstream, and every upstream that offers one of them can serve it under its own name, as backups for one another.
- When the same Claude model has different names across upstreams (official, Bedrock, Vertex, OpenRouter), the tab and the new-alias dialog suggest grouping them, and a dot on the tab shows there is a suggestion.
- An alias can also be created from an upstream's model list. The dialog shows which upstream receives which name, and warns when the name takes over another upstream's model or when a connected client treats the name specially.
- Clients see aliases in their model lists, and answers carry the name the client asked for.
- In a key's model scope, allowing an upstream model also allows the aliases that list it.

**Routing:**
- A rule can forward to specified models: an upstream and one of its models, with backups tried in order. The model is sent as written.
- A model condition written for an upstream model also matches the aliases that list it.
- Traffic marks requests sent under an alias or to a specified model, and shows the model each attempt sent.

**Claude Desktop:**
- Takeover follows the model-name rule of the current Claude Desktop, including 1M spellings.
- When no upstream offers a Claude model, takeover asks which upstream model Claude Desktop should use and adds a routing rule for its own key. Cancelling the takeover removes the rule.

**Also:**
- Tool-call inspection has a new built-in rule, "Read or change ThinkWatch's own data", for tool calls whose path or command points into ThinkWatch's data directory, where every upstream key is kept. A document that only mentions the directory does not count.
- Model lists sent to clients carry each model's context window, which Claude Desktop, Hermes and others use to size conversations.
- Upstream addresses that end in their own API version, such as Zhipu's `…/api/paas/v4` and Ark's `…/api/v3`, work without a second `/v1`.
- System notifications name every built-in tool-call rule; two used to show their internal id.
10 changes: 10 additions & 0 deletions scripts/shots/core/en/dryrun.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
[
{
"candidate_models": [
{
"provider": "relay",
"sent_model": "claude-sonnet-5"
},
{
"provider": "anthropic",
"sent_model": "claude-sonnet-5"
}
],
"candidates": [
"relay",
"anthropic"
Expand Down
15 changes: 15 additions & 0 deletions scripts/shots/core/en/security.json
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,21 @@
"on_by_default": true,
"why": "Sends a credential to a host that is neither local nor the credential's own provider"
},
{
"action": "cut",
"custom": false,
"default_action": "cut",
"enabled": true,
"id": "thinkwatch-data",
"kind": "command",
"matcher": {
"check": "thinkwatch-data",
"kind": "builtin"
},
"name": "Read or change ThinkWatch's own data",
"on_by_default": true,
"why": "Reads or changes ThinkWatch's data directory, which holds every upstream key in plain text and the settings of these protections"
},
{
"action": "cut",
"custom": false,
Expand Down
2 changes: 1 addition & 1 deletion scripts/shots/core/en/status.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"api_version": 37,
"api_version": 38,
"clients": 4,
"config_path": "",
"gateway_addr": null,
Expand Down
10 changes: 10 additions & 0 deletions scripts/shots/core/zh/dryrun.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
[
{
"candidate_models": [
{
"provider": "relay",
"sent_model": "claude-sonnet-5"
},
{
"provider": "anthropic",
"sent_model": "claude-sonnet-5"
}
],
"candidates": [
"relay",
"anthropic"
Expand Down
15 changes: 15 additions & 0 deletions scripts/shots/core/zh/security.json
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,21 @@
"on_by_default": true,
"why": "Sends a credential to a host that is neither local nor the credential's own provider"
},
{
"action": "cut",
"custom": false,
"default_action": "cut",
"enabled": true,
"id": "thinkwatch-data",
"kind": "command",
"matcher": {
"check": "thinkwatch-data",
"kind": "builtin"
},
"name": "Read or change ThinkWatch's own data",
"on_by_default": true,
"why": "Reads or changes ThinkWatch's data directory, which holds every upstream key in plain text and the settings of these protections"
},
{
"action": "cut",
"custom": false,
Expand Down
2 changes: 1 addition & 1 deletion scripts/shots/core/zh/status.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"api_version": 37,
"api_version": 38,
"clients": 4,
"config_path": "",
"gateway_addr": null,
Expand Down
6 changes: 5 additions & 1 deletion scripts/shots/mock/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export function providerModels(name: string): ProviderModelsView | null {
price,
price_source: price ? priceSource(name) : null,
estimated: false,
aliases: [],
};
});
return {
Expand All @@ -209,7 +210,10 @@ export function providerModels(name: string): ProviderModelsView | null {
export function knownModels() {
const by = new Map<string, string[]>();
for (const p of providers()) for (const m of catalogOf(p)) by.set(m, [...(by.get(m) ?? []), p.name]);
return [...by.entries()].map(([id, ps]) => ({ id, providers: ps })).sort((a, b) => a.id.localeCompare(b.id));
// 示例配置里没有别名
return [...by.entries()]
.map(([id, ps]) => ({ id, providers: ps, aliases: [] }))
.sort((a, b) => a.id.localeCompare(b.id));
}

// ───────────────────────────────────────── 概览、密钥、安全、价格
Expand Down
7 changes: 7 additions & 0 deletions scripts/shots/mock/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ export const CORE: { [N in WebviewEndpoint]: Handler<N> } = {
UpdateGroup: refuse,
DeleteGroup: refuse,
KnownModels: () => knownModels(),
// 示例配置里没有别名
Aliases: () => ({ aliases: [], suggestions: [] }),
CreateAlias: refuse,
UpdateAlias: refuse,
DeleteAlias: refuse,
PreviewAlias: () => notFound("The alias preview"),
AliasUsage: (_req, [name]) => notFound(`The alias ${name}`),
// 没给时间窗就是今天:本地零点到现在(`Window` 的默认)
// 没给时间窗就是最近 7 天(`UpstreamHealth` 的默认)
UpstreamHealth: (req) => upstreamHealth(req.from_ms ?? NOW - 7 * DAY, req.to_ms ?? Date.now()),
Expand Down
48 changes: 24 additions & 24 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "thinkwatch-lite"
version = "2026.10.4"
version = "2026.10.5"
edition = "2024"
# **这个数决定依赖能升到哪一版。**edition 2024 的解析器只挑声明的 Rust
# 版本编得动的依赖:写 1.85 的时候,`cargo update` 一直停在旧的 time 和
Expand All @@ -26,12 +26,12 @@ license = "MIT"
# twcore 二进制必须和这里编译进去的协议镜像来自同一个 core 版本 —— 打包脚本正是
# 从 tw-api 锁到的 tag 去取二进制的(见 scripts/fetch-core.sh)。
[workspace.dependencies]
tw-api = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.61.0" }
tw-types = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.61.0" }
tw-yaml = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.61.0" }
tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.61.0" }
tw-watch = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.61.0" }
tw-link = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.61.0" }
tw-api = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.62.0" }
tw-types = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.62.0" }
tw-yaml = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.62.0" }
tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.62.0" }
tw-watch = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.62.0" }
tw-link = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.62.0" }

[lib]
name = "thinkwatch_lite_lib"
Expand Down
5 changes: 3 additions & 2 deletions src-tauri/crates/tw-adopt/src/cloud.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,9 @@ pub fn unpinned_models(home: &Path, settings: &str, around: &Around) -> Vec<&'st
out
}

/// Claude Code 的模型别名(model-config 一页的表)。带 `[1m]` 的也是
fn is_alias(m: &str) -> bool {
/// Claude Code 的模型别名(model-config 一页的表)。带 `[1m]` 的也是。它请求之前先换成完整的
/// 模型名 —— 网关的模型别名起成这些名称,Claude Code 的请求用不到(Lite 的 `aliases` 据此提示)
pub fn is_alias(m: &str) -> bool {
let m = m.trim().to_ascii_lowercase();
let m = m.strip_suffix("[1m]").unwrap_or(&m);
matches!(
Expand Down
Loading
Loading