ui: Refactor Built-In Tools naming (Server/Browser) (#27271)
* server: rename built-in tools to server tools * ui: rename built-in tools to server/browser tools
This commit is contained in:
@@ -189,7 +189,7 @@ This endpoint is intended to be used internally by the Web UI and subject to cha
|
|||||||
Get a list of tools, each tool has these fields:
|
Get a list of tools, each tool has these fields:
|
||||||
- `tool` (string): the ID name of the tool, to be used in POST call. Example: `read_file`
|
- `tool` (string): the ID name of the tool, to be used in POST call. Example: `read_file`
|
||||||
- `display_name` (string): the name to be displayed on UI. Example: `Read file`
|
- `display_name` (string): the name to be displayed on UI. Example: `Read file`
|
||||||
- `type` (string): `"builtin"` for a built-in tool, or `"mcp"` for a tool exposed by an MCP server
|
- `type` (string): `"server"` for a server tool, or `"mcp"` for a tool exposed by an MCP server
|
||||||
- `permissions` (object): a mapping string --> boolean that indicates the permission required by this tool. This is useful for the UI to ask the user before calling the tool. For now, the only permission supported is `"write"`
|
- `permissions` (object): a mapping string --> boolean that indicates the permission required by this tool. This is useful for the UI to ask the user before calling the tool. For now, the only permission supported is `"write"`
|
||||||
- `definition` (object): the OAI-compat definition of this tool
|
- `definition` (object): the OAI-compat definition of this tool
|
||||||
|
|
||||||
|
|||||||
@@ -196,11 +196,11 @@ For the full list of features, please refer to [server's changelog](https://gith
|
|||||||
| `--ui-config, --webui-config JSON` | JSON that provides default UI settings (overrides UI defaults)<br/>(env: LLAMA_ARG_UI_CONFIG) |
|
| `--ui-config, --webui-config JSON` | JSON that provides default UI settings (overrides UI defaults)<br/>(env: LLAMA_ARG_UI_CONFIG) |
|
||||||
| `--ui-config-file, --webui-config-file PATH` | JSON file that provides default UI settings (overrides UI defaults)<br/>(env: LLAMA_ARG_UI_CONFIG_FILE) |
|
| `--ui-config-file, --webui-config-file PATH` | JSON file that provides default UI settings (overrides UI defaults)<br/>(env: LLAMA_ARG_UI_CONFIG_FILE) |
|
||||||
| `--ui-mcp-proxy, --webui-mcp-proxy, --no-ui-mcp-proxy, --no-webui-mcp-proxy` | experimental: whether to enable MCP CORS proxy - do not enable in untrusted environments (default: disabled)<br/>(env: LLAMA_ARG_UI_MCP_PROXY) |
|
| `--ui-mcp-proxy, --webui-mcp-proxy, --no-ui-mcp-proxy, --no-webui-mcp-proxy` | experimental: whether to enable MCP CORS proxy - do not enable in untrusted environments (default: disabled)<br/>(env: LLAMA_ARG_UI_MCP_PROXY) |
|
||||||
| `--tools TOOL1,TOOL2,...` | experimental: whether to enable built-in tools for AI agents - do not enable in untrusted environments (default: no tools)<br/>specify "all" to enable all tools<br/>available tools: read_file, file_glob_search, grep_search, exec_shell_command, write_file, edit_file, get_info<br/>note: for security reasons, this will limit --cors-origins to localhost by default<br/>(env: LLAMA_ARG_TOOLS) |
|
| `--tools TOOL1,TOOL2,...` | experimental: whether to enable server tools for AI agents - do not enable in untrusted environments (default: no tools)<br/>specify "all" to enable all tools<br/>available tools: read_file, file_glob_search, grep_search, exec_shell_command, write_file, edit_file, get_info<br/>note: for security reasons, this will limit --cors-origins to localhost by default<br/>(env: LLAMA_ARG_TOOLS) |
|
||||||
| `--tools-runtime OPTION` | experimental: run tools in a separate runtime environment (default: none, use host environment)<br/>available options:<br/> 'docker:<image>', 'podman:<image>': spin up a new container and reuse it for all invocations, clean up on server exit<br/> 'docker-container:<id>', 'podman-container:<id>': use an existing container by ID, won't stop on server exit<br/> 'ssh:<target>': run tools on a remote POSIX host over SSH, key-based auth and a trusted host key are required<br/><br/>(env: LLAMA_ARG_TOOLS_RUNTIME) |
|
| `--tools-runtime OPTION` | experimental: run tools in a separate runtime environment (default: none, use host environment)<br/>available options:<br/> 'docker:<image>', 'podman:<image>': spin up a new container and reuse it for all invocations, clean up on server exit<br/> 'docker-container:<id>', 'podman-container:<id>': use an existing container by ID, won't stop on server exit<br/> 'ssh:<target>': run tools on a remote POSIX host over SSH, key-based auth and a trusted host key are required<br/><br/>(env: LLAMA_ARG_TOOLS_RUNTIME) |
|
||||||
| `--mcp-servers-config PATH` | experimental: path to JSON file with MCP server definitions (Cursor-compatible format) - do not enable in untrusted environments (default: none)<br/>note: for security reasons, this will limit --cors-origins to localhost by default<br/>(env: LLAMA_ARG_MCP_SERVERS_CONFIG) |
|
| `--mcp-servers-config PATH` | experimental: path to JSON file with MCP server definitions (Cursor-compatible format) - do not enable in untrusted environments (default: none)<br/>note: for security reasons, this will limit --cors-origins to localhost by default<br/>(env: LLAMA_ARG_MCP_SERVERS_CONFIG) |
|
||||||
| `--mcp-servers-json JSON` | experimental: inline JSON with MCP server definitions (Cursor-compatible format) - do not enable in untrusted environments (default: none)<br/>note: for security reasons, this will limit --cors-origins to localhost by default<br/>(env: LLAMA_ARG_MCP_SERVERS_JSON) |
|
| `--mcp-servers-json JSON` | experimental: inline JSON with MCP server definitions (Cursor-compatible format) - do not enable in untrusted environments (default: none)<br/>note: for security reasons, this will limit --cors-origins to localhost by default<br/>(env: LLAMA_ARG_MCP_SERVERS_JSON) |
|
||||||
| `-ag, --agent, -no-ag, --no-agent` | whether to enable CORS proxy and all built-in tools - do not enable in untrusted environments (default: disabled)<br/>note: for security reasons, this will limit --cors-origins to localhost by default<br/>(env: LLAMA_ARG_AGENT) |
|
| `-ag, --agent, -no-ag, --no-agent` | whether to enable CORS proxy and all server tools - do not enable in untrusted environments (default: disabled)<br/>note: for security reasons, this will limit --cors-origins to localhost by default<br/>(env: LLAMA_ARG_AGENT) |
|
||||||
| `--ui, --webui, --no-ui, --no-webui` | whether to enable the Web UI (default: enabled)<br/>(env: LLAMA_ARG_UI) |
|
| `--ui, --webui, --no-ui, --no-webui` | whether to enable the Web UI (default: enabled)<br/>(env: LLAMA_ARG_UI) |
|
||||||
| `--embedding, --embeddings` | restrict to only support embedding use case; use only with dedicated embedding models (default: disabled)<br/>(env: LLAMA_ARG_EMBEDDINGS) |
|
| `--embedding, --embeddings` | restrict to only support embedding use case; use only with dedicated embedding models (default: disabled)<br/>(env: LLAMA_ARG_EMBEDDINGS) |
|
||||||
| `--rerank, --reranking` | enable reranking endpoint on server (default: disabled)<br/>(env: LLAMA_ARG_RERANKING) |
|
| `--rerank, --reranking` | enable reranking endpoint on server (default: disabled)<br/>(env: LLAMA_ARG_RERANKING) |
|
||||||
@@ -337,9 +337,9 @@ It is currently available in the following endpoints:
|
|||||||
|
|
||||||
For more details, please refer to [multimodal documentation](../../docs/multimodal.md)
|
For more details, please refer to [multimodal documentation](../../docs/multimodal.md)
|
||||||
|
|
||||||
### Built-in tools support
|
### Server tools support
|
||||||
|
|
||||||
The server includes a set of built-in tools that enable the LLM to access the local file system directly from the Web UI.
|
The server includes a set of server tools that enable the LLM to access the local file system directly from the Web UI.
|
||||||
|
|
||||||
To use this feature, start the server with `--tools all`. You can also enable only specific tools by passing a comma-separated list: `--tools name1,name2,...`. Run `--help` for the full list of available tool names.
|
To use this feature, start the server with `--tools all`. You can also enable only specific tools by passing a comma-separated list: `--tools name1,name2,...`. Run `--help` for the full list of available tool names.
|
||||||
|
|
||||||
@@ -1631,9 +1631,9 @@ curl http://localhost:8080/v1/messages/count_tokens \
|
|||||||
{"input_tokens": 10}
|
{"input_tokens": 10}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Server built-in tools
|
## Server tools
|
||||||
|
|
||||||
The server exposes a REST API under `/tools` that allows the Web UI to call built-in tools. This endpoint is intended to be used internally by the Web UI and subject to change or to be removed in the future.
|
The server exposes a REST API under `/tools` that allows the Web UI to call server tools. This endpoint is intended to be used internally by the Web UI and subject to change or to be removed in the future.
|
||||||
|
|
||||||
**Please do NOT use this endpoint in a downstream application**
|
**Please do NOT use this endpoint in a downstream application**
|
||||||
|
|
||||||
|
|||||||
@@ -2035,7 +2035,7 @@ void server_tools::setup(const std::vector<std::string> & enabled_tools,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// append MCP tools, skipping any that collide with a built-in or another MCP tool of the same "<server>_<tool>" name
|
// append MCP tools, skipping any that collide with a server tool or another MCP tool of the same "<server>_<tool>" name
|
||||||
if (!mcp_mgr.empty()) {
|
if (!mcp_mgr.empty()) {
|
||||||
std::unordered_set<std::string> seen_names;
|
std::unordered_set<std::string> seen_names;
|
||||||
for (auto & t : tools) {
|
for (auto & t : tools) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ struct server_tool {
|
|||||||
|
|
||||||
virtual ~server_tool() = default;
|
virtual ~server_tool() = default;
|
||||||
virtual json get_definition() const = 0;
|
virtual json get_definition() const = 0;
|
||||||
virtual std::string type() const { return "builtin"; }
|
virtual std::string type() const { return "server"; }
|
||||||
|
|
||||||
struct stream {
|
struct stream {
|
||||||
server_response & qr;
|
server_response & qr;
|
||||||
|
|||||||
@@ -346,7 +346,7 @@ int llama_server(common_params & params, int argc, char ** argv) {
|
|||||||
ctx_http.get ("/tools", ex_wrapper(tools.handle_get));
|
ctx_http.get ("/tools", ex_wrapper(tools.handle_get));
|
||||||
ctx_http.post("/tools", ex_wrapper(tools.handle_post));
|
ctx_http.post("/tools", ex_wrapper(tools.handle_post));
|
||||||
if (!params.server_tools.empty()) {
|
if (!params.server_tools.empty()) {
|
||||||
warn_names.push_back("built-in tools (experimental)");
|
warn_names.push_back("server tools (experimental)");
|
||||||
}
|
}
|
||||||
if (!params.server_tools_runtime.empty()) {
|
if (!params.server_tools_runtime.empty()) {
|
||||||
warn_names.push_back("tools runtime (experimental)");
|
warn_names.push_back("tools runtime (experimental)");
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@
|
|||||||
<span>
|
<span>
|
||||||
Run llama-server with <code>{CLI_FLAGS.TOOLS}</code> flag to enable
|
Run llama-server with <code>{CLI_FLAGS.TOOLS}</code> flag to enable
|
||||||
|
|
||||||
<strong>Built-in Tools</strong>.
|
<strong>Server Tools</strong>.
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -62,7 +62,7 @@
|
|||||||
// it, the picker still opens for manual entry but explains why search is
|
// it, the picker still opens for manual entry but explains why search is
|
||||||
// unavailable instead of firing searches that would only fail. Browse is
|
// unavailable instead of firing searches that would only fail. Browse is
|
||||||
// hidden too: it resolves the picked folder name through the same tool.
|
// hidden too: it resolves the picked folder name through the same tool.
|
||||||
const fileSearchKey = $derived(toolsStore.getPermissionKey(BuiltInTool.FILE_GLOB_SEARCH));
|
const fileSearchKey = $derived(toolsStore.getPermissionKey(BuiltInTool.SERVER_FILE_GLOB_SEARCH));
|
||||||
const fileSearchEnabled = $derived(
|
const fileSearchEnabled = $derived(
|
||||||
fileSearchKey !== null && toolsStore.isToolEnabled(fileSearchKey)
|
fileSearchKey !== null && toolsStore.isToolEnabled(fileSearchKey)
|
||||||
);
|
);
|
||||||
@@ -212,7 +212,7 @@
|
|||||||
// so the caller fails visibly instead of committing a bare leaf name.
|
// so the caller fails visibly instead of committing a bare leaf name.
|
||||||
async function resolveNativeName(name: string): Promise<string | null> {
|
async function resolveNativeName(name: string): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
const res = await ToolsService.executeToolRaw(BuiltInTool.FILE_GLOB_SEARCH, {
|
const res = await ToolsService.executeToolRaw(BuiltInTool.SERVER_FILE_GLOB_SEARCH, {
|
||||||
include: buildCaseInsensitiveGlob(name),
|
include: buildCaseInsensitiveGlob(name),
|
||||||
limit: SEARCH.NATIVE_LIMIT,
|
limit: SEARCH.NATIVE_LIMIT,
|
||||||
max_depth: SEARCH.NATIVE_MAX_DEPTH,
|
max_depth: SEARCH.NATIVE_MAX_DEPTH,
|
||||||
|
|||||||
+1
-1
@@ -51,7 +51,7 @@
|
|||||||
// When the server does not expose file_glob_search (started without
|
// When the server does not expose file_glob_search (started without
|
||||||
// --tools) or the user disabled it, the picker still opens but explains
|
// --tools) or the user disabled it, the picker still opens but explains
|
||||||
// why instead of firing searches that would only fail.
|
// why instead of firing searches that would only fail.
|
||||||
const fileSearchKey = $derived(toolsStore.getPermissionKey(BuiltInTool.FILE_GLOB_SEARCH));
|
const fileSearchKey = $derived(toolsStore.getPermissionKey(BuiltInTool.SERVER_FILE_GLOB_SEARCH));
|
||||||
const fileSearchEnabled = $derived(
|
const fileSearchEnabled = $derived(
|
||||||
fileSearchKey !== null && toolsStore.isToolEnabled(fileSearchKey)
|
fileSearchKey !== null && toolsStore.isToolEnabled(fileSearchKey)
|
||||||
);
|
);
|
||||||
|
|||||||
+10
-10
@@ -35,19 +35,19 @@
|
|||||||
|
|
||||||
{#if isSearchCall}
|
{#if isSearchCall}
|
||||||
<ChatMessageToolCallBlockSearchResults {section} {open} {isStreaming} {onToggle} />
|
<ChatMessageToolCallBlockSearchResults {section} {open} {isStreaming} {onToggle} />
|
||||||
{:else if section.toolName === BuiltInTool.GET_DATETIME}
|
{:else if section.toolName === BuiltInTool.BROWSER_GET_DATETIME}
|
||||||
<ChatMessageToolCallBlockGetDatetime {section} {isStreaming} />
|
<ChatMessageToolCallBlockGetDatetime {section} {isStreaming} />
|
||||||
{:else if section.toolName === BuiltInTool.GET_INFO}
|
{:else if section.toolName === BuiltInTool.SERVER_GET_INFO}
|
||||||
<ChatMessageToolCallBlockGetInfo {section} {isStreaming} />
|
<ChatMessageToolCallBlockGetInfo {section} {isStreaming} />
|
||||||
{:else if section.toolName === BuiltInTool.READ_FILE}
|
{:else if section.toolName === BuiltInTool.SERVER_READ_FILE}
|
||||||
<ChatMessageToolCallBlockReadFile {section} {open} {isStreaming} {onToggle} />
|
<ChatMessageToolCallBlockReadFile {section} {open} {isStreaming} {onToggle} />
|
||||||
{:else if section.toolName === BuiltInTool.READ_MEDIA}
|
{:else if section.toolName === BuiltInTool.BROWSER_READ_MEDIA}
|
||||||
<ChatMessageToolCallBlockReadMedia {section} {open} {isStreaming} {onToggle} />
|
<ChatMessageToolCallBlockReadMedia {section} {open} {isStreaming} {onToggle} />
|
||||||
{:else if section.toolName === BuiltInTool.EDIT_FILE}
|
{:else if section.toolName === BuiltInTool.SERVER_EDIT_FILE}
|
||||||
<ChatMessageToolCallBlockEditFile {section} {open} {isStreaming} {onToggle} />
|
<ChatMessageToolCallBlockEditFile {section} {open} {isStreaming} {onToggle} />
|
||||||
{:else if section.toolName === BuiltInTool.WRITE_FILE}
|
{:else if section.toolName === BuiltInTool.SERVER_WRITE_FILE}
|
||||||
<ChatMessageToolCallBlockWriteFile {section} {open} {isStreaming} {onToggle} />
|
<ChatMessageToolCallBlockWriteFile {section} {open} {isStreaming} {onToggle} />
|
||||||
{:else if section.toolName === BuiltInTool.EXEC_SHELL_COMMAND}
|
{:else if section.toolName === BuiltInTool.SERVER_EXEC_SHELL_COMMAND}
|
||||||
<ChatMessageToolCallBlockExecShellCommand
|
<ChatMessageToolCallBlockExecShellCommand
|
||||||
{section}
|
{section}
|
||||||
{open}
|
{open}
|
||||||
@@ -56,11 +56,11 @@
|
|||||||
{attachments}
|
{attachments}
|
||||||
{onToggle}
|
{onToggle}
|
||||||
/>
|
/>
|
||||||
{:else if section.toolName === BuiltInTool.FILE_GLOB_SEARCH}
|
{:else if section.toolName === BuiltInTool.SERVER_FILE_GLOB_SEARCH}
|
||||||
<ChatMessageToolCallBlockFileGlobSearch {section} {open} {isStreaming} {onToggle} />
|
<ChatMessageToolCallBlockFileGlobSearch {section} {open} {isStreaming} {onToggle} />
|
||||||
{:else if section.toolName === BuiltInTool.GREP_SEARCH}
|
{:else if section.toolName === BuiltInTool.SERVER_GREP_SEARCH}
|
||||||
<ChatMessageToolCallBlockGrepSearch {section} {open} {isStreaming} {onToggle} />
|
<ChatMessageToolCallBlockGrepSearch {section} {open} {isStreaming} {onToggle} />
|
||||||
{:else if section.toolName === BuiltInTool.RUN_JAVASCRIPT}
|
{:else if section.toolName === BuiltInTool.BROWSER_RUN_JAVASCRIPT}
|
||||||
<ChatMessageToolCallBlockRunJavascript {section} {open} {isStreaming} {onToggle} />
|
<ChatMessageToolCallBlockRunJavascript {section} {open} {isStreaming} {onToggle} />
|
||||||
{:else}
|
{:else}
|
||||||
<ChatMessageToolCallBlockDefault {section} {open} {isStreaming} {attachments} {onToggle} />
|
<ChatMessageToolCallBlockDefault {section} {open} {isStreaming} {attachments} {onToggle} />
|
||||||
|
|||||||
+2
-2
@@ -12,7 +12,7 @@
|
|||||||
import {
|
import {
|
||||||
classifyToolResult,
|
classifyToolResult,
|
||||||
formatJsonPretty,
|
formatJsonPretty,
|
||||||
getBuiltinToolUi,
|
getToolUi,
|
||||||
parseToolResultWithMedia
|
parseToolResultWithMedia
|
||||||
} from '$lib/utils';
|
} from '$lib/utils';
|
||||||
import { createBase64DataUrl } from '$lib/utils/data-url';
|
import { createBase64DataUrl } from '$lib/utils/data-url';
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
let { attachments, isStreaming, onToggle, open, section }: Props = $props();
|
let { attachments, isStreaming, onToggle, open, section }: Props = $props();
|
||||||
|
|
||||||
const title = $derived(getBuiltinToolUi(section.toolName)?.label ?? section.toolName ?? '');
|
const title = $derived(getToolUi(section.toolName)?.label ?? section.toolName ?? '');
|
||||||
const outputKind = $derived(classifyToolResult(section.toolResult));
|
const outputKind = $derived(classifyToolResult(section.toolResult));
|
||||||
const parsedLines: ToolResultLine[] = $derived(
|
const parsedLines: ToolResultLine[] = $derived(
|
||||||
section.toolResult ? parseToolResultWithMedia(section.toolResult, attachments) : []
|
section.toolResult ? parseToolResultWithMedia(section.toolResult, attachments) : []
|
||||||
|
|||||||
+2
-2
@@ -6,7 +6,7 @@
|
|||||||
import { MAX_HEIGHT_CODE_BLOCK } from '$lib/constants';
|
import { MAX_HEIGHT_CODE_BLOCK } from '$lib/constants';
|
||||||
import { FileTypeText } from '$lib/enums';
|
import { FileTypeText } from '$lib/enums';
|
||||||
import type { AgenticSection } from '$lib/types';
|
import type { AgenticSection } from '$lib/types';
|
||||||
import { getBuiltinToolUi } from '$lib/utils';
|
import { getToolUi } from '$lib/utils';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
section: AgenticSection;
|
section: AgenticSection;
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
let { isStreaming, onToggle, open, section }: Props = $props();
|
let { isStreaming, onToggle, open, section }: Props = $props();
|
||||||
|
|
||||||
const runJsMeta = $derived(parseRunJavascriptMeta(section));
|
const runJsMeta = $derived(parseRunJavascriptMeta(section));
|
||||||
const title = $derived(getBuiltinToolUi(section.toolName)?.label ?? section.toolName ?? '');
|
const title = $derived(getToolUi(section.toolName)?.label ?? section.toolName ?? '');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ToolCallBlock {section} {open} {isStreaming} meta={runJsMeta} {title} {onToggle}>
|
<ToolCallBlock {section} {open} {isStreaming} meta={runJsMeta} {title} {onToggle}>
|
||||||
|
|||||||
+3
-3
@@ -14,8 +14,8 @@
|
|||||||
import { 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 { AgenticSectionType } from '$lib/enums';
|
||||||
import { mcpStore } from '$lib/stores';
|
import { mcpStore } from '$lib/stores';
|
||||||
import type { AgenticSection, BuiltinToolUiEntry } from '$lib/types';
|
import type { AgenticSection, ToolUiEntry } from '$lib/types';
|
||||||
import { getBuiltinToolUi } from '$lib/utils';
|
import { getToolUi } from '$lib/utils';
|
||||||
import type { Component, Snippet } from 'svelte';
|
import type { Component, Snippet } from 'svelte';
|
||||||
|
|
||||||
type ToolCallBlockMetaWithError = TMeta & { errorMessage?: string };
|
type ToolCallBlockMetaWithError = TMeta & { errorMessage?: string };
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
const showSpinner = $derived(isPending || (isStreamingCall && isStreaming) || extraLiveStreaming);
|
const showSpinner = $derived(isPending || (isStreamingCall && isStreaming) || extraLiveStreaming);
|
||||||
const isCodeStreaming = $derived(isStreaming && (isPending || isStreamingCall));
|
const isCodeStreaming = $derived(isStreaming && (isPending || isStreamingCall));
|
||||||
|
|
||||||
const toolUi: BuiltinToolUiEntry | null = $derived(getBuiltinToolUi(section.toolName));
|
const toolUi: ToolUiEntry | null = $derived(getToolUi(section.toolName));
|
||||||
const toolIcon: Component = $derived(
|
const toolIcon: Component = $derived(
|
||||||
spinIconWhenActive && showSpinner ? Loader2 : (toolUi?.icon ?? Wrench)
|
spinIconWhenActive && showSpinner ? Loader2 : (toolUi?.icon ?? Wrench)
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ export type EditFileMeta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function parseEditFileMeta(section: AgenticSection): EditFileMeta | null {
|
export function parseEditFileMeta(section: AgenticSection): EditFileMeta | null {
|
||||||
const args = parseToolArgs(BuiltInTool.EDIT_FILE, section, { partial: true });
|
const args = parseToolArgs(BuiltInTool.SERVER_EDIT_FILE, section, { partial: true });
|
||||||
|
|
||||||
if (!args) return null;
|
if (!args) return null;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ export type ExecShellCommandMeta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function parseExecShellCommandMeta(section: AgenticSection): ExecShellCommandMeta | null {
|
export function parseExecShellCommandMeta(section: AgenticSection): ExecShellCommandMeta | null {
|
||||||
const args = parseToolArgs(BuiltInTool.EXEC_SHELL_COMMAND, section);
|
const args = parseToolArgs(BuiltInTool.SERVER_EXEC_SHELL_COMMAND, section);
|
||||||
|
|
||||||
if (!args) return null;
|
if (!args) return null;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ export type FileGlobSearchMeta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function parseFileGlobSearchMeta(section: AgenticSection): FileGlobSearchMeta | null {
|
export function parseFileGlobSearchMeta(section: AgenticSection): FileGlobSearchMeta | null {
|
||||||
const args = parseToolArgs(BuiltInTool.FILE_GLOB_SEARCH, section);
|
const args = parseToolArgs(BuiltInTool.SERVER_FILE_GLOB_SEARCH, section);
|
||||||
|
|
||||||
if (!args) return null;
|
if (!args) return null;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ export type GrepSearchMeta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function parseGrepSearchMeta(section: AgenticSection): GrepSearchMeta | null {
|
export function parseGrepSearchMeta(section: AgenticSection): GrepSearchMeta | null {
|
||||||
const args = parseToolArgs(BuiltInTool.GREP_SEARCH, section);
|
const args = parseToolArgs(BuiltInTool.SERVER_GREP_SEARCH, section);
|
||||||
|
|
||||||
if (!args) return null;
|
if (!args) return null;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ export type ReadFileMeta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function parseReadFileMeta(section: AgenticSection): ReadFileMeta | null {
|
export function parseReadFileMeta(section: AgenticSection): ReadFileMeta | null {
|
||||||
const args = parseToolArgs(BuiltInTool.READ_FILE, section, { partial: true });
|
const args = parseToolArgs(BuiltInTool.SERVER_READ_FILE, section, { partial: true });
|
||||||
|
|
||||||
if (!args) return null;
|
if (!args) return null;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ export type RunJavascriptMeta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function parseRunJavascriptMeta(section: AgenticSection): RunJavascriptMeta | null {
|
export function parseRunJavascriptMeta(section: AgenticSection): RunJavascriptMeta | null {
|
||||||
const args = parseToolArgs(BuiltInTool.RUN_JAVASCRIPT, section);
|
const args = parseToolArgs(BuiltInTool.BROWSER_RUN_JAVASCRIPT, section);
|
||||||
|
|
||||||
if (!args) return null;
|
if (!args) return null;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ export type WriteFileMeta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function parseWriteFileMeta(section: AgenticSection): WriteFileMeta | null {
|
export function parseWriteFileMeta(section: AgenticSection): WriteFileMeta | null {
|
||||||
const args = parseToolArgs(BuiltInTool.WRITE_FILE, section, { partial: true });
|
const args = parseToolArgs(BuiltInTool.SERVER_WRITE_FILE, section, { partial: true });
|
||||||
|
|
||||||
if (!args) return null;
|
if (!args) return null;
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -61,8 +61,8 @@
|
|||||||
{:else}
|
{:else}
|
||||||
{@const source = toolsStore.getToolSource(toolName)}
|
{@const source = toolsStore.getToolSource(toolName)}
|
||||||
{@const providerName =
|
{@const providerName =
|
||||||
source === ToolSource.BUILTIN
|
source === ToolSource.SERVER
|
||||||
? TOOL_SERVER_LABELS[ToolSource.BUILTIN]
|
? TOOL_SERVER_LABELS[ToolSource.SERVER]
|
||||||
: source === ToolSource.CUSTOM
|
: source === ToolSource.CUSTOM
|
||||||
? TOOL_SERVER_LABELS[ToolSource.CUSTOM]
|
? TOOL_SERVER_LABELS[ToolSource.CUSTOM]
|
||||||
: 'MCP Tools'}
|
: 'MCP Tools'}
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ export { default as ChatFormInput } from './ChatForm/ChatFormInput/ChatFormInput
|
|||||||
/**
|
/**
|
||||||
* Working directory selector for agent mode. Renders a chip below the chat
|
* Working directory selector for agent mode. Renders a chip below the chat
|
||||||
* form; clicking it opens a popover with a directory picker backed by the
|
* form; clicking it opens a popover with a directory picker backed by the
|
||||||
* server's `file_glob_search` built-in tool (POST /tools). The picked
|
* server's `file_glob_search` server tool (POST /tools). The picked
|
||||||
* directory is exposed via `bind:directory`; changing it records a
|
* directory is exposed via `bind:directory`; changing it records a
|
||||||
* synthetic "Set working directory to ..." user message into chat history
|
* synthetic "Set working directory to ..." user message into chat history
|
||||||
* and is enforced on tool calls via the `x-tool-cwd` request header.
|
* and is enforced on tool calls via the `x-tool-cwd` request header.
|
||||||
@@ -380,7 +380,7 @@ export { default as ChatFormPickerListItemSkeleton } from './ChatForm/ChatFormPi
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* `@`-triggered file/folder mention picker. Resolves `@<query>` in the chat
|
* `@`-triggered file/folder mention picker. Resolves `@<query>` in the chat
|
||||||
* input to a filesystem match via the server's `file_glob_search` built-in
|
* input to a filesystem match via the server's `file_glob_search` server tool
|
||||||
* tool, scoped to the conversation cwd (or server home when unset).
|
* tool, scoped to the conversation cwd (or server home when unset).
|
||||||
* Selection splices a `[name](file:///<abs path>)` link into the input.
|
* Selection splices a `[name](file:///<abs path>)` link into the input.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||||
import { ToolSource } from '$lib/enums/tools.enums';
|
import { ToolSource } from '$lib/enums/tools.enums';
|
||||||
import { mcpStore, permissionsStore, toolsStore } from '$lib/stores';
|
import { mcpStore, permissionsStore, toolsStore } from '$lib/stores';
|
||||||
import { getBuiltinToolUi } from '$lib/utils';
|
import { getToolUi } from '$lib/utils';
|
||||||
import { SvelteSet } from 'svelte/reactivity';
|
import { SvelteSet } from 'svelte/reactivity';
|
||||||
|
|
||||||
let expandedGroups = new SvelteSet<string>();
|
let expandedGroups = new SvelteSet<string>();
|
||||||
@@ -69,12 +69,12 @@
|
|||||||
|
|
||||||
{#each group.tools as entry (entry.key)}
|
{#each group.tools as entry (entry.key)}
|
||||||
{@const toolName = entry.definition.function.name}
|
{@const toolName = entry.definition.function.name}
|
||||||
{@const builtinUi =
|
{@const toolUi =
|
||||||
entry.source === ToolSource.BUILTIN || entry.source === ToolSource.FRONTEND
|
entry.source === ToolSource.SERVER || entry.source === ToolSource.BROWSER
|
||||||
? getBuiltinToolUi(toolName)
|
? getToolUi(toolName)
|
||||||
: null}
|
: null}
|
||||||
{@const displayLabel = builtinUi?.label ?? toolName}
|
{@const displayLabel = toolUi?.label ?? toolName}
|
||||||
{@const IconComponent = builtinUi?.icon ?? null}
|
{@const IconComponent = toolUi?.icon ?? null}
|
||||||
{@const isEnabled = toolsStore.isToolEnabled(entry.key)}
|
{@const isEnabled = toolsStore.isToolEnabled(entry.key)}
|
||||||
{@const permissionKey = entry.key}
|
{@const permissionKey = entry.key}
|
||||||
{@const isAlwaysAllowed = permissionsStore.hasTool(permissionKey)}
|
{@const isAlwaysAllowed = permissionsStore.hasTool(permissionKey)}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export { default as SettingsChatFields } from './SettingsChat/SettingsChatFields
|
|||||||
/**
|
/**
|
||||||
* **SettingsChatToolsTab** - Tools configuration tab for chat settings
|
* **SettingsChatToolsTab** - Tools configuration tab for chat settings
|
||||||
*
|
*
|
||||||
* Displays available tools grouped by source (built-in, MCP, custom) with
|
* Displays available tools grouped by source (server, browser, MCP, custom) with
|
||||||
* toggles to enable/disable individual tools and tool groups. Shows MCP
|
* toggles to enable/disable individual tools and tool groups. Shows MCP
|
||||||
* server favicons and permission management controls.
|
* server favicons and permission management controls.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { CLI_FLAGS } from './cli-flags.constants';
|
|||||||
import { BuiltInTool, JsonSchemaType, ToolCallType } from '$lib/enums';
|
import { BuiltInTool, JsonSchemaType, ToolCallType } from '$lib/enums';
|
||||||
import type { OpenAIToolDefinition } from '$lib/types';
|
import type { OpenAIToolDefinition } from '$lib/types';
|
||||||
|
|
||||||
export const BROWSER_INFO_TOOL_NAME = BuiltInTool.GET_INFO;
|
// get_info is served by the server, but the browser falls back to this
|
||||||
|
// implementation when the server does not provide it - same wire name.
|
||||||
|
export const BROWSER_INFO_TOOL_NAME = BuiltInTool.SERVER_GET_INFO;
|
||||||
|
|
||||||
/** UA token to OS name, first match wins - Android and iOS UAs also carry the Linux / Mac OS X tokens */
|
/** UA token to OS name, first match wins - Android and iOS UAs also carry the Linux / Mac OS X tokens */
|
||||||
export const BROWSER_INFO_OS_UA_PATTERNS: readonly [RegExp, string][] = [
|
export const BROWSER_INFO_OS_UA_PATTERNS: readonly [RegExp, string][] = [
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
// Registry of built-in and frontend (browser) tools whose renderer
|
|
||||||
// shows a recognizable icon and friendly label inline in the chat UI.
|
|
||||||
//
|
|
||||||
// To add a new built-in tool, add an entry to BUILTIN_TOOL_UI. To give a
|
|
||||||
// tool a custom title or body renderer, add a dedicated component under
|
|
||||||
// ChatMessageToolCall/ and route it in ChatMessageToolCallBlock.svelte
|
|
||||||
// (see ChatMessageToolCallBlockGetDatetime and
|
|
||||||
// ChatMessageToolCallBlockSearchResults for prior art).
|
|
||||||
|
|
||||||
import {
|
|
||||||
Braces,
|
|
||||||
Clock,
|
|
||||||
Eye,
|
|
||||||
FilePen,
|
|
||||||
FilePlus,
|
|
||||||
FileSearch,
|
|
||||||
FileText,
|
|
||||||
Info,
|
|
||||||
SearchCode,
|
|
||||||
Terminal
|
|
||||||
} from '@lucide/svelte';
|
|
||||||
import { BuiltInTool, ToolSource } from '$lib/enums';
|
|
||||||
import type { BuiltinToolUiEntry } from '$lib/types';
|
|
||||||
|
|
||||||
export const BUILTIN_TOOL_UI: Readonly<Record<BuiltInTool, BuiltinToolUiEntry>> = {
|
|
||||||
[BuiltInTool.EDIT_FILE]: { icon: FilePen, label: 'Edit file', source: ToolSource.BUILTIN },
|
|
||||||
[BuiltInTool.EXEC_SHELL_COMMAND]: {
|
|
||||||
icon: Terminal,
|
|
||||||
label: 'Run command',
|
|
||||||
source: ToolSource.BUILTIN
|
|
||||||
},
|
|
||||||
[BuiltInTool.FILE_GLOB_SEARCH]: {
|
|
||||||
icon: FileSearch,
|
|
||||||
label: 'Search files',
|
|
||||||
source: ToolSource.BUILTIN
|
|
||||||
},
|
|
||||||
[BuiltInTool.GET_DATETIME]: { icon: Clock, label: 'Current time', source: ToolSource.FRONTEND },
|
|
||||||
[BuiltInTool.GET_INFO]: { icon: Info, label: 'Runtime info', source: ToolSource.BUILTIN },
|
|
||||||
[BuiltInTool.GREP_SEARCH]: {
|
|
||||||
icon: SearchCode,
|
|
||||||
label: 'Search in files',
|
|
||||||
source: ToolSource.BUILTIN
|
|
||||||
},
|
|
||||||
[BuiltInTool.READ_FILE]: { icon: FileText, label: 'Read file', source: ToolSource.BUILTIN },
|
|
||||||
[BuiltInTool.READ_MEDIA]: { icon: Eye, label: 'Read media', source: ToolSource.FRONTEND },
|
|
||||||
[BuiltInTool.RUN_JAVASCRIPT]: {
|
|
||||||
icon: Braces,
|
|
||||||
label: 'Run JavaScript',
|
|
||||||
source: ToolSource.FRONTEND
|
|
||||||
},
|
|
||||||
[BuiltInTool.WRITE_FILE]: { icon: FilePlus, label: 'Write file', source: ToolSource.BUILTIN }
|
|
||||||
} as const;
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { BuiltInTool, JsonSchemaType, ToolCallType } from '$lib/enums';
|
import { BuiltInTool, JsonSchemaType, ToolCallType } from '$lib/enums';
|
||||||
import type { OpenAIToolDefinition } from '$lib/types';
|
import type { OpenAIToolDefinition } from '$lib/types';
|
||||||
|
|
||||||
export const GET_DATETIME_TOOL_NAME = BuiltInTool.GET_DATETIME;
|
export const GET_DATETIME_TOOL_NAME = BuiltInTool.BROWSER_GET_DATETIME;
|
||||||
|
|
||||||
export function buildGetDatetimeToolDefinition(): OpenAIToolDefinition {
|
export function buildGetDatetimeToolDefinition(): OpenAIToolDefinition {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export * from './context-gauge-popup.constants';
|
|||||||
export * from './conversation-import.constants';
|
export * from './conversation-import.constants';
|
||||||
export * from './binary-detection.constants';
|
export * from './binary-detection.constants';
|
||||||
export * from './content-detection.constants';
|
export * from './content-detection.constants';
|
||||||
export * from './built-in-tools.constants';
|
export * from './tool-ui.constants';
|
||||||
export * from './cache.constants';
|
export * from './cache.constants';
|
||||||
export * from './chat-form.constants';
|
export * from './chat-form.constants';
|
||||||
export * from './cli-flags.constants';
|
export * from './cli-flags.constants';
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
} from '$lib/enums';
|
} from '$lib/enums';
|
||||||
import type { OpenAIToolDefinition } from '$lib/types';
|
import type { OpenAIToolDefinition } from '$lib/types';
|
||||||
|
|
||||||
export const READ_MEDIA_TOOL_NAME = BuiltInTool.READ_MEDIA;
|
export const READ_MEDIA_TOOL_NAME = BuiltInTool.BROWSER_READ_MEDIA;
|
||||||
|
|
||||||
// header lines of the tool result, parsed back by the read_media renderer
|
// header lines of the tool result, parsed back by the read_media renderer
|
||||||
export const PREFIX_FILE = 'File: ';
|
export const PREFIX_FILE = 'File: ';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { BuiltInTool } from '$lib/enums';
|
import { BuiltInTool } from '$lib/enums';
|
||||||
|
|
||||||
export const SANDBOX_TOOL_NAME = BuiltInTool.RUN_JAVASCRIPT;
|
export const SANDBOX_TOOL_NAME = BuiltInTool.BROWSER_RUN_JAVASCRIPT;
|
||||||
|
|
||||||
export const SANDBOX_TIMEOUT_MS_DEFAULT = 10000;
|
export const SANDBOX_TIMEOUT_MS_DEFAULT = 10000;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
// Registry of server and browser tools whose renderer
|
||||||
|
// shows a recognizable icon and friendly label inline in the chat UI.
|
||||||
|
//
|
||||||
|
// To add a new tool, add an entry to TOOL_UI. To give a
|
||||||
|
// tool a custom title or body renderer, add a dedicated component under
|
||||||
|
// ChatMessageToolCall/ and route it in ChatMessageToolCallBlock.svelte
|
||||||
|
// (see ChatMessageToolCallBlockGetDatetime and
|
||||||
|
// ChatMessageToolCallBlockSearchResults for prior art).
|
||||||
|
|
||||||
|
import {
|
||||||
|
Braces,
|
||||||
|
Clock,
|
||||||
|
Eye,
|
||||||
|
FilePen,
|
||||||
|
FilePlus,
|
||||||
|
FileSearch,
|
||||||
|
FileText,
|
||||||
|
Info,
|
||||||
|
SearchCode,
|
||||||
|
Terminal
|
||||||
|
} from '@lucide/svelte';
|
||||||
|
import { BuiltInTool, ToolSource } from '$lib/enums';
|
||||||
|
import type { ToolUiEntry } from '$lib/types';
|
||||||
|
|
||||||
|
export const TOOL_UI: Readonly<Record<BuiltInTool, ToolUiEntry>> = {
|
||||||
|
[BuiltInTool.BROWSER_GET_DATETIME]: {
|
||||||
|
icon: Clock,
|
||||||
|
label: 'Current time',
|
||||||
|
source: ToolSource.BROWSER
|
||||||
|
},
|
||||||
|
[BuiltInTool.BROWSER_READ_MEDIA]: { icon: Eye, label: 'Read media', source: ToolSource.BROWSER },
|
||||||
|
[BuiltInTool.BROWSER_RUN_JAVASCRIPT]: {
|
||||||
|
icon: Braces,
|
||||||
|
label: 'Run JavaScript',
|
||||||
|
source: ToolSource.BROWSER
|
||||||
|
},
|
||||||
|
[BuiltInTool.SERVER_EDIT_FILE]: { icon: FilePen, label: 'Edit file', source: ToolSource.SERVER },
|
||||||
|
[BuiltInTool.SERVER_EXEC_SHELL_COMMAND]: {
|
||||||
|
icon: Terminal,
|
||||||
|
label: 'Run command',
|
||||||
|
source: ToolSource.SERVER
|
||||||
|
},
|
||||||
|
[BuiltInTool.SERVER_FILE_GLOB_SEARCH]: {
|
||||||
|
icon: FileSearch,
|
||||||
|
label: 'Search files',
|
||||||
|
source: ToolSource.SERVER
|
||||||
|
},
|
||||||
|
[BuiltInTool.SERVER_GET_INFO]: { icon: Info, label: 'Runtime info', source: ToolSource.SERVER },
|
||||||
|
[BuiltInTool.SERVER_GREP_SEARCH]: {
|
||||||
|
icon: SearchCode,
|
||||||
|
label: 'Search in files',
|
||||||
|
source: ToolSource.SERVER
|
||||||
|
},
|
||||||
|
[BuiltInTool.SERVER_READ_FILE]: { icon: FileText, label: 'Read file', source: ToolSource.SERVER },
|
||||||
|
[BuiltInTool.SERVER_WRITE_FILE]: {
|
||||||
|
icon: FilePlus,
|
||||||
|
label: 'Write file',
|
||||||
|
source: ToolSource.SERVER
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
@@ -18,15 +18,15 @@ export const UI_DATA_ATTRS = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const TOOL_GROUP_LABELS = {
|
export const TOOL_GROUP_LABELS = {
|
||||||
[ToolSource.BUILTIN]: 'Built-in',
|
[ToolSource.BROWSER]: 'Browser',
|
||||||
[ToolSource.CUSTOM]: 'JSON Schema',
|
[ToolSource.CUSTOM]: 'JSON Schema',
|
||||||
[ToolSource.FRONTEND]: 'Browser'
|
[ToolSource.SERVER]: 'Server'
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const TOOL_SERVER_LABELS = {
|
export const TOOL_SERVER_LABELS = {
|
||||||
[ToolSource.BUILTIN]: 'Built-in Tools',
|
[ToolSource.BROWSER]: 'Browser Tools',
|
||||||
[ToolSource.CUSTOM]: 'Custom Tools',
|
[ToolSource.CUSTOM]: 'Custom Tools',
|
||||||
[ToolSource.FRONTEND]: 'Browser Tools'
|
[ToolSource.SERVER]: 'Server Tools'
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const TOOLTIP_DELAY_DURATION = 500;
|
export const TOOLTIP_DELAY_DURATION = 500;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
export enum ToolSource {
|
export enum ToolSource {
|
||||||
BUILTIN = 'builtin',
|
BROWSER = 'browser',
|
||||||
MCP = 'mcp',
|
|
||||||
CUSTOM = 'custom',
|
CUSTOM = 'custom',
|
||||||
FRONTEND = 'frontend'
|
MCP = 'mcp',
|
||||||
|
SERVER = 'server'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ToolPermissionDecision {
|
export enum ToolPermissionDecision {
|
||||||
@@ -28,22 +28,28 @@ export enum GlobSearchType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wire-format identifiers for built-in and frontend tools. The string
|
* Wire-format identifiers for server and browser tools. The string
|
||||||
* value matches what the model emits in tool call names, so comparing
|
* value matches what the model emits in tool call names, so comparing
|
||||||
* against `BuiltInTool.READ_FILE` is equivalent to comparing against the
|
* against `BuiltInTool.SERVER_READ_FILE` is equivalent to comparing
|
||||||
* raw `'read_file'` literal - the enum just keeps the two in lock-step
|
* against the raw `'read_file'` literal - the enum just keeps the two in
|
||||||
* and gives TypeScript a single source of truth for autocomplete / rename
|
* lock-step and gives TypeScript a single source of truth for autocomplete
|
||||||
* support.
|
* / rename support.
|
||||||
|
*
|
||||||
|
* The `SERVER_` / `BROWSER_` prefixes mirror the tool's primary source
|
||||||
|
* (llama-server vs llama-ui). `get_info` is the exception: it is served by
|
||||||
|
* the server, but llama-ui falls back to a browser implementation when the
|
||||||
|
* server does not provide it, so it can surface under both categories in
|
||||||
|
* the UI while keeping a single wire name.
|
||||||
*/
|
*/
|
||||||
export enum BuiltInTool {
|
export enum BuiltInTool {
|
||||||
READ_FILE = 'read_file',
|
BROWSER_GET_DATETIME = 'get_datetime',
|
||||||
READ_MEDIA = 'read_media',
|
BROWSER_READ_MEDIA = 'read_media',
|
||||||
EDIT_FILE = 'edit_file',
|
BROWSER_RUN_JAVASCRIPT = 'run_javascript',
|
||||||
WRITE_FILE = 'write_file',
|
SERVER_EDIT_FILE = 'edit_file',
|
||||||
GET_DATETIME = 'get_datetime',
|
SERVER_EXEC_SHELL_COMMAND = 'exec_shell_command',
|
||||||
GET_INFO = 'get_info',
|
SERVER_FILE_GLOB_SEARCH = 'file_glob_search',
|
||||||
FILE_GLOB_SEARCH = 'file_glob_search',
|
SERVER_GET_INFO = 'get_info',
|
||||||
GREP_SEARCH = 'grep_search',
|
SERVER_GREP_SEARCH = 'grep_search',
|
||||||
EXEC_SHELL_COMMAND = 'exec_shell_command',
|
SERVER_READ_FILE = 'read_file',
|
||||||
RUN_JAVASCRIPT = 'run_javascript'
|
SERVER_WRITE_FILE = 'write_file'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,13 +46,13 @@ export function useToolsPanel(): UseToolsPanelReturn {
|
|||||||
|
|
||||||
// Tools endpoint is unreachable (404) — server started without --tools
|
// Tools endpoint is unreachable (404) — server started without --tools
|
||||||
if (toolsStore.isToolsEndpointUnreachable) {
|
if (toolsStore.isToolsEndpointUnreachable) {
|
||||||
return `To enable Built-In Tools you need to run llama-server with ${CLI_FLAGS.TOOLS} all or ${CLI_FLAGS.TOOLS} <name> flag. To see MCP Tools you need to add / enable MCP Server(s).`;
|
return `To enable Server Tools you need to run llama-server with ${CLI_FLAGS.TOOLS} all or ${CLI_FLAGS.TOOLS} <name> flag. To see MCP Tools you need to add / enable MCP Server(s).`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other errors — return null so UI shows "Failed to load tools"
|
// Other errors — return null so UI shows "Failed to load tools"
|
||||||
if (toolsStore.error) return null;
|
if (toolsStore.error) return null;
|
||||||
|
|
||||||
return `To enable Built-In Tools you need to run llama-server with ${CLI_FLAGS.TOOLS} all or ${CLI_FLAGS.TOOLS} <name> flag. To see MCP Tools you need to add / enable MCP Server(s).`;
|
return `To enable Server Tools you need to run llama-server with ${CLI_FLAGS.TOOLS} all or ${CLI_FLAGS.TOOLS} <name> flag. To see MCP Tools you need to add / enable MCP Server(s).`;
|
||||||
});
|
});
|
||||||
|
|
||||||
function isGroupChecked(group: ToolGroup): boolean {
|
function isGroupChecked(group: ToolGroup): boolean {
|
||||||
@@ -95,8 +95,8 @@ export function useToolsPanel(): UseToolsPanelReturn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleOpen(): void {
|
function handleOpen(): void {
|
||||||
if (toolsStore.builtinTools.length === 0 && !toolsStore.loading) {
|
if (toolsStore.serverTools.length === 0 && !toolsStore.loading) {
|
||||||
toolsStore.fetchBuiltinTools();
|
toolsStore.fetchServerTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
mcpStore.runHealthChecksForServers(mcpStore.getServers().filter((s) => s.enabled));
|
mcpStore.runHealthChecksForServers(mcpStore.getServers().filter((s) => s.enabled));
|
||||||
|
|||||||
@@ -262,9 +262,9 @@ export { ParameterSyncService } from './parameter-sync.service';
|
|||||||
export { MCPService } from './mcp.service';
|
export { MCPService } from './mcp.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* **SandboxService** - Frontend JavaScript execution in a browser sandbox
|
* **SandboxService** - Browser JavaScript execution in a browser sandbox
|
||||||
*
|
*
|
||||||
* Stateless executor for the run_javascript frontend tool. Model generated
|
* Stateless executor for the run_javascript browser tool. Model generated
|
||||||
* code runs in a Web Worker spawned inside a sandboxed iframe with an opaque
|
* code runs in a Web Worker spawned inside a sandboxed iframe with an opaque
|
||||||
* origin: no access to the app origin, its storage or its API, and outgoing
|
* origin: no access to the app origin, its storage or its API, and outgoing
|
||||||
* requests carry a null origin. The code never touches a main thread, so the
|
* requests carry a null origin. The code never touches a main thread, so the
|
||||||
@@ -274,7 +274,7 @@ export { MCPService } from './mcp.service';
|
|||||||
* **Architecture & Relationships:**
|
* **Architecture & Relationships:**
|
||||||
* - **SandboxService** (this class): Stateless sandbox execution
|
* - **SandboxService** (this class): Stateless sandbox execution
|
||||||
* - **toolsStore**: Exposes the tool definition when the sandbox is enabled
|
* - **toolsStore**: Exposes the tool definition when the sandbox is enabled
|
||||||
* - **agenticStore**: Dispatches ToolSource.FRONTEND calls here
|
* - **agenticStore**: Dispatches ToolSource.BROWSER calls here
|
||||||
*
|
*
|
||||||
* @see buildSandboxToolDefinition in utils/sandbox-tool - 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
|
* @see agenticStore in stores/agentic.svelte.ts - tool dispatch
|
||||||
|
|||||||
@@ -28,15 +28,15 @@ function fileExtension(path: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* **ReadMediaService** - frontend executor for the `read_media` tool
|
* **ReadMediaService** - browser executor for the `read_media` tool
|
||||||
*
|
*
|
||||||
* The tool is synthetic: no such tool exists on the server. It reads the file
|
* The tool is synthetic: no such tool exists on the server. It reads the file
|
||||||
* through the built-in `read_file` tool with the `base64` response type, then
|
* through the server `read_file` tool with the `base64` response type, then
|
||||||
* turns the bytes into a data URI line. The agentic store lifts that line into
|
* turns the bytes into a data URI line. The agentic store lifts that line into
|
||||||
* an image or audio attachment on the tool result message, which is what makes
|
* an image or audio attachment on the tool result message, which is what makes
|
||||||
* the model perceive the file instead of reading a wall of base64.
|
* the model perceive the file instead of reading a wall of base64.
|
||||||
*
|
*
|
||||||
* Living in the frontend is what lets it exist only for models that can
|
* Living in the browser is what lets it exist only for models that can
|
||||||
* actually use the result - the server has no idea which model is selected.
|
* actually use the result - the server has no idea which model is selected.
|
||||||
*
|
*
|
||||||
* @see buildReadMediaToolDefinition in constants/read-media.ts - tool schema sent to the LLM
|
* @see buildReadMediaToolDefinition in constants/read-media.ts - tool schema sent to the LLM
|
||||||
@@ -82,7 +82,7 @@ export class ReadMediaService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const raw = await ToolsService.executeToolRaw(
|
const raw = await ToolsService.executeToolRaw(
|
||||||
BuiltInTool.READ_FILE,
|
BuiltInTool.SERVER_READ_FILE,
|
||||||
{ path },
|
{ path },
|
||||||
signal,
|
signal,
|
||||||
cwd,
|
cwd,
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ function formatReply(reply: SandboxReply): ToolExecutionResult {
|
|||||||
|
|
||||||
export class SandboxService {
|
export class SandboxService {
|
||||||
/**
|
/**
|
||||||
* Execute a frontend sandbox tool call and return its output.
|
* Execute a browser sandbox tool call and return its output.
|
||||||
* One disposable iframe per execution, removed on completion,
|
* One disposable iframe per execution, removed on completion,
|
||||||
* timeout or abort. Removing the iframe terminates the worker
|
* timeout or abort. Removing the iframe terminates the worker
|
||||||
* at the browser level, so runaway code cannot outlive it.
|
* at the browser level, so runaway code cannot outlive it.
|
||||||
@@ -79,7 +79,7 @@ export class SandboxService {
|
|||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
): Promise<ToolExecutionResult> {
|
): Promise<ToolExecutionResult> {
|
||||||
if (toolName !== SANDBOX_TOOL_NAME) {
|
if (toolName !== SANDBOX_TOOL_NAME) {
|
||||||
return { content: `Unknown frontend tool: ${toolName}`, isError: true };
|
return { content: `Unknown browser tool: ${toolName}`, isError: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
const code = typeof params.code === 'string' ? params.code : '';
|
const code = typeof params.code === 'string' ? params.code : '';
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import { base } from '$app/paths';
|
import { base } from '$app/paths';
|
||||||
import { API_TOOLS, HEADERS } from '$lib/constants';
|
import { API_TOOLS, HEADERS } from '$lib/constants';
|
||||||
import { ToolResponseField } from '$lib/enums';
|
import { ToolResponseField } from '$lib/enums';
|
||||||
import type { ServerBuiltinToolInfo, ToolExecutionResult } from '$lib/types';
|
import type { ServerToolInfo, ToolExecutionResult } from '$lib/types';
|
||||||
import { apiFetch } from '$lib/utils';
|
import { apiFetch } from '$lib/utils';
|
||||||
import { getJsonHeaders } from '$lib/utils/api-headers';
|
import { getJsonHeaders } from '$lib/utils/api-headers';
|
||||||
import { parseSseJsonStream, type SseJsonEvent } from '$lib/utils/sse';
|
import { parseSseJsonStream, type SseJsonEvent } from '$lib/utils/sse';
|
||||||
|
|
||||||
export class ToolsService {
|
export class ToolsService {
|
||||||
/**
|
/**
|
||||||
* Fetch the list of built-in tools from the server.
|
* Fetch the list of server tools from the server.
|
||||||
*
|
*
|
||||||
* @returns Array of tool definitions in OpenAI-compatible format
|
* @returns Array of tool definitions in OpenAI-compatible format
|
||||||
*/
|
*/
|
||||||
static async list(): Promise<ServerBuiltinToolInfo[]> {
|
static async list(): Promise<ServerToolInfo[]> {
|
||||||
return apiFetch<ServerBuiltinToolInfo[]>(API_TOOLS.LIST);
|
return apiFetch<ServerToolInfo[]>(API_TOOLS.LIST);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a built-in tool on the server.
|
* Execute a server tool on the server.
|
||||||
*
|
*
|
||||||
* @param cwd - Working directory for the tool call, sent as the
|
* @param cwd - Working directory for the tool call, sent as the
|
||||||
* x-tool-cwd request header. The server resolves relative paths
|
* x-tool-cwd request header. The server resolves relative paths
|
||||||
@@ -48,7 +48,7 @@ export class ToolsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a built-in tool and return the raw JSON response. Unlike
|
* Execute a server tool and return the raw JSON response. Unlike
|
||||||
* executeTool, this preserves structured fields (e.g. file_glob_search's
|
* executeTool, this preserves structured fields (e.g. file_glob_search's
|
||||||
* `entries` and `base`) that the flattened ToolExecutionResult drops.
|
* `entries` and `base`) that the flattened ToolExecutionResult drops.
|
||||||
*
|
*
|
||||||
@@ -77,7 +77,7 @@ export class ToolsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stream a built-in tool's output chunks from the server. The server
|
* Stream a server tool's output chunks from the server. The server
|
||||||
* `POST /tools` endpoint with `{stream: true}` emits `data: {"chunk": "..."}`
|
* `POST /tools` endpoint with `{stream: true}` emits `data: {"chunk": "..."}`
|
||||||
* events followed by a terminal `data: {"done": true}` (optionally with
|
* events followed by a terminal `data: {"done": true}` (optionally with
|
||||||
* `error`). Yields the chunk string for each partial event.
|
* `error`). Yields the chunk string for each partial event.
|
||||||
|
|||||||
@@ -326,8 +326,8 @@ class AgenticStore {
|
|||||||
const maxTurns = Number(settings.agenticMaxTurns) || DEFAULT_AGENTIC_CONFIG.maxTurns;
|
const maxTurns = Number(settings.agenticMaxTurns) || DEFAULT_AGENTIC_CONFIG.maxTurns;
|
||||||
const hasTools =
|
const hasTools =
|
||||||
mcpStore.hasEnabledServers(perChatOverrides) ||
|
mcpStore.hasEnabledServers(perChatOverrides) ||
|
||||||
toolsStore.builtinTools.length > 0 ||
|
toolsStore.serverTools.length > 0 ||
|
||||||
toolsStore.frontendTools.length > 0 ||
|
toolsStore.browserTools.length > 0 ||
|
||||||
toolsStore.customTools.length > 0;
|
toolsStore.customTools.length > 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -455,9 +455,9 @@ class AgenticStore {
|
|||||||
this._continueResolvers.delete(conversationId);
|
this._continueResolvers.delete(conversationId);
|
||||||
this._steeringMessages.delete(conversationId);
|
this._steeringMessages.delete(conversationId);
|
||||||
|
|
||||||
// Ensure built-in tools are fetched before checking if agentic is enabled
|
// Ensure server tools are fetched before checking if agentic is enabled
|
||||||
if (toolsStore.builtinTools.length === 0 && !toolsStore.loading) {
|
if (toolsStore.serverTools.length === 0 && !toolsStore.loading) {
|
||||||
await toolsStore.fetchBuiltinTools();
|
await toolsStore.fetchServerTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
const agenticConfig = this.getConfig(settingsStore.config, perChatOverrides);
|
const agenticConfig = this.getConfig(settingsStore.config, perChatOverrides);
|
||||||
@@ -906,8 +906,8 @@ class AgenticStore {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
toolSource === ToolSource.BUILTIN &&
|
toolSource === ToolSource.SERVER &&
|
||||||
toolName === BuiltInTool.EXEC_SHELL_COMMAND &&
|
toolName === BuiltInTool.SERVER_EXEC_SHELL_COMMAND &&
|
||||||
createToolResultMessage &&
|
createToolResultMessage &&
|
||||||
updateToolResultMessage
|
updateToolResultMessage
|
||||||
) {
|
) {
|
||||||
@@ -938,7 +938,7 @@ class AgenticStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = accumulated;
|
result = accumulated;
|
||||||
} else if (toolSource === ToolSource.BUILTIN) {
|
} else if (toolSource === ToolSource.SERVER) {
|
||||||
const args = this.parseToolArguments(toolCall.function.arguments);
|
const args = this.parseToolArguments(toolCall.function.arguments);
|
||||||
const cwd = conversationsStore.activeConversation?.cwd;
|
const cwd = conversationsStore.activeConversation?.cwd;
|
||||||
const executionResult = await ToolsService.executeTool(toolName, args, signal, cwd);
|
const executionResult = await ToolsService.executeTool(toolName, args, signal, cwd);
|
||||||
@@ -946,16 +946,16 @@ class AgenticStore {
|
|||||||
result = executionResult.content;
|
result = executionResult.content;
|
||||||
|
|
||||||
if (executionResult.isError) toolSuccess = false;
|
if (executionResult.isError) toolSuccess = false;
|
||||||
} else if (toolSource === ToolSource.FRONTEND) {
|
} else if (toolSource === ToolSource.BROWSER) {
|
||||||
const args = this.parseToolArguments(toolCall.function.arguments);
|
const args = this.parseToolArguments(toolCall.function.arguments);
|
||||||
|
|
||||||
let executionResult: ToolExecutionResult;
|
let executionResult: ToolExecutionResult;
|
||||||
|
|
||||||
if (toolName === BuiltInTool.GET_DATETIME) {
|
if (toolName === BuiltInTool.BROWSER_GET_DATETIME) {
|
||||||
executionResult = executeGetDatetimeTool();
|
executionResult = executeGetDatetimeTool();
|
||||||
} else if (toolName === BuiltInTool.GET_INFO) {
|
} else if (toolName === BuiltInTool.SERVER_GET_INFO) {
|
||||||
executionResult = executeBrowserInfoTool();
|
executionResult = executeBrowserInfoTool();
|
||||||
} else if (toolName === BuiltInTool.READ_MEDIA) {
|
} else if (toolName === BuiltInTool.BROWSER_READ_MEDIA) {
|
||||||
executionResult = await ReadMediaService.executeTool(
|
executionResult = await ReadMediaService.executeTool(
|
||||||
args,
|
args,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,11 +28,11 @@ import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
|||||||
/** Stable selection identity for a tool, shared by the disabled set and the permission store */
|
/** Stable selection identity for a tool, shared by the disabled set and the permission store */
|
||||||
|
|
||||||
class ToolsStore {
|
class ToolsStore {
|
||||||
private _builtinTools = $state<OpenAIToolDefinition[]>([]);
|
private _serverTools = $state<OpenAIToolDefinition[]>([]);
|
||||||
private _loading = $state(false);
|
private _loading = $state(false);
|
||||||
private _error = $state<string | null>(null);
|
private _error = $state<string | null>(null);
|
||||||
private _disabledTools = $state(new SvelteSet<string>());
|
private _disabledTools = $state(new SvelteSet<string>());
|
||||||
// builtin tools that resolve their paths against the working directory,
|
// server tools that resolve their paths against the working directory,
|
||||||
// as declared by the server in its `/tools` listing
|
// as declared by the server in its `/tools` listing
|
||||||
private _cwdAwareTools = $state(new SvelteSet<string>());
|
private _cwdAwareTools = $state(new SvelteSet<string>());
|
||||||
private _toolsEndpointUnreachable = $state(false);
|
private _toolsEndpointUnreachable = $state(false);
|
||||||
@@ -58,7 +58,7 @@ class ToolsStore {
|
|||||||
console.error('[ToolsStore] Failed to load disabled tools from localStorage:', err);
|
console.error('[ToolsStore] Failed to load disabled tools from localStorage:', err);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchBuiltinTools();
|
this.fetchServerTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
private persistDisabledTools(): void {
|
private persistDisabledTools(): void {
|
||||||
@@ -78,10 +78,10 @@ class ToolsStore {
|
|||||||
return serverId ? `mcp-${serverId}:${name}` : `mcp:${name}`;
|
return serverId ? `mcp-${serverId}:${name}` : `mcp:${name}`;
|
||||||
case ToolSource.CUSTOM:
|
case ToolSource.CUSTOM:
|
||||||
return `custom:${name}`;
|
return `custom:${name}`;
|
||||||
case ToolSource.FRONTEND:
|
case ToolSource.BROWSER:
|
||||||
return `frontend:${name}`;
|
return `browser:${name}`;
|
||||||
default:
|
default:
|
||||||
return `builtin:${name}`;
|
return `server:${name}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,8 +164,8 @@ class ToolsStore {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get builtinTools(): OpenAIToolDefinition[] {
|
get serverTools(): OpenAIToolDefinition[] {
|
||||||
return this._builtinTools;
|
return this._serverTools;
|
||||||
}
|
}
|
||||||
|
|
||||||
get serverHome(): string | null {
|
get serverHome(): string | null {
|
||||||
@@ -176,7 +176,7 @@ class ToolsStore {
|
|||||||
return this.mcpEntries().map((e) => e.definition);
|
return this.mcpEntries().map((e) => e.definition);
|
||||||
}
|
}
|
||||||
|
|
||||||
get frontendTools(): OpenAIToolDefinition[] {
|
get browserTools(): OpenAIToolDefinition[] {
|
||||||
const tools: OpenAIToolDefinition[] = [buildGetDatetimeToolDefinition()];
|
const tools: OpenAIToolDefinition[] = [buildGetDatetimeToolDefinition()];
|
||||||
|
|
||||||
if (settingsStore.config.jsSandboxEnabled) {
|
if (settingsStore.config.jsSandboxEnabled) {
|
||||||
@@ -188,25 +188,25 @@ class ToolsStore {
|
|||||||
if (readMedia) tools.push(readMedia);
|
if (readMedia) tools.push(readMedia);
|
||||||
|
|
||||||
// provide browser's get_info tool if server doesn't provide one
|
// provide browser's get_info tool if server doesn't provide one
|
||||||
if (!this.hasBuiltinTool(BuiltInTool.GET_INFO)) {
|
if (!this.hasServerTool(BuiltInTool.SERVER_GET_INFO)) {
|
||||||
tools.push(buildBrowserInfoToolDefinition());
|
tools.push(buildBrowserInfoToolDefinition());
|
||||||
}
|
}
|
||||||
|
|
||||||
return tools;
|
return tools;
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasBuiltinTool(name: BuiltInTool): boolean {
|
private hasServerTool(name: BuiltInTool): boolean {
|
||||||
return this._builtinTools.some((def) => def.function.name === name);
|
return this._serverTools.some((def) => def.function.name === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `read_media` runs in the frontend on top of the server's `read_file`, so it
|
* `read_media` runs in the browser on top of the server's `read_file`, so it
|
||||||
* exists only when that tool is served and the active model can perceive the
|
* exists only when that tool is served and the active model can perceive the
|
||||||
* bytes. The server cannot make this call - it does not know which model the
|
* bytes. The server cannot make this call - it does not know which model the
|
||||||
* conversation uses.
|
* conversation uses.
|
||||||
*/
|
*/
|
||||||
private readMediaTool(): OpenAIToolDefinition | null {
|
private readMediaTool(): OpenAIToolDefinition | null {
|
||||||
if (!this.hasBuiltinTool(BuiltInTool.READ_FILE)) return null;
|
if (!this.hasServerTool(BuiltInTool.SERVER_READ_FILE)) return null;
|
||||||
|
|
||||||
const model = modelsStore.selectedModelName ?? modelsStore.models[0]?.model ?? '';
|
const model = modelsStore.selectedModelName ?? modelsStore.models[0]?.model ?? '';
|
||||||
|
|
||||||
@@ -304,23 +304,23 @@ class ToolsStore {
|
|||||||
entries.push(entry);
|
entries.push(entry);
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const def of this._builtinTools) {
|
for (const def of this._serverTools) {
|
||||||
const name = def.function.name;
|
const name = def.function.name;
|
||||||
|
|
||||||
push({
|
push({
|
||||||
definition: def,
|
definition: def,
|
||||||
key: this.toolKey(ToolSource.BUILTIN, name),
|
key: this.toolKey(ToolSource.SERVER, name),
|
||||||
source: ToolSource.BUILTIN
|
source: ToolSource.SERVER
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const def of this.frontendTools) {
|
for (const def of this.browserTools) {
|
||||||
const name = def.function.name;
|
const name = def.function.name;
|
||||||
|
|
||||||
push({
|
push({
|
||||||
definition: def,
|
definition: def,
|
||||||
key: this.toolKey(ToolSource.FRONTEND, name),
|
key: this.toolKey(ToolSource.BROWSER, name),
|
||||||
source: ToolSource.FRONTEND
|
source: ToolSource.BROWSER
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,17 +384,17 @@ class ToolsStore {
|
|||||||
return entry.serverName ?? '';
|
return entry.serverName ?? '';
|
||||||
case ToolSource.CUSTOM:
|
case ToolSource.CUSTOM:
|
||||||
return TOOL_GROUP_LABELS[ToolSource.CUSTOM];
|
return TOOL_GROUP_LABELS[ToolSource.CUSTOM];
|
||||||
case ToolSource.FRONTEND:
|
case ToolSource.BROWSER:
|
||||||
return TOOL_GROUP_LABELS[ToolSource.FRONTEND];
|
return TOOL_GROUP_LABELS[ToolSource.BROWSER];
|
||||||
default:
|
default:
|
||||||
return TOOL_GROUP_LABELS[ToolSource.BUILTIN];
|
return TOOL_GROUP_LABELS[ToolSource.SERVER];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enabled tool definitions for sending to the LLM.
|
* Enabled tool definitions for sending to the LLM.
|
||||||
* MCP tool schemas are normalized here so the wire payload is consistent
|
* MCP tool schemas are normalized here so the wire payload is consistent
|
||||||
* across all four sources (built-in, frontend/sandbox, MCP, custom JSON).
|
* across all four sources (server, browser/sandbox, MCP, custom JSON).
|
||||||
* The API identifies tools by name, so a name is sent at most once.
|
* The API identifies tools by name, so a name is sent at most once.
|
||||||
*/
|
*/
|
||||||
getEnabledToolsForLLM(): OpenAIToolDefinition[] {
|
getEnabledToolsForLLM(): OpenAIToolDefinition[] {
|
||||||
@@ -417,8 +417,8 @@ class ToolsStore {
|
|||||||
result.push(def);
|
result.push(def);
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const def of this._builtinTools) take(def);
|
for (const def of this._serverTools) take(def);
|
||||||
for (const def of this.frontendTools) take(def);
|
for (const def of this.browserTools) take(def);
|
||||||
// mcpEntries() over mcpStore directly so wire shape stays normalized and aligned with the tools UI.
|
// mcpEntries() over mcpStore directly so wire shape stays normalized and aligned with the tools UI.
|
||||||
for (const entry of this.mcpEntries()) take(entry.definition);
|
for (const entry of this.mcpEntries()) take(entry.definition);
|
||||||
for (const def of this.customTools) take(def);
|
for (const def of this.customTools) take(def);
|
||||||
@@ -542,11 +542,11 @@ class ToolsStore {
|
|||||||
|
|
||||||
if (entry.serverName) return mcpStore.getServerDisplayName(entry.serverName);
|
if (entry.serverName) return mcpStore.getServerDisplayName(entry.serverName);
|
||||||
|
|
||||||
if (entry.source === ToolSource.BUILTIN) return TOOL_SERVER_LABELS[ToolSource.BUILTIN];
|
if (entry.source === ToolSource.SERVER) return TOOL_SERVER_LABELS[ToolSource.SERVER];
|
||||||
|
|
||||||
if (entry.source === ToolSource.CUSTOM) return TOOL_SERVER_LABELS[ToolSource.CUSTOM];
|
if (entry.source === ToolSource.CUSTOM) return TOOL_SERVER_LABELS[ToolSource.CUSTOM];
|
||||||
|
|
||||||
if (entry.source === ToolSource.FRONTEND) return TOOL_SERVER_LABELS[ToolSource.FRONTEND];
|
if (entry.source === ToolSource.BROWSER) return TOOL_SERVER_LABELS[ToolSource.BROWSER];
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -556,27 +556,27 @@ class ToolsStore {
|
|||||||
return this.findEntryByName(toolName)?.key ?? null;
|
return this.findEntryByName(toolName)?.key ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Check if there are any enabled tools available (builtin, MCP, or custom) */
|
/** Check if there are any enabled tools available (server, MCP, or custom) */
|
||||||
get hasEnabledTools(): boolean {
|
get hasEnabledTools(): boolean {
|
||||||
return this.getEnabledToolsForLLM().length > 0;
|
return this.getEnabledToolsForLLM().length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a working directory is worth setting: at least one builtin tool
|
* Check if a working directory is worth setting: at least one server tool
|
||||||
* that reads it is both served and left enabled by the user.
|
* that reads it is both served and left enabled by the user.
|
||||||
*/
|
*/
|
||||||
get hasEnabledCwdTools(): boolean {
|
get hasEnabledCwdTools(): boolean {
|
||||||
return this._builtinTools.some((def) => {
|
return this._serverTools.some((def) => {
|
||||||
const name = def.function.name;
|
const name = def.function.name;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this._cwdAwareTools.has(name) &&
|
this._cwdAwareTools.has(name) &&
|
||||||
!this._disabledTools.has(this.toolKey(ToolSource.BUILTIN, name))
|
!this._disabledTools.has(this.toolKey(ToolSource.SERVER, name))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchBuiltinTools(): Promise<void> {
|
async fetchServerTools(): Promise<void> {
|
||||||
if (this._loading) return;
|
if (this._loading) return;
|
||||||
|
|
||||||
this._loading = true;
|
this._loading = true;
|
||||||
@@ -586,7 +586,7 @@ class ToolsStore {
|
|||||||
try {
|
try {
|
||||||
const toolInfos = await ToolsService.list();
|
const toolInfos = await ToolsService.list();
|
||||||
|
|
||||||
this._builtinTools = toolInfos.map((info) => info.definition);
|
this._serverTools = toolInfos.map((info) => info.definition);
|
||||||
this._cwdAwareTools = new SvelteSet(
|
this._cwdAwareTools = new SvelteSet(
|
||||||
toolInfos.filter((info) => info.uses_cwd).map((info) => info.tool)
|
toolInfos.filter((info) => info.uses_cwd).map((info) => info.tool)
|
||||||
);
|
);
|
||||||
@@ -599,9 +599,9 @@ class ToolsStore {
|
|||||||
// TODO: check status code instead of relying on message
|
// TODO: check status code instead of relying on message
|
||||||
if (errorMessage.includes('this feature is disabled')) {
|
if (errorMessage.includes('this feature is disabled')) {
|
||||||
this._toolsEndpointUnreachable = true;
|
this._toolsEndpointUnreachable = true;
|
||||||
console.info('[ToolsStore] Built-in tools are disabled on the server');
|
console.info('[ToolsStore] Server tools are disabled on the server');
|
||||||
} else {
|
} else {
|
||||||
console.error('[ToolsStore] Failed to fetch built-in tools:', err);
|
console.error('[ToolsStore] Failed to fetch server tools:', err);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this._loading = false;
|
this._loading = false;
|
||||||
@@ -618,7 +618,7 @@ class ToolsStore {
|
|||||||
if (this._serverHome !== undefined) return this._serverHome;
|
if (this._serverHome !== undefined) return this._serverHome;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await ToolsService.executeToolRaw(BuiltInTool.FILE_GLOB_SEARCH, {
|
const res = await ToolsService.executeToolRaw(BuiltInTool.SERVER_FILE_GLOB_SEARCH, {
|
||||||
limit: 1,
|
limit: 1,
|
||||||
max_depth: 1,
|
max_depth: 1,
|
||||||
path: HOME_TILDE,
|
path: HOME_TILDE,
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export type {
|
|||||||
ServerStatus,
|
ServerStatus,
|
||||||
ToolCallParams,
|
ToolCallParams,
|
||||||
ToolExecutionResult,
|
ToolExecutionResult,
|
||||||
ServerBuiltinToolInfo,
|
ServerToolInfo,
|
||||||
Tool,
|
Tool,
|
||||||
Prompt,
|
Prompt,
|
||||||
GetPromptResult,
|
GetPromptResult,
|
||||||
@@ -208,7 +208,7 @@ export type {
|
|||||||
export type { DesktopIconStripItem } from './navigation';
|
export type { DesktopIconStripItem } from './navigation';
|
||||||
|
|
||||||
// Tools types
|
// Tools types
|
||||||
export type { ToolEntry, ToolGroup, BuiltinToolUiEntry } from './tools';
|
export type { ToolEntry, ToolGroup, ToolUiEntry } from './tools';
|
||||||
|
|
||||||
// Reasoning
|
// Reasoning
|
||||||
export type { ReasoningEffortLevel } from './reasoning';
|
export type { ReasoningEffortLevel } from './reasoning';
|
||||||
|
|||||||
Vendored
+2
-2
@@ -285,10 +285,10 @@ export interface ToolExecutionResult {
|
|||||||
isError: boolean;
|
isError: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerBuiltinToolInfo {
|
export interface ServerToolInfo {
|
||||||
display_name: string;
|
display_name: string;
|
||||||
tool: string;
|
tool: string;
|
||||||
type: ToolSource.BUILTIN;
|
type: ToolSource.SERVER;
|
||||||
permissions: {
|
permissions: {
|
||||||
write: boolean;
|
write: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+4
-4
@@ -3,12 +3,12 @@ import type { ToolSource } from '$lib/enums';
|
|||||||
import type { Component } from 'svelte';
|
import type { Component } from 'svelte';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UI metadata for a built-in or frontend tool, keyed by its `BuiltInTool` id.
|
* UI metadata for a server or browser tool, keyed by its `BuiltInTool` id.
|
||||||
*/
|
*/
|
||||||
export interface BuiltinToolUiEntry {
|
export interface ToolUiEntry {
|
||||||
icon: Component;
|
icon: Component;
|
||||||
label: string;
|
label: string;
|
||||||
source: ToolSource.BUILTIN | ToolSource.FRONTEND;
|
source: ToolSource.SERVER | ToolSource.BROWSER;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToolEntry {
|
export interface ToolEntry {
|
||||||
@@ -17,7 +17,7 @@ export interface ToolEntry {
|
|||||||
serverName?: string;
|
serverName?: string;
|
||||||
/** For MCP tools, the server ID (used for permission keys) */
|
/** For MCP tools, the server ID (used for permission keys) */
|
||||||
serverId?: string;
|
serverId?: string;
|
||||||
/** Stable selection identity: builtin:name, mcp-<serverId>:name, mcp:name, custom:name */
|
/** Stable selection identity: server:name, mcp-<serverId>:name, mcp:name, custom:name */
|
||||||
key: string;
|
key: string;
|
||||||
definition: OpenAIToolDefinition;
|
definition: OpenAIToolDefinition;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
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<string, BuiltinToolUiEntry>)[toolName] ?? null;
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Frontend executor for the `get_datetime` tool. It runs in the browser, so it
|
* Browser executor for the `get_datetime` tool. It runs in the browser, so it
|
||||||
* reports the user's own clock and time zone instead of the server's UTC time -
|
* reports the user's own clock and time zone instead of the server's UTC time -
|
||||||
* a chat about "tomorrow" means the user's tomorrow, not the host's.
|
* a chat about "tomorrow" means the user's tomorrow, not the host's.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export async function runGlobSearch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const res = await ToolsService.executeToolRaw(
|
const res = await ToolsService.executeToolRaw(
|
||||||
BuiltInTool.FILE_GLOB_SEARCH,
|
BuiltInTool.SERVER_FILE_GLOB_SEARCH,
|
||||||
{ include: args.include, limit, max_depth: args.maxDepth, path: args.path, type },
|
{ include: args.include, limit, max_depth: args.maxDepth, path: args.path, type },
|
||||||
signal
|
signal
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ export { sanitizeKeyValuePairKey, sanitizeKeyValuePairValue } from './sanitize';
|
|||||||
// Image error fallback utilities
|
// Image error fallback utilities
|
||||||
export { getImageErrorFallbackHtml } from './image-error-fallback';
|
export { getImageErrorFallbackHtml } from './image-error-fallback';
|
||||||
|
|
||||||
// SSE-with-JSON stream iterator (used by built-in tool streaming, decoupled
|
// SSE-with-JSON stream iterator (used by server tool streaming, decoupled
|
||||||
// from chat.service.ts which embeds its own SSE parser for resume support)
|
// from chat.service.ts which embeds its own SSE parser for resume support)
|
||||||
export { parseSseJsonStream } from './sse';
|
export { parseSseJsonStream } from './sse';
|
||||||
|
|
||||||
@@ -310,7 +310,7 @@ export {
|
|||||||
withAbortSignal
|
withAbortSignal
|
||||||
} from './abort';
|
} from './abort';
|
||||||
|
|
||||||
// Tool-call meta utilities. Parsers for each built-in tool live next to
|
// Tool-call meta utilities. Parsers for each server tool live next to
|
||||||
// their renderer family under
|
// their renderer family under
|
||||||
// `src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/`.
|
// `src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/parsers/`.
|
||||||
// This module only carries the helpers that genuinely cross tool
|
// This module only carries the helpers that genuinely cross tool
|
||||||
@@ -321,7 +321,7 @@ export { tryParseToolResultObject } from './tool-call-meta';
|
|||||||
// Per-tool UI metadata (label + icon) used by the tool-call chrome.
|
// Per-tool UI metadata (label + icon) used by the tool-call chrome.
|
||||||
// Re-exported through $lib/utils so renderer components can read the
|
// Re-exported through $lib/utils so renderer components can read the
|
||||||
// label without depending on $lib/constants directly.
|
// label without depending on $lib/constants directly.
|
||||||
export { getBuiltinToolUi } from './built-in-tools';
|
export { getToolUi } from './tool-ui';
|
||||||
|
|
||||||
// Chat command picker
|
// Chat command picker
|
||||||
|
|
||||||
@@ -331,7 +331,7 @@ export { getChatCommands } from './chat-commands';
|
|||||||
// SANDBOX_TOOL_DEFINITION is deprecated; kept for backward compatibility.
|
// SANDBOX_TOOL_DEFINITION is deprecated; kept for backward compatibility.
|
||||||
export { buildSandboxToolDefinition, SANDBOX_TOOL_DEFINITION } from './sandbox-tool';
|
export { buildSandboxToolDefinition, SANDBOX_TOOL_DEFINITION } from './sandbox-tool';
|
||||||
|
|
||||||
// Frontend `get_datetime` executor (the browser clock, not the server's)
|
// Browser `get_datetime` executor (the browser clock, not the server's)
|
||||||
export { executeGetDatetimeTool } from './get-datetime';
|
export { executeGetDatetimeTool } from './get-datetime';
|
||||||
|
|
||||||
// Browser fallback for the server's get_info tool
|
// Browser fallback for the server's get_info tool
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { TOOL_UI } from '$lib/constants';
|
||||||
|
import type { ToolUiEntry } from '$lib/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the UI metadata (label + icon) for a server or browser tool by its
|
||||||
|
* name. Falls back to null for unknown tools so callers can render a generic
|
||||||
|
* chrome instead.
|
||||||
|
*/
|
||||||
|
export function getToolUi(toolName: string | undefined): ToolUiEntry | null {
|
||||||
|
if (!toolName) return null;
|
||||||
|
|
||||||
|
return (TOOL_UI as Record<string, ToolUiEntry>)[toolName] ?? null;
|
||||||
|
}
|
||||||
@@ -13,15 +13,15 @@ import { afterEach, describe, expect, it } from 'vitest';
|
|||||||
import { render } from 'vitest-browser-svelte';
|
import { render } from 'vitest-browser-svelte';
|
||||||
|
|
||||||
const FILE_SEARCH_DEF: OpenAIToolDefinition = {
|
const FILE_SEARCH_DEF: OpenAIToolDefinition = {
|
||||||
function: { description: '', name: BuiltInTool.FILE_GLOB_SEARCH, parameters: {} },
|
function: { description: '', name: BuiltInTool.SERVER_FILE_GLOB_SEARCH, parameters: {} },
|
||||||
type: 'function'
|
type: 'function'
|
||||||
};
|
};
|
||||||
const FILE_SEARCH_KEY = `builtin:${BuiltInTool.FILE_GLOB_SEARCH}`;
|
const FILE_SEARCH_KEY = `server:${BuiltInTool.SERVER_FILE_GLOB_SEARCH}`;
|
||||||
|
|
||||||
// The store keeps its builtin tool list private; tests inject it through
|
// The store keeps its server tool list private; tests inject it through
|
||||||
// the reactive field so the derived gates recompute.
|
// the reactive field so the derived gates recompute.
|
||||||
function setBuiltinTools(defs: OpenAIToolDefinition[]) {
|
function setServerTools(defs: OpenAIToolDefinition[]) {
|
||||||
(toolsStore as unknown as { _builtinTools: OpenAIToolDefinition[] })._builtinTools = defs;
|
(toolsStore as unknown as { _serverTools: OpenAIToolDefinition[] })._serverTools = defs;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderPicker() {
|
function renderPicker() {
|
||||||
@@ -34,14 +34,14 @@ function renderPicker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
setBuiltinTools([]);
|
setServerTools([]);
|
||||||
toolsStore.setToolEnabled(FILE_SEARCH_KEY, true);
|
toolsStore.setToolEnabled(FILE_SEARCH_KEY, true);
|
||||||
localStorage.removeItem(DISABLED_TOOL_KEYS_LOCALSTORAGE_KEY);
|
localStorage.removeItem(DISABLED_TOOL_KEYS_LOCALSTORAGE_KEY);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ChatFormPickerMention file_glob_search gate', () => {
|
describe('ChatFormPickerMention file_glob_search gate', () => {
|
||||||
it('explains that file search is unavailable when the server has no tools', async () => {
|
it('explains that file search is unavailable when the server has no tools', async () => {
|
||||||
setBuiltinTools([]);
|
setServerTools([]);
|
||||||
renderPicker();
|
renderPicker();
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ describe('ChatFormPickerMention file_glob_search gate', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('explains that file search must be enabled when the user disabled it', async () => {
|
it('explains that file search must be enabled when the user disabled it', async () => {
|
||||||
setBuiltinTools([FILE_SEARCH_DEF]);
|
setServerTools([FILE_SEARCH_DEF]);
|
||||||
toolsStore.setToolEnabled(FILE_SEARCH_KEY, false);
|
toolsStore.setToolEnabled(FILE_SEARCH_KEY, false);
|
||||||
renderPicker();
|
renderPicker();
|
||||||
await tick();
|
await tick();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { describe, expect, it } from 'vitest';
|
|||||||
|
|
||||||
function makeSection(
|
function makeSection(
|
||||||
overrides: Partial<AgenticSection> = {},
|
overrides: Partial<AgenticSection> = {},
|
||||||
toolName = BuiltInTool.READ_FILE
|
toolName = BuiltInTool.SERVER_READ_FILE
|
||||||
): AgenticSection {
|
): AgenticSection {
|
||||||
return {
|
return {
|
||||||
content: '',
|
content: '',
|
||||||
@@ -115,15 +115,18 @@ describe('formatCwdMessage / parseCwdMessage', () => {
|
|||||||
|
|
||||||
describe('parseToolArgs (shared)', () => {
|
describe('parseToolArgs (shared)', () => {
|
||||||
it('returns null when the section has no toolArgs', () => {
|
it('returns null when the section has no toolArgs', () => {
|
||||||
const result = parseToolArgs(BuiltInTool.READ_FILE, makeSection({ toolArgs: undefined }));
|
const result = parseToolArgs(
|
||||||
|
BuiltInTool.SERVER_READ_FILE,
|
||||||
|
makeSection({ toolArgs: undefined })
|
||||||
|
);
|
||||||
|
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns null when the tool name does not match', () => {
|
it('returns null when the tool name does not match', () => {
|
||||||
const result = parseToolArgs(
|
const result = parseToolArgs(
|
||||||
BuiltInTool.READ_FILE,
|
BuiltInTool.SERVER_READ_FILE,
|
||||||
makeSection({ toolArgs: '{"path":"/x"}' }, BuiltInTool.WRITE_FILE)
|
makeSection({ toolArgs: '{"path":"/x"}' }, BuiltInTool.SERVER_WRITE_FILE)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
@@ -131,7 +134,7 @@ describe('parseToolArgs (shared)', () => {
|
|||||||
|
|
||||||
it('returns null when args are not valid final JSON (partial: false)', () => {
|
it('returns null when args are not valid final JSON (partial: false)', () => {
|
||||||
const result = parseToolArgs(
|
const result = parseToolArgs(
|
||||||
BuiltInTool.READ_FILE,
|
BuiltInTool.SERVER_READ_FILE,
|
||||||
makeSection({ toolArgs: '{"path": "/foo.tx' })
|
makeSection({ toolArgs: '{"path": "/foo.tx' })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -140,7 +143,7 @@ describe('parseToolArgs (shared)', () => {
|
|||||||
|
|
||||||
it('returns parsed args when valid final JSON', () => {
|
it('returns parsed args when valid final JSON', () => {
|
||||||
const result = parseToolArgs(
|
const result = parseToolArgs(
|
||||||
BuiltInTool.READ_FILE,
|
BuiltInTool.SERVER_READ_FILE,
|
||||||
makeSection({ toolArgs: '{"path":"/foo.txt"}' })
|
makeSection({ toolArgs: '{"path":"/foo.txt"}' })
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -149,7 +152,7 @@ describe('parseToolArgs (shared)', () => {
|
|||||||
|
|
||||||
it('accepts partial JSON when partial: true', () => {
|
it('accepts partial JSON when partial: true', () => {
|
||||||
const result = parseToolArgs(
|
const result = parseToolArgs(
|
||||||
BuiltInTool.READ_FILE,
|
BuiltInTool.SERVER_READ_FILE,
|
||||||
makeSection({ toolArgs: '{"path": "/foo.tx' }),
|
makeSection({ toolArgs: '{"path": "/foo.tx' }),
|
||||||
{ partial: true }
|
{ partial: true }
|
||||||
);
|
);
|
||||||
@@ -162,7 +165,10 @@ describe('parseWriteFileMeta', () => {
|
|||||||
it('returns null for sections with a different tool name', () => {
|
it('returns null for sections with a different tool name', () => {
|
||||||
expect(
|
expect(
|
||||||
parseWriteFileMeta(
|
parseWriteFileMeta(
|
||||||
makeSection({ toolArgs: '{"path":"/x","content":"y"}', toolName: BuiltInTool.READ_FILE })
|
makeSection({
|
||||||
|
toolArgs: '{"path":"/x","content":"y"}',
|
||||||
|
toolName: BuiltInTool.SERVER_READ_FILE
|
||||||
|
})
|
||||||
)
|
)
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
});
|
});
|
||||||
@@ -170,14 +176,14 @@ describe('parseWriteFileMeta', () => {
|
|||||||
it('returns null when args have no path-like field', () => {
|
it('returns null when args have no path-like field', () => {
|
||||||
expect(
|
expect(
|
||||||
parseWriteFileMeta(
|
parseWriteFileMeta(
|
||||||
makeSection({ toolArgs: '{"content":"x"}', toolName: BuiltInTool.WRITE_FILE })
|
makeSection({ toolArgs: '{"content":"x"}', toolName: BuiltInTool.SERVER_WRITE_FILE })
|
||||||
)
|
)
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accepts partial args (renders incrementally as content streams in)', () => {
|
it('accepts partial args (renders incrementally as content streams in)', () => {
|
||||||
const meta = parseWriteFileMeta(
|
const meta = parseWriteFileMeta(
|
||||||
makeSection({ toolArgs: '{"path":"/foo.t', toolName: BuiltInTool.WRITE_FILE })
|
makeSection({ toolArgs: '{"path":"/foo.t', toolName: BuiltInTool.SERVER_WRITE_FILE })
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(meta?.filePath).toBe('/foo.t');
|
expect(meta?.filePath).toBe('/foo.t');
|
||||||
@@ -188,10 +194,10 @@ describe('parseWriteFileMeta', () => {
|
|||||||
makeSection(
|
makeSection(
|
||||||
{
|
{
|
||||||
toolArgs: '{"path":"/foo.ts","content":"x"}',
|
toolArgs: '{"path":"/foo.ts","content":"x"}',
|
||||||
toolName: BuiltInTool.WRITE_FILE,
|
toolName: BuiltInTool.SERVER_WRITE_FILE,
|
||||||
toolResult: '{"result":"wrote","bytes":42}'
|
toolResult: '{"result":"wrote","bytes":42}'
|
||||||
},
|
},
|
||||||
BuiltInTool.WRITE_FILE
|
BuiltInTool.SERVER_WRITE_FILE
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -208,7 +214,7 @@ describe('parseWriteFileMeta', () => {
|
|||||||
const meta = parseWriteFileMeta(
|
const meta = parseWriteFileMeta(
|
||||||
makeSection({
|
makeSection({
|
||||||
toolArgs: '{"path":"/foo","content":"x"}',
|
toolArgs: '{"path":"/foo","content":"x"}',
|
||||||
toolName: BuiltInTool.WRITE_FILE,
|
toolName: BuiltInTool.SERVER_WRITE_FILE,
|
||||||
toolResult: '{"error":"permission denied"}'
|
toolResult: '{"error":"permission denied"}'
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -223,10 +229,10 @@ describe('parseEditFileMeta', () => {
|
|||||||
{
|
{
|
||||||
toolArgs:
|
toolArgs:
|
||||||
'{"path":"/foo.ts","edits":[{"old_text":"a","new_text":"b"},{"old_text":"c","new_text":"d"}]}',
|
'{"path":"/foo.ts","edits":[{"old_text":"a","new_text":"b"},{"old_text":"c","new_text":"d"}]}',
|
||||||
toolName: BuiltInTool.EDIT_FILE,
|
toolName: BuiltInTool.SERVER_EDIT_FILE,
|
||||||
toolResult: '{"result":"ok","edits_applied":2}'
|
toolResult: '{"result":"ok","edits_applied":2}'
|
||||||
},
|
},
|
||||||
BuiltInTool.EDIT_FILE
|
BuiltInTool.SERVER_EDIT_FILE
|
||||||
);
|
);
|
||||||
const meta = parseEditFileMeta(section);
|
const meta = parseEditFileMeta(section);
|
||||||
|
|
||||||
@@ -242,9 +248,9 @@ describe('parseEditFileMeta', () => {
|
|||||||
const section = makeSection(
|
const section = makeSection(
|
||||||
{
|
{
|
||||||
toolArgs: '{"path":"/foo","edits":[{"old_text":""},{"old_text":"a","new_text":""}]}',
|
toolArgs: '{"path":"/foo","edits":[{"old_text":""},{"old_text":"a","new_text":""}]}',
|
||||||
toolName: BuiltInTool.EDIT_FILE
|
toolName: BuiltInTool.SERVER_EDIT_FILE
|
||||||
},
|
},
|
||||||
BuiltInTool.EDIT_FILE
|
BuiltInTool.SERVER_EDIT_FILE
|
||||||
);
|
);
|
||||||
const meta = parseEditFileMeta(section);
|
const meta = parseEditFileMeta(section);
|
||||||
|
|
||||||
@@ -257,10 +263,10 @@ describe('parseEditFileMeta', () => {
|
|||||||
const section = makeSection(
|
const section = makeSection(
|
||||||
{
|
{
|
||||||
toolArgs: '{"path":"/foo"}',
|
toolArgs: '{"path":"/foo"}',
|
||||||
toolName: BuiltInTool.EDIT_FILE,
|
toolName: BuiltInTool.SERVER_EDIT_FILE,
|
||||||
toolResult: '{"error":"bad path","result":"ok"}'
|
toolResult: '{"error":"bad path","result":"ok"}'
|
||||||
},
|
},
|
||||||
BuiltInTool.EDIT_FILE
|
BuiltInTool.SERVER_EDIT_FILE
|
||||||
);
|
);
|
||||||
const meta = parseEditFileMeta(section);
|
const meta = parseEditFileMeta(section);
|
||||||
|
|
||||||
@@ -272,7 +278,7 @@ describe('parseEditFileMeta', () => {
|
|||||||
describe('parseReadFileMeta', () => {
|
describe('parseReadFileMeta', () => {
|
||||||
it('parses file name alone (no range)', () => {
|
it('parses file name alone (no range)', () => {
|
||||||
const meta = parseReadFileMeta(
|
const meta = parseReadFileMeta(
|
||||||
makeSection({ toolArgs: '{"path":"/foo.txt"}' }, BuiltInTool.READ_FILE)
|
makeSection({ toolArgs: '{"path":"/foo.txt"}' }, BuiltInTool.SERVER_READ_FILE)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(meta?.fileName).toBe('foo.txt');
|
expect(meta?.fileName).toBe('foo.txt');
|
||||||
@@ -283,7 +289,7 @@ describe('parseReadFileMeta', () => {
|
|||||||
const meta = parseReadFileMeta(
|
const meta = parseReadFileMeta(
|
||||||
makeSection(
|
makeSection(
|
||||||
{ toolArgs: '{"path":"/foo.ts","start_line":10,"end_line":20}' },
|
{ toolArgs: '{"path":"/foo.ts","start_line":10,"end_line":20}' },
|
||||||
BuiltInTool.READ_FILE
|
BuiltInTool.SERVER_READ_FILE
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -294,7 +300,7 @@ describe('parseReadFileMeta', () => {
|
|||||||
const meta = parseReadFileMeta(
|
const meta = parseReadFileMeta(
|
||||||
makeSection(
|
makeSection(
|
||||||
{ toolArgs: '{"path":"/foo.ts","start_line":10,"line_count":5}' },
|
{ toolArgs: '{"path":"/foo.ts","start_line":10,"line_count":5}' },
|
||||||
BuiltInTool.READ_FILE
|
BuiltInTool.SERVER_READ_FILE
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -302,7 +308,9 @@ describe('parseReadFileMeta', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns null when args cannot be parsed', () => {
|
it('returns null when args cannot be parsed', () => {
|
||||||
expect(parseReadFileMeta(makeSection({ toolArgs: '{bad' }, BuiltInTool.READ_FILE))).toBeNull();
|
expect(
|
||||||
|
parseReadFileMeta(makeSection({ toolArgs: '{bad' }, BuiltInTool.SERVER_READ_FILE))
|
||||||
|
).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -310,12 +318,12 @@ describe('parseGrepSearchMeta', () => {
|
|||||||
it('returns null when path or pattern is missing', () => {
|
it('returns null when path or pattern is missing', () => {
|
||||||
expect(
|
expect(
|
||||||
parseGrepSearchMeta(
|
parseGrepSearchMeta(
|
||||||
makeSection({ toolArgs: '{"pattern":"foo"}', toolName: BuiltInTool.GREP_SEARCH })
|
makeSection({ toolArgs: '{"pattern":"foo"}', toolName: BuiltInTool.SERVER_GREP_SEARCH })
|
||||||
)
|
)
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
expect(
|
expect(
|
||||||
parseGrepSearchMeta(
|
parseGrepSearchMeta(
|
||||||
makeSection({ toolArgs: '{"path":"/x"}', toolName: BuiltInTool.GREP_SEARCH })
|
makeSection({ toolArgs: '{"path":"/x"}', toolName: BuiltInTool.SERVER_GREP_SEARCH })
|
||||||
)
|
)
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
});
|
});
|
||||||
@@ -325,10 +333,10 @@ describe('parseGrepSearchMeta', () => {
|
|||||||
makeSection(
|
makeSection(
|
||||||
{
|
{
|
||||||
toolArgs: '{"path":"/x","pattern":"foo"}',
|
toolArgs: '{"path":"/x","pattern":"foo"}',
|
||||||
toolName: BuiltInTool.GREP_SEARCH,
|
toolName: BuiltInTool.SERVER_GREP_SEARCH,
|
||||||
toolResult: JSON.stringify({ plain_text_response: 'a.ts:hello\nb.ts:world' })
|
toolResult: JSON.stringify({ plain_text_response: 'a.ts:hello\nb.ts:world' })
|
||||||
},
|
},
|
||||||
BuiltInTool.GREP_SEARCH
|
BuiltInTool.SERVER_GREP_SEARCH
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -341,10 +349,10 @@ describe('parseGrepSearchMeta', () => {
|
|||||||
makeSection(
|
makeSection(
|
||||||
{
|
{
|
||||||
toolArgs: '{"path":"/x","pattern":"foo"}',
|
toolArgs: '{"path":"/x","pattern":"foo"}',
|
||||||
toolName: BuiltInTool.GREP_SEARCH,
|
toolName: BuiltInTool.SERVER_GREP_SEARCH,
|
||||||
toolResult: 'a.ts:hello\nb.ts:world'
|
toolResult: 'a.ts:hello\nb.ts:world'
|
||||||
},
|
},
|
||||||
BuiltInTool.GREP_SEARCH
|
BuiltInTool.SERVER_GREP_SEARCH
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -356,10 +364,10 @@ describe('parseGrepSearchMeta', () => {
|
|||||||
makeSection(
|
makeSection(
|
||||||
{
|
{
|
||||||
toolArgs: '{"path":"/x","pattern":"foo","return_line_numbers":true}',
|
toolArgs: '{"path":"/x","pattern":"foo","return_line_numbers":true}',
|
||||||
toolName: BuiltInTool.GREP_SEARCH,
|
toolName: BuiltInTool.SERVER_GREP_SEARCH,
|
||||||
toolResult: 'a.ts:12:hello'
|
toolResult: 'a.ts:12:hello'
|
||||||
},
|
},
|
||||||
BuiltInTool.GREP_SEARCH
|
BuiltInTool.SERVER_GREP_SEARCH
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -374,10 +382,10 @@ describe('parseFileGlobSearchMeta', () => {
|
|||||||
makeSection(
|
makeSection(
|
||||||
{
|
{
|
||||||
toolArgs: '{"path":"/x"}',
|
toolArgs: '{"path":"/x"}',
|
||||||
toolName: BuiltInTool.FILE_GLOB_SEARCH,
|
toolName: BuiltInTool.SERVER_FILE_GLOB_SEARCH,
|
||||||
toolResult: 'a.ts\nb.ts'
|
toolResult: 'a.ts\nb.ts'
|
||||||
},
|
},
|
||||||
BuiltInTool.FILE_GLOB_SEARCH
|
BuiltInTool.SERVER_FILE_GLOB_SEARCH
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -389,10 +397,10 @@ describe('parseFileGlobSearchMeta', () => {
|
|||||||
makeSection(
|
makeSection(
|
||||||
{
|
{
|
||||||
toolArgs: '{"path":"/x"}',
|
toolArgs: '{"path":"/x"}',
|
||||||
toolName: BuiltInTool.FILE_GLOB_SEARCH,
|
toolName: BuiltInTool.SERVER_FILE_GLOB_SEARCH,
|
||||||
toolResult: JSON.stringify({ plain_text_response: 'a.ts\nb.ts' })
|
toolResult: JSON.stringify({ plain_text_response: 'a.ts\nb.ts' })
|
||||||
},
|
},
|
||||||
BuiltInTool.FILE_GLOB_SEARCH
|
BuiltInTool.SERVER_FILE_GLOB_SEARCH
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -404,10 +412,10 @@ describe('parseFileGlobSearchMeta', () => {
|
|||||||
makeSection(
|
makeSection(
|
||||||
{
|
{
|
||||||
toolArgs: '{"path":"/x"}',
|
toolArgs: '{"path":"/x"}',
|
||||||
toolName: BuiltInTool.FILE_GLOB_SEARCH,
|
toolName: BuiltInTool.SERVER_FILE_GLOB_SEARCH,
|
||||||
toolResult: JSON.stringify({ error: 'permission denied' })
|
toolResult: JSON.stringify({ error: 'permission denied' })
|
||||||
},
|
},
|
||||||
BuiltInTool.FILE_GLOB_SEARCH
|
BuiltInTool.SERVER_FILE_GLOB_SEARCH
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -418,15 +426,20 @@ describe('parseFileGlobSearchMeta', () => {
|
|||||||
describe('parseRunJavascriptMeta', () => {
|
describe('parseRunJavascriptMeta', () => {
|
||||||
it('returns null when code is missing', () => {
|
it('returns null when code is missing', () => {
|
||||||
expect(
|
expect(
|
||||||
parseRunJavascriptMeta(makeSection({ toolArgs: '{}', toolName: BuiltInTool.RUN_JAVASCRIPT }))
|
parseRunJavascriptMeta(
|
||||||
|
makeSection({ toolArgs: '{}', toolName: BuiltInTool.BROWSER_RUN_JAVASCRIPT })
|
||||||
|
)
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reads code and timeout', () => {
|
it('reads code and timeout', () => {
|
||||||
const meta = parseRunJavascriptMeta(
|
const meta = parseRunJavascriptMeta(
|
||||||
makeSection(
|
makeSection(
|
||||||
{ toolArgs: '{"code":"Math.PI","timeout_ms":5000}', toolName: BuiltInTool.RUN_JAVASCRIPT },
|
{
|
||||||
BuiltInTool.RUN_JAVASCRIPT
|
toolArgs: '{"code":"Math.PI","timeout_ms":5000}',
|
||||||
|
toolName: BuiltInTool.BROWSER_RUN_JAVASCRIPT
|
||||||
|
},
|
||||||
|
BuiltInTool.BROWSER_RUN_JAVASCRIPT
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -439,10 +452,10 @@ describe('parseRunJavascriptMeta', () => {
|
|||||||
makeSection(
|
makeSection(
|
||||||
{
|
{
|
||||||
toolArgs: '{"code":"throw new Error()"}',
|
toolArgs: '{"code":"throw new Error()"}',
|
||||||
toolName: BuiltInTool.RUN_JAVASCRIPT,
|
toolName: BuiltInTool.BROWSER_RUN_JAVASCRIPT,
|
||||||
toolResult: JSON.stringify({ error: 'undefined is not a function' })
|
toolResult: JSON.stringify({ error: 'undefined is not a function' })
|
||||||
},
|
},
|
||||||
BuiltInTool.RUN_JAVASCRIPT
|
BuiltInTool.BROWSER_RUN_JAVASCRIPT
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -457,10 +470,10 @@ describe('parseRunJavascriptMeta', () => {
|
|||||||
makeSection(
|
makeSection(
|
||||||
{
|
{
|
||||||
toolArgs: '{"code":"[1,2,3]"}',
|
toolArgs: '{"code":"[1,2,3]"}',
|
||||||
toolName: BuiltInTool.RUN_JAVASCRIPT,
|
toolName: BuiltInTool.BROWSER_RUN_JAVASCRIPT,
|
||||||
toolResult: '[1,2,3]'
|
toolResult: '[1,2,3]'
|
||||||
},
|
},
|
||||||
BuiltInTool.RUN_JAVASCRIPT
|
BuiltInTool.BROWSER_RUN_JAVASCRIPT
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -472,10 +485,10 @@ describe('parseRunJavascriptMeta', () => {
|
|||||||
makeSection(
|
makeSection(
|
||||||
{
|
{
|
||||||
toolArgs: '{"code":"foo"}',
|
toolArgs: '{"code":"foo"}',
|
||||||
toolName: BuiltInTool.RUN_JAVASCRIPT,
|
toolName: BuiltInTool.BROWSER_RUN_JAVASCRIPT,
|
||||||
toolResult: 'Error: undefined is not a function\n at <anonymous>:1:1'
|
toolResult: 'Error: undefined is not a function\n at <anonymous>:1:1'
|
||||||
},
|
},
|
||||||
BuiltInTool.RUN_JAVASCRIPT
|
BuiltInTool.BROWSER_RUN_JAVASCRIPT
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -487,8 +500,8 @@ describe('parseExecShellCommandMeta', () => {
|
|||||||
it('reads command from the args', () => {
|
it('reads command from the args', () => {
|
||||||
const meta = parseExecShellCommandMeta(
|
const meta = parseExecShellCommandMeta(
|
||||||
makeSection(
|
makeSection(
|
||||||
{ toolArgs: '{"command":"ls -la"}', toolName: BuiltInTool.EXEC_SHELL_COMMAND },
|
{ toolArgs: '{"command":"ls -la"}', toolName: BuiltInTool.SERVER_EXEC_SHELL_COMMAND },
|
||||||
BuiltInTool.EXEC_SHELL_COMMAND
|
BuiltInTool.SERVER_EXEC_SHELL_COMMAND
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -499,16 +512,16 @@ describe('parseExecShellCommandMeta', () => {
|
|||||||
expect(
|
expect(
|
||||||
parseExecShellCommandMeta(
|
parseExecShellCommandMeta(
|
||||||
makeSection(
|
makeSection(
|
||||||
{ toolArgs: '{"cmd":"ls"}', toolName: BuiltInTool.EXEC_SHELL_COMMAND },
|
{ toolArgs: '{"cmd":"ls"}', toolName: BuiltInTool.SERVER_EXEC_SHELL_COMMAND },
|
||||||
BuiltInTool.EXEC_SHELL_COMMAND
|
BuiltInTool.SERVER_EXEC_SHELL_COMMAND
|
||||||
)
|
)
|
||||||
)?.command
|
)?.command
|
||||||
).toBe('ls');
|
).toBe('ls');
|
||||||
expect(
|
expect(
|
||||||
parseExecShellCommandMeta(
|
parseExecShellCommandMeta(
|
||||||
makeSection(
|
makeSection(
|
||||||
{ toolArgs: '{"shell_command":"ls"}', toolName: BuiltInTool.EXEC_SHELL_COMMAND },
|
{ toolArgs: '{"shell_command":"ls"}', toolName: BuiltInTool.SERVER_EXEC_SHELL_COMMAND },
|
||||||
BuiltInTool.EXEC_SHELL_COMMAND
|
BuiltInTool.SERVER_EXEC_SHELL_COMMAND
|
||||||
)
|
)
|
||||||
)?.command
|
)?.command
|
||||||
).toBe('ls');
|
).toBe('ls');
|
||||||
@@ -518,8 +531,8 @@ describe('parseExecShellCommandMeta', () => {
|
|||||||
expect(
|
expect(
|
||||||
parseExecShellCommandMeta(
|
parseExecShellCommandMeta(
|
||||||
makeSection(
|
makeSection(
|
||||||
{ toolArgs: '{"cwd":"/x"}', toolName: BuiltInTool.EXEC_SHELL_COMMAND },
|
{ toolArgs: '{"cwd":"/x"}', toolName: BuiltInTool.SERVER_EXEC_SHELL_COMMAND },
|
||||||
BuiltInTool.EXEC_SHELL_COMMAND
|
BuiltInTool.SERVER_EXEC_SHELL_COMMAND
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
|
|||||||
Reference in New Issue
Block a user