fix(desktop): keep model runtime state per session (#43702)
* fix(desktop): keep model runtime state per session (cherry picked from commit f72ee87d99ee38cb7b5badeb9a8af869bb92073a) * fix(desktop): keep footer model state scoped to active session (cherry picked from commit d91942ebd4671ff857b5c8526dbf133f04782ecb) * fix(desktop): restore stored runtime when resuming sessions (cherry picked from commit 32b3793418257617b8da57e26151f079c2620d00) * fix(desktop): persist live runtime changes for resume (cherry picked from commit c58467779436dcef44a80ad55b52664752dc0837) * fix(desktop): persist resumed endpoint runtime * chore(attribution): map pinguarmy's commit email in AUTHOR_MAP The salvaged commits on this branch preserve @pinguarmy's authorship (郝鹏宇 / peterhao@Peters-MacBook-Air.local). Add the mapping so the check-attribution CI gate resolves the email to the GitHub username. --------- Co-authored-by: 郝鹏宇 <peterhao@Peters-MacBook-Air.local>
This commit is contained in:
parent
07ac185904
commit
6de3963e37
13 changed files with 474 additions and 32 deletions
|
|
@ -878,6 +878,97 @@ def test_session_resume_uses_parent_lineage_for_display(monkeypatch):
|
|||
assert captured["history_calls"] == [("tip", False), ("tip", True)]
|
||||
|
||||
|
||||
def test_session_resume_passes_stored_runtime_to_agent(monkeypatch):
|
||||
captured = {}
|
||||
|
||||
class FakeDB:
|
||||
def get_session(self, target):
|
||||
return {
|
||||
"id": target,
|
||||
"model": "gpt-5.4",
|
||||
"billing_provider": "openai-codex",
|
||||
"model_config": '{"reasoning_config":{"enabled":true,"effort":"high"},"service_tier":"priority","base_url":"https://custom.example/v1","api_mode":"chat_completions"}',
|
||||
}
|
||||
|
||||
def reopen_session(self, target):
|
||||
pass
|
||||
|
||||
def get_messages_as_conversation(self, target, include_ancestors=False):
|
||||
return [{"role": "user", "content": "hello"}]
|
||||
|
||||
def fake_make_agent(sid, key, session_id=None, session_db=None, **kwargs):
|
||||
captured.update(kwargs)
|
||||
return types.SimpleNamespace(model="gpt-5.4", provider="openai-codex")
|
||||
|
||||
monkeypatch.setattr(server, "_get_db", lambda: FakeDB())
|
||||
monkeypatch.setattr(server, "_enable_gateway_prompts", lambda: None)
|
||||
monkeypatch.setattr(server, "_set_session_context", lambda target: [])
|
||||
monkeypatch.setattr(server, "_clear_session_context", lambda tokens: None)
|
||||
monkeypatch.setattr(server, "_make_agent", fake_make_agent)
|
||||
monkeypatch.setattr(server, "_session_info", lambda agent, *a: {"model": agent.model, "provider": agent.provider})
|
||||
|
||||
def fake_init_session(sid, key, agent, history, cols=80):
|
||||
server._sessions[sid] = {"agent": agent, "session_key": key}
|
||||
|
||||
monkeypatch.setattr(server, "_init_session", fake_init_session)
|
||||
|
||||
resp = server.handle_request(
|
||||
{"id": "1", "method": "session.resume", "params": {"session_id": "stored-session"}}
|
||||
)
|
||||
|
||||
assert resp["result"]["info"] == {"model": "gpt-5.4", "provider": "openai-codex"}
|
||||
assert captured["model_override"] == {
|
||||
"model": "gpt-5.4",
|
||||
"provider": "openai-codex",
|
||||
"base_url": "https://custom.example/v1",
|
||||
"api_mode": "chat_completions",
|
||||
}
|
||||
assert captured["provider_override"] == "openai-codex"
|
||||
assert captured["reasoning_config_override"] == {"enabled": True, "effort": "high"}
|
||||
assert captured["service_tier_override"] == "priority"
|
||||
runtime_sid = resp["result"]["session_id"]
|
||||
assert server._sessions[runtime_sid]["model_override"] == captured["model_override"]
|
||||
|
||||
|
||||
def test_persist_live_session_runtime_preserves_resume_metadata(monkeypatch):
|
||||
updates = {}
|
||||
|
||||
class FakeDB:
|
||||
def get_session(self, session_id):
|
||||
assert session_id == "stored-session"
|
||||
return {"model_config": '{"_branched_from":"root"}'}
|
||||
|
||||
def update_session_meta(self, session_id, model_config_json, model=None):
|
||||
updates["meta"] = (session_id, json.loads(model_config_json), model)
|
||||
|
||||
agent = types.SimpleNamespace(
|
||||
model="gpt-5.4",
|
||||
provider="openai-codex",
|
||||
base_url="https://custom.example/v1",
|
||||
api_mode="chat_completions",
|
||||
reasoning_config={"enabled": True, "effort": "high"},
|
||||
service_tier="priority",
|
||||
_session_db=FakeDB(),
|
||||
)
|
||||
|
||||
server._persist_live_session_runtime({"agent": agent, "session_key": "stored-session"})
|
||||
|
||||
assert "model" not in updates
|
||||
assert updates["meta"] == (
|
||||
"stored-session",
|
||||
{
|
||||
"_branched_from": "root",
|
||||
"model": "gpt-5.4",
|
||||
"provider": "openai-codex",
|
||||
"base_url": "https://custom.example/v1",
|
||||
"api_mode": "chat_completions",
|
||||
"reasoning_config": {"enabled": True, "effort": "high"},
|
||||
"service_tier": "priority",
|
||||
},
|
||||
"gpt-5.4",
|
||||
)
|
||||
|
||||
|
||||
def test_status_callback_emits_kind_and_text():
|
||||
with patch("tui_gateway.server._emit") as emit:
|
||||
cb = server._agent_cbs("sid")["status_callback"]
|
||||
|
|
@ -3593,8 +3684,9 @@ def test_session_info_includes_mcp_servers(monkeypatch):
|
|||
fake_mod.get_mcp_status = lambda: fake_status
|
||||
monkeypatch.setitem(sys.modules, "tools.mcp_tool", fake_mod)
|
||||
|
||||
info = server._session_info(types.SimpleNamespace(tools=[], model=""))
|
||||
info = server._session_info(types.SimpleNamespace(tools=[], model="", provider="openai-codex"))
|
||||
|
||||
assert info["provider"] == "openai-codex"
|
||||
assert info["mcp_servers"] == fake_status
|
||||
|
||||
|
||||
|
|
@ -5983,6 +6075,45 @@ def test_make_agent_defaults_to_90(monkeypatch):
|
|||
assert mock_agent.call_args.kwargs["max_iterations"] == 90
|
||||
|
||||
|
||||
def test_make_agent_uses_session_runtime_overrides(monkeypatch):
|
||||
_setup_make_agent_mocks(monkeypatch, {})
|
||||
resolved = {}
|
||||
|
||||
def fake_resolve_runtime_provider(requested=None, target_model=None):
|
||||
resolved["requested"] = requested
|
||||
resolved["target_model"] = target_model
|
||||
return {
|
||||
"provider": requested,
|
||||
"base_url": None,
|
||||
"api_key": None,
|
||||
"api_mode": None,
|
||||
"command": None,
|
||||
"args": None,
|
||||
"credential_pool": None,
|
||||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.runtime_provider.resolve_runtime_provider",
|
||||
fake_resolve_runtime_provider,
|
||||
)
|
||||
|
||||
with patch("run_agent.AIAgent") as mock_agent:
|
||||
server._make_agent(
|
||||
"sid1",
|
||||
"key1",
|
||||
model_override="gpt-5.4",
|
||||
provider_override="openai-codex",
|
||||
reasoning_config_override={"enabled": True, "effort": "high"},
|
||||
service_tier_override="priority",
|
||||
)
|
||||
|
||||
assert resolved == {"requested": "openai-codex", "target_model": "gpt-5.4"}
|
||||
assert mock_agent.call_args.kwargs["model"] == "gpt-5.4"
|
||||
assert mock_agent.call_args.kwargs["provider"] == "openai-codex"
|
||||
assert mock_agent.call_args.kwargs["reasoning_config"] == {"enabled": True, "effort": "high"}
|
||||
assert mock_agent.call_args.kwargs["service_tier"] == "priority"
|
||||
|
||||
|
||||
def test_make_agent_handles_null_agent_config(monkeypatch):
|
||||
_setup_make_agent_mocks(monkeypatch, {"agent": None, "max_turns": 80})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue