This commit is contained in:
Timothy Jaeryang Baek 2026-09-19 17:05:57 -04:00
parent 25948ea213
commit 946be43237
2 changed files with 83 additions and 5 deletions

View file

@ -3062,6 +3062,17 @@ async def process_chat_payload(request, form_data, user, metadata, model):
'server': tool_server,
}
if terminal_id and terminal_capability:
from open_webui.utils.terminals import get_terminal_agents_md
agents_md = await get_terminal_agents_md(request, user, metadata, extra_params)
if agents_md:
form_data['messages'] = add_or_update_system_message(
agents_md,
form_data['messages'],
append=True,
)
if mcp_clients:
metadata['mcp_clients'] = mcp_clients

View file

@ -1,5 +1,8 @@
"""Shared routing helpers for admin-configured terminal servers."""
import asyncio
import logging
import posixpath
from urllib.parse import quote
from open_webui.utils.chat_id import is_saved_chat_id
@ -9,6 +12,9 @@ TERMINAL_CONTEXT_DEFAULT = 'default'
TERMINAL_CONTEXT_TYPES = {'chat', 'automation'}
TERMINAL_CONTEXT_ID_SOURCES = {'chat': 'chat_id', 'automation': 'automation_id'}
TERMINAL_CHAT_UPLOAD_MODES = {'default', 'filesystem'}
MAX_AGENTS_MD_BYTES = 32 * 1024
log = logging.getLogger(__name__)
def is_terminal_orchestrator(connection: dict) -> bool:
@ -131,7 +137,14 @@ async def get_terminal_request_info(request, user, metadata: dict, extra_params:
connection = next((item for item in connections if item.get('id') == terminal_id), None)
if connection:
if not connection.get('enabled', True):
terminal_context = 'automation' if metadata.get('automation_id') else 'chat'
context_id = terminal_context_id(connection, metadata, terminal_context)
config = terminal_context_config(connection, terminal_context)
if (
not connection.get('enabled', True)
or not terminal_context_available(connection, terminal_context)
or (config.get('context_id') in {'chat_id', 'automation_id'} and not context_id)
):
return None
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user_model.id)}
if not await has_connection_access(user_model, connection, user_group_ids):
@ -148,15 +161,17 @@ async def get_terminal_request_info(request, user, metadata: dict, extra_params:
headers['X-User-Id'] = user_model.id
if metadata.get('chat_id'):
headers['X-Session-Id'] = metadata['chat_id']
terminal_context = 'automation' if metadata.get('automation_id') else 'chat'
context_id = terminal_context_id(connection, metadata, terminal_context)
if context_id:
headers[TERMINAL_CONTEXT_HEADER] = context_id
return get_terminal_server_url(connection), headers, cookies
selector = str(terminal_id).rstrip('/')
direct_terminal = next(
(server for server in metadata.get('tool_servers') or [] if str(server.get('url') or '').rstrip('/') == selector),
(
server
for server in metadata.get('tool_servers') or []
if str(server.get('url') or '').rstrip('/') == selector
),
None,
)
if not direct_terminal:
@ -171,10 +186,62 @@ async def get_terminal_request_info(request, user, metadata: dict, extra_params:
return selector, headers, {}
async def get_terminal_skill(request, user, metadata: dict, skill_name: str, extra_params: dict | None = None) -> dict | None:
async def get_terminal_agents_md(request, user, metadata: dict, extra_params: dict | None = None) -> str | None:
"""Load the selected terminal user's home AGENTS.md afresh for this turn."""
import aiohttp
from open_webui.env import AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL
try:
async with asyncio.timeout(5):
terminal_request = await get_terminal_request_info(request, user, metadata, extra_params)
if not terminal_request:
return None
base_url, headers, cookies = terminal_request
async with aiohttp.ClientSession(
headers=headers, cookies=cookies, timeout=aiohttp.ClientTimeout(total=5), trust_env=True
) as session:
async with session.get(
f'{base_url.rstrip("/")}/files/cwd',
ssl=AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL,
allow_redirects=False,
) as response:
if response.status != 200:
log.debug('Skipping terminal AGENTS.md: home lookup returned HTTP %s', response.status)
return None
data = await response.json()
home = data.get('home') if isinstance(data, dict) else None
if not isinstance(home, str) or not posixpath.isabs(home):
return None
path = posixpath.join(home, 'AGENTS.md')
async with session.get(
f'{base_url.rstrip("/")}/files/read',
params={'path': path},
ssl=AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL,
allow_redirects=False,
) as response:
if response.status == 404:
return None
response.raise_for_status()
data = await response.json()
content = data.get('content') if isinstance(data, dict) else None
if not isinstance(content, str) or not content.strip():
return None
if len(content.encode('utf-8')) > MAX_AGENTS_MD_BYTES:
log.warning('Skipping terminal AGENTS.md: exceeds %s bytes', MAX_AGENTS_MD_BYTES)
return None
return f'# AGENTS.md instructions for {home}\n\n<INSTRUCTIONS>\n{content}\n</INSTRUCTIONS>'
except Exception as e:
log.debug('Failed to load terminal AGENTS.md (%s)', type(e).__name__)
return None
async def get_terminal_skill(
request, user, metadata: dict, skill_name: str, extra_params: dict | None = None
) -> dict | None:
from urllib.parse import quote
import aiohttp
from open_webui.env import AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL, AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA
terminal_request = await get_terminal_request_info(request, user, metadata, extra_params)