mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
Refactor Gemini and IAIClass to remove IActioner dependency, update streamRequest method signature, and enhance service registration. Remove unused Actioner and related action definitions. Introduce AIFunctionService and implement ListVaultFiles function definition.
This commit is contained in:
parent
692b3b793e
commit
82ee600979
15 changed files with 53 additions and 199 deletions
9
AIClasses/FunctionDefinitions/AIFunctionDefinition.ts
Normal file
9
AIClasses/FunctionDefinitions/AIFunctionDefinition.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export interface FunctionDefinition {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: {
|
||||
type: string;
|
||||
properties: Record<string, any>;
|
||||
required?: string[];
|
||||
};
|
||||
}
|
||||
14
AIClasses/FunctionDefinitions/ListVaultFiles.ts
Normal file
14
AIClasses/FunctionDefinitions/ListVaultFiles.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { AIFunction } from "Enums/AIFunction";
|
||||
import type { FunctionDefinition } from "./AIFunctionDefinition";
|
||||
|
||||
export const ListVaultFiles: FunctionDefinition = {
|
||||
name: AIFunction.ListVaultFiles,
|
||||
description: `Returns complete list of vault files with metadata (names, paths, sizes).
|
||||
Call this whenever you need to know what files exist in the vault to answer questions,
|
||||
verify file presence, or to perform further agentic functions. Use proactively
|
||||
when vault contents would inform your response.`,
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
import { Resolve } from "Services/DependencyService";
|
||||
import { Services } from "Services/Services";
|
||||
import type { IActioner } from "Actioner/IActioner";
|
||||
import type { GeminiActionDefinitions } from "Actioner/Gemini/GeminiActionDefinitions";
|
||||
import type { IAIClass } from "AIClasses/IAIClass";
|
||||
import type { IPrompt } from "AIClasses/IPrompt";
|
||||
import { StreamingService, type StreamChunk } from "Services/StreamingService";
|
||||
|
|
@ -11,23 +9,15 @@ import { Role } from "Enums/Role";
|
|||
export class Gemini implements IAIClass {
|
||||
private readonly apiKey: string;
|
||||
private readonly aiPrompt: IPrompt;
|
||||
private readonly actionDefinitions: GeminiActionDefinitions;
|
||||
private readonly streamingService: StreamingService;
|
||||
|
||||
public constructor(apiKey: string) {
|
||||
this.apiKey = apiKey;
|
||||
this.aiPrompt = Resolve(Services.IPrompt);
|
||||
this.actionDefinitions = Resolve(Services.IActionDefinitions);
|
||||
this.streamingService = new StreamingService();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream response from Gemini API
|
||||
*/
|
||||
public async* streamRequest(
|
||||
conversation: Conversation,
|
||||
actioner: IActioner
|
||||
): AsyncGenerator<StreamChunk, void, unknown> {
|
||||
public async* streamRequest(conversation: Conversation): AsyncGenerator<StreamChunk, void, unknown> {
|
||||
|
||||
const contents = conversation.contents.map(content => ({
|
||||
role: content.role === Role.User ? "user" : "model",
|
||||
|
|
@ -53,7 +43,7 @@ export class Gemini implements IAIClass {
|
|||
tools: [
|
||||
{
|
||||
google_search: {},
|
||||
//TODO: functionDeclarations: [this.actionDefinitions[create_file]],
|
||||
functionDeclarations: [],
|
||||
},
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import type { IActioner } from "Actioner/IActioner";
|
||||
import type { StreamChunk } from "Services/StreamingService";
|
||||
import type { Conversation } from "Conversations/Conversation";
|
||||
|
||||
export interface IAIClass {
|
||||
streamRequest(conversation: Conversation, actioner: IActioner): AsyncGenerator<StreamChunk, void, unknown>;
|
||||
streamRequest(conversation: Conversation): AsyncGenerator<StreamChunk, void, unknown>;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import type AIAgentPlugin from "main";
|
||||
import type { TAbstractFile, TFile, Vault } from "obsidian";
|
||||
import type { TFile, Vault } from "obsidian";
|
||||
import { Resolve } from "Services/DependencyService";
|
||||
import { Services } from "Services/Services";
|
||||
import { SystemInstruction } from "./SystemPrompt";
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
import { create_file } from "./Actions";
|
||||
import type { IActioner } from "./IActioner";
|
||||
import type { Vault } from "obsidian";
|
||||
import { Resolve } from "Services/DependencyService";
|
||||
import { Services } from "Services/Services";
|
||||
import type AIAgentPlugin from "main";
|
||||
|
||||
export class Actioner implements IActioner {
|
||||
|
||||
private vault: Vault;
|
||||
|
||||
public constructor() {
|
||||
this.vault = Resolve<AIAgentPlugin>(Services.AIAgentPlugin).app.vault;
|
||||
}
|
||||
|
||||
public async [create_file](action: CreateFileRequest) {
|
||||
//await createDirectories(this.vault, action.file_path);
|
||||
await this.vault.create(action.file_path, JSON.stringify(action.file_content, null, 4));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
const request_directories: symbol = Symbol.for("request_directories");
|
||||
const request_contents: symbol = Symbol.for("request_contents");
|
||||
const create_schema: symbol = Symbol.for("create_schema");
|
||||
const create_file: symbol = Symbol.for("create_file");
|
||||
const delete_file: symbol = Symbol.for("delete_file");
|
||||
const edit_file: symbol = Symbol.for("edit_file");
|
||||
const rename_file: symbol = Symbol.for("rename_file");
|
||||
|
||||
export { request_directories, request_contents, create_schema, create_file, delete_file, edit_file, rename_file };
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
import { Type } from "@google/genai";
|
||||
import { create_file, create_schema, delete_file, edit_file, rename_file, request_contents, request_directories } from "Actioner/Actions";
|
||||
import type { IActionDefinitions } from "Actioner/IActionDefinitions";
|
||||
|
||||
export class GeminiActionDefinitions implements IActionDefinitions {
|
||||
public [request_directories](): object {
|
||||
return {
|
||||
name: "request_directories",
|
||||
description: "Request the available user directories. Call this for further available functions",
|
||||
parameters: {}
|
||||
};
|
||||
}
|
||||
|
||||
public [request_contents](): object {
|
||||
return {
|
||||
name: "request_contents",
|
||||
description: "Request the contents of a file. The contents will be added using the Files API",
|
||||
parameters: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
file_path: {
|
||||
type: Type.STRING,
|
||||
description: "The file path of the file to be requested"
|
||||
}
|
||||
},
|
||||
requited: ["file_path"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public [create_schema](): object {
|
||||
return {
|
||||
name: "create_schema",
|
||||
description: "Create a new data definition schema for the user",
|
||||
parameters: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
file_path: {
|
||||
type: Type.STRING,
|
||||
description: "The file path of the schema to be created"
|
||||
},
|
||||
schema_content: {
|
||||
type: Type.OBJECT,
|
||||
description: "The schema definition where each field is the property name and each value is the default property value. Nested properties are accepted"
|
||||
}
|
||||
},
|
||||
required: ["file_path", "file_content"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public [create_file](): object {
|
||||
return {
|
||||
name: "create_file",
|
||||
description: "Create a new file for the user",
|
||||
parameters: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
file_path: {
|
||||
type: Type.STRING,
|
||||
description: "The file path of the file to be created"
|
||||
},
|
||||
file_content: {
|
||||
type: Type.STRING,
|
||||
description: "The content of the file to be created"
|
||||
}
|
||||
},
|
||||
required: ["file_path", "file_content"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public [delete_file](): object {
|
||||
return {
|
||||
name: "delete_file",
|
||||
description: "Request a file to be deleted",
|
||||
parameters: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
file_path: {
|
||||
type: Type.STRING,
|
||||
description: "The file path of the file to be deleted"
|
||||
}
|
||||
},
|
||||
requited: ["file_path"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public [edit_file](): object {
|
||||
return {
|
||||
name: "edit_file",
|
||||
description: "Edit the contents of an existing file",
|
||||
parameters: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
file_path: {
|
||||
type: Type.STRING,
|
||||
description: "The file path of the file to be edited"
|
||||
},
|
||||
file_content: {
|
||||
type: Type.STRING,
|
||||
description: "The new content of the file to be written"
|
||||
}
|
||||
},
|
||||
requited: ["file_path, file_content"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public [rename_file](): object {
|
||||
return {
|
||||
name: "rename_file",
|
||||
description: "Rename or move a file",
|
||||
parameters: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
old_file_path: {
|
||||
type: Type.STRING,
|
||||
description: "The old file path of the file"
|
||||
},
|
||||
new_file_path: {
|
||||
type: Type.STRING,
|
||||
description: "The new file path of the file"
|
||||
}
|
||||
},
|
||||
requited: ["old_file_path", "new_file_path"]
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import type { create_file, create_schema, delete_file, edit_file, rename_file, request_contents, request_directories } from "./Actions";
|
||||
|
||||
export interface IActionDefinitions {
|
||||
[request_directories](): object;
|
||||
[request_contents](): object;
|
||||
[create_schema](): object;
|
||||
[create_file](): object;
|
||||
[delete_file](): object;
|
||||
[edit_file](): object;
|
||||
[rename_file](): object;
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export interface IActioner {
|
||||
[key: symbol]: (...args: any[]) => any;
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
<script lang="ts">
|
||||
import type { IActioner } from "Actioner/IActioner";
|
||||
import { Semaphore } from "Helpers/Semaphore";
|
||||
import { Resolve } from "Services/DependencyService";
|
||||
import { Services } from "Services/Services";
|
||||
|
|
@ -14,7 +13,6 @@
|
|||
import { ConversationContent } from "Conversations/ConversationContent";
|
||||
|
||||
let ai: IAIClass = Resolve(Services.IAIClass);
|
||||
let actioner: IActioner = Resolve(Services.IActioner);
|
||||
let conversationService: ConversationFileSystemService = Resolve(Services.ConversationFileSystemService);
|
||||
|
||||
let semaphore: Semaphore = new Semaphore(1, false);
|
||||
|
|
@ -59,7 +57,7 @@
|
|||
// Stream the response
|
||||
let accumulatedContent = "";
|
||||
|
||||
for await (const chunk of ai.streamRequest(conversation, actioner)) {
|
||||
for await (const chunk of ai.streamRequest(conversation)) {
|
||||
if (chunk.error) {
|
||||
console.error("Streaming error:", chunk.error);
|
||||
// Update message with error
|
||||
|
|
|
|||
3
Enums/AIFunction.ts
Normal file
3
Enums/AIFunction.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export enum AIFunction {
|
||||
ListVaultFiles = "list_vault_files"
|
||||
}
|
||||
18
Services/AIFunctionService.ts
Normal file
18
Services/AIFunctionService.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { Resolve } from "./DependencyService";
|
||||
import { Services } from "./Services";
|
||||
import type { FileSystemService } from "./FileSystemService";
|
||||
import { TFile } from "obsidian";
|
||||
|
||||
export class AIFunctionService {
|
||||
|
||||
private fileSystemService: FileSystemService = Resolve(Services.FileSystemService);
|
||||
|
||||
public async listVaultFiles(): Promise<object[]> {
|
||||
const files: TFile[] = await this.fileSystemService.listFilesInDirectory("/");
|
||||
return files.map((file) => ({
|
||||
name: file.basename,
|
||||
path: file.path,
|
||||
size_bytes: file.stat.size
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -4,11 +4,7 @@ import type AIAgentPlugin from "main";
|
|||
import { RegisterSingleton, RegisterTransient } from "./DependencyService";
|
||||
import { Services } from "./Services";
|
||||
import { AIPrompt, type IPrompt } from "AIClasses/IPrompt";
|
||||
import { Actioner } from "Actioner/Actioner";
|
||||
import type { IActioner } from "Actioner/IActioner";
|
||||
import type { IAIClass } from "AIClasses/IAIClass";
|
||||
import { GeminiActionDefinitions } from "Actioner/Gemini/GeminiActionDefinitions";
|
||||
import type { IActionDefinitions } from "Actioner/IActionDefinitions";
|
||||
import { Gemini } from "AIClasses/Gemini/Gemini";
|
||||
import { StreamingMarkdownService } from "./StreamingMarkdownService";
|
||||
import { MessageService } from "./MessageService";
|
||||
|
|
@ -16,6 +12,7 @@ import { FileSystemService } from "./FileSystemService";
|
|||
import { ConversationFileSystemService } from "./ConversationFileSystemService";
|
||||
import { ConversationHistoryModal } from "Modals/ConversationHistoryModal";
|
||||
import type { App } from "obsidian";
|
||||
import { AIFunctionService } from "./AIFunctionService";
|
||||
|
||||
export function RegisterDependencies(plugin: AIAgentPlugin) {
|
||||
RegisterSingleton(Services.MessageService, new MessageService());
|
||||
|
|
@ -25,7 +22,7 @@ export function RegisterDependencies(plugin: AIAgentPlugin) {
|
|||
RegisterSingleton(Services.ConversationFileSystemService, new ConversationFileSystemService());
|
||||
|
||||
RegisterSingleton<IPrompt>(Services.IPrompt, new AIPrompt());
|
||||
RegisterSingleton<IActioner>(Services.IActioner, new Actioner());
|
||||
RegisterSingleton<AIFunctionService>(Services.AIFunctionService, new AIFunctionService());
|
||||
|
||||
RegisterTransient<StreamingMarkdownService>(Services.StreamingMarkdownService, () => new StreamingMarkdownService());
|
||||
|
||||
|
|
@ -34,8 +31,7 @@ export function RegisterDependencies(plugin: AIAgentPlugin) {
|
|||
}
|
||||
|
||||
export function RegisterAiProvider(plugin: AIAgentPlugin) {
|
||||
if (plugin.settings.apiProvider == AIProvider.Gemini && plugin.settings.apiKey != "") {
|
||||
RegisterTransient<IActionDefinitions>(Services.IActionDefinitions, () => new GeminiActionDefinitions());
|
||||
if (plugin.settings.apiProvider == AIProvider.Gemini) {
|
||||
RegisterSingleton<IAIClass>(Services.IAIClass, new Gemini(plugin.settings.apiKey));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export class Services {
|
|||
static StreamingService = Symbol("StreamingService");
|
||||
static MarkdownService = Symbol("MarkdownService");
|
||||
static StreamingMarkdownService = Symbol("StreamingMarkdownService");
|
||||
static AIFunctionService = Symbol("AIFunctionService");
|
||||
|
||||
// interfaces
|
||||
static IAIClass = Symbol("IAIClass");
|
||||
|
|
|
|||
Loading…
Reference in a new issue