package assistant_test import ( stdContext "context" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/yaoapp/yao/agent/assistant" "github.com/yaoapp/yao/agent/context" searchTypes "github.com/yaoapp/yao/agent/search/types" store "github.com/yaoapp/yao/agent/store/types" "github.com/yaoapp/yao/agent/testutils" "github.com/yaoapp/yao/openapi/oauth/types" ) // TestLoadStoreWithSource tests loading assistant from database with Source field func TestLoadStoreWithSource(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) // Create assistant with Source assistantID := "test.store-with-source" now := time.Now().UnixNano() ast := &assistant.Assistant{ AssistantModel: store.AssistantModel{ ID: assistantID, Name: "Test Assistant With Source", Type: "assistant", Connector: "gpt-4o", Description: "Test assistant loaded from store with source code", Prompts: []store.Prompt{ {Role: "system", Content: "You are a helpful assistant."}, }, Options: map[string]interface{}{ "temperature": 0.7, }, Tags: []string{"Test", "Source"}, // Simple Create hook that returns null Source: ` // @ts-nocheck function Create(ctx, messages) { return null; } `, CreatedAt: now, UpdatedAt: now, }, } // Save to database err := ast.Save() require.NoError(t, err) // Cleanup after test defer func() { storage := assistant.GetStorage() if storage != nil { storage.DeleteAssistant(assistantID) } assistant.GetCache().Clear() }() // Clear cache to ensure fresh load from database assistant.GetCache().Clear() // Load from store loaded, err := assistant.Get(assistantID) require.NoError(t, err) require.NotNil(t, loaded) // Verify basic fields assert.Equal(t, assistantID, loaded.ID) assert.Equal(t, "Test Assistant With Source", loaded.Name) assert.Equal(t, "assistant", loaded.Type) assert.Equal(t, "Test assistant loaded from store with source code", loaded.Description) // Verify prompts require.NotNil(t, loaded.Prompts) assert.Len(t, loaded.Prompts, 1) assert.Equal(t, "system", loaded.Prompts[0].Role) assert.Equal(t, "You are a helpful assistant.", loaded.Prompts[0].Content) // Verify options assert.NotNil(t, loaded.Options) assert.Equal(t, 0.7, loaded.Options["temperature"]) // Verify tags assert.NotNil(t, loaded.Tags) assert.Contains(t, loaded.Tags, "Test") assert.Contains(t, loaded.Tags, "Source") // Verify script was compiled from source assert.NotNil(t, loaded.HookScript, "HookScript should be compiled from Source field") // Verify source is stored assert.NotEmpty(t, loaded.Source) } // TestLoadStoreWithoutSource tests loading assistant from database without Source field func TestLoadStoreWithoutSource(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) // Create assistant without Source assistantID := "test.store-without-source" now := time.Now().UnixNano() ast := &assistant.Assistant{ AssistantModel: store.AssistantModel{ ID: assistantID, Name: "Test Assistant Without Source", Type: "assistant", Connector: "gpt-4o", Description: "Test assistant loaded from store without source code", Prompts: []store.Prompt{ {Role: "system", Content: "You are a helpful assistant without hooks."}, }, Options: map[string]interface{}{ "temperature": 0.5, "max_tokens": 1000, }, Tags: []string{"Test", "NoSource"}, CreatedAt: now, UpdatedAt: now, }, } // Save to database err := ast.Save() require.NoError(t, err) // Cleanup after test defer func() { storage := assistant.GetStorage() if storage != nil { storage.DeleteAssistant(assistantID) } assistant.GetCache().Clear() }() // Clear cache to ensure fresh load from database assistant.GetCache().Clear() // Load from store loaded, err := assistant.Get(assistantID) require.NoError(t, err) require.NotNil(t, loaded) // Verify basic fields assert.Equal(t, assistantID, loaded.ID) assert.Equal(t, "Test Assistant Without Source", loaded.Name) assert.Equal(t, "assistant", loaded.Type) assert.Equal(t, "Test assistant loaded from store without source code", loaded.Description) // Verify prompts require.NotNil(t, loaded.Prompts) assert.Len(t, loaded.Prompts, 1) assert.Equal(t, "system", loaded.Prompts[0].Role) // Verify options assert.NotNil(t, loaded.Options) assert.Equal(t, 0.5, loaded.Options["temperature"]) assert.Equal(t, float64(1000), loaded.Options["max_tokens"]) // Verify tags assert.NotNil(t, loaded.Tags) assert.Contains(t, loaded.Tags, "Test") assert.Contains(t, loaded.Tags, "NoSource") // Verify script is nil (no source) assert.Nil(t, loaded.HookScript, "HookScript should be nil when no Source field") assert.Empty(t, loaded.Source) } // newStoreTestContext creates a Context for testing with commonly used fields pre-populated. func newStoreTestContext(chatID, assistantID string) *context.Context { authorized := &types.AuthorizedInfo{ Subject: "test-user", ClientID: "test-client-id", Scope: "openid profile email", SessionID: "test-session-id", UserID: "test-user-123", TeamID: "test-team-456", TenantID: "test-tenant-789", } ctx := context.New(stdContext.Background(), authorized, chatID) ctx.AssistantID = assistantID ctx.Locale = "en-us" ctx.Theme = "light" ctx.Client = context.Client{ Type: "web", UserAgent: "TestAgent/1.0", IP: "127.0.0.1", } ctx.Referer = context.RefererAPI ctx.Accept = context.AcceptWebCUI ctx.Route = "" ctx.Metadata = make(map[string]interface{}) return ctx } // TestLoadStoreWithSourceExecuteHook tests that Source-based script is properly compiled and can execute func TestLoadStoreWithSourceExecuteHook(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) // Create assistant with a working Create hook assistantID := "test.store-source-hook" now := time.Now().UnixNano() ast := &assistant.Assistant{ AssistantModel: store.AssistantModel{ ID: assistantID, Name: "Test Source Hook", Type: "assistant", Connector: "gpt-4o", Prompts: []store.Prompt{ {Role: "system", Content: "Default prompt"}, }, // Create hook that modifies temperature and adds metadata Source: ` // @ts-nocheck function Create(ctx: any, messages: any[]): any { return { temperature: 0.9, metadata: { hook_executed: true, chat_id: ctx.chat_id } }; } `, CreatedAt: now, UpdatedAt: now, }, } // Save to database err := ast.Save() require.NoError(t, err) // Cleanup after test defer func() { storage := assistant.GetStorage() if storage != nil { storage.DeleteAssistant(assistantID) } assistant.GetCache().Clear() }() // Clear cache assistant.GetCache().Clear() // Load from store loaded, err := assistant.Get(assistantID) require.NoError(t, err) require.NotNil(t, loaded) require.NotNil(t, loaded.HookScript, "HookScript should be compiled from Source") // Verify the script object exists and is usable assert.NotNil(t, loaded.HookScript.Script) // Execute the Create hook ctx := newStoreTestContext("test-chat-id", assistantID) messages := []context.Message{{Role: "user", Content: "Hello"}} res, _, err := loaded.HookScript.Create(ctx, messages, &context.Options{}) require.NoError(t, err, "Create hook should execute without error") require.NotNil(t, res, "Create hook should return a response") // Verify temperature was set require.NotNil(t, res.Temperature, "Temperature should be set") assert.Equal(t, 0.9, *res.Temperature, "Temperature should be 0.9") // Verify metadata was set require.NotNil(t, res.Metadata, "Metadata should be set") assert.Equal(t, true, res.Metadata["hook_executed"], "hook_executed should be true") assert.Equal(t, "test-chat-id", res.Metadata["chat_id"], "chat_id should match context") } // TestLoadStoreWithPromptPresets tests loading assistant with prompt presets from database func TestLoadStoreWithPromptPresets(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) assistantID := "test.store-with-presets" now := time.Now().UnixNano() ast := &assistant.Assistant{ AssistantModel: store.AssistantModel{ ID: assistantID, Name: "Test With Presets", Type: "assistant", Connector: "gpt-4o", Prompts: []store.Prompt{ {Role: "system", Content: "Default prompt"}, }, PromptPresets: map[string][]store.Prompt{ "friendly": { {Role: "system", Content: "You are a friendly assistant."}, }, "professional": { {Role: "system", Content: "You are a professional assistant."}, }, "mode.casual": { {Role: "system", Content: "You are a casual assistant."}, }, }, CreatedAt: now, UpdatedAt: now, }, } err := ast.Save() require.NoError(t, err) defer func() { storage := assistant.GetStorage() if storage != nil { storage.DeleteAssistant(assistantID) } assistant.GetCache().Clear() }() assistant.GetCache().Clear() loaded, err := assistant.Get(assistantID) require.NoError(t, err) require.NotNil(t, loaded) // Verify prompt presets require.NotNil(t, loaded.PromptPresets) assert.Len(t, loaded.PromptPresets, 3) friendlyPreset, ok := loaded.PromptPresets["friendly"] assert.True(t, ok) assert.Len(t, friendlyPreset, 1) assert.Equal(t, "You are a friendly assistant.", friendlyPreset[0].Content) professionalPreset, ok := loaded.PromptPresets["professional"] assert.True(t, ok) assert.Len(t, professionalPreset, 1) assert.Equal(t, "You are a professional assistant.", professionalPreset[0].Content) casualPreset, ok := loaded.PromptPresets["mode.casual"] assert.True(t, ok) assert.Len(t, casualPreset, 1) assert.Equal(t, "You are a casual assistant.", casualPreset[0].Content) } // TestLoadStoreWithDisableGlobalPrompts tests loading assistant with disable_global_prompts flag func TestLoadStoreWithDisableGlobalPrompts(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) assistantID := "test.store-disable-global" now := time.Now().UnixNano() ast := &assistant.Assistant{ AssistantModel: store.AssistantModel{ ID: assistantID, Name: "Test Disable Global Prompts", Type: "assistant", Connector: "gpt-4o", DisableGlobalPrompts: true, Prompts: []store.Prompt{ {Role: "system", Content: "Only this prompt should be used."}, }, CreatedAt: now, UpdatedAt: now, }, } err := ast.Save() require.NoError(t, err) defer func() { storage := assistant.GetStorage() if storage != nil { storage.DeleteAssistant(assistantID) } assistant.GetCache().Clear() }() assistant.GetCache().Clear() loaded, err := assistant.Get(assistantID) require.NoError(t, err) require.NotNil(t, loaded) assert.True(t, loaded.DisableGlobalPrompts) } // TestLoadStoreCaching tests that loaded assistants are cached func TestLoadStoreCaching(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) assistantID := "test.store-caching" now := time.Now().UnixNano() ast := &assistant.Assistant{ AssistantModel: store.AssistantModel{ ID: assistantID, Name: "Test Caching", Type: "assistant", Connector: "gpt-4o", CreatedAt: now, UpdatedAt: now, }, } err := ast.Save() require.NoError(t, err) defer func() { storage := assistant.GetStorage() if storage != nil { storage.DeleteAssistant(assistantID) } assistant.GetCache().Clear() }() assistant.GetCache().Clear() // First load ast1, err := assistant.Get(assistantID) require.NoError(t, err) require.NotNil(t, ast1) // Second load - should be from cache ast2, err := assistant.Get(assistantID) require.NoError(t, err) require.NotNil(t, ast2) // Should be the same instance (from cache) assert.Same(t, ast1, ast2) } // TestLoadStoreNotFound tests loading non-existent assistant func TestLoadStoreNotFound(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) assistant.GetCache().Clear() _, err := assistant.Get("non-existent-assistant-id-12345") assert.Error(t, err) } // TestLoadStoreWithAllFields tests loading assistant with comprehensive fields func TestLoadStoreWithAllFields(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) assistantID := "test.store-all-fields" now := time.Now().UnixNano() ast := &assistant.Assistant{ AssistantModel: store.AssistantModel{ ID: assistantID, Name: "Test All Fields", Type: "assistant", Avatar: "/api/icons/test.png", Connector: "gpt-4o", Description: "Test assistant with all fields", Tags: []string{"Test", "AllFields", "Complete"}, Readonly: true, Public: true, Share: "team", Mentionable: true, Automated: false, Sort: 100, Options: map[string]interface{}{ "temperature": 0.8, "max_tokens": 2000, }, Prompts: []store.Prompt{ {Role: "system", Content: "You are a test assistant."}, {Role: "system", Content: "Follow all instructions carefully."}, }, PromptPresets: map[string][]store.Prompt{ "default": { {Role: "system", Content: "Default mode prompt."}, }, }, DisableGlobalPrompts: true, Placeholder: &store.Placeholder{ Title: "Test Placeholder", Description: "This is a test placeholder", Prompts: []string{"Test prompt 1", "Test prompt 2"}, }, Dependencies: map[string]string{ "echo": "^1.0.0", "customer": ">=2.0.0", }, Source: ` // @ts-nocheck function Create(ctx: any, messages: any[]): any { return { temperature: 0.5, metadata: { assistant_name: "Test All Fields", executed: true } }; } `, CreatedAt: now, UpdatedAt: now, }, } err := ast.Save() require.NoError(t, err) defer func() { storage := assistant.GetStorage() if storage != nil { storage.DeleteAssistant(assistantID) } assistant.GetCache().Clear() }() assistant.GetCache().Clear() loaded, err := assistant.Get(assistantID) require.NoError(t, err) require.NotNil(t, loaded) // Verify all fields assert.Equal(t, assistantID, loaded.ID) assert.Equal(t, "Test All Fields", loaded.Name) assert.Equal(t, "assistant", loaded.Type) assert.Equal(t, "/api/icons/test.png", loaded.Avatar) assert.Equal(t, "Test assistant with all fields", loaded.Description) // Boolean fields assert.True(t, loaded.Readonly) assert.True(t, loaded.Public) assert.Equal(t, "team", loaded.Share) assert.True(t, loaded.Mentionable) assert.False(t, loaded.Automated) assert.True(t, loaded.DisableGlobalPrompts) assert.Equal(t, 100, loaded.Sort) // Tags assert.Len(t, loaded.Tags, 3) assert.Contains(t, loaded.Tags, "Test") assert.Contains(t, loaded.Tags, "AllFields") assert.Contains(t, loaded.Tags, "Complete") // Options assert.Equal(t, 0.8, loaded.Options["temperature"]) assert.Equal(t, float64(2000), loaded.Options["max_tokens"]) // Prompts assert.Len(t, loaded.Prompts, 2) // Prompt presets assert.NotNil(t, loaded.PromptPresets) assert.Contains(t, loaded.PromptPresets, "default") // Placeholder assert.NotNil(t, loaded.Placeholder) assert.Equal(t, "Test Placeholder", loaded.Placeholder.Title) assert.Equal(t, "This is a test placeholder", loaded.Placeholder.Description) assert.Len(t, loaded.Placeholder.Prompts, 2) // Script from source assert.NotNil(t, loaded.HookScript) assert.NotEmpty(t, loaded.Source) // Dependencies require.NotNil(t, loaded.Dependencies) assert.Len(t, loaded.Dependencies, 2) assert.Equal(t, "^1.0.0", loaded.Dependencies["echo"]) assert.Equal(t, ">=2.0.0", loaded.Dependencies["customer"]) // Execute the Create hook to verify it works ctx := newStoreTestContext("test-chat-all-fields", assistantID) messages := []context.Message{{Role: "user", Content: "Test message"}} res, _, err := loaded.HookScript.Create(ctx, messages, &context.Options{}) require.NoError(t, err, "Create hook should execute without error") require.NotNil(t, res, "Create hook should return a response") // Verify hook returned expected values require.NotNil(t, res.Temperature, "Temperature should be set") assert.Equal(t, 0.5, *res.Temperature, "Temperature should be 0.5") require.NotNil(t, res.Metadata, "Metadata should be set") assert.Equal(t, "Test All Fields", res.Metadata["assistant_name"], "assistant_name should match") assert.Equal(t, true, res.Metadata["executed"], "executed should be true") } // TestLoadStoreHookWithTypeScript tests that TypeScript features work in Source field func TestLoadStoreHookWithTypeScript(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) assistantID := "test.store-typescript-hook" now := time.Now().UnixNano() ast := &assistant.Assistant{ AssistantModel: store.AssistantModel{ ID: assistantID, Name: "Test TypeScript Hook", Type: "assistant", Connector: "gpt-4o", Prompts: []store.Prompt{ {Role: "system", Content: "Default prompt"}, }, // TypeScript code with type annotations and interfaces Source: ` // TypeScript interfaces interface CreateContext { chat_id: string; assistant_id: string; locale: string; authorized?: { user_id: string; team_id: string; }; } interface Message { role: string; content: string | object; } interface CreateResponse { temperature?: number; messages?: Message[]; metadata?: Record; } // Create hook with full TypeScript syntax function Create(ctx: CreateContext, messages: Message[]): CreateResponse | null { // Type-safe access to context const chatId: string = ctx.chat_id || "unknown"; const locale: string = ctx.locale || "en-us"; const userId: string = ctx.authorized?.user_id || "anonymous"; // Process messages const userMessages: Message[] = messages.filter((m: Message) => m.role === "user"); const messageCount: number = userMessages.length; // Return typed response return { temperature: 0.7, messages: [ { role: "system", content: "TypeScript hook executed successfully" } ], metadata: { chat_id: chatId, locale: locale, user_id: userId, message_count: messageCount, typescript_features: true } }; } `, CreatedAt: now, UpdatedAt: now, }, } err := ast.Save() require.NoError(t, err) defer func() { storage := assistant.GetStorage() if storage != nil { storage.DeleteAssistant(assistantID) } assistant.GetCache().Clear() }() assistant.GetCache().Clear() loaded, err := assistant.Get(assistantID) require.NoError(t, err) require.NotNil(t, loaded) require.NotNil(t, loaded.HookScript, "HookScript should be compiled from TypeScript Source") // Execute the Create hook ctx := newStoreTestContext("ts-test-chat", assistantID) messages := []context.Message{ {Role: "user", Content: "Hello"}, {Role: "assistant", Content: "Hi there"}, {Role: "user", Content: "How are you?"}, } res, _, err := loaded.HookScript.Create(ctx, messages, &context.Options{}) require.NoError(t, err, "TypeScript Create hook should execute without error") require.NotNil(t, res, "Create hook should return a response") // Verify temperature require.NotNil(t, res.Temperature) assert.Equal(t, 0.7, *res.Temperature) // Verify messages require.Len(t, res.Messages, 1) assert.Equal(t, context.RoleSystem, res.Messages[0].Role) assert.Equal(t, "TypeScript hook executed successfully", res.Messages[0].Content) // Verify metadata require.NotNil(t, res.Metadata) assert.Equal(t, "ts-test-chat", res.Metadata["chat_id"]) assert.Equal(t, "en-us", res.Metadata["locale"]) assert.Equal(t, "test-user-123", res.Metadata["user_id"]) assert.Equal(t, float64(2), res.Metadata["message_count"]) // 2 user messages assert.Equal(t, true, res.Metadata["typescript_features"]) } // TestLoadStoreHookReturnNull tests that hook returning null works correctly func TestLoadStoreHookReturnNull(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) assistantID := "test.store-hook-null" now := time.Now().UnixNano() ast := &assistant.Assistant{ AssistantModel: store.AssistantModel{ ID: assistantID, Name: "Test Hook Return Null", Type: "assistant", Connector: "gpt-4o", Source: ` function Create(ctx: any, messages: any[]): any { // Return null to indicate no modifications return null; } `, CreatedAt: now, UpdatedAt: now, }, } err := ast.Save() require.NoError(t, err) defer func() { storage := assistant.GetStorage() if storage != nil { storage.DeleteAssistant(assistantID) } assistant.GetCache().Clear() }() assistant.GetCache().Clear() loaded, err := assistant.Get(assistantID) require.NoError(t, err) require.NotNil(t, loaded) require.NotNil(t, loaded.HookScript) ctx := newStoreTestContext("null-test-chat", assistantID) messages := []context.Message{{Role: "user", Content: "Hello"}} res, _, err := loaded.HookScript.Create(ctx, messages, &context.Options{}) require.NoError(t, err, "Hook returning null should not error") assert.Nil(t, res, "Hook returning null should return nil response") } // TestLoadStoreHookWithPromptPreset tests that hook can return prompt_preset func TestLoadStoreHookWithPromptPreset(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) assistantID := "test.store-hook-preset" now := time.Now().UnixNano() ast := &assistant.Assistant{ AssistantModel: store.AssistantModel{ ID: assistantID, Name: "Test Hook Prompt Preset", Type: "assistant", Connector: "gpt-4o", Prompts: []store.Prompt{ {Role: "system", Content: "Default prompt"}, }, PromptPresets: map[string][]store.Prompt{ "friendly": { {Role: "system", Content: "You are a friendly assistant."}, }, "professional": { {Role: "system", Content: "You are a professional assistant."}, }, }, Source: ` function Create(ctx: any, messages: any[]): any { // Check first message to determine preset const firstMsg = messages[0]; if (firstMsg && typeof firstMsg.content === "string") { if (firstMsg.content.includes("friendly")) { return { prompt_preset: "friendly" }; } if (firstMsg.content.includes("professional")) { return { prompt_preset: "professional" }; } } return null; } `, CreatedAt: now, UpdatedAt: now, }, } err := ast.Save() require.NoError(t, err) defer func() { storage := assistant.GetStorage() if storage != nil { storage.DeleteAssistant(assistantID) } assistant.GetCache().Clear() }() assistant.GetCache().Clear() loaded, err := assistant.Get(assistantID) require.NoError(t, err) require.NotNil(t, loaded) require.NotNil(t, loaded.HookScript) // Test friendly preset selection t.Run("SelectFriendlyPreset", func(t *testing.T) { ctx := newStoreTestContext("preset-test-1", assistantID) messages := []context.Message{{Role: "user", Content: "Be friendly please"}} res, _, err := loaded.HookScript.Create(ctx, messages, &context.Options{}) require.NoError(t, err) require.NotNil(t, res) assert.Equal(t, "friendly", res.PromptPreset) }) // Test professional preset selection t.Run("SelectProfessionalPreset", func(t *testing.T) { ctx := newStoreTestContext("preset-test-2", assistantID) messages := []context.Message{{Role: "user", Content: "Be professional"}} res, _, err := loaded.HookScript.Create(ctx, messages, &context.Options{}) require.NoError(t, err) require.NotNil(t, res) assert.Equal(t, "professional", res.PromptPreset) }) // Test no preset (returns null) t.Run("NoPreset", func(t *testing.T) { ctx := newStoreTestContext("preset-test-3", assistantID) messages := []context.Message{{Role: "user", Content: "Hello"}} res, _, err := loaded.HookScript.Create(ctx, messages, &context.Options{}) require.NoError(t, err) assert.Nil(t, res) }) } // TestLoadStoreHookDisableGlobalPrompts tests that hook can disable global prompts func TestLoadStoreHookDisableGlobalPrompts(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) assistantID := "test.store-hook-disable-global" now := time.Now().UnixNano() ast := &assistant.Assistant{ AssistantModel: store.AssistantModel{ ID: assistantID, Name: "Test Hook Disable Global", Type: "assistant", Connector: "gpt-4o", Source: ` function Create(ctx: any, messages: any[]): any { const firstMsg = messages[0]; if (firstMsg && typeof firstMsg.content === "string") { if (firstMsg.content.includes("disable_global")) { return { disable_global_prompts: true }; } if (firstMsg.content.includes("enable_global")) { return { disable_global_prompts: false }; } } return null; } `, CreatedAt: now, UpdatedAt: now, }, } err := ast.Save() require.NoError(t, err) defer func() { storage := assistant.GetStorage() if storage != nil { storage.DeleteAssistant(assistantID) } assistant.GetCache().Clear() }() assistant.GetCache().Clear() loaded, err := assistant.Get(assistantID) require.NoError(t, err) require.NotNil(t, loaded) require.NotNil(t, loaded.HookScript) // Test disable global prompts t.Run("DisableGlobalPrompts", func(t *testing.T) { ctx := newStoreTestContext("disable-test-1", assistantID) messages := []context.Message{{Role: "user", Content: "disable_global prompts"}} res, _, err := loaded.HookScript.Create(ctx, messages, &context.Options{}) require.NoError(t, err) require.NotNil(t, res) require.NotNil(t, res.DisableGlobalPrompts) assert.True(t, *res.DisableGlobalPrompts) }) // Test enable global prompts t.Run("EnableGlobalPrompts", func(t *testing.T) { ctx := newStoreTestContext("disable-test-2", assistantID) messages := []context.Message{{Role: "user", Content: "enable_global prompts"}} res, _, err := loaded.HookScript.Create(ctx, messages, &context.Options{}) require.NoError(t, err) require.NotNil(t, res) require.NotNil(t, res.DisableGlobalPrompts) assert.False(t, *res.DisableGlobalPrompts) }) } // TestLoadStoreWithSearchConfig tests loading assistant with search configuration from database func TestLoadStoreWithSearchConfig(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) assistantID := "test.store-with-search" now := time.Now().UnixNano() ast := &assistant.Assistant{ AssistantModel: store.AssistantModel{ ID: assistantID, Name: "Test With Search Config", Type: "assistant", Connector: "gpt-4o", Uses: &context.Uses{ Vision: "agent", Audio: "mcp:audio-server", Fetch: "agent", Web: "builtin", Keyword: "builtin", QueryDSL: "builtin", Rerank: "builtin", }, Search: &searchTypes.Config{ Web: &searchTypes.WebConfig{ Provider: "tavily", MaxResults: 15, }, KB: &searchTypes.KBConfig{ Collections: []string{"docs", "faq"}, Threshold: 0.8, Graph: true, }, DB: &searchTypes.DBConfig{ Models: []string{"user", "product"}, MaxResults: 50, }, Keyword: &searchTypes.KeywordConfig{ MaxKeywords: 8, Language: "auto", }, QueryDSL: &searchTypes.QueryDSLConfig{ Strict: true, }, Rerank: &searchTypes.RerankConfig{ TopN: 5, }, Citation: &searchTypes.CitationConfig{ Format: "#cite:{id}", AutoInjectPrompt: false, CustomPrompt: "Please cite sources.", }, Weights: &searchTypes.WeightsConfig{ User: 1.0, Hook: 0.85, Auto: 0.65, }, Options: &searchTypes.OptionsConfig{ SkipThreshold: 3, }, }, CreatedAt: now, UpdatedAt: now, }, } err := ast.Save() require.NoError(t, err) defer func() { storage := assistant.GetStorage() if storage != nil { storage.DeleteAssistant(assistantID) } assistant.GetCache().Clear() }() assistant.GetCache().Clear() loaded, err := assistant.Get(assistantID) require.NoError(t, err) require.NotNil(t, loaded) // Verify Uses require.NotNil(t, loaded.Uses) assert.Equal(t, "agent", loaded.Uses.Vision) assert.Equal(t, "mcp:audio-server", loaded.Uses.Audio) assert.Equal(t, "agent", loaded.Uses.Fetch) assert.Equal(t, "builtin", loaded.Uses.Web) assert.Equal(t, "builtin", loaded.Uses.Keyword) assert.Equal(t, "builtin", loaded.Uses.QueryDSL) assert.Equal(t, "builtin", loaded.Uses.Rerank) // Verify Search config require.NotNil(t, loaded.Search) // Web config require.NotNil(t, loaded.Search.Web) assert.Equal(t, "tavily", loaded.Search.Web.Provider) assert.Equal(t, 15, loaded.Search.Web.MaxResults) // KB config require.NotNil(t, loaded.Search.KB) assert.Equal(t, []string{"docs", "faq"}, loaded.Search.KB.Collections) assert.Equal(t, 0.8, loaded.Search.KB.Threshold) assert.True(t, loaded.Search.KB.Graph) // DB config require.NotNil(t, loaded.Search.DB) assert.Equal(t, []string{"user", "product"}, loaded.Search.DB.Models) assert.Equal(t, 50, loaded.Search.DB.MaxResults) // Keyword config require.NotNil(t, loaded.Search.Keyword) assert.Equal(t, 8, loaded.Search.Keyword.MaxKeywords) assert.Equal(t, "auto", loaded.Search.Keyword.Language) // QueryDSL config require.NotNil(t, loaded.Search.QueryDSL) assert.True(t, loaded.Search.QueryDSL.Strict) // Rerank config require.NotNil(t, loaded.Search.Rerank) assert.Equal(t, 5, loaded.Search.Rerank.TopN) // Citation config require.NotNil(t, loaded.Search.Citation) assert.Equal(t, "#cite:{id}", loaded.Search.Citation.Format) assert.False(t, loaded.Search.Citation.AutoInjectPrompt) assert.Equal(t, "Please cite sources.", loaded.Search.Citation.CustomPrompt) // Weights config require.NotNil(t, loaded.Search.Weights) assert.Equal(t, 1.0, loaded.Search.Weights.User) assert.Equal(t, 0.85, loaded.Search.Weights.Hook) assert.Equal(t, 0.65, loaded.Search.Weights.Auto) // Options config require.NotNil(t, loaded.Search.Options) assert.Equal(t, 3, loaded.Search.Options.SkipThreshold) } // TestLoadStoreWithPartialSearchConfig tests loading assistant with partial search configuration func TestLoadStoreWithPartialSearchConfig(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) assistantID := "test.store-partial-search" now := time.Now().UnixNano() ast := &assistant.Assistant{ AssistantModel: store.AssistantModel{ ID: assistantID, Name: "Test Partial Search Config", Type: "assistant", Connector: "gpt-4o", Search: &searchTypes.Config{ Web: &searchTypes.WebConfig{ Provider: "serper", }, // Only web config, others are nil }, CreatedAt: now, UpdatedAt: now, }, } err := ast.Save() require.NoError(t, err) defer func() { storage := assistant.GetStorage() if storage != nil { storage.DeleteAssistant(assistantID) } assistant.GetCache().Clear() }() assistant.GetCache().Clear() loaded, err := assistant.Get(assistantID) require.NoError(t, err) require.NotNil(t, loaded) // Verify Search config require.NotNil(t, loaded.Search) // Web config should be set require.NotNil(t, loaded.Search.Web) assert.Equal(t, "serper", loaded.Search.Web.Provider) // Other configs should be nil assert.Nil(t, loaded.Search.KB) assert.Nil(t, loaded.Search.DB) assert.Nil(t, loaded.Search.Keyword) assert.Nil(t, loaded.Search.QueryDSL) assert.Nil(t, loaded.Search.Rerank) assert.Nil(t, loaded.Search.Citation) assert.Nil(t, loaded.Search.Weights) assert.Nil(t, loaded.Search.Options) } // TestLoadStoreWithoutSearchConfig tests loading assistant without search configuration func TestLoadStoreWithoutSearchConfig(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) assistantID := "test.store-no-search" now := time.Now().UnixNano() ast := &assistant.Assistant{ AssistantModel: store.AssistantModel{ ID: assistantID, Name: "Test No Search Config", Type: "assistant", Connector: "gpt-4o", // No Search config CreatedAt: now, UpdatedAt: now, }, } err := ast.Save() require.NoError(t, err) defer func() { storage := assistant.GetStorage() if storage != nil { storage.DeleteAssistant(assistantID) } assistant.GetCache().Clear() }() assistant.GetCache().Clear() loaded, err := assistant.Get(assistantID) require.NoError(t, err) require.NotNil(t, loaded) // Search config should be nil assert.Nil(t, loaded.Search) }