Rename 'AIfunction' to 'AITool' globally.

This commit is contained in:
Andrew Beal 2026-01-30 22:35:54 +00:00
parent a2c83f4e02
commit 9d4a4452c5
58 changed files with 708 additions and 708 deletions

View file

@ -1,13 +1,13 @@
import { AIFunction } from "Enums/AIFunction";
import { AITool } from "Enums/AITool";
// platform agnostic function call class used to execute the requested function
export class AIFunctionCall {
public readonly name: AIFunction;
export class AIToolCall {
public readonly name: AITool;
public readonly arguments: Record<string, unknown>;
public readonly toolId?: string;
public readonly thoughtSignature?: string;
constructor(name: AIFunction, args: Record<string, unknown>, toolId?: string, thoughtSignature?: string) {
constructor(name: AITool, args: Record<string, unknown>, toolId?: string, thoughtSignature?: string) {
this.name = name;
this.arguments = args;
this.toolId = toolId;

View file

@ -4,18 +4,18 @@ import type { IAIClass } from "AIClasses/IAIClass";
import { type IStreamChunk } from "Services/StreamingService";
import type { Conversation } from "Conversations/Conversation";
import type { AIProvider } from "Enums/ApiProvider";
import type { IAIFunctionDefinition } from "AIClasses/FunctionDefinitions/IAIFunctionDefinition";
import type { IAIToolDefinition } from "AIClasses/FunctionDefinitions/IAIToolDefinition";
import type { ConversationContent } from "Conversations/ConversationContent";
import type { Attachment } from "Conversations/Attachment";
import type { SettingsService } from "Services/SettingsService";
import type { StreamingService } from "Services/StreamingService";
import type { StoredFunctionCall, StoredFunctionResponse } from "AIClasses/Schemas/AIFunctionTypes";
import type { StoredFunctionCall, StoredFunctionResponse } from "AIClasses/Schemas/AIToolTypes";
import { Exception } from "Helpers/Exception";
import { ApiError, ApiErrorType } from "Types/ApiError";
import type { AbortService } from "Services/AbortService";
import type { IAIFileService } from "./IAIFileService";
import { AgentType } from "Enums/AgentType";
import { AIFunctionUsageMode } from "Enums/AIFunctionUsageMode";
import { AIToolUsageMode } from "Enums/AIToolUsageMode";
export abstract class BaseAIClass implements IAIClass {
@ -29,8 +29,8 @@ export abstract class BaseAIClass implements IAIClass {
private _systemPrompt: string = "";
private _userInstruction: string = "";
private _agentType: AgentType = AgentType.Main;
private _aiFunctionDefinitions: IAIFunctionDefinition[] = [];
private _aiFunctionUsageMode: AIFunctionUsageMode = AIFunctionUsageMode.Auto;
private _aiToolDefinitions: IAIToolDefinition[] = [];
private _aiToolUsageMode: AIToolUsageMode = AIToolUsageMode.Auto;
protected constructor(provider: AIProvider) {
this.provider = provider;
@ -62,12 +62,12 @@ export abstract class BaseAIClass implements IAIClass {
return this._userInstruction;
}
public get aiFunctionDefinitions(): IAIFunctionDefinition[] {
return this._aiFunctionDefinitions;
public get aiToolDefinitions(): IAIToolDefinition[] {
return this._aiToolDefinitions;
}
public set aiFunctionDefinitions(aiFunctionDefinitions: IAIFunctionDefinition[]) {
this._aiFunctionDefinitions = aiFunctionDefinitions;
public set aiToolDefinitions(aiToolDefinitions: IAIToolDefinition[]) {
this._aiToolDefinitions = aiToolDefinitions;
}
public get agentType() {
@ -78,12 +78,12 @@ export abstract class BaseAIClass implements IAIClass {
this._agentType = agentType;
}
public get aiFunctionUsageMode(): AIFunctionUsageMode {
return this._aiFunctionUsageMode;
public get aiToolUsageMode(): AIToolUsageMode {
return this._aiToolUsageMode;
}
public set aiFunctionUsageMode(mode: AIFunctionUsageMode) {
this._aiFunctionUsageMode = mode;
public set aiToolUsageMode(mode: AIToolUsageMode) {
this._aiToolUsageMode = mode;
}
public abstract streamRequest(conversation: Conversation): AsyncGenerator<IStreamChunk, void, unknown>;
@ -92,7 +92,7 @@ export abstract class BaseAIClass implements IAIClass {
protected abstract parseStreamChunk(chunk: string): IStreamChunk;
protected abstract extractContents(conversationContent: ConversationContent[]): unknown;
protected abstract mapFunctionDefinitions(aiFunctionDefinitions: IAIFunctionDefinition[]): object;
protected abstract mapFunctionDefinitions(aiToolDefinitions: IAIToolDefinition[]): object;
protected model(): string {
switch (this._agentType) {

View file

@ -3,9 +3,9 @@ import type { IStreamChunk } from "Services/StreamingService";
import type { Conversation } from "Conversations/Conversation";
import type { Attachment } from "Conversations/Attachment";
import { AIProvider, AIProviderURL } from "Enums/ApiProvider";
import { AIFunctionCall } from "AIClasses/AIFunctionCall";
import { fromString as aiFunctionFromString } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "AIClasses/FunctionDefinitions/IAIFunctionDefinition";
import { AIToolCall } from "AIClasses/AIToolCall";
import { fromString as aiToolFromString } from "Enums/AITool";
import type { IAIToolDefinition } from "AIClasses/FunctionDefinitions/IAIToolDefinition";
import type { ConversationContent } from "Conversations/ConversationContent";
import { Role } from "Enums/Role";
import type { RawMessageStreamEvent, ContentBlockParam, Tool, TextBlockParam, ToolUnion, WebSearchTool20250305 } from '@anthropic-ai/sdk/resources/messages';
@ -15,7 +15,7 @@ import { isTextFile } from "Enums/FileType";
import { MimeTypeToFileTypes } from "Enums/FileTypeMimeTypeMapping";
import { ApiError, ApiErrorType } from "Types/ApiError";
import { parseFunctionCall, parseFunctionResponse } from "Helpers/ResponseHelper";
import { AIFunctionUsageMode } from "Enums/AIFunctionUsageMode";
import { AIToolUsageMode } from "Enums/AIToolUsageMode";
export class Claude extends BaseAIClass {
@ -69,7 +69,7 @@ export class Claude extends BaseAIClass {
};
const tools: ToolUnion[] = this.addCacheControlToTools([
webSearchTool, ...this.mapFunctionDefinitions(this.aiFunctionDefinitions)]);
webSearchTool, ...this.mapFunctionDefinitions(this.aiToolDefinitions)]);
const requestBody = {
model: this.model(),
@ -103,7 +103,7 @@ export class Claude extends BaseAIClass {
const data = JSON.parse(chunk) as RawMessageStreamEvent;
let text = "";
let functionCall: AIFunctionCall | undefined = undefined;
let functionCall: AIToolCall | undefined = undefined;
let isComplete = false;
let shouldContinue = false;
@ -135,8 +135,8 @@ export class Claude extends BaseAIClass {
if (this.accumulatedFunctionName && this.accumulatedFunctionArgs) {
try {
const args = JSON.parse(this.accumulatedFunctionArgs) as Record<string, unknown>;
functionCall = new AIFunctionCall(
aiFunctionFromString(this.accumulatedFunctionName),
functionCall = new AIToolCall(
aiToolFromString(this.accumulatedFunctionName),
args as Record<string, object>,
this.accumulatedFunctionId || undefined,
undefined // thoughtSignature not used by Claude
@ -268,8 +268,8 @@ export class Claude extends BaseAIClass {
return results.filter(message => message.content.length > 0);
}
protected mapFunctionDefinitions(aiFunctionDefinitions: IAIFunctionDefinition[]): Tool[] {
return aiFunctionDefinitions.map((functionDefinition) => ({
protected mapFunctionDefinitions(aiToolDefinitions: IAIToolDefinition[]): Tool[] {
return aiToolDefinitions.map((functionDefinition) => ({
name: functionDefinition.name,
description: functionDefinition.description,
input_schema: {
@ -369,16 +369,16 @@ export class Claude extends BaseAIClass {
private buildClaudeToolChoice(): { type: string } {
// If no tools defined, fall back to auto
if (this.aiFunctionDefinitions.length === 0) {
if (this.aiToolDefinitions.length === 0) {
return { type: "auto" };
}
switch (this.aiFunctionUsageMode) {
case AIFunctionUsageMode.Auto:
switch (this.aiToolUsageMode) {
case AIToolUsageMode.Auto:
return { type: "auto" };
case AIFunctionUsageMode.Enabled:
case AIToolUsageMode.Enabled:
return { type: "any" };
case AIFunctionUsageMode.Disabled:
case AIToolUsageMode.Disabled:
return { type: "none" };
}
}

View file

@ -1,4 +1,4 @@
import type { IAIFunctionDefinition } from "./IAIFunctionDefinition";
import type { IAIToolDefinition } from "./IAIToolDefinition";
import { SearchVaultFiles } from "./Functions/SearchVaultFiles";
import { ReadVaultFiles } from "./Functions/ReadVaultFiles";
import { WriteVaultFile } from "./Functions/WriteVaultFile";
@ -15,14 +15,14 @@ import { CompletePlan } from "./Functions/CompletePlan";
import { CompleteTask } from "./Functions/CompleteTask";
import { ExecuteWorkflow } from "./Functions/ExecuteWorkflow";
export abstract class AIFunctionDefinitions {
export abstract class AIToolDefinitions {
public static isGated: boolean = false;
// Definitions list provides a list of function definitions that does not include any planning functions (used as reference in planning agent prompt)
private static readonly definitionsList = [SearchVaultFiles, ReadVaultFiles, ListVaultFiles, WriteVaultFile, PatchVaultFile, DeleteVaultFiles, MoveVaultFiles];
public static agentDefinitions(destructive: boolean, planning: boolean): IAIFunctionDefinition[] {
public static agentDefinitions(destructive: boolean, planning: boolean): IAIToolDefinition[] {
this.isGated = false;
if (planning) {
@ -47,15 +47,15 @@ export abstract class AIFunctionDefinitions {
return actions;
}
public static orchestrationAgentDefinitions(): IAIFunctionDefinition[] {
public static orchestrationAgentDefinitions(): IAIToolDefinition[] {
return [CompleteStep, Replan, CancelPlan, CompletePlan];
}
public static planningAgentDefinitions(): IAIFunctionDefinition[] {
public static planningAgentDefinitions(): IAIToolDefinition[] {
return [SearchVaultFiles, ReadVaultFiles, ListVaultFiles, AskUserQuestionPlanning, SubmitPlan];
}
public static executionAgentDefinitions(): IAIFunctionDefinition[] {
public static executionAgentDefinitions(): IAIToolDefinition[] {
return [...this.agentDefinitions(true, false), CompleteTask];
}

View file

@ -1,10 +1,10 @@
// Platform agnostic class for function responses
import type { AIFunction } from "Enums/AIFunction";
import type { AITool } from "Enums/AITool";
// Used by AI providers to format function execution results for API calls
export class AIFunctionResponse {
public readonly name: AIFunction;
export class AIToolResponse {
public readonly name: AITool;
public readonly response: object;
public readonly toolId?: string;
@ -29,7 +29,7 @@ export class AIFunctionResponse {
**User's Suggestion:**`;
constructor(name: AIFunction, response: object, toolId?: string) {
constructor(name: AITool, response: object, toolId?: string) {
this.name = name;
this.response = response;
this.toolId = toolId;

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const AskUserQuestionExecution: IAIFunctionDefinition = {
name: AIFunction.AskUserQuestionExecution,
export const AskUserQuestionExecution: IAIToolDefinition = {
name: AITool.AskUserQuestionExecution,
description: `Asks the user a question during plan execution and waits for their response.
Use this function to resolve ambiguity at the point of discovery rather than reporting uncertain outcomes.

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const AskUserQuestionPlanning: IAIFunctionDefinition = {
name: AIFunction.AskUserQuestionPlanning,
export const AskUserQuestionPlanning: IAIToolDefinition = {
name: AITool.AskUserQuestionPlanning,
description: `Asks the user a question during the planning phase and waits for their response.
This allows interactive planning where user feedback shapes the plan before it's submitted.

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const CancelPlan: IAIFunctionDefinition = {
name: AIFunction.CancelPlan,
export const CancelPlan: IAIToolDefinition = {
name: AITool.CancelPlan,
description: `Terminates the current plan execution immediately and returns control to the main conversation loop.
Use this function when plan execution cannot or should not continue.

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const CompletePlan: IAIFunctionDefinition = {
name: AIFunction.CompletePlan,
export const CompletePlan: IAIToolDefinition = {
name: AITool.CompletePlan,
description: `Marks the current execution plan as fully completed.
Use this function when all steps in the plan have been successfully executed and

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const CompleteStep: IAIFunctionDefinition = {
name: AIFunction.CompleteStep,
export const CompleteStep: IAIToolDefinition = {
name: AITool.CompleteStep,
description: `Signals that plan execution should proceed to the next step without modification.
Call this function:

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const CompleteTask: IAIFunctionDefinition = {
name: AIFunction.CompleteTask,
export const CompleteTask: IAIToolDefinition = {
name: AITool.CompleteTask,
description: `Signals that you have finished executing all of your assigned instructions.
This function MUST be called exactly once, only after you have fully completed or can no longer make progress on everything you were asked to do.

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const ContinuePlanExecution: IAIFunctionDefinition = {
name: AIFunction.ContinuePlanExecution,
export const ContinuePlanExecution: IAIToolDefinition = {
name: AITool.ContinuePlanExecution,
description: `Signals that you need to perform additional actions to complete the current step.
Call this function:

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const DeleteVaultFiles: IAIFunctionDefinition = {
name: AIFunction.DeleteVaultFiles,
export const DeleteVaultFiles: IAIToolDefinition = {
name: AITool.DeleteVaultFiles,
description: `Permanently removes files and folders from the vault.
IMPORTANT: This action is irreversible - always confirm the exact file path(s) before deletion.

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const ExecuteWorkflow: IAIFunctionDefinition = {
name: AIFunction.ExecuteWorkflow,
export const ExecuteWorkflow: IAIToolDefinition = {
name: AITool.ExecuteWorkflow,
description: `Delegates a complex goal to a specialized workflow system that will autonomously plan, execute, and return the completed result.
When called, this tool hands off the goal to a planning and execution engine that operates independently. The system will analyze the goal, create a detailed plan, execute each step programmatically, handle any necessary replanning if steps fail, and return the final outcome. You do not need to manage or monitor the execution the result will be returned to you once the workflow completes or if it fails irrecoverably.

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const ListVaultFiles: IAIFunctionDefinition = {
name: AIFunction.ListVaultFiles,
export const ListVaultFiles: IAIToolDefinition = {
name: AITool.ListVaultFiles,
description: `Lists files and directories in the vault's directory structure.
Returns a structured view of the vault's organization including file names, paths, and directory hierarchy.

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const MoveVaultFiles: IAIFunctionDefinition = {
name: AIFunction.MoveVaultFiles,
export const MoveVaultFiles: IAIToolDefinition = {
name: AITool.MoveVaultFiles,
description: `Moves or renames one or more files within the vault to new locations.
This operation preserves file content while updating paths and names.

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const PatchVaultFile: IAIFunctionDefinition = {
name: AIFunction.PatchVaultFile,
export const PatchVaultFile: IAIToolDefinition = {
name: AITool.PatchVaultFile,
description: `Apply targeted changes to an existing file in the vault by finding and replacing specific content.
This tool modifies specific sections of a file by matching exact content and replacing it with new content. It works by performing a direct string match and replace operation.

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const ReadVaultFiles: IAIFunctionDefinition = {
name: AIFunction.ReadVaultFiles,
export const ReadVaultFiles: IAIToolDefinition = {
name: AITool.ReadVaultFiles,
description: `Reads and returns the complete content of one or more files from the vault.
**IMPORTANT: This function gives you the ability to SEE and ANALYZE images

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const Replan: IAIFunctionDefinition = {
name: AIFunction.Replan,
export const Replan: IAIToolDefinition = {
name: AITool.Replan,
description: `Signals that the current plan needs to be revised before execution can continue.
Call this function:

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const SearchVaultFiles: IAIFunctionDefinition = {
name: AIFunction.SearchVaultFiles,
export const SearchVaultFiles: IAIToolDefinition = {
name: AITool.SearchVaultFiles,
description: `Searches the content of all vault files using regex pattern matching.
Returns files containing any of the search terms with contextual snippets showing where matches appear.

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const SubmitPlan: IAIFunctionDefinition = {
name: AIFunction.SubmitPlan,
export const SubmitPlan: IAIToolDefinition = {
name: AITool.SubmitPlan,
description: `Submits an execution plan with ordered, actionable steps.
Call this function:

View file

@ -1,8 +1,8 @@
import { AIFunction } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "../IAIFunctionDefinition";
import { AITool } from "Enums/AITool";
import type { IAIToolDefinition } from "../IAIToolDefinition";
export const WriteVaultFile: IAIFunctionDefinition = {
name: AIFunction.WriteVaultFile,
export const WriteVaultFile: IAIToolDefinition = {
name: AITool.WriteVaultFile,
description: `Writes content to a file, creating it if it doesn't exist or replacing its contents if it does.
Call this function:

View file

@ -1,9 +1,9 @@
import type { AIFunction } from "Enums/AIFunction";
import type { JSONSchemaProperty } from "../Schemas/AIFunctionTypes";
import type { AITool } from "Enums/AITool";
import type { JSONSchemaProperty } from "../Schemas/AIToolTypes";
// platform agnostic function definition used to present function calls in an API call
export interface IAIFunctionDefinition {
name: AIFunction;
export interface IAIToolDefinition {
name: AITool;
description: string;
parameters: {
type: string;

View file

@ -4,9 +4,9 @@ import type { Conversation } from "Conversations/Conversation";
import type { Attachment } from "Conversations/Attachment";
import { Role } from "Enums/Role";
import { AIProvider, AIProviderURL } from "Enums/ApiProvider";
import { AIFunctionCall } from "AIClasses/AIFunctionCall";
import { fromString as aiFunctionFromString } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "AIClasses/FunctionDefinitions/IAIFunctionDefinition";
import { AIToolCall } from "AIClasses/AIToolCall";
import { fromString as aiToolFromString } from "Enums/AITool";
import type { IAIToolDefinition } from "AIClasses/FunctionDefinitions/IAIToolDefinition";
import type { ConversationContent } from "Conversations/ConversationContent";
import type { Candidate, Part, FunctionDeclaration } from "@google/genai";
import { FinishReason } from "@google/genai";
@ -17,7 +17,7 @@ import { Exception } from "Helpers/Exception";
import { ApiError, ApiErrorType } from "Types/ApiError";
import { parseFunctionCall, parseFunctionResponse } from "Helpers/ResponseHelper";
import type { GeminiRetryInfo, GeminiErrorResponse } from "./GeminiTypes";
import { AIFunctionUsageMode } from "Enums/AIFunctionUsageMode";
import { AIToolUsageMode } from "Enums/AIToolUsageMode";
export class Gemini extends BaseAIClass {
@ -104,7 +104,7 @@ export class Gemini extends BaseAIClass {
information, recent events, news, or facts that may have changed.
After calling this, you will be able to perform web searches.`,
},
...this.mapFunctionDefinitions(this.aiFunctionDefinitions),
...this.mapFunctionDefinitions(this.aiToolDefinitions),
]
}
@ -157,7 +157,7 @@ export class Gemini extends BaseAIClass {
const data = JSON.parse(chunk) as { candidates?: Candidate[] };
let text = "";
let functionCall: AIFunctionCall | undefined = undefined;
let functionCall: AIToolCall | undefined = undefined;
const candidate = data.candidates?.[0];
if (candidate) {
@ -199,8 +199,8 @@ export class Gemini extends BaseAIClass {
// If streaming is complete and we have accumulated a function call, return it
if (isComplete && this.accumulatedFunctionName) {
functionCall = new AIFunctionCall(
aiFunctionFromString(this.accumulatedFunctionName),
functionCall = new AIToolCall(
aiToolFromString(this.accumulatedFunctionName),
this.accumulatedFunctionArgs as Record<string, object>,
undefined, // toolId not used by Gemini
this.accumulatedThoughtSignature || undefined
@ -317,8 +317,8 @@ export class Gemini extends BaseAIClass {
return results.filter(message => message.parts.length > 0);
}
protected mapFunctionDefinitions(aiFunctionDefinitions: IAIFunctionDefinition[]): FunctionDeclaration[] {
return aiFunctionDefinitions.map((functionDefinition) => ({
protected mapFunctionDefinitions(aiToolDefinitions: IAIToolDefinition[]): FunctionDeclaration[] {
return aiToolDefinitions.map((functionDefinition) => ({
name: functionDefinition.name,
description: functionDefinition.description,
parameters: functionDefinition.parameters as FunctionDeclaration['parameters']
@ -362,16 +362,16 @@ export class Gemini extends BaseAIClass {
private buildGeminiToolConfig(): { function_calling_config: { mode: string } } {
// If no tools defined, fall back to auto
if (this.aiFunctionDefinitions.length === 0) {
if (this.aiToolDefinitions.length === 0) {
return { function_calling_config: { mode: "AUTO" } };
}
switch (this.aiFunctionUsageMode) {
case AIFunctionUsageMode.Auto:
switch (this.aiToolUsageMode) {
case AIToolUsageMode.Auto:
return { function_calling_config: { mode: "AUTO" } };
case AIFunctionUsageMode.Enabled:
case AIToolUsageMode.Enabled:
return { function_calling_config: { mode: "ANY" } };
case AIFunctionUsageMode.Disabled:
case AIToolUsageMode.Disabled:
return { function_calling_config: { mode: "NONE" } };
}
}

View file

@ -1,18 +1,18 @@
import type { IStreamChunk } from "Services/StreamingService";
import type { Conversation } from "Conversations/Conversation";
import type { Attachment } from "Conversations/Attachment";
import type { IAIFunctionDefinition } from "./FunctionDefinitions/IAIFunctionDefinition";
import type { IAIToolDefinition } from "./FunctionDefinitions/IAIToolDefinition";
import type { AIProvider } from "Enums/ApiProvider";
import type { AgentType } from "Enums/AgentType";
import type { AIFunctionUsageMode } from "Enums/AIFunctionUsageMode";
import type { AIToolUsageMode } from "Enums/AIToolUsageMode";
export interface IAIClass {
get currentProvider(): AIProvider;
set agentType(agentType: AgentType);
set systemPrompt(systemPrompt: string);
set userInstruction(userInstruction: string);
set aiFunctionDefinitions(aiFunctionDefinitions: IAIFunctionDefinition[]);
set aiFunctionUsageMode(mode: AIFunctionUsageMode);
set aiToolDefinitions(aiToolDefinitions: IAIToolDefinition[]);
set aiToolUsageMode(mode: AIToolUsageMode);
streamRequest(conversation: Conversation): AsyncGenerator<IStreamChunk, void, unknown>;
formatBinaryFiles(attachments: Attachment[]): string;

View file

@ -4,17 +4,17 @@ import type { Conversation } from "Conversations/Conversation";
import type { ConversationContent } from "Conversations/ConversationContent";
import type { Attachment } from "Conversations/Attachment";
import { AIProvider, AIProviderURL } from "Enums/ApiProvider";
import { AIFunctionCall } from "AIClasses/AIFunctionCall";
import { fromString as aiFunctionFromString } from "Enums/AIFunction";
import type { IAIFunctionDefinition } from "AIClasses/FunctionDefinitions/IAIFunctionDefinition";
import type { ResponseEvent, ResponseOutputTextDelta, ResponseOutputItemDone, ResponseErrorEvent, ResponseFailedEvent, OpenAIFunctionTool, ResponsesAPIInput } from "./OpenAITypes";
import { AIToolCall } from "AIClasses/AIToolCall";
import { fromString as aiToolFromString } from "Enums/AITool";
import type { IAIToolDefinition } from "AIClasses/FunctionDefinitions/IAIToolDefinition";
import type { ResponseEvent, ResponseOutputTextDelta, ResponseOutputItemDone, ResponseErrorEvent, ResponseFailedEvent, OpenAIToolTool, ResponsesAPIInput } from "./OpenAITypes";
import { Exception } from "Helpers/Exception";
import { ApiError, ApiErrorType } from "Types/ApiError";
import { MimeType, toMimeType } from "Enums/MimeType";
import { isTextFile } from "Enums/FileType";
import { MimeTypeToFileTypes } from "Enums/FileTypeMimeTypeMapping";
import { parseFunctionCall, parseFunctionResponse } from "Helpers/ResponseHelper";
import { AIFunctionUsageMode } from "Enums/AIFunctionUsageMode";
import { AIToolUsageMode } from "Enums/AIToolUsageMode";
export class OpenAI extends BaseAIClass {
@ -43,7 +43,7 @@ export class OpenAI extends BaseAIClass {
const tools = [{
type: "web_search"
}, ...this.mapFunctionDefinitions(this.aiFunctionDefinitions)];
}, ...this.mapFunctionDefinitions(this.aiToolDefinitions)];
const requestBody = {
model: this.model(),
@ -78,7 +78,7 @@ export class OpenAI extends BaseAIClass {
const event = JSON.parse(chunk) as ResponseEvent;
let text = "";
let functionCall: AIFunctionCall | undefined = undefined;
let functionCall: AIToolCall | undefined = undefined;
let isComplete = false;
let shouldContinue = false;
@ -148,8 +148,8 @@ export class OpenAI extends BaseAIClass {
itemDoneEvent.item.arguments) {
try {
const args = JSON.parse(itemDoneEvent.item.arguments) as Record<string, unknown>;
functionCall = new AIFunctionCall(
aiFunctionFromString(itemDoneEvent.item.name),
functionCall = new AIToolCall(
aiToolFromString(itemDoneEvent.item.name),
args as Record<string, object>,
itemDoneEvent.item.call_id || itemDoneEvent.item_id,
undefined // thoughtSignature not used by OpenAI
@ -305,8 +305,8 @@ export class OpenAI extends BaseAIClass {
return results;
}
protected mapFunctionDefinitions(aiFunctionDefinitions: IAIFunctionDefinition[]): OpenAIFunctionTool[] {
return aiFunctionDefinitions.map((functionDefinition) => ({
protected mapFunctionDefinitions(aiToolDefinitions: IAIToolDefinition[]): OpenAIToolTool[] {
return aiToolDefinitions.map((functionDefinition) => ({
type: "function",
name: functionDefinition.name,
description: functionDefinition.description,
@ -354,16 +354,16 @@ export class OpenAI extends BaseAIClass {
private buildOpenAIToolChoice(): string {
// If no tools defined, fall back to auto
if (this.aiFunctionDefinitions.length === 0) {
if (this.aiToolDefinitions.length === 0) {
return "auto";
}
switch (this.aiFunctionUsageMode) {
case AIFunctionUsageMode.Auto:
switch (this.aiToolUsageMode) {
case AIToolUsageMode.Auto:
return "auto";
case AIFunctionUsageMode.Enabled:
case AIToolUsageMode.Enabled:
return "required";
case AIFunctionUsageMode.Disabled:
case AIToolUsageMode.Disabled:
return "none";
}
}

View file

@ -1,4 +1,4 @@
import type { IAIFunctionDefinition } from "AIClasses/FunctionDefinitions/IAIFunctionDefinition";
import type { IAIToolDefinition } from "AIClasses/FunctionDefinitions/IAIToolDefinition";
/* SDK types are a bit complicated and verbose so define simpler interfaces */
@ -73,11 +73,11 @@ export interface ResponseFailedEvent extends ResponseEvent {
};
}
export interface OpenAIFunctionTool {
export interface OpenAIToolTool {
type: "function";
name: string;
description: string;
parameters: IAIFunctionDefinition["parameters"];
parameters: IAIToolDefinition["parameters"];
}
/**

View file

@ -1,4 +1,4 @@
import { AIFunctionDefinitions } from "AIClasses/FunctionDefinitions/AIFunctionDefinitions";
import { AIToolDefinitions } from "AIClasses/FunctionDefinitions/AIToolDefinitions";
export const PlanningPrompt: string = `
# Obsidian Vault Planning Agent
@ -229,7 +229,7 @@ If extensive searching yields nothing:
The main agent has access to the following vault operation capabilities:
${AIFunctionDefinitions.compactSummaryForPlanningAgent()}
${AIToolDefinitions.compactSummaryForPlanningAgent()}
**Important**:
- Design your plan steps around these capabilities

View file

@ -1,9 +1,9 @@
import { StringTools } from "Helpers/StringTools";
import { ConversationContent } from "./ConversationContent";
import { Attachment } from "./Attachment";
import type { AIFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionResponse";
import type { AIToolResponse } from "AIClasses/FunctionDefinitions/AIToolResponse";
import { Role } from "Enums/Role";
import { AIFunction } from "Enums/AIFunction";
import { AITool } from "Enums/AITool";
import { isTextFile, toFileType } from "Enums/FileType";
import { FileTypeToMimeType } from "Enums/FileTypeMimeTypeMapping";
@ -27,8 +27,8 @@ export class Conversation {
return this.contents.some(c => c.attachments.length > 0);
}
public addFunctionResponse(functionResponse: AIFunctionResponse): void {
if (functionResponse.name !== AIFunction.ReadVaultFiles) {
public addFunctionResponse(functionResponse: AIToolResponse): void {
if (functionResponse.name !== AITool.ReadVaultFiles) {
const functionResponseString = functionResponse.toConversationString();
this.contents.push(new ConversationContent({
role: Role.User,

View file

@ -1,4 +1,4 @@
export enum AIFunction {
export enum AITool {
SearchVaultFiles = "search_vault_files",
ReadVaultFiles = "read_vault_files",
WriteVaultFile = "write_vault_file",
@ -25,14 +25,14 @@ export enum AIFunction {
Unknown = "unknown"
}
export function fromString(functionName: string): AIFunction {
const enumValue = Object.values(AIFunction).find((value: string) => value === functionName);
export function fromString(functionName: string): AITool {
const enumValue = Object.values(AITool).find((value: string) => value === functionName);
if (enumValue) {
return enumValue as AIFunction;
return enumValue as AITool;
}
return AIFunction.Unknown;
return AITool.Unknown;
}
export function isAIFunction(value: unknown, aiFunction: AIFunction): value is AIFunction {
return value === aiFunction;
export function isAITool(value: unknown, aiTool: AITool): value is AITool {
return value === aiTool;
}

View file

@ -1,4 +1,4 @@
export enum AIFunctionUsageMode {
export enum AIToolUsageMode {
Auto = "auto",
Enabled = "enabled",
Disabled = "disabled"

View file

@ -1,10 +1,10 @@
import type { AIFunctionCall } from "AIClasses/AIFunctionCall";
import type { StoredFunctionCall, StoredFunctionResponse } from "AIClasses/Schemas/AIFunctionTypes";
import type { AIToolCall } from "AIClasses/AIToolCall";
import type { StoredFunctionCall, StoredFunctionResponse } from "AIClasses/Schemas/AIToolTypes";
import { StringTools } from "./StringTools";
import { Exception } from "./Exception";
// handle the rare event where a function call is also included in content (gemini sometimes does this)
export function sanitizeFunctionCallContent(content: string, functionCall: AIFunctionCall | null): string {
export function sanitizeFunctionCallContent(content: string, functionCall: AIToolCall | null): string {
// Early returns for simple cases
if (!functionCall || !content.trim()) {
return content;

View file

@ -1,9 +1,9 @@
import { Resolve } from "../DependencyService";
import { Services } from "../Services";
import type { FileSystemService } from "../FileSystemService";
import { AIFunction, fromString } from "Enums/AIFunction";
import { AIFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionResponse";
import type { AIFunctionCall } from "AIClasses/AIFunctionCall";
import { AITool, fromString } from "Enums/AITool";
import { AIToolResponse } from "AIClasses/FunctionDefinitions/AIToolResponse";
import type { AIToolCall } from "AIClasses/AIToolCall";
import type { ISearchMatch } from "../../Types/SearchTypes";
import { AbortService } from "../AbortService";
import { normalizePath, TAbstractFile, TFile } from "obsidian";
@ -17,9 +17,9 @@ import {
MoveVaultFilesArgsSchema,
ListVaultFilesArgsSchema,
PatchVaultFileArgsSchema
} from "AIClasses/Schemas/AIFunctionSchemas";
} from "AIClasses/Schemas/AIToolSchemas";
export class AIFunctionService {
export class AIToolService {
private readonly fileSystemService: FileSystemService;
private readonly abortService: AbortService;
@ -29,122 +29,122 @@ export class AIFunctionService {
this.abortService = Resolve<AbortService>(Services.AbortService);
}
public async performAIFunction(functionCall: AIFunctionCall): Promise<AIFunctionResponse> {
public async performAITool(functionCall: AIToolCall): Promise<AIToolResponse> {
return await this.abortService.abortableOperation(async () => {
switch (functionCall.name) {
case AIFunction.SearchVaultFiles: {
case AITool.SearchVaultFiles: {
const parseResult = SearchVaultFilesArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
return new AIFunctionResponse(
return new AIToolResponse(
functionCall.name,
{ error: `Invalid arguments for ${AIFunction.SearchVaultFiles}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AITool.SearchVaultFiles}: ${parseResult.error.message}` },
functionCall.toolId
);
}
return new AIFunctionResponse(functionCall.name, await this.searchVaultFiles(parseResult.data.search_terms), functionCall.toolId);
return new AIToolResponse(functionCall.name, await this.searchVaultFiles(parseResult.data.search_terms), functionCall.toolId);
}
case AIFunction.ReadVaultFiles: {
case AITool.ReadVaultFiles: {
const parseResult = ReadVaultFilesArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
return new AIFunctionResponse(
return new AIToolResponse(
functionCall.name,
{ error: `Invalid arguments for ${AIFunction.ReadVaultFiles}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AITool.ReadVaultFiles}: ${parseResult.error.message}` },
functionCall.toolId
);
}
return new AIFunctionResponse(functionCall.name, await this.readVaultFiles(parseResult.data.file_paths), functionCall.toolId);
return new AIToolResponse(functionCall.name, await this.readVaultFiles(parseResult.data.file_paths), functionCall.toolId);
}
case AIFunction.WriteVaultFile: {
case AITool.WriteVaultFile: {
const parseResult = WriteVaultFileArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
return new AIFunctionResponse(
return new AIToolResponse(
functionCall.name,
{ error: `Invalid arguments for ${AIFunction.WriteVaultFile}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AITool.WriteVaultFile}: ${parseResult.error.message}` },
functionCall.toolId
);
}
return new AIFunctionResponse(functionCall.name, await this.writeVaultFile(parseResult.data.file_path, parseResult.data.content), functionCall.toolId);
return new AIToolResponse(functionCall.name, await this.writeVaultFile(parseResult.data.file_path, parseResult.data.content), functionCall.toolId);
}
case AIFunction.PatchVaultFile: {
case AITool.PatchVaultFile: {
const parseResult = PatchVaultFileArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
return new AIFunctionResponse(
return new AIToolResponse(
functionCall.name,
{ error: `Invalid arguments for ${AIFunction.PatchVaultFile}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AITool.PatchVaultFile}: ${parseResult.error.message}` },
functionCall.toolId
);
}
return new AIFunctionResponse(functionCall.name, await this.patchVaultFile(parseResult.data.file_path, parseResult.data.oldContent, parseResult.data.newContent), functionCall.toolId);
return new AIToolResponse(functionCall.name, await this.patchVaultFile(parseResult.data.file_path, parseResult.data.oldContent, parseResult.data.newContent), functionCall.toolId);
}
case AIFunction.DeleteVaultFiles: {
case AITool.DeleteVaultFiles: {
const parseResult = DeleteVaultFilesArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
return new AIFunctionResponse(
return new AIToolResponse(
functionCall.name,
{ error: `Invalid arguments for ${AIFunction.DeleteVaultFiles}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AITool.DeleteVaultFiles}: ${parseResult.error.message}` },
functionCall.toolId
);
}
return new AIFunctionResponse(functionCall.name, await this.deleteVaultFiles(parseResult.data.file_paths, parseResult.data.confirm_deletion), functionCall.toolId);
return new AIToolResponse(functionCall.name, await this.deleteVaultFiles(parseResult.data.file_paths, parseResult.data.confirm_deletion), functionCall.toolId);
}
case AIFunction.MoveVaultFiles: {
case AITool.MoveVaultFiles: {
const parseResult = MoveVaultFilesArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
return new AIFunctionResponse(
return new AIToolResponse(
functionCall.name,
{ error: `Invalid arguments for ${AIFunction.MoveVaultFiles}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AITool.MoveVaultFiles}: ${parseResult.error.message}` },
functionCall.toolId
);
}
return new AIFunctionResponse(functionCall.name, await this.moveVaultFiles(parseResult.data.source_paths, parseResult.data.destination_paths), functionCall.toolId);
return new AIToolResponse(functionCall.name, await this.moveVaultFiles(parseResult.data.source_paths, parseResult.data.destination_paths), functionCall.toolId);
}
case AIFunction.ListVaultFiles: {
case AITool.ListVaultFiles: {
const parseResult = ListVaultFilesArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
return new AIFunctionResponse(
return new AIToolResponse(
functionCall.name,
{ error: `Invalid arguments for ${AIFunction.ListVaultFiles}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AITool.ListVaultFiles}: ${parseResult.error.message}` },
functionCall.toolId
);
}
return new AIFunctionResponse(functionCall.name, await this.ListVaultFiles(parseResult.data.path, parseResult.data.recursive), functionCall.toolId);
return new AIToolResponse(functionCall.name, await this.ListVaultFiles(parseResult.data.path, parseResult.data.recursive), functionCall.toolId);
}
// This is only used by gemini
case AIFunction.RequestWebSearch:
return new AIFunctionResponse(functionCall.name, {}, functionCall.toolId)
case AITool.RequestWebSearch:
return new AIToolResponse(functionCall.name, {}, functionCall.toolId)
// multi-agent functions are handled elsewhere - this shouldn't really ever get hit
case AIFunction.ExecuteWorkflow:
case AIFunction.ContinuePlanExecution:
case AIFunction.Replan:
case AIFunction.SubmitPlan:
case AIFunction.AskUserQuestionPlanning:
case AIFunction.AskUserQuestionExecution:
case AIFunction.CompleteTask:
case AIFunction.CompleteStep:
case AIFunction.CompletePlan:
case AIFunction.CancelPlan: {
Exception.log(`Multi-agent function ${functionCall.name} should not be handled by AIFunctionService`);
return new AIFunctionResponse(
case AITool.ExecuteWorkflow:
case AITool.ContinuePlanExecution:
case AITool.Replan:
case AITool.SubmitPlan:
case AITool.AskUserQuestionPlanning:
case AITool.AskUserQuestionExecution:
case AITool.CompleteTask:
case AITool.CompleteStep:
case AITool.CompletePlan:
case AITool.CancelPlan: {
Exception.log(`Multi-agent function ${functionCall.name} should not be handled by AIToolService`);
return new AIToolResponse(
functionCall.name,
{ error: `Failed to execute ${functionCall.name}.` },
functionCall.toolId
);
}
case AIFunction.Unknown:
case AITool.Unknown:
default: {
const functionCallName = fromString(functionCall.name);
const error = `Unknown function request ${functionCallName}`
Exception.log(error);
return new AIFunctionResponse(
return new AIToolResponse(
functionCallName,
{ error: error },
functionCall.toolId

View file

@ -1,4 +1,4 @@
import type { AIFunctionCall } from "AIClasses/AIFunctionCall";
import type { AIToolCall } from "AIClasses/AIToolCall";
import type { IAIClass } from "AIClasses/IAIClass";
import type { IPrompt } from "AIPrompts/IPrompt";
import type { Conversation } from "Conversations/Conversation";
@ -10,24 +10,24 @@ import { sanitizeFunctionCallContent } from "Helpers/ResponseHelper";
import type { IChatServiceCallbacks } from "Services/ChatService";
import { Resolve, TryResolve } from "Services/DependencyService";
import { Services } from "Services/Services";
import type { AIFunctionService } from "./AIFunctionService";
import type { AIToolService } from "./AIToolService";
import type { DebugService } from "Services/DebugService";
import { DebugColor } from "Enums/DebugColor";
import { Exception } from "Helpers/Exception";
import { AIFunctionUsageMode } from "Enums/AIFunctionUsageMode";
import { AIToolUsageMode } from "Enums/AIToolUsageMode";
export abstract class BaseAgent {
protected ai: IAIClass | undefined;
protected readonly aiPrompt: IPrompt;
protected readonly aiFunctionService: AIFunctionService;
protected readonly aiToolService: AIToolService;
protected readonly debugService: DebugService | undefined;
private onSaveConversation?: (conversation: Conversation) => Promise<void>;
public constructor() {
this.aiPrompt = Resolve<IPrompt>(Services.IPrompt);
this.aiFunctionService = Resolve<AIFunctionService>(Services.AIFunctionService);
this.aiToolService = Resolve<AIToolService>(Services.AIToolService);
this.debugService = TryResolve<DebugService>(Services.DebugService);
this.setDebugColor();
}
@ -41,7 +41,7 @@ export abstract class BaseAgent {
}
protected async runAgentLoop(agentType: AgentType, conversation: Conversation, callbacks: IChatServiceCallbacks,
handleFunctionCall: (functionCall: AIFunctionCall) => Promise<{ shouldExit: boolean }>
handleFunctionCall: (functionCall: AIToolCall) => Promise<{ shouldExit: boolean }>
): Promise<void> {
this.debugService?.log("AgentLoop", `Starting ${agentType} agent loop`);
let response = await this.streamRequestResponse(agentType, this.ensureCorrectConversationStructure(conversation), callbacks);
@ -86,7 +86,7 @@ export abstract class BaseAgent {
});
}
protected updateThought(functionCall: AIFunctionCall | null, callbacks: IChatServiceCallbacks) {
protected updateThought(functionCall: AIToolCall | null, callbacks: IChatServiceCallbacks) {
const userMessage = functionCall?.arguments.user_message;
if (userMessage && typeof userMessage === "string") {
callbacks.onThoughtUpdate(userMessage);
@ -117,7 +117,7 @@ export abstract class BaseAgent {
}
private async streamRequestResponse(agentType: AgentType, conversation: Conversation, callbacks: IChatServiceCallbacks
): Promise<{ functionCall: AIFunctionCall | null, shouldContinue: boolean }> {
): Promise<{ functionCall: AIToolCall | null, shouldContinue: boolean }> {
if (!this.ai) { // this should never happen
return { functionCall: null, shouldContinue: false };
}
@ -126,7 +126,7 @@ export abstract class BaseAgent {
conversation.contents.push(conversationContent);
let accumulatedContent = "";
let capturedFunctionCall: AIFunctionCall | null = null;
let capturedFunctionCall: AIToolCall | null = null;
let capturedShouldContinue = false;
for await (const chunk of this.ai.streamRequest(conversation)) {
@ -191,12 +191,12 @@ export abstract class BaseAgent {
Exception.throw("Error: No AI provider has been set!");
}
const aiFunctionUsageMode = this.ai.aiFunctionUsageMode;
this.ai.aiFunctionUsageMode = AIFunctionUsageMode.Disabled;
const aiToolUsageMode = this.ai.aiToolUsageMode;
this.ai.aiToolUsageMode = AIToolUsageMode.Disabled;
const result = await callback();
this.ai.aiFunctionUsageMode = aiFunctionUsageMode;
this.ai.aiToolUsageMode = aiToolUsageMode;
return result;
}
}

View file

@ -5,14 +5,14 @@ import { Conversation } from "Conversations/Conversation";
import type { IChatServiceCallbacks } from "Services/ChatService";
import { ConversationContent } from "Conversations/ConversationContent";
import { Role } from "Enums/Role";
import { AIFunction, isAIFunction } from "Enums/AIFunction";
import { CompleteTaskArgsSchema, type CompleteTaskArgs } from "AIClasses/Schemas/AIFunctionSchemas";
import { AIFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionResponse";
import { AITool, isAITool } from "Enums/AITool";
import { CompleteTaskArgsSchema, type CompleteTaskArgs } from "AIClasses/Schemas/AIToolSchemas";
import { AIToolResponse } from "AIClasses/FunctionDefinitions/AIToolResponse";
import { Exception } from "Helpers/Exception";
import { AIFunctionDefinitions } from "AIClasses/FunctionDefinitions/AIFunctionDefinitions";
import { AIToolDefinitions } from "AIClasses/FunctionDefinitions/AIToolDefinitions";
import { Copy, replaceCopy } from "Enums/Copy";
import { DebugColor } from "Enums/DebugColor";
import { AIFunctionUsageMode } from "Enums/AIFunctionUsageMode";
import { AIToolUsageMode } from "Enums/AIToolUsageMode";
export class ExecutionAgent extends BaseAgent {
@ -48,12 +48,12 @@ export class ExecutionAgent extends BaseAgent {
await this.runAgentLoop(AgentType.Execution, this.conversation, callbacks, async functionCall => {
const functionCallName = functionCall.name;
if (isAIFunction(functionCallName, AIFunction.CompleteTask)) {
if (isAITool(functionCallName, AITool.CompleteTask)) {
const parseResult = CompleteTaskArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
this.conversation.addFunctionResponse(new AIFunctionResponse(
this.conversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ error: `Invalid arguments for ${AIFunction.CompleteTask}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AITool.CompleteTask}: ${parseResult.error.message}` },
functionCall.toolId
));
return { shouldExit: false };
@ -66,7 +66,7 @@ export class ExecutionAgent extends BaseAgent {
this.debugService?.log("ExecutionAgent", `Executing function: ${functionCall.name}`);
this.updateThought(functionCall, callbacks);
const functionResponse = await this.aiFunctionService.performAIFunction(functionCall);
const functionResponse = await this.aiToolService.performAITool(functionCall);
this.conversation.addFunctionResponse(functionResponse);
return { shouldExit: false };
});
@ -87,10 +87,10 @@ export class ExecutionAgent extends BaseAgent {
Exception.throw("Error: No AI provider has been set!");
}
this.ai.agentType = AgentType.Execution;
this.ai.aiFunctionUsageMode = AIFunctionUsageMode.Enabled;
this.ai.aiToolUsageMode = AIToolUsageMode.Enabled;
this.ai.systemPrompt = this.aiPrompt.executionInstruction();
this.ai.userInstruction = ""; // do not include user instruction for execution agent
this.ai.aiFunctionDefinitions = AIFunctionDefinitions.executionAgentDefinitions();
this.ai.aiToolDefinitions = AIToolDefinitions.executionAgentDefinitions();
}
protected override setDebugColor(): void {

View file

@ -1,18 +1,18 @@
import type { Conversation } from "Conversations/Conversation";
import type { IChatServiceCallbacks } from "../ChatService";
import { AIFunctionDefinitions } from "AIClasses/FunctionDefinitions/AIFunctionDefinitions";
import { AIToolDefinitions } from "AIClasses/FunctionDefinitions/AIToolDefinitions";
import { Exception } from "Helpers/Exception";
import { AIFunction, isAIFunction } from "Enums/AIFunction";
import { AITool, isAITool } from "Enums/AITool";
import { AgentType } from "Enums/AgentType";
import { BaseAgent } from "./BaseAgent";
import { ExecuteWorkflowArgsSchema, type ExecuteWorkflowArgs } from "AIClasses/Schemas/AIFunctionSchemas";
import { AIFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionResponse";
import type { AIFunctionCall } from "AIClasses/AIFunctionCall";
import { ExecuteWorkflowArgsSchema, type ExecuteWorkflowArgs } from "AIClasses/Schemas/AIToolSchemas";
import { AIToolResponse } from "AIClasses/FunctionDefinitions/AIToolResponse";
import type { AIToolCall } from "AIClasses/AIToolCall";
import { OrchestrationAgent } from "./OrchestrationAgent";
import { ConversationContent } from "Conversations/ConversationContent";
import { Role } from "Enums/Role";
import { DebugColor } from "Enums/DebugColor";
import { AIFunctionUsageMode } from "Enums/AIFunctionUsageMode";
import { AIToolUsageMode } from "Enums/AIToolUsageMode";
export class MainAgent extends BaseAgent {
@ -38,7 +38,7 @@ export class MainAgent extends BaseAgent {
const workflowResult = await orchestrationAgent.runPlannedWorkflow(result.planRequest, callbacks);
this.debugService?.log("MainAgent", "OrchestrationAgent workflow completed");
conversation.addFunctionResponse(new AIFunctionResponse(
conversation.addFunctionResponse(new AIToolResponse(
result.functionCall.name,
workflowResult,
result.functionCall.toolId
@ -51,20 +51,20 @@ export class MainAgent extends BaseAgent {
// the main agent loop - may return an execution plan if the agent has requested a planned workflow
private async runMainAgentLoop(conversation: Conversation, callbacks: IChatServiceCallbacks
): Promise<{ planRequest: ExecuteWorkflowArgs | undefined, functionCall: AIFunctionCall | undefined }> {
): Promise<{ planRequest: ExecuteWorkflowArgs | undefined, functionCall: AIToolCall | undefined }> {
let planRequest: ExecuteWorkflowArgs | undefined;
let planFunctionCall: AIFunctionCall | undefined;
let planFunctionCall: AIToolCall | undefined;
await this.runAgentLoop(AgentType.Main, conversation, callbacks, async functionCall => {
const functionCallName = functionCall.name;
if (isAIFunction(functionCallName, AIFunction.ExecuteWorkflow)) {
if (isAITool(functionCallName, AITool.ExecuteWorkflow)) {
this.debugService?.log("MainAgent", "ExecuteWorkflow function detected - transitioning to orchestration");
const parseResult = ExecuteWorkflowArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
conversation.addFunctionResponse(new AIFunctionResponse(
conversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ error: `Invalid arguments for ${AIFunction.ExecuteWorkflow}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AITool.ExecuteWorkflow}: ${parseResult.error.message}` },
functionCall.toolId
));
return { shouldExit: false };
@ -77,7 +77,7 @@ export class MainAgent extends BaseAgent {
this.debugService?.log("MainAgent", `Executing function: ${functionCall.name}`);
this.updateThought(functionCall, callbacks);
const functionResponse = await this.aiFunctionService.performAIFunction(functionCall);
const functionResponse = await this.aiToolService.performAITool(functionCall);
conversation.addFunctionResponse(functionResponse);
return { shouldExit: false };
});
@ -89,10 +89,10 @@ export class MainAgent extends BaseAgent {
Exception.throw("Error: No AI provider has been set!");
}
this.ai.agentType = AgentType.Main;
this.ai.aiFunctionUsageMode = AIFunctionUsageMode.Auto;
this.ai.aiToolUsageMode = AIToolUsageMode.Auto;
this.ai.systemPrompt = this.aiPrompt.systemInstruction();
this.ai.userInstruction = await this.aiPrompt.userInstruction();
this.ai.aiFunctionDefinitions = AIFunctionDefinitions.agentDefinitions(allowDestructiveActions, planningMode);
this.ai.aiToolDefinitions = AIToolDefinitions.agentDefinitions(allowDestructiveActions, planningMode);
}
protected override setDebugColor(): void {

View file

@ -1,4 +1,4 @@
import { CancelPlanArgsSchema, CompletePlanArgsSchema, CompleteStepArgsSchema, ReplanArgsSchema, type ExecuteWorkflowArgs } from "AIClasses/Schemas/AIFunctionSchemas";
import { CancelPlanArgsSchema, CompletePlanArgsSchema, CompleteStepArgsSchema, ReplanArgsSchema, type ExecuteWorkflowArgs } from "AIClasses/Schemas/AIToolSchemas";
import { BaseAgent } from "./BaseAgent";
import { ConversationContent } from "Conversations/ConversationContent";
import { Role } from "Enums/Role";
@ -8,13 +8,13 @@ import { Copy, replaceCopy } from "Enums/Copy";
import { Conversation } from "Conversations/Conversation";
import { PlanningAgent } from "./PlanningAgent";
import { Exception } from "Helpers/Exception";
import { AIFunctionDefinitions } from "AIClasses/FunctionDefinitions/AIFunctionDefinitions";
import { AIToolDefinitions } from "AIClasses/FunctionDefinitions/AIToolDefinitions";
import { OrchestrationResult } from "Types/OrchestrationResult";
import { AgentType } from "Enums/AgentType";
import { AIFunction, isAIFunction } from "Enums/AIFunction";
import { AIFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionResponse";
import { AITool, isAITool } from "Enums/AITool";
import { AIToolResponse } from "AIClasses/FunctionDefinitions/AIToolResponse";
import { DebugColor } from "Enums/DebugColor";
import { AIFunctionUsageMode } from "Enums/AIFunctionUsageMode";
import { AIToolUsageMode } from "Enums/AIToolUsageMode";
export class OrchestrationAgent extends BaseAgent {
@ -142,9 +142,9 @@ export class OrchestrationAgent extends BaseAgent {
await this.runAgentLoop(AgentType.Orchestration, planningConversation, callbacks, async functionCall => {
const functionCallName = functionCall.name;
if (!AIFunctionDefinitions.orchestrationAgentDefinitions().some(definition => isAIFunction(functionCallName, definition.name))) {
if (!AIToolDefinitions.orchestrationAgentDefinitions().some(definition => isAITool(functionCallName, definition.name))) {
this.debugService?.log("Orchestration", `Invalid tool call denied: ${functionCallName}`);
planningConversation.addFunctionResponse(new AIFunctionResponse(
planningConversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ message: Copy.OrchestrationToolDenial },
functionCall.toolId
@ -152,18 +152,18 @@ export class OrchestrationAgent extends BaseAgent {
return Promise.resolve({ shouldExit: false });
}
if (isAIFunction(functionCallName, AIFunction.CompleteStep)) {
if (isAITool(functionCallName, AITool.CompleteStep)) {
const parseResult = CompleteStepArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
planningConversation.addFunctionResponse(new AIFunctionResponse(
planningConversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ error: `Invalid arguments for ${AIFunction.CompleteStep}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AITool.CompleteStep}: ${parseResult.error.message}` },
functionCall.toolId
));
return Promise.resolve({ shouldExit: false });
}
if (!parseResult.data.confirm_completion) {
planningConversation.addFunctionResponse(new AIFunctionResponse(
planningConversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ error: "Confirmation was false, no action taken" },
functionCall.toolId
@ -172,7 +172,7 @@ export class OrchestrationAgent extends BaseAgent {
}
this.debugService?.log("Orchestration", `CompleteStep called (confirmed: ${parseResult.data.confirm_completion})`);
this.updateThought(functionCall, callbacks);
planningConversation.addFunctionResponse(new AIFunctionResponse(
planningConversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ message: "Step Completed" },
functionCall.toolId
@ -181,18 +181,18 @@ export class OrchestrationAgent extends BaseAgent {
return Promise.resolve({ shouldExit: true });
}
if (isAIFunction(functionCallName, AIFunction.CompletePlan)) {
if (isAITool(functionCallName, AITool.CompletePlan)) {
const parseResult = CompletePlanArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
planningConversation.addFunctionResponse(new AIFunctionResponse(
planningConversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ error: `Invalid arguments for ${AIFunction.CompletePlan}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AITool.CompletePlan}: ${parseResult.error.message}` },
functionCall.toolId
));
return Promise.resolve({ shouldExit: false });
}
if (!parseResult.data.confirm_completion) {
planningConversation.addFunctionResponse(new AIFunctionResponse(
planningConversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ error: "Confirmation was false, no action taken" },
functionCall.toolId
@ -201,7 +201,7 @@ export class OrchestrationAgent extends BaseAgent {
}
this.debugService?.log("Orchestration", `CompletePlan called (confirmed: ${parseResult.data.confirm_completion})`);
this.updateThought(functionCall, callbacks);
planningConversation.addFunctionResponse(new AIFunctionResponse(
planningConversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ message: "Plan Completed" },
functionCall.toolId
@ -210,19 +210,19 @@ export class OrchestrationAgent extends BaseAgent {
return Promise.resolve({ shouldExit: true });
}
if (isAIFunction(functionCallName, AIFunction.Replan)) {
if (isAITool(functionCallName, AITool.Replan)) {
const parseResult = ReplanArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
planningConversation.addFunctionResponse(new AIFunctionResponse(
planningConversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ error: `Invalid arguments for ${AIFunction.Replan}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AITool.Replan}: ${parseResult.error.message}` },
functionCall.toolId
));
return Promise.resolve({ shouldExit: false });
}
this.debugService?.log("Orchestration", `Replan requested: ${parseResult.data.context}`);
this.updateThought(functionCall, callbacks);
planningConversation.addFunctionResponse(new AIFunctionResponse(
planningConversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ message: "Replan Requested" },
functionCall.toolId
@ -231,19 +231,19 @@ export class OrchestrationAgent extends BaseAgent {
return Promise.resolve({ shouldExit: false });
}
if (isAIFunction(functionCallName, AIFunction.CancelPlan)) {
if (isAITool(functionCallName, AITool.CancelPlan)) {
const parseResult = CancelPlanArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
planningConversation.addFunctionResponse(new AIFunctionResponse(
planningConversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ error: `Invalid arguments for ${AIFunction.CancelPlan}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AITool.CancelPlan}: ${parseResult.error.message}` },
functionCall.toolId
));
return Promise.resolve({ shouldExit: false });
}
this.debugService?.log("Orchestration", `Plan cancellation requested: ${parseResult.data.context}`);
this.updateThought(functionCall, callbacks);
planningConversation.addFunctionResponse(new AIFunctionResponse(
planningConversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ message: "Plan Cancelled" },
functionCall.toolId
@ -276,10 +276,10 @@ export class OrchestrationAgent extends BaseAgent {
Exception.throw("Error: No AI provider has been set!");
}
this.ai.agentType = AgentType.Orchestration;
this.ai.aiFunctionUsageMode = AIFunctionUsageMode.Enabled;
this.ai.aiToolUsageMode = AIToolUsageMode.Enabled;
this.ai.systemPrompt = this.aiPrompt.orchestrationInstruction();
this.ai.userInstruction = ""; // do not include user instruction for orchestration agent
this.ai.aiFunctionDefinitions = AIFunctionDefinitions.orchestrationAgentDefinitions();
this.ai.aiToolDefinitions = AIToolDefinitions.orchestrationAgentDefinitions();
}
protected override setDebugColor(): void {

View file

@ -1,18 +1,18 @@
import { Conversation } from "Conversations/Conversation";
import { BaseAgent } from "./BaseAgent";
import { AskUserQuestionPlanningArgsSchema, SubmitPlanArgsSchema } from "AIClasses/Schemas/AIFunctionSchemas";
import { AskUserQuestionPlanningArgsSchema, SubmitPlanArgsSchema } from "AIClasses/Schemas/AIToolSchemas";
import { Exception } from "Helpers/Exception";
import type { IChatServiceCallbacks } from "Services/ChatService";
import { AgentType } from "Enums/AgentType";
import { AIFunction, isAIFunction } from "Enums/AIFunction";
import { AIFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionResponse";
import { AITool, isAITool } from "Enums/AITool";
import { AIToolResponse } from "AIClasses/FunctionDefinitions/AIToolResponse";
import { ExecutionPlan } from "Types/ExecutionPlan";
import { AIFunctionDefinitions } from "AIClasses/FunctionDefinitions/AIFunctionDefinitions";
import { AIToolDefinitions } from "AIClasses/FunctionDefinitions/AIToolDefinitions";
import { Copy } from "Enums/Copy";
import { ConversationContent } from "Conversations/ConversationContent";
import { Role } from "Enums/Role";
import { DebugColor } from "Enums/DebugColor";
import { AIFunctionUsageMode } from "Enums/AIFunctionUsageMode";
import { AIToolUsageMode } from "Enums/AIToolUsageMode";
export class PlanningAgent extends BaseAgent {
@ -35,9 +35,9 @@ export class PlanningAgent extends BaseAgent {
await this.runAgentLoop(AgentType.Planning, conversation, callbacks, async (functionCall) => {
const functionCallName = functionCall.name;
if (!AIFunctionDefinitions.planningAgentDefinitions().some(definition => isAIFunction(functionCallName, definition.name))) {
if (!AIToolDefinitions.planningAgentDefinitions().some(definition => isAITool(functionCallName, definition.name))) {
this.debugService?.log("PlanningAgent", `Invalid tool call denied: ${functionCallName}`);
conversation.addFunctionResponse(new AIFunctionResponse(
conversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ message: Copy.PlanningToolDenial },
functionCall.toolId
@ -45,12 +45,12 @@ export class PlanningAgent extends BaseAgent {
return { shouldExit: false };
}
if (isAIFunction(functionCallName, AIFunction.AskUserQuestionPlanning)) {
if (isAITool(functionCallName, AITool.AskUserQuestionPlanning)) {
const parseResult = AskUserQuestionPlanningArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
conversation.addFunctionResponse(new AIFunctionResponse(
conversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ error: `Invalid arguments for ${AIFunction.AskUserQuestionPlanning}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AITool.AskUserQuestionPlanning}: ${parseResult.error.message}` },
functionCall.toolId
));
return { shouldExit: false };
@ -59,7 +59,7 @@ export class PlanningAgent extends BaseAgent {
this.updateThought(functionCall, callbacks);
const answer = await callbacks.onUserQuestion(parseResult.data.question);
this.debugService?.log("PlanningAgent", "User answer received");
conversation.addFunctionResponse(new AIFunctionResponse(
conversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ answer: answer },
functionCall.toolId
@ -67,19 +67,19 @@ export class PlanningAgent extends BaseAgent {
return { shouldExit: false };
}
if (isAIFunction(functionCallName, AIFunction.SubmitPlan)) {
if (isAITool(functionCallName, AITool.SubmitPlan)) {
const parseResult = SubmitPlanArgsSchema.safeParse(functionCall.arguments);
if (!parseResult.success) {
conversation.addFunctionResponse(new AIFunctionResponse(
conversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ error: `Invalid arguments for ${AIFunction.SubmitPlan}: ${parseResult.error.message}` },
{ error: `Invalid arguments for ${AITool.SubmitPlan}: ${parseResult.error.message}` },
functionCall.toolId
));
return { shouldExit: false };
}
this.debugService?.log("PlanningAgent", `Plan submitted successfully with ${parseResult.data.steps.length} steps`);
capturedPlan = new ExecutionPlan(parseResult.data, isReplan);
conversation.addFunctionResponse(new AIFunctionResponse(
conversation.addFunctionResponse(new AIToolResponse(
functionCallName,
{ message: Copy.PlanReceived },
functionCall.toolId
@ -88,7 +88,7 @@ export class PlanningAgent extends BaseAgent {
}
this.updateThought(functionCall, callbacks);
const functionResponse = await this.aiFunctionService.performAIFunction(functionCall);
const functionResponse = await this.aiToolService.performAITool(functionCall);
conversation.addFunctionResponse(functionResponse);
return { shouldExit: false };
});
@ -111,10 +111,10 @@ export class PlanningAgent extends BaseAgent {
Exception.throw("Error: No AI provider has been set!");
}
this.ai.agentType = AgentType.Planning;
this.ai.aiFunctionUsageMode = AIFunctionUsageMode.Enabled;
this.ai.aiToolUsageMode = AIToolUsageMode.Enabled;
this.ai.systemPrompt = this.aiPrompt.planningInstruction();
this.ai.userInstruction = await this.aiPrompt.userInstruction();
this.ai.aiFunctionDefinitions = AIFunctionDefinitions.planningAgentDefinitions();
this.ai.aiToolDefinitions = AIToolDefinitions.planningAgentDefinitions();
}
protected override setDebugColor(): void {

View file

@ -11,7 +11,7 @@ import { StreamingMarkdownService } from "./StreamingMarkdownService";
import { FileSystemService } from "./FileSystemService";
import { ConversationFileSystemService } from "./ConversationFileSystemService";
import { ConversationHistoryModal } from "Modals/ConversationHistoryModal";
import { AIFunctionService } from "./AIServices/AIFunctionService";
import { AIToolService } from "./AIServices/AIToolService";
import { StreamingService } from "./StreamingService";
import { WorkSpaceService } from "./WorkSpaceService";
import { ChatService } from "./ChatService";
@ -67,7 +67,7 @@ export function RegisterDependencies() {
RegisterSingleton<ConversationNamingService>(Services.ConversationNamingService, new ConversationNamingService());
RegisterSingleton<IPrompt>(Services.IPrompt, new AIPrompt());
RegisterSingleton<AIFunctionService>(Services.AIFunctionService, new AIFunctionService());
RegisterSingleton<AIToolService>(Services.AIToolService, new AIToolService());
RegisterSingleton<MainAgent>(Services.MainAgent, new MainAgent());
RegisterSingleton<StreamingService>(Services.StreamingService, new StreamingService());
RegisterSingleton<ChatService>(Services.ChatService, new ChatService());

View file

@ -14,7 +14,7 @@ export class Services {
static StreamingService = Symbol("StreamingService");
static MarkdownService = Symbol("MarkdownService");
static StreamingMarkdownService = Symbol("StreamingMarkdownService");
static AIFunctionService = Symbol("AIFunctionService");
static AIToolService = Symbol("AIToolService");
static MainAgent = Symbol("MainAgent");
static ChatService = Symbol("ChatService");
static SanitiserService = Symbol("SanitiserService");

View file

@ -1,4 +1,4 @@
import type { AIFunctionCall } from "AIClasses/AIFunctionCall";
import type { AIToolCall } from "AIClasses/AIToolCall";
import { Event } from "Enums/Event";
import { Exception } from "Helpers/Exception";
import { ApiError, ApiErrorType } from "Types/ApiError";
@ -13,7 +13,7 @@ export interface IStreamChunk {
isComplete: boolean;
error?: string;
errorType?: ApiErrorType;
functionCall?: AIFunctionCall;
functionCall?: AIToolCall;
shouldContinue?: boolean;
}

View file

@ -15,7 +15,7 @@ import { DiffService } from "./DiffService";
import * as path from "path-browserify";
import { Event } from "Enums/Event";
import { AbortService } from "./AbortService";
import { AIFunctionResponse } from "AIClasses/FunctionDefinitions/AIFunctionResponse";
import { AIToolResponse } from "AIClasses/FunctionDefinitions/AIToolResponse";
import { FileType, isBinaryFile, isFileType } from "Enums/FileType";
import { readPDF } from "Helpers/PDFHelper";
@ -524,9 +524,9 @@ export class VaultService {
return await performChange();
}
let response = AIFunctionResponse.UserRejectionMessage;
let response = AIToolResponse.UserRejectionMessage;
if (result.suggestion) {
response = `${AIFunctionResponse.UserSuggestionMessage}\n${result.suggestion}`;
response = `${AIToolResponse.UserSuggestionMessage}\n${result.suggestion}`;
}
return Exception.new(response);
} catch (error) {

View file

@ -1,5 +1,5 @@
import type { ExecutionStep } from "./ExecutionStep";
import type { SubmitPlanArgs } from "AIClasses/Schemas/AIFunctionSchemas";
import type { SubmitPlanArgs } from "AIClasses/Schemas/AIToolSchemas";
export class ExecutionPlan {

View file

@ -1,29 +1,29 @@
import { describe, it, expect } from 'vitest';
import { AIFunctionCall } from '../../AIClasses/AIFunctionCall';
import { AIFunction } from '../../Enums/AIFunction';
import { AIToolCall } from '../../AIClasses/AIToolCall';
import { AITool } from '../../Enums/AITool';
describe('AIFunctionCall', () => {
describe('AIToolCall', () => {
describe('constructor', () => {
it('should create instance with name and arguments only', () => {
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
{ query: 'test' }
);
expect(functionCall.name).toBe(AIFunction.SearchVaultFiles);
expect(functionCall.name).toBe(AITool.SearchVaultFiles);
expect(functionCall.arguments).toEqual({ query: 'test' });
expect(functionCall.toolId).toBeUndefined();
expect(functionCall.thoughtSignature).toBeUndefined();
});
it('should create instance with name, arguments, and toolId', () => {
const functionCall = new AIFunctionCall(
AIFunction.ReadVaultFiles,
const functionCall = new AIToolCall(
AITool.ReadVaultFiles,
{ path: 'test.md' },
'tool-123'
);
expect(functionCall.name).toBe(AIFunction.ReadVaultFiles);
expect(functionCall.name).toBe(AITool.ReadVaultFiles);
expect(functionCall.arguments).toEqual({ path: 'test.md' });
expect(functionCall.toolId).toBe('tool-123');
expect(functionCall.thoughtSignature).toBeUndefined();
@ -31,14 +31,14 @@ describe('AIFunctionCall', () => {
it('should create instance with all four parameters including thoughtSignature', () => {
const signature = 'base64EncodedSignature==';
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
{ query: 'notes' },
undefined,
signature
);
expect(functionCall.name).toBe(AIFunction.SearchVaultFiles);
expect(functionCall.name).toBe(AITool.SearchVaultFiles);
expect(functionCall.arguments).toEqual({ query: 'notes' });
expect(functionCall.toolId).toBeUndefined();
expect(functionCall.thoughtSignature).toBe(signature);
@ -46,26 +46,26 @@ describe('AIFunctionCall', () => {
it('should create instance with toolId and thoughtSignature', () => {
const signature = 'aGVsbG8gd29ybGQ=';
const functionCall = new AIFunctionCall(
AIFunction.WriteVaultFile,
const functionCall = new AIToolCall(
AITool.WriteVaultFile,
{ path: 'note.md', content: 'Hello' },
'tool-456',
signature
);
expect(functionCall.name).toBe(AIFunction.WriteVaultFile);
expect(functionCall.name).toBe(AITool.WriteVaultFile);
expect(functionCall.arguments).toEqual({ path: 'note.md', content: 'Hello' });
expect(functionCall.toolId).toBe('tool-456');
expect(functionCall.thoughtSignature).toBe(signature);
});
it('should handle empty arguments object', () => {
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
{}
);
expect(functionCall.name).toBe(AIFunction.SearchVaultFiles);
expect(functionCall.name).toBe(AITool.SearchVaultFiles);
expect(functionCall.arguments).toEqual({});
});
@ -78,8 +78,8 @@ describe('AIFunctionCall', () => {
limit: 10
};
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
complexArgs
);
@ -89,8 +89,8 @@ describe('AIFunctionCall', () => {
describe('toConversationString', () => {
it('should serialize to JSON with only name and args when no optional fields', () => {
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
{ query: 'test' }
);
@ -99,7 +99,7 @@ describe('AIFunctionCall', () => {
expect(parsed).toEqual({
functionCall: {
name: AIFunction.SearchVaultFiles,
name: AITool.SearchVaultFiles,
args: { query: 'test' },
id: undefined,
thoughtSignature: undefined
@ -108,8 +108,8 @@ describe('AIFunctionCall', () => {
});
it('should serialize with toolId when present', () => {
const functionCall = new AIFunctionCall(
AIFunction.ReadVaultFiles,
const functionCall = new AIToolCall(
AITool.ReadVaultFiles,
{ path: 'note.md' },
'tool-789'
);
@ -119,7 +119,7 @@ describe('AIFunctionCall', () => {
expect(parsed).toEqual({
functionCall: {
name: AIFunction.ReadVaultFiles,
name: AITool.ReadVaultFiles,
args: { path: 'note.md' },
id: 'tool-789',
thoughtSignature: undefined
@ -129,8 +129,8 @@ describe('AIFunctionCall', () => {
it('should serialize with thoughtSignature when present', () => {
const signature = 'dGVzdFNpZ25hdHVyZQ==';
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
{ query: 'notes' },
undefined,
signature
@ -141,7 +141,7 @@ describe('AIFunctionCall', () => {
expect(parsed).toEqual({
functionCall: {
name: AIFunction.SearchVaultFiles,
name: AITool.SearchVaultFiles,
args: { query: 'notes' },
id: undefined,
thoughtSignature: signature
@ -151,8 +151,8 @@ describe('AIFunctionCall', () => {
it('should serialize with both toolId and thoughtSignature when present', () => {
const signature = 'YW5vdGhlclNpZ25hdHVyZQ==';
const functionCall = new AIFunctionCall(
AIFunction.WriteVaultFile,
const functionCall = new AIToolCall(
AITool.WriteVaultFile,
{ path: 'file.md', content: 'content' },
'tool-999',
signature
@ -163,7 +163,7 @@ describe('AIFunctionCall', () => {
expect(parsed).toEqual({
functionCall: {
name: AIFunction.WriteVaultFile,
name: AITool.WriteVaultFile,
args: { path: 'file.md', content: 'content' },
id: 'tool-999',
thoughtSignature: signature
@ -172,8 +172,8 @@ describe('AIFunctionCall', () => {
});
it('should produce valid JSON string', () => {
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
{ query: 'test' },
'tool-123',
'c2lnbmF0dXJl'
@ -185,8 +185,8 @@ describe('AIFunctionCall', () => {
});
it('should handle special characters in arguments', () => {
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
{ query: 'test "quotes" and \'apostrophes\' and\nnewlines' }
);
@ -197,8 +197,8 @@ describe('AIFunctionCall', () => {
});
it('should handle empty string thoughtSignature', () => {
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
{ query: 'test' },
undefined,
''
@ -213,19 +213,19 @@ describe('AIFunctionCall', () => {
describe('properties', () => {
it('should have immutable name property', () => {
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
{ query: 'test' }
);
// Readonly is enforced at TypeScript compile time
// At runtime, the properties are accessible
expect(functionCall.name).toBe(AIFunction.SearchVaultFiles);
expect(functionCall.name).toBe(AITool.SearchVaultFiles);
});
it('should have immutable arguments property', () => {
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
{ query: 'test' }
);
@ -233,8 +233,8 @@ describe('AIFunctionCall', () => {
});
it('should have immutable toolId property', () => {
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
{ query: 'test' },
'tool-123'
);
@ -243,8 +243,8 @@ describe('AIFunctionCall', () => {
});
it('should have immutable thoughtSignature property', () => {
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
{ query: 'test' },
undefined,
'signature'
@ -257,8 +257,8 @@ describe('AIFunctionCall', () => {
describe('edge cases', () => {
it('should handle very long thoughtSignature (realistic base64)', () => {
const longSignature = 'A'.repeat(10000);
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
{ query: 'test' },
undefined,
longSignature
@ -270,8 +270,8 @@ describe('AIFunctionCall', () => {
it('should handle thoughtSignature with base64 special characters', () => {
const base64Signature = 'SGVsbG8gV29ybGQ+Pz8/Pz8+Pg==';
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
{ query: 'test' },
undefined,
base64Signature
@ -281,8 +281,8 @@ describe('AIFunctionCall', () => {
});
it('should handle undefined toolId and defined thoughtSignature', () => {
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
{ query: 'test' },
undefined,
'signature'
@ -293,8 +293,8 @@ describe('AIFunctionCall', () => {
});
it('should handle defined toolId and undefined thoughtSignature', () => {
const functionCall = new AIFunctionCall(
AIFunction.SearchVaultFiles,
const functionCall = new AIToolCall(
AITool.SearchVaultFiles,
{ query: 'test' },
'tool-123',
undefined

View file

@ -5,7 +5,7 @@ import { Services } from '../../Services/Services';
import { StreamingService } from '../../Services/StreamingService';
import type { IPrompt } from '../../AIPrompts/IPrompt';
import type VaultkeeperAIPlugin from '../../main';
import { AIFunctionDefinitions } from '../../AIClasses/FunctionDefinitions/AIFunctionDefinitions';
import { AIToolDefinitions } from '../../AIClasses/FunctionDefinitions/AIToolDefinitions';
import { Conversation } from '../../Conversations/Conversation';
import { ConversationContent } from '../../Conversations/ConversationContent';
import { Role } from '../../Enums/Role';
@ -118,7 +118,7 @@ describe('Claude', () => {
expect(plugin).toBe(mockPlugin);
expect(settingsService).toBe(mockSettingsService);
expect(streaming).toBe(mockStreamingService);
expect(AIFunctionDefinitions.agentDefinitions).toBeDefined();
expect(AIToolDefinitions.agentDefinitions).toBeDefined();
});
});
@ -1157,7 +1157,7 @@ describe('Claude', () => {
// Set system prompts before calling streamRequest
claude.systemPrompt = 'System instruction';
claude.userInstruction = 'User instruction';
claude.aiFunctionDefinitions = [];
claude.aiToolDefinitions = [];
mockStreamingService.streamRequest.mockImplementation(async function* () {
yield { content: 'response', isComplete: true };

View file

@ -100,7 +100,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
displayContent: '',
functionCall: JSON.stringify({
functionCall: {
id: 'call_abc123', // AIFunctionCall.toConversationString() includes id in JSON
id: 'call_abc123', // AIToolCall.toConversationString() includes id in JSON
name: 'search_vault_files',
args: { query: 'meeting notes' }
}
@ -122,13 +122,13 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
it('should convert OpenAI function call (no thoughtSignature) to legacy text format', async () => {
// Simulate a conversation started with OpenAI
const openaiFunctionCall = new ConversationContent({
const openaiToolCall = new ConversationContent({
role: Role.Assistant,
content: '',
displayContent: '',
functionCall: JSON.stringify({
functionCall: {
id: 'call_xyz789', // AIFunctionCall.toConversationString() includes id in JSON
id: 'call_xyz789', // AIToolCall.toConversationString() includes id in JSON
name: 'read_file',
args: { path: 'project.md' }
}
@ -138,7 +138,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
toolId: 'call_xyz789'
});
const result = await (gemini as any).extractContents([openaiFunctionCall]);
const result = await (gemini as any).extractContents([openaiToolCall]);
expect(result[0].parts[0].text).toContain('<!-- Historical tool call');
expect(result[0].parts[0].text).toContain('"name": "read_file"');
@ -231,7 +231,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
displayContent: '',
functionCall: JSON.stringify({
functionCall: {
id: 'call_claude_123', // AIFunctionCall.toConversationString() includes id
id: 'call_claude_123', // AIToolCall.toConversationString() includes id
name: 'search_vault_files',
args: { query: 'project' }
}
@ -340,7 +340,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
displayContent: '',
functionCall: JSON.stringify({
functionCall: {
id: 'call_openai_456', // AIFunctionCall.toConversationString() includes id
id: 'call_openai_456', // AIToolCall.toConversationString() includes id
name: 'list_files',
args: {}
}
@ -394,7 +394,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
displayContent: '',
functionCall: JSON.stringify({
functionCall: {
id: 'call-1', // AIFunctionCall.toConversationString() includes id
id: 'call-1', // AIToolCall.toConversationString() includes id
name: 'func1',
args: { a: 1 }
}
@ -540,7 +540,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
it('should handle OpenAI function call when switching to Claude', async () => {
// Simulate a conversation started with OpenAI
const openaiFunctionCall = new ConversationContent({
const openaiToolCall = new ConversationContent({
role: Role.Assistant,
content: '',
displayContent: '',
@ -576,7 +576,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
// Now switch to Claude - it should read OpenAI's function call
const claude = new Claude();
const result = await (claude as any).extractContents([openaiFunctionCall, openaiResponse]);
const result = await (claude as any).extractContents([openaiToolCall, openaiResponse]);
// Claude should convert OpenAI's function call to its tool_use format
expect(result).toHaveLength(2);
@ -694,8 +694,8 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
expect(openaiResult.length).toBeGreaterThan(0);
// Both function calls should be present in Responses API format
const openaiFunctionCalls = openaiResult.filter((r: any) => r.type === 'function_call');
expect(openaiFunctionCalls).toHaveLength(2);
const openaiToolCalls = openaiResult.filter((r: any) => r.type === 'function_call');
expect(openaiToolCalls).toHaveLength(2);
});
it('should handle function call with toolId but empty string', async () => {
@ -760,7 +760,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
it('should handle OpenAI function call when switching to Claude', async () => {
// Detailed test already covered in "Claude ↔ OpenAI Switching"
// This test focuses on verifying Claude properly interprets OpenAI's call_id
const openaiFunctionCall = new ConversationContent({
const openaiToolCall = new ConversationContent({
role: Role.Assistant,
content: 'Let me read that file for you',
displayContent: '',
@ -777,7 +777,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
});
const claude = new Claude();
const result = await (claude as any).extractContents([openaiFunctionCall]);
const result = await (claude as any).extractContents([openaiToolCall]);
// Claude should preserve the OpenAI call_id in its tool_use format
expect(result).toHaveLength(1);
@ -794,7 +794,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
it('should convert OpenAI function call (no thoughtSignature) to Gemini legacy format', async () => {
// OpenAI function call should be converted to legacy text when sent to Gemini
const openaiFunctionCall = new ConversationContent({
const openaiToolCall = new ConversationContent({
role: Role.Assistant,
content: '',
displayContent: '',
@ -810,7 +810,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
toolId: 'call_abc'
});
const result = await (gemini as any).extractContents([openaiFunctionCall]);
const result = await (gemini as any).extractContents([openaiToolCall]);
// Gemini should convert to legacy format since there's no thoughtSignature
expect(result).toHaveLength(1);
@ -1499,10 +1499,10 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
);
expect(claudeToolUses.length).toBe(3);
const openaiFunctionCalls = openaiResult.filter((r: any) =>
const openaiToolCalls = openaiResult.filter((r: any) =>
r.type === 'function_call' || r.type === 'message'
);
expect(openaiFunctionCalls.length).toBeGreaterThan(0);
expect(openaiToolCalls.length).toBeGreaterThan(0);
// Gemini should have 1 native call (with signature) and 2 legacy calls
const geminiNativeCalls = geminiResult.filter((r: any) =>
@ -1661,7 +1661,7 @@ describe('Cross-Provider Integration - Thought Signature Support', () => {
it('should handle inconsistent state: toolId set but id missing from JSON (data corruption)', async () => {
// Edge case: metadata field has toolId but the serialized JSON doesn't have id
// This should never happen in production (AIFunctionCall prevents this)
// This should never happen in production (AIToolCall prevents this)
// but tests defensive behavior
const content = new ConversationContent({
role: Role.Assistant,

View file

@ -410,7 +410,7 @@ describe('Gemini', () => {
// Set system prompts before calling streamRequest
gemini.systemPrompt = 'System instruction';
gemini.userInstruction = 'User instruction';
gemini.aiFunctionDefinitions = [];
gemini.aiToolDefinitions = [];
mockStreamingService.streamRequest.mockImplementation(async function* () {
yield { content: 'done', isComplete: true };

View file

@ -311,7 +311,7 @@ describe('OpenAI', () => {
// Set system prompts before calling streamRequest
openai.systemPrompt = 'System instruction';
openai.userInstruction = 'User instruction';
openai.aiFunctionDefinitions = [];
openai.aiToolDefinitions = [];
mockStreamingService.streamRequest.mockImplementation(async function* () {
yield { content: 'response', isComplete: true };

View file

@ -2,8 +2,8 @@ import { describe, it, expect } from 'vitest';
import { Conversation } from '../../Conversations/Conversation';
import { ConversationContent } from '../../Conversations/ConversationContent';
import { Role } from '../../Enums/Role';
import { AIFunctionResponse } from '../../AIClasses/FunctionDefinitions/AIFunctionResponse';
import { AIFunction } from '../../Enums/AIFunction';
import { AIToolResponse } from '../../AIClasses/FunctionDefinitions/AIToolResponse';
import { AITool } from '../../Enums/AITool';
describe('Conversation', () => {
describe('constructor', () => {
@ -385,8 +385,8 @@ describe('Conversation', () => {
describe('read_vault_files with errors', () => {
it('should handle file not found errors without creating attachments', () => {
const conversation = new Conversation();
const functionResponse = new AIFunctionResponse(
AIFunction.ReadVaultFiles,
const functionResponse = new AIToolResponse(
AITool.ReadVaultFiles,
{
results: [
{ path: 'nonexistent.md', error: 'File does not exist: nonexistent.md' }
@ -413,8 +413,8 @@ describe('Conversation', () => {
it('should handle mix of successful reads and errors', () => {
const conversation = new Conversation();
const functionResponse = new AIFunctionResponse(
AIFunction.ReadVaultFiles,
const functionResponse = new AIToolResponse(
AITool.ReadVaultFiles,
{
results: [
{ path: 'existing.md', type: 'md', contents: '# Hello' },
@ -451,8 +451,8 @@ describe('Conversation', () => {
it('should not create attachments for error results with binary file extensions', () => {
const conversation = new Conversation();
const functionResponse = new AIFunctionResponse(
AIFunction.ReadVaultFiles,
const functionResponse = new AIToolResponse(
AITool.ReadVaultFiles,
{
results: [
{ path: 'image.png', error: 'File does not exist: image.png' }
@ -476,8 +476,8 @@ describe('Conversation', () => {
it('should handle binary files with errors not creating invalid attachments', () => {
const conversation = new Conversation();
const functionResponse = new AIFunctionResponse(
AIFunction.ReadVaultFiles,
const functionResponse = new AIToolResponse(
AITool.ReadVaultFiles,
{
results: [
{ path: 'image.png', error: 'File does not exist: image.png' },
@ -506,8 +506,8 @@ describe('Conversation', () => {
describe('read_vault_files without errors', () => {
it('should handle successful text file reads', () => {
const conversation = new Conversation();
const functionResponse = new AIFunctionResponse(
AIFunction.ReadVaultFiles,
const functionResponse = new AIToolResponse(
AITool.ReadVaultFiles,
{
results: [
{ path: 'file.md', type: 'md', contents: '# Title' }
@ -525,8 +525,8 @@ describe('Conversation', () => {
it('should create attachments for binary files', () => {
const conversation = new Conversation();
const functionResponse = new AIFunctionResponse(
AIFunction.ReadVaultFiles,
const functionResponse = new AIToolResponse(
AITool.ReadVaultFiles,
{
results: [
{ path: 'image.png', type: 'png', contents: 'base64data...' }
@ -547,8 +547,8 @@ describe('Conversation', () => {
describe('non-read_vault_files functions', () => {
it('should handle other functions normally', () => {
const conversation = new Conversation();
const functionResponse = new AIFunctionResponse(
AIFunction.WriteVaultFile,
const functionResponse = new AIToolResponse(
AITool.WriteVaultFile,
{ success: true },
'tool-123'
);

View file

@ -5,9 +5,9 @@ import { Services } from '../../Services/Services';
import { Conversation } from '../../Conversations/Conversation';
import { ConversationContent } from '../../Conversations/ConversationContent';
import { Role } from '../../Enums/Role';
import { AIFunction } from '../../Enums/AIFunction';
import { AIFunctionCall } from '../../AIClasses/AIFunctionCall';
import { AIFunctionResponse } from '../../AIClasses/FunctionDefinitions/AIFunctionResponse';
import { AITool } from '../../Enums/AITool';
import { AIToolCall } from '../../AIClasses/AIToolCall';
import { AIToolResponse } from '../../AIClasses/FunctionDefinitions/AIToolResponse';
/**
* INTEGRATION TESTS - AIControllerService
@ -24,7 +24,7 @@ describe('AIControllerService - Integration Tests', () => {
let service: MainAgent;
let mockAI: any;
let mockPrompt: any;
let mockAIFunctionService: any;
let mockAIToolService: any;
beforeEach(() => {
// Mock IPrompt
@ -35,13 +35,13 @@ describe('AIControllerService - Integration Tests', () => {
};
RegisterSingleton(Services.IPrompt, mockPrompt);
// Mock AIFunctionService
mockAIFunctionService = {
performAIFunction: vi.fn().mockResolvedValue(
new AIFunctionResponse(AIFunction.SearchVaultFiles, { results: [] }, 'test-tool-id')
// Mock AIToolService
mockAIToolService = {
performAITool: vi.fn().mockResolvedValue(
new AIToolResponse(AITool.SearchVaultFiles, { results: [] }, 'test-tool-id')
)
};
RegisterSingleton(Services.AIFunctionService, mockAIFunctionService);
RegisterSingleton(Services.AIToolService, mockAIToolService);
// Mock IAIClass
mockAI = {
@ -190,8 +190,8 @@ describe('AIControllerService - Integration Tests', () => {
// First call returns a function call
yield {
content: 'Let me search',
functionCall: new AIFunctionCall(
AIFunction.SearchVaultFiles,
functionCall: new AIToolCall(
AITool.SearchVaultFiles,
{ search_terms: ['test'], user_message: 'Searching' },
'tool-1'
),
@ -207,7 +207,7 @@ describe('AIControllerService - Integration Tests', () => {
await service.runMainAgent(conversation, true, false, callbacks);
// Verify function was called
expect(mockAIFunctionService.performAIFunction).toHaveBeenCalledTimes(1);
expect(mockAIToolService.performAITool).toHaveBeenCalledTimes(1);
expect(callCount).toBe(2);
});
@ -235,8 +235,8 @@ describe('AIControllerService - Integration Tests', () => {
if (callCount === 1) {
yield {
content: '',
functionCall: new AIFunctionCall(
AIFunction.SearchVaultFiles,
functionCall: new AIToolCall(
AITool.SearchVaultFiles,
{ search_terms: ['notes'], user_message: 'Searching for notes' },
'tool-1'
),
@ -349,8 +349,8 @@ describe('AIControllerService - Integration Tests', () => {
if (callCount === 1) {
yield {
content: '',
functionCall: new AIFunctionCall(
AIFunction.SearchVaultFiles,
functionCall: new AIToolCall(
AITool.SearchVaultFiles,
{ search_terms: ['test'], user_message: 'Searching' },
'tool-1'
),

View file

@ -1,14 +1,14 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { AIFunctionService } from '../../Services/AIServices/AIFunctionService';
import { AIToolService } from '../../Services/AIServices/AIToolService';
import { RegisterSingleton, DeregisterAllServices } from '../../Services/DependencyService';
import { Services } from '../../Services/Services';
import { AIFunction } from '../../Enums/AIFunction';
import { AITool } from '../../Enums/AITool';
import { TFile } from 'obsidian';
import { Exception } from '../../Helpers/Exception';
import { AbortService } from '../../Services/AbortService';
/**
* INTEGRATION TESTS - AIFunctionService
* INTEGRATION TESTS - AIToolService
*
* Tests the AI function dispatcher with real FileSystemService integration.
* Mocks only the Obsidian API layer (VaultService).
@ -23,8 +23,8 @@ import { AbortService } from '../../Services/AbortService';
* - RequestWebSearch (Gemini only)
*/
describe('AIFunctionService - Integration Tests', () => {
let service: AIFunctionService;
describe('AIToolService - Integration Tests', () => {
let service: AIToolService;
let mockFileSystemService: any;
let abortService: AbortService;
@ -51,7 +51,7 @@ describe('AIFunctionService - Integration Tests', () => {
vi.spyOn(Exception, 'log').mockImplementation(() => {});
// Create service - it will resolve the mock FileSystemService and real AbortService
service = new AIFunctionService();
service = new AIToolService();
});
afterEach(() => {
@ -70,7 +70,7 @@ describe('AIFunctionService - Integration Tests', () => {
return file;
}
describe('performAIFunction - SearchVaultFiles', () => {
describe('performAITool - SearchVaultFiles', () => {
it('should return search results with snippets', async () => {
const mockMatches = [
{
@ -90,13 +90,13 @@ describe('AIFunctionService - Integration Tests', () => {
mockFileSystemService.searchVaultFiles.mockResolvedValue(mockMatches);
const result = await service.performAIFunction({
name: AIFunction.SearchVaultFiles,
const result = await service.performAITool({
name: AITool.SearchVaultFiles,
arguments: { search_terms: ['test'], user_message: 'test search' },
toolId: 'tool_1'
} as any);
expect(result.name).toBe(AIFunction.SearchVaultFiles);
expect(result.name).toBe(AITool.SearchVaultFiles);
expect(result.toolId).toBe('tool_1');
expect(result.response).toEqual([{searchTerm: 'test', results: [
{
@ -119,8 +119,8 @@ describe('AIFunctionService - Integration Tests', () => {
// Mock returns empty array for empty search term
mockFileSystemService.searchVaultFiles.mockResolvedValue([]);
const result = await service.performAIFunction({
name: AIFunction.SearchVaultFiles,
const result = await service.performAITool({
name: AITool.SearchVaultFiles,
arguments: { search_terms: [''], user_message: 'test search' },
toolId: 'tool_2'
} as any);
@ -133,8 +133,8 @@ describe('AIFunctionService - Integration Tests', () => {
// Mock returns empty array for whitespace search term
mockFileSystemService.searchVaultFiles.mockResolvedValue([]);
const result = await service.performAIFunction({
name: AIFunction.SearchVaultFiles,
const result = await service.performAITool({
name: AITool.SearchVaultFiles,
arguments: { search_terms: [' '], user_message: 'test search' },
toolId: 'tool_3'
} as any);
@ -146,8 +146,8 @@ describe('AIFunctionService - Integration Tests', () => {
it('should return empty array when no matches found', async () => {
mockFileSystemService.searchVaultFiles.mockResolvedValue([]);
const result = await service.performAIFunction({
name: AIFunction.SearchVaultFiles,
const result = await service.performAITool({
name: AITool.SearchVaultFiles,
arguments: { search_terms: ['nonexistent'], user_message: 'test search' },
toolId: 'tool_4'
} as any);
@ -166,8 +166,8 @@ describe('AIFunctionService - Integration Tests', () => {
mockFileSystemService.searchVaultFiles.mockResolvedValue(mockMatches);
const result = await service.performAIFunction({
name: AIFunction.SearchVaultFiles,
const result = await service.performAITool({
name: AITool.SearchVaultFiles,
arguments: { search_terms: ['single'], user_message: 'test search' },
toolId: 'tool_5'
} as any);
@ -179,15 +179,15 @@ describe('AIFunctionService - Integration Tests', () => {
});
});
describe('performAIFunction - ReadVaultFiles', () => {
describe('performAITool - ReadVaultFiles', () => {
it('should read multiple files successfully', async () => {
mockFileSystemService.readFile
.mockResolvedValueOnce('Content of file 1')
.mockResolvedValueOnce('Content of file 2')
.mockResolvedValueOnce('Content of file 3');
const result = await service.performAIFunction({
name: AIFunction.ReadVaultFiles,
const result = await service.performAITool({
name: AITool.ReadVaultFiles,
arguments: { file_paths: ['file1.md', 'file2.md', 'file3.md'], user_message: 'test search' },
toolId: 'tool_6'
} as any);
@ -210,8 +210,8 @@ describe('AIFunctionService - Integration Tests', () => {
.mockResolvedValueOnce(error1)
.mockResolvedValueOnce(error2);
const result = await service.performAIFunction({
name: AIFunction.ReadVaultFiles,
const result = await service.performAITool({
name: AITool.ReadVaultFiles,
arguments: { file_paths: ['exists.md', 'missing1.md', 'missing2.md'], user_message: 'test search' },
toolId: 'tool_7'
} as any);
@ -231,8 +231,8 @@ describe('AIFunctionService - Integration Tests', () => {
.mockResolvedValueOnce(new Error('File not found'))
.mockResolvedValueOnce('Content B');
const result = await service.performAIFunction({
name: AIFunction.ReadVaultFiles,
const result = await service.performAITool({
name: AITool.ReadVaultFiles,
arguments: { file_paths: ['a.md', 'missing.md', 'b.md'], user_message: 'test search' },
toolId: 'tool_8'
} as any);
@ -244,8 +244,8 @@ describe('AIFunctionService - Integration Tests', () => {
});
it('should handle empty file list', async () => {
const result = await service.performAIFunction({
name: AIFunction.ReadVaultFiles,
const result = await service.performAITool({
name: AITool.ReadVaultFiles,
arguments: { file_paths: [], user_message: 'test search' },
toolId: 'tool_9'
} as any);
@ -256,8 +256,8 @@ describe('AIFunctionService - Integration Tests', () => {
it('should handle single file read', async () => {
mockFileSystemService.readFile.mockResolvedValue('Single file content');
const result = await service.performAIFunction({
name: AIFunction.ReadVaultFiles,
const result = await service.performAITool({
name: AITool.ReadVaultFiles,
arguments: { file_paths: ['single.md'], user_message: 'test search' },
toolId: 'tool_10'
} as any);
@ -267,12 +267,12 @@ describe('AIFunctionService - Integration Tests', () => {
});
});
describe('performAIFunction - WriteVaultFile', () => {
describe('performAITool - WriteVaultFile', () => {
it('should write file successfully', async () => {
mockFileSystemService.writeFile.mockResolvedValue(undefined);
const result = await service.performAIFunction({
name: AIFunction.WriteVaultFile,
const result = await service.performAITool({
name: AITool.WriteVaultFile,
arguments: {
file_path: 'notes/new-note.md',
content: '# New Note\n\nContent here',
@ -292,8 +292,8 @@ describe('AIFunctionService - Integration Tests', () => {
const error = new Error('Permission denied');
mockFileSystemService.writeFile.mockResolvedValue(error);
const result = await service.performAIFunction({
name: AIFunction.WriteVaultFile,
const result = await service.performAITool({
name: AITool.WriteVaultFile,
arguments: {
file_path: 'protected.md',
content: 'Content',
@ -309,8 +309,8 @@ describe('AIFunctionService - Integration Tests', () => {
it('should normalize file path', async () => {
mockFileSystemService.writeFile.mockResolvedValue(undefined);
await service.performAIFunction({
name: AIFunction.WriteVaultFile,
await service.performAITool({
name: AITool.WriteVaultFile,
arguments: {
file_path: 'folder\\subfolder\\file.md',
content: 'Content',
@ -329,8 +329,8 @@ describe('AIFunctionService - Integration Tests', () => {
it('should handle empty content', async () => {
mockFileSystemService.writeFile.mockResolvedValue(undefined);
const result = await service.performAIFunction({
name: AIFunction.WriteVaultFile,
const result = await service.performAITool({
name: AITool.WriteVaultFile,
arguments: {
file_path: 'empty.md',
content: '',
@ -344,15 +344,15 @@ describe('AIFunctionService - Integration Tests', () => {
});
});
describe('performAIFunction - PatchVaultFile', () => {
describe('performAITool - PatchVaultFile', () => {
it('should apply patch successfully', async () => {
mockFileSystemService.patchFile.mockResolvedValue(createMockFile('notes/test.md', 'test'));
const oldContent = 'old content';
const newContent = 'new content';
const result = await service.performAIFunction({
name: AIFunction.PatchVaultFile,
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'notes/test.md',
oldContent: oldContent,
@ -373,8 +373,8 @@ describe('AIFunctionService - Integration Tests', () => {
const oldContent = 'old content';
const newContent = 'new content';
const result = await service.performAIFunction({
name: AIFunction.PatchVaultFile,
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'notes/test.md',
oldContent: oldContent,
@ -394,8 +394,8 @@ describe('AIFunctionService - Integration Tests', () => {
const oldContent = 'old';
const newContent = 'new';
await service.performAIFunction({
name: AIFunction.PatchVaultFile,
await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'folder\\subfolder\\file.md',
oldContent: oldContent,
@ -420,8 +420,8 @@ describe('AIFunctionService - Integration Tests', () => {
const oldContent = 'old';
const newContent = 'new';
const result = await service.performAIFunction({
name: AIFunction.PatchVaultFile,
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'missing.md',
oldContent: oldContent,
@ -441,8 +441,8 @@ describe('AIFunctionService - Integration Tests', () => {
const oldContent = '# Title\nOld line 1\nContext line';
const newContent = '# Title\nNew line 1\nContext line';
const result = await service.performAIFunction({
name: AIFunction.PatchVaultFile,
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'complex.md',
oldContent: oldContent,
@ -462,8 +462,8 @@ describe('AIFunctionService - Integration Tests', () => {
const oldContent = '# Title\nExisting content';
const newContent = '# Title\nExisting content\nNew line 1\nNew line 2';
const result = await service.performAIFunction({
name: AIFunction.PatchVaultFile,
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'additions.md',
oldContent: oldContent,
@ -482,8 +482,8 @@ describe('AIFunctionService - Integration Tests', () => {
const oldContent = '# Title\nLine to remove 1\nLine to remove 2\nRemaining content';
const newContent = '# Title\nRemaining content';
const result = await service.performAIFunction({
name: AIFunction.PatchVaultFile,
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'deletions.md',
oldContent: oldContent,
@ -503,8 +503,8 @@ describe('AIFunctionService - Integration Tests', () => {
const oldContent = 'old';
const newContent = 'new';
const result = await service.performAIFunction({
name: AIFunction.PatchVaultFile,
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'protected.md',
oldContent: oldContent,
@ -524,8 +524,8 @@ describe('AIFunctionService - Integration Tests', () => {
const oldContent = 'old';
const newContent = 'new';
const result = await service.performAIFunction({
name: AIFunction.PatchVaultFile,
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'test.md',
oldContent: oldContent,
@ -536,12 +536,12 @@ describe('AIFunctionService - Integration Tests', () => {
} as any);
expect(result.toolId).toBe('unique_tool_id_123');
expect(result.name).toBe(AIFunction.PatchVaultFile);
expect(result.name).toBe(AITool.PatchVaultFile);
});
it('should handle invalid arguments', async () => {
const result = await service.performAIFunction({
name: AIFunction.PatchVaultFile,
const result = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
// missing required fields
file_path: 'test.md'
@ -555,15 +555,15 @@ describe('AIFunctionService - Integration Tests', () => {
});
});
describe('performAIFunction - DeleteVaultFiles', () => {
describe('performAITool - DeleteVaultFiles', () => {
it('should delete multiple files successfully', async () => {
mockFileSystemService.deleteFile
.mockResolvedValueOnce({ success: true })
.mockResolvedValueOnce({ success: true })
.mockResolvedValueOnce({ success: true });
const result = await service.performAIFunction({
name: AIFunction.DeleteVaultFiles,
const result = await service.performAITool({
name: AITool.DeleteVaultFiles,
arguments: {
file_paths: ['file1.md', 'file2.md', 'file3.md'],
confirm_deletion: true,
@ -582,8 +582,8 @@ describe('AIFunctionService - Integration Tests', () => {
});
it('should reject deletion when confirmation is false', async () => {
const result = await service.performAIFunction({
name: AIFunction.DeleteVaultFiles,
const result = await service.performAITool({
name: AITool.DeleteVaultFiles,
arguments: {
file_paths: ['file1.md', 'file2.md'],
confirm_deletion: false,
@ -606,8 +606,8 @@ describe('AIFunctionService - Integration Tests', () => {
.mockResolvedValueOnce(error)
.mockResolvedValueOnce(undefined); // void = success
const result = await service.performAIFunction({
name: AIFunction.DeleteVaultFiles,
const result = await service.performAITool({
name: AITool.DeleteVaultFiles,
arguments: {
file_paths: ['a.md', 'missing.md', 'c.md'],
confirm_deletion: true,
@ -628,8 +628,8 @@ describe('AIFunctionService - Integration Tests', () => {
.mockResolvedValueOnce(new Error('Error 1'))
.mockResolvedValueOnce(new Error('Error 2'));
const result = await service.performAIFunction({
name: AIFunction.DeleteVaultFiles,
const result = await service.performAITool({
name: AITool.DeleteVaultFiles,
arguments: {
file_paths: ['file1.md', 'file2.md'],
confirm_deletion: true,
@ -644,8 +644,8 @@ describe('AIFunctionService - Integration Tests', () => {
});
it('should handle empty file list with confirmation', async () => {
const result = await service.performAIFunction({
name: AIFunction.DeleteVaultFiles,
const result = await service.performAITool({
name: AITool.DeleteVaultFiles,
arguments: {
file_paths: [],
confirm_deletion: true,
@ -658,15 +658,15 @@ describe('AIFunctionService - Integration Tests', () => {
});
});
describe('performAIFunction - MoveVaultFiles', () => {
describe('performAITool - MoveVaultFiles', () => {
it('should move multiple files successfully', async () => {
mockFileSystemService.moveFile
.mockResolvedValueOnce({ success: true })
.mockResolvedValueOnce({ success: true })
.mockResolvedValueOnce({ success: true });
const result = await service.performAIFunction({
name: AIFunction.MoveVaultFiles,
const result = await service.performAITool({
name: AITool.MoveVaultFiles,
arguments: {
source_paths: ['a.md', 'b.md', 'c.md'],
destination_paths: ['dest/a.md', 'dest/b.md', 'dest/c.md'],
@ -685,8 +685,8 @@ describe('AIFunctionService - Integration Tests', () => {
});
it('should reject when array lengths dont match', async () => {
const result = await service.performAIFunction({
name: AIFunction.MoveVaultFiles,
const result = await service.performAITool({
name: AITool.MoveVaultFiles,
arguments: {
source_paths: ['a.md', 'b.md'],
destination_paths: ['dest/a.md'],
@ -709,8 +709,8 @@ describe('AIFunctionService - Integration Tests', () => {
.mockResolvedValueOnce(error)
.mockResolvedValueOnce(undefined); // void = success
const result = await service.performAIFunction({
name: AIFunction.MoveVaultFiles,
const result = await service.performAITool({
name: AITool.MoveVaultFiles,
arguments: {
source_paths: ['a.md', 'b.md', 'c.md'],
destination_paths: ['new/a.md', 'existing.md', 'new/c.md'],
@ -729,8 +729,8 @@ describe('AIFunctionService - Integration Tests', () => {
it('should call moveFile with correct parameters', async () => {
mockFileSystemService.moveFile.mockResolvedValue(undefined); // void = success
await service.performAIFunction({
name: AIFunction.MoveVaultFiles,
await service.performAITool({
name: AITool.MoveVaultFiles,
arguments: {
source_paths: ['old/file.md'],
destination_paths: ['new/file.md'],
@ -743,8 +743,8 @@ describe('AIFunctionService - Integration Tests', () => {
});
it('should handle empty arrays', async () => {
const result = await service.performAIFunction({
name: AIFunction.MoveVaultFiles,
const result = await service.performAITool({
name: AITool.MoveVaultFiles,
arguments: {
source_paths: [],
destination_paths: [],
@ -757,22 +757,22 @@ describe('AIFunctionService - Integration Tests', () => {
});
});
describe('performAIFunction - RequestWebSearch', () => {
describe('performAITool - RequestWebSearch', () => {
it('should return empty object for Gemini web search', async () => {
const result = await service.performAIFunction({
name: AIFunction.RequestWebSearch,
const result = await service.performAITool({
name: AITool.RequestWebSearch,
arguments: {},
toolId: 'tool_25'
} as any);
expect(result.name).toBe(AIFunction.RequestWebSearch);
expect(result.name).toBe(AITool.RequestWebSearch);
expect(result.response).toEqual({});
expect(result.toolId).toBe('tool_25');
});
it('should handle web search without arguments', async () => {
const result = await service.performAIFunction({
name: AIFunction.RequestWebSearch,
const result = await service.performAITool({
name: AITool.RequestWebSearch,
arguments: {},
toolId: 'tool_26'
} as any);
@ -781,9 +781,9 @@ describe('AIFunctionService - Integration Tests', () => {
});
});
describe('performAIFunction - Unknown Function', () => {
describe('performAITool - Unknown Function', () => {
it('should return unknown response for unknown function', async () => {
const result = await service.performAIFunction({
const result = await service.performAITool({
name: 'UnknownFunction' as any,
arguments: {},
toolId: 'tool_27'
@ -794,7 +794,7 @@ describe('AIFunctionService - Integration Tests', () => {
});
it('should return unknown response for invalid function', async () => {
const result = await service.performAIFunction({
const result = await service.performAITool({
name: 'InvalidFunction' as any,
arguments: {},
toolId: 'tool_error'
@ -816,8 +816,8 @@ describe('AIFunctionService - Integration Tests', () => {
];
mockFileSystemService.searchVaultFiles.mockResolvedValue(mockMatches);
const searchResult = await service.performAIFunction({
name: AIFunction.SearchVaultFiles,
const searchResult = await service.performAITool({
name: AITool.SearchVaultFiles,
arguments: { search_terms: ['test'], user_message: 'test search' },
toolId: 'search_1'
} as any);
@ -827,8 +827,8 @@ describe('AIFunctionService - Integration Tests', () => {
// Then read
mockFileSystemService.readFile.mockResolvedValue('File content here');
const readResult = await service.performAIFunction({
name: AIFunction.ReadVaultFiles,
const readResult = await service.performAITool({
name: AITool.ReadVaultFiles,
arguments: { file_paths: [foundPath], user_message: 'test search' },
toolId: 'read_1'
} as any);
@ -841,8 +841,8 @@ describe('AIFunctionService - Integration Tests', () => {
// First write
mockFileSystemService.writeFile.mockResolvedValue(undefined);
const writeResult = await service.performAIFunction({
name: AIFunction.WriteVaultFile,
const writeResult = await service.performAITool({
name: AITool.WriteVaultFile,
arguments: {
file_path: 'temp.md',
content: 'Temporary content',
@ -856,8 +856,8 @@ describe('AIFunctionService - Integration Tests', () => {
// Then move
mockFileSystemService.moveFile.mockResolvedValue({ success: true });
const moveResult = await service.performAIFunction({
name: AIFunction.MoveVaultFiles,
const moveResult = await service.performAITool({
name: AITool.MoveVaultFiles,
arguments: {
source_paths: ['temp.md'],
destination_paths: ['archive/temp.md'],
@ -873,8 +873,8 @@ describe('AIFunctionService - Integration Tests', () => {
// First read the file
mockFileSystemService.readFile.mockResolvedValue('# Original Title\n\nOriginal content');
const readResult = await service.performAIFunction({
name: AIFunction.ReadVaultFiles,
const readResult = await service.performAITool({
name: AITool.ReadVaultFiles,
arguments: { file_paths: ['document.md'], user_message: 'Reading file' },
toolId: 'read_2'
} as any);
@ -887,8 +887,8 @@ describe('AIFunctionService - Integration Tests', () => {
const oldContent = '# Original Title';
const newContent = '# Updated Title';
const patchResult = await service.performAIFunction({
name: AIFunction.PatchVaultFile,
const patchResult = await service.performAITool({
name: AITool.PatchVaultFile,
arguments: {
file_path: 'document.md',
oldContent: oldContent,

View file

@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { ChatService } from '../../Services/ChatService';
import { RegisterSingleton, DeregisterAllServices } from '../../Services/DependencyService';
import { Services } from '../../Services/Services';
import { AIFunction, fromString } from '../../Enums/AIFunction';
import { AITool, fromString } from '../../Enums/AITool';
import { AbortService } from '../../Services/AbortService';
/**
@ -117,20 +117,20 @@ describe('ChatService - Integration Tests (Sync Methods Only)', () => {
});
describe('fromString', () => {
it('should convert valid function name strings to AIFunction enum', () => {
expect(fromString('search_vault_files')).toBe(AIFunction.SearchVaultFiles);
expect(fromString('read_vault_files')).toBe(AIFunction.ReadVaultFiles);
expect(fromString('write_vault_file')).toBe(AIFunction.WriteVaultFile);
expect(fromString('delete_vault_files')).toBe(AIFunction.DeleteVaultFiles);
expect(fromString('move_vault_files')).toBe(AIFunction.MoveVaultFiles);
expect(fromString('list_vault_files')).toBe(AIFunction.ListVaultFiles);
expect(fromString('request_web_search')).toBe(AIFunction.RequestWebSearch);
it('should convert valid function name strings to AITool enum', () => {
expect(fromString('search_vault_files')).toBe(AITool.SearchVaultFiles);
expect(fromString('read_vault_files')).toBe(AITool.ReadVaultFiles);
expect(fromString('write_vault_file')).toBe(AITool.WriteVaultFile);
expect(fromString('delete_vault_files')).toBe(AITool.DeleteVaultFiles);
expect(fromString('move_vault_files')).toBe(AITool.MoveVaultFiles);
expect(fromString('list_vault_files')).toBe(AITool.ListVaultFiles);
expect(fromString('request_web_search')).toBe(AITool.RequestWebSearch);
});
it('should return Unknown for unknown function names', () => {
expect(fromString('unknown_function')).toBe(AIFunction.Unknown);
expect(fromString('test_function')).toBe(AIFunction.Unknown);
expect(fromString('')).toBe(AIFunction.Unknown);
expect(fromString('unknown_function')).toBe(AITool.Unknown);
expect(fromString('test_function')).toBe(AITool.Unknown);
expect(fromString('')).toBe(AITool.Unknown);
});
});

View file

@ -2,9 +2,9 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { ExecutionAgent } from '../../Services/AIServices/ExecutionAgent';
import { RegisterSingleton, DeregisterAllServices } from '../../Services/DependencyService';
import { Services } from '../../Services/Services';
import { AIFunction } from '../../Enums/AIFunction';
import { AIFunctionCall } from '../../AIClasses/AIFunctionCall';
import { AIFunctionResponse } from '../../AIClasses/FunctionDefinitions/AIFunctionResponse';
import { AITool } from '../../Enums/AITool';
import { AIToolCall } from '../../AIClasses/AIToolCall';
import { AIToolResponse } from '../../AIClasses/FunctionDefinitions/AIToolResponse';
import type { ExecutionStep } from '../../Types/ExecutionStep';
/**
@ -22,7 +22,7 @@ describe('ExecutionAgent - Unit Tests', () => {
let service: ExecutionAgent;
let mockAI: any;
let mockPrompt: any;
let mockAIFunctionService: any;
let mockAIToolService: any;
const createMockCallbacks = () => ({
onSubmit: vi.fn(),
@ -47,13 +47,13 @@ describe('ExecutionAgent - Unit Tests', () => {
};
RegisterSingleton(Services.IPrompt, mockPrompt);
// Mock AIFunctionService
mockAIFunctionService = {
performAIFunction: vi.fn().mockResolvedValue(
new AIFunctionResponse(AIFunction.SearchVaultFiles, { results: [] }, 'test-tool-id')
// Mock AIToolService
mockAIToolService = {
performAITool: vi.fn().mockResolvedValue(
new AIToolResponse(AITool.SearchVaultFiles, { results: [] }, 'test-tool-id')
)
};
RegisterSingleton(Services.AIFunctionService, mockAIFunctionService);
RegisterSingleton(Services.AIToolService, mockAIToolService);
// Mock IAIClass
mockAI = {
@ -85,8 +85,8 @@ describe('ExecutionAgent - Unit Tests', () => {
mockAI.streamRequest.mockImplementation(async function* () {
yield {
content: 'Task completed',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Found 10 markdown files'
@ -115,8 +115,8 @@ describe('ExecutionAgent - Unit Tests', () => {
mockAI.streamRequest.mockImplementation(async function* () {
yield {
content: 'Task completed',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Processed 5 files',
@ -152,8 +152,8 @@ describe('ExecutionAgent - Unit Tests', () => {
mockAI.streamRequest.mockImplementation(async function* () {
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Completed'
@ -190,8 +190,8 @@ describe('ExecutionAgent - Unit Tests', () => {
mockAI.streamRequest.mockImplementation(async function* () {
yield {
content: 'Task failed',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: false,
description: 'No temporary files found'
@ -220,8 +220,8 @@ describe('ExecutionAgent - Unit Tests', () => {
mockAI.streamRequest.mockImplementation(async function* () {
yield {
content: 'Failed',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: false,
description: 'Permission denied: cannot write to /protected/file.md',
@ -259,8 +259,8 @@ describe('ExecutionAgent - Unit Tests', () => {
// Call search function
yield {
content: 'Searching',
functionCall: new AIFunctionCall(
AIFunction.SearchVaultFiles,
functionCall: new AIToolCall(
AITool.SearchVaultFiles,
{
search_terms: ['#important'],
user_message: 'Searching for tagged files'
@ -273,8 +273,8 @@ describe('ExecutionAgent - Unit Tests', () => {
// Complete task
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Found 3 files with #important tag'
@ -289,7 +289,7 @@ describe('ExecutionAgent - Unit Tests', () => {
service.resolveAIProvider();
const result = await service.runExecutionAgent(step, callbacks);
expect(mockAIFunctionService.performAIFunction).toHaveBeenCalled();
expect(mockAIToolService.performAITool).toHaveBeenCalled();
expect(result?.success).toBe(true);
expect(functionCallCount).toBe(2);
});
@ -308,8 +308,8 @@ describe('ExecutionAgent - Unit Tests', () => {
if (functionCallCount === 1) {
yield {
content: 'Reading',
functionCall: new AIFunctionCall(
AIFunction.ReadVaultFiles,
functionCall: new AIToolCall(
AITool.ReadVaultFiles,
{
file_paths: ['note.md'],
user_message: 'Reading file'
@ -321,8 +321,8 @@ describe('ExecutionAgent - Unit Tests', () => {
} else {
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'File read successfully'
@ -337,7 +337,7 @@ describe('ExecutionAgent - Unit Tests', () => {
service.resolveAIProvider();
const result = await service.runExecutionAgent(step, callbacks);
expect(mockAIFunctionService.performAIFunction).toHaveBeenCalled();
expect(mockAIToolService.performAITool).toHaveBeenCalled();
expect(result?.success).toBe(true);
});
@ -355,8 +355,8 @@ describe('ExecutionAgent - Unit Tests', () => {
if (functionCallCount === 1) {
yield {
content: 'Writing',
functionCall: new AIFunctionCall(
AIFunction.WriteVaultFile,
functionCall: new AIToolCall(
AITool.WriteVaultFile,
{
file_path: 'new-note.md',
content: '# New Note\n\nContent here',
@ -369,8 +369,8 @@ describe('ExecutionAgent - Unit Tests', () => {
} else {
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'File created successfully'
@ -385,7 +385,7 @@ describe('ExecutionAgent - Unit Tests', () => {
service.resolveAIProvider();
const result = await service.runExecutionAgent(step, callbacks);
expect(mockAIFunctionService.performAIFunction).toHaveBeenCalled();
expect(mockAIToolService.performAITool).toHaveBeenCalled();
expect(result?.success).toBe(true);
});
@ -403,8 +403,8 @@ describe('ExecutionAgent - Unit Tests', () => {
if (functionCallCount === 1) {
yield {
content: 'Searching',
functionCall: new AIFunctionCall(
AIFunction.SearchVaultFiles,
functionCall: new AIToolCall(
AITool.SearchVaultFiles,
{
search_terms: ['TODO'],
user_message: 'Searching'
@ -416,8 +416,8 @@ describe('ExecutionAgent - Unit Tests', () => {
} else if (functionCallCount === 2) {
yield {
content: 'Reading',
functionCall: new AIFunctionCall(
AIFunction.ReadVaultFiles,
functionCall: new AIToolCall(
AITool.ReadVaultFiles,
{
file_paths: ['found.md'],
user_message: 'Reading'
@ -429,8 +429,8 @@ describe('ExecutionAgent - Unit Tests', () => {
} else if (functionCallCount === 3) {
yield {
content: 'Writing',
functionCall: new AIFunctionCall(
AIFunction.PatchVaultFile,
functionCall: new AIToolCall(
AITool.PatchVaultFile,
{
file_path: 'found.md',
patch_operations: [],
@ -443,8 +443,8 @@ describe('ExecutionAgent - Unit Tests', () => {
} else {
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Updated 1 file'
@ -459,7 +459,7 @@ describe('ExecutionAgent - Unit Tests', () => {
service.resolveAIProvider();
const result = await service.runExecutionAgent(step, callbacks);
expect(mockAIFunctionService.performAIFunction).toHaveBeenCalledTimes(3);
expect(mockAIToolService.performAITool).toHaveBeenCalledTimes(3);
expect(result?.success).toBe(true);
});
});
@ -486,8 +486,8 @@ describe('ExecutionAgent - Unit Tests', () => {
// Complete on retry
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Completed'
@ -527,8 +527,8 @@ describe('ExecutionAgent - Unit Tests', () => {
// Complete on third attempt
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Finally completed'
@ -574,8 +574,8 @@ describe('ExecutionAgent - Unit Tests', () => {
} else {
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Completed'
@ -640,8 +640,8 @@ describe('ExecutionAgent - Unit Tests', () => {
// Complete on third attempt (max depth)
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Barely made it'
@ -680,8 +680,8 @@ describe('ExecutionAgent - Unit Tests', () => {
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Processed files from context'
@ -710,8 +710,8 @@ describe('ExecutionAgent - Unit Tests', () => {
mockAI.streamRequest.mockImplementation(async function* () {
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Task completed without context'
@ -747,8 +747,8 @@ describe('ExecutionAgent - Unit Tests', () => {
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Processed all files'
@ -783,8 +783,8 @@ describe('ExecutionAgent - Unit Tests', () => {
// Invalid: missing description
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true
// Missing description field
@ -797,8 +797,8 @@ describe('ExecutionAgent - Unit Tests', () => {
// Valid completion
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Valid completion'
@ -832,8 +832,8 @@ describe('ExecutionAgent - Unit Tests', () => {
// Invalid: success is not boolean
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: 'yes',
description: 'Done'
@ -846,8 +846,8 @@ describe('ExecutionAgent - Unit Tests', () => {
// Valid
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Done properly'
@ -882,8 +882,8 @@ describe('ExecutionAgent - Unit Tests', () => {
if (functionCallCount === 1) {
yield {
content: 'Searching',
functionCall: new AIFunctionCall(
AIFunction.SearchVaultFiles,
functionCall: new AIToolCall(
AITool.SearchVaultFiles,
{
search_terms: ['test'],
user_message: 'Searching for files'
@ -895,8 +895,8 @@ describe('ExecutionAgent - Unit Tests', () => {
} else {
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Completed'

View file

@ -8,9 +8,9 @@ import { Services } from '../../Services/Services';
import { Conversation } from '../../Conversations/Conversation';
import { ConversationContent } from '../../Conversations/ConversationContent';
import { Role } from '../../Enums/Role';
import { AIFunction } from '../../Enums/AIFunction';
import { AIFunctionCall } from '../../AIClasses/AIFunctionCall';
import { AIFunctionResponse } from '../../AIClasses/FunctionDefinitions/AIFunctionResponse';
import { AITool } from '../../Enums/AITool';
import { AIToolCall } from '../../AIClasses/AIToolCall';
import { AIToolResponse } from '../../AIClasses/FunctionDefinitions/AIToolResponse';
import type { ExecutionStep } from '../../Types/ExecutionStep';
/**
@ -30,7 +30,7 @@ import type { ExecutionStep } from '../../Types/ExecutionStep';
describe('Multi-Agent Integration Tests', () => {
let mockAI: any;
let mockPrompt: any;
let mockAIFunctionService: any;
let mockAIToolService: any;
const createMockCallbacks = () => ({
onSubmit: vi.fn(),
@ -57,13 +57,13 @@ describe('Multi-Agent Integration Tests', () => {
};
RegisterSingleton(Services.IPrompt, mockPrompt);
// Mock AIFunctionService
mockAIFunctionService = {
performAIFunction: vi.fn().mockResolvedValue(
new AIFunctionResponse(AIFunction.SearchVaultFiles, { results: ['file1.md', 'file2.md'] }, 'test-tool-id')
// Mock AIToolService
mockAIToolService = {
performAITool: vi.fn().mockResolvedValue(
new AIToolResponse(AITool.SearchVaultFiles, { results: ['file1.md', 'file2.md'] }, 'test-tool-id')
)
};
RegisterSingleton(Services.AIFunctionService, mockAIFunctionService);
RegisterSingleton(Services.AIToolService, mockAIToolService);
// Mock IAIClass
mockAI = {
@ -123,8 +123,8 @@ describe('Multi-Agent Integration Tests', () => {
mockAI.streamRequest.mockImplementation(async function* () {
yield {
content: 'Plan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -159,8 +159,8 @@ describe('Multi-Agent Integration Tests', () => {
mockAI.streamRequest.mockImplementation(async function* () {
yield {
content: 'Replan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -201,8 +201,8 @@ describe('Multi-Agent Integration Tests', () => {
mockAI.streamRequest.mockImplementation(async function* () {
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Task completed'
@ -232,8 +232,8 @@ describe('Multi-Agent Integration Tests', () => {
mockAI.streamRequest.mockImplementation(async function* () {
yield {
content: 'Failed',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: false,
description: 'Task failed'
@ -264,8 +264,8 @@ describe('Multi-Agent Integration Tests', () => {
mockAI.streamRequest.mockImplementation(async function* () {
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Processed with context',
@ -400,8 +400,8 @@ describe('Multi-Agent Integration Tests', () => {
searchCalled = true;
yield {
content: 'Searching',
functionCall: new AIFunctionCall(
AIFunction.SearchVaultFiles,
functionCall: new AIToolCall(
AITool.SearchVaultFiles,
{
search_terms: ['test'],
user_message: 'Searching'
@ -413,8 +413,8 @@ describe('Multi-Agent Integration Tests', () => {
} else {
yield {
content: 'Plan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -433,7 +433,7 @@ describe('Multi-Agent Integration Tests', () => {
service.resolveAIProvider();
const result = await service.runPlanningAgent(conversation, callbacks, false);
expect(mockAIFunctionService.performAIFunction).toHaveBeenCalled();
expect(mockAIToolService.performAITool).toHaveBeenCalled();
expect(result).toBeDefined();
});
@ -451,8 +451,8 @@ describe('Multi-Agent Integration Tests', () => {
if (functionCallCount === 1) {
yield {
content: 'Searching',
functionCall: new AIFunctionCall(
AIFunction.SearchVaultFiles,
functionCall: new AIToolCall(
AITool.SearchVaultFiles,
{
search_terms: ['test'],
user_message: 'Searching'
@ -464,8 +464,8 @@ describe('Multi-Agent Integration Tests', () => {
} else {
yield {
content: 'Done',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Found and processed files'
@ -480,7 +480,7 @@ describe('Multi-Agent Integration Tests', () => {
service.resolveAIProvider();
const result = await service.runExecutionAgent(step, callbacks);
expect(mockAIFunctionService.performAIFunction).toHaveBeenCalled();
expect(mockAIToolService.performAITool).toHaveBeenCalled();
expect(result?.success).toBe(true);
});
});

View file

@ -2,8 +2,8 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { OrchestrationAgent } from '../../Services/AIServices/OrchestrationAgent';
import { RegisterSingleton, DeregisterAllServices } from '../../Services/DependencyService';
import { Services } from '../../Services/Services';
import { AIFunction } from '../../Enums/AIFunction';
import { AIFunctionResponse } from '../../AIClasses/FunctionDefinitions/AIFunctionResponse';
import { AITool } from '../../Enums/AITool';
import { AIToolResponse } from '../../AIClasses/FunctionDefinitions/AIToolResponse';
/**
* UNIT TESTS - OrchestrationAgent
@ -20,7 +20,7 @@ describe('OrchestrationAgent - Unit Tests', () => {
let service: OrchestrationAgent;
let mockAI: any;
let mockPrompt: any;
let mockAIFunctionService: any;
let mockAIToolService: any;
beforeEach(() => {
// Mock IPrompt
@ -33,13 +33,13 @@ describe('OrchestrationAgent - Unit Tests', () => {
};
RegisterSingleton(Services.IPrompt, mockPrompt);
// Mock AIFunctionService
mockAIFunctionService = {
performAIFunction: vi.fn().mockResolvedValue(
new AIFunctionResponse(AIFunction.SearchVaultFiles, { results: [] }, 'test-tool-id')
// Mock AIToolService
mockAIToolService = {
performAITool: vi.fn().mockResolvedValue(
new AIToolResponse(AITool.SearchVaultFiles, { results: [] }, 'test-tool-id')
)
};
RegisterSingleton(Services.AIFunctionService, mockAIFunctionService);
RegisterSingleton(Services.AIToolService, mockAIToolService);
// Mock IAIClass
mockAI = {

View file

@ -5,9 +5,9 @@ import { Services } from '../../Services/Services';
import { Conversation } from '../../Conversations/Conversation';
import { ConversationContent } from '../../Conversations/ConversationContent';
import { Role } from '../../Enums/Role';
import { AIFunction } from '../../Enums/AIFunction';
import { AIFunctionCall } from '../../AIClasses/AIFunctionCall';
import { AIFunctionResponse } from '../../AIClasses/FunctionDefinitions/AIFunctionResponse';
import { AITool } from '../../Enums/AITool';
import { AIToolCall } from '../../AIClasses/AIToolCall';
import { AIToolResponse } from '../../AIClasses/FunctionDefinitions/AIToolResponse';
/**
* UNIT TESTS - PlanningAgent
@ -24,7 +24,7 @@ describe('PlanningAgent - Unit Tests', () => {
let service: PlanningAgent;
let mockAI: any;
let mockPrompt: any;
let mockAIFunctionService: any;
let mockAIToolService: any;
const createMockCallbacks = () => ({
onSubmit: vi.fn(),
@ -49,13 +49,13 @@ describe('PlanningAgent - Unit Tests', () => {
};
RegisterSingleton(Services.IPrompt, mockPrompt);
// Mock AIFunctionService
mockAIFunctionService = {
performAIFunction: vi.fn().mockResolvedValue(
new AIFunctionResponse(AIFunction.SearchVaultFiles, { results: [] }, 'test-tool-id')
// Mock AIToolService
mockAIToolService = {
performAITool: vi.fn().mockResolvedValue(
new AIToolResponse(AITool.SearchVaultFiles, { results: [] }, 'test-tool-id')
)
};
RegisterSingleton(Services.AIFunctionService, mockAIFunctionService);
RegisterSingleton(Services.AIToolService, mockAIToolService);
// Mock IAIClass
mockAI = {
@ -88,8 +88,8 @@ describe('PlanningAgent - Unit Tests', () => {
mockAI.streamRequest.mockImplementation(async function* () {
yield {
content: 'Creating plan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -135,8 +135,8 @@ describe('PlanningAgent - Unit Tests', () => {
mockAI.streamRequest.mockImplementation(async function* () {
yield {
content: 'Plan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -169,8 +169,8 @@ describe('PlanningAgent - Unit Tests', () => {
mockAI.streamRequest.mockImplementation(async function* () {
yield {
content: 'Replan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -210,8 +210,8 @@ describe('PlanningAgent - Unit Tests', () => {
// Invalid: missing required fields
yield {
content: 'Invalid plan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -228,8 +228,8 @@ describe('PlanningAgent - Unit Tests', () => {
// Valid plan
yield {
content: 'Valid plan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -274,8 +274,8 @@ describe('PlanningAgent - Unit Tests', () => {
// Submit plan on retry
yield {
content: 'Plan ready',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -318,8 +318,8 @@ describe('PlanningAgent - Unit Tests', () => {
// Ask user question
yield {
content: 'Question',
functionCall: new AIFunctionCall(
AIFunction.AskUserQuestionPlanning,
functionCall: new AIToolCall(
AITool.AskUserQuestionPlanning,
{
question: 'What topic should I search for?',
user_message: 'Asking user for clarification'
@ -332,8 +332,8 @@ describe('PlanningAgent - Unit Tests', () => {
// Submit plan with user's answer incorporated
yield {
content: 'Plan with answer',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -376,8 +376,8 @@ describe('PlanningAgent - Unit Tests', () => {
if (functionCallCount === 1) {
yield {
content: 'Q1',
functionCall: new AIFunctionCall(
AIFunction.AskUserQuestionPlanning,
functionCall: new AIToolCall(
AITool.AskUserQuestionPlanning,
{
question: 'What file type?',
user_message: 'Asking about file type'
@ -389,8 +389,8 @@ describe('PlanningAgent - Unit Tests', () => {
} else if (functionCallCount === 2) {
yield {
content: 'Q2',
functionCall: new AIFunctionCall(
AIFunction.AskUserQuestionPlanning,
functionCall: new AIToolCall(
AITool.AskUserQuestionPlanning,
{
question: 'Which folder?',
user_message: 'Asking about folder'
@ -402,8 +402,8 @@ describe('PlanningAgent - Unit Tests', () => {
} else {
yield {
content: 'Plan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -442,8 +442,8 @@ describe('PlanningAgent - Unit Tests', () => {
// Invalid: missing question
yield {
content: 'Invalid',
functionCall: new AIFunctionCall(
AIFunction.AskUserQuestionPlanning,
functionCall: new AIToolCall(
AITool.AskUserQuestionPlanning,
{
// Missing question field
user_message: 'Message'
@ -456,8 +456,8 @@ describe('PlanningAgent - Unit Tests', () => {
// Valid plan
yield {
content: 'Plan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -496,8 +496,8 @@ describe('PlanningAgent - Unit Tests', () => {
searchCalled = true;
yield {
content: 'Searching',
functionCall: new AIFunctionCall(
AIFunction.SearchVaultFiles,
functionCall: new AIToolCall(
AITool.SearchVaultFiles,
{
search_terms: ['test'],
user_message: 'Searching'
@ -509,8 +509,8 @@ describe('PlanningAgent - Unit Tests', () => {
} else {
yield {
content: 'Plan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -529,7 +529,7 @@ describe('PlanningAgent - Unit Tests', () => {
service.resolveAIProvider();
const result = await service.runPlanningAgent(conversation, callbacks, false);
expect(mockAIFunctionService.performAIFunction).toHaveBeenCalled();
expect(mockAIToolService.performAITool).toHaveBeenCalled();
expect(result).toBeDefined();
});
@ -548,8 +548,8 @@ describe('PlanningAgent - Unit Tests', () => {
readCalled = true;
yield {
content: 'Reading',
functionCall: new AIFunctionCall(
AIFunction.ReadVaultFiles,
functionCall: new AIToolCall(
AITool.ReadVaultFiles,
{
file_paths: ['test.md'],
user_message: 'Reading'
@ -561,8 +561,8 @@ describe('PlanningAgent - Unit Tests', () => {
} else {
yield {
content: 'Plan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -581,7 +581,7 @@ describe('PlanningAgent - Unit Tests', () => {
service.resolveAIProvider();
const result = await service.runPlanningAgent(conversation, callbacks, false);
expect(mockAIFunctionService.performAIFunction).toHaveBeenCalled();
expect(mockAIToolService.performAITool).toHaveBeenCalled();
expect(result).toBeDefined();
});
@ -601,8 +601,8 @@ describe('PlanningAgent - Unit Tests', () => {
// Try to write file (not allowed in planning)
yield {
content: 'Writing',
functionCall: new AIFunctionCall(
AIFunction.WriteVaultFile,
functionCall: new AIToolCall(
AITool.WriteVaultFile,
{
file_path: 'test.md',
content: 'content'
@ -615,8 +615,8 @@ describe('PlanningAgent - Unit Tests', () => {
// Submit valid plan
yield {
content: 'Plan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -636,8 +636,8 @@ describe('PlanningAgent - Unit Tests', () => {
const result = await service.runPlanningAgent(conversation, callbacks, false);
// WriteVaultFile should be denied, not executed
expect(mockAIFunctionService.performAIFunction).not.toHaveBeenCalledWith(
expect.objectContaining({ name: AIFunction.WriteVaultFile })
expect(mockAIToolService.performAITool).not.toHaveBeenCalledWith(
expect.objectContaining({ name: AITool.WriteVaultFile })
);
expect(result).toBeDefined();
expect(attemptCount).toBeGreaterThan(1);
@ -659,8 +659,8 @@ describe('PlanningAgent - Unit Tests', () => {
// Try to complete task (execution agent tool)
yield {
content: 'Completing',
functionCall: new AIFunctionCall(
AIFunction.CompleteTask,
functionCall: new AIToolCall(
AITool.CompleteTask,
{
success: true,
description: 'Done'
@ -672,8 +672,8 @@ describe('PlanningAgent - Unit Tests', () => {
} else {
yield {
content: 'Plan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -711,8 +711,8 @@ describe('PlanningAgent - Unit Tests', () => {
// Try to complete step (orchestration agent tool)
yield {
content: 'Completing',
functionCall: new AIFunctionCall(
AIFunction.CompleteStep,
functionCall: new AIToolCall(
AITool.CompleteStep,
{
confirm_completion: true
},
@ -723,8 +723,8 @@ describe('PlanningAgent - Unit Tests', () => {
} else {
yield {
content: 'Plan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -796,8 +796,8 @@ describe('PlanningAgent - Unit Tests', () => {
// Submit plan on third attempt (just before max depth)
yield {
content: 'Plan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{
@ -834,8 +834,8 @@ describe('PlanningAgent - Unit Tests', () => {
// Empty plan is valid according to schema
yield {
content: 'Empty plan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: []
},
@ -883,8 +883,8 @@ describe('PlanningAgent - Unit Tests', () => {
if (hasRetryMessage) {
yield {
content: 'Plan',
functionCall: new AIFunctionCall(
AIFunction.SubmitPlan,
functionCall: new AIToolCall(
AITool.SubmitPlan,
{
steps: [
{