Merge commit '34af94cd9ab277632e27caeec2d41de2fd091b31' into concedo_experimental

# Conflicts:
#	.github/workflows/docker.yml
#	.github/workflows/make-release.yml
#	.github/workflows/release.yml
#	.pi/gg/SYSTEM.md
#	CMakeLists.txt
#	build-xcframework.sh
#	docs/development/HOWTO-add-model.md
#	docs/ops.md
#	docs/ops/SYCL.csv
#	docs/speculative.md
#	examples/sycl/update-ops-doc.sh
#	ggml/CMakeLists.txt
#	ggml/src/ggml-sycl/cpy.cpp
#	ggml/src/ggml-sycl/ggml-sycl.cpp
#	scripts/make-release-checks.sh
#	scripts/sync-ggml.last
#	tests/test-chat-auto-parser.cpp
#	tests/test-chat.cpp
#	tests/test-jinja.cpp
#	tests/test-llama-archs.cpp
#	tests/testing.h
#	tools/llama-bench/llama-bench.cpp
#	tools/server/README.md
This commit is contained in:
Concedo
2026-08-20 17:08:46 +08:00
135 changed files with 3116 additions and 312 deletions
@@ -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 {};
@@ -158,6 +158,8 @@
<div class="relative">
<Input
id="api-key-input"
type="password"
autocomplete="new-password"
placeholder="Enter your API key..."
bind:value={apiKeyInput}
onkeydown={handleApiKeyKeydown}
@@ -81,7 +81,8 @@
<div class="relative w-full">
<Input
id={field.key}
type={field.isPositiveInteger ? 'number' : 'text'}
type={field.isPrivate ? 'password' : field.isPositiveInteger ? 'number' : 'text'}
autocomplete={field.isPrivate ? 'new-password' : undefined}
{...field.isPositiveInteger
? {
min: String(field.min ?? 1),
@@ -0,0 +1,36 @@
import { CLI_FLAGS } from './cli-flags.constants';
import { BuiltInTool, JsonSchemaType, ToolCallType } from '$lib/enums';
import type { OpenAIToolDefinition } from '$lib/types';
export const BROWSER_INFO_TOOL_NAME = BuiltInTool.GET_INFO;
/** 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][] = [
[/Windows NT/, 'Windows'],
[/Android/, 'Android'],
[/iPhone|iPad|iPod/, 'iOS'],
[/CrOS/, 'ChromeOS'],
[/Mac OS X/, 'macOS'],
[/Linux/, 'Linux']
];
export const BROWSER_INFO_OS_UNKNOWN = 'unknown';
/** Sent to the model as the `note` field of the tool result, next to the OS name */
export const BROWSER_INFO_NOTE = `This environment is browser-only, it cannot read or modify local files, and it cannot run shell commands. To get local file access, tell user to launch llama-server with the ${CLI_FLAGS.AGENT} argument.`;
export function buildBrowserInfoToolDefinition(): OpenAIToolDefinition {
return {
function: {
description:
'Get runtime info (OS name), may call when user asks about local files or shell commands',
name: BROWSER_INFO_TOOL_NAME,
parameters: {
properties: {},
required: [],
type: JsonSchemaType.OBJECT
}
},
type: ToolCallType.FUNCTION
};
}
@@ -34,7 +34,7 @@ export const BUILTIN_TOOL_UI: Readonly<Record<BuiltInTool, BuiltinToolUiEntry>>
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,
@@ -1,4 +1,5 @@
export const CLI_FLAGS = {
AGENT: '--agent',
API_KEY: '--api-key',
MCP_PROXY: '--ui-mcp-proxy',
SLOTS: '--slots',
@@ -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
};
}
+2
View File
@@ -59,3 +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';
@@ -324,6 +324,7 @@ const SETTINGS_REGISTRY: Record<string, SettingsSectionEntry> = {
{
defaultValue: '',
help: `Set the API Key if you are using <code> ${CLI_FLAGS.API_KEY} </code> option for the server.`,
isPrivate: true,
key: SETTINGS_KEYS.API_KEY,
label: 'API Key',
section: SETTINGS_SECTION_SLUGS.GENERAL,
@@ -713,6 +714,7 @@ export const SETTINGS_CHAT_SECTIONS: SettingsSection[] = [
help: s.help,
isExperimental: s.isExperimental,
isPositiveInteger: s.isPositiveInteger,
isPrivate: s.isPrivate,
key: s.key,
label: s.label,
max: s.max,
+15 -3
View File
@@ -18,7 +18,8 @@ import {
DEFAULT_CLIENT_VERSION,
DEFAULT_IMAGE_MIME_TYPE,
DEFAULT_MCP_CONFIG,
HEADERS
HEADERS,
NEWLINE
} from '$lib/constants';
import {
MCPConnectionPhase,
@@ -70,6 +71,7 @@ interface ToolResultContentItem {
interface ToolCallResult {
content?: ToolResultContentItem[];
structuredContent?: Record<string, unknown>;
isError?: boolean;
_meta?: Record<string, unknown>;
}
@@ -1012,10 +1014,20 @@ export class MCPService {
if (!Array.isArray(content)) return '';
return content
const formatted = content
.map((item) => this.formatSingleContent(item))
.filter(Boolean)
.join('\n');
.join(NEWLINE);
if (formatted !== '') {
return formatted;
}
if (result.structuredContent && typeof result.structuredContent === 'object') {
return JSON.stringify(result.structuredContent);
}
return '';
}
private static formatSingleContent(content: ToolResultContentItem): string {
+28 -14
View File
@@ -56,7 +56,8 @@ import type {
AgenticSession,
McpServerOverride,
MCPToolCall,
SettingsConfigType
SettingsConfigType,
ToolExecutionResult
} from '$lib/types';
import type {
AgenticFlowCallbacks,
@@ -83,7 +84,12 @@ import type {
DatabaseMessageExtraAudioFile,
DatabaseMessageExtraImageFile
} from '$lib/types/database';
import { getAudioInputFormat, isAbortError } from '$lib/utils';
import {
executeBrowserInfoTool,
executeGetDatetimeTool,
getAudioInputFormat,
isAbortError
} from '$lib/utils';
import { SvelteMap } from 'svelte/reactivity';
function createDefaultSession(): AgenticSession {
@@ -942,18 +948,26 @@ class AgenticStore {
if (executionResult.isError) toolSuccess = false;
} else if (toolSource === ToolSource.FRONTEND) {
const args = this.parseToolArguments(toolCall.function.arguments);
const executionResult =
toolName === BuiltInTool.READ_MEDIA
? await ReadMediaService.executeTool(
args,
{
audio: modelsStore.modelSupportsAudio(effectiveModel),
vision: modelsStore.modelSupportsVision(effectiveModel)
},
signal,
conversationsStore.activeConversation?.cwd
)
: await SandboxService.executeTool(toolName, args, signal);
let executionResult: ToolExecutionResult;
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(
args,
{
audio: modelsStore.modelSupportsAudio(effectiveModel),
vision: modelsStore.modelSupportsVision(effectiveModel)
},
signal,
conversationsStore.activeConversation?.cwd
);
} else {
executionResult = await SandboxService.executeTool(toolName, args, signal);
}
result = executionResult.content;
+13 -6
View File
@@ -1,5 +1,7 @@
import { browser } from '$app/environment';
import {
buildBrowserInfoToolDefinition,
buildGetDatetimeToolDefinition,
buildReadMediaToolDefinition,
DISABLED_TOOL_KEYS_LOCALSTORAGE_KEY,
HOME_TILDE,
@@ -175,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));
@@ -185,9 +187,18 @@ class ToolsStore {
if (readMedia) tools.push(readMedia);
// provide browser's get_info tool if server doesn't provide one
if (!this.hasBuiltinTool(BuiltInTool.GET_INFO)) {
tools.push(buildBrowserInfoToolDefinition());
}
return tools;
}
private hasBuiltinTool(name: BuiltInTool): boolean {
return this._builtinTools.some((def) => def.function.name === name);
}
/**
* `read_media` runs in the frontend on top of the server's `read_file`, so it
* exists only when that tool is served and the active model can perceive the
@@ -195,11 +206,7 @@ class ToolsStore {
* conversation uses.
*/
private readMediaTool(): OpenAIToolDefinition | null {
const hasReadFile = this._builtinTools.some(
(def) => def.function.name === BuiltInTool.READ_FILE
);
if (!hasReadFile) return null;
if (!this.hasBuiltinTool(BuiltInTool.READ_FILE)) return null;
const model = modelsStore.selectedModelName ?? modelsStore.models[0]?.model ?? '';
+2
View File
@@ -31,6 +31,7 @@ export interface SettingsEntry {
radioOptions?: Array<{ value: string; label: string; key: string; isExperimental?: boolean }>;
isExperimental?: boolean;
isPositiveInteger?: boolean;
isPrivate?: boolean;
placeholder?: string;
min?: number;
max?: number;
@@ -55,6 +56,7 @@ export interface SettingsFieldConfig {
type: SettingsFieldType;
isExperimental?: boolean;
isPositiveInteger?: boolean;
isPrivate?: boolean;
placeholder?: string;
min?: number;
max?: number;
+39
View File
@@ -0,0 +1,39 @@
/**
* Browser fallback for the server's `get_info` tool, offered only when the
* server does not serve one (llama-server without --agent). It tells the model
* which OS the browser runs on and that there is no local file or shell access,
* so it does not plan around tools that are not there.
*
* @see server_tool_get_info in tools/server/server-tools.cpp - the served variant
* @see buildBrowserInfoToolDefinition in constants/browser-info.ts - tool schema sent to the LLM
*/
import { browser } from '$app/environment';
import {
BROWSER_INFO_NOTE,
BROWSER_INFO_OS_UA_PATTERNS,
BROWSER_INFO_OS_UNKNOWN
} from '$lib/constants';
import type { ToolExecutionResult } from '$lib/types';
function detectOs(userAgent: string): string {
for (const [pattern, os] of BROWSER_INFO_OS_UA_PATTERNS) {
if (pattern.test(userAgent)) return os;
}
return BROWSER_INFO_OS_UNKNOWN;
}
/**
* Result shape mirrors the server tool's JSON so the `get_info` renderer reads
* `os` the same way, minus `cwd` - there is no working directory to report.
*/
export function executeBrowserInfoTool(): ToolExecutionResult {
return {
content: JSON.stringify({
note: BROWSER_INFO_NOTE,
os: browser ? detectOs(navigator.userAgent) : BROWSER_INFO_OS_UNKNOWN
}),
isError: false
};
}
+38
View File
@@ -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
};
}
+6
View File
@@ -331,6 +331,12 @@ 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';
// Cryptography utilities
export { uuid } from './uuid';