diff --git a/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/ChatMessageToolCallBlockDefault.svelte b/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/ChatMessageToolCallBlockDefault.svelte index 03f6c27ba3..4608232fa6 100644 --- a/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/ChatMessageToolCallBlockDefault.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/ChatMessageToolCallBlockDefault.svelte @@ -6,13 +6,14 @@ import ToolCallBlock from './ToolCallBlock.svelte'; import { Loader2 } from '@lucide/svelte'; import { MarkdownContent, SyntaxHighlightedCode } from '$lib/components/app'; - import { getBuiltinToolUi, MAX_HEIGHT_CODE_BLOCK } from '$lib/constants'; + import { MAX_HEIGHT_CODE_BLOCK } from '$lib/constants'; import { FileTypeText, ToolResultKind } from '$lib/enums'; import type { DatabaseMessageExtra } from '$lib/types'; import { type AgenticSection, classifyToolResult, formatJsonPretty, + getBuiltinToolUi, parseToolResultWithImages } from '$lib/utils'; diff --git a/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/ToolCallBlock.svelte b/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/ToolCallBlock.svelte index 7d8358571a..c43495b6c7 100644 --- a/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/ToolCallBlock.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/ToolCallBlock.svelte @@ -11,10 +11,10 @@ import { Loader2, Wrench } from '@lucide/svelte'; import { CollapsibleContentBlock } from '$lib/components/app'; - import { getBuiltinToolUi, ICON_CLASS_DEFAULT, ICON_CLASS_SPIN } from '$lib/constants'; + import { ICON_CLASS_DEFAULT, ICON_CLASS_SPIN } from '$lib/constants'; import { AgenticSectionType } from '$lib/enums'; import { mcpStore } from '$lib/stores/mcp.svelte'; - import type { AgenticSection, BuiltinToolUiEntry } from '$lib/utils'; + import { type AgenticSection, type BuiltinToolUiEntry, getBuiltinToolUi } from '$lib/utils'; import type { Component, Snippet } from 'svelte'; type ToolCallBlockMetaWithError = TMeta & { errorMessage?: string }; diff --git a/tools/ui/src/lib/components/app/settings/SettingsChat/SettingsChatToolsTab.svelte b/tools/ui/src/lib/components/app/settings/SettingsChat/SettingsChatToolsTab.svelte index 4d3b69c41c..c4682f174c 100644 --- a/tools/ui/src/lib/components/app/settings/SettingsChat/SettingsChatToolsTab.svelte +++ b/tools/ui/src/lib/components/app/settings/SettingsChat/SettingsChatToolsTab.svelte @@ -3,11 +3,12 @@ import { McpServerIdentity, TruncatedText } from '$lib/components/app'; import { Checkbox } from '$lib/components/ui/checkbox'; import * as Collapsible from '$lib/components/ui/collapsible'; - import { getBuiltinToolUi, ICON_CLASS_DEFAULT } from '$lib/constants'; + import { ICON_CLASS_DEFAULT } from '$lib/constants'; import { ToolSource } from '$lib/enums/tools.enums'; import { mcpStore } from '$lib/stores/mcp.svelte'; import { permissionsStore } from '$lib/stores/permissions.svelte'; import { toolsStore } from '$lib/stores/tools.svelte'; + import { getBuiltinToolUi } from '$lib/utils'; import { SvelteSet } from 'svelte/reactivity'; let expandedGroups = new SvelteSet(); diff --git a/tools/ui/src/lib/constants/built-in-tools.constants.ts b/tools/ui/src/lib/constants/built-in-tools.constants.ts index ec0826fbc3..70b7d895e6 100644 --- a/tools/ui/src/lib/constants/built-in-tools.constants.ts +++ b/tools/ui/src/lib/constants/built-in-tools.constants.ts @@ -48,9 +48,3 @@ export const BUILTIN_TOOL_UI: Readonly> }, [BuiltInTool.WRITE_FILE]: { icon: FilePlus, label: 'Write file', source: ToolSource.BUILTIN } } as const; - -export function getBuiltinToolUi(toolName: string | undefined): BuiltinToolUiEntry | null { - if (!toolName) return null; - - return (BUILTIN_TOOL_UI as Record)[toolName] ?? null; -} diff --git a/tools/ui/src/lib/constants/control-actions.constants.ts b/tools/ui/src/lib/constants/control-actions.constants.ts index 935ae9542a..c8ebf701b1 100644 --- a/tools/ui/src/lib/constants/control-actions.constants.ts +++ b/tools/ui/src/lib/constants/control-actions.constants.ts @@ -3,5 +3,3 @@ export const CONTROL_ACTION = { END_REASONING: 'reasoning_end' } as const; - -export type ControlAction = (typeof CONTROL_ACTION)[keyof typeof CONTROL_ACTION]; diff --git a/tools/ui/src/lib/constants/floating-ui-constraints.constants.ts b/tools/ui/src/lib/constants/floating-ui-constraints.constants.ts deleted file mode 100644 index 003fc77acb..0000000000 --- a/tools/ui/src/lib/constants/floating-ui-constraints.constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const VIEWPORT_GUTTER = 8; -export const MENU_OFFSET = 6; diff --git a/tools/ui/src/lib/constants/headers.constants.ts b/tools/ui/src/lib/constants/headers.constants.ts new file mode 100644 index 0000000000..8e0ccb9745 --- /dev/null +++ b/tools/ui/src/lib/constants/headers.constants.ts @@ -0,0 +1,29 @@ +/** HTTP header handling for API and MCP requests. */ +export const HEADERS = { + /** Canonical casing for the Authorization header (RFC 7235) */ + AUTHORIZATION: 'Authorization', + /** Bearer scheme prefix used for Authorization headers (RFC 6750) */ + BEARER: 'Bearer ', + /** Content-Type HTTP header name */ + CONTENT_TYPE: 'Content-Type', + /** Partial-redaction rules for MCP headers: header name -> visible trailing chars */ + PARTIAL_REDACT: new Map([['mcp-session-id', 5]]), + + /** Header names whose values should be redacted in diagnostic logs */ + REDACTED: new Set([ + 'authorization', + 'api-key', + 'cookie', + 'mcp-session-id', + 'proxy-authorization', + 'set-cookie', + 'x-auth-token', + 'x-api-key' + ]), + + /** Header carrying the stream-session identity (conversation id, optionally with a model suffix) */ + X_CONVERSATION_ID_HEADER: 'X-Conversation-Id', + + /** Header carrying the working directory a tool call runs in; the model cannot override it */ + X_TOOL_CWD_HEADER: 'x-tool-cwd' +}; diff --git a/tools/ui/src/lib/constants/index.ts b/tools/ui/src/lib/constants/index.ts index bae0e736e4..5d1260e036 100644 --- a/tools/ui/src/lib/constants/index.ts +++ b/tools/ui/src/lib/constants/index.ts @@ -18,14 +18,13 @@ export * from './content-detection.constants'; export * from './built-in-tools.constants'; export * from './cache.constants'; export * from './chat-form.constants'; -export * from './chat-commands.constants'; export * from './cli-flags.constants'; export * from './code-block.constants'; export * from './context-keys.constants'; export * from './control-actions.constants'; export * from './css-classes.constants'; -export * from './floating-ui-constraints.constants'; export * from './formatters.constants'; +export * from './headers.constants'; export * from './key-value-pairs.constants'; export * from './latex-protection.constants'; export * from './literal-html.constants'; @@ -45,7 +44,6 @@ export * from './path-display.constants'; export * from './model-id.constants'; export * from './model-loading.constants'; export * from './precision.constants'; -export * from './processing-info.constants'; export * from './pwa.constants'; export * from './routes.constants'; export * from './sandbox.constants'; @@ -56,10 +54,7 @@ export * from './stream.constants'; export * from './supported-file-types.constants'; export * from './table-html-restorer.constants'; export * from './title-generation.constants'; -export * from './tools.constants'; -export * from './tooltip-config.constants'; export * from './ui.constants'; export * from './uri-template.constants'; export * from './url.constants'; -export * from './viewport.constants'; export * from './working-directory.constants'; diff --git a/tools/ui/src/lib/constants/mcp.constants.ts b/tools/ui/src/lib/constants/mcp.constants.ts index 507f44d09a..11013d2cb1 100644 --- a/tools/ui/src/lib/constants/mcp.constants.ts +++ b/tools/ui/src/lib/constants/mcp.constants.ts @@ -59,31 +59,6 @@ export const CORS_PROXY = { URL_PARAM: 'url' } as const; -/** HTTP header handling for API and MCP requests. */ -export const HEADERS = { - /** Canonical casing for the Authorization header (RFC 7235) */ - AUTHORIZATION: 'Authorization', - /** Bearer scheme prefix used for Authorization headers (RFC 6750) */ - BEARER: 'Bearer ', - /** Content-Type HTTP header name */ - CONTENT_TYPE: 'Content-Type', - - /** Partial-redaction rules for MCP headers: header name -> visible trailing chars */ - PARTIAL_REDACT: new Map([['mcp-session-id', 5]]), - - /** Header names whose values should be redacted in diagnostic logs */ - REDACTED: new Set([ - 'authorization', - 'api-key', - 'cookie', - 'mcp-session-id', - 'proxy-authorization', - 'set-cookie', - 'x-auth-token', - 'x-api-key' - ]) -}; - /** Standard SSE endpoint path indicators */ export const MCP_SSE = { ENDPOINT: '/sse', diff --git a/tools/ui/src/lib/constants/processing-info.constants.ts b/tools/ui/src/lib/constants/processing-info.constants.ts deleted file mode 100644 index 2c3f7dc534..0000000000 --- a/tools/ui/src/lib/constants/processing-info.constants.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const PROCESSING_INFO_TIMEOUT = 2000; - -/** - * Statistics units labels - */ -export const STATS_UNITS = { - TOKENS_PER_SECOND: 't/s' -} as const; diff --git a/tools/ui/src/lib/constants/sandbox.constants.ts b/tools/ui/src/lib/constants/sandbox.constants.ts index 11e5fcf8fc..9846e471a9 100644 --- a/tools/ui/src/lib/constants/sandbox.constants.ts +++ b/tools/ui/src/lib/constants/sandbox.constants.ts @@ -1,5 +1,4 @@ -import { BuiltInTool, JsonSchemaType, ToolCallType } from '$lib/enums'; -import type { OpenAIToolDefinition } from '$lib/types'; +import { BuiltInTool } from '$lib/enums'; export const SANDBOX_TOOL_NAME = BuiltInTool.RUN_JAVASCRIPT; @@ -12,48 +11,3 @@ export const SANDBOX_OUTPUT_MAX_CHARS = 8192; export const SANDBOX_EMPTY_OUTPUT = '(no output)'; export const SANDBOX_TRUNCATION_NOTICE = '[output truncated]'; - -const NERDAMER_DESCRIPTION = ` -Symbolic/numeric math via \`nerdamer\` -nerdamer(expr,subs?,opts?)/nerdamer.func(...)→Expression Format via .text(fmt?) (fmt: 'decimals'|'fractions'|'scientific') eval via .evaluate(subs?) -nerdamer(expr,{x:2}) substitutes numeric via opts 'numer' or .evaluate() -simplify/expand/factor(expr) div/gcd/lcm(...) coeffs/partfrac(expr,var) -diff/integrate(expr,var) defint(expr,lo,hi,var?) sum/product(expr,var,lo,hi) limit(expr,var,pt) -solve(expr,var) solveEquations([eq1,eq2],[var1,var2]) -polarform/rectform/arg/realpart/imagpart(z) -set/get Var/Constant(name,val?) setFunction(name,[params],body) -IMPORTANT:Identifier 'nerdamer' has already been declared, use it directly`; - -/** - * Build the sandbox tool definition. When `includeSymbolicMath` is true, - * the description includes nerdamer API documentation; otherwise it - * describes a plain JavaScript sandbox. - */ -export function buildSandboxToolDefinition(includeSymbolicMath: boolean): OpenAIToolDefinition { - return { - function: { - description: includeSymbolicMath - ? `Execute JS in a sandboxed browser worker (no DOM/page access). Top-level await ok; console.log for intermediates; top-level return is captured as result.${NERDAMER_DESCRIPTION}` - : 'Execute JS in a sandboxed browser worker (no DOM/page access). Top-level await ok; console.log for intermediates; top-level return is captured as result.', - name: SANDBOX_TOOL_NAME, - parameters: { - properties: { - code: { - description: 'JavaScript source to execute', - type: JsonSchemaType.STRING - }, - timeout_ms: { - description: `Execution timeout in milliseconds, default ${SANDBOX_TIMEOUT_MS_DEFAULT}, max ${SANDBOX_TIMEOUT_MS_MAX}`, - type: JsonSchemaType.NUMBER - } - }, - required: ['code'], - type: JsonSchemaType.OBJECT - } - }, - type: ToolCallType.FUNCTION - }; -} - -/** @deprecated Use {@link buildSandboxToolDefinition} instead. Kept for backward compatibility. */ -export const SANDBOX_TOOL_DEFINITION = buildSandboxToolDefinition(true); diff --git a/tools/ui/src/lib/constants/stream.constants.ts b/tools/ui/src/lib/constants/stream.constants.ts index ad04a1e74d..64f67243c2 100644 --- a/tools/ui/src/lib/constants/stream.constants.ts +++ b/tools/ui/src/lib/constants/stream.constants.ts @@ -2,6 +2,10 @@ // while the tab was hidden. covers brief background pauses without thrashing live streams export const STREAM_VISIBILITY_KICK_MS = 3000; +// separator joining a conversation id and its per-model stream identity +// suffix (conv::model) used by the server side replay buffer +export const CONVERSATION_ID_SEPARATOR = '::'; + /** * Server-sent events wire format, shared by the chat stream and the * /models/sse status feed (text/event-stream). diff --git a/tools/ui/src/lib/constants/tools.constants.ts b/tools/ui/src/lib/constants/tools.constants.ts deleted file mode 100644 index 65f4457c96..0000000000 --- a/tools/ui/src/lib/constants/tools.constants.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ToolSource } from '$lib/enums/tools.enums'; - -/** HTTP header carrying the working directory a tool call runs in. The server resolves relative paths against it; the model cannot override it. */ -export const X_TOOL_CWD_HEADER = 'x-tool-cwd'; - -export const TOOL_GROUP_LABELS = { - [ToolSource.BUILTIN]: 'Built-in', - [ToolSource.CUSTOM]: 'JSON Schema', - [ToolSource.FRONTEND]: 'Browser' -} as const; - -export const TOOL_SERVER_LABELS = { - [ToolSource.BUILTIN]: 'Built-in Tools', - [ToolSource.CUSTOM]: 'Custom Tools', - [ToolSource.FRONTEND]: 'Browser Tools' -} as const; diff --git a/tools/ui/src/lib/constants/tooltip-config.constants.ts b/tools/ui/src/lib/constants/tooltip-config.constants.ts deleted file mode 100644 index ad76ab3522..0000000000 --- a/tools/ui/src/lib/constants/tooltip-config.constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const TOOLTIP_DELAY_DURATION = 500; diff --git a/tools/ui/src/lib/constants/ui.constants.ts b/tools/ui/src/lib/constants/ui.constants.ts index a50153852e..3374b6e008 100644 --- a/tools/ui/src/lib/constants/ui.constants.ts +++ b/tools/ui/src/lib/constants/ui.constants.ts @@ -1,11 +1,40 @@ import { ROUTES } from './routes.constants'; import { Package, Search, Settings, SquarePen } from '@lucide/svelte'; import McpLogo from '$lib/components/app/mcp/McpLogo.svelte'; +import { ToolSource } from '$lib/enums/tools.enums'; import type { DesktopIconStripItem } from '$lib/types'; export const FORK_TREE_DEPTH_PADDING = 8; export const SYSTEM_MESSAGE_PLACEHOLDER = 'System message'; +export const TOOL_GROUP_LABELS = { + [ToolSource.BUILTIN]: 'Built-in', + [ToolSource.CUSTOM]: 'JSON Schema', + [ToolSource.FRONTEND]: 'Browser' +} as const; + +export const TOOL_SERVER_LABELS = { + [ToolSource.BUILTIN]: 'Built-in Tools', + [ToolSource.CUSTOM]: 'Custom Tools', + [ToolSource.FRONTEND]: 'Browser Tools' +} as const; + +export const TOOLTIP_DELAY_DURATION = 500; + +export const VIEWPORT_GUTTER = 8; +export const MENU_OFFSET = 6; + +export const PROCESSING_INFO_TIMEOUT = 2000; + +/** + * Statistics units labels + */ +export const STATS_UNITS = { + TOKENS_PER_SECOND: 't/s' +} as const; + +export const DEFAULT_MOBILE_BREAKPOINT = 768; + /** Icon used for the model selector and the `/model` slash command. */ export const MODEL_SELECTOR_ICON = Package; diff --git a/tools/ui/src/lib/constants/viewport.constants.ts b/tools/ui/src/lib/constants/viewport.constants.ts deleted file mode 100644 index 26e202cfea..0000000000 --- a/tools/ui/src/lib/constants/viewport.constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const DEFAULT_MOBILE_BREAKPOINT = 768; diff --git a/tools/ui/src/lib/hooks/use-chat-form-pickers.svelte.ts b/tools/ui/src/lib/hooks/use-chat-form-pickers.svelte.ts index 712189746b..986cc46c72 100644 --- a/tools/ui/src/lib/hooks/use-chat-form-pickers.svelte.ts +++ b/tools/ui/src/lib/hooks/use-chat-form-pickers.svelte.ts @@ -1,6 +1,7 @@ -import { getChatCommands, PROMPT_TRIGGER_PREFIX } from '$lib/constants'; +import { PROMPT_TRIGGER_PREFIX } from '$lib/constants'; import { ChatFormCommandAction, KeyboardKey } from '$lib/enums'; import type { ChatFormCommand } from '$lib/types'; +import { getChatCommands } from '$lib/utils'; import { type CommandDismissSnapshot, findCommandToken, diff --git a/tools/ui/src/lib/services/chat.service.ts b/tools/ui/src/lib/services/chat.service.ts index a3f0f77aee..bc2cd9c1e2 100644 --- a/tools/ui/src/lib/services/chat.service.ts +++ b/tools/ui/src/lib/services/chat.service.ts @@ -5,6 +5,7 @@ import { API_SLOTS, API_STREAM, CONTROL_ACTION, + HEADERS, LEGACY_AGENTIC_REGEX, REASONING_EFFORT_TOKENS, SETTINGS_KEYS, @@ -361,7 +362,7 @@ export class ChatService { // server side replay buffer and powers discoverActiveStream on tab reopen. with an explicit // model the ::model suffix keeps the per model session distinct if (stream && conversationId) { - headers['X-Conversation-Id'] = streamIdentity(conversationId, options.model); + headers[HEADERS.X_CONVERSATION_ID_HEADER] = streamIdentity(conversationId, options.model); // persist the pending stream before the fetch: a reload during the model load or // the prompt processing must still find its way back to the session once it exists ChatService.saveStreamState(conversationId, 0, options.model ?? null); diff --git a/tools/ui/src/lib/services/index.ts b/tools/ui/src/lib/services/index.ts index 8704b1bd6c..328d3b4828 100644 --- a/tools/ui/src/lib/services/index.ts +++ b/tools/ui/src/lib/services/index.ts @@ -276,7 +276,7 @@ export { MCPService } from './mcp.service'; * - **toolsStore**: Exposes the tool definition when the sandbox is enabled * - **agenticStore**: Dispatches ToolSource.FRONTEND calls here * - * @see buildSandboxToolDefinition in constants/sandbox.ts - tool schema sent to the LLM + * @see buildSandboxToolDefinition in utils/sandbox-tool - tool schema sent to the LLM * @see agenticStore in stores/agentic.svelte.ts - tool dispatch */ export { SandboxService } from './sandbox.service'; diff --git a/tools/ui/src/lib/services/tools.service.ts b/tools/ui/src/lib/services/tools.service.ts index cd6b12cebe..d15b213c75 100644 --- a/tools/ui/src/lib/services/tools.service.ts +++ b/tools/ui/src/lib/services/tools.service.ts @@ -1,5 +1,5 @@ import { base } from '$app/paths'; -import { API_TOOLS, X_TOOL_CWD_HEADER } from '$lib/constants'; +import { API_TOOLS, HEADERS } from '$lib/constants'; import { ToolResponseField } from '$lib/enums'; import type { ServerBuiltinToolInfo, ToolExecutionResult } from '$lib/types'; import { apiFetch } from '$lib/utils'; @@ -31,7 +31,7 @@ export class ToolsService { ): Promise { const result = await apiFetch>(API_TOOLS.EXECUTE, { body: JSON.stringify({ params, tool: toolName }), - headers: cwd ? { [X_TOOL_CWD_HEADER]: cwd } : undefined, + headers: cwd ? { [HEADERS.X_TOOL_CWD_HEADER]: cwd } : undefined, method: 'POST', signal }); @@ -60,7 +60,7 @@ export class ToolsService { ): Promise> { return apiFetch>(API_TOOLS.EXECUTE, { body: JSON.stringify({ params, tool: toolName }), - headers: cwd ? { [X_TOOL_CWD_HEADER]: cwd } : undefined, + headers: cwd ? { [HEADERS.X_TOOL_CWD_HEADER]: cwd } : undefined, method: 'POST', signal }); @@ -89,7 +89,7 @@ export class ToolsService { ): AsyncGenerator { const headers = getJsonHeaders(); - if (cwd) headers[X_TOOL_CWD_HEADER] = cwd; + if (cwd) headers[HEADERS.X_TOOL_CWD_HEADER] = cwd; const response = await fetch(`${base}${API_TOOLS.EXECUTE}`, { body: JSON.stringify({ params, stream: true, tool: toolName }), diff --git a/tools/ui/src/lib/stores/chat.svelte.ts b/tools/ui/src/lib/stores/chat.svelte.ts index 0d069eb35e..3965c4cbb5 100644 --- a/tools/ui/src/lib/stores/chat.svelte.ts +++ b/tools/ui/src/lib/stores/chat.svelte.ts @@ -12,6 +12,7 @@ */ import { + CONVERSATION_ID_SEPARATOR, HEADERS, INACTIVE_CONVERSATION, STREAM_RESUME_RETRY_MS, @@ -413,7 +414,7 @@ class ChatStore { // extract the model suffix, the resume calls in handleStreamResponse must reuse the model // the session was tagged with, not the live dropdown - const sepIdx = id.indexOf('::'); + const sepIdx = id.indexOf(CONVERSATION_ID_SEPARATOR); const attachedModel: string | null = sepIdx === -1 ? null : id.slice(sepIdx + 2); this.setChatStreaming(convId, existingContent, targetMessageId, attachedModel); @@ -828,7 +829,7 @@ class ChatStore { for (const s of sessions) { if (s && !s.is_done && typeof s.conversation_id === 'string' && s.conversation_id) { // strip the optional ::model suffix, the sidebar set is keyed by the bare conv id - const sepIdx = s.conversation_id.indexOf('::'); + const sepIdx = s.conversation_id.indexOf(CONVERSATION_ID_SEPARATOR); const bareId = sepIdx === -1 ? s.conversation_id : s.conversation_id.slice(0, sepIdx); running.add(bareId); diff --git a/tools/ui/src/lib/stores/tools.svelte.ts b/tools/ui/src/lib/stores/tools.svelte.ts index b1a946391a..ab9ebdeb3e 100644 --- a/tools/ui/src/lib/stores/tools.svelte.ts +++ b/tools/ui/src/lib/stores/tools.svelte.ts @@ -1,5 +1,4 @@ import { - buildSandboxToolDefinition, DISABLED_TOOL_KEYS_LOCALSTORAGE_KEY, HOME_TILDE, TOOL_GROUP_LABELS, @@ -17,6 +16,7 @@ import { ToolsService } from '$lib/services/tools.service'; import { mcpStore } from '$lib/stores/mcp.svelte'; import { config } from '$lib/stores/settings.svelte'; import type { OpenAIToolDefinition, ToolEntry, ToolGroup } from '$lib/types'; +import { buildSandboxToolDefinition } from '$lib/utils'; import { SvelteMap, SvelteSet } from 'svelte/reactivity'; /** Stable selection identity for a tool, shared by the disabled set and the permission store */ diff --git a/tools/ui/src/lib/types/chat.d.ts b/tools/ui/src/lib/types/chat.d.ts index a476fce81a..cb93e77951 100644 --- a/tools/ui/src/lib/types/chat.d.ts +++ b/tools/ui/src/lib/types/chat.d.ts @@ -215,6 +215,18 @@ export interface FileMentionEntry { * command whose backing capability is unavailable (e.g. `/prompt` when no * MCP server exposes prompts): visible but greyed out and not selectable. */ +export interface ChatCommandsOptions { + /** Gates `/model`. */ + showModelSelector: boolean; + /** Gates `/prompt`. */ + hasPrompts: () => boolean; + /** Gates `/cwd`. */ + hasCwdTools: () => boolean; +} + +/** Protocol-level verbs accepted by the realtime inference control endpoint. Mirrors `CONTROL_ACTION`. */ +export type ControlAction = 'reasoning_end'; + export interface ChatFormCommand { name: string; description: string; diff --git a/tools/ui/src/lib/types/index.ts b/tools/ui/src/lib/types/index.ts index 27d0b4224b..a7e755040c 100644 --- a/tools/ui/src/lib/types/index.ts +++ b/tools/ui/src/lib/types/index.ts @@ -56,7 +56,9 @@ export type { AttachmentDisplayItemsOptions, FileProcessingResult, FileMentionEntry, - ChatFormCommand + ChatFormCommand, + ChatCommandsOptions, + ControlAction } from './chat.d'; // Database types diff --git a/tools/ui/src/lib/utils/built-in-tools.ts b/tools/ui/src/lib/utils/built-in-tools.ts new file mode 100644 index 0000000000..73b5547900 --- /dev/null +++ b/tools/ui/src/lib/utils/built-in-tools.ts @@ -0,0 +1,13 @@ +import { BUILTIN_TOOL_UI } from '$lib/constants'; +import type { BuiltinToolUiEntry } from '$lib/types'; + +/** + * Resolve the UI metadata (label + icon) for a built-in tool by its name. + * Falls back to null for unknown or non-built-in tools so callers can render + * a generic chrome instead. + */ +export function getBuiltinToolUi(toolName: string | undefined): BuiltinToolUiEntry | null { + if (!toolName) return null; + + return (BUILTIN_TOOL_UI as Record)[toolName] ?? null; +} diff --git a/tools/ui/src/lib/constants/chat-commands.constants.ts b/tools/ui/src/lib/utils/chat-commands.ts similarity index 71% rename from tools/ui/src/lib/constants/chat-commands.constants.ts rename to tools/ui/src/lib/utils/chat-commands.ts index d10d7b1519..9345f0a48e 100644 --- a/tools/ui/src/lib/constants/chat-commands.constants.ts +++ b/tools/ui/src/lib/utils/chat-commands.ts @@ -1,23 +1,14 @@ import { SET_WORKING_DIRECTORY_LABEL } from '$lib/constants'; import { ChatFormCommandAction } from '$lib/enums'; -import type { ChatFormCommand } from '$lib/types'; - -interface ChatCommandsOptions { - /** Gates `/model`. */ - showModelSelector: boolean; - /** Gates `/prompt`. */ - hasPrompts: () => boolean; - /** Gates `/cwd`. */ - hasCwdTools: () => boolean; -} +import type { ChatCommandsOptions, ChatFormCommand } from '$lib/types'; /** * The slash commands surfaced by the `/` command picker, in display order. * * Availability is supplied as predicates rather than store imports: this - * module is re-exported through the `$lib/constants` barrel, and importing + * module is re-exported through the `$lib/utils` barrel, and importing * stores at module load would create a circular dependency (the stores - * themselves import from `$lib/constants`). + * themselves import from `$lib/utils`). */ export function getChatCommands(options: ChatCommandsOptions): ChatFormCommand[] { return [ diff --git a/tools/ui/src/lib/utils/index.ts b/tools/ui/src/lib/utils/index.ts index 25c7234bb5..54d555fa1c 100644 --- a/tools/ui/src/lib/utils/index.ts +++ b/tools/ui/src/lib/utils/index.ts @@ -317,9 +317,17 @@ export { tryParseToolResultObject } from './tool-call-meta'; // Per-tool UI metadata (label + icon) used by the tool-call chrome. // Re-exported through $lib/utils so renderer components can read the // label without depending on $lib/constants directly. -export { getBuiltinToolUi } from '$lib/constants'; +export { getBuiltinToolUi } from './built-in-tools'; export type { BuiltinToolUiEntry } from '$lib/types'; +// Chat command picker + +export { getChatCommands } from './chat-commands'; + +// Sandbox tool definition +// SANDBOX_TOOL_DEFINITION is deprecated; kept for backward compatibility. +export { buildSandboxToolDefinition, SANDBOX_TOOL_DEFINITION } from './sandbox-tool'; + // Cryptography utilities export { uuid } from './uuid'; diff --git a/tools/ui/src/lib/utils/sandbox-tool.ts b/tools/ui/src/lib/utils/sandbox-tool.ts new file mode 100644 index 0000000000..bc057e409d --- /dev/null +++ b/tools/ui/src/lib/utils/sandbox-tool.ts @@ -0,0 +1,52 @@ +import { + SANDBOX_TIMEOUT_MS_DEFAULT, + SANDBOX_TIMEOUT_MS_MAX, + SANDBOX_TOOL_NAME +} from '$lib/constants'; +import { JsonSchemaType, ToolCallType } from '$lib/enums'; +import type { OpenAIToolDefinition } from '$lib/types'; + +const NERDAMER_DESCRIPTION = ` +Symbolic/numeric math via \`nerdamer\` +nerdamer(expr,subs?,opts?)/nerdamer.func(...)→Expression Format via .text(fmt?) (fmt: 'decimals'|'fractions'|'scientific') eval via .evaluate(subs?) +nerdamer(expr,{x:2}) substitutes numeric via opts 'numer' or .evaluate() +simplify/expand/factor(expr) div/gcd/lcm(...) coeffs/partfrac(expr,var) +diff/integrate(expr,var) defint(expr,lo,hi,var?) sum/product(expr,var,lo,hi) limit(expr,var,pt) +solve(expr,var) solveEquations([eq1,eq2],[var1,var2]) +polarform/rectform/arg/realpart/imagpart(z) +set/get Var/Constant(name,val?) setFunction(name,[params],body) +IMPORTANT:Identifier 'nerdamer' has already been declared, use it directly`; + +/** + * Build the sandbox tool definition. When `includeSymbolicMath` is true, + * the description includes nerdamer API documentation; otherwise it + * describes a plain JavaScript sandbox. + */ +export function buildSandboxToolDefinition(includeSymbolicMath: boolean): OpenAIToolDefinition { + return { + function: { + description: includeSymbolicMath + ? `Execute JS in a sandboxed browser worker (no DOM/page access). Top-level await ok; console.log for intermediates; top-level return is captured as result.${NERDAMER_DESCRIPTION}` + : 'Execute JS in a sandboxed browser worker (no DOM/page access). Top-level await ok; console.log for intermediates; top-level return is captured as result.', + name: SANDBOX_TOOL_NAME, + parameters: { + properties: { + code: { + description: 'JavaScript source to execute', + type: JsonSchemaType.STRING + }, + timeout_ms: { + description: `Execution timeout in milliseconds, default ${SANDBOX_TIMEOUT_MS_DEFAULT}, max ${SANDBOX_TIMEOUT_MS_MAX}`, + type: JsonSchemaType.NUMBER + } + }, + required: ['code'], + type: JsonSchemaType.OBJECT + } + }, + type: ToolCallType.FUNCTION + }; +} + +/** @deprecated Use {@link buildSandboxToolDefinition} instead. Kept for backward compatibility. */ +export const SANDBOX_TOOL_DEFINITION = buildSandboxToolDefinition(true); diff --git a/tools/ui/src/lib/utils/stream-identity.ts b/tools/ui/src/lib/utils/stream-identity.ts index 253786a7e3..bf0946fc09 100644 --- a/tools/ui/src/lib/utils/stream-identity.ts +++ b/tools/ui/src/lib/utils/stream-identity.ts @@ -1,3 +1,5 @@ +import { CONVERSATION_ID_SEPARATOR } from '$lib/constants'; + /** * Build the conversation identity used by the server side replay buffer. * @@ -11,5 +13,5 @@ export function streamIdentity(conversationId: string, model?: string | null): s if (!model) return conversationId; - return `${conversationId}::${model}`; + return `${conversationId}${CONVERSATION_ID_SEPARATOR}${model}`; }