mirror of
https://github.com/open-webui/open-webui.git
synced 2026-11-15 18:55:07 +03:00
7429093191
Cursor and Pi model names carry brackets, equals signs, commas or slashes, and Open WebUI kept those in the ids of their agent models, so a pinned Cursor or Pi model opened a new chat on "Select a model". Agent models now get ids made only of letters, digits, dashes and underscores, with a few extra characters added when the name had to change, so two models never end up with the same id. The agent itself still runs on its own model name. Existing pins, default model choices and automations that point at an agent model whose name had other characters (dots included, as in most Codex models) stop matching and need the model picked again, and an existing chat with such an agent answers "Agent model is unavailable in the selected terminal" until the model is picked again. Fixes #34168
65 lines
2.5 KiB
Python
65 lines
2.5 KiB
Python
"""Read native conversation state from signed, server-persisted chat messages."""
|
|
|
|
import hashlib
|
|
import re
|
|
from urllib.parse import quote
|
|
|
|
from open_webui.models.chats import ChatModel, Chats
|
|
from open_webui.utils.auth import decode_token
|
|
|
|
|
|
def model_id(state):
|
|
model = re.sub(r'[^a-zA-Z0-9_-]+', '-', state['model']).strip('-')
|
|
if model != state['model']:
|
|
# Keeps CLI model names apart that only differ in the replaced characters.
|
|
model = f'{model}-{hashlib.sha256(state["model"].encode()).hexdigest()[:8]}'
|
|
return f'agent:{quote(state["terminal_id"], safe="")}:{state["provider"]}:{model}'
|
|
|
|
|
|
async def ensure_chat_selections(chat):
|
|
if all(key in (chat.chat or {}) for key in ('models', 'terminal_id', 'working_directory')):
|
|
return chat
|
|
state = await get_session(chat)
|
|
if not state:
|
|
return chat
|
|
async with Chats._chat_transaction(chat.id) as (_, row):
|
|
if row is None:
|
|
return chat
|
|
# Check missing fields under the lock so concurrent selection changes win.
|
|
data = dict(row.chat or {})
|
|
data.setdefault('models', [model_id(state)])
|
|
data.setdefault('terminal_id', state['terminal_id'])
|
|
if data['terminal_id'] == state['terminal_id']:
|
|
data.setdefault('working_directory', state['cwd'])
|
|
row.chat = data
|
|
chat = ChatModel.model_validate(row)
|
|
return chat
|
|
|
|
|
|
async def get_session(chat, message_id=None):
|
|
message_id = (
|
|
message_id or getattr(chat, 'current_message_id', None) or (chat.chat or {}).get('history', {}).get('currentId')
|
|
)
|
|
if not message_id:
|
|
return None
|
|
messages = await Chats.get_messages_map_by_chat_id(chat.id) or {}
|
|
visited = set()
|
|
while message_id and message_id not in visited:
|
|
visited.add(message_id)
|
|
message = messages.get(message_id)
|
|
if not message:
|
|
return None
|
|
token = (message.get('meta') or {}).get('agent')
|
|
if token:
|
|
state = decode_token(token) if isinstance(token, str) else None
|
|
if state and state.get('purpose') == 'terminal-agent':
|
|
if state.get('chat') != chat.id:
|
|
return None # Forks and clones start independent native conversations.
|
|
if all(
|
|
isinstance(state.get(key), str) and state[key]
|
|
for key in ('terminal_id', 'provider', 'model', 'cwd', 'user')
|
|
):
|
|
return state
|
|
message_id = message.get('parentId')
|
|
return None
|