This commit is contained in:
Timothy Jaeryang Baek 2026-09-24 12:34:04 -04:00
parent 893251ed11
commit 7ad0ae4687
4 changed files with 76 additions and 114 deletions

View file

@ -3484,6 +3484,7 @@ async def view_skill(
__request__: Request = None,
__user__: dict = None,
__metadata__: dict = None,
__event_call__: callable = None,
) -> str:
"""
Load the full instructions of a skill by its id from the available skills manifest.
@ -3504,7 +3505,9 @@ async def view_skill(
from open_webui.utils.terminals import get_terminal_skill
skill_name = unquote(id.removeprefix(terminal_skill_prefix))
skill = await get_terminal_skill(__request__, __user__, __metadata__ or {}, skill_name)
skill = await get_terminal_skill(
__request__, __user__, __metadata__ or {}, skill_name, {'__event_call__': __event_call__}
)
if not skill:
return JSONCodec.dumps({'error': f"Skill '{id}' not found"})
return JSONCodec.dumps(skill, ensure_ascii=False)

View file

@ -2812,13 +2812,11 @@ async def process_chat_payload(request, form_data, user, metadata, model):
)
if skill_ids or use_builtin_tools:
import aiohttp
from open_webui.env import AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL, AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA
from open_webui.models.skills import Skills as SkillsModel
from open_webui.utils.terminals import (
format_terminal_skill_context,
format_terminal_skill_manifest_entry,
get_terminal_request_info,
get_terminal_json,
get_terminal_skill,
)
@ -2852,49 +2850,34 @@ async def process_chat_payload(request, form_data, user, metadata, model):
f'<description>{skill.description or ""}</description>\n</skill>\n'
)
terminal_request = (
await get_terminal_request_info(request, user, metadata, extra_params)
listed = (
await get_terminal_json(request, user, metadata, '/skills', extra_params)
if terminal_id or terminal_skill_ids
else None
)
listed_terminal_skills = []
if terminal_request:
terminal_base_url, terminal_headers, terminal_cookies = terminal_request
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA)
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
async with session.get(
f'{terminal_base_url.rstrip("/")}/skills',
headers=terminal_headers,
cookies=terminal_cookies,
ssl=AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL,
) as response:
if response.status == 200:
listed = await response.json()
listed_terminal_skills = listed if isinstance(listed, list) else []
listed_terminal_skills = listed if isinstance(listed, list) else []
if terminal_id and use_builtin_tools:
terminal_skills = listed_terminal_skills
elif terminal_skill_ids:
terminal_skill_map = {skill['id']: skill for skill in listed_terminal_skills}
terminal_skills = [skill for sid in terminal_skill_ids if (skill := terminal_skill_map.get(sid))]
if terminal_id and use_builtin_tools:
terminal_skills = listed_terminal_skills
elif terminal_skill_ids:
terminal_skill_map = {skill['id']: skill for skill in listed_terminal_skills}
terminal_skills = [skill for sid in terminal_skill_ids if (skill := terminal_skill_map.get(sid))]
for skill in terminal_skills:
sid = skill['id']
if sid in mentioned_skill_ids or not use_builtin_tools:
skill_name = unquote(sid.removeprefix(terminal_skill_prefix))
loaded = await get_terminal_skill(
request, user.model_dump(), metadata, skill_name, extra_params
)
if loaded:
form_data['messages'] = add_or_update_system_message(
format_terminal_skill_context(loaded),
form_data['messages'],
append=True,
)
else:
view_skill_ids.append(sid)
skill_manifest += format_terminal_skill_manifest_entry(skill)
for skill in terminal_skills:
sid = skill['id']
if sid in mentioned_skill_ids or not use_builtin_tools:
skill_name = unquote(sid.removeprefix(terminal_skill_prefix))
loaded = await get_terminal_skill(request, user.model_dump(), metadata, skill_name, extra_params)
if loaded:
form_data['messages'] = add_or_update_system_message(
format_terminal_skill_context(loaded),
form_data['messages'],
append=True,
)
else:
view_skill_ids.append(sid)
skill_manifest += format_terminal_skill_manifest_entry(skill)
if skill_manifest:
form_data['messages'] = add_or_update_system_message(

View file

@ -119,12 +119,15 @@ def terminal_chat_uploads(connection: dict) -> str:
return value if value in TERMINAL_CHAT_UPLOAD_MODES else 'default'
async def get_terminal_request_info(request, user, metadata: dict, extra_params: dict | None = None):
async def get_terminal_json(request, user, metadata: dict, path: str, extra_params: dict | None = None):
"""Read from an admin terminal on the backend or a personal terminal in its browser."""
import aiohttp
from open_webui.env import AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL, AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA
from open_webui.models.config import Config
from open_webui.models.groups import Groups
from open_webui.models.users import UserModel
from open_webui.utils.access_control import has_connection_access
from open_webui.utils.headers import bearer_auth_header
from open_webui.utils.tools import build_tool_server_headers
metadata = metadata or {}
@ -163,66 +166,39 @@ async def get_terminal_request_info(request, user, metadata: dict, extra_params:
headers['X-Session-Id'] = metadata['chat_id']
if context_id:
headers[TERMINAL_CONTEXT_HEADER] = context_id
return get_terminal_server_url(connection), headers, cookies
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA)
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
async with session.get(
f'{get_terminal_server_url(connection)}{path}',
headers=headers,
cookies=cookies,
ssl=AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL,
allow_redirects=False,
) as response:
return await response.json() if response.status == 200 else None
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
),
None,
)
if not direct_terminal:
return None
headers = {'Accept': 'application/json'}
key = str(direct_terminal.get('key') or '').strip()
if key:
headers.update(bearer_auth_header(key))
if metadata.get('chat_id'):
headers['X-Session-Id'] = metadata['chat_id']
return selector, headers, {}
event_caller = (extra_params or {}).get('__event_call__')
if event_caller:
result = await event_caller(
{
'type': 'request:terminal',
'data': {'terminal_id': terminal_id, 'path': path, 'session_id': metadata.get('session_id')},
}
)
return result.get('data') if isinstance(result, dict) else None
return 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:
data = await get_terminal_json(request, user, metadata, '/files/cwd', extra_params)
home = data.get('home') if isinstance(data, dict) else None
if not isinstance(home, str) or not posixpath.isabs(home):
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()
path = quote(posixpath.join(home, 'AGENTS.md'), safe='')
data = await get_terminal_json(request, user, metadata, f'/files/read?path={path}', extra_params)
content = data.get('content') if isinstance(data, dict) else None
if not isinstance(content, str) or not content.strip():
@ -247,25 +223,9 @@ def add_terminal_agents_md(messages: list[dict], agents_md: str) -> list[dict]:
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)
if not terminal_request:
return None
base_url, headers, cookies = terminal_request
timeout = aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT_TOOL_SERVER_DATA)
async with aiohttp.ClientSession(timeout=timeout, trust_env=True) as session:
async with session.get(
f'{base_url.rstrip("/")}/skills/{quote(skill_name, safe="")}',
headers=headers,
cookies=cookies,
ssl=AIOHTTP_CLIENT_SESSION_TOOL_SERVER_SSL,
) as response:
skill = await response.json() if response.status == 200 else None
skill = await get_terminal_json(
request, user, metadata, f'/skills/read?name={quote(skill_name, safe="")}', extra_params
)
if not isinstance(skill, dict):
return None

View file

@ -81,6 +81,7 @@
import { getUserSettings } from '$lib/apis/users';
import dayjs from 'dayjs';
import { getChannels } from '$lib/apis/channels';
import { resolveTerminalConnection, terminalRequest } from '$lib/apis/terminal';
const unregisterServiceWorkers = async () => {
if ('serviceWorker' in navigator) {
@ -645,6 +646,21 @@
console.log('execute:tool', data);
executeTool(data, cb, event.chat_id);
return;
} else if (type === 'request:terminal') {
try {
const connection = resolveTerminalConnection(
data.terminal_id,
[],
$settings?.terminalServers ?? [],
localStorage.token
);
if (!connection) throw new Error('Terminal Not Found');
const result = await terminalRequest(connection, event.chat_id, data.path);
cb?.({ data: result });
} catch (error) {
cb?.({ error: `${error}` });
}
return;
} else if (type === 'request:chat:completion') {
console.log(data, $socket.id);
const { session_id, channel, form_data, model } = data;