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 <admin@serveurperso.com>
This commit is contained in:
co-authored by
Pascal
parent
805984d676
commit
666f8898a2
+1
-1
@@ -3362,7 +3362,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
|
|||||||
{"--tools"}, "TOOL1,TOOL2,...",
|
{"--tools"}, "TOOL1,TOOL2,...",
|
||||||
"experimental: whether to enable built-in tools for AI agents - do not enable in untrusted environments (default: no tools)\n"
|
"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"
|
"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",
|
"note: for security reasons, this will limit --cors-origins to localhost by default",
|
||||||
[](common_params & params, const std::string & value) {
|
[](common_params & params, const std::string & value) {
|
||||||
params.server_tools = parse_csv_row(value);
|
params.server_tools = parse_csv_row(value);
|
||||||
|
|||||||
@@ -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)<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_datetime, 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 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-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) |
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#include <regex>
|
#include <regex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <ctime>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
@@ -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)
|
// get_info: returns runtime info (OS name/version and cwd)
|
||||||
//
|
//
|
||||||
@@ -2005,6 +1949,10 @@ static server_tool & find_tool(std::vector<std::unique_ptr<server_tool>> & tools
|
|||||||
//
|
//
|
||||||
|
|
||||||
static std::vector<std::unique_ptr<server_tool>> build_tools() {
|
static std::vector<std::unique_ptr<server_tool>> 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<std::unique_ptr<server_tool>> tools;
|
std::vector<std::unique_ptr<server_tool>> tools;
|
||||||
tools.push_back(std::make_unique<server_tool_read_file>());
|
tools.push_back(std::make_unique<server_tool_read_file>());
|
||||||
tools.push_back(std::make_unique<server_tool_file_glob_search>());
|
tools.push_back(std::make_unique<server_tool_file_glob_search>());
|
||||||
@@ -2012,7 +1960,6 @@ static std::vector<std::unique_ptr<server_tool>> build_tools() {
|
|||||||
tools.push_back(std::make_unique<server_tool_exec_shell_command>());
|
tools.push_back(std::make_unique<server_tool_exec_shell_command>());
|
||||||
tools.push_back(std::make_unique<server_tool_write_file>());
|
tools.push_back(std::make_unique<server_tool_write_file>());
|
||||||
tools.push_back(std::make_unique<server_tool_edit_file>());
|
tools.push_back(std::make_unique<server_tool_edit_file>());
|
||||||
tools.push_back(std::make_unique<server_tool_get_datetime>());
|
|
||||||
tools.push_back(std::make_unique<server_tool_get_info>());
|
tools.push_back(std::make_unique<server_tool_get_info>());
|
||||||
return tools;
|
return tools;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@
|
|||||||
if (typeof obj.result === 'string') return { dateString: obj.result.trim() };
|
if (typeof obj.result === 'string') return { dateString: obj.result.trim() };
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
return { dateString: toolResultString.trim() };
|
// not JSON - nothing to show
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const BUILTIN_TOOL_UI: Readonly<Record<BuiltInTool, BuiltinToolUiEntry>>
|
|||||||
label: 'Search files',
|
label: 'Search files',
|
||||||
source: ToolSource.BUILTIN
|
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.GET_INFO]: { icon: Info, label: 'Runtime info', source: ToolSource.BUILTIN },
|
||||||
[BuiltInTool.GREP_SEARCH]: {
|
[BuiltInTool.GREP_SEARCH]: {
|
||||||
icon: SearchCode,
|
icon: SearchCode,
|
||||||
|
|||||||
@@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -59,4 +59,5 @@ export * from './uri-template.constants';
|
|||||||
export * from './url.constants';
|
export * from './url.constants';
|
||||||
export * from './working-directory.constants';
|
export * from './working-directory.constants';
|
||||||
export * from './read-media';
|
export * from './read-media';
|
||||||
|
export * from './get-datetime';
|
||||||
export * from './browser-info';
|
export * from './browser-info';
|
||||||
|
|||||||
@@ -84,7 +84,12 @@ import type {
|
|||||||
DatabaseMessageExtraAudioFile,
|
DatabaseMessageExtraAudioFile,
|
||||||
DatabaseMessageExtraImageFile
|
DatabaseMessageExtraImageFile
|
||||||
} from '$lib/types/database';
|
} from '$lib/types/database';
|
||||||
import { executeBrowserInfoTool, getAudioInputFormat, isAbortError } from '$lib/utils';
|
import {
|
||||||
|
executeBrowserInfoTool,
|
||||||
|
executeGetDatetimeTool,
|
||||||
|
getAudioInputFormat,
|
||||||
|
isAbortError
|
||||||
|
} from '$lib/utils';
|
||||||
import { SvelteMap } from 'svelte/reactivity';
|
import { SvelteMap } from 'svelte/reactivity';
|
||||||
|
|
||||||
function createDefaultSession(): AgenticSession {
|
function createDefaultSession(): AgenticSession {
|
||||||
@@ -946,7 +951,9 @@ class AgenticStore {
|
|||||||
|
|
||||||
let executionResult: ToolExecutionResult;
|
let executionResult: ToolExecutionResult;
|
||||||
|
|
||||||
if (toolName === BuiltInTool.GET_INFO) {
|
if (toolName === BuiltInTool.GET_DATETIME) {
|
||||||
|
executionResult = executeGetDatetimeTool();
|
||||||
|
} else if (toolName === BuiltInTool.GET_INFO) {
|
||||||
executionResult = executeBrowserInfoTool();
|
executionResult = executeBrowserInfoTool();
|
||||||
} else if (toolName === BuiltInTool.READ_MEDIA) {
|
} else if (toolName === BuiltInTool.READ_MEDIA) {
|
||||||
executionResult = await ReadMediaService.executeTool(
|
executionResult = await ReadMediaService.executeTool(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import {
|
import {
|
||||||
buildBrowserInfoToolDefinition,
|
buildBrowserInfoToolDefinition,
|
||||||
|
buildGetDatetimeToolDefinition,
|
||||||
buildReadMediaToolDefinition,
|
buildReadMediaToolDefinition,
|
||||||
DISABLED_TOOL_KEYS_LOCALSTORAGE_KEY,
|
DISABLED_TOOL_KEYS_LOCALSTORAGE_KEY,
|
||||||
HOME_TILDE,
|
HOME_TILDE,
|
||||||
@@ -176,7 +177,7 @@ class ToolsStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get frontendTools(): OpenAIToolDefinition[] {
|
get frontendTools(): OpenAIToolDefinition[] {
|
||||||
const tools: OpenAIToolDefinition[] = [];
|
const tools: OpenAIToolDefinition[] = [buildGetDatetimeToolDefinition()];
|
||||||
|
|
||||||
if (settingsStore.config.jsSandboxEnabled) {
|
if (settingsStore.config.jsSandboxEnabled) {
|
||||||
tools.push(buildSandboxToolDefinition(!!settingsStore.config.symbolicMathEnabled));
|
tools.push(buildSandboxToolDefinition(!!settingsStore.config.symbolicMathEnabled));
|
||||||
|
|||||||
@@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -331,6 +331,9 @@ 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)
|
||||||
|
export { executeGetDatetimeTool } from './get-datetime';
|
||||||
|
|
||||||
// Browser fallback for the server's get_info tool
|
// Browser fallback for the server's get_info tool
|
||||||
export { executeBrowserInfoTool } from './browser-info';
|
export { executeBrowserInfoTool } from './browser-info';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user