fix(desktop): scope remote model catalog and primary-label REST to the focused profile
A shared dashboard's launch HERMES_HOME is not the selected profile. model.options now runs under @_profile_scoped, and global-remote REST keeps ?profile= even for the primary label. Co-authored-by: fangliquanflq <fangliquan@qq.com>
This commit is contained in:
committed by
brooklyn!
parent
8b28bdceb5
commit
0e7eebc266
@@ -334,10 +334,10 @@ const ROUTES = [
|
||||
expected: { backend: 'primary', descriptorProfile: null, scopePath: false }
|
||||
},
|
||||
{
|
||||
name: 'a renamed primary profile still owns the window backend',
|
||||
name: 'a renamed primary profile on a global remote is still scoped on the wire',
|
||||
profile: ' coder ',
|
||||
opts: { primaryProfile: 'coder', globalRemote: true },
|
||||
expected: { backend: 'primary', descriptorProfile: null, scopePath: false }
|
||||
expected: { backend: 'primary', descriptorProfile: 'coder', scopePath: true }
|
||||
},
|
||||
{
|
||||
name: 'an unset profile resolves to the primary',
|
||||
@@ -526,14 +526,14 @@ test('pathWithGlobalRemoteProfile appends profile in global remote mode', () =>
|
||||
)
|
||||
})
|
||||
|
||||
test('pathWithGlobalRemoteProfile skips the primary profile, which the remote already serves', () => {
|
||||
test('pathWithGlobalRemoteProfile scopes the primary label because the dashboard launch home may differ', () => {
|
||||
assert.equal(
|
||||
pathWithGlobalRemoteProfile('/api/model/info', 'coder', {
|
||||
globalRemote: true,
|
||||
primaryProfile: 'coder',
|
||||
profileRemoteOverride: false
|
||||
}),
|
||||
'/api/model/info'
|
||||
'/api/model/info?profile=coder'
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -653,7 +653,9 @@ function localPrimaryRequestScope(opts: ProfileRouteOptions): boolean | null {
|
||||
* The one place that answers "which backend serves profile P, and does its
|
||||
* REST path need a profile scope?". Six routes, in precedence order:
|
||||
*
|
||||
* 1. The primary profile owns the window backend outright.
|
||||
* 1. The primary profile owns a local/window backend outright; on a global
|
||||
* remote its label is still carried per request because launch home can
|
||||
* differ from the selected profile.
|
||||
* 2. A profile with its own remote override gets a pooled descriptor for that
|
||||
* host, which is already scoped to it.
|
||||
* 3. A profile inheriting the app-global remote shares the primary backend —
|
||||
@@ -673,10 +675,20 @@ function resolveProfileBackendRoute(profile, opts: ProfileRouteOptions = {}): Pr
|
||||
const scopedProfile = connectionScopeKey(profile)
|
||||
const primaryProfile = connectionScopeKey(opts.primaryProfile) || 'default'
|
||||
|
||||
if (!scopedProfile || scopedProfile === primaryProfile) {
|
||||
if (!scopedProfile) {
|
||||
return { backend: 'primary', descriptorProfile: null, scopePath: false }
|
||||
}
|
||||
|
||||
if (scopedProfile === primaryProfile) {
|
||||
// A global remote is a multi-profile dashboard, not a backend process
|
||||
// launched for this Desktop label. Even its "primary" label must travel on
|
||||
// the wire: the dashboard's process HERMES_HOME can belong to a different
|
||||
// launch profile, so a bare request silently reads that profile instead.
|
||||
return opts.globalRemote
|
||||
? { backend: 'primary', descriptorProfile: scopedProfile, scopePath: true }
|
||||
: { backend: 'primary', descriptorProfile: null, scopePath: false }
|
||||
}
|
||||
|
||||
if (opts.profileRemoteOverride) {
|
||||
return { backend: 'pool', descriptorProfile: null, scopePath: false }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
from pathlib import Path
|
||||
|
||||
import tui_gateway.server as server
|
||||
|
||||
|
||||
def test_model_options_binds_requested_profile_home(monkeypatch, tmp_path):
|
||||
profile_home = tmp_path / "profiles" / "fred-work"
|
||||
profile_home.mkdir(parents=True)
|
||||
seen = {}
|
||||
|
||||
monkeypatch.setattr(
|
||||
server,
|
||||
"_profile_home",
|
||||
lambda name: profile_home if name == "fred-work" else None,
|
||||
)
|
||||
monkeypatch.setattr(server, "_model_picker_context", lambda agent: object())
|
||||
|
||||
def build_payload(ctx, **kwargs):
|
||||
from hermes_constants import get_hermes_home
|
||||
|
||||
seen["home"] = Path(get_hermes_home())
|
||||
return {"providers": []}
|
||||
|
||||
monkeypatch.setattr("hermes_cli.inventory.build_model_options_payload", build_payload)
|
||||
|
||||
response = server._methods["model.options"](1, {"profile": "fred-work"})
|
||||
|
||||
assert response["result"] == {"providers": []}
|
||||
assert seen["home"] == profile_home
|
||||
@@ -467,6 +467,7 @@ def _(rid, params: dict) -> dict:
|
||||
|
||||
|
||||
@method("model.options")
|
||||
@_profile_scoped
|
||||
def _(rid, params: dict) -> dict:
|
||||
try:
|
||||
from hermes_cli.inventory import build_model_options_payload
|
||||
|
||||
Reference in New Issue
Block a user