mirror of
https://github.com/andy-stack/vaultkeeper-ai.git
synced 2026-07-22 06:42:03 +00:00
Rename 'AIfunction' to 'AITool' globally.
This commit is contained in:
parent
a2c83f4e02
commit
9d4a4452c5
58 changed files with 708 additions and 708 deletions
|
|
@ -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;
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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" };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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" } };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
export enum AIFunctionUsageMode {
|
||||
export enum AIToolUsageMode {
|
||||
Auto = "auto",
|
||||
Enabled = "enabled",
|
||||
Disabled = "disabled"
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue