shitton
This commit is contained in:
@@ -4,12 +4,12 @@ import {toolsLogger} from "./tool-logger";
|
||||
|
||||
const logger = toolsLogger.child("market-rates");
|
||||
|
||||
export const GET_FINANCIAL_MARKET_DATA = "get_financial_market_data";
|
||||
export const GET_FINANCIAL_MARKET_DATA_TOOL_NAME = "get_financial_market_data";
|
||||
|
||||
export const getFinancialMarketData = {
|
||||
type: "function",
|
||||
function: {
|
||||
name: GET_FINANCIAL_MARKET_DATA,
|
||||
name: GET_FINANCIAL_MARKET_DATA_TOOL_NAME,
|
||||
description:
|
||||
"Retrieve the latest exchange rates for supported currency, crypto, and precious metal pairs, including 24-hour change data when available. Supported pairs: USD/RUB, USD/EUR, USD/KZT, USD/UAH, USD/BYN, USD/GBP, USD/CNY, TON/USD, BTC/USD, ETH/USD, SOL/USD, and XAU/USD. Use this tool when the user asks for current rates, currency conversion, crypto prices, gold price, or recent 24-hour movement. This tool takes no parameters.",
|
||||
parameters: {
|
||||
@@ -22,9 +22,9 @@ export const getFinancialMarketData = {
|
||||
|
||||
export const financialMarketDataToolPrompt = [
|
||||
"Currency rates tool rules:",
|
||||
`- Use \`${GET_FINANCIAL_MARKET_DATA}\` whenever the answer depends on current exchange rates, crypto prices, or gold price.`,
|
||||
`- Use \`${GET_FINANCIAL_MARKET_DATA}\` when the user asks whether a supported asset went up or down recently.`,
|
||||
`- Use \`${GET_FINANCIAL_MARKET_DATA}\` when the user asks for the 24-hour change, percentage change, or movement direction for a supported pair.`,
|
||||
`- Use \`${GET_FINANCIAL_MARKET_DATA_TOOL_NAME}\` whenever the answer depends on current exchange rates, crypto prices, or gold price.`,
|
||||
`- Use \`${GET_FINANCIAL_MARKET_DATA_TOOL_NAME}\` when the user asks whether a supported asset went up or down recently.`,
|
||||
`- Use \`${GET_FINANCIAL_MARKET_DATA_TOOL_NAME}\` when the user asks for the 24-hour change, percentage change, or movement direction for a supported pair.`,
|
||||
"- Never guess current rates, prices, or 24-hour changes. Call the tool first.",
|
||||
"- Do not use this tool for unsupported pairs unless the user asks about one of the supported pairs listed below.",
|
||||
"- Do not use this tool for historical rates beyond the provided 24-hour comparison.",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import {AiTool} from "../tool-types";
|
||||
import path from "node:path";
|
||||
import {readdir, readFile, unlink, writeFile} from "node:fs/promises";
|
||||
import {readdir, readFile, stat, unlink, writeFile} from "node:fs/promises";
|
||||
import {notesDir, notesRootFile} from "../../index";
|
||||
import {asNonEmptyString} from "./utils";
|
||||
import {toolsLogger} from "./tool-logger";
|
||||
import {z} from "zod";
|
||||
|
||||
const logger = toolsLogger.child("notes");
|
||||
|
||||
@@ -338,3 +339,110 @@ async function removeNoteLinkFromRoot(noteFilePath: string): Promise<void> {
|
||||
function escapeRegExp(value: string): string {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
export type NoteFileAttachment = {
|
||||
type: "local_file";
|
||||
fileName: string;
|
||||
// filePath: string;
|
||||
relativePath: string;
|
||||
mimeType: "text/markdown";
|
||||
sizeBytes: number;
|
||||
};
|
||||
|
||||
export type GetNoteFileResult =
|
||||
| {
|
||||
success: true;
|
||||
attachment: NoteFileAttachment;
|
||||
} | { success: false; error: string };
|
||||
|
||||
export const NoteFileAttachmentSchema = z.object({
|
||||
type: z.literal("local_file"),
|
||||
fileName: z.string(),
|
||||
// filePath: z.string(),
|
||||
relativePath: z.string(),
|
||||
mimeType: z.literal("text/markdown"),
|
||||
sizeBytes: z.number(),
|
||||
});
|
||||
|
||||
export const GetNoteFileResultSchema = z.discriminatedUnion("success", [
|
||||
z.object({
|
||||
success: z.literal(true),
|
||||
attachment: NoteFileAttachmentSchema,
|
||||
}),
|
||||
z.object({
|
||||
success: z.literal(false),
|
||||
error: z.string(),
|
||||
}),
|
||||
]);
|
||||
|
||||
export const sendNoteAsFileTool = {
|
||||
type: "function",
|
||||
function: {
|
||||
name: "send_note_as_file",
|
||||
description:
|
||||
"Prepare a Markdown note file to be sent to the user as a .md attachment. Returns a local file descriptor that the host application should use to upload or send the file.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
fileName: {
|
||||
type: "string",
|
||||
description:
|
||||
"The file name of the note to send. It may be provided with or without the .md extension. Must not contain forbidden or unsafe characters such as /, \\, :, *, ?, \", <, >, |, or control characters.",
|
||||
},
|
||||
},
|
||||
required: ["fileName"],
|
||||
},
|
||||
},
|
||||
} satisfies AiTool;
|
||||
|
||||
export async function sendNoteAsFile(
|
||||
args?: Record<string, unknown>,
|
||||
): Promise<GetNoteFileResult> {
|
||||
logger.debug("start", {args});
|
||||
|
||||
const fileName = asNonEmptyString(args?.fileName) ?? "";
|
||||
if (!fileName.trim().length) {
|
||||
return {success: false, error: "No file name provided"};
|
||||
}
|
||||
|
||||
const noteFilePath = buildSafeNoteFilePath(fileName);
|
||||
if (!noteFilePath) {
|
||||
return {success: false, error: "Invalid or unsafe file name provided"};
|
||||
}
|
||||
|
||||
try {
|
||||
// Проверяем, что файл существует и действительно читается.
|
||||
await readFile(noteFilePath, "utf-8");
|
||||
|
||||
const fileStat = await stat(noteFilePath);
|
||||
if (!fileStat.isFile()) {
|
||||
return {success: false, error: "Note path is not a file"};
|
||||
}
|
||||
|
||||
const normalizedFileName = path.basename(noteFilePath);
|
||||
const relativePath = path.relative(path.dirname(notesRootFile), noteFilePath);
|
||||
|
||||
const result: GetNoteFileResult = {
|
||||
success: true,
|
||||
attachment: {
|
||||
type: "local_file",
|
||||
fileName: normalizedFileName,
|
||||
// filePath: noteFilePath,
|
||||
relativePath,
|
||||
mimeType: "text/markdown",
|
||||
sizeBytes: fileStat.size,
|
||||
},
|
||||
};
|
||||
|
||||
logger.debug("done", {
|
||||
fileName: result.attachment.fileName,
|
||||
relativePath: result.attachment.relativePath,
|
||||
sizeBytes: result.attachment.sizeBytes
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return {success: false, error: `Failed to prepare note file: ${errorMessage}`};
|
||||
}
|
||||
}
|
||||
+46
-43
@@ -7,7 +7,7 @@ import {ToolHandler} from "./types";
|
||||
import {getWeather, getWeatherTool} from "./weather";
|
||||
import {
|
||||
financialMarketDataToolPrompt,
|
||||
GET_FINANCIAL_MARKET_DATA,
|
||||
GET_FINANCIAL_MARKET_DATA_TOOL_NAME,
|
||||
getFinancialMarketData,
|
||||
getMarketRates
|
||||
} from "./market-rates";
|
||||
@@ -38,33 +38,45 @@ import {
|
||||
getNoteContentTool,
|
||||
listNotes,
|
||||
listNotesTool,
|
||||
sendNoteAsFile,
|
||||
sendNoteAsFileTool,
|
||||
updateNoteContent,
|
||||
updateNoteContentTool
|
||||
} from "./list-notes";
|
||||
import {sendNoteAsFileTool, sendNoteAsFile} from "./send-note-as-file";
|
||||
} from "./notes";
|
||||
import {searchNotes, searchNotesTool} from "./search-notes";
|
||||
|
||||
export const getTools = () => {
|
||||
export const defaultFileTools: AiTool[] = [
|
||||
getCurrentDateTimeTool,
|
||||
getFinancialMarketData,
|
||||
]
|
||||
|
||||
export const fileSystemTools: AiTool[] = [
|
||||
readFileTool,
|
||||
listDirectoryTool,
|
||||
createFileTool,
|
||||
createDirectoryTool,
|
||||
updateFileTool,
|
||||
renamePathTool,
|
||||
copyPathTool,
|
||||
deletePathTool,
|
||||
];
|
||||
|
||||
export const notesFileTools: AiTool[] = [
|
||||
createNoteTool,
|
||||
listNotesTool,
|
||||
getNoteContentTool,
|
||||
updateNoteContentTool,
|
||||
deleteNoteTool,
|
||||
sendNoteAsFileTool,
|
||||
searchNotesTool
|
||||
]
|
||||
|
||||
export const getTools = (forCreator?: boolean) => {
|
||||
const tools: AiTool[] = [
|
||||
getCurrentDateTimeTool,
|
||||
getFinancialMarketData,
|
||||
createNoteTool,
|
||||
listNotesTool,
|
||||
getNoteContentTool,
|
||||
updateNoteContentTool,
|
||||
deleteNoteTool,
|
||||
sendNoteAsFileTool,
|
||||
searchNotesTool
|
||||
...defaultFileTools,
|
||||
...notesFileTools
|
||||
];
|
||||
|
||||
if (Environment.ENABLE_PYTHON_INTERPRETER) {
|
||||
tools.push(pythonInterpreterTool);
|
||||
}
|
||||
|
||||
if (Environment.ENABLE_UNSAFE_EVAL) {
|
||||
tools.push(shellExecuteTool);
|
||||
}
|
||||
|
||||
if (Environment.BRAVE_SEARCH_API_KEY) {
|
||||
tools.push(braveSearchTool);
|
||||
}
|
||||
@@ -73,30 +85,21 @@ export const getTools = () => {
|
||||
tools.push(getWeatherTool);
|
||||
}
|
||||
|
||||
if (Environment.FILE_TOOLS_ROOT_DIR && Environment.ENABLE_FS_TOOLS) {
|
||||
tools.push(
|
||||
readFileTool,
|
||||
listDirectoryTool,
|
||||
createFileTool,
|
||||
createDirectoryTool,
|
||||
updateFileTool,
|
||||
renamePathTool,
|
||||
copyPathTool,
|
||||
deletePathTool,
|
||||
);
|
||||
if (forCreator) {
|
||||
if (Environment.ENABLE_PYTHON_INTERPRETER) {
|
||||
tools.push(pythonInterpreterTool);
|
||||
}
|
||||
|
||||
if (Environment.ENABLE_UNSAFE_EVAL) {
|
||||
tools.push(shellExecuteTool);
|
||||
}
|
||||
|
||||
if (Environment.FILE_TOOLS_ROOT_DIR && Environment.ENABLE_FS_TOOLS) {
|
||||
tools.push(...fileSystemTools);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return tools;
|
||||
// return [
|
||||
// createNoteTool,
|
||||
// listNotesTool,
|
||||
// getNoteContentTool,
|
||||
// updateNoteContentTool,
|
||||
// deleteNoteTool,
|
||||
// getNoteFileTool,
|
||||
// searchNotesTool
|
||||
// ];
|
||||
};
|
||||
|
||||
export const getToolHandlers = () => {
|
||||
@@ -162,7 +165,7 @@ export function getToolPrompts(toolNames: string[]): string[] {
|
||||
|
||||
for (const toolName of toolNames) {
|
||||
switch (toolName) {
|
||||
case GET_FINANCIAL_MARKET_DATA:
|
||||
case GET_FINANCIAL_MARKET_DATA_TOOL_NAME:
|
||||
prompts.push(financialMarketDataToolPrompt);
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
import {AiTool} from "../tool-types";
|
||||
import path from "node:path";
|
||||
import {readFile, stat} from "node:fs/promises";
|
||||
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";
|
||||
fileName: string;
|
||||
// filePath: string;
|
||||
relativePath: string;
|
||||
mimeType: "text/markdown";
|
||||
sizeBytes: number;
|
||||
};
|
||||
|
||||
export type GetNoteFileResult =
|
||||
| {
|
||||
success: true;
|
||||
attachment: NoteFileAttachment;
|
||||
} | { success: false; error: string };
|
||||
|
||||
export const NoteFileAttachmentSchema = z.object({
|
||||
type: z.literal("local_file"),
|
||||
fileName: z.string(),
|
||||
// filePath: z.string(),
|
||||
relativePath: z.string(),
|
||||
mimeType: z.literal("text/markdown"),
|
||||
sizeBytes: z.number(),
|
||||
});
|
||||
|
||||
export const GetNoteFileResultSchema = z.discriminatedUnion("success", [
|
||||
z.object({
|
||||
success: z.literal(true),
|
||||
attachment: NoteFileAttachmentSchema,
|
||||
}),
|
||||
z.object({
|
||||
success: z.literal(false),
|
||||
error: z.string(),
|
||||
}),
|
||||
]);
|
||||
|
||||
export const sendNoteAsFileTool = {
|
||||
type: "function",
|
||||
function: {
|
||||
name: "send_note_as_file",
|
||||
description:
|
||||
"Prepare a Markdown note file to be sent to the user as a .md attachment. Returns a local file descriptor that the host application should use to upload or send the file.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
fileName: {
|
||||
type: "string",
|
||||
description:
|
||||
"The file name of the note to send. It may be provided with or without the .md extension. Must not contain forbidden or unsafe characters such as /, \\, :, *, ?, \", <, >, |, or control characters.",
|
||||
},
|
||||
},
|
||||
required: ["fileName"],
|
||||
},
|
||||
},
|
||||
} satisfies AiTool;
|
||||
|
||||
export async function sendNoteAsFile(
|
||||
args?: Record<string, unknown>,
|
||||
): Promise<GetNoteFileResult> {
|
||||
logger.debug("start", {args});
|
||||
|
||||
const fileName = asNonEmptyString(args?.fileName) ?? "";
|
||||
if (!fileName.trim().length) {
|
||||
return {success: false, error: "No file name provided"};
|
||||
}
|
||||
|
||||
const noteFilePath = buildSafeNoteFilePath(fileName);
|
||||
if (!noteFilePath) {
|
||||
return {success: false, error: "Invalid or unsafe file name provided"};
|
||||
}
|
||||
|
||||
try {
|
||||
// Проверяем, что файл существует и действительно читается.
|
||||
await readFile(noteFilePath, "utf-8");
|
||||
|
||||
const fileStat = await stat(noteFilePath);
|
||||
if (!fileStat.isFile()) {
|
||||
return {success: false, error: "Note path is not a file"};
|
||||
}
|
||||
|
||||
const normalizedFileName = path.basename(noteFilePath);
|
||||
const relativePath = path.relative(path.dirname(notesRootFile), noteFilePath);
|
||||
|
||||
const result: GetNoteFileResult = {
|
||||
success: true,
|
||||
attachment: {
|
||||
type: "local_file",
|
||||
fileName: normalizedFileName,
|
||||
// filePath: noteFilePath,
|
||||
relativePath,
|
||||
mimeType: "text/markdown",
|
||||
sizeBytes: fileStat.size,
|
||||
},
|
||||
};
|
||||
|
||||
logger.debug("done", {fileName: result.attachment.fileName, relativePath: result.attachment.relativePath, sizeBytes: result.attachment.sizeBytes});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return {success: false, error: `Failed to prepare note file: ${errorMessage}`};
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import {getFinancialMarketData} from "./tools/market-rates";
|
||||
import {getWeatherTool} from "./tools/weather";
|
||||
import {loadOllamaModel, unloadAllOllamaModels} from "./tools/utils";
|
||||
import {createOllamaClient} from "./ai-runtime-target";
|
||||
import {GetNoteFileResult, GetNoteFileResultSchema, sendNoteAsFileTool} from "./tools/send-note-as-file";
|
||||
import {aiLog, aiLogDuration, aiLogMessageIdentity, aiLogProviderTarget, aiLogToolCall} from "../logging/ai-logger";
|
||||
|
||||
import {
|
||||
@@ -42,7 +41,15 @@ import {
|
||||
import {latestUserTextFromOllamaMessages, OllamaToolRanker} from "./unified-ai-runner.tool-ranker";
|
||||
import {getToolPrompts} from "./tools/registry";
|
||||
import {createNoteTool} from "./tools/create-note";
|
||||
import {deleteNoteTool, getNoteContentTool, listNotesTool, updateNoteContentTool} from "./tools/list-notes";
|
||||
import {
|
||||
deleteNoteTool,
|
||||
getNoteContentTool,
|
||||
GetNoteFileResult,
|
||||
GetNoteFileResultSchema,
|
||||
listNotesTool,
|
||||
sendNoteAsFileTool,
|
||||
updateNoteContentTool
|
||||
} from "./tools/notes";
|
||||
import {searchNotesTool} from "./tools/search-notes";
|
||||
|
||||
export async function runOllama(
|
||||
|
||||
Reference in New Issue
Block a user