feat: reasoning token budget + session name in header

- reasoning_budget_tokens: optional int field on local_openai models;
  when set, injects {"reasoning": {"budget_tokens": N}} via extra_body
  into every OpenRouter API call (both tool-loop and confirmation-gate
  rounds). Field exposed in the model edit form in Settings.

- session name moved from standalone full-row div between #messages
  and #input-area into the persona-switcher block in the header, as a
  third dim line under "Cortex · Local". Collapses when empty via
  :empty CSS. No JS changes required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-05-09 21:35:23 -04:00
parent 85792a7bcf
commit a66c5a7f84
5 changed files with 80 additions and 57 deletions

View File

@@ -664,7 +664,8 @@ def save_model(username: str, model_id: str | None, host_id: str,
label: str, model_name: str, context_k: int = 0,
tags: list[str] | None = None,
max_rounds: int | None = None,
tools: bool = True) -> str:
tools: bool = True,
reasoning_budget_tokens: int | None = None) -> str:
"""Create or update a local_openai model entry. Returns the model ID."""
data = _load(username)
tags = tags or []
@@ -679,6 +680,7 @@ def save_model(username: str, model_id: str | None, host_id: str,
m["max_rounds"] = max_rounds
m["tools"] = tools
m["tags"] = tags
m["reasoning_budget_tokens"] = reasoning_budget_tokens
_save(username, data)
return model_id
model_id = None
@@ -695,6 +697,7 @@ def save_model(username: str, model_id: str | None, host_id: str,
"max_rounds": max_rounds,
"tools": tools,
"tags": tags,
"reasoning_budget_tokens": reasoning_budget_tokens,
})
_save(username, data)
return model_id

View File

@@ -287,6 +287,9 @@ async def _run_from_messages(
if active_tools:
call_kwargs["tools"] = active_tools
call_kwargs["tool_choice"] = "auto"
reasoning_budget = (model_cfg or {}).get("reasoning_budget_tokens")
if reasoning_budget:
call_kwargs["extra_body"] = {"reasoning": {"budget_tokens": reasoning_budget}}
response = await _chat_with_retry(client, **call_kwargs)
choice = response.choices[0]
@@ -346,6 +349,8 @@ async def _run_from_messages(
conf_call: dict = {"model": model_name, "messages": messages, "tool_choice": "none"}
if active_tools:
conf_call["tools"] = active_tools
if reasoning_budget:
conf_call["extra_body"] = {"reasoning": {"budget_tokens": reasoning_budget}}
conf_resp = await _chat_with_retry(client, **conf_call)
final_response = conf_resp.choices[0].message.content or (
"This action requires your explicit confirmation before it can proceed."

View File

@@ -210,6 +210,7 @@ def _render(username: str, success: str = "", error: str = "") -> str:
cur_max_rounds = m.get("max_rounds") or 0
cur_tools = m.get("tools", True)
cur_tags = ", ".join(m.get("tags") or [])
cur_reasoning_budget = m.get("reasoning_budget_tokens") or 0
model_rows += f'''
<div class="model-row" id="model-{m["id"]}">
@@ -256,6 +257,11 @@ def _render(username: str, success: str = "", error: str = "") -> str:
<input type="number" name="max_rounds" value="{cur_max_rounds}" min="0"
title="Per-model tool loop cap. 0 = use the global default (orchestrator_max_rounds).">
</div>
<div class="field" style="flex:0 0 auto">
<label title="OpenRouter reasoning budget in tokens. 0 = no reasoning override (model default). Injects reasoning.budget_tokens into the API call.">Reasoning tokens</label>
<input type="number" name="reasoning_budget_tokens" value="{cur_reasoning_budget}" min="0"
title="OpenRouter reasoning budget in tokens. 0 = disabled. E.g. 2048 for light thinking, 8192 for deep reasoning.">
</div>
<div class="field" style="flex:0 0 auto">
<label title="Whether this model supports tool calling. If not supported, requests skip the tool loop entirely.">Tool calling</label>
<select name="tools"
@@ -466,6 +472,7 @@ async def add_model(
max_rounds: int = Form(0),
tools: int = Form(1),
tags: str = Form(""),
reasoning_budget_tokens: int = Form(0),
# local-only fields
host_id: str = Form(""),
model_name: str = Form(""),
@@ -481,6 +488,7 @@ async def add_model(
tag_list = [t.strip() for t in tags.split(",") if t.strip()]
max_rounds_ = max_rounds or None
tools_bool = tools != 0
reasoning_budget_ = reasoning_budget_tokens or None
if provider == "local":
if not model_name.strip():
@@ -488,7 +496,8 @@ async def add_model(
if not host_id.strip():
return HTMLResponse(_render(username, error="Select a host."))
reg.save_model(username, None, host_id, label, model_name, context_k, tag_list,
max_rounds=max_rounds_, tools=tools_bool)
max_rounds=max_rounds_, tools=tools_bool,
reasoning_budget_tokens=reasoning_budget_)
display = label or model_name
elif provider in ("google", "anthropic"):
@@ -522,6 +531,7 @@ async def edit_model(
max_rounds: int = Form(0),
tools: int = Form(1),
tags: str = Form(""),
reasoning_budget_tokens: int = Form(0),
host_id: str = Form(""),
account_id: str = Form(""),
credential_id: str = Form("cli"),
@@ -534,11 +544,13 @@ async def edit_model(
tag_list = [t.strip() for t in tags.split(",") if t.strip()]
max_rounds_ = max_rounds or None
tools_bool = tools != 0
reasoning_budget_ = reasoning_budget_tokens or None
if mtype == "local_openai":
if not host_id.strip():
return HTMLResponse(_render(username, error="Select a host for this model."))
reg.save_model(username, model_id, host_id, label, model_name, context_k, tag_list,
max_rounds=max_rounds_, tools=tools_bool)
max_rounds=max_rounds_, tools=tools_bool,
reasoning_budget_tokens=reasoning_budget_)
elif mtype == "gemini_api":
reg.save_cloud_model(username, model_id, "google", model_name, label,
account_id=account_id or None, context_k=context_k, tags=tag_list,

View File

@@ -41,6 +41,7 @@
<div class="persona-switcher" id="persona-switcher">
<div class="name" id="persona-name">Inara</div>
<div class="subtitle">Cortex · Local</div>
<div id="session-id"></div>
<div class="persona-dropdown" id="persona-dropdown"></div>
</div>
@@ -164,7 +165,6 @@
</div>
<div id="messages"></div>
<div id="session-id"></div>
<div id="input-area">
<!-- Mode select — compact dropdown, opens upward, MRU sorted -->

View File

@@ -897,11 +897,14 @@
#stop:hover { background: #5c1a1a; }
#session-id {
font-size: 0.7rem;
font-size: 0.68rem;
color: var(--border);
padding: 0 20px 6px;
background: var(--surface);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 220px;
}
#session-id:empty { display: none; }
/* ── Message wrappers (edit/delete controls) ──────────────── */
.msg-wrapper {