refactor: improve type safety and add Zod validation for function arguments

- Change function arguments type from `Record<string, object>` to `Record<string, unknown>`
- Add Zod schemas for all AI function arguments validation
- Improve TypeScript types across modals and services
- Add ESLint disable comments for intentional exceptions
- Fix async/await handling in modal and view lifecycle methods
- Update dependencies (@typescript-eslint 8.46.4, rollup 4.53.2, zod 4.1.12)
This commit is contained in:
Andrew Beal 2025-11-10 19:41:17 +00:00
parent 235609dc4c
commit 37db1a8908
19 changed files with 432 additions and 286 deletions

View file

@ -1,10 +1,10 @@
// platform agnostic function call class used to execute the requested function
export class AIFunctionCall {
public readonly name: string;
public readonly arguments: Record<string, object>;
public readonly arguments: Record<string, unknown>;
public readonly toolId?: string;
constructor(name: string, args: Record<string, object>, toolId?: string) {
constructor(name: string, args: Record<string, unknown>, toolId?: string) {
this.name = name;
this.arguments = args;
this.toolId = toolId;

View file

@ -13,7 +13,7 @@ import type { ConversationContent } from "Conversations/ConversationContent";
import { Role } from "Enums/Role";
import type { SettingsService } from "Services/SettingsService";
import type { RawMessageStreamEvent, ContentBlockParam, Tool } from '@anthropic-ai/sdk/resources/messages';
import type { StoredFunctionCall, StoredFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionTypes";
import type { StoredFunctionCall, StoredFunctionResponse } from "AIClasses/Schemas/AIFunctionTypes";
export class Claude implements IAIClass {

View file

@ -1,40 +0,0 @@
// Shared type definitions for AI function calls and responses across all providers
/**
* JSON Schema property definition supporting nested structures
* Used for defining function parameters in a provider-agnostic way
*/
export type JSONSchemaProperty = {
type?: string;
description?: string;
items?: JSONSchemaProperty;
properties?: Record<string, JSONSchemaProperty>;
required?: string[];
enum?: string[];
[key: string]: unknown;
};
/**
* Stored function call format used across all AI providers
* This is the format saved to conversation history when a function is called
*/
export interface StoredFunctionCall {
functionCall: {
id: string;
name: string;
args: Record<string, unknown>;
};
}
/**
* Stored function response format used across all AI providers
* This is the format saved to conversation history when a function returns
* Note: The actual stored data includes a 'name' field (see AIFunctionResponse.toConversationString)
*/
export interface StoredFunctionResponse {
id: string;
functionResponse: {
name: string;
response: unknown;
};
}

View file

@ -1,4 +1,4 @@
import type { JSONSchemaProperty } from "./AIFunctionTypes";
import type { JSONSchemaProperty } from "../Schemas/AIFunctionTypes";
// platform agnostic function definition used to present function calls in an API call
export interface IAIFunctionDefinition {

View file

@ -13,7 +13,7 @@ import type { AIFunctionDefinitions } from "AIClasses/FunctionDefinitions/AIFunc
import { isValidJson } from "Helpers/Helpers";
import type { ConversationContent } from "Conversations/ConversationContent";
import type { SettingsService } from "Services/SettingsService";
import type { StoredFunctionCall, StoredFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionTypes";
import type { StoredFunctionCall, StoredFunctionResponse } from "AIClasses/Schemas/AIFunctionTypes";
import type { Candidate, Part, FunctionDeclaration } from "@google/genai";
import { FinishReason } from "@google/genai";

View file

@ -12,7 +12,7 @@ import type { AIFunctionDefinitions } from "AIClasses/FunctionDefinitions/AIFunc
import { Role } from "Enums/Role";
import { isValidJson } from "Helpers/Helpers";
import type { SettingsService } from "Services/SettingsService";
import type { StoredFunctionCall, StoredFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionTypes";
import type { StoredFunctionCall, StoredFunctionResponse } from "AIClasses/Schemas/AIFunctionTypes";
import type { ChatCompletionChunk, ChatCompletionTool } from "openai/resources/chat/completions";
interface IToolCallAccumulator {

View file

@ -0,0 +1,48 @@
/** Zod schemas for runtime validation of AI function arguments **/
import { z } from "zod";
// Zod schemas for AI function arguments
// These provide runtime validation of data received from AI providers
export const SearchVaultFilesArgsSchema = z.object({
search_terms: z.array(z.string()),
user_message: z.string()
});
export const ReadVaultFilesArgsSchema = z.object({
file_paths: z.array(z.string()),
user_message: z.string()
});
export const WriteVaultFileArgsSchema = z.object({
file_path: z.string(),
content: z.string(),
user_message: z.string()
});
export const DeleteVaultFilesArgsSchema = z.object({
file_paths: z.array(z.string()),
user_message: z.string(),
confirm_deletion: z.boolean()
});
export const MoveVaultFilesArgsSchema = z.object({
source_paths: z.array(z.string()),
destination_paths: z.array(z.string()),
user_message: z.string()
});
export const ListVaultFilesArgsSchema = z.object({
path: z.string(),
recursive: z.boolean(),
user_message: z.string()
});
// Infer TypeScript types from schemas
export type SearchVaultFilesArgs = z.infer<typeof SearchVaultFilesArgsSchema>;
export type ReadVaultFilesArgs = z.infer<typeof ReadVaultFilesArgsSchema>;
export type WriteVaultFileArgs = z.infer<typeof WriteVaultFileArgsSchema>;
export type DeleteVaultFilesArgs = z.infer<typeof DeleteVaultFilesArgsSchema>;
export type MoveVaultFilesArgs = z.infer<typeof MoveVaultFilesArgsSchema>;
export type ListVaultFilesArgs = z.infer<typeof ListVaultFilesArgsSchema>;

View file

@ -0,0 +1,33 @@
/** Shared type definitions for AI function calls and responses across all providers **/
// JSON Schema property definition supporting nested structures
// Used for defining function parameters in a provider-agnostic way
export type JSONSchemaProperty = {
type?: string;
description?: string;
items?: JSONSchemaProperty;
properties?: Record<string, JSONSchemaProperty>;
required?: string[];
enum?: string[];
[key: string]: unknown;
};
//Stored function call format used across all AI providers
// This is the format saved to conversation history when a function is called
export interface StoredFunctionCall {
functionCall: {
id: string;
name: string;
args: Record<string, unknown>;
};
}
// Stored function response format used across all AI providers
// This is the format saved to conversation history when a function returns
export interface StoredFunctionResponse {
id: string;
functionResponse: {
name: string;
response: unknown;
};
}

View file

@ -2,15 +2,17 @@ import type VaultkeeperAIPlugin from "main";
export function openPluginSettings(plugin: VaultkeeperAIPlugin) {
// @ts-ignore - accessing internal API
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
plugin.app.setting.open();
// @ts-ignore - accessing internal API
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
plugin.app.setting.openTabById(plugin.manifest.id);
}
export function isValidJson(str: string): boolean {
try {
JSON.parse(str);
} catch (e) {
} catch {
return false;
}
return true;

View file

@ -27,7 +27,7 @@ export class ConversationHistoryModal extends Modal {
private readonly fileSystemService: FileSystemService = Resolve<FileSystemService>(Services.FileSystemService);
private readonly chatService: ChatService = Resolve<ChatService>(Services.ChatService);
private component: Record<string, any> | null = null;
private component: ReturnType<typeof mount> | null = null;
private items: IListItem[];
private conversations: Conversation[];
public onModalClose?: () => void;
@ -37,27 +37,31 @@ export class ConversationHistoryModal extends Modal {
super(plugin.app);
}
override async open() {
private async loadConversations() {
this.conversations = await this.conversationFileSystemService.getAllConversations();
this.items = this.conversations
.sort((a, b) => b.updated.getTime() - a.updated.getTime())
.map((conversation) => {
const filePath = this.conversationFileSystemService.generateConversationPath(conversation);
return {
id: filePath,
date: dateToString(conversation.created, false),
updated: conversation.updated,
title: conversation.title,
selected: false,
filePath: filePath
};
});
.sort((a, b) => b.updated.getTime() - a.updated.getTime())
.map((conversation) => {
const filePath = this.conversationFileSystemService.generateConversationPath(conversation);
return {
id: filePath,
date: dateToString(conversation.created, false),
updated: conversation.updated,
title: conversation.title,
selected: false,
filePath: filePath
};
});
super.open();
// Update the component with loaded items if it's already mounted
if (this.component) {
this.component.items = this.items;
}
}
onOpen() {
void this.loadConversations();
const { contentEl, modalEl, containerEl } = this;
containerEl.addClass(Selector.ConversationHistoryModal);
@ -115,7 +119,7 @@ export class ConversationHistoryModal extends Modal {
onClose() {
if (this.component) {
unmount(this.component);
void unmount(this.component);
this.component = null;
}

View file

@ -8,7 +8,8 @@ import { Selector } from 'Enums/Selector';
export class HelpModal extends Modal {
private component: Record<string, any> | null = null;
private component: ReturnType<typeof mount> | null = null;
private initialTopic?: number;
public constructor() {
const plugin = Resolve<VaultkeeperAIPlugin>(Services.VaultkeeperAIPlugin);
@ -25,19 +26,19 @@ export class HelpModal extends Modal {
target: contentEl,
props: {
onClose: () => this.close(),
initialTopic: (this as any).initialTopic
initialTopic: this.initialTopic
}
});
}
public open(initialTopic?: number): void {
(this as any).initialTopic = initialTopic;
this.initialTopic = initialTopic;
super.open();
}
onClose() {
if (this.component) {
unmount(this.component);
void unmount(this.component);
this.component = null;
}

View file

@ -6,6 +6,14 @@ import { AIFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionResp
import type { AIFunctionCall } from "AIClasses/AIFunctionCall";
import type { ISearchMatch } from "../Helpers/SearchTypes";
import { normalizePath, TAbstractFile, TFile } from "obsidian";
import {
SearchVaultFilesArgsSchema,
ReadVaultFilesArgsSchema,
WriteVaultFileArgsSchema,
DeleteVaultFilesArgsSchema,
MoveVaultFilesArgsSchema,
ListVaultFilesArgsSchema
} from "AIClasses/Schemas/AIFunctionSchemas";
export class AIFunctionService {
@ -13,23 +21,77 @@ export class AIFunctionService {
public async performAIFunction(functionCall: AIFunctionCall): Promise<AIFunctionResponse> {
switch (functionCall.name) {
case AIFunction.SearchVaultFiles:
return new AIFunctionResponse(functionCall.name, await this.searchVaultFiles(functionCall.arguments.search_terms), functionCall.toolId);
case AIFunction.SearchVaultFiles: {
const parseResult = SearchVaultFilesArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
return new AIFunctionResponse(
functionCall.name,
{ error: `Invalid arguments for SearchVaultFiles: ${parseResult.error.message}` },
functionCall.toolId
);
}
return new AIFunctionResponse(functionCall.name, await this.searchVaultFiles(parseResult.data.search_terms), functionCall.toolId);
}
case AIFunction.ReadVaultFiles:
return new AIFunctionResponse(functionCall.name, await this.readVaultFiles(functionCall.arguments.file_paths), functionCall.toolId);
case AIFunction.ReadVaultFiles: {
const parseResult = ReadVaultFilesArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
return new AIFunctionResponse(
functionCall.name,
{ error: `Invalid arguments for ReadVaultFiles: ${parseResult.error.message}` },
functionCall.toolId
);
}
return new AIFunctionResponse(functionCall.name, await this.readVaultFiles(parseResult.data.file_paths), functionCall.toolId);
}
case AIFunction.WriteVaultFile:
return new AIFunctionResponse(functionCall.name, await this.writeVaultFile(functionCall.arguments.file_path, functionCall.arguments.content), functionCall.toolId);
case AIFunction.WriteVaultFile: {
const parseResult = WriteVaultFileArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
return new AIFunctionResponse(
functionCall.name,
{ error: `Invalid arguments for WriteVaultFile: ${parseResult.error.message}` },
functionCall.toolId
);
}
return new AIFunctionResponse(functionCall.name, await this.writeVaultFile(parseResult.data.file_path, parseResult.data.content), functionCall.toolId);
}
case AIFunction.DeleteVaultFiles:
return new AIFunctionResponse(functionCall.name, await this.deleteVaultFiles(functionCall.arguments.file_paths, functionCall.arguments.confirm_deletion), functionCall.toolId);
case AIFunction.DeleteVaultFiles: {
const parseResult = DeleteVaultFilesArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
return new AIFunctionResponse(
functionCall.name,
{ error: `Invalid arguments for DeleteVaultFiles: ${parseResult.error.message}` },
functionCall.toolId
);
}
return new AIFunctionResponse(functionCall.name, await this.deleteVaultFiles(parseResult.data.file_paths, parseResult.data.confirm_deletion), functionCall.toolId);
}
case AIFunction.MoveVaultFiles:
return new AIFunctionResponse(functionCall.name, await this.moveVaultFiles(functionCall.arguments.source_paths, functionCall.arguments.destination_paths), functionCall.toolId);
case AIFunction.MoveVaultFiles: {
const parseResult = MoveVaultFilesArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
return new AIFunctionResponse(
functionCall.name,
{ error: `Invalid arguments for MoveVaultFiles: ${parseResult.error.message}` },
functionCall.toolId
);
}
return new AIFunctionResponse(functionCall.name, await this.moveVaultFiles(parseResult.data.source_paths, parseResult.data.destination_paths), functionCall.toolId);
}
case AIFunction.ListVaultFiles:
return new AIFunctionResponse(functionCall.name, await this.ListVaultFiles(functionCall.arguments.path, functionCall.arguments.recursive), functionCall.toolId);
case AIFunction.ListVaultFiles: {
const parseResult = ListVaultFilesArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
return new AIFunctionResponse(
functionCall.name,
{ error: `Invalid arguments for ListVaultFiles: ${parseResult.error.message}` },
functionCall.toolId
);
}
return new AIFunctionResponse(functionCall.name, await this.ListVaultFiles(parseResult.data.path, parseResult.data.recursive), functionCall.toolId);
}
// this is only used by gemini
case AIFunction.RequestWebSearch:

View file

@ -64,22 +64,23 @@ export class ChatService {
this.abortController = new AbortController();
conversation.contents.push(new ConversationContent(Role.User, userRequest, formattedRequest));
this.conversationService.saveConversation(conversation);
await this.conversationService.saveConversation(conversation);
callbacks.onSubmit();
callbacks.onStreamingUpdate(null);
if (conversation.contents.length === 1) {
this.onNameChanged?.(conversation.title); // on change for initial conversation name
this.namingService.requestName(conversation, formattedRequest, this.onNameChanged, this.abortController);
await this.namingService.requestName(conversation, formattedRequest, this.onNameChanged, this.abortController);
}
// Process AI responses and function calls
let response = await this.streamRequestResponse(conversation, allowDestructiveActions, callbacks);
while (response.functionCall || response.shouldContinue) {
if (response.functionCall) {
if (response.functionCall.arguments.user_message) {
callbacks.onThoughtUpdate(response.functionCall.arguments.user_message);
const userMessage = response.functionCall.arguments.user_message;
if (userMessage && typeof userMessage === "string") {
callbacks.onThoughtUpdate(userMessage);
}
const functionResponse = await this.aiFunctionService.performAIFunction(response.functionCall);
@ -94,7 +95,7 @@ export class ChatService {
response = await this.streamRequestResponse(conversation, allowDestructiveActions, callbacks);
}
} finally {
this.conversationService.saveConversation(conversation);
await this.conversationService.saveConversation(conversation);
this.abortController = null;
if (this.semaphoreHeld) {
this.semaphoreHeld = false;
@ -238,7 +239,7 @@ export class ChatService {
// Step 1: Remove markdown code blocks that might contain the function call
// Pattern matches ```json\n...\n``` or ```\n...\n```
sanitized = sanitized.replace(/```(?:json)?\s*\n?([\s\S]*?)\n?```/g, (match, codeContent) => {
sanitized = sanitized.replace(/```(?:json)?\s*\n?([\s\S]*?)\n?```/g, (match: string, codeContent: string) => {
// If the code block contains our function call, remove it entirely
if (codeContent.trim() === functionCallString.trim()) {
return '';
@ -252,16 +253,16 @@ export class ChatService {
// Step 3: Handle pretty-printed variations by normalizing both strings
try {
const functionCallObj = JSON.parse(functionCallString);
const functionCallObj: unknown = JSON.parse(functionCallString);
const normalizedTarget = JSON.stringify(functionCallObj);
// Find and remove any JSON that matches when normalized
// This regex finds JSON objects/arrays in the text
const jsonPattern = /\{(?:[^{}]|(?:\{(?:[^{}]|(?:\{[^{}]*\}))*\}))*\}|\[(?:[^\[\]]|(?:\[(?:[^\[\]]|(?:\[[^\[\]]*\]))*\]))*\]/g;
const jsonPattern = /\{(?:[^{}]|(?:\{(?:[^{}]|(?:\{[^{}]*\}))*\}))*\}|\[(?:[^[\]]|(?:\[(?:[^[\]]|(?:\[[^[\]]*\]))*\]))*\]/g;
sanitized = sanitized.replace(jsonPattern, (match) => {
try {
const parsedMatch = JSON.parse(match);
const parsedMatch: unknown = JSON.parse(match);
const normalizedMatch = JSON.stringify(parsedMatch);
// Remove if it matches our function call when normalized
return normalizedMatch === normalizedTarget ? '' : match;

View file

@ -10,6 +10,7 @@ export interface ISanitizeOptions {
export class SanitiserService {
// Regular expressions for different character classes
private readonly illegalRe = /[?<>\\:*|"]/g;
// eslint-disable-next-line no-control-regex -- Intentionally matching control characters for sanitization
private readonly controlRe = /[\u0000-\u001f\u0080-\u009f]/g;
private readonly reservedRe = /^\.+$/;
private readonly windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;

View file

@ -8,6 +8,11 @@ import { Services } from 'Services/Services';
export const VIEW_TYPE_MAIN = 'vaultkeeper-ai-main-view';
interface ChatWindowComponent {
focusInput: () => void;
resetChatArea: () => void;
}
export class MainView extends ItemView {
private statusBarService: StatusBarService = Resolve<StatusBarService>(Services.StatusBarService);
@ -17,7 +22,7 @@ export class MainView extends ItemView {
}
topBar: ReturnType<typeof TopBar> | undefined;
input: ReturnType<typeof ChatWindow> | undefined;
input: ChatWindowComponent | undefined;
getViewType() {
return VIEW_TYPE_MAIN;
@ -31,6 +36,8 @@ export class MainView extends ItemView {
return 'sparkles';
}
// ItemView requires onOpen to return Promise<void>, but mount operations are synchronous
// eslint-disable-next-line @typescript-eslint/require-await
async onOpen() {
const container = this.contentEl;
container.empty();
@ -51,15 +58,15 @@ export class MainView extends ItemView {
this.input = mount(ChatWindow, {
target: container,
props: {}
});
}) as ChatWindowComponent;
}
async onClose() {
if (this.topBar) {
unmount(this.topBar);
await unmount(this.topBar);
}
if (this.input) {
unmount(this.input);
await unmount(this.input);
}
this.statusBarService.removeStatusBarMessage();
}

View file

@ -80,7 +80,7 @@ describe('AIFunctionService - Integration Tests', () => {
const result = await service.performAIFunction({
name: AIFunction.SearchVaultFiles,
arguments: { search_terms: ['test'] },
arguments: { search_terms: ['test'], user_message: 'test search' },
toolId: 'tool_1'
} as any);
@ -106,7 +106,7 @@ describe('AIFunctionService - Integration Tests', () => {
it('should return empty array when search term is empty', async () => {
const result = await service.performAIFunction({
name: AIFunction.SearchVaultFiles,
arguments: { search_terms: [''] },
arguments: { search_terms: [''], user_message: 'test search' },
toolId: 'tool_2'
} as any);
@ -117,7 +117,7 @@ describe('AIFunctionService - Integration Tests', () => {
it('should return empty array when search term is whitespace', async () => {
const result = await service.performAIFunction({
name: AIFunction.SearchVaultFiles,
arguments: { search_terms: [' '] },
arguments: { search_terms: [' '], user_message: 'test search' },
toolId: 'tool_3'
} as any);
@ -130,7 +130,7 @@ describe('AIFunctionService - Integration Tests', () => {
const result = await service.performAIFunction({
name: AIFunction.SearchVaultFiles,
arguments: { search_terms: ['nonexistent'] },
arguments: { search_terms: ['nonexistent'], user_message: 'test search' },
toolId: 'tool_4'
} as any);
@ -150,7 +150,7 @@ describe('AIFunctionService - Integration Tests', () => {
const result = await service.performAIFunction({
name: AIFunction.SearchVaultFiles,
arguments: { search_terms: ['single'] },
arguments: { search_terms: ['single'], user_message: 'test search' },
toolId: 'tool_5'
} as any);
@ -170,7 +170,7 @@ describe('AIFunctionService - Integration Tests', () => {
const result = await service.performAIFunction({
name: AIFunction.ReadVaultFiles,
arguments: { file_paths: ['file1.md', 'file2.md', 'file3.md'] },
arguments: { file_paths: ['file1.md', 'file2.md', 'file3.md'], user_message: 'test search' },
toolId: 'tool_6'
} as any);
@ -191,7 +191,7 @@ describe('AIFunctionService - Integration Tests', () => {
const result = await service.performAIFunction({
name: AIFunction.ReadVaultFiles,
arguments: { file_paths: ['exists.md', 'missing1.md', 'missing2.md'] },
arguments: { file_paths: ['exists.md', 'missing1.md', 'missing2.md'], user_message: 'test search' },
toolId: 'tool_7'
} as any);
@ -212,7 +212,7 @@ describe('AIFunctionService - Integration Tests', () => {
const result = await service.performAIFunction({
name: AIFunction.ReadVaultFiles,
arguments: { file_paths: ['a.md', 'missing.md', 'b.md'] },
arguments: { file_paths: ['a.md', 'missing.md', 'b.md'], user_message: 'test search' },
toolId: 'tool_8'
} as any);
@ -225,7 +225,7 @@ describe('AIFunctionService - Integration Tests', () => {
it('should handle empty file list', async () => {
const result = await service.performAIFunction({
name: AIFunction.ReadVaultFiles,
arguments: { file_paths: [] },
arguments: { file_paths: [], user_message: 'test search' },
toolId: 'tool_9'
} as any);
@ -237,7 +237,7 @@ describe('AIFunctionService - Integration Tests', () => {
const result = await service.performAIFunction({
name: AIFunction.ReadVaultFiles,
arguments: { file_paths: ['single.md'] },
arguments: { file_paths: ['single.md'], user_message: 'test search' },
toolId: 'tool_10'
} as any);
@ -254,7 +254,8 @@ describe('AIFunctionService - Integration Tests', () => {
name: AIFunction.WriteVaultFile,
arguments: {
file_path: 'notes/new-note.md',
content: '# New Note\n\nContent here'
content: '# New Note\n\nContent here',
user_message: 'test search'
},
toolId: 'tool_11'
} as any);
@ -274,7 +275,8 @@ describe('AIFunctionService - Integration Tests', () => {
name: AIFunction.WriteVaultFile,
arguments: {
file_path: 'protected.md',
content: 'Content'
content: 'Content',
user_message: 'test search'
},
toolId: 'tool_12'
} as any);
@ -290,7 +292,8 @@ describe('AIFunctionService - Integration Tests', () => {
name: AIFunction.WriteVaultFile,
arguments: {
file_path: 'folder\\subfolder\\file.md',
content: 'Content'
content: 'Content',
user_message: 'test search'
},
toolId: 'tool_13'
} as any);
@ -309,7 +312,8 @@ describe('AIFunctionService - Integration Tests', () => {
name: AIFunction.WriteVaultFile,
arguments: {
file_path: 'empty.md',
content: ''
content: '',
user_message: 'test search'
},
toolId: 'tool_14'
} as any);
@ -330,7 +334,8 @@ describe('AIFunctionService - Integration Tests', () => {
name: AIFunction.DeleteVaultFiles,
arguments: {
file_paths: ['file1.md', 'file2.md', 'file3.md'],
confirm_deletion: true
confirm_deletion: true,
user_message: 'test search'
},
toolId: 'tool_15'
} as any);
@ -349,7 +354,8 @@ describe('AIFunctionService - Integration Tests', () => {
name: AIFunction.DeleteVaultFiles,
arguments: {
file_paths: ['file1.md', 'file2.md'],
confirm_deletion: false
confirm_deletion: false,
user_message: 'test search'
},
toolId: 'tool_16'
} as any);
@ -370,7 +376,8 @@ describe('AIFunctionService - Integration Tests', () => {
name: AIFunction.DeleteVaultFiles,
arguments: {
file_paths: ['a.md', 'missing.md', 'c.md'],
confirm_deletion: true
confirm_deletion: true,
user_message: 'test search'
},
toolId: 'tool_17'
} as any);
@ -391,7 +398,8 @@ describe('AIFunctionService - Integration Tests', () => {
name: AIFunction.DeleteVaultFiles,
arguments: {
file_paths: ['file1.md', 'file2.md'],
confirm_deletion: true
confirm_deletion: true,
user_message: 'test search'
},
toolId: 'tool_18'
} as any);
@ -406,7 +414,8 @@ describe('AIFunctionService - Integration Tests', () => {
name: AIFunction.DeleteVaultFiles,
arguments: {
file_paths: [],
confirm_deletion: true
confirm_deletion: true,
user_message: 'test search'
},
toolId: 'tool_19'
} as any);
@ -426,7 +435,8 @@ describe('AIFunctionService - Integration Tests', () => {
name: AIFunction.MoveVaultFiles,
arguments: {
source_paths: ['a.md', 'b.md', 'c.md'],
destination_paths: ['dest/a.md', 'dest/b.md', 'dest/c.md']
destination_paths: ['dest/a.md', 'dest/b.md', 'dest/c.md'],
user_message: 'test search'
},
toolId: 'tool_20'
} as any);
@ -445,7 +455,8 @@ describe('AIFunctionService - Integration Tests', () => {
name: AIFunction.MoveVaultFiles,
arguments: {
source_paths: ['a.md', 'b.md'],
destination_paths: ['dest/a.md']
destination_paths: ['dest/a.md'],
user_message: 'test search'
},
toolId: 'tool_21'
} as any);
@ -466,7 +477,8 @@ describe('AIFunctionService - Integration Tests', () => {
name: AIFunction.MoveVaultFiles,
arguments: {
source_paths: ['a.md', 'b.md', 'c.md'],
destination_paths: ['new/a.md', 'existing.md', 'new/c.md']
destination_paths: ['new/a.md', 'existing.md', 'new/c.md'],
user_message: 'test search'
},
toolId: 'tool_22'
} as any);
@ -485,7 +497,8 @@ describe('AIFunctionService - Integration Tests', () => {
name: AIFunction.MoveVaultFiles,
arguments: {
source_paths: ['old/file.md'],
destination_paths: ['new/file.md']
destination_paths: ['new/file.md'],
user_message: 'test search'
},
toolId: 'tool_23'
} as any);
@ -498,7 +511,8 @@ describe('AIFunctionService - Integration Tests', () => {
name: AIFunction.MoveVaultFiles,
arguments: {
source_paths: [],
destination_paths: []
destination_paths: [],
user_message: 'test search'
},
toolId: 'tool_24'
} as any);
@ -575,7 +589,7 @@ describe('AIFunctionService - Integration Tests', () => {
const searchResult = await service.performAIFunction({
name: AIFunction.SearchVaultFiles,
arguments: { search_terms: ['test'] },
arguments: { search_terms: ['test'], user_message: 'test search' },
toolId: 'search_1'
} as any);
@ -586,7 +600,7 @@ describe('AIFunctionService - Integration Tests', () => {
const readResult = await service.performAIFunction({
name: AIFunction.ReadVaultFiles,
arguments: { file_paths: [foundPath] },
arguments: { file_paths: [foundPath], user_message: 'test search' },
toolId: 'read_1'
} as any);
@ -602,7 +616,8 @@ describe('AIFunctionService - Integration Tests', () => {
name: AIFunction.WriteVaultFile,
arguments: {
file_path: 'temp.md',
content: 'Temporary content'
content: 'Temporary content',
user_message: 'test search'
},
toolId: 'write_1'
} as any);
@ -616,7 +631,8 @@ describe('AIFunctionService - Integration Tests', () => {
name: AIFunction.MoveVaultFiles,
arguments: {
source_paths: ['temp.md'],
destination_paths: ['archive/temp.md']
destination_paths: ['archive/temp.md'],
user_message: 'test search'
},
toolId: 'move_1'
} as any);

16
main.ts
View file

@ -25,15 +25,15 @@ export default class VaultkeeperAIPlugin extends Plugin {
);
this.addCommand({
id: "vaultkeeper-ai",
name: "Vaultkeeper AI",
callback: () => {
this.activateView();
id: "open",
name: "Open",
callback: async () => {
await this.activateView();
}
});
this.addRibbonIcon("sparkles", "Vaultkeeper AI", (_: MouseEvent) => {
this.activateView();
this.addRibbonIcon("sparkles", "Vaultkeeper AI", async () => {
await this.activateView();
});
this.addSettingTab(new VaultkeeperAISettingTab());
@ -43,7 +43,7 @@ export default class VaultkeeperAIPlugin extends Plugin {
});
}
public async onunload() {
public onunload() {
Resolve<StatusBarService>(Services.StatusBarService).removeStatusBarMessage();
DeregisterAllServices();
}
@ -62,7 +62,7 @@ export default class VaultkeeperAIPlugin extends Plugin {
}
if (leaf != null) {
workspace.revealLeaf(leaf);
await workspace.revealLeaf(leaf);
}
}

320
package-lock.json generated
View file

@ -31,15 +31,16 @@
"remark-rehype": "^11.1.2",
"remark-wiki-link": "^2.0.1",
"unified": "^11.0.5",
"uuid": "^13.0.0"
"uuid": "^13.0.0",
"zod": "^4.1.12"
},
"devDependencies": {
"@testing-library/svelte": "^5.2.8",
"@types/express": "^5.0.5",
"@types/node": "^24.10.0",
"@types/path-browserify": "^1.0.3",
"@typescript-eslint/eslint-plugin": "8.46.3",
"@typescript-eslint/parser": "8.46.3",
"@typescript-eslint/eslint-plugin": "8.46.4",
"@typescript-eslint/parser": "8.46.4",
"@vitest/ui": "^4.0.8",
"builtin-modules": "5.0.0",
"esbuild": "^0.27.0",
@ -1021,9 +1022,9 @@
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.1.tgz",
"integrity": "sha512-bxZtughE4VNVJlL1RdoSE545kc4JxL7op57KKoi59/gwuU5rV6jLWFXXc8jwgFoT6vtj+ZjO+Z2C5nrY0Cl6wA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz",
"integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==",
"cpu": [
"arm"
],
@ -1035,9 +1036,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.1.tgz",
"integrity": "sha512-44a1hreb02cAAfAKmZfXVercPFaDjqXCK+iKeVOlJ9ltvnO6QqsBHgKVPTu+MJHSLLeMEUbeG2qiDYgbFPU48g==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz",
"integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==",
"cpu": [
"arm64"
],
@ -1049,9 +1050,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.1.tgz",
"integrity": "sha512-usmzIgD0rf1syoOZ2WZvy8YpXK5G1V3btm3QZddoGSa6mOgfXWkkv+642bfUUldomgrbiLQGrPryb7DXLovPWQ==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz",
"integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==",
"cpu": [
"arm64"
],
@ -1063,9 +1064,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.1.tgz",
"integrity": "sha512-is3r/k4vig2Gt8mKtTlzzyaSQ+hd87kDxiN3uDSDwggJLUV56Umli6OoL+/YZa/KvtdrdyNfMKHzL/P4siOOmg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz",
"integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==",
"cpu": [
"x64"
],
@ -1077,9 +1078,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.1.tgz",
"integrity": "sha512-QJ1ksgp/bDJkZB4daldVmHaEQkG4r8PUXitCOC2WRmRaSaHx5RwPoI3DHVfXKwDkB+Sk6auFI/+JHacTekPRSw==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz",
"integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==",
"cpu": [
"arm64"
],
@ -1091,9 +1092,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.1.tgz",
"integrity": "sha512-J6ma5xgAzvqsnU6a0+jgGX/gvoGokqpkx6zY4cWizRrm0ffhHDpJKQgC8dtDb3+MqfZDIqs64REbfHDMzxLMqQ==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz",
"integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==",
"cpu": [
"x64"
],
@ -1105,9 +1106,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.1.tgz",
"integrity": "sha512-JzWRR41o2U3/KMNKRuZNsDUAcAVUYhsPuMlx5RUldw0E4lvSIXFUwejtYz1HJXohUmqs/M6BBJAUBzKXZVddbg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz",
"integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==",
"cpu": [
"arm"
],
@ -1119,9 +1120,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.1.tgz",
"integrity": "sha512-L8kRIrnfMrEoHLHtHn+4uYA52fiLDEDyezgxZtGUTiII/yb04Krq+vk3P2Try+Vya9LeCE9ZHU8CXD6J9EhzHQ==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz",
"integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==",
"cpu": [
"arm"
],
@ -1133,9 +1134,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.1.tgz",
"integrity": "sha512-ysAc0MFRV+WtQ8li8hi3EoFi7us6d1UzaS/+Dp7FYZfg3NdDljGMoVyiIp6Ucz7uhlYDBZ/zt6XI0YEZbUO11Q==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz",
"integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==",
"cpu": [
"arm64"
],
@ -1147,9 +1148,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.1.tgz",
"integrity": "sha512-UV6l9MJpDbDZZ/fJvqNcvO1PcivGEf1AvKuTcHoLjVZVFeAMygnamCTDikCVMRnA+qJe+B3pSbgX2+lBMqgBhA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz",
"integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==",
"cpu": [
"arm64"
],
@ -1161,9 +1162,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.1.tgz",
"integrity": "sha512-UDUtelEprkA85g95Q+nj3Xf0M4hHa4DiJ+3P3h4BuGliY4NReYYqwlc0Y8ICLjN4+uIgCEvaygYlpf0hUj90Yg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz",
"integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==",
"cpu": [
"loong64"
],
@ -1175,9 +1176,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.1.tgz",
"integrity": "sha512-vrRn+BYhEtNOte/zbc2wAUQReJXxEx2URfTol6OEfY2zFEUK92pkFBSXRylDM7aHi+YqEPJt9/ABYzmcrS4SgQ==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz",
"integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==",
"cpu": [
"ppc64"
],
@ -1189,9 +1190,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.1.tgz",
"integrity": "sha512-gto/1CxHyi4A7YqZZNznQYrVlPSaodOBPKM+6xcDSCMVZN/Fzb4K+AIkNz/1yAYz9h3Ng+e2fY9H6bgawVq17w==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz",
"integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==",
"cpu": [
"riscv64"
],
@ -1203,9 +1204,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.1.tgz",
"integrity": "sha512-KZ6Vx7jAw3aLNjFR8eYVcQVdFa/cvBzDNRFM3z7XhNNunWjA03eUrEwJYPk0G8V7Gs08IThFKcAPS4WY/ybIrQ==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz",
"integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==",
"cpu": [
"riscv64"
],
@ -1217,9 +1218,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.1.tgz",
"integrity": "sha512-HvEixy2s/rWNgpwyKpXJcHmE7om1M89hxBTBi9Fs6zVuLU4gOrEMQNbNsN/tBVIMbLyysz/iwNiGtMOpLAOlvA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz",
"integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==",
"cpu": [
"s390x"
],
@ -1231,9 +1232,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.1.tgz",
"integrity": "sha512-E/n8x2MSjAQgjj9IixO4UeEUeqXLtiA7pyoXCFYLuXpBA/t2hnbIdxHfA7kK9BFsYAoNU4st1rHYdldl8dTqGA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz",
"integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==",
"cpu": [
"x64"
],
@ -1245,9 +1246,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.1.tgz",
"integrity": "sha512-IhJ087PbLOQXCN6Ui/3FUkI9pWNZe/Z7rEIVOzMsOs1/HSAECCvSZ7PkIbkNqL/AZn6WbZvnoVZw/qwqYMo4/w==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz",
"integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==",
"cpu": [
"x64"
],
@ -1259,9 +1260,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.1.tgz",
"integrity": "sha512-0++oPNgLJHBblreu0SFM7b3mAsBJBTY0Ksrmu9N6ZVrPiTkRgda52mWR7TKhHAsUb9noCjFvAw9l6ZO1yzaVbA==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz",
"integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==",
"cpu": [
"arm64"
],
@ -1273,9 +1274,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.1.tgz",
"integrity": "sha512-VJXivz61c5uVdbmitLkDlbcTk9Or43YC2QVLRkqp86QoeFSqI81bNgjhttqhKNMKnQMWnecOCm7lZz4s+WLGpQ==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz",
"integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==",
"cpu": [
"arm64"
],
@ -1287,9 +1288,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.1.tgz",
"integrity": "sha512-NmZPVTUOitCXUH6erJDzTQ/jotYw4CnkMDjCYRxNHVD9bNyfrGoIse684F9okwzKCV4AIHRbUkeTBc9F2OOH5Q==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz",
"integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==",
"cpu": [
"ia32"
],
@ -1301,9 +1302,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.1.tgz",
"integrity": "sha512-2SNj7COIdAf6yliSpLdLG8BEsp5lgzRehgfkP0Av8zKfQFKku6JcvbobvHASPJu4f3BFxej5g+HuQPvqPhHvpQ==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz",
"integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==",
"cpu": [
"x64"
],
@ -1315,9 +1316,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.1.tgz",
"integrity": "sha512-rLarc1Ofcs3DHtgSzFO31pZsCh8g05R2azN1q3fF+H423Co87My0R+tazOEvYVKXSLh8C4LerMK41/K7wlklcg==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz",
"integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==",
"cpu": [
"x64"
],
@ -1745,17 +1746,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.46.3",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.3.tgz",
"integrity": "sha512-sbaQ27XBUopBkRiuY/P9sWGOWUW4rl8fDoHIUmLpZd8uldsTyB4/Zg6bWTegPoTLnKj9Hqgn3QD6cjPNB32Odw==",
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.4.tgz",
"integrity": "sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.46.3",
"@typescript-eslint/type-utils": "8.46.3",
"@typescript-eslint/utils": "8.46.3",
"@typescript-eslint/visitor-keys": "8.46.3",
"@typescript-eslint/scope-manager": "8.46.4",
"@typescript-eslint/type-utils": "8.46.4",
"@typescript-eslint/utils": "8.46.4",
"@typescript-eslint/visitor-keys": "8.46.4",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@ -1769,22 +1770,22 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.46.3",
"@typescript-eslint/parser": "^8.46.4",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.46.3",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.3.tgz",
"integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==",
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.4.tgz",
"integrity": "sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.3",
"@typescript-eslint/types": "8.46.3",
"@typescript-eslint/typescript-estree": "8.46.3",
"@typescript-eslint/visitor-keys": "8.46.3",
"@typescript-eslint/scope-manager": "8.46.4",
"@typescript-eslint/types": "8.46.4",
"@typescript-eslint/typescript-estree": "8.46.4",
"@typescript-eslint/visitor-keys": "8.46.4",
"debug": "^4.3.4"
},
"engines": {
@ -1800,14 +1801,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.46.3",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.3.tgz",
"integrity": "sha512-Fz8yFXsp2wDFeUElO88S9n4w1I4CWDTXDqDr9gYvZgUpwXQqmZBr9+NTTql5R3J7+hrJZPdpiWaB9VNhAKYLuQ==",
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.4.tgz",
"integrity": "sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.46.3",
"@typescript-eslint/types": "^8.46.3",
"@typescript-eslint/tsconfig-utils": "^8.46.4",
"@typescript-eslint/types": "^8.46.4",
"debug": "^4.3.4"
},
"engines": {
@ -1822,14 +1823,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.46.3",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.3.tgz",
"integrity": "sha512-FCi7Y1zgrmxp3DfWfr+3m9ansUUFoy8dkEdeQSgA9gbm8DaHYvZCdkFRQrtKiedFf3Ha6VmoqoAaP68+i+22kg==",
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.4.tgz",
"integrity": "sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.46.3",
"@typescript-eslint/visitor-keys": "8.46.3"
"@typescript-eslint/types": "8.46.4",
"@typescript-eslint/visitor-keys": "8.46.4"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1840,9 +1841,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.46.3",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.3.tgz",
"integrity": "sha512-GLupljMniHNIROP0zE7nCcybptolcH8QZfXOpCfhQDAdwJ/ZTlcaBOYebSOZotpti/3HrHSw7D3PZm75gYFsOA==",
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.4.tgz",
"integrity": "sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A==",
"dev": true,
"license": "MIT",
"engines": {
@ -1857,15 +1858,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.46.3",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.3.tgz",
"integrity": "sha512-ZPCADbr+qfz3aiTTYNNkCbUt+cjNwI/5McyANNrFBpVxPt7GqpEYz5ZfdwuFyGUnJ9FdDXbGODUu6iRCI6XRXw==",
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.4.tgz",
"integrity": "sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.46.3",
"@typescript-eslint/typescript-estree": "8.46.3",
"@typescript-eslint/utils": "8.46.3",
"@typescript-eslint/types": "8.46.4",
"@typescript-eslint/typescript-estree": "8.46.4",
"@typescript-eslint/utils": "8.46.4",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@ -1882,9 +1883,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.46.3",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.3.tgz",
"integrity": "sha512-G7Ok9WN/ggW7e/tOf8TQYMaxgID3Iujn231hfi0Pc7ZheztIJVpO44ekY00b7akqc6nZcvregk0Jpah3kep6hA==",
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.4.tgz",
"integrity": "sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==",
"dev": true,
"license": "MIT",
"engines": {
@ -1896,16 +1897,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.46.3",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.3.tgz",
"integrity": "sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==",
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.4.tgz",
"integrity": "sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.46.3",
"@typescript-eslint/tsconfig-utils": "8.46.3",
"@typescript-eslint/types": "8.46.3",
"@typescript-eslint/visitor-keys": "8.46.3",
"@typescript-eslint/project-service": "8.46.4",
"@typescript-eslint/tsconfig-utils": "8.46.4",
"@typescript-eslint/types": "8.46.4",
"@typescript-eslint/visitor-keys": "8.46.4",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
@ -1925,16 +1926,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.46.3",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.3.tgz",
"integrity": "sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==",
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.4.tgz",
"integrity": "sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.46.3",
"@typescript-eslint/types": "8.46.3",
"@typescript-eslint/typescript-estree": "8.46.3"
"@typescript-eslint/scope-manager": "8.46.4",
"@typescript-eslint/types": "8.46.4",
"@typescript-eslint/typescript-estree": "8.46.4"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1949,13 +1950,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.46.3",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.3.tgz",
"integrity": "sha512-uk574k8IU0rOF/AjniX8qbLSGURJVUCeM5e4MIMKBFFi8weeiLrG1fyQejyLXQpRZbU/1BuQasleV/RfHC3hHg==",
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.4.tgz",
"integrity": "sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.46.3",
"@typescript-eslint/types": "8.46.4",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@ -8068,9 +8069,9 @@
}
},
"node_modules/rollup": {
"version": "4.53.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.1.tgz",
"integrity": "sha512-n2I0V0lN3E9cxxMqBCT3opWOiQBzRN7UG60z/WDKqdX2zHUS/39lezBcsckZFsV6fUTSnfqI7kHf60jDAPGKug==",
"version": "4.53.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz",
"integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -8084,28 +8085,28 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.53.1",
"@rollup/rollup-android-arm64": "4.53.1",
"@rollup/rollup-darwin-arm64": "4.53.1",
"@rollup/rollup-darwin-x64": "4.53.1",
"@rollup/rollup-freebsd-arm64": "4.53.1",
"@rollup/rollup-freebsd-x64": "4.53.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.53.1",
"@rollup/rollup-linux-arm-musleabihf": "4.53.1",
"@rollup/rollup-linux-arm64-gnu": "4.53.1",
"@rollup/rollup-linux-arm64-musl": "4.53.1",
"@rollup/rollup-linux-loong64-gnu": "4.53.1",
"@rollup/rollup-linux-ppc64-gnu": "4.53.1",
"@rollup/rollup-linux-riscv64-gnu": "4.53.1",
"@rollup/rollup-linux-riscv64-musl": "4.53.1",
"@rollup/rollup-linux-s390x-gnu": "4.53.1",
"@rollup/rollup-linux-x64-gnu": "4.53.1",
"@rollup/rollup-linux-x64-musl": "4.53.1",
"@rollup/rollup-openharmony-arm64": "4.53.1",
"@rollup/rollup-win32-arm64-msvc": "4.53.1",
"@rollup/rollup-win32-ia32-msvc": "4.53.1",
"@rollup/rollup-win32-x64-gnu": "4.53.1",
"@rollup/rollup-win32-x64-msvc": "4.53.1",
"@rollup/rollup-android-arm-eabi": "4.53.2",
"@rollup/rollup-android-arm64": "4.53.2",
"@rollup/rollup-darwin-arm64": "4.53.2",
"@rollup/rollup-darwin-x64": "4.53.2",
"@rollup/rollup-freebsd-arm64": "4.53.2",
"@rollup/rollup-freebsd-x64": "4.53.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.53.2",
"@rollup/rollup-linux-arm-musleabihf": "4.53.2",
"@rollup/rollup-linux-arm64-gnu": "4.53.2",
"@rollup/rollup-linux-arm64-musl": "4.53.2",
"@rollup/rollup-linux-loong64-gnu": "4.53.2",
"@rollup/rollup-linux-ppc64-gnu": "4.53.2",
"@rollup/rollup-linux-riscv64-gnu": "4.53.2",
"@rollup/rollup-linux-riscv64-musl": "4.53.2",
"@rollup/rollup-linux-s390x-gnu": "4.53.2",
"@rollup/rollup-linux-x64-gnu": "4.53.2",
"@rollup/rollup-linux-x64-musl": "4.53.2",
"@rollup/rollup-openharmony-arm64": "4.53.2",
"@rollup/rollup-win32-arm64-msvc": "4.53.2",
"@rollup/rollup-win32-ia32-msvc": "4.53.2",
"@rollup/rollup-win32-x64-gnu": "4.53.2",
"@rollup/rollup-win32-x64-msvc": "4.53.2",
"fsevents": "~2.3.2"
}
},
@ -9306,17 +9307,17 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.46.3",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.3.tgz",
"integrity": "sha512-bAfgMavTuGo+8n6/QQDVQz4tZ4f7Soqg53RbrlZQEoAltYop/XR4RAts/I0BrO3TTClTSTFJ0wYbla+P8cEWJA==",
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.4.tgz",
"integrity": "sha512-KALyxkpYV5Ix7UhvjTwJXZv76VWsHG+NjNlt/z+a17SOQSiOcBdUXdbJdyXi7RPxrBFECtFOiPwUJQusJuCqrg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.46.3",
"@typescript-eslint/parser": "8.46.3",
"@typescript-eslint/typescript-estree": "8.46.3",
"@typescript-eslint/utils": "8.46.3"
"@typescript-eslint/eslint-plugin": "8.46.4",
"@typescript-eslint/parser": "8.46.4",
"@typescript-eslint/typescript-estree": "8.46.4",
"@typescript-eslint/utils": "8.46.4"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -10568,6 +10569,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/zod": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",

View file

@ -24,8 +24,8 @@
"@types/express": "^5.0.5",
"@types/node": "^24.10.0",
"@types/path-browserify": "^1.0.3",
"@typescript-eslint/eslint-plugin": "8.46.3",
"@typescript-eslint/parser": "8.46.3",
"@typescript-eslint/eslint-plugin": "8.46.4",
"@typescript-eslint/parser": "8.46.4",
"@vitest/ui": "^4.0.8",
"builtin-modules": "5.0.0",
"esbuild": "^0.27.0",
@ -64,6 +64,7 @@
"remark-rehype": "^11.1.2",
"remark-wiki-link": "^2.0.1",
"unified": "^11.0.5",
"uuid": "^13.0.0"
"uuid": "^13.0.0",
"zod": "^4.1.12"
}
}