This commit is contained in:
2026-05-13 12:05:55 +03:00
parent c5b61ee3d8
commit 674c3cbd44
43 changed files with 382 additions and 639 deletions
+6 -2
View File
@@ -1,4 +1,7 @@
import axios from "axios";
import {toolsLogger} from "./tool-logger";
const logger = toolsLogger.child("brave-search");
import {Environment} from "../../common/environment";
import {logError} from "../../util/utils";
import {AiTool} from "../tool-types";
@@ -264,7 +267,8 @@ function normalizeBraveResultFilter(value: unknown): string {
}
export async function webSearch(args?: Record<string, unknown>) {
console.log("braveSearch()");
const startedAt = Date.now();
logger.info("start", {args});
try {
const query = asNonEmptyString(args?.query);
@@ -370,7 +374,7 @@ export async function webSearch(args?: Record<string, unknown>) {
response: data ?? null,
};
} finally {
console.log("END: braveSearch()");
logger.debug("done", {duration: logger.duration(startedAt)});
}
}
+7 -1
View File
@@ -4,6 +4,9 @@ import {readFile, writeFile} from "node:fs/promises";
import {NOTES_HEADER, notesDir, notesRootFile} from "../../index";
import {asNonEmptyString} from "./utils";
import fs from "node:fs";
import {toolsLogger} from "./tool-logger";
const logger = toolsLogger.child("create-note");
export type CreateNoteResult =
| { success: true; filePath: string }
@@ -38,7 +41,8 @@ export const createNoteTool = {
export async function createNote(
args?: Record<string, unknown>
): Promise<CreateNoteResult> {
console.log("CREATE_NOTE; ARGS: ", args);
const startedAt = Date.now();
logger.debug("start", {args});
const fileName = asNonEmptyString(args?.fileName) ?? "";
if (!fileName.trim().length) {
@@ -76,8 +80,10 @@ export async function createNote(
}
await writeFile(notesRootFile, rootContent, "utf-8");
logger.debug("done", {fileName, filePath: newFilePath, duration: logger.duration(startedAt)});
return {success: true, filePath: newFilePath};
} catch (error) {
logger.error("failed", {duration: logger.duration(startedAt), error});
const errorMessage = error instanceof Error ? error.message : String(error);
return {success: false, error: `Failed to process files: ${errorMessage}`};
}
+19 -4
View File
@@ -3,6 +3,9 @@ import path from "node:path";
import {readdir, readFile, unlink, writeFile} from "node:fs/promises";
import {notesDir, notesRootFile} from "../../index";
import {asNonEmptyString} from "./utils";
import {toolsLogger} from "./tool-logger";
const logger = toolsLogger.child("notes");
export type NoteListItem = {
fileName: string;
@@ -58,7 +61,8 @@ export const getNoteContentTool = {
} satisfies AiTool;
export async function listNotes(): Promise<ListNotesResult> {
console.log("LIST_NOTES");
const startedAt = Date.now();
logger.debug("list.start");
try {
const entries = await readdir(notesDir, {withFileTypes: true});
@@ -91,8 +95,10 @@ export async function listNotes(): Promise<ListNotesResult> {
notes.sort((a, b) => a.title.localeCompare(b.title));
logger.debug("list.done", {notes: notes.length, duration: logger.duration(startedAt)});
return {success: true, notes};
} catch (error) {
logger.error("list.failed", {duration: logger.duration(startedAt), error});
const errorMessage = error instanceof Error ? error.message : String(error);
return {success: false, error: `Failed to list notes: ${errorMessage}`};
}
@@ -101,7 +107,8 @@ export async function listNotes(): Promise<ListNotesResult> {
export async function getNoteContent(
args?: Record<string, unknown>,
): Promise<GetNoteContentResult> {
console.log("GET_NOTE_CONTENT; ARGS: ", args);
const startedAt = Date.now();
logger.debug("get_content.start", {args});
const fileName = asNonEmptyString(args?.fileName) ?? "";
if (!fileName.trim().length) {
@@ -118,6 +125,7 @@ export async function getNoteContent(
const normalizedFileName = path.basename(noteFilePath);
const relativePath = path.relative(path.dirname(notesRootFile), noteFilePath);
logger.debug("get_content.done", {fileName: normalizedFileName, relativePath, chars: content.length, duration: logger.duration(startedAt)});
return {
success: true,
fileName: normalizedFileName,
@@ -127,6 +135,7 @@ export async function getNoteContent(
content,
};
} catch (error) {
logger.error("list.failed", {duration: logger.duration(startedAt), error});
const errorMessage = error instanceof Error ? error.message : String(error);
return {success: false, error: `Failed to read note: ${errorMessage}`};
}
@@ -219,7 +228,8 @@ export const deleteNoteTool = {
export async function updateNoteContent(
args?: Record<string, unknown>,
): Promise<UpdateNoteContentResult> {
console.log("UPDATE_NOTE_CONTENT; ARGS: ", args);
const startedAt = Date.now();
logger.debug("update_content.start", {args});
const fileName = asNonEmptyString(args?.fileName) ?? "";
if (!fileName.trim().length) {
@@ -239,9 +249,11 @@ export async function updateNoteContent(
try {
await readFile(noteFilePath, "utf-8");
await writeFile(noteFilePath, content, "utf-8");
logger.debug("update_content.done", {fileName, filePath: noteFilePath, chars: content.length, duration: logger.duration(startedAt)});
return {success: true, filePath: noteFilePath};
} catch (error) {
logger.error("list.failed", {duration: logger.duration(startedAt), error});
const errorMessage = error instanceof Error ? error.message : String(error);
return {success: false, error: `Failed to update note: ${errorMessage}`};
}
@@ -250,7 +262,8 @@ export async function updateNoteContent(
export async function deleteNote(
args?: Record<string, unknown>,
): Promise<DeleteNoteResult> {
console.log("DELETE_NOTE; ARGS: ", args);
const startedAt = Date.now();
logger.debug("delete.start", {args});
const fileName = asNonEmptyString(args?.fileName) ?? "";
if (!fileName.trim().length) {
@@ -265,9 +278,11 @@ export async function deleteNote(
try {
await unlink(noteFilePath);
await removeNoteLinkFromRoot(noteFilePath);
logger.debug("delete.done", {fileName, filePath: noteFilePath, duration: logger.duration(startedAt)});
return {success: true, filePath: noteFilePath};
} catch (error) {
logger.error("list.failed", {duration: logger.duration(startedAt), error});
const errorMessage = error instanceof Error ? error.message : String(error);
return {success: false, error: `Failed to delete note: ${errorMessage}`};
}
+7 -1
View File
@@ -1,5 +1,8 @@
import {AiTool} from "../tool-types";
import axios from "axios";
import {toolsLogger} from "./tool-logger";
const logger = toolsLogger.child("market-rates");
export const getMarketRatesTool = {
type: "function",
@@ -59,11 +62,14 @@ export const marketRatesToolPrompt = [
].join("\n");
export async function getMarketRates(): Promise<unknown | undefined> {
const startedAt = Date.now();
try {
logger.info("start");
const response = await axios.get("https://apid.r00t.top/api/v2/currency/rates");
logger.debug("done", {duration: logger.duration(startedAt), status: response.status});
return response.data;
} catch (e: unknown) {
console.error("GET_MARKET_RATES", e);
logger.error("failed", {duration: logger.duration(startedAt), error: e});
return undefined;
}
}
+16 -12
View File
@@ -5,6 +5,9 @@ import path from "node:path";
import {AiTool} from "../tool-types";
import {Environment} from "../../common/environment";
import {randomUUID} from "node:crypto";
import {toolsLogger} from "./tool-logger";
const logger = toolsLogger.child("python-interpreter");
export const PYTHON_INTERPRETER_TOOL_NAME = "python_interpreter";
@@ -203,17 +206,17 @@ export async function runPythonInterpreter(
};
}
console.time("python.syntax");
const syntaxStartedAt = Date.now();
const syntax = await validatePythonSyntax(args.code, options);
console.timeEnd("python.syntax");
logger.debug("syntax.done", {duration: logger.duration(syntaxStartedAt), ok: syntax.ok});
if (!syntax.ok) {
return syntax;
}
console.time("python.execution");
const executionStartedAt = Date.now();
const result = await executePythonCode(args, options);
console.timeEnd("python.execution");
logger.debug("execution.done", {duration: logger.duration(executionStartedAt), ok: result.ok, phase: result.phase});
return result;
}
@@ -293,7 +296,8 @@ async function executePythonCode(
args: PythonInterpreterArgs,
options: PythonInterpreterOptions = {},
): Promise<PythonToolResult> {
console.log("EXECUTE_PYTHON_CODE", "ARGS: ", JSON.stringify(args), "; OPTIONS: ", JSON.stringify(options));
const startedAt = Date.now();
logger.info("execute.start", {args, options});
const pythonBinary =
options.pythonBinary ?? process.env.PYTHON_INTERPRETER_BINARY ?? "C:\\Users\\meloda\\Desktop\\AI_BOT\\.venv\\Scripts\\python.exe";
@@ -329,7 +333,7 @@ async function executePythonCode(
mode: 0o600,
});
console.log("EXECUTE_PYTHON_CODE", "SCRIPT FILE WRITTEN", new Date());
logger.debug("script.written", {tempDir, userScriptPath, runnerPath, duration: logger.duration(startedAt)});
const result = await runProcess({
command: pythonBinary,
@@ -346,10 +350,10 @@ async function executePythonCode(
},
});
console.log("EXECUTE_PYTHON_CODE", "RESULT ACHIEVED", new Date());
logger.debug("process.done", {duration: logger.duration(startedAt), exitCode: result.exitCode, timedOut: result.timedOut, outputTruncated: result.outputTruncated});
if (result.timedOut) {
console.log("EXECUTE_PYTHON_CODE", "RESULT ERROR TIMED OUT", new Date());
logger.warn("process.timeout", {duration: logger.duration(startedAt)});
return {
ok: false,
phase: "execution",
@@ -365,7 +369,7 @@ async function executePythonCode(
}
if (result.outputTruncated) {
console.log("EXECUTE_PYTHON_CODE", "RESULT ERROR TRUNCATED", new Date());
logger.warn("process.output_truncated", {duration: logger.duration(startedAt), stdoutChars: result.stdout.length, stderrChars: result.stderr.length});
return {
ok: false,
@@ -382,7 +386,7 @@ async function executePythonCode(
}
if (result.exitCode !== 0) {
console.log("EXECUTE_PYTHON_CODE", "RESULT ERROR EXIT CODE", new Date(), "\n", JSON.stringify(result, null, 2));
logger.warn("process.non_zero_exit", {duration: logger.duration(startedAt), result});
return {
ok: false,
@@ -398,7 +402,7 @@ async function executePythonCode(
};
}
console.log("EXECUTE_PYTHON_CODE", "RESULT NORMAL", new Date());
logger.debug("process.ok", {duration: logger.duration(startedAt)});
const {
artifacts,
@@ -420,7 +424,7 @@ async function executePythonCode(
skippedArtifacts,
};
} catch (error) {
console.log("EXECUTE_PYTHON_CODE", "RESULT ERROR", new Date());
logger.error("execute.failed", {duration: logger.duration(startedAt), error});
return {
ok: false,
phase: "internal",
+10 -2
View File
@@ -1,6 +1,9 @@
import {getToolHandlers} from "./registry";
import {normalizeToolArguments} from "./utils";
import {PYTHON_INTERPRETER_TOOL_NAME, PythonInterpreterInputFile, runPythonInterpreter} from "./python-interpretator";
import {toolsLogger} from "./tool-logger";
const logger = toolsLogger.child("runtime");
export type ToolRuntimeContext = {
pythonInputFiles?: PythonInterpreterInputFile[];
@@ -16,7 +19,9 @@ export async function executeToolCall(
args?: unknown,
context: ToolRuntimeContext = {},
): Promise<string> {
const startedAt = Date.now();
const handler = getToolHandlers()[name];
logger.info("execute.start", {name, args});
if (!handler) {
return stringifyToolResult({
@@ -35,14 +40,17 @@ export async function executeToolCall(
});
const s = stringifyToolResult(result);
console.log("PYTHON_INTERPRETER_STRING_RESULT", s);
logger.debug("execute.done", {name, chars: s.length, duration: logger.duration(startedAt)});
return s;
}
const result = await handler(normalizeToolArguments(args));
return stringifyToolResult(result);
const s = stringifyToolResult(result);
logger.debug("execute.done", {name, chars: s.length, duration: logger.duration(startedAt)});
return s;
} catch (error) {
logger.error("execute.failed", {name, duration: logger.duration(startedAt), error});
return stringifyToolResult({
error: error instanceof Error ? error.message : String(error),
});
+6 -1
View File
@@ -3,6 +3,9 @@ import path from "node:path";
import {readdir, readFile} from "node:fs/promises";
import {notesDir, notesRootFile} from "../../index";
import {asNonEmptyString} from "./utils";
import {toolsLogger} from "./tool-logger";
const logger = toolsLogger.child("search-notes");
export type SearchNoteMatchedField = "file_name" | "title" | "content";
@@ -51,7 +54,8 @@ export const searchNotesTool = {
export async function searchNotes(
args?: Record<string, unknown>,
): Promise<SearchNotesResult> {
console.log("SEARCH_NOTES; ARGS: ", args);
const startedAt = Date.now();
logger.debug("start", {args});
const query = asNonEmptyString(args?.query) ?? "";
if (!query.trim().length) {
@@ -127,6 +131,7 @@ export async function searchNotes(
.sort((a, b) => b.score - a.score)
.slice(0, limit);
logger.debug("done", {query, limit, results: results.length, duration: logger.duration(startedAt)});
return {success: true, results};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
+5 -2
View File
@@ -5,6 +5,9 @@ import {notesRootFile} from "../../index";
import {asNonEmptyString} from "./utils";
import {buildSafeNoteFilePath} from "./list-notes";
import z from "zod";
import {toolsLogger} from "./tool-logger";
const logger = toolsLogger.child("get-note-file");
export type NoteFileAttachment = {
type: "local_file";
@@ -64,7 +67,7 @@ export const getNoteFileTool = {
export async function getNoteFile(
args?: Record<string, unknown>,
): Promise<GetNoteFileResult> {
console.log("GET_NOTE_FILE; ARGS: ", args);
logger.debug("start", {args});
const fileName = asNonEmptyString(args?.fileName) ?? "";
if (!fileName.trim().length) {
@@ -100,7 +103,7 @@ export async function getNoteFile(
},
};
console.log("GET_NOTE_FILE; RESULT: ", result);
logger.debug("done", {fileName: result.attachment.fileName, relativePath: result.attachment.relativePath, sizeBytes: result.attachment.sizeBytes});
return result;
} catch (error) {
+8 -3
View File
@@ -1,5 +1,8 @@
import {Ollama} from "ollama";
import {z} from "zod";
import {toolsLogger} from "./tool-logger";
const logger = toolsLogger.child("utils");
export function asNonEmptyString(value: unknown): string | undefined {
return typeof value === "string" && value.trim().length > 0
@@ -79,14 +82,15 @@ export async function unloadAllOllamaModels(ollama: Ollama, exceptFor?: string[]
);
await Promise.all(unloadPromises);
console.log("All models have been requested to unload" + (exceptFor?.length ? ` except for [${exceptFor?.join(", ")}].` : "."));
logger.info("ollama.unload_all.done", {count: modelsToUnload.length, exceptFor});
} catch (error) {
console.error("Error unloading models:", error);
logger.error("ollama.unload_all.failed", {exceptFor, error});
}
}
export async function loadOllamaModel(model: string, ollama: Ollama, contextLength: number): Promise<boolean> {
try {
logger.info("ollama.load.start", {model, contextLength});
await ollama.generate({
model: model,
stream: false,
@@ -95,9 +99,10 @@ export async function loadOllamaModel(model: string, ollama: Ollama, contextLeng
num_ctx: contextLength
}
});
logger.info("ollama.load.done", {model, contextLength});
return true;
} catch (e: unknown) {
console.error("Error loading Ollama model:", model);
logger.error("ollama.load.failed", {model, contextLength, error: e});
return false;
}
}
+9 -4
View File
@@ -1,4 +1,7 @@
import axios from "axios";
import {toolsLogger} from "./tool-logger";
const logger = toolsLogger.child("weather");
import {Environment} from "../../common/environment";
import {logError} from "../../util/utils";
import {AiTool} from "../tool-types";
@@ -43,7 +46,8 @@ export const weatherToolPrompt = [
].join("\n");
export async function getWeather(args?: Record<string, unknown>): Promise<Record<string, unknown> | null> {
console.log("getWeather()");
const startedAt = Date.now();
logger.info("start", {args});
try {
const city = asNonEmptyString(args?.city);
const lang = asNonEmptyString(args?.lang);
@@ -61,7 +65,7 @@ export async function getWeather(args?: Record<string, unknown>): Promise<Record
appid: apiKey,
},
})).data[0];
console.log("GEOCODE_RESPONSE", geocodeResponse);
logger.debug("geocode.done", {city, country: geocodeResponse?.country, hasResult: !!geocodeResponse, geocodeResponse});
if (!geocodeResponse) {
return {
ok: false,
@@ -83,7 +87,7 @@ export async function getWeather(args?: Record<string, unknown>): Promise<Record
...(lang ? {lang} : {}),
},
})).data;
console.log("RESPONSE: getWeather(lang=" + lang + "): ", response);
logger.debug("weather_api.done", {city, country: geocodeResponse.country, lang, units: "metric", hasResponse: !!response});
const main = response.main;
const sys = response.sys;
@@ -138,9 +142,10 @@ export async function getWeather(args?: Record<string, unknown>): Promise<Record
},
};
} catch (e: unknown) {
logger.error("failed", {duration: logger.duration(startedAt), error: e});
logError(e);
return null;
} finally {
console.log("END: getWeather()");
logger.debug("done", {duration: logger.duration(startedAt)});
}
}