From 666f8898a25a2d5e86cd53ea4dfa4e24e4426439 Mon Sep 17 00:00:00 2001 From: Xuan-Son Nguyen Date: Mon, 17 Aug 2026 14:33:55 +0200 Subject: [PATCH] ui: move get_datetime tool to frontend (#27255) * ui: move get_datetime tool to frontend * clarify docs * server: drop the now unused ctime include strftime() and gmtime_r() were the only users, both went away with the get_datetime tool. Also make the renderer's catch inert: the browser executor always emits JSON, so a non-JSON result is no longer a date to display. --------- Co-authored-by: Pascal --- common/arg.cpp | 2 +- tools/server/README.md | 2 +- tools/server/server-tools.cpp | 61 ++----------------- ...ChatMessageToolCallBlockGetDatetime.svelte | 2 +- .../lib/constants/built-in-tools.constants.ts | 2 +- tools/ui/src/lib/constants/get-datetime.ts | 20 ++++++ tools/ui/src/lib/constants/index.ts | 1 + tools/ui/src/lib/stores/agentic.svelte.ts | 11 +++- tools/ui/src/lib/stores/tools.svelte.ts | 3 +- tools/ui/src/lib/utils/get-datetime.ts | 38 ++++++++++++ tools/ui/src/lib/utils/index.ts | 3 + 11 files changed, 81 insertions(+), 64 deletions(-) create mode 100644 tools/ui/src/lib/constants/get-datetime.ts create mode 100644 tools/ui/src/lib/utils/get-datetime.ts diff --git a/common/arg.cpp b/common/arg.cpp index 49b33806a..0766087c3 100644 --- a/common/arg.cpp +++ b/common/arg.cpp @@ -3362,7 +3362,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex {"--tools"}, "TOOL1,TOOL2,...", "experimental: whether to enable built-in tools for AI agents - do not enable in untrusted environments (default: no tools)\n" "specify \"all\" to enable all tools\n" - "available tools: read_file, file_glob_search, grep_search, exec_shell_command, write_file, edit_file, get_datetime, get_info\n" + "available tools: read_file, file_glob_search, grep_search, exec_shell_command, write_file, edit_file, get_info\n" "note: for security reasons, this will limit --cors-origins to localhost by default", [](common_params & params, const std::string & value) { params.server_tools = parse_csv_row(value); diff --git a/tools/server/README.md b/tools/server/README.md index c3cbf7be0..017981fd1 100644 --- a/tools/server/README.md +++ b/tools/server/README.md @@ -196,7 +196,7 @@ 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)
(env: LLAMA_ARG_UI_CONFIG) | | `--ui-config-file, --webui-config-file PATH` | JSON file that provides default UI settings (overrides UI defaults)
(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)
(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)
specify "all" to enable all tools
available tools: read_file, file_glob_search, grep_search, exec_shell_command, write_file, edit_file, get_datetime, get_info
note: for security reasons, this will limit --cors-origins to localhost by default
(env: LLAMA_ARG_TOOLS) | +| `--tools TOOL1,TOOL2,...` | experimental: whether to enable built-in tools for AI agents - do not enable in untrusted environments (default: no tools)
specify "all" to enable all tools
available tools: read_file, file_glob_search, grep_search, exec_shell_command, write_file, edit_file, get_info
note: for security reasons, this will limit --cors-origins to localhost by default
(env: LLAMA_ARG_TOOLS) | | `--tools-runtime OPTION` | experimental: run tools in a separate runtime environment (default: none, use host environment)
available options:
'docker:', 'podman:': spin up a new container and reuse it for all invocations, clean up on server exit
'docker-container:', 'podman-container:': use an existing container by ID, won't stop on server exit
'ssh:': run tools on a remote POSIX host over SSH, key-based auth and a trusted host key are required

(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)
note: for security reasons, this will limit --cors-origins to localhost by default
(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)
note: for security reasons, this will limit --cors-origins to localhost by default
(env: LLAMA_ARG_MCP_SERVERS_JSON) | diff --git a/tools/server/server-tools.cpp b/tools/server/server-tools.cpp index fd0ff8ddd..5e5e60efd 100644 --- a/tools/server/server-tools.cpp +++ b/tools/server/server-tools.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -1692,61 +1691,6 @@ private: } }; -// -// get_datetime: returns the current date and time -// - -struct server_tool_get_datetime : server_tool { - server_tool_get_datetime() { - name = "get_datetime"; - display_name = "Get Date & Time"; - permission_write = false; - } - - json get_definition() const override { - return { - {"type", "function"}, - {"function", { - {"name", name}, - {"description", "Returns the current date and time in UTC"}, - {"parameters", { - {"type", "object"}, - {"properties", { - {"format", { - {"type", "string"}, - {"description", - "strftime()-style format string for the output (default: \"%Y-%m-%dT%H:%M:%SZ\", " - "e.g. ISO 8601). Choose your own format if you need something else, " - "e.g. \"%A, %B %d %Y\" for a human-readable date."}, - }}, - }}, - }}, - }}, - }; - } - - json invoke(json params, server_tool::stream *) const override { - std::string format = json_value(params, "format", std::string("%Y-%m-%dT%H:%M:%SZ")); - - auto now = std::chrono::system_clock::now(); - auto time = std::chrono::system_clock::to_time_t(now); - std::tm tm_utc; -#ifdef _WIN32 - gmtime_s(&tm_utc, &time); -#else - gmtime_r(&time, &tm_utc); -#endif - - char buf[256]; - size_t len = std::strftime(buf, sizeof(buf), format.c_str(), &tm_utc); - if (len == 0) { - return {{"error", "invalid format string"}}; - } - - return {{"result", std::string(buf, len)}}; - } -}; - // // get_info: returns runtime info (OS name/version and cwd) // @@ -2005,6 +1949,10 @@ static server_tool & find_tool(std::vector> & tools // static std::vector> build_tools() { + // IMPORTANT: for contributors, please keep this array of tools as minimal as possible + // we only accept minimal i/o and shell command tools here + // for example, do not add: web search, get date time, etc. + // high-level functionality should be added either via MCP or web UI std::vector> tools; tools.push_back(std::make_unique()); tools.push_back(std::make_unique()); @@ -2012,7 +1960,6 @@ static std::vector> build_tools() { tools.push_back(std::make_unique()); tools.push_back(std::make_unique()); tools.push_back(std::make_unique()); - tools.push_back(std::make_unique()); tools.push_back(std::make_unique()); return tools; } diff --git a/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/ChatMessageToolCallBlockGetDatetime.svelte b/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/ChatMessageToolCallBlockGetDatetime.svelte index 23de7cbd6..44b3ec645 100644 --- a/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/ChatMessageToolCallBlockGetDatetime.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageToolCall/ChatMessageToolCallBlockGetDatetime.svelte @@ -33,7 +33,7 @@ if (typeof obj.result === 'string') return { dateString: obj.result.trim() }; } } catch { - return { dateString: toolResultString.trim() }; + // not JSON - nothing to show } return {}; diff --git a/tools/ui/src/lib/constants/built-in-tools.constants.ts b/tools/ui/src/lib/constants/built-in-tools.constants.ts index 679c61459..61e90e105 100644 --- a/tools/ui/src/lib/constants/built-in-tools.constants.ts +++ b/tools/ui/src/lib/constants/built-in-tools.constants.ts @@ -34,7 +34,7 @@ export const BUILTIN_TOOL_UI: Readonly> label: 'Search files', source: ToolSource.BUILTIN }, - [BuiltInTool.GET_DATETIME]: { icon: Clock, label: 'Current time', 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, diff --git a/tools/ui/src/lib/constants/get-datetime.ts b/tools/ui/src/lib/constants/get-datetime.ts new file mode 100644 index 000000000..c8726d9b5 --- /dev/null +++ b/tools/ui/src/lib/constants/get-datetime.ts @@ -0,0 +1,20 @@ +import { BuiltInTool, JsonSchemaType, ToolCallType } from '$lib/enums'; +import type { OpenAIToolDefinition } from '$lib/types'; + +export const GET_DATETIME_TOOL_NAME = BuiltInTool.GET_DATETIME; + +export function buildGetDatetimeToolDefinition(): OpenAIToolDefinition { + return { + function: { + description: + 'Returns the current local date and time in ISO 8601 format, with the IANA time zone name', + name: GET_DATETIME_TOOL_NAME, + parameters: { + properties: {}, + required: [], + type: JsonSchemaType.OBJECT + } + }, + type: ToolCallType.FUNCTION + }; +} diff --git a/tools/ui/src/lib/constants/index.ts b/tools/ui/src/lib/constants/index.ts index cdd91a073..289239113 100644 --- a/tools/ui/src/lib/constants/index.ts +++ b/tools/ui/src/lib/constants/index.ts @@ -59,4 +59,5 @@ export * from './uri-template.constants'; export * from './url.constants'; export * from './working-directory.constants'; export * from './read-media'; +export * from './get-datetime'; export * from './browser-info'; diff --git a/tools/ui/src/lib/stores/agentic.svelte.ts b/tools/ui/src/lib/stores/agentic.svelte.ts index d6b57ffa8..06c7661fe 100644 --- a/tools/ui/src/lib/stores/agentic.svelte.ts +++ b/tools/ui/src/lib/stores/agentic.svelte.ts @@ -84,7 +84,12 @@ import type { DatabaseMessageExtraAudioFile, DatabaseMessageExtraImageFile } from '$lib/types/database'; -import { executeBrowserInfoTool, getAudioInputFormat, isAbortError } from '$lib/utils'; +import { + executeBrowserInfoTool, + executeGetDatetimeTool, + getAudioInputFormat, + isAbortError +} from '$lib/utils'; import { SvelteMap } from 'svelte/reactivity'; function createDefaultSession(): AgenticSession { @@ -946,7 +951,9 @@ class AgenticStore { let executionResult: ToolExecutionResult; - if (toolName === BuiltInTool.GET_INFO) { + if (toolName === BuiltInTool.GET_DATETIME) { + executionResult = executeGetDatetimeTool(); + } else if (toolName === BuiltInTool.GET_INFO) { executionResult = executeBrowserInfoTool(); } else if (toolName === BuiltInTool.READ_MEDIA) { executionResult = await ReadMediaService.executeTool( diff --git a/tools/ui/src/lib/stores/tools.svelte.ts b/tools/ui/src/lib/stores/tools.svelte.ts index fb3662025..4cfb9f370 100644 --- a/tools/ui/src/lib/stores/tools.svelte.ts +++ b/tools/ui/src/lib/stores/tools.svelte.ts @@ -1,6 +1,7 @@ import { browser } from '$app/environment'; import { buildBrowserInfoToolDefinition, + buildGetDatetimeToolDefinition, buildReadMediaToolDefinition, DISABLED_TOOL_KEYS_LOCALSTORAGE_KEY, HOME_TILDE, @@ -176,7 +177,7 @@ class ToolsStore { } get frontendTools(): OpenAIToolDefinition[] { - const tools: OpenAIToolDefinition[] = []; + const tools: OpenAIToolDefinition[] = [buildGetDatetimeToolDefinition()]; if (settingsStore.config.jsSandboxEnabled) { tools.push(buildSandboxToolDefinition(!!settingsStore.config.symbolicMathEnabled)); diff --git a/tools/ui/src/lib/utils/get-datetime.ts b/tools/ui/src/lib/utils/get-datetime.ts new file mode 100644 index 000000000..cd17bb1e8 --- /dev/null +++ b/tools/ui/src/lib/utils/get-datetime.ts @@ -0,0 +1,38 @@ +/** + * Frontend 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 - + * a chat about "tomorrow" means the user's tomorrow, not the host's. + * + * @see buildGetDatetimeToolDefinition in constants/get-datetime.ts - tool schema sent to the LLM + */ + +import type { ToolExecutionResult } from '$lib/types'; + +function pad(value: number): string { + return String(value).padStart(2, '0'); +} + +/** ISO 8601 in local time, e.g. `2026-08-17T14:05:09+02:00` */ +function localIsoString(date: Date): string { + // getTimezoneOffset() counts minutes behind UTC, ISO 8601 counts them ahead + const offset = -date.getTimezoneOffset(); + const sign = offset < 0 ? '-' : '+'; + const absOffset = Math.abs(offset); + const day = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`; + const time = `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; + + return `${day}T${time}${sign}${pad(Math.floor(absOffset / 60))}:${pad(absOffset % 60)}`; +} + +/** The `result` field keeps the shape the `get_datetime` renderer already reads. */ +export function executeGetDatetimeTool(): ToolExecutionResult { + const now = new Date(); + + return { + content: JSON.stringify({ + result: localIsoString(now), + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone + }), + isError: false + }; +} diff --git a/tools/ui/src/lib/utils/index.ts b/tools/ui/src/lib/utils/index.ts index d991363fc..dd997c206 100644 --- a/tools/ui/src/lib/utils/index.ts +++ b/tools/ui/src/lib/utils/index.ts @@ -331,6 +331,9 @@ export { getChatCommands } from './chat-commands'; // SANDBOX_TOOL_DEFINITION is deprecated; kept for backward compatibility. export { buildSandboxToolDefinition, SANDBOX_TOOL_DEFINITION } from './sandbox-tool'; +// Frontend `get_datetime` executor (the browser clock, not the server's) +export { executeGetDatetimeTool } from './get-datetime'; + // Browser fallback for the server's get_info tool export { executeBrowserInfoTool } from './browser-info';