webui: Agentic Loop + MCP Client with support for Tools, Resources and Prompts (#18655)
This commit is contained in:
committed by
GitHub
parent
2850bc6a13
commit
f6235a41ef
@@ -1,3 +1,20 @@
|
||||
import type { AgenticConfig } from '$lib/types/agentic';
|
||||
|
||||
export const ATTACHMENT_SAVED_REGEX = /\[Attachment saved: ([^\]]+)\]/;
|
||||
|
||||
export const NEWLINE_SEPARATOR = '\n';
|
||||
|
||||
export const TURN_LIMIT_MESSAGE = '\n\n```\nTurn limit reached\n```\n';
|
||||
|
||||
export const LLM_ERROR_BLOCK_START = '\n\n```\nUpstream LLM error:\n';
|
||||
export const LLM_ERROR_BLOCK_END = '\n```\n';
|
||||
|
||||
export const DEFAULT_AGENTIC_CONFIG: AgenticConfig = {
|
||||
enabled: true,
|
||||
maxTurns: 100,
|
||||
maxToolPreviewLines: 25
|
||||
} as const;
|
||||
|
||||
// Agentic tool call tag markers
|
||||
export const AGENTIC_TAGS = {
|
||||
TOOL_CALL_START: '<<<AGENTIC_TOOL_CALL_START>>>',
|
||||
@@ -13,6 +30,9 @@ export const REASONING_TAGS = {
|
||||
END: '<<<reasoning_content_end>>>'
|
||||
} as const;
|
||||
|
||||
// Regex for trimming leading/trailing newlines
|
||||
export const TRIM_NEWLINES_REGEX = /^\n+|\n+$/g;
|
||||
|
||||
// Regex patterns for parsing agentic content
|
||||
export const AGENTIC_REGEX = {
|
||||
// Matches completed tool calls (with END marker)
|
||||
@@ -32,6 +52,10 @@ export const AGENTIC_REGEX = {
|
||||
REASONING_BLOCK: /<<<reasoning_content_start>>>[\s\S]*?<<<reasoning_content_end>>>/g,
|
||||
// Matches an opening reasoning tag and any remaining content (unterminated)
|
||||
REASONING_OPEN: /<<<reasoning_content_start>>>[\s\S]*$/,
|
||||
// Matches a complete agentic tool call display block (start to end marker)
|
||||
AGENTIC_TOOL_CALL_BLOCK: /\n*<<<AGENTIC_TOOL_CALL_START>>>[\s\S]*?<<<AGENTIC_TOOL_CALL_END>>>/g,
|
||||
// Matches a pending/partial agentic tool call (start marker with no matching end)
|
||||
AGENTIC_TOOL_CALL_OPEN: /\n*<<<AGENTIC_TOOL_CALL_START>>>[\s\S]*$/,
|
||||
// Matches tool name inside content
|
||||
TOOL_NAME_EXTRACT: /<<<TOOL_NAME:([^>]+)>>>/
|
||||
} as const;
|
||||
|
||||
@@ -3,3 +3,6 @@ export const API_MODELS = {
|
||||
LOAD: '/models/load',
|
||||
UNLOAD: '/models/unload'
|
||||
};
|
||||
|
||||
/** CORS proxy endpoint path */
|
||||
export const CORS_PROXY_ENDPOINT = '/cors-proxy';
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export const ATTACHMENT_LABEL_FILE = 'File';
|
||||
export const ATTACHMENT_LABEL_PDF_FILE = 'PDF File';
|
||||
export const ATTACHMENT_LABEL_MCP_PROMPT = 'MCP Prompt';
|
||||
export const ATTACHMENT_LABEL_MCP_RESOURCE = 'MCP Resource';
|
||||
|
||||
@@ -27,6 +27,18 @@ export const MODEL_PROPS_CACHE_TTL_MS = 10 * 60 * 1000;
|
||||
*/
|
||||
export const MODEL_PROPS_CACHE_MAX_ENTRIES = 50;
|
||||
|
||||
/**
|
||||
* Maximum number of MCP resources to cache
|
||||
* @default 50
|
||||
*/
|
||||
export const MCP_RESOURCE_CACHE_MAX_ENTRIES = 50;
|
||||
|
||||
/**
|
||||
* TTL for MCP resource cache entries in milliseconds
|
||||
* @default 5 minutes
|
||||
*/
|
||||
export const MCP_RESOURCE_CACHE_TTL_MS = 5 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Maximum number of inactive conversation states to keep in memory
|
||||
* States for conversations beyond this limit will be cleaned up
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export const INITIAL_FILE_SIZE = 0;
|
||||
export const PROMPT_CONTENT_SEPARATOR = '\n\n';
|
||||
export const CLIPBOARD_CONTENT_QUOTE_PREFIX = '"';
|
||||
export const PROMPT_TRIGGER_PREFIX = '/';
|
||||
export const RESOURCE_TRIGGER_PREFIX = '@';
|
||||
|
||||
@@ -8,3 +8,12 @@ export const INPUT_CLASSES = `
|
||||
outline-none
|
||||
text-foreground
|
||||
`;
|
||||
|
||||
export const PANEL_CLASSES = `
|
||||
bg-background
|
||||
border border-border/30 dark:border-border/20
|
||||
shadow-sm backdrop-blur-lg!
|
||||
rounded-t-lg!
|
||||
`;
|
||||
|
||||
export const CHAT_FORM_POPOVER_MAX_HEIGHT = 'max-h-80';
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export const GOOGLE_FAVICON_BASE_URL = 'https://www.google.com/s2/favicons';
|
||||
export const DEFAULT_FAVICON_SIZE = 32;
|
||||
export const DOMAIN_SEPARATOR = '.';
|
||||
export const ROOT_DOMAIN_MIN_PARTS = 2;
|
||||
@@ -11,14 +11,19 @@ export * from './chat-form';
|
||||
export * from './code-blocks';
|
||||
export * from './code';
|
||||
export * from './css-classes';
|
||||
export * from './favicon';
|
||||
export * from './floating-ui-constraints';
|
||||
export * from './formatters';
|
||||
export * from './key-value-pairs';
|
||||
export * from './icons';
|
||||
export * from './latex-protection';
|
||||
export * from './literal-html';
|
||||
export * from './localstorage-keys';
|
||||
export * from './markdown';
|
||||
export * from './max-bundle-size';
|
||||
export * from './mcp';
|
||||
export * from './mcp-form';
|
||||
export * from './mcp-resource';
|
||||
export * from './model-id';
|
||||
export * from './precision';
|
||||
export * from './processing-info';
|
||||
@@ -30,4 +35,5 @@ export * from './supported-file-types';
|
||||
export * from './table-html-restorer';
|
||||
export * from './tooltip-config';
|
||||
export * from './ui';
|
||||
export * from './uri-template';
|
||||
export * from './viewport';
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Key-value pair form constraints and sanitization patterns.
|
||||
*
|
||||
* Both regexes target characters dangerous in HTTP-header / env-var contexts:
|
||||
* \x00 – null byte (injection)
|
||||
* \x0A (\n) – LF (HTTP header injection / response splitting)
|
||||
* \x0D (\r) – CR (HTTP header injection / response splitting)
|
||||
* \x01–\x08, \x0B–\x0C, \x0E–\x1F, \x7F – other C0/DEL control chars
|
||||
*
|
||||
* KEY_UNSAFE_RE additionally strips TAB (\x09); values keep TAB because it is
|
||||
* a valid header-value continuation character per RFC 7230.
|
||||
*/
|
||||
|
||||
export const KEY_VALUE_PAIR_KEY_MAX_LENGTH = 256;
|
||||
export const KEY_VALUE_PAIR_VALUE_MAX_LENGTH = 8192;
|
||||
|
||||
// eslint-disable-next-line no-control-regex
|
||||
export const KEY_VALUE_PAIR_UNSAFE_KEY_RE = /[\x00-\x1F\x7F]/g;
|
||||
// eslint-disable-next-line no-control-regex
|
||||
export const KEY_VALUE_PAIR_UNSAFE_VALUE_RE = /[\x00-\x08\x0A-\x0D\x0E-\x1F\x7F]/g;
|
||||
@@ -0,0 +1,2 @@
|
||||
export const MCP_SERVER_URL_PLACEHOLDER = 'https://mcp.example.com/sse';
|
||||
export const MIN_AUTOCOMPLETE_INPUT_LENGTH = 1;
|
||||
@@ -0,0 +1,55 @@
|
||||
import { MimeTypeImage } from '$lib/enums';
|
||||
|
||||
// File extension patterns for resource type detection
|
||||
export const IMAGE_FILE_EXTENSION_REGEX = /\.(png|jpg|jpeg|gif|svg|webp)$/i;
|
||||
export const CODE_FILE_EXTENSION_REGEX =
|
||||
/\.(js|ts|json|yaml|yml|xml|html|css|py|rs|go|java|cpp|c|h|rb|sh|toml)$/i;
|
||||
export const TEXT_FILE_EXTENSION_REGEX = /\.(txt|md|log)$/i;
|
||||
|
||||
// URI protocol prefix pattern
|
||||
export const PROTOCOL_PREFIX_REGEX = /^[a-z]+:\/\//;
|
||||
|
||||
// File extension regex for display name extraction
|
||||
export const FILE_EXTENSION_REGEX = /\.[^.]+$/;
|
||||
|
||||
// Separator regex for splitting display names (kebab-case/snake_case)
|
||||
export const DISPLAY_NAME_SEPARATOR_REGEX = /[-_]/;
|
||||
|
||||
// Regex for matching base64-encoded data URIs
|
||||
export const DATA_URI_BASE64_REGEX = /^data:([^;]+);base64,([A-Za-z0-9+/]+=*)$/;
|
||||
|
||||
// Prefix for MCP attachment filenames
|
||||
export const MCP_ATTACHMENT_NAME_PREFIX = 'mcp-attachment';
|
||||
|
||||
// Prefix for MCP resource attachment IDs
|
||||
export const MCP_RESOURCE_ATTACHMENT_ID_PREFIX = 'res';
|
||||
|
||||
// Default file extension for unknown image types
|
||||
export const DEFAULT_IMAGE_EXTENSION = 'img';
|
||||
|
||||
// Default filename for resource content downloads
|
||||
export const DEFAULT_RESOURCE_FILENAME = 'resource.txt';
|
||||
|
||||
// Path separator for resource URI parsing
|
||||
export const PATH_SEPARATOR = '/';
|
||||
|
||||
// Separator for joining text content from multiple resource parts
|
||||
export const RESOURCE_TEXT_CONTENT_SEPARATOR = '\n\n';
|
||||
|
||||
// Fallback text for unknown content types
|
||||
export const RESOURCE_UNKNOWN_TYPE = 'unknown type';
|
||||
|
||||
// Label prefix for binary blob content
|
||||
export const BINARY_CONTENT_LABEL = 'Binary content';
|
||||
|
||||
/**
|
||||
* Mapping from image MIME types to file extensions.
|
||||
* Used for generating attachment filenames from MIME types.
|
||||
*/
|
||||
export const IMAGE_MIME_TO_EXTENSION: Record<string, string> = {
|
||||
[MimeTypeImage.JPEG]: 'jpg',
|
||||
[MimeTypeImage.JPG]: 'jpg',
|
||||
[MimeTypeImage.PNG]: 'png',
|
||||
[MimeTypeImage.GIF]: 'gif',
|
||||
[MimeTypeImage.WEBP]: 'webp'
|
||||
} as const;
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Zap, Globe, Radio } from '@lucide/svelte';
|
||||
import { MCPTransportType } from '$lib/enums';
|
||||
import type { ClientCapabilities, Implementation } from '$lib/types';
|
||||
import type { Component } from 'svelte';
|
||||
import { MimeTypeImage } from '$lib/enums/files';
|
||||
|
||||
export const DEFAULT_CLIENT_VERSION = '1.0.0';
|
||||
export const DEFAULT_IMAGE_MIME_TYPE = MimeTypeImage.PNG;
|
||||
|
||||
/** MIME types considered safe for rendering MCP server icons */
|
||||
export const MCP_ALLOWED_ICON_MIME_TYPES = new Set([
|
||||
MimeTypeImage.PNG,
|
||||
MimeTypeImage.JPEG,
|
||||
MimeTypeImage.JPG,
|
||||
MimeTypeImage.SVG,
|
||||
MimeTypeImage.WEBP
|
||||
]);
|
||||
|
||||
/**
|
||||
* MCP specification version this client targets.
|
||||
* Update when the upstream MCP spec introduces a new stable version:
|
||||
* https://spec.modelcontextprotocol.io/
|
||||
*/
|
||||
export const MCP_PROTOCOL_VERSION = '2025-06-18';
|
||||
|
||||
export const DEFAULT_MCP_CONFIG = {
|
||||
protocolVersion: MCP_PROTOCOL_VERSION,
|
||||
capabilities: { tools: { listChanged: true } } as ClientCapabilities,
|
||||
clientInfo: { name: 'llama-webui-mcp', version: DEFAULT_CLIENT_VERSION } as Implementation,
|
||||
requestTimeoutSeconds: 300, // 5 minutes for long-running tools
|
||||
connectionTimeoutMs: 10_000 // 10 seconds for connection establishment
|
||||
} as const;
|
||||
|
||||
export const MCP_SERVER_ID_PREFIX = 'LlamaCpp-WebUI-MCP-Server';
|
||||
|
||||
export const MCP_RECONNECT_INITIAL_DELAY = 1000;
|
||||
export const MCP_RECONNECT_BACKOFF_MULTIPLIER = 2;
|
||||
export const MCP_RECONNECT_MAX_DELAY = 30000;
|
||||
/** Per-attempt timeout for a single reconnection attempt before giving up and backing off. */
|
||||
export const MCP_RECONNECT_ATTEMPT_TIMEOUT_MS = 15_000;
|
||||
|
||||
/** Maximum number of MCP server avatars to display in the chat form */
|
||||
export const MAX_DISPLAYED_MCP_AVATARS = 3;
|
||||
|
||||
/** Expected count when two theme-less icons represent a light/dark pair */
|
||||
export const EXPECTED_THEMED_ICON_PAIR_COUNT = 2;
|
||||
|
||||
/** CORS proxy URL query parameter name */
|
||||
export const CORS_PROXY_URL_PARAM = 'url';
|
||||
|
||||
/** Human-readable labels for MCP transport types */
|
||||
export const MCP_TRANSPORT_LABELS: Record<MCPTransportType, string> = {
|
||||
[MCPTransportType.WEBSOCKET]: 'WebSocket',
|
||||
[MCPTransportType.STREAMABLE_HTTP]: 'HTTP',
|
||||
[MCPTransportType.SSE]: 'SSE'
|
||||
};
|
||||
|
||||
/** Icon components for MCP transport types */
|
||||
export const MCP_TRANSPORT_ICONS: Record<MCPTransportType, Component> = {
|
||||
[MCPTransportType.WEBSOCKET]: Zap,
|
||||
[MCPTransportType.STREAMABLE_HTTP]: Globe,
|
||||
[MCPTransportType.SSE]: Radio
|
||||
};
|
||||
@@ -24,6 +24,12 @@ export const SETTING_CONFIG_DEFAULT: Record<string, string | number | boolean> =
|
||||
autoMicOnEmpty: false,
|
||||
fullHeightCodeBlocks: false,
|
||||
showRawModelNames: false,
|
||||
mcpServers: '[]',
|
||||
mcpServerUsageStats: '{}', // JSON object: { [serverId]: usageCount }
|
||||
agenticMaxTurns: 10,
|
||||
agenticMaxToolPreviewLines: 25,
|
||||
showToolCallInProgress: false,
|
||||
alwaysShowAgenticTurns: false,
|
||||
// make sure these default values are in sync with `common.h`
|
||||
samplers: 'top_k;typ_p;top_p;min_p;temperature',
|
||||
backend_sampling: false,
|
||||
@@ -119,6 +125,16 @@ export const SETTING_CONFIG_INFO: Record<string, string> = {
|
||||
'Always display code blocks at their full natural height, overriding any height limits.',
|
||||
showRawModelNames:
|
||||
'Display full raw model identifiers (e.g. "unsloth/Qwen3.5-27B-GGUF:BF16") instead of parsed names with badges.',
|
||||
mcpServers:
|
||||
'Configure MCP servers as a JSON list. Use the form in the MCP Client settings section to edit.',
|
||||
mcpServerUsageStats:
|
||||
'Usage statistics for MCP servers. Tracks how many times tools from each server have been used.',
|
||||
agenticMaxTurns:
|
||||
'Maximum number of tool execution cycles before stopping (prevents infinite loops).',
|
||||
agenticMaxToolPreviewLines:
|
||||
'Number of lines shown in tool output previews (last N lines). Only these previews and the final LLM response persist after the agentic loop completes.',
|
||||
showToolCallInProgress:
|
||||
'Automatically expand tool call details while executing and keep them expanded after completion.',
|
||||
pyInterpreterEnabled:
|
||||
'Enable Python interpreter using Pyodide. Allows running Python code in markdown code blocks.',
|
||||
enableContinueGeneration:
|
||||
|
||||
@@ -47,6 +47,11 @@ export const SETTINGS_KEYS = {
|
||||
DRY_BASE: 'dry_base',
|
||||
DRY_ALLOWED_LENGTH: 'dry_allowed_length',
|
||||
DRY_PENALTY_LAST_N: 'dry_penalty_last_n',
|
||||
// MCP
|
||||
AGENTIC_MAX_TURNS: 'agenticMaxTurns',
|
||||
ALWAYS_SHOW_AGENTIC_TURNS: 'alwaysShowAgenticTurns',
|
||||
AGENTIC_MAX_TOOL_PREVIEW_LINES: 'agenticMaxToolPreviewLines',
|
||||
SHOW_TOOL_CALL_IN_PROGRESS: 'showToolCallInProgress',
|
||||
// Developer
|
||||
DISABLE_REASONING_PARSING: 'disableReasoningParsing',
|
||||
SHOW_RAW_OUTPUT_SWITCH: 'showRawOutputSwitch',
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
/**
|
||||
* Settings section titles constants for ChatSettings component.
|
||||
*
|
||||
* These titles define the navigation sections in the settings dialog.
|
||||
* Used for both sidebar navigation and mobile horizontal scroll menu.
|
||||
*/
|
||||
export const SETTINGS_SECTION_TITLES = {
|
||||
GENERAL: 'General',
|
||||
@@ -7,8 +10,10 @@ export const SETTINGS_SECTION_TITLES = {
|
||||
SAMPLING: 'Sampling',
|
||||
PENALTIES: 'Penalties',
|
||||
IMPORT_EXPORT: 'Import/Export',
|
||||
MCP: 'MCP',
|
||||
DEVELOPER: 'Developer'
|
||||
} as const;
|
||||
|
||||
/** Type for settings section titles */
|
||||
export type SettingsSectionTitle =
|
||||
(typeof SETTINGS_SECTION_TITLES)[keyof typeof SETTINGS_SECTION_TITLES];
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* URI Template constants for RFC 6570 template processing.
|
||||
*/
|
||||
|
||||
/** URI scheme separator */
|
||||
export const URI_SCHEME_SEPARATOR = '://';
|
||||
|
||||
/** Regex to match template expressions like {var}, {+var}, {#var}, {/var} */
|
||||
export const TEMPLATE_EXPRESSION_REGEX = /\{([+#./;?&]?)([^}]+)\}/g;
|
||||
|
||||
/** RFC 6570 URI template operators */
|
||||
export const URI_TEMPLATE_OPERATORS = {
|
||||
/** Simple string expansion (default) */
|
||||
SIMPLE: '',
|
||||
/** Reserved expansion */
|
||||
RESERVED: '+',
|
||||
/** Fragment expansion */
|
||||
FRAGMENT: '#',
|
||||
/** Path segment expansion */
|
||||
PATH_SEGMENT: '/',
|
||||
/** Label expansion */
|
||||
LABEL: '.',
|
||||
/** Path-style parameters */
|
||||
PATH_PARAM: ';',
|
||||
/** Form-style query */
|
||||
FORM_QUERY: '?',
|
||||
/** Form-style query continuation */
|
||||
FORM_CONTINUATION: '&'
|
||||
} as const;
|
||||
|
||||
/** URI template separators used in expansion */
|
||||
export const URI_TEMPLATE_SEPARATORS = {
|
||||
/** Comma separator for list expansion */
|
||||
COMMA: ',',
|
||||
/** Slash separator for path segments */
|
||||
SLASH: '/',
|
||||
/** Period separator for label expansion */
|
||||
PERIOD: '.',
|
||||
/** Semicolon separator for path parameters */
|
||||
SEMICOLON: ';',
|
||||
/** Question mark prefix for query string */
|
||||
QUERY_PREFIX: '?',
|
||||
/** Ampersand prefix for query continuation */
|
||||
QUERY_CONTINUATION: '&'
|
||||
} as const;
|
||||
|
||||
/** Maximum number of leading slashes to strip during URI normalization */
|
||||
export const MAX_LEADING_SLASHES_TO_STRIP = 3;
|
||||
|
||||
/** Regex to strip explode modifier (*) from variable names */
|
||||
export const VARIABLE_EXPLODE_MODIFIER_REGEX = /[*]$/;
|
||||
|
||||
/** Regex to strip prefix modifier (:N) from variable names */
|
||||
export const VARIABLE_PREFIX_MODIFIER_REGEX = /:[\d]+$/;
|
||||
|
||||
/** Regex to strip one or more leading slashes */
|
||||
export const LEADING_SLASHES_REGEX = /^\/+/;
|
||||
Reference in New Issue
Block a user