From 3dd63e553024b1868e579b9be4e7d6dbd58d6c3e Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 2 Dec 2025 11:33:09 +0800 Subject: [PATCH 1/7] Refactor model capabilities to use OpenAI struct - Updated the model capabilities throughout the agent to utilize the new gouOpenAI.Capabilities struct instead of the previous ModelCapabilities. - Adjusted related methods and types to ensure compatibility with the new capabilities structure, enhancing clarity and maintainability. - Improved context handling and message processing by directly integrating OpenAI capabilities, streamlining the overall architecture. --- agent/assistant/agent.go | 68 +++-------- agent/assistant/load.go | 5 +- agent/assistant/trace.go | 3 +- agent/assistant/types.go | 13 -- agent/context/output.go | 15 +-- agent/context/types.go | 3 +- agent/context/types_llm.go | 17 ++- agent/llm/adapters/reasoning.go | 7 +- agent/llm/providers/base/base.go | 17 +-- agent/llm/providers/openai/claude_test.go | 103 +++++++--------- .../llm/providers/openai/deepseek_r1_test.go | 47 ++++---- .../llm/providers/openai/deepseek_v3_test.go | 47 ++++---- agent/llm/providers/openai/gpt5_test.go | 45 ++++--- agent/llm/providers/openai/openai.go | 35 +++--- agent/llm/providers/openai/openai_test.go | 111 ++++++++---------- .../llm/providers/openai/temperature_test.go | 42 +++---- agent/load.go | 3 +- agent/output/adapters/openai/types.go | 2 +- agent/output/adapters/openai/writer.go | 5 +- agent/output/message/types.go | 16 +-- agent/types/types.go | 3 +- 21 files changed, 244 insertions(+), 363 deletions(-) diff --git a/agent/assistant/agent.go b/agent/assistant/agent.go index c6cdb31b..bcb3af53 100644 --- a/agent/assistant/agent.go +++ b/agent/assistant/agent.go @@ -6,6 +6,7 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/connector" + "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/agent/assistant/handlers" "github.com/yaoapp/yao/agent/context" @@ -303,7 +304,7 @@ func (ast *Assistant) Stream(ctx *context.Context, inputMessages []context.Messa // GetConnector get the connector object, capabilities, and error with priority: createResponse > ctx > ast // Note: createResponse.Connector is already applied to ctx.Connector by applyContextAdjustments in create.go // Returns: (connector, capabilities, error) -func (ast *Assistant) GetConnector(ctx *context.Context) (connector.Connector, *context.ModelCapabilities, error) { +func (ast *Assistant) GetConnector(ctx *context.Context) (connector.Connector, *openai.Capabilities, error) { // Determine connector ID with priority connectorID := ast.Connector if ctx.Connector != "" { @@ -328,62 +329,27 @@ func (ast *Assistant) GetConnector(ctx *context.Context) (connector.Connector, * } // getConnectorCapabilities get the capabilities of a connector from settings -func (ast *Assistant) getConnectorCapabilities(connectorID string) *context.ModelCapabilities { - // Initialize with default capabilities (all disabled) - falseVal := false - capabilities := &context.ModelCapabilities{ - Vision: falseVal, - ToolCalls: &falseVal, - Audio: &falseVal, - Reasoning: &falseVal, - Streaming: &falseVal, - } - +func (ast *Assistant) getConnectorCapabilities(connectorID string) *openai.Capabilities { // Get model capabilities from global configuration modelCaps, exists := modelCapabilities[connectorID] if !exists { // Return default capabilities if model not found in configuration - return capabilities + falseVal := false + return &openai.Capabilities{ + Vision: falseVal, + ToolCalls: false, + Audio: false, + Reasoning: false, + Streaming: false, + JSON: false, + Multimodal: false, + TemperatureAdjustable: true, // Default to true for non-reasoning models + } } - // Update capabilities based on model configuration - // Vision can be bool or string (VisionFormat) - if modelCaps.Vision != nil { - capabilities.Vision = modelCaps.Vision - } - - // Handle both Tools (deprecated) and ToolCalls - if modelCaps.ToolCalls || modelCaps.Tools { - v := true - capabilities.ToolCalls = &v - } - - if modelCaps.Audio { - v := true - capabilities.Audio = &v - } - - if modelCaps.Reasoning { - v := true - capabilities.Reasoning = &v - } - - if modelCaps.Streaming { - v := true - capabilities.Streaming = &v - } - - if modelCaps.JSON { - v := true - capabilities.JSON = &v - } - - if modelCaps.Multimodal { - v := true - capabilities.Multimodal = &v - } - - return capabilities + // Return capabilities directly + // Note: TemperatureAdjustable is automatically set in connector.Setting() based on Reasoning flag + return &modelCaps } // Info get the assistant information diff --git a/agent/assistant/load.go b/agent/assistant/load.go index cf98ad55..718d6303 100644 --- a/agent/assistant/load.go +++ b/agent/assistant/load.go @@ -11,6 +11,7 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/spf13/cast" "github.com/yaoapp/gou/application" + gouOpenAI "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/gou/fs" v8 "github.com/yaoapp/gou/runtime/v8" "github.com/yaoapp/yao/agent/assistant/hook" @@ -26,7 +27,7 @@ import ( var loaded = NewCache(200) // 200 is the default capacity var storage store.Store = nil var search interface{} = nil -var modelCapabilities map[string]ModelCapabilities = map[string]ModelCapabilities{} +var modelCapabilities map[string]gouOpenAI.Capabilities = map[string]gouOpenAI.Capabilities{} var defaultConnector string = "" // default connector var globalUses *context.Uses = nil // global uses configuration from agent.yml @@ -131,7 +132,7 @@ func SetStorage(s store.Store) { } // SetModelCapabilities set the model capabilities configuration -func SetModelCapabilities(capabilities map[string]ModelCapabilities) { +func SetModelCapabilities(capabilities map[string]gouOpenAI.Capabilities) { modelCapabilities = capabilities } diff --git a/agent/assistant/trace.go b/agent/assistant/trace.go index c8807f60..61ea2b7e 100644 --- a/agent/assistant/trace.go +++ b/agent/assistant/trace.go @@ -3,6 +3,7 @@ package assistant import ( "fmt" + "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/i18n" @@ -48,7 +49,7 @@ func (ast *Assistant) traceCreateHook(agentNode types.Node, createResponse *cont } // traceConnectorCapabilities logs the connector capabilities to the agent trace node -func (ast *Assistant) traceConnectorCapabilities(agentNode types.Node, capabilities *context.ModelCapabilities) { +func (ast *Assistant) traceConnectorCapabilities(agentNode types.Node, capabilities *openai.Capabilities) { if agentNode == nil { return } diff --git a/agent/assistant/types.go b/agent/assistant/types.go index 542e76d6..6345930d 100644 --- a/agent/assistant/types.go +++ b/agent/assistant/types.go @@ -40,19 +40,6 @@ type Assistant struct { // toolCalls bool // Whether this assistant supports tool_calls } -// ModelCapabilities defines the capabilities of a language model -// This configuration is loaded from agent/models.yml -type ModelCapabilities struct { - Vision interface{} `json:"vision,omitempty" yaml:"vision,omitempty"` // Supports vision/image input: bool or VisionFormat string ("openai", "claude"/"base64", "default") - Tools bool `json:"tools,omitempty" yaml:"tools,omitempty"` // Supports tool/function calling (deprecated, use ToolCalls) - ToolCalls bool `json:"tool_calls,omitempty" yaml:"tool_calls,omitempty"` // Supports tool/function calling - Audio bool `json:"audio,omitempty" yaml:"audio,omitempty"` // Supports audio input/output - Reasoning bool `json:"reasoning,omitempty" yaml:"reasoning,omitempty"` // Supports reasoning/thinking mode (o1, DeepSeek R1) - Streaming bool `json:"streaming,omitempty" yaml:"streaming,omitempty"` // Supports streaming responses - JSON bool `json:"json,omitempty" yaml:"json,omitempty"` // Supports JSON mode - Multimodal bool `json:"multimodal,omitempty" yaml:"multimodal,omitempty"` // Supports multimodal input -} - // VisionCapableModels list of LLM models that support vision capabilities var VisionCapableModels = map[string]bool{ // OpenAI Models diff --git a/agent/context/output.go b/agent/context/output.go index 23683a60..e50bdc54 100644 --- a/agent/context/output.go +++ b/agent/context/output.go @@ -3,6 +3,7 @@ package context import ( "time" + "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/yao/agent/output" "github.com/yaoapp/yao/agent/output/message" ) @@ -290,18 +291,10 @@ func (ctx *Context) getOutput() (*output.Output, error) { Accept: string(ctx.Accept), } - // Convert ModelCapabilities to message.ModelCapabilities + // Set ModelCapabilities (now using openai.Capabilities directly) if ctx.Capabilities != nil { - options.Capabilities = &message.ModelCapabilities{ - Vision: ctx.Capabilities.Vision, - ToolCalls: ctx.Capabilities.ToolCalls, - Audio: ctx.Capabilities.Audio, - Reasoning: ctx.Capabilities.Reasoning, - Streaming: ctx.Capabilities.Streaming, - JSON: ctx.Capabilities.JSON, - Multimodal: ctx.Capabilities.Multimodal, - TemperatureAdjustable: ctx.Capabilities.TemperatureAdjustable, - } + caps := openai.Capabilities(*ctx.Capabilities) + options.Capabilities = &caps } var err error diff --git a/agent/context/types.go b/agent/context/types.go index 1e6ecc82..4a5f6587 100644 --- a/agent/context/types.go +++ b/agent/context/types.go @@ -5,6 +5,7 @@ import ( "sync" "time" + "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/gou/plan" "github.com/yaoapp/gou/store" "github.com/yaoapp/yao/agent/output" @@ -238,7 +239,7 @@ type Context struct { Skip *Skip `json:"skip,omitempty"` // Skip configuration (history, trace, etc.), nil means don't skip anything // Model capabilities (set by assistant, used by output adapters) - Capabilities *ModelCapabilities `json:"-"` // Model capabilities for the current connector + Capabilities *openai.Capabilities `json:"-"` // Model capabilities for the current connector // Interrupt control (all interrupt-related logic is encapsulated in InterruptController) Interrupt *InterruptController `json:"-"` // Interrupt controller for handling user interrupts during streaming diff --git a/agent/context/types_llm.go b/agent/context/types_llm.go index 64cc9ea0..541e51b8 100644 --- a/agent/context/types_llm.go +++ b/agent/context/types_llm.go @@ -1,6 +1,9 @@ package context -import "github.com/yaoapp/yao/agent/output/message" +import ( + "github.com/yaoapp/gou/connector/openai" + "github.com/yaoapp/yao/agent/output/message" +) // Uses represents the wrapper configurations for assistant // Used to specify which assistant or MCP server to use for vision, audio, search, and fetch operations @@ -28,17 +31,13 @@ const ( VisionFormatDefault VisionFormat = "default" ) -// ModelCapabilities defines the capabilities of a language model -// Used by LLM to select appropriate provider and validate requests -type ModelCapabilities message.ModelCapabilities - // GetVisionSupport returns whether vision is supported and the format -func (m *ModelCapabilities) GetVisionSupport() (bool, VisionFormat) { - if m == nil || m.Vision == nil { +func GetVisionSupport(cap *openai.Capabilities) (bool, VisionFormat) { + if cap == nil || cap.Vision == nil { return false, VisionFormatNone } - switch v := m.Vision.(type) { + switch v := cap.Vision.(type) { case bool: // Legacy bool format return v, VisionFormatDefault @@ -65,7 +64,7 @@ func (m *ModelCapabilities) GetVisionSupport() (bool, VisionFormat) { type CompletionOptions struct { // Model capabilities (used by LLM to select appropriate provider) // nil means capabilities are not specified/checked - Capabilities *ModelCapabilities `json:"capabilities,omitempty"` + Capabilities *openai.Capabilities `json:"capabilities,omitempty"` // User-specified tools for vision, audio, search, and fetch processing Uses *Uses `json:"uses,omitempty"` diff --git a/agent/llm/adapters/reasoning.go b/agent/llm/adapters/reasoning.go index 62a45474..a864e6a0 100644 --- a/agent/llm/adapters/reasoning.go +++ b/agent/llm/adapters/reasoning.go @@ -1,6 +1,7 @@ package adapters import ( + "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/yao/agent/context" ) @@ -28,7 +29,7 @@ type ReasoningAdapter struct { // NewReasoningAdapter creates a new reasoning adapter // If cap.TemperatureAdjustable is provided, it overrides the default behavior -func NewReasoningAdapter(format ReasoningFormat, cap *context.ModelCapabilities) *ReasoningAdapter { +func NewReasoningAdapter(format ReasoningFormat, cap *openai.Capabilities) *ReasoningAdapter { supportsEffort := false supportsTemperature := true @@ -49,8 +50,8 @@ func NewReasoningAdapter(format ReasoningFormat, cap *context.ModelCapabilities) } // Override with explicit capability if provided - if cap != nil && cap.TemperatureAdjustable != nil { - supportsTemperature = *cap.TemperatureAdjustable + if cap != nil { + supportsTemperature = cap.TemperatureAdjustable } return &ReasoningAdapter{ diff --git a/agent/llm/providers/base/base.go b/agent/llm/providers/base/base.go index 1f281888..10ebc12e 100644 --- a/agent/llm/providers/base/base.go +++ b/agent/llm/providers/base/base.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/yaoapp/gou/connector" + "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/yao/agent/context" ) @@ -11,11 +12,11 @@ import ( // Provides common functionality for all LLM providers type Provider struct { Connector connector.Connector - Capabilities *context.ModelCapabilities + Capabilities *openai.Capabilities } // NewProvider create a new base provider -func NewProvider(conn connector.Connector, capabilities *context.ModelCapabilities) *Provider { +func NewProvider(conn connector.Connector, capabilities *openai.Capabilities) *Provider { return &Provider{ Connector: conn, Capabilities: capabilities, @@ -74,33 +75,33 @@ func (p *Provider) SupportsVision() bool { if p.Capabilities == nil { return false } - supported, _ := p.Capabilities.GetVisionSupport() + supported, _ := context.GetVisionSupport(p.Capabilities) return supported } // SupportsAudio check if this provider supports audio func (p *Provider) SupportsAudio() bool { - return p.Capabilities != nil && p.Capabilities.Audio != nil && *p.Capabilities.Audio + return p.Capabilities != nil && p.Capabilities.Audio } // SupportsTools check if this provider supports tool calls func (p *Provider) SupportsTools() bool { - return p.Capabilities != nil && p.Capabilities.ToolCalls != nil && *p.Capabilities.ToolCalls + return p.Capabilities != nil && p.Capabilities.ToolCalls } // SupportsStreaming check if this provider supports streaming func (p *Provider) SupportsStreaming() bool { - return p.Capabilities != nil && p.Capabilities.Streaming != nil && *p.Capabilities.Streaming + return p.Capabilities != nil && p.Capabilities.Streaming } // SupportsJSON check if this provider supports JSON mode func (p *Provider) SupportsJSON() bool { - return p.Capabilities != nil && p.Capabilities.JSON != nil && *p.Capabilities.JSON + return p.Capabilities != nil && p.Capabilities.JSON } // SupportsReasoning check if this provider supports reasoning mode func (p *Provider) SupportsReasoning() bool { - return p.Capabilities != nil && p.Capabilities.Reasoning != nil && *p.Capabilities.Reasoning + return p.Capabilities != nil && p.Capabilities.Reasoning } // GetConnectorSetting gets a setting value from the connector diff --git a/agent/llm/providers/openai/claude_test.go b/agent/llm/providers/openai/claude_test.go index 3b31566f..d0b1e2bd 100644 --- a/agent/llm/providers/openai/claude_test.go +++ b/agent/llm/providers/openai/claude_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/yaoapp/gou/connector" + "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/gou/plan" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/llm" @@ -60,15 +61,13 @@ func TestClaudeSonnet4StreamBasic(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - falseVal := false options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - Reasoning: &falseVal, // Claude Sonnet 4 (non-thinking) doesn't expose reasoning - ToolCalls: &trueVal, - Vision: &trueVal, - Multimodal: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: true, + Reasoning: false, // Claude Sonnet 4 (non-thinking) doesn't expose reasoning + ToolCalls: true, + Vision: "claude", // Claude requires base64 format + Multimodal: true, }, } @@ -140,15 +139,13 @@ func TestClaudeSonnet4PostBasic(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - falseVal := false options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &falseVal, - Reasoning: &falseVal, - ToolCalls: &trueVal, - Vision: &trueVal, - Multimodal: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: false, + Reasoning: false, + ToolCalls: true, + Vision: "claude", // Claude requires base64 format + Multimodal: true, }, } @@ -206,15 +203,13 @@ func TestClaudeSonnet4WithToolCalls(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - falseVal := false options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &falseVal, - Reasoning: &falseVal, - ToolCalls: &trueVal, - Vision: &trueVal, - Multimodal: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: false, + Reasoning: false, + ToolCalls: true, + Vision: "claude", // Claude requires base64 format + Multimodal: true, }, } @@ -244,8 +239,8 @@ func TestClaudeSonnet4WithToolCalls(t *testing.T) { options.Tools = []map[string]interface{}{simpleTool} options.ToolChoice = "auto" - // Set lower max_tokens for faster response - maxTokens := 50 + // Set enough tokens for tool call response + maxTokens := 150 options.MaxTokens = &maxTokens llmInstance, err := llm.New(conn, options) @@ -256,7 +251,7 @@ func TestClaudeSonnet4WithToolCalls(t *testing.T) { messages := []context.Message{ { Role: context.RoleUser, - Content: "Call get_info with query='A' and count=1", + Content: "Please use the get_info function to retrieve information. Pass 'A' as the query parameter and 1 as the count parameter.", }, } @@ -299,15 +294,13 @@ func TestClaudeSonnet4Vision(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - falseVal := false options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &falseVal, - Reasoning: &falseVal, - ToolCalls: &trueVal, - Vision: &trueVal, - Multimodal: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: false, + Reasoning: false, + ToolCalls: true, + Vision: "claude", // Claude requires base64 format + Multimodal: true, }, } @@ -376,15 +369,13 @@ func TestClaudeSonnet4ThinkingStream(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - falseVal := false options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - Reasoning: &trueVal, // Claude Thinking mode exposes reasoning - ToolCalls: &falseVal, - Vision: &trueVal, - Multimodal: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: true, + Reasoning: true, // Claude Thinking mode exposes reasoning + ToolCalls: false, + Vision: "claude", // Claude requires base64 format + Multimodal: true, }, } @@ -456,15 +447,13 @@ func TestClaudeSonnet4ThinkingPost(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - falseVal := false options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &falseVal, - Reasoning: &trueVal, - ToolCalls: &falseVal, - Vision: &trueVal, - Multimodal: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: false, + Reasoning: true, + ToolCalls: false, + Vision: "claude", // Claude requires base64 format + Multimodal: true, }, } @@ -550,14 +539,12 @@ func TestClaudeTemperatureHandling(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - falseVal := false options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &falseVal, - Reasoning: &tt.reasoning, - ToolCalls: &trueVal, - Vision: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: false, + Reasoning: tt.reasoning, + ToolCalls: true, + Vision: true, }, } diff --git a/agent/llm/providers/openai/deepseek_r1_test.go b/agent/llm/providers/openai/deepseek_r1_test.go index 4d8a3a3a..839fe642 100644 --- a/agent/llm/providers/openai/deepseek_r1_test.go +++ b/agent/llm/providers/openai/deepseek_r1_test.go @@ -7,6 +7,7 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/connector" + "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/gou/plan" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/llm" @@ -28,16 +29,14 @@ func TestDeepSeekR1StreamBasic(t *testing.T) { } // Create LLM instance with capabilities - trueVal := true - falseVal := false options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - Reasoning: &trueVal, // DeepSeek R1 supports reasoning - ToolCalls: &falseVal, // R1 doesn't support native tool calls - Vision: &falseVal, - Audio: &falseVal, - Multimodal: &falseVal, + Capabilities: &openai.Capabilities{ + Streaming: true, + Reasoning: true, // DeepSeek R1 supports reasoning + ToolCalls: false, // R1 doesn't support native tool calls + Vision: false, + Audio: false, + Multimodal: false, }, } @@ -207,15 +206,13 @@ func TestDeepSeekR1PostBasic(t *testing.T) { } // Create LLM instance - trueVal := true - falseVal := false options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Reasoning: &trueVal, - ToolCalls: &falseVal, - Vision: &falseVal, - Audio: &falseVal, - Multimodal: &falseVal, + Capabilities: &openai.Capabilities{ + Reasoning: true, + ToolCalls: false, + Vision: false, + Audio: false, + Multimodal: false, }, } @@ -295,16 +292,14 @@ func TestDeepSeekR1LogicPuzzle(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - falseVal := false options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - Reasoning: &trueVal, - ToolCalls: &falseVal, - Vision: &falseVal, - Audio: &falseVal, - Multimodal: &falseVal, + Capabilities: &openai.Capabilities{ + Streaming: true, + Reasoning: true, + ToolCalls: false, + Vision: false, + Audio: false, + Multimodal: false, }, } diff --git a/agent/llm/providers/openai/deepseek_v3_test.go b/agent/llm/providers/openai/deepseek_v3_test.go index f9194ad6..d593d473 100644 --- a/agent/llm/providers/openai/deepseek_v3_test.go +++ b/agent/llm/providers/openai/deepseek_v3_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/yaoapp/gou/connector" + "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/gou/plan" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/llm" @@ -24,16 +25,14 @@ func TestDeepSeekV3StreamBasic(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - falseVal := false options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - Reasoning: &falseVal, // V3 doesn't support reasoning - ToolCalls: &trueVal, // V3 supports tool calls - Vision: &falseVal, - Audio: &falseVal, - Multimodal: &falseVal, + Capabilities: &openai.Capabilities{ + Streaming: true, + Reasoning: false, // V3 doesn't support reasoning + ToolCalls: true, // V3 supports tool calls + Vision: false, + Audio: false, + Multimodal: false, }, } @@ -135,15 +134,13 @@ func TestDeepSeekV3PostBasic(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - falseVal := false options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Reasoning: &falseVal, - ToolCalls: &trueVal, - Vision: &falseVal, - Audio: &falseVal, - Multimodal: &falseVal, + Capabilities: &openai.Capabilities{ + Reasoning: false, + ToolCalls: true, + Vision: false, + Audio: false, + Multimodal: false, }, } @@ -226,12 +223,10 @@ func TestDeepSeekV3WithToolCalls(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - falseVal := false options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Reasoning: &falseVal, - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Reasoning: false, + ToolCalls: true, }, } @@ -318,13 +313,11 @@ func TestDeepSeekV3NoReasoningEffort(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - falseVal := false effort := "high" options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Reasoning: &falseVal, // V3 doesn't support reasoning - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Reasoning: false, // V3 doesn't support reasoning + ToolCalls: true, }, ReasoningEffort: &effort, // Should be ignored by adapter } diff --git a/agent/llm/providers/openai/gpt5_test.go b/agent/llm/providers/openai/gpt5_test.go index 5e2d709c..b71b0a8c 100644 --- a/agent/llm/providers/openai/gpt5_test.go +++ b/agent/llm/providers/openai/gpt5_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/yaoapp/gou/connector" + "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/gou/plan" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/llm" @@ -24,14 +25,13 @@ func TestGPT5StreamBasic(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - Reasoning: &trueVal, // GPT-5 supports reasoning - ToolCalls: &trueVal, - Vision: &trueVal, - Multimodal: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: true, + Reasoning: true, // GPT-5 supports reasoning + ToolCalls: true, + Vision: true, + Multimodal: true, }, } @@ -105,11 +105,10 @@ func TestGPT5ReasoningEffort(t *testing.T) { for _, effort := range effortLevels { t.Run("effort_"+effort, func(t *testing.T) { - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Reasoning: &trueVal, - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Reasoning: true, + ToolCalls: true, }, ReasoningEffort: &effort, } @@ -172,11 +171,10 @@ func TestGPT5PostWithToolCalls(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Reasoning: &trueVal, - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Reasoning: true, + ToolCalls: true, }, } @@ -259,12 +257,11 @@ func TestGPT5Vision(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Reasoning: &trueVal, - Vision: &trueVal, - Multimodal: &trueVal, + Capabilities: &openai.Capabilities{ + Reasoning: true, + Vision: true, + Multimodal: true, }, } @@ -331,13 +328,11 @@ func TestGPT5ReasoningEffortWithGPT4o(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - falseVal := false effort := "high" options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Reasoning: &falseVal, // GPT-4o doesn't support reasoning - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Reasoning: false, // GPT-4o doesn't support reasoning + ToolCalls: true, }, ReasoningEffort: &effort, // Should be ignored by adapter } diff --git a/agent/llm/providers/openai/openai.go b/agent/llm/providers/openai/openai.go index 6f60886b..637aa7b0 100644 --- a/agent/llm/providers/openai/openai.go +++ b/agent/llm/providers/openai/openai.go @@ -8,6 +8,7 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/connector" + gouOpenAI "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/gou/http" "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/agent/context" @@ -142,7 +143,7 @@ func buildAPIURL(host, endpoint string) string { } // New create a new OpenAI provider with capability adapters -func New(conn connector.Connector, capabilities *context.ModelCapabilities) *Provider { +func New(conn connector.Connector, capabilities *gouOpenAI.Capabilities) *Provider { return &Provider{ Provider: base.NewProvider(conn, capabilities), adapters: buildAdapters(capabilities), @@ -150,7 +151,7 @@ func New(conn connector.Connector, capabilities *context.ModelCapabilities) *Pro } // buildAdapters builds capability adapters based on model capabilities -func buildAdapters(cap *context.ModelCapabilities) []adapters.CapabilityAdapter { +func buildAdapters(cap *gouOpenAI.Capabilities) []adapters.CapabilityAdapter { if cap == nil { return []adapters.CapabilityAdapter{} } @@ -158,12 +159,10 @@ func buildAdapters(cap *context.ModelCapabilities) []adapters.CapabilityAdapter result := make([]adapters.CapabilityAdapter, 0) // Tool call adapter - if cap.ToolCalls != nil { - result = append(result, adapters.NewToolCallAdapter(*cap.ToolCalls)) - } + result = append(result, adapters.NewToolCallAdapter(cap.ToolCalls)) // Vision adapter - visionSupport, visionFormat := cap.GetVisionSupport() + visionSupport, visionFormat := context.GetVisionSupport(cap) if visionSupport { result = append(result, adapters.NewVisionAdapter(true, visionFormat)) } else if cap.Vision != nil { @@ -172,31 +171,27 @@ func buildAdapters(cap *context.ModelCapabilities) []adapters.CapabilityAdapter } // Audio adapter - if cap.Audio != nil { - result = append(result, adapters.NewAudioAdapter(*cap.Audio)) - } + result = append(result, adapters.NewAudioAdapter(cap.Audio)) // Reasoning adapter (always add to handle reasoning_effort and temperature parameters) // Even if the model doesn't support reasoning, we need the adapter to strip reasoning_effort - if cap.Reasoning != nil { - if *cap.Reasoning { - // Detect reasoning format based on capabilities - format := detectReasoningFormat(cap) - result = append(result, adapters.NewReasoningAdapter(format, cap)) - } else { - // Model doesn't support reasoning, use None format to strip reasoning parameters - result = append(result, adapters.NewReasoningAdapter(adapters.ReasoningFormatNone, cap)) - } + if cap.Reasoning { + // Detect reasoning format based on capabilities + format := detectReasoningFormat(cap) + result = append(result, adapters.NewReasoningAdapter(format, cap)) + } else { + // Model doesn't support reasoning, use None format to strip reasoning parameters + result = append(result, adapters.NewReasoningAdapter(adapters.ReasoningFormatNone, cap)) } return result } // detectReasoningFormat detects the reasoning format based on capabilities -func detectReasoningFormat(cap *context.ModelCapabilities) adapters.ReasoningFormat { +func detectReasoningFormat(cap *gouOpenAI.Capabilities) adapters.ReasoningFormat { // TODO: Implement better detection logic // For now, default to OpenAI o1 format if reasoning is supported - if cap.Reasoning != nil && *cap.Reasoning { + if cap.Reasoning { return adapters.ReasoningFormatOpenAI } return adapters.ReasoningFormatNone diff --git a/agent/llm/providers/openai/openai_test.go b/agent/llm/providers/openai/openai_test.go index 36d44b2a..b4195137 100644 --- a/agent/llm/providers/openai/openai_test.go +++ b/agent/llm/providers/openai/openai_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/yaoapp/gou/connector" + "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/gou/plan" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/llm" @@ -29,11 +30,10 @@ func TestOpenAIStreamBasic(t *testing.T) { } // Create LLM instance with capabilities - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: true, + ToolCalls: true, }, } @@ -117,10 +117,9 @@ func TestOpenAIPostBasic(t *testing.T) { } // Create LLM instance - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + ToolCalls: true, }, } @@ -192,11 +191,10 @@ func TestOpenAIStreamWithToolCalls(t *testing.T) { } // Create LLM instance with tool call capabilities - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: true, + ToolCalls: true, }, } @@ -307,10 +305,9 @@ func TestOpenAIPostWithToolCalls(t *testing.T) { } // Create LLM instance with tool call capabilities - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + ToolCalls: true, }, } @@ -424,11 +421,10 @@ func TestOpenAIStreamWithInvalidToolCall(t *testing.T) { } // Create LLM instance - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: true, + ToolCalls: true, }, } @@ -523,11 +519,10 @@ func TestOpenAIStreamRetry(t *testing.T) { } // Create LLM instance - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - ToolCalls: &trueVal, // Need this to select OpenAI provider + Capabilities: &openai.Capabilities{ + Streaming: true, + ToolCalls: true, // Need this to select OpenAI provider }, } @@ -582,11 +577,10 @@ func TestOpenAIStreamChunkTypes(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: true, + ToolCalls: true, }, } @@ -649,11 +643,10 @@ func TestOpenAIStreamErrorCallback(t *testing.T) { t.Fatalf("Failed to create test connector: %v", err) } - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: true, + ToolCalls: true, }, } @@ -711,11 +704,10 @@ func TestOpenAIToolCallValidationRetry(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: true, + ToolCalls: true, }, Tools: []map[string]interface{}{ { @@ -815,11 +807,10 @@ func TestOpenAIJSONMode(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: true, + ToolCalls: true, }, ResponseFormat: &context.ResponseFormat{ Type: context.ResponseFormatJSON, @@ -902,10 +893,9 @@ func TestOpenAIJSONModePost(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + ToolCalls: true, }, ResponseFormat: &context.ResponseFormat{ Type: context.ResponseFormatJSON, @@ -973,8 +963,6 @@ func TestOpenAIJSONSchema(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - // Define a strict JSON schema // Note: For OpenAI strict mode, 'required' must include ALL properties schema := map[string]interface{}{ @@ -1009,9 +997,9 @@ func TestOpenAIJSONSchema(t *testing.T) { } options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: true, + ToolCalls: true, }, ResponseFormat: &context.ResponseFormat{ Type: context.ResponseFormatJSONSchema, @@ -1019,7 +1007,7 @@ func TestOpenAIJSONSchema(t *testing.T) { Name: "user_info", Description: "User information schema", Schema: schema, - Strict: &trueVal, + Strict: func() *bool { v := true; return &v }(), }, }, } @@ -1121,8 +1109,6 @@ func TestOpenAIJSONSchemaPost(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - // Simple schema for testing // Note: For OpenAI strict mode, 'required' must include ALL properties schema := map[string]interface{}{ @@ -1144,8 +1130,8 @@ func TestOpenAIJSONSchemaPost(t *testing.T) { } options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + ToolCalls: true, }, ResponseFormat: &context.ResponseFormat{ Type: context.ResponseFormatJSONSchema, @@ -1153,7 +1139,7 @@ func TestOpenAIJSONSchemaPost(t *testing.T) { Name: "api_response", Description: "API response format", Schema: schema, - Strict: &trueVal, + Strict: func() *bool { v := true; return &v }(), }, }, } @@ -1263,11 +1249,10 @@ func TestOpenAIStreamLifecycleEvents(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: true, + ToolCalls: true, }, } @@ -1369,11 +1354,10 @@ func TestOpenAIStreamContextCancellation(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Streaming: true, + ToolCalls: true, }, } @@ -1443,13 +1427,12 @@ func TestOpenAIStreamWithTemperature(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true temperature := 0.7 // Moderate temperature options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Streaming: &trueVal, - ToolCalls: &trueVal, // Need this to select OpenAI provider + Capabilities: &openai.Capabilities{ + Streaming: true, + ToolCalls: true, // Need this to select OpenAI provider }, Temperature: &temperature, } diff --git a/agent/llm/providers/openai/temperature_test.go b/agent/llm/providers/openai/temperature_test.go index 83b5f6be..d648a74f 100644 --- a/agent/llm/providers/openai/temperature_test.go +++ b/agent/llm/providers/openai/temperature_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/yaoapp/gou/connector" + "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/gou/plan" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/llm" @@ -23,11 +24,10 @@ func TestTemperatureGPT5AutoReset(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true invalidTemp := 0.7 // GPT-5 doesn't support this options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Reasoning: &trueVal, + Capabilities: &openai.Capabilities{ + Reasoning: true, }, Temperature: &invalidTemp, // Should be reset to 1.0 } @@ -73,11 +73,10 @@ func TestTemperatureDeepSeekR1AutoReset(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true invalidTemp := 0.5 // DeepSeek R1 doesn't support this options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Reasoning: &trueVal, + Capabilities: &openai.Capabilities{ + Reasoning: true, }, Temperature: &invalidTemp, // Should be reset to 1.0 } @@ -126,13 +125,11 @@ func TestTemperatureGPT4oPreserved(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - falseVal := false customTemp := 0.3 // GPT-4o should preserve this options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Reasoning: &falseVal, // Not a reasoning model - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Reasoning: false, // Not a reasoning model + ToolCalls: true, }, Temperature: &customTemp, // Should be preserved } @@ -178,13 +175,11 @@ func TestTemperatureDeepSeekV3Preserved(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - falseVal := false customTemp := 0.8 // DeepSeek V3 should preserve this options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Reasoning: &falseVal, // Not a reasoning model - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Reasoning: false, // Not a reasoning model + ToolCalls: true, }, Temperature: &customTemp, // Should be preserved } @@ -230,11 +225,10 @@ func TestTemperatureGPT5Default(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true defaultTemp := 1.0 // GPT-5's valid temperature options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Reasoning: &trueVal, + Capabilities: &openai.Capabilities{ + Reasoning: true, }, Temperature: &defaultTemp, // Should work fine } @@ -293,16 +287,14 @@ func TestTemperatureNoTemperatureProvided(t *testing.T) { t.Fatalf("Failed to select connector: %v", err) } - trueVal := true - falseVal := false options := &context.CompletionOptions{ - Capabilities: &context.ModelCapabilities{ - Reasoning: &falseVal, - ToolCalls: &trueVal, + Capabilities: &openai.Capabilities{ + Reasoning: false, + ToolCalls: true, }, } if tc.reasoning { - options.Capabilities.Reasoning = &trueVal + options.Capabilities.Reasoning = true } // Temperature not set - should use API default diff --git a/agent/load.go b/agent/load.go index b03819c5..d5c56f24 100644 --- a/agent/load.go +++ b/agent/load.go @@ -6,6 +6,7 @@ import ( "github.com/yaoapp/gou/application" "github.com/yaoapp/gou/connector" + gouOpenAI "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/yao/agent/assistant" "github.com/yaoapp/yao/agent/context" "github.com/yaoapp/yao/agent/i18n" @@ -116,7 +117,7 @@ func initModelCapabilities() error { return err } - var models map[string]assistant.ModelCapabilities = map[string]assistant.ModelCapabilities{} + var models map[string]gouOpenAI.Capabilities = map[string]gouOpenAI.Capabilities{} err = application.Parse("models.yml", bytes, &models) if err != nil { return err diff --git a/agent/output/adapters/openai/types.go b/agent/output/adapters/openai/types.go index 0a154bd6..c2da4598 100644 --- a/agent/output/adapters/openai/types.go +++ b/agent/output/adapters/openai/types.go @@ -35,7 +35,7 @@ type AdapterConfig struct { Locale string } -// ModelCapabilities is a simplified version of context.ModelCapabilities +// ModelCapabilities is a simplified version of openai.Capabilities // We use a local type to avoid circular dependencies type ModelCapabilities struct { Reasoning *bool // Supports reasoning/thinking mode (o1, DeepSeek R1) diff --git a/agent/output/adapters/openai/writer.go b/agent/output/adapters/openai/writer.go index 40ab8be8..05a40404 100644 --- a/agent/output/adapters/openai/writer.go +++ b/agent/output/adapters/openai/writer.go @@ -22,9 +22,10 @@ type Writer struct { func NewWriter(options message.Options) (*Writer, error) { // Get model capabilities from context (set by assistant) var capabilities *ModelCapabilities - if options.Capabilities != nil && options.Capabilities.Reasoning != nil { + if options.Capabilities != nil && options.Capabilities.Reasoning { + v := true capabilities = &ModelCapabilities{ - Reasoning: options.Capabilities.Reasoning, + Reasoning: &v, } } diff --git a/agent/output/message/types.go b/agent/output/message/types.go index 0f752d46..41ac872e 100644 --- a/agent/output/message/types.go +++ b/agent/output/message/types.go @@ -3,6 +3,7 @@ package message import ( "net/http" + "github.com/yaoapp/gou/connector/openai" traceTypes "github.com/yaoapp/yao/trace/types" ) @@ -12,23 +13,10 @@ type Options struct { Accept string Writer http.ResponseWriter Trace traceTypes.Manager - Capabilities *ModelCapabilities + Capabilities *openai.Capabilities Locale string } -// ModelCapabilities defines the capabilities of a language model -// Used by LLM to select appropriate provider and validate requests -type ModelCapabilities struct { - Vision interface{} `json:"vision,omitempty"` // Supports vision/image input: bool or VisionFormat string ("openai", "claude"/"base64", "default") - ToolCalls *bool `json:"tool_calls,omitempty"` // Supports tool/function calling - Audio *bool `json:"audio,omitempty"` // Supports audio input/output - Reasoning *bool `json:"reasoning,omitempty"` // Supports reasoning/thinking mode (o1, DeepSeek R1) - Streaming *bool `json:"streaming,omitempty"` // Supports streaming responses - JSON *bool `json:"json,omitempty"` // Supports JSON mode - Multimodal *bool `json:"multimodal,omitempty"` // Supports multimodal input (text + images + audio) - TemperatureAdjustable *bool `json:"temperature_adjustable,omitempty"` // Supports temperature adjustment (reasoning models typically don't) -} - // Message represents a universal message structure (DSL) // All messages are expressed through Type + Props, without predefining specific types type Message struct { diff --git a/agent/types/types.go b/agent/types/types.go index 427c9c33..38d980dd 100644 --- a/agent/types/types.go +++ b/agent/types/types.go @@ -1,6 +1,7 @@ package types import ( + "github.com/yaoapp/gou/connector/openai" "github.com/yaoapp/yao/agent/assistant" store "github.com/yaoapp/yao/agent/store/types" ) @@ -16,7 +17,7 @@ type DSL struct { // Global External Settings - model capabilities, tools, etc. // =============================== - Models map[string]assistant.ModelCapabilities `json:"models,omitempty" yaml:"models,omitempty"` // The model capabilities configuration + Models map[string]openai.Capabilities `json:"models,omitempty" yaml:"models,omitempty"` // The model capabilities configuration // Internal // =============================== From 339a486eb4651fee7d2d5ef639f35e5e9f7e0171 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 2 Dec 2025 12:36:47 +0800 Subject: [PATCH 2/7] Refactor assistant capabilities and update data handling - Enhanced the getConnectorCapabilities method to prioritize model capabilities and connector settings, improving capability retrieval logic. - Deprecated the tools field in the Assistant model, transitioning to MCP for tool management, and updated related methods accordingly. - Introduced new fields for connector options and prompt presets in the Assistant model, allowing for more flexible configurations. - Updated the GetAssistant method to support field selection, improving data retrieval efficiency and flexibility. - Refactored tests and documentation to reflect changes in the assistant structure and capabilities, ensuring clarity and maintainability. --- agent/assistant/agent.go | 47 ++- agent/assistant/assistant.go | 40 +-- agent/assistant/load.go | 33 +- agent/store/mongo/mongo.go | 3 +- agent/store/redis/redis.go | 3 +- agent/store/types/convert.go | 36 ++- agent/store/types/convert_test.go | 235 ++++++++++++-- agent/store/types/fields.go | 108 +++++-- agent/store/types/fields_test.go | 97 +++++- agent/store/types/store.go | 4 +- agent/store/types/types.go | 77 +++-- agent/store/xun/assistant.go | 85 +++-- agent/store/xun/assistant_test.go | 318 +++++++++++++++---- data/bindata.go | 284 ++++++++--------- openapi/agent/assistant.go | 19 +- openapi/agent/filter.go | 7 +- openapi/agent/models.go | 15 +- openapi/tests/agent/assistant_update_test.go | 64 +--- yao/models/agent/assistant.mod.yao | 24 +- 19 files changed, 1019 insertions(+), 480 deletions(-) diff --git a/agent/assistant/agent.go b/agent/assistant/agent.go index bcb3af53..c3d9fa42 100644 --- a/agent/assistant/agent.go +++ b/agent/assistant/agent.go @@ -323,33 +323,56 @@ func (ast *Assistant) GetConnector(ctx *context.Context) (connector.Connector, * } // Get connector capabilities from settings - capabilities := ast.getConnectorCapabilities(connectorID) + capabilities := ast.getConnectorCapabilities(conn) return conn, capabilities, nil } // getConnectorCapabilities get the capabilities of a connector from settings -func (ast *Assistant) getConnectorCapabilities(connectorID string) *openai.Capabilities { - // Get model capabilities from global configuration - modelCaps, exists := modelCapabilities[connectorID] - if !exists { - // Return default capabilities if model not found in configuration - falseVal := false +// Priority: 1. modelCapabilities mapping, 2. connector's Setting()["capabilities"] +func (ast *Assistant) getConnectorCapabilities(conn connector.Connector) *openai.Capabilities { + if conn == nil { return &openai.Capabilities{ - Vision: falseVal, + Vision: false, ToolCalls: false, Audio: false, Reasoning: false, Streaming: false, JSON: false, Multimodal: false, - TemperatureAdjustable: true, // Default to true for non-reasoning models + TemperatureAdjustable: true, } } - // Return capabilities directly - // Note: TemperatureAdjustable is automatically set in connector.Setting() based on Reasoning flag - return &modelCaps + // Get connector ID + connectorID := conn.ID() + + // Priority 1: Check global modelCapabilities mapping + if modelCaps, exists := modelCapabilities[connectorID]; exists { + return &modelCaps + } + + // Priority 2: Get capabilities from connector's Setting() method + // Modern connectors (post-upgrade) provide default capabilities via Setting() + settings := conn.Setting() + if caps, ok := settings["capabilities"]; ok { + if capabilities, ok := caps.(*openai.Capabilities); ok { + return capabilities + } + } + + // Fallback: Return minimal default capabilities + // This should rarely happen with upgraded connectors + return &openai.Capabilities{ + Vision: false, + ToolCalls: false, + Audio: false, + Reasoning: false, + Streaming: false, + JSON: false, + Multimodal: false, + TemperatureAdjustable: true, // Default to true for non-reasoning models + } } // Info get the assistant information diff --git a/agent/assistant/assistant.go b/agent/assistant/assistant.go index b80c221a..46d06eb1 100644 --- a/agent/assistant/assistant.go +++ b/agent/assistant/assistant.go @@ -4,7 +4,6 @@ import ( "fmt" "path" - jsoniter "github.com/json-iterator/go" "github.com/yaoapp/gou/fs" "github.com/yaoapp/yao/agent/i18n" store "github.com/yaoapp/yao/agent/store/types" @@ -105,7 +104,6 @@ func (ast *Assistant) Map() map[string]interface{} { "prompts": ast.Prompts, "kb": ast.KB, "mcp": ast.MCP, - "tools": ast.Tools, "workflow": ast.Workflow, "tags": ast.Tags, "mentionable": ast.Mentionable, @@ -247,20 +245,6 @@ func (ast *Assistant) Clone() *Assistant { copy(clone.Prompts, ast.Prompts) } - // Deep copy tools - if ast.Tools != nil { - clone.Tools = &store.ToolCalls{} - if ast.Tools.Tools != nil { - clone.Tools.Tools = make([]store.Tool, len(ast.Tools.Tools)) - copy(clone.Tools.Tools, ast.Tools.Tools) - } - - if ast.Tools.Prompts != nil { - clone.Tools.Prompts = make([]store.Prompt, len(ast.Tools.Prompts)) - copy(clone.Tools.Prompts, ast.Tools.Prompts) - } - } - // Deep copy workflow if ast.Workflow != nil { clone.Workflow = &store.Workflow{} @@ -328,29 +312,7 @@ func (ast *Assistant) Update(data map[string]interface{}) error { ast.Connector = v } - if v, has := data["tools"]; has { - switch tools := v.(type) { - case []store.Tool: - ast.Tools = &store.ToolCalls{ - Tools: tools, - Prompts: ast.Prompts, - } - - case *store.ToolCalls: - ast.Tools = tools - - default: - raw, err := jsoniter.Marshal(tools) - if err != nil { - return err - } - ast.Tools = &store.ToolCalls{} - err = jsoniter.Unmarshal(raw, &ast.Tools) - if err != nil { - return err - } - } - } + // Note: tools field is deprecated, now handled by MCP if v, ok := data["type"].(string); ok { ast.Type = v diff --git a/agent/assistant/load.go b/agent/assistant/load.go index 718d6303..c46c541c 100644 --- a/agent/assistant/load.go +++ b/agent/assistant/load.go @@ -181,7 +181,8 @@ func LoadStore(id string) (*Assistant, error) { return nil, fmt.Errorf("storage is not set") } - storeModel, err := storage.GetAssistant(id) + // Request all fields when loading assistant from store + storeModel, err := storage.GetAssistant(id, store.AssistantFullFields) if err != nil { return nil, err } @@ -509,32 +510,10 @@ func loadMap(data map[string]interface{}) (*Assistant, error) { } } - // tools - if tools, has := data["tools"]; has { - switch vv := tools.(type) { - case []store.Tool: - assistant.Tools = &store.ToolCalls{ - Tools: vv, - Prompts: assistant.Prompts, - } - - case store.ToolCalls: - assistant.Tools = &vv - - default: - raw, err := jsoniter.Marshal(tools) - if err != nil { - return nil, fmt.Errorf("tools format error %s", err.Error()) - } - - var tools store.ToolCalls - err = jsoniter.Unmarshal(raw, &tools) - if err != nil { - return nil, fmt.Errorf("tools format error %s", err.Error()) - } - assistant.Tools = &tools - } - } + // tools - deprecated, now handled by MCP + // if tools, has := data["tools"]; has { + // ... removed ... + // } // kb if kb, has := data["kb"]; has { diff --git a/agent/store/mongo/mongo.go b/agent/store/mongo/mongo.go index b7f7fda3..9567c762 100644 --- a/agent/store/mongo/mongo.go +++ b/agent/store/mongo/mongo.go @@ -76,7 +76,8 @@ func (m *Mongo) GetAssistants(filter types.AssistantFilter, locale ...string) (* } // GetAssistant retrieves a single assistant by ID -func (m *Mongo) GetAssistant(assistantID string, locale ...string) (*types.AssistantModel, error) { +// fields: Optional list of fields to retrieve. If empty, a default set of fields will be returned. +func (m *Mongo) GetAssistant(assistantID string, fields []string, locale ...string) (*types.AssistantModel, error) { return nil, nil } diff --git a/agent/store/redis/redis.go b/agent/store/redis/redis.go index 36abd48a..cfc52b16 100644 --- a/agent/store/redis/redis.go +++ b/agent/store/redis/redis.go @@ -76,7 +76,8 @@ func (r *Redis) GetAssistants(filter types.AssistantFilter, locale ...string) (* } // GetAssistant retrieves a single assistant by ID -func (r *Redis) GetAssistant(assistantID string, locale ...string) (*types.AssistantModel, error) { +// fields: Optional list of fields to retrieve. If empty, a default set of fields will be returned. +func (r *Redis) GetAssistant(assistantID string, fields []string, locale ...string) (*types.AssistantModel, error) { return nil, nil } diff --git a/agent/store/types/convert.go b/agent/store/types/convert.go index d8ddc59f..d8a59b44 100644 --- a/agent/store/types/convert.go +++ b/agent/store/types/convert.go @@ -217,6 +217,9 @@ func ToAssistantModel(v interface{}) (*AssistantModel, error) { if path, ok := data["path"].(string); ok { model.Path = path } + if source, ok := data["source"].(string); ok { + model.Source = source + } if description, ok := data["description"].(string); ok { model.Description = description } @@ -277,6 +280,28 @@ func ToAssistantModel(v interface{}) (*AssistantModel, error) { } } + // PromptPresets + if promptPresets, ok := data["prompt_presets"]; ok && promptPresets != nil { + raw, err := jsoniter.Marshal(promptPresets) + if err == nil { + var pp map[string][]Prompt + if err := jsoniter.Unmarshal(raw, &pp); err == nil { + model.PromptPresets = pp + } + } + } + + // ConnectorOptions + if connectorOptions, ok := data["connector_options"]; ok && connectorOptions != nil { + raw, err := jsoniter.Marshal(connectorOptions) + if err == nil { + var co ConnectorOptions + if err := jsoniter.Unmarshal(raw, &co); err == nil { + model.ConnectorOptions = &co + } + } + } + // KB if kb, ok := data["kb"]; ok && kb != nil { kbConverted, err := ToKnowledgeBase(kb) @@ -301,17 +326,6 @@ func ToAssistantModel(v interface{}) (*AssistantModel, error) { } } - // Tools - if tools, ok := data["tools"]; ok && tools != nil { - raw, err := jsoniter.Marshal(tools) - if err == nil { - var tc ToolCalls - if err := jsoniter.Unmarshal(raw, &tc); err == nil { - model.Tools = &tc - } - } - } - // Placeholder if placeholder, ok := data["placeholder"]; ok && placeholder != nil { raw, err := jsoniter.Marshal(placeholder) diff --git a/agent/store/types/convert_test.go b/agent/store/types/convert_test.go index ad17acbe..3fbfce1f 100644 --- a/agent/store/types/convert_test.go +++ b/agent/store/types/convert_test.go @@ -428,24 +428,38 @@ func TestToAssistantModel(t *testing.T) { "name": "Test Assistant", "avatar": "https://example.com/avatar.png", "connector": "openai", - "path": "/path/to/assistant", - "description": "Test description", - "share": "team", - "built_in": true, - "readonly": false, - "public": true, - "mentionable": true, - "automated": false, - "sort": 100, - "created_at": int64(1609459200), - "updated_at": int64(1609459300), - "tags": []string{"tag1", "tag2"}, + "connector_options": map[string]interface{}{ + "optional": true, + "connectors": []string{"openai", "anthropic"}, + "filters": []string{"vision", "tool_calls"}, + }, + "path": "/path/to/assistant", + "description": "Test description", + "share": "team", + "built_in": true, + "readonly": false, + "public": true, + "mentionable": true, + "automated": false, + "sort": 100, + "created_at": int64(1609459200), + "updated_at": int64(1609459300), + "tags": []string{"tag1", "tag2"}, "options": map[string]interface{}{ "temperature": 0.7, }, "prompts": []map[string]interface{}{ {"role": "system", "content": "You are helpful"}, }, + "prompt_presets": map[string]interface{}{ + "chat": []map[string]interface{}{ + {"role": "system", "content": "You are a chat assistant"}, + }, + "task": []map[string]interface{}{ + {"role": "system", "content": "You are a task assistant"}, + }, + }, + "source": "function hook() { return 'test'; }", "kb": map[string]interface{}{ "collections": []string{"col1"}, }, @@ -455,9 +469,6 @@ func TestToAssistantModel(t *testing.T) { "workflow": map[string]interface{}{ "workflows": []string{"wf1"}, }, - "tools": map[string]interface{}{ - "calls": []string{"tool1"}, - }, "placeholder": map[string]interface{}{ "title": "Enter message", }, @@ -489,9 +500,25 @@ func TestToAssistantModel(t *testing.T) { if result.Connector != "openai" { t.Errorf("Expected Connector 'openai', got '%s'", result.Connector) } + if result.ConnectorOptions == nil { + t.Error("Expected ConnectorOptions to be set") + } else { + if !result.ConnectorOptions.Optional { + t.Error("Expected ConnectorOptions.Optional to be true") + } + if len(result.ConnectorOptions.Connectors) != 2 { + t.Errorf("Expected 2 connectors in options, got %d", len(result.ConnectorOptions.Connectors)) + } + if len(result.ConnectorOptions.Filters) != 2 { + t.Errorf("Expected 2 filters, got %d", len(result.ConnectorOptions.Filters)) + } + } if result.Path != "/path/to/assistant" { t.Errorf("Expected Path, got '%s'", result.Path) } + if result.Source != "function hook() { return 'test'; }" { + t.Errorf("Expected Source, got '%s'", result.Source) + } if result.Description != "Test description" { t.Errorf("Expected Description, got '%s'", result.Description) } @@ -531,6 +558,23 @@ func TestToAssistantModel(t *testing.T) { if len(result.Prompts) != 1 { t.Errorf("Expected 1 prompt, got %d", len(result.Prompts)) } + if result.PromptPresets == nil { + t.Error("Expected PromptPresets to be set") + } else { + if len(result.PromptPresets) != 2 { + t.Errorf("Expected 2 prompt presets, got %d", len(result.PromptPresets)) + } + if chatPrompts, ok := result.PromptPresets["chat"]; !ok { + t.Error("Expected 'chat' prompt preset") + } else if len(chatPrompts) != 1 { + t.Errorf("Expected 1 chat prompt, got %d", len(chatPrompts)) + } + if taskPrompts, ok := result.PromptPresets["task"]; !ok { + t.Error("Expected 'task' prompt preset") + } else if len(taskPrompts) != 1 { + t.Errorf("Expected 1 task prompt, got %d", len(taskPrompts)) + } + } if result.KB == nil { t.Error("Expected KB to be set") } @@ -540,9 +584,6 @@ func TestToAssistantModel(t *testing.T) { if result.Workflow == nil { t.Error("Expected Workflow to be set") } - if result.Tools == nil { - t.Error("Expected Tools to be set") - } if result.Placeholder == nil { t.Error("Expected Placeholder to be set") } @@ -583,7 +624,6 @@ func TestToAssistantModel(t *testing.T) { "kb": nil, "mcp": nil, "workflow": nil, - "tools": nil, "placeholder": nil, "locales": nil, } @@ -659,6 +699,163 @@ func TestToAssistantModel(t *testing.T) { }) } +// TestToAssistantModelNewFields tests the newly added fields +func TestToAssistantModelNewFields(t *testing.T) { + t.Run("ConnectorOptions", func(t *testing.T) { + data := map[string]interface{}{ + "connector_options": map[string]interface{}{ + "optional": true, + "connectors": []string{"openai", "anthropic", "azure"}, + "filters": []string{"vision", "tool_calls", "audio"}, + }, + } + + result, err := ToAssistantModel(data) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + + if result.ConnectorOptions == nil { + t.Fatal("Expected ConnectorOptions to be set") + } + + if !result.ConnectorOptions.Optional { + t.Error("Expected Optional to be true") + } + + if len(result.ConnectorOptions.Connectors) != 3 { + t.Errorf("Expected 3 connectors, got %d", len(result.ConnectorOptions.Connectors)) + } + + if len(result.ConnectorOptions.Filters) != 3 { + t.Errorf("Expected 3 filters, got %d", len(result.ConnectorOptions.Filters)) + } + }) + + t.Run("PromptPresets", func(t *testing.T) { + data := map[string]interface{}{ + "prompt_presets": map[string]interface{}{ + "chat": []map[string]interface{}{ + {"role": "system", "content": "You are a helpful chat assistant"}, + {"role": "user", "content": "Example question"}, + }, + "task": []map[string]interface{}{ + {"role": "system", "content": "You are a task completion assistant"}, + }, + "analyze": []map[string]interface{}{ + {"role": "system", "content": "You are a data analysis assistant"}, + }, + }, + } + + result, err := ToAssistantModel(data) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + + if result.PromptPresets == nil { + t.Fatal("Expected PromptPresets to be set") + } + + if len(result.PromptPresets) != 3 { + t.Errorf("Expected 3 prompt preset modes, got %d", len(result.PromptPresets)) + } + + if chatPrompts, ok := result.PromptPresets["chat"]; !ok { + t.Error("Expected 'chat' mode in prompt presets") + } else if len(chatPrompts) != 2 { + t.Errorf("Expected 2 prompts in chat mode, got %d", len(chatPrompts)) + } + + if taskPrompts, ok := result.PromptPresets["task"]; !ok { + t.Error("Expected 'task' mode in prompt presets") + } else if len(taskPrompts) != 1 { + t.Errorf("Expected 1 prompt in task mode, got %d", len(taskPrompts)) + } + + if analyzePrompts, ok := result.PromptPresets["analyze"]; !ok { + t.Error("Expected 'analyze' mode in prompt presets") + } else if len(analyzePrompts) != 1 { + t.Errorf("Expected 1 prompt in analyze mode, got %d", len(analyzePrompts)) + } + }) + + t.Run("Source", func(t *testing.T) { + hookScript := ` +function beforeChat(context) { + console.log('Hook called'); + return context; +} +` + data := map[string]interface{}{ + "source": hookScript, + } + + result, err := ToAssistantModel(data) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + + if result.Source != hookScript { + t.Errorf("Expected Source to match, got '%s'", result.Source) + } + }) + + t.Run("AllNewFields", func(t *testing.T) { + data := map[string]interface{}{ + "connector_options": map[string]interface{}{ + "optional": true, + "connectors": []string{"openai"}, + "filters": []string{"vision"}, + }, + "prompt_presets": map[string]interface{}{ + "chat": []map[string]interface{}{ + {"role": "system", "content": "Chat mode"}, + }, + }, + "source": "function test() {}", + } + + result, err := ToAssistantModel(data) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + + if result.ConnectorOptions == nil { + t.Error("Expected ConnectorOptions to be set") + } + if result.PromptPresets == nil { + t.Error("Expected PromptPresets to be set") + } + if result.Source == "" { + t.Error("Expected Source to be set") + } + }) + + t.Run("NilNewFields", func(t *testing.T) { + data := map[string]interface{}{ + "connector_options": nil, + "prompt_presets": nil, + "source": nil, + } + + result, err := ToAssistantModel(data) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + + if result.ConnectorOptions != nil { + t.Error("Expected ConnectorOptions to be nil") + } + if result.PromptPresets != nil { + t.Error("Expected PromptPresets to be nil") + } + if result.Source != "" { + t.Error("Expected Source to be empty") + } + }) +} + // TestToAssistantModelComplexTypes tests complex type conversions in ToAssistantModel func TestToAssistantModelComplexTypes(t *testing.T) { t.Run("CompleteLocales", func(t *testing.T) { diff --git a/agent/store/types/fields.go b/agent/store/types/fields.go index b6d0ea05..d5c4975d 100644 --- a/agent/store/types/fields.go +++ b/agent/store/types/fields.go @@ -4,39 +4,43 @@ import "github.com/yaoapp/kun/log" // AssistantAllowedFields defines the whitelist of fields that can be selected for assistants var AssistantAllowedFields = map[string]bool{ - "id": true, - "assistant_id": true, - "type": true, - "name": true, - "avatar": true, - "connector": true, - "description": true, - "path": true, - "sort": true, - "built_in": true, - "placeholder": true, - "options": true, - "prompts": true, - "workflow": true, - "kb": true, - "mcp": true, - "tools": true, - "tags": true, - "readonly": true, - "public": true, - "share": true, - "locales": true, - "automated": true, - "mentionable": true, - "created_at": true, - "updated_at": true, - "__yao_created_by": true, - "__yao_updated_by": true, - "__yao_team_id": true, - "__yao_tenant_id": true, + "id": true, + "assistant_id": true, + "type": true, + "name": true, + "avatar": true, + "connector": true, + "connector_options": true, + "description": true, + "path": true, + "sort": true, + "built_in": true, + "placeholder": true, + "options": true, + "prompts": true, + "prompt_presets": true, + "workflow": true, + "kb": true, + "mcp": true, + "source": true, + "tags": true, + "readonly": true, + "public": true, + "share": true, + "locales": true, + "uses": true, + "automated": true, + "mentionable": true, + "created_at": true, + "updated_at": true, + "__yao_created_by": true, + "__yao_updated_by": true, + "__yao_team_id": true, + "__yao_tenant_id": true, } // AssistantDefaultFields defines the default fields to select for assistants when no specific fields are requested +// These are lightweight fields suitable for list views and basic information display var AssistantDefaultFields = []string{ "assistant_id", "type", @@ -44,6 +48,7 @@ var AssistantDefaultFields = []string{ "avatar", "connector", "description", + "tags", // Tags for categorization (lightweight) "sort", "built_in", "readonly", @@ -51,8 +56,51 @@ var AssistantDefaultFields = []string{ "share", "automated", "mentionable", + "kb", // Knowledge base configuration (lightweight) + "mcp", // MCP servers configuration (lightweight) "created_at", "updated_at", + "__yao_created_by", // Permission: creator user ID + "__yao_updated_by", // Permission: updater user ID + "__yao_team_id", // Permission: team ID + "__yao_tenant_id", // Permission: tenant ID +} + +// AssistantFullFields defines all available fields including complex/large fields +// Use this when you need complete assistant data for backend processing +var AssistantFullFields = []string{ + "assistant_id", + "type", + "name", + "avatar", + "connector", + "connector_options", + "description", + "path", + "sort", + "built_in", + "placeholder", + "options", + "prompts", + "prompt_presets", + "workflow", + "kb", + "mcp", + "source", + "tags", + "readonly", + "public", + "share", + "locales", + "uses", + "automated", + "mentionable", + "created_at", + "updated_at", + "__yao_created_by", + "__yao_updated_by", + "__yao_team_id", + "__yao_tenant_id", } // ValidateAssistantFields validates and filters assistant select fields against the whitelist diff --git a/agent/store/types/fields_test.go b/agent/store/types/fields_test.go index 48e12e65..9666e611 100644 --- a/agent/store/types/fields_test.go +++ b/agent/store/types/fields_test.go @@ -118,12 +118,15 @@ func TestAssistantAllowedFields(t *testing.T) { complexFields := []string{ "options", "prompts", + "prompt_presets", "workflow", "kb", "mcp", - "tools", "placeholder", "locales", + "uses", + "connector_options", + "source", } for _, field := range complexFields { if !AssistantAllowedFields[field] { @@ -139,6 +142,12 @@ func TestAssistantDefaultFields(t *testing.T) { "assistant_id", "name", "type", + "kb", // Knowledge base is essential for assistant functionality + "mcp", // MCP servers are essential for assistant functionality + "__yao_created_by", // Permission fields are essential for access control + "__yao_updated_by", + "__yao_team_id", + "__yao_tenant_id", } defaultFieldsMap := make(map[string]bool) @@ -155,15 +164,17 @@ func TestAssistantDefaultFields(t *testing.T) { t.Run("DoesNotContainSensitiveFields", func(t *testing.T) { // Default fields should not include complex/large fields by default + // Note: kb, mcp, and tags are lightweight and included in defaults sensitiveFields := []string{ "options", "prompts", + "prompt_presets", "workflow", - "kb", - "mcp", - "tools", "placeholder", "locales", + "uses", + "connector_options", + "source", } defaultFieldsMap := make(map[string]bool) @@ -178,3 +189,81 @@ func TestAssistantDefaultFields(t *testing.T) { } }) } + +func TestAssistantFullFields(t *testing.T) { + t.Run("ContainsAllAllowedFields", func(t *testing.T) { + // Full fields should contain all fields from allowed fields + fullFieldsMap := make(map[string]bool) + for _, field := range AssistantFullFields { + fullFieldsMap[field] = true + } + + for field := range AssistantAllowedFields { + if field == "id" { + // "id" is an alias for "assistant_id", skip + continue + } + if !fullFieldsMap[field] { + t.Errorf("Allowed field %s is missing from full fields", field) + } + } + }) + + t.Run("AllFieldsAreAllowed", func(t *testing.T) { + // All fields in full list should be in allowed fields + for _, field := range AssistantFullFields { + if !AssistantAllowedFields[field] { + t.Errorf("Full field %s is not in allowed fields", field) + } + } + }) + + t.Run("ContainsComplexFields", func(t *testing.T) { + // Full fields should include all complex/large fields + complexFields := []string{ + "options", + "prompts", + "prompt_presets", + "workflow", + "kb", + "mcp", + "placeholder", + "locales", + "uses", + "connector_options", + "source", + } + + fullFieldsMap := make(map[string]bool) + for _, field := range AssistantFullFields { + fullFieldsMap[field] = true + } + + for _, field := range complexFields { + if !fullFieldsMap[field] { + t.Errorf("Complex field %s is missing from full fields", field) + } + } + }) + + t.Run("ContainsPermissionFields", func(t *testing.T) { + // Full fields should include permission fields + permissionFields := []string{ + "__yao_created_by", + "__yao_updated_by", + "__yao_team_id", + "__yao_tenant_id", + } + + fullFieldsMap := make(map[string]bool) + for _, field := range AssistantFullFields { + fullFieldsMap[field] = true + } + + for _, field := range permissionFields { + if !fullFieldsMap[field] { + t.Errorf("Permission field %s is missing from full fields", field) + } + } + }) +} diff --git a/agent/store/types/store.go b/agent/store/types/store.go index 283e66cd..f1d79648 100644 --- a/agent/store/types/store.go +++ b/agent/store/types/store.go @@ -91,8 +91,10 @@ type Store interface { // GetAssistant retrieves a single assistant by ID // assistantID: Assistant ID + // fields: List of fields to select, empty/nil means default fields (AssistantDefaultFields) + // locale: Optional locale for i18n translations // Returns: Assistant information and potential error - GetAssistant(assistantID string, locale ...string) (*AssistantModel, error) + GetAssistant(assistantID string, fields []string, locale ...string) (*AssistantModel, error) // DeleteAssistants deletes assistants based on filter conditions // filter: Filter conditions diff --git a/agent/store/types/types.go b/agent/store/types/types.go index c145c65f..a4d1ca13 100644 --- a/agent/store/types/types.go +++ b/agent/store/types/types.go @@ -221,34 +221,59 @@ type Placeholder struct { Prompts []string `json:"prompts,omitempty"` } +// ModelCapability defines the available model capability filters +type ModelCapability string + +// Model capability constants for filtering connectors +const ( + CapVision ModelCapability = "vision" + CapAudio ModelCapability = "audio" + CapToolCalls ModelCapability = "tool_calls" + CapReasoning ModelCapability = "reasoning" + CapStreaming ModelCapability = "streaming" + CapJSON ModelCapability = "json" + CapMultimodal ModelCapability = "multimodal" + CapTemperatureAdjustable ModelCapability = "temperature_adjustable" +) + +// ConnectorOptions the connector selection options +// Allows defining optional connector selection with filtering capabilities +type ConnectorOptions struct { + Optional bool `json:"optional,omitempty"` // Whether connector is optional for user selection + Connectors []string `json:"connectors,omitempty"` // List of available connectors, empty means all connectors are available + Filters []ModelCapability `json:"filters,omitempty"` // Filter by model capabilities, conditions can be stacked +} + // AssistantModel the assistant database model type AssistantModel struct { - ID string `json:"assistant_id"` // Assistant ID - Type string `json:"type,omitempty"` // Assistant Type, default is assistant - Name string `json:"name,omitempty"` // Assistant Name - Avatar string `json:"avatar,omitempty"` // Assistant Avatar - Connector string `json:"connector"` // AI Connector - Path string `json:"path,omitempty"` // Assistant Path - BuiltIn bool `json:"built_in,omitempty"` // Whether this is a built-in assistant - Sort int `json:"sort,omitempty"` // Assistant Sort - Description string `json:"description,omitempty"` // Assistant Description - Tags []string `json:"tags,omitempty"` // Assistant Tags - Readonly bool `json:"readonly,omitempty"` // Whether this assistant is readonly - Public bool `json:"public,omitempty"` // Whether this assistant is shared across all teams in the platform - Share string `json:"share,omitempty"` // Assistant sharing scope (private/team) - Mentionable bool `json:"mentionable,omitempty"` // Whether this assistant is mentionable - Automated bool `json:"automated,omitempty"` // Whether this assistant is automated - Options map[string]interface{} `json:"options,omitempty"` // AI Options - Prompts []Prompt `json:"prompts,omitempty"` // AI Prompts - KB *KnowledgeBase `json:"kb,omitempty"` // Knowledge base configuration - MCP *MCPServers `json:"mcp,omitempty"` // MCP servers configuration - Tools *ToolCalls `json:"tools,omitempty"` // Assistant Tools - Workflow *Workflow `json:"workflow,omitempty"` // Workflow configuration - Placeholder *Placeholder `json:"placeholder,omitempty"` // Assistant Placeholder - Locales i18n.Map `json:"locales,omitempty"` // Assistant Locales - Uses *context.Uses `json:"uses,omitempty"` // Assistant-specific wrapper configurations for vision, audio, etc. If not set, use global settings - CreatedAt int64 `json:"created_at"` // Creation timestamp - UpdatedAt int64 `json:"updated_at"` // Last update timestamp + ID string `json:"assistant_id"` // Assistant ID + Type string `json:"type,omitempty"` // Assistant Type, default is assistant + Name string `json:"name,omitempty"` // Assistant Name + Avatar string `json:"avatar,omitempty"` // Assistant Avatar + Connector string `json:"connector"` // AI Connector (default connector) + ConnectorOptions *ConnectorOptions `json:"connector_options,omitempty"` // Connector selection options for user to choose from + Path string `json:"path,omitempty"` // Assistant Path + BuiltIn bool `json:"built_in,omitempty"` // Whether this is a built-in assistant + Sort int `json:"sort,omitempty"` // Assistant Sort + Description string `json:"description,omitempty"` // Assistant Description + Tags []string `json:"tags,omitempty"` // Assistant Tags + Readonly bool `json:"readonly,omitempty"` // Whether this assistant is readonly + Public bool `json:"public,omitempty"` // Whether this assistant is shared across all teams in the platform + Share string `json:"share,omitempty"` // Assistant sharing scope (private/team) + Mentionable bool `json:"mentionable,omitempty"` // Whether this assistant is mentionable + Automated bool `json:"automated,omitempty"` // Whether this assistant is automated + Options map[string]interface{} `json:"options,omitempty"` // AI Options + Prompts []Prompt `json:"prompts,omitempty"` // AI Prompts (default prompts) + PromptPresets map[string][]Prompt `json:"prompt_presets,omitempty"` // Prompt presets organized by mode (e.g., "chat", "task", etc.) + KB *KnowledgeBase `json:"kb,omitempty"` // Knowledge base configuration + MCP *MCPServers `json:"mcp,omitempty"` // MCP servers configuration + Workflow *Workflow `json:"workflow,omitempty"` // Workflow configuration + Placeholder *Placeholder `json:"placeholder,omitempty"` // Assistant Placeholder + Source string `json:"source,omitempty"` // Hook script source code + Locales i18n.Map `json:"locales,omitempty"` // Assistant Locales + Uses *context.Uses `json:"uses,omitempty"` // Assistant-specific wrapper configurations for vision, audio, etc. If not set, use global settings + CreatedAt int64 `json:"created_at"` // Creation timestamp + UpdatedAt int64 `json:"updated_at"` // Last update timestamp // Permission management fields (not exposed in JSON API responses) YaoCreatedBy string `json:"-"` // User who created the assistant (not exposed in JSON) diff --git a/agent/store/xun/assistant.go b/agent/store/xun/assistant.go index d1339bcb..d36bcfc4 100644 --- a/agent/store/xun/assistant.go +++ b/agent/store/xun/assistant.go @@ -102,6 +102,11 @@ func (conv *Xun) SaveAssistant(assistant *types.AssistantModel) (string, error) } else { data["path"] = nil } + if assistant.Source != "" { + data["source"] = assistant.Source + } else { + data["source"] = nil + } // Share field: nullable: false with default "private" // Apply default if empty @@ -152,14 +157,15 @@ func (conv *Xun) SaveAssistant(assistant *types.AssistantModel) (string, error) // Handle interface{} fields - they should already be in the correct format jsonFields := map[string]interface{}{ - "prompts": assistant.Prompts, - "kb": assistant.KB, - "mcp": assistant.MCP, - "workflow": assistant.Workflow, - "tools": assistant.Tools, - "placeholder": assistant.Placeholder, - "locales": assistant.Locales, - "uses": assistant.Uses, + "prompts": assistant.Prompts, + "prompt_presets": assistant.PromptPresets, + "connector_options": assistant.ConnectorOptions, + "kb": assistant.KB, + "mcp": assistant.MCP, + "workflow": assistant.Workflow, + "placeholder": assistant.Placeholder, + "locales": assistant.Locales, + "uses": assistant.Uses, } for field, value := range jsonFields { @@ -218,14 +224,14 @@ func (conv *Xun) UpdateAssistant(assistantID string, updates map[string]interfac data := make(map[string]interface{}) // List of fields that need JSON marshaling - jsonFields := []string{"options", "tags", "prompts", "kb", "mcp", "workflow", "tools", "placeholder", "locales", "uses"} + jsonFields := []string{"options", "tags", "prompts", "prompt_presets", "connector_options", "kb", "mcp", "workflow", "placeholder", "locales", "uses"} jsonFieldSet := make(map[string]bool) for _, field := range jsonFields { jsonFieldSet[field] = true } // List of nullable string fields - nullableStringFields := []string{"name", "avatar", "description", "path", "__yao_created_by", "__yao_updated_by", "__yao_team_id", "__yao_tenant_id"} + nullableStringFields := []string{"name", "avatar", "description", "path", "source", "__yao_created_by", "__yao_updated_by", "__yao_team_id", "__yao_tenant_id"} nullableFieldSet := make(map[string]bool) for _, field := range nullableStringFields { nullableFieldSet[field] = true @@ -418,7 +424,7 @@ func (conv *Xun) GetAssistants(filter types.AssistantFilter, locale ...string) ( // Convert rows to types.AssistantModel slice assistants := make([]*types.AssistantModel, 0, len(rows)) - jsonFields := []string{"tags", "options", "prompts", "workflow", "kb", "mcp", "tools", "placeholder", "locales", "uses"} + jsonFields := []string{"tags", "options", "prompts", "prompt_presets", "connector_options", "workflow", "kb", "mcp", "placeholder", "locales", "uses"} for _, row := range rows { data := row.ToMap() @@ -456,11 +462,27 @@ func (conv *Xun) GetAssistants(filter types.AssistantFilter, locale ...string) ( } // GetAssistant retrieves a single assistant by ID -func (conv *Xun) GetAssistant(assistantID string, locale ...string) (*types.AssistantModel, error) { - row, err := conv.query.New(). +func (conv *Xun) GetAssistant(assistantID string, fields []string, locale ...string) (*types.AssistantModel, error) { + qb := conv.query.New(). Table(conv.getAssistantTable()). - Where("assistant_id", assistantID). - First() + Where("assistant_id", assistantID) + + // Apply select fields with security validation + // If no fields specified, use default fields + fieldsToSelect := fields + if len(fieldsToSelect) == 0 { + fieldsToSelect = types.AssistantDefaultFields + } + + // ValidateAssistantFields will validate fields against whitelist + sanitized := types.ValidateAssistantFields(fieldsToSelect) + selectFields := make([]interface{}, len(sanitized)) + for i, field := range sanitized { + selectFields[i] = field + } + qb.Select(selectFields...) + + row, err := qb.First() if err != nil { return nil, err } @@ -475,7 +497,7 @@ func (conv *Xun) GetAssistant(assistantID string, locale ...string) (*types.Assi } // Parse JSON fields - jsonFields := []string{"tags", "options", "prompts", "workflow", "kb", "mcp", "tools", "placeholder", "locales", "uses"} + jsonFields := []string{"tags", "options", "prompts", "prompt_presets", "connector_options", "workflow", "kb", "mcp", "placeholder", "locales", "uses"} conv.parseJSONFields(data, jsonFields) // Convert map to types.AssistantModel @@ -486,6 +508,7 @@ func (conv *Xun) GetAssistant(assistantID string, locale ...string) (*types.Assi Avatar: getString(data, "avatar"), Connector: getString(data, "connector"), Path: getString(data, "path"), + Source: getString(data, "source"), BuiltIn: getBool(data, "built_in"), Sort: getInt(data, "sort"), Description: getString(data, "description"), @@ -529,6 +552,26 @@ func (conv *Xun) GetAssistant(assistantID string, locale ...string) (*types.Assi } } + if promptPresets, has := data["prompt_presets"]; has && promptPresets != nil { + raw, err := jsoniter.Marshal(promptPresets) + if err == nil { + var pp map[string][]types.Prompt + if err := jsoniter.Unmarshal(raw, &pp); err == nil { + model.PromptPresets = pp + } + } + } + + if connectorOptions, has := data["connector_options"]; has && connectorOptions != nil { + raw, err := jsoniter.Marshal(connectorOptions) + if err == nil { + var co types.ConnectorOptions + if err := jsoniter.Unmarshal(raw, &co); err == nil { + model.ConnectorOptions = &co + } + } + } + if kb, has := data["kb"]; has && kb != nil { kbConverted, err := types.ToKnowledgeBase(kb) if err == nil { @@ -550,16 +593,6 @@ func (conv *Xun) GetAssistant(assistantID string, locale ...string) (*types.Assi } } - if tools, has := data["tools"]; has && tools != nil { - raw, err := jsoniter.Marshal(tools) - if err == nil { - var tc types.ToolCalls - if err := jsoniter.Unmarshal(raw, &tc); err == nil { - model.Tools = &tc - } - } - } - if placeholder, has := data["placeholder"]; has && placeholder != nil { raw, err := jsoniter.Marshal(placeholder) if err == nil { diff --git a/agent/store/xun/assistant_test.go b/agent/store/xun/assistant_test.go index 4ba8e4a2..485cb87e 100644 --- a/agent/store/xun/assistant_test.go +++ b/agent/store/xun/assistant_test.go @@ -103,8 +103,8 @@ func TestSaveAssistant(t *testing.T) { t.Errorf("Expected ID %s, got %s", id, updatedID) } - // Verify update - retrieved, err := store.GetAssistant(id) + // Verify update - request all fields to see the update + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) if err != nil { t.Fatalf("Failed to retrieve updated assistant: %v", err) } @@ -183,8 +183,8 @@ func TestSaveAssistant(t *testing.T) { t.Fatalf("Failed to save complex assistant: %v", err) } - // Retrieve and verify - retrieved, err := store.GetAssistant(id) + // Retrieve and verify - request all fields for complex data + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) if err != nil { t.Fatalf("Failed to retrieve complex assistant: %v", err) } @@ -237,8 +237,8 @@ func TestSaveAssistant(t *testing.T) { t.Fatalf("Failed to save assistant with MCP: %v", err) } - // Retrieve and verify MCP configuration - retrieved, err := store.GetAssistant(id) + // Retrieve and verify MCP configuration - mcp is in default fields + retrieved, err := store.GetAssistant(id, []string{}) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -316,8 +316,8 @@ func TestSaveAssistant(t *testing.T) { t.Fatalf("Failed to update assistant with MCP: %v", err) } - // Retrieve and verify - retrieved, err := store.GetAssistant(id) + // Retrieve and verify - mcp is in default fields + retrieved, err := store.GetAssistant(id, []string{}) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -353,8 +353,8 @@ func TestSaveAssistant(t *testing.T) { t.Fatalf("Failed to save assistant with uses: %v", err) } - // Retrieve and verify uses configuration - retrieved, err := store.GetAssistant(id) + // Retrieve and verify uses configuration - uses is NOT in default fields, need to request all + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -396,8 +396,8 @@ func TestSaveAssistant(t *testing.T) { t.Fatalf("Failed to save assistant without uses: %v", err) } - // Retrieve and verify uses is nil - retrieved, err := store.GetAssistant(id) + // Retrieve and verify uses is nil - request all fields to check uses + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -425,8 +425,8 @@ func TestSaveAssistant(t *testing.T) { t.Fatalf("Failed to save assistant with partial uses: %v", err) } - // Retrieve and verify - retrieved, err := store.GetAssistant(id) + // Retrieve and verify - request all fields for uses + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -451,6 +451,194 @@ func TestSaveAssistant(t *testing.T) { t.Errorf("Expected fetch to be empty, got '%s'", retrieved.Uses.Fetch) } }) + + t.Run("ConnectorOptions", func(t *testing.T) { + // Test assistant with connector options + assistant := &types.AssistantModel{ + Name: "Connector Options Test", + Type: "assistant", + Connector: "openai", + Share: "private", + ConnectorOptions: &types.ConnectorOptions{ + Optional: true, + Connectors: []string{"openai", "anthropic"}, + Filters: []types.ModelCapability{types.CapVision, types.CapToolCalls}, + }, + } + + id, err := store.SaveAssistant(assistant) + if err != nil { + t.Fatalf("Failed to save assistant with connector options: %v", err) + } + + // Retrieve and verify - connector_options is NOT in default fields + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) + if err != nil { + t.Fatalf("Failed to retrieve assistant: %v", err) + } + + if retrieved.ConnectorOptions == nil { + t.Fatal("Expected connector options to be set") + } + + if !retrieved.ConnectorOptions.Optional { + t.Error("Expected optional to be true") + } + + if len(retrieved.ConnectorOptions.Connectors) != 2 { + t.Errorf("Expected 2 connectors, got %d", len(retrieved.ConnectorOptions.Connectors)) + } + + if len(retrieved.ConnectorOptions.Filters) != 2 { + t.Errorf("Expected 2 filters, got %d", len(retrieved.ConnectorOptions.Filters)) + } + + if retrieved.ConnectorOptions.Filters[0] != types.CapVision { + t.Errorf("Expected first filter to be vision, got '%s'", retrieved.ConnectorOptions.Filters[0]) + } + + t.Logf("Successfully saved and retrieved connector options for assistant %s", id) + }) + + t.Run("PromptPresets", func(t *testing.T) { + // Test assistant with prompt presets + assistant := &types.AssistantModel{ + Name: "Prompt Presets Test", + Type: "assistant", + Connector: "openai", + Share: "private", + PromptPresets: map[string][]types.Prompt{ + "chat": { + {Role: "system", Content: "You are a friendly chatbot"}, + {Role: "user", Content: "Hello!"}, + }, + "task": { + {Role: "system", Content: "You are a task executor"}, + }, + }, + } + + id, err := store.SaveAssistant(assistant) + if err != nil { + t.Fatalf("Failed to save assistant with prompt presets: %v", err) + } + + // Retrieve and verify - prompt_presets is NOT in default fields + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) + if err != nil { + t.Fatalf("Failed to retrieve assistant: %v", err) + } + + if retrieved.PromptPresets == nil { + t.Fatal("Expected prompt presets to be set") + } + + if len(retrieved.PromptPresets) != 2 { + t.Errorf("Expected 2 preset groups, got %d", len(retrieved.PromptPresets)) + } + + chatPrompts, ok := retrieved.PromptPresets["chat"] + if !ok { + t.Fatal("Expected 'chat' preset to exist") + } + + if len(chatPrompts) != 2 { + t.Errorf("Expected 2 chat prompts, got %d", len(chatPrompts)) + } + + if chatPrompts[0].Role != "system" { + t.Errorf("Expected system role, got '%s'", chatPrompts[0].Role) + } + + taskPrompts, ok := retrieved.PromptPresets["task"] + if !ok { + t.Fatal("Expected 'task' preset to exist") + } + + if len(taskPrompts) != 1 { + t.Errorf("Expected 1 task prompt, got %d", len(taskPrompts)) + } + + t.Logf("Successfully saved and retrieved prompt presets for assistant %s", id) + }) + + t.Run("SourceField", func(t *testing.T) { + // Test assistant with source code + sourceCode := `function onMessage(msg) { + console.log("Received:", msg); + return { status: "ok" }; +}` + assistant := &types.AssistantModel{ + Name: "Source Field Test", + Type: "assistant", + Connector: "openai", + Share: "private", + Source: sourceCode, + } + + id, err := store.SaveAssistant(assistant) + if err != nil { + t.Fatalf("Failed to save assistant with source: %v", err) + } + + // Retrieve and verify - source is NOT in default fields + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) + if err != nil { + t.Fatalf("Failed to retrieve assistant: %v", err) + } + + if retrieved.Source != sourceCode { + t.Errorf("Expected source code to match, got '%s'", retrieved.Source) + } + + t.Logf("Successfully saved and retrieved source code for assistant %s", id) + }) + + t.Run("AllNewFieldsTogether", func(t *testing.T) { + // Test assistant with all new fields together + assistant := &types.AssistantModel{ + Name: "All New Fields Test", + Type: "assistant", + Connector: "openai", + Share: "private", + ConnectorOptions: &types.ConnectorOptions{ + Optional: false, + Connectors: []string{"openai"}, + Filters: []types.ModelCapability{types.CapVision}, + }, + PromptPresets: map[string][]types.Prompt{ + "default": { + {Role: "system", Content: "Default system prompt"}, + }, + }, + Source: "// Hook code here", + } + + id, err := store.SaveAssistant(assistant) + if err != nil { + t.Fatalf("Failed to save assistant with all new fields: %v", err) + } + + // Retrieve and verify all new fields + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) + if err != nil { + t.Fatalf("Failed to retrieve assistant: %v", err) + } + + if retrieved.ConnectorOptions == nil { + t.Error("Expected connector options to be set") + } + + if retrieved.PromptPresets == nil { + t.Error("Expected prompt presets to be set") + } + + if retrieved.Source == "" { + t.Error("Expected source to be set") + } + + t.Logf("Successfully saved and retrieved all new fields for assistant %s", id) + }) } // TestDeleteAssistant tests deleting a single assistant @@ -487,7 +675,7 @@ func TestDeleteAssistant(t *testing.T) { } // Verify deletion - _, err = store.GetAssistant(id) + _, err = store.GetAssistant(id, nil) if err == nil { t.Error("Expected error when getting deleted assistant") } @@ -534,8 +722,8 @@ func TestGetAssistant(t *testing.T) { t.Fatalf("Failed to create assistant: %v", err) } - // Retrieve it - retrieved, err := store.GetAssistant(id) + // Retrieve it with default fields (tags are now in default fields) + retrieved, err := store.GetAssistant(id, nil) if err != nil { t.Fatalf("Failed to get assistant: %v", err) } @@ -562,7 +750,7 @@ func TestGetAssistant(t *testing.T) { }) t.Run("GetNonExistentAssistant", func(t *testing.T) { - _, err := store.GetAssistant("nonexistent-id") + _, err := store.GetAssistant("nonexistent-id", nil) if err == nil { t.Error("Expected error when getting non-existent assistant") } @@ -1313,8 +1501,8 @@ func TestAssistantPermissionFields(t *testing.T) { t.Fatalf("Failed to save assistant with permission fields: %v", err) } - // Retrieve and verify - retrieved, err := store.GetAssistant(id) + // Retrieve and verify - default fields include permission fields + retrieved, err := store.GetAssistant(id, nil) if err != nil { t.Fatalf("Failed to get assistant: %v", err) } @@ -1361,8 +1549,8 @@ func TestAssistantPermissionFields(t *testing.T) { t.Fatalf("Failed to update assistant: %v", err) } - // Verify update - retrieved, err := store.GetAssistant(id) + // Verify update - default fields include permission fields + retrieved, err := store.GetAssistant(id, nil) if err != nil { t.Fatalf("Failed to get updated assistant: %v", err) } @@ -1393,7 +1581,7 @@ func TestAssistantPermissionFields(t *testing.T) { } // Retrieve and verify fields are empty - retrieved, err := store.GetAssistant(id) + retrieved, err := store.GetAssistant(id, nil) if err != nil { t.Fatalf("Failed to get assistant: %v", err) } @@ -1448,7 +1636,7 @@ func TestEmptyStringAsNull(t *testing.T) { } // Retrieve and verify empty strings are returned (not stored as empty strings) - retrieved, err := store.GetAssistant(id) + retrieved, err := store.GetAssistant(id, nil) if err != nil { t.Fatalf("Failed to get assistant: %v", err) } @@ -1493,8 +1681,8 @@ func TestEmptyStringAsNull(t *testing.T) { t.Fatalf("Failed to save assistant: %v", err) } - // Retrieve and verify values are preserved - retrieved, err := store.GetAssistant(id) + // Retrieve and verify values are preserved - path is sensitive, need full fields + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) if err != nil { t.Fatalf("Failed to get assistant: %v", err) } @@ -1576,8 +1764,8 @@ func TestGetAssistantWithLocale(t *testing.T) { }, } - // Test English locale - retrievedEN, err := store.GetAssistant(id, "en") + // Test English locale - request all fields for placeholder + retrievedEN, err := store.GetAssistant(id, types.AssistantFullFields, "en") if err != nil { t.Fatalf("Failed to get assistant with EN locale: %v", err) } @@ -1604,8 +1792,8 @@ func TestGetAssistantWithLocale(t *testing.T) { t.Errorf("Expected first prompt 'How can I help you?', got '%s'", retrievedEN.Placeholder.Prompts[0]) } - // Test Chinese locale - retrievedZH, err := store.GetAssistant(id, "zh-cn") + // Test Chinese locale - request all fields for placeholder + retrievedZH, err := store.GetAssistant(id, types.AssistantFullFields, "zh-cn") if err != nil { t.Fatalf("Failed to get assistant with ZH locale: %v", err) } @@ -1623,8 +1811,8 @@ func TestGetAssistantWithLocale(t *testing.T) { t.Errorf("Expected placeholder title '与我聊天', got '%s'", retrievedZH.Placeholder.Title) } - // Test without locale (should return original {{...}} values) - retrievedNoLocale, err := store.GetAssistant(id) + // Test without locale (should return original {{...}} values) - request all fields for placeholder + retrievedNoLocale, err := store.GetAssistant(id, types.AssistantFullFields) if err != nil { t.Fatalf("Failed to get assistant without locale: %v", err) } @@ -2039,8 +2227,8 @@ func TestUpdateAssistant(t *testing.T) { t.Fatalf("Failed to update assistant: %v", err) } - // Verify update - retrieved, err := store.GetAssistant(id) + // Verify update - need full fields to see tags + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -2087,8 +2275,8 @@ func TestUpdateAssistant(t *testing.T) { t.Fatalf("Failed to update assistant: %v", err) } - // Verify all updates - retrieved, err := store.GetAssistant(id) + // Verify all updates - use default fields (includes name, description, sort, mentionable) + retrieved, err := store.GetAssistant(id, nil) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -2144,8 +2332,8 @@ func TestUpdateAssistant(t *testing.T) { t.Fatalf("Failed to update JSON fields: %v", err) } - // Verify updates - retrieved, err := store.GetAssistant(id) + // Verify updates - need full fields for tags, options, prompts + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -2193,8 +2381,8 @@ func TestUpdateAssistant(t *testing.T) { t.Fatalf("Failed to update KB and MCP: %v", err) } - // Verify updates - retrieved, err := store.GetAssistant(id) + // Verify updates - KB and MCP are in default fields + retrieved, err := store.GetAssistant(id, nil) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -2247,8 +2435,8 @@ func TestUpdateAssistant(t *testing.T) { t.Fatalf("Failed to update MCP: %v", err) } - // Verify updates - retrieved, err := store.GetAssistant(id) + // Verify updates - MCP is in default fields + retrieved, err := store.GetAssistant(id, nil) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -2325,8 +2513,8 @@ func TestUpdateAssistant(t *testing.T) { t.Fatalf("Failed to update uses: %v", err) } - // Verify updates - retrieved, err := store.GetAssistant(id) + // Verify updates - uses is NOT in default fields + retrieved, err := store.GetAssistant(id, types.AssistantFullFields) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -2361,8 +2549,8 @@ func TestUpdateAssistant(t *testing.T) { t.Fatalf("Failed to update uses again: %v", err) } - // Verify second update - retrieved2, err := store.GetAssistant(id) + // Verify second update - uses is NOT in default fields + retrieved2, err := store.GetAssistant(id, types.AssistantFullFields) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -2384,8 +2572,8 @@ func TestUpdateAssistant(t *testing.T) { t.Fatalf("Failed to set uses to nil: %v", err) } - // Verify uses is nil - retrieved3, err := store.GetAssistant(id) + // Verify uses is nil - uses is NOT in default fields + retrieved3, err := store.GetAssistant(id, types.AssistantFullFields) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -2422,8 +2610,8 @@ func TestUpdateAssistant(t *testing.T) { t.Fatalf("Failed to update permission fields: %v", err) } - // Verify updates - retrieved, err := store.GetAssistant(id) + // Verify updates - permission fields are in default fields + retrieved, err := store.GetAssistant(id, nil) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -2467,8 +2655,8 @@ func TestUpdateAssistant(t *testing.T) { t.Fatalf("Failed to update with empty strings: %v", err) } - // Verify empty strings are stored as NULL - retrieved, err := store.GetAssistant(id) + // Verify empty strings are stored as NULL - default fields include avatar, description + retrieved, err := store.GetAssistant(id, nil) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -2553,8 +2741,8 @@ func TestUpdateAssistant(t *testing.T) { t.Fatalf("Failed to create assistant: %v", err) } - // Get original updated_at - original, err := store.GetAssistant(id) + // Get original updated_at - default fields include updated_at + original, err := store.GetAssistant(id, nil) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -2572,8 +2760,8 @@ func TestUpdateAssistant(t *testing.T) { t.Fatalf("Failed to update assistant: %v", err) } - // Get updated assistant - updated, err := store.GetAssistant(id) + // Get updated assistant - default fields include description, updated_at + updated, err := store.GetAssistant(id, nil) if err != nil { t.Fatalf("Failed to retrieve updated assistant: %v", err) } @@ -2607,8 +2795,8 @@ func TestUpdateAssistant(t *testing.T) { t.Fatalf("Failed to create assistant: %v", err) } - // Get original - original, err := store.GetAssistant(id) + // Get original - default fields + original, err := store.GetAssistant(id, nil) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -2625,8 +2813,8 @@ func TestUpdateAssistant(t *testing.T) { t.Fatalf("Failed to update assistant: %v", err) } - // Verify system fields unchanged, but name updated - retrieved, err := store.GetAssistant(id) + // Verify system fields unchanged, but name updated - default fields + retrieved, err := store.GetAssistant(id, nil) if err != nil { t.Fatalf("Failed to retrieve assistant: %v", err) } @@ -2693,9 +2881,9 @@ func TestAssistantCompleteWorkflow(t *testing.T) { t.Errorf("Expected at least 3 assistants, got %d", len(response.Data)) } - // Step 3: Update one assistant + // Step 3: Update one assistant - need full fields for tags updatedID := assistantIDs[1] - updatedAssistant, err := store.GetAssistant(updatedID) + updatedAssistant, err := store.GetAssistant(updatedID, types.AssistantFullFields) if err != nil { t.Fatalf("Failed to get assistant for update: %v", err) } @@ -2708,8 +2896,8 @@ func TestAssistantCompleteWorkflow(t *testing.T) { t.Fatalf("Failed to update assistant: %v", err) } - // Verify update - verifyAssistant, err := store.GetAssistant(updatedID) + // Verify update - default fields include description + verifyAssistant, err := store.GetAssistant(updatedID, nil) if err != nil { t.Fatalf("Failed to verify update: %v", err) } @@ -2725,7 +2913,7 @@ func TestAssistantCompleteWorkflow(t *testing.T) { } // Verify deletion - _, err = store.GetAssistant(assistantIDs[0]) + _, err = store.GetAssistant(assistantIDs[0], nil) if err == nil { t.Error("Expected error when getting deleted assistant") } diff --git a/data/bindata.go b/data/bindata.go index 4cdba5c9..892300ef 100644 --- a/data/bindata.go +++ b/data/bindata.go @@ -319,7 +319,7 @@ func cuiSetupIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -339,7 +339,7 @@ func cuiV09IndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v0.9/index.html", size: 13, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "cui/v0.9/index.html", size: 13, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -359,7 +359,7 @@ func cuiV10IndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v1.0/index.html", size: 49, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "cui/v1.0/index.html", size: 49, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -379,7 +379,7 @@ func cuiV10Layouts__indexAsyncJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v1.0/layouts__index.async.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "cui/v1.0/layouts__index.async.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -399,7 +399,7 @@ func cuiV10UmiJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v1.0/umi.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "cui/v1.0/umi.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -419,7 +419,7 @@ func initEnv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.env", size: 219, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.env", size: 219, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -439,7 +439,7 @@ func initVscodeSettingsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -459,7 +459,7 @@ func initVscodeTypesRuntimeConsoleDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 221, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 221, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -479,7 +479,7 @@ func initVscodeTypesRuntimeExceptionDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/exception.d.ts", size: 738, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/exception.d.ts", size: 738, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -499,7 +499,7 @@ func initVscodeTypesRuntimeFsDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/fs.d.ts", size: 8554, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/fs.d.ts", size: 8554, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -519,7 +519,7 @@ func initVscodeTypesRuntimeGlobalDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 1759, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 1759, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -539,7 +539,7 @@ func initVscodeTypesRuntimeHttpDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/http.d.ts", size: 6179, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/http.d.ts", size: 6179, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -559,7 +559,7 @@ func initVscodeTypesRuntimeIoDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/io.d.ts", size: 587, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/io.d.ts", size: 587, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -579,7 +579,7 @@ func initVscodeTypesRuntimeLogDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/log.d.ts", size: 1692, mode: os.FileMode(493), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/log.d.ts", size: 1692, mode: os.FileMode(493), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -599,7 +599,7 @@ func initVscodeTypesRuntimeNeoDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/neo.d.ts", size: 3750, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/neo.d.ts", size: 3750, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -619,7 +619,7 @@ func initVscodeTypesRuntimeProcessFsDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process/fs.d.ts", size: 11133, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/fs.d.ts", size: 11133, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -639,7 +639,7 @@ func initVscodeTypesRuntimeProcessHttpDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process/http.d.ts", size: 5653, mode: os.FileMode(493), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/http.d.ts", size: 5653, mode: os.FileMode(493), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -659,7 +659,7 @@ func initVscodeTypesRuntimeProcessModelDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process/model.d.ts", size: 6656, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/model.d.ts", size: 6656, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -679,7 +679,7 @@ func initVscodeTypesRuntimeProcessDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process.d.ts", size: 23165, mode: os.FileMode(493), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process.d.ts", size: 23165, mode: os.FileMode(493), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -699,7 +699,7 @@ func initVscodeTypesRuntimeQueryDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6124, mode: os.FileMode(493), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6124, mode: os.FileMode(493), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -719,7 +719,7 @@ func initVscodeTypesRuntimeStoreDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 2251, mode: os.FileMode(493), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 2251, mode: os.FileMode(493), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -739,7 +739,7 @@ func initVscodeTypesRuntimeSuiDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/sui.d.ts", size: 1713, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/sui.d.ts", size: 1713, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -759,7 +759,7 @@ func initVscodeTypesRuntimeTimeDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/time.d.ts", size: 711, mode: os.FileMode(493), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/time.d.ts", size: 711, mode: os.FileMode(493), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -779,7 +779,7 @@ func initVscodeTypesRuntimeDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 424, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 424, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -799,7 +799,7 @@ func initVscodeTypesSuiDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 8931, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 8931, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -819,7 +819,7 @@ func initAppYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/app.yao", size: 3115, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/app.yao", size: 3115, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -839,7 +839,7 @@ func initDataReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -859,7 +859,7 @@ func initDataTemplatesDefault__assetsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -879,7 +879,7 @@ func initDataTemplatesDefault__assetsImagesIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -899,7 +899,7 @@ func initDataTemplatesDefault__assetsImagesLogosLogo_colorSvg() (*asset, error) return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -919,7 +919,7 @@ func initDataTemplatesDefault__assetsImagesLogosWordmarkSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -939,7 +939,7 @@ func initDataTemplatesDefault__dataJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 30, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 30, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -959,7 +959,7 @@ func initDataTemplatesDefault__documentHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__document.html", size: 492, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__document.html", size: 492, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -979,7 +979,7 @@ func initDataTemplatesDefaultIndexIndexCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 2896, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 2896, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -999,7 +999,7 @@ func initDataTemplatesDefaultIndexIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 2361, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 2361, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1019,7 +1019,7 @@ func initDataTemplatesDefaultIndexIndexJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 31, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 31, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1039,7 +1039,7 @@ func initDbReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1059,7 +1059,7 @@ func initFlowsMenuFlowYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/flows/menu.flow.yao", size: 813, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/flows/menu.flow.yao", size: 813, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1079,7 +1079,7 @@ func initFormsAccountFormYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/forms/account.form.yao", size: 1194, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/forms/account.form.yao", size: 1194, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1099,7 +1099,7 @@ func initIconsAppIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1119,7 +1119,7 @@ func initIconsAppIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1139,7 +1139,7 @@ func initIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1159,7 +1159,7 @@ func initLoginsAdminLoginYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/logins/admin.login.yao", size: 302, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/logins/admin.login.yao", size: 302, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1179,7 +1179,7 @@ func initLogsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1199,7 +1199,7 @@ func initModelsAdminUserModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/models/admin/user.mod.yao", size: 6416, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/models/admin/user.mod.yao", size: 6416, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1219,7 +1219,7 @@ func initModelsTestsPetModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/models/tests/pet.mod.yao", size: 525, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/models/tests/pet.mod.yao", size: 525, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1239,7 +1239,7 @@ func initNeoNeoYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/neo/neo.yml", size: 724, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/neo/neo.yml", size: 724, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1259,7 +1259,7 @@ func initPublicReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/README.md", size: 108, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/public/README.md", size: 108, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1279,7 +1279,7 @@ func initPublicAssetsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/public/assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1299,7 +1299,7 @@ func initPublicAssetsImagesIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/public/assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1319,7 +1319,7 @@ func initPublicAssetsImagesLogosLogo_colorSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/public/assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1339,7 +1339,7 @@ func initPublicAssetsImagesLogosWordmarkSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/public/assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1359,7 +1359,7 @@ func initPublicAssetsLibsuiMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/libsui.min.js", size: 12569, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/public/assets/libsui.min.js", size: 12569, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1379,7 +1379,7 @@ func initPublicAssetsLibsuiMinJsMap() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/libsui.min.js.map", size: 38553, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/public/assets/libsui.min.js.map", size: 38553, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1399,7 +1399,7 @@ func initPublicIndexCfg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/index.cfg", size: 85, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/public/index.cfg", size: 85, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1419,7 +1419,7 @@ func initPublicIndexSui() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/index.sui", size: 5682, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/public/index.sui", size: 5682, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1439,7 +1439,7 @@ func initScriptsAccountTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/account.ts", size: 2521, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/scripts/account.ts", size: 2521, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1459,7 +1459,7 @@ func initScriptsAiNeoTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/ai/neo.ts", size: 375, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/scripts/ai/neo.ts", size: 375, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1479,7 +1479,7 @@ func initScriptsTestsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/tests.ts", size: 1044, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/scripts/tests.ts", size: 1044, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1499,7 +1499,7 @@ func initScriptsUtilsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/utils.ts", size: 1230, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/scripts/utils.ts", size: 1230, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1519,7 +1519,7 @@ func initSuisWebSuiYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/suis/web.sui.yao", size: 675, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/suis/web.sui.yao", size: 675, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1539,7 +1539,7 @@ func initTablesAccountTabYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/tables/account.tab.yao", size: 5597, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/tables/account.tab.yao", size: 5597, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1559,7 +1559,7 @@ func initTsconfigJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1579,7 +1579,7 @@ func libsuiAgentTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/agent.ts", size: 15267, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "libsui/agent.ts", size: 15267, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1599,7 +1599,7 @@ func libsuiIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/index.ts", size: 13049, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "libsui/index.ts", size: 13049, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1619,7 +1619,7 @@ func libsuiUtilsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1639,7 +1639,7 @@ func libsuiYaoTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1659,7 +1659,7 @@ func publicIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1679,7 +1679,7 @@ func uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1699,7 +1699,7 @@ func yaoDataIcons404Png() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1719,7 +1719,7 @@ func yaoDataIconsIconIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1739,7 +1739,7 @@ func yaoDataIconsIconIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1759,7 +1759,7 @@ func yaoDataIconsIconPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1779,7 +1779,7 @@ func yaoDataIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1799,7 +1799,7 @@ func yaoDataKbProvidersChunkingSemanticEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/en.json", size: 5543, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/en.json", size: 5543, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1819,7 +1819,7 @@ func yaoDataKbProvidersChunkingSemanticZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/zh-cn.json", size: 5446, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/zh-cn.json", size: 5446, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1839,7 +1839,7 @@ func yaoDataKbProvidersChunkingStructuredEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/en.json", size: 2423, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/en.json", size: 2423, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1859,7 +1859,7 @@ func yaoDataKbProvidersChunkingStructuredZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/zh-cn.json", size: 2321, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/zh-cn.json", size: 2321, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1879,7 +1879,7 @@ func yaoDataKbProvidersConverterMcpEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/en.json", size: 4235, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/en.json", size: 4235, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1899,7 +1899,7 @@ func yaoDataKbProvidersConverterMcpZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/zh-cn.json", size: 4060, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/zh-cn.json", size: 4060, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1919,7 +1919,7 @@ func yaoDataKbProvidersConverterOcrEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/en.json", size: 6631, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/en.json", size: 6631, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1939,7 +1939,7 @@ func yaoDataKbProvidersConverterOcrZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/zh-cn.json", size: 6501, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/zh-cn.json", size: 6501, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1959,7 +1959,7 @@ func yaoDataKbProvidersConverterOfficeEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/en.json", size: 5476, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/en.json", size: 5476, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1979,7 +1979,7 @@ func yaoDataKbProvidersConverterOfficeZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/zh-cn.json", size: 5356, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/zh-cn.json", size: 5356, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1999,7 +1999,7 @@ func yaoDataKbProvidersConverterUtf8EnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/en.json", size: 292, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/en.json", size: 292, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2019,7 +2019,7 @@ func yaoDataKbProvidersConverterUtf8ZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/zh-cn.json", size: 281, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/zh-cn.json", size: 281, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2039,7 +2039,7 @@ func yaoDataKbProvidersConverterVideoEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/en.json", size: 6411, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/en.json", size: 6411, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2059,7 +2059,7 @@ func yaoDataKbProvidersConverterVideoZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/zh-cn.json", size: 6297, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/zh-cn.json", size: 6297, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2079,7 +2079,7 @@ func yaoDataKbProvidersConverterVisionEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/en.json", size: 4085, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/en.json", size: 4085, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2099,7 +2099,7 @@ func yaoDataKbProvidersConverterVisionZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/zh-cn.json", size: 3949, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/zh-cn.json", size: 3949, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2119,7 +2119,7 @@ func yaoDataKbProvidersConverterWhisperEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/en.json", size: 4449, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/en.json", size: 4449, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2139,7 +2139,7 @@ func yaoDataKbProvidersConverterWhisperZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/zh-cn.json", size: 4312, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/zh-cn.json", size: 4312, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2159,7 +2159,7 @@ func yaoDataKbProvidersEmbeddingFastembedEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/en.json", size: 6865, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/en.json", size: 6865, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2179,7 +2179,7 @@ func yaoDataKbProvidersEmbeddingFastembedZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/zh-cn.json", size: 6685, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/zh-cn.json", size: 6685, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2199,7 +2199,7 @@ func yaoDataKbProvidersEmbeddingOpenaiEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/en.json", size: 5636, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/en.json", size: 5636, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2219,7 +2219,7 @@ func yaoDataKbProvidersEmbeddingOpenaiZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/zh-cn.json", size: 5463, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/zh-cn.json", size: 5463, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2239,7 +2239,7 @@ func yaoDataKbProvidersExtractionOpenaiEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/en.json", size: 9110, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/en.json", size: 9110, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2259,7 +2259,7 @@ func yaoDataKbProvidersExtractionOpenaiZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/zh-cn.json", size: 8827, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/zh-cn.json", size: 8827, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2279,7 +2279,7 @@ func yaoDataKbProvidersFetcherHttpEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/en.json", size: 5885, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/en.json", size: 5885, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2299,7 +2299,7 @@ func yaoDataKbProvidersFetcherHttpZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/zh-cn.json", size: 5925, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/zh-cn.json", size: 5925, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2319,7 +2319,7 @@ func yaoDataKbProvidersFetcherMcpEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/en.json", size: 6819, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/en.json", size: 6819, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2339,7 +2339,7 @@ func yaoDataKbProvidersFetcherMcpZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/zh-cn.json", size: 6611, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/zh-cn.json", size: 6611, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2359,7 +2359,7 @@ func yaoFieldsModelTransJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/fields/model.trans.json", size: 14938, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/fields/model.trans.json", size: 14938, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2379,7 +2379,7 @@ func yaoLangsEnUsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2399,7 +2399,7 @@ func yaoLangsZhCnGlobalYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-cn/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2419,7 +2419,7 @@ func yaoLangsZhCnLoginsAdminLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-cn/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2439,7 +2439,7 @@ func yaoLangsZhCnLoginsUserLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-cn/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2459,7 +2459,7 @@ func yaoLangsZhHkGlobalYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-hk/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2479,7 +2479,7 @@ func yaoLangsZhHkLoginsAdminLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-hk/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2499,12 +2499,12 @@ func yaoLangsZhHkLoginsUserLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-hk/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoModelsAgentAssistantModYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x98\x5f\x73\xdb\x36\x0c\xc0\xdf\xf3\x29\x70\x7a\x76\xd3\x6e\x0f\xbb\x25\x4f\x4b\xdb\x97\xdc\x9a\x35\xd7\x36\xeb\x43\x2f\xe7\x83\x25\xc8\xe2\x42\x91\x1a\x01\x25\xcd\xe5\xf2\xdd\x77\xa4\x65\xfd\xb1\xe8\x58\x72\xf6\x92\x9c\x49\x00\xfc\x81\x00\x41\x81\x4f\x27\x00\x89\xc1\x92\x92\x73\x48\x2e\x98\x15\x0b\x1a\x49\x16\x7e\x58\xe3\x8a\x74\x64\x3c\x23\x4e\x9d\xaa\x44\x59\x33\x98\x05\xc1\x95\x26\xc8\xad\x03\x16\xeb\x94\x59\xc3\xc5\x25\x60\x3b\x9d\x5a\x93\xab\x75\xed\xd0\x6b\x32\xa0\xc9\xa0\x24\xc1\x0c\x05\x37\x86\x05\xd7\x9c\x9c\xc3\x8f\x04\xd7\xe4\x17\x83\x84\x1f\x59\xa8\x4c\x6e\xc3\xf4\xaa\x56\x5a\x94\x5f\x53\x5c\x4d\x61\xc8\x11\x66\xd6\xe8\xc7\xfe\x18\x5b\x27\xc9\x39\x9c\x9d\x9d\x9d\x35\x56\x57\xda\xbb\xf7\xd4\x39\x1a\xec\x2f\xb1\x73\x0b\x92\xd4\x96\xa5\x5f\xd4\x3b\xe4\x67\x7b\xdc\x1b\x03\xf0\x1c\xac\xa5\x56\xd7\xa5\x09\x98\x27\x00\x00\x4f\xe1\x6f\x6f\x13\x55\x16\x9c\x09\x63\xf2\x58\x85\xb1\xcb\x8f\xdd\xd8\x78\x57\xa1\x3f\xdd\xe3\xb8\x31\xea\xdf\x9a\x7a\x20\x2a\x23\x23\x2a\x57\xe4\x92\x20\xfe\xbc\x88\x23\xb4\x1a\xcb\x18\x0c\x8b\x0f\xcd\x31\x40\x17\x31\x92\xce\x0e\x99\xb5\x14\xc9\x39\xfc\xfa\xee\x5d\x3b\x68\x6a\xad\x9b\xfd\xcf\x51\x33\xb5\x13\x75\x70\xae\x17\xb7\x30\xaa\x4c\x46\x3f\x9b\xc1\x17\x5d\x0c\xce\x4c\x77\xed\xdb\x40\x3c\xea\xd2\xd0\x62\xd4\x99\x8c\x72\xac\xb5\x0c\xb6\x38\x99\xcf\x1e\xfe\x4f\x67\xff\x6b\x20\x1e\x65\x1f\x5a\x3c\x14\x88\x83\x80\x78\x8f\x82\x6e\x4e\xe6\xec\x28\x44\x21\x37\x56\xe1\xe6\xcb\xa7\xff\x11\x35\xb5\xc6\x50\x2a\x76\x0e\xed\x87\xb1\x4e\x14\xb8\x09\x37\xb4\x6b\x2c\x40\xe5\x60\xac\x00\x93\x2c\xa0\x66\x02\x29\x08\xd6\xda\xae\x50\x8f\xa5\x67\x9e\x8c\x17\xdd\xec\x97\xdc\xe9\x8e\x7e\x8c\x69\xed\x71\x35\x22\xd9\x62\xff\xb6\x3f\x38\xf3\xb3\xbf\x42\x29\x66\xf8\x70\x3d\x10\x8f\xc2\xfb\xcb\x06\xd7\x04\x43\xcb\xaf\x4e\xad\x70\x8f\x8c\x40\x95\x11\x5a\x0f\xca\xde\x96\xf4\xeb\x40\x3e\x4e\x6a\x9d\x80\x75\x59\x5f\xbf\x2b\x2a\xdb\x1b\x6b\xde\x7e\x86\x5b\x71\xa9\x22\x79\xb1\xb2\x56\x13\x9a\x08\xea\x7b\xaf\x03\x97\xf1\xac\xf8\x5e\x90\x14\xe4\x40\x0a\xc5\xa0\x18\x10\xc2\x12\x6f\x94\x81\x48\xd5\xeb\xf0\x87\xf5\x7d\x7a\x3e\x68\x4c\xa9\xb0\x7a\xb0\x29\x5b\x17\xfe\x61\x1b\xe3\xbf\x8e\xe9\x44\x77\x3c\x6a\x7d\x4e\x16\xd8\x70\x28\x78\x32\xda\xe7\x5d\xf9\x28\xd6\xc8\xea\x1c\xa4\xca\xd9\xb2\x92\xe9\x48\xd7\xbb\xf2\xf1\x9d\xda\x95\x9a\x83\xf4\x60\xdd\x5d\xae\xed\xc3\x64\xa6\xef\x23\x85\x28\xd4\xd8\xee\x1c\xaa\xbb\xd5\x64\x9e\x3f\x8d\x7d\xd0\x94\xad\x09\xde\x23\x1f\xba\x6e\xef\x5a\xe1\x15\x32\x41\x6a\xb5\xa6\xf4\x15\x01\x2d\xd3\x6a\x32\xe8\xd5\x87\x6b\xf8\x4a\xee\x9e\x5c\x3c\xa0\x7e\x9e\x37\xf3\xfe\xc6\x55\xba\xfd\x26\xf7\x77\x55\xef\xa3\xd6\xfa\x0b\xec\x38\x5e\xb1\x56\x4f\x4f\xbf\x6f\x43\xe9\xf8\xc7\xd7\x50\x66\x16\x8c\x6f\x1a\x26\xb3\x0c\x84\xe3\x28\x03\x91\x39\x24\x6d\x33\x32\xa3\x12\x7f\x19\xe9\x44\xa1\xb6\xa6\x81\x05\xa5\xe6\xe3\xca\xef\x9e\x6a\x52\xaf\xb4\x4a\xe7\x30\x5f\x07\x0d\xb8\x18\x5f\x06\xfb\x6e\x91\x5e\x0b\xc3\xc0\x05\x3a\xca\x00\x53\x67\x99\x01\xb5\x06\x21\x2c\x19\x94\x09\x29\x5a\x69\x94\xdc\xba\xf2\xb0\x8f\xb3\xbe\xa0\xc2\xaa\x63\x2f\xc9\xd4\x65\xec\x2e\x1f\x4a\xc7\x2f\xf3\x02\x43\x8f\xcb\xa9\xed\x77\x0e\x76\xdb\x18\xff\x68\x46\xc0\x57\x6c\x75\x8f\x42\xc9\x02\xde\xbe\x85\xcf\x3e\x8e\xf7\x8a\x95\x3f\x98\x62\x83\xd3\xf6\xc1\x90\xeb\xe4\xfd\x86\x24\x5e\xf6\xef\x4e\x6c\xbb\x51\x50\x52\xb9\x22\xc7\x8d\xf4\x6d\xac\x3b\x69\xd7\xdb\xb7\x55\x47\xe4\x89\xb6\x29\x6a\x9a\x7e\xd4\x3e\xed\xca\xc7\x1b\xc9\x5f\x7e\x37\x30\x32\x3d\xe7\xd4\xd5\x3c\x03\xea\x86\x0f\x11\xbd\xe1\x8a\x52\x95\xab\x14\x1e\x1c\x56\x15\xb9\xdd\x67\x0b\x5f\x4a\x7d\xf4\xac\x59\x00\xd6\x99\xb2\x0b\x20\x49\x4f\xe1\x72\xa7\x35\x68\xda\x02\x26\x11\x65\x8e\x2d\x29\x58\x8b\x2d\x51\x28\xd2\xc6\xef\x3f\x9f\x17\x63\xa5\x78\x43\xb6\x95\x7b\xa1\xaa\x1c\xf7\x8d\xef\xd7\x52\xd6\x04\x2f\x67\x80\x5f\xc5\xd4\x0e\xd7\x94\x14\x0d\xf8\x50\xa1\xf3\x35\xe4\x0f\x68\x56\x07\xad\x38\xfa\xa1\x7a\xc0\xa7\x93\xe6\x50\x25\x8e\xf4\x26\xe6\xc9\x79\xe3\x62\x92\x16\x28\xdd\xcf\x9e\x53\x05\xf2\x15\x9a\x5e\x19\x2f\x6d\xb6\x71\x6a\xb9\x7c\x44\x7b\x1a\x5e\x9c\x4e\xbd\x7a\x27\x72\x47\x8f\xfb\x5f\x6b\x72\xeb\x48\xad\xcd\x48\xa0\x65\xdc\x3c\x47\x05\x7c\x7a\xf1\x39\xea\xe7\x72\xe7\xb9\x6b\xe9\xa1\x97\xdb\xd7\xb4\xde\x46\xb7\x2f\x5b\xcd\xf3\x4a\xaf\xb9\xb8\x8d\x34\x42\x7e\xe3\x62\x61\xba\xf4\x33\xe1\x9c\xe0\xe0\x75\x25\xbc\xf8\xb5\xbd\x44\x93\x73\x2f\xe6\x51\x0c\x9e\x77\xda\xac\x0e\x7a\x33\xd3\x3f\x34\xaf\xa5\xf6\x16\x7d\x89\xf7\xe0\x8d\x55\x5f\xdb\x77\xf2\xa4\xad\xf8\x4f\x90\x88\x2a\x89\x05\xcb\x8a\xb7\x89\xe6\x1b\xc9\x5c\x96\x19\x69\x92\x10\xa8\x4d\x01\x86\xa4\x22\x57\x2a\xe6\x8d\xaa\x17\x85\xe7\x93\xe7\x93\xff\x02\x00\x00\xff\xff\x89\xbf\xed\x86\x91\x15\x00\x00") +var _yaoModelsAgentAssistantModYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x58\x4b\x73\xdb\x36\x10\xbe\xfb\x57\xec\xf0\xd4\xce\x28\x4e\xda\x43\xa7\xd6\xa9\x4e\x72\xa8\xa7\x71\xa3\x49\xe2\xe6\x90\xf1\x68\x96\xe4\x92\x42\x05\x02\x2c\xb0\xb4\xad\x7a\xfc\xdf\x3b\x00\x29\x3e\x44\xe8\x41\xa5\x17\x3d\x80\xdd\xc5\xb7\x0f\xec\x62\xf7\xf9\x02\x20\x52\x58\x50\x34\x87\xe8\xda\x5a\x61\x19\x15\x47\x33\xb7\x2c\x31\x26\x19\x58\x4f\xc9\x26\x46\x94\x2c\xb4\x1a\xec\x02\x63\x2c\x09\x32\x6d\xc0\xb2\x36\x42\xe5\x70\x7d\x03\xd8\x6e\x27\x5a\x65\x22\xaf\x0c\x3a\x4e\x0b\xa8\x52\x28\x88\x31\x45\xc6\x5a\x30\x63\x6e\xa3\x39\x7c\x8b\x30\x27\x77\x18\x44\x76\x63\x99\x8a\xe8\xde\x6f\xc7\x95\x90\x2c\xdc\x99\x6c\x2a\xf2\x4b\x86\x30\xd5\x4a\x6e\xfa\x6b\x56\x1b\x8e\xe6\x70\x75\x75\x75\xd5\x48\x8d\xa5\x53\xef\xb9\x53\xd4\xcb\x5f\x62\xa7\x16\x44\x89\x2e\x0a\x77\xa8\x53\xc8\xed\xf6\x70\xd7\x02\xe0\xc5\x4b\x4b\xb4\xac\x0a\xe5\x61\x5e\x00\x00\x3c\xfb\xcf\x9e\x11\x45\xea\x95\xf1\x6b\xbc\x29\xfd\xda\xcd\xfb\x6e\x6d\x6c\x55\xe8\x6f\xf7\x70\xdc\x29\xf1\x4f\x45\x3d\x20\x22\x25\xc5\x22\x13\x64\x22\x4f\xfe\x32\x0b\x43\x68\x39\x96\x21\x30\x96\x9d\x6b\xce\x01\x74\x1d\x42\xd2\xc9\x21\x95\xf3\x2a\x9a\xc3\xcf\x6f\xde\xb4\x8b\xaa\x92\xb2\xb1\x7f\x86\xd2\x52\xbb\x51\x79\xe5\x7a\x7e\xf3\xab\x42\xa5\xf4\xd4\x2c\x1e\x54\xd1\x2b\x73\xba\x6a\x5f\x06\xe4\x41\x95\x86\x12\x83\xca\xa4\x94\x61\x25\x79\x60\xe2\x68\x3a\x76\xff\x7d\x3a\xf6\x3f\x07\xe4\x41\xec\x43\x89\xc7\x1c\x71\x14\x20\x3e\x20\xa3\x99\x12\x39\x3b\x0c\x41\x90\xb5\x54\xb8\xfb\xf4\xe1\x7f\x84\x9a\x68\xa5\x28\x61\x3d\x05\xed\xbb\x31\x4f\x10\x70\xe3\x6e\x68\xcf\x98\x81\xc8\x40\x69\x06\x4b\x3c\x83\xca\x12\xf0\x8a\x20\x97\x3a\x46\x39\xa6\x9e\x78\x33\x4e\x53\x73\xa9\x7d\xde\xb5\x63\x75\xff\xb6\x5a\x1d\x52\x16\x3e\xee\x72\xf6\x94\xee\xa8\x2c\x49\x4a\x1c\x21\x34\x27\xcd\x9b\x1f\x28\x21\x93\x98\xcf\x9c\x1f\x85\x47\xde\xa9\x6a\x41\x0a\xcb\x33\x9f\xd2\x13\x2c\x31\x16\x52\xf0\x06\x32\x21\x99\x4c\xef\xc4\x29\x9e\xed\x57\x99\xd3\x7d\xfb\x3e\xc4\xb5\xc7\xbb\x01\xca\xd6\x53\xbf\xec\x8f\xc7\xe9\x17\xbe\x44\x5e\x4d\xd0\x61\x31\x20\x0f\x82\x77\xf5\x15\x73\x82\xa1\xe4\xef\xbe\x4d\xbe\x74\x8e\x80\x0a\xc5\x94\x0f\x32\xfd\x16\xe9\xe7\x01\x7d\x18\xa9\x36\x0c\xda\xa4\x7d\xfe\x2e\x8f\x6e\x8b\xf4\x34\x7b\xfa\x87\xc0\x52\x04\xe2\x22\xd6\x5a\x12\x86\xee\xc1\x5b\xc7\x03\x37\xe1\xa8\xf8\xba\x22\x5e\x91\x01\x5e\x09\x0b\xc2\x02\x82\x3f\xe2\x95\x50\x10\x48\xf4\x1d\xfc\x61\x49\x3b\x3d\x1e\x24\x26\xb4\xd2\x72\x60\x94\x23\xf7\x78\x11\xe2\x09\x5a\x3c\x28\x7d\x4a\x14\x4c\x4d\x31\x87\x12\x4b\x07\x6b\x24\x75\x0a\xa4\xd2\xe8\xa2\xe4\xd3\x21\x2d\x76\xe9\x0f\x26\xf8\x91\xf4\xe9\xd0\x96\xa5\x21\x4b\x93\x11\xc2\x62\x97\xad\x07\xb4\x21\x69\x24\x83\x36\x39\x2a\xf1\x2f\xa5\x10\x6f\xa0\xd0\x29\xc1\x0f\x74\x99\x5f\xce\x20\x59\x21\xcf\x80\xd1\xae\x67\x40\x9c\x5c\xfe\x78\x9e\x22\x8f\xda\xac\x33\xa9\x1f\x4f\x56\xe1\xeb\x88\x21\x68\xe5\xb1\xdc\x29\xa8\xd6\xf1\xc9\x78\xfe\x50\xfa\x51\x52\x9a\x13\xbc\x45\x7b\xec\xc9\xb4\x6e\x89\x63\xb4\xae\x94\xc9\xa6\xee\x9d\x19\x06\x45\x52\x9e\x0c\xf4\xf6\xdd\x02\x3e\x93\x79\x18\xd4\xc6\x1e\x4a\xb7\x6f\xeb\xfd\x5e\xb5\x75\x7d\x95\x7b\x6f\xf4\x1a\x13\xed\x1e\x21\xe7\xe1\xb5\xba\x32\x49\xe0\x19\xca\xf4\xc4\xc1\x4c\x3f\x24\xef\xa1\xfd\x5d\xeb\x35\xd4\xb5\x14\x6a\xa9\x90\xe8\xf4\x4c\x58\xbe\x05\x3c\xd5\x8e\x5f\x06\xc4\xe1\x57\xfd\x80\x64\x0a\x92\xb6\xb5\x9c\x50\x64\x3e\x8d\x78\x82\xa0\xb6\xa2\xc1\x32\x72\x65\xcf\xab\x2c\x7b\xb2\x51\x15\x4b\x91\x4c\xc1\xbc\xf0\x1c\x70\x3d\xae\x73\xfb\x0a\x64\xaf\x21\xb5\x60\x57\x68\x28\x05\x4c\x8c\xb6\x16\x50\x4a\x60\xc2\xc2\x82\x50\x3e\x58\x4b\x89\x9c\x69\x53\x1c\xd7\x71\xd2\x7b\xd8\x9f\x3a\xd6\x92\x54\x55\x84\x82\x77\x48\x1d\x7e\xa7\xac\xd0\x4f\x2c\x6c\xa2\xfb\x7d\xa0\xde\x8e\x39\xbe\x35\x2b\xe0\x32\xbe\x78\x40\xa6\x68\x06\xaf\x5f\xc3\x47\xe7\xc7\x07\x61\x85\xbb\xa2\xac\xbd\xd2\xfa\x51\x91\xe9\xe8\x9d\x41\x22\x47\xfb\x57\x47\xb6\x35\x14\x14\x54\xc4\x64\x6c\x43\x7d\x1f\xea\x35\xdb\xf3\xf6\x99\xea\x8c\x38\x91\x3a\x41\x49\xa7\x5f\xb5\x0f\xbb\xf4\xe1\xb1\xc0\x4f\xbf\x2a\x18\x89\x9e\x72\xeb\x2a\x3b\x01\xd4\x9d\x3d\x86\xe8\x95\x2d\x29\x11\x99\x48\xe0\xd1\x60\x59\x92\xd9\x1d\x42\xb9\xa4\xea\xbc\xa7\xd5\x0c\xb0\x4a\x85\xae\x4b\x28\xdc\xec\x34\x7a\x4d\x93\x67\x89\x59\xa8\x73\x53\x0a\x56\xac\x0b\x64\x0a\x0c\x65\xf6\xdf\xcf\xeb\x31\x53\xb8\xbd\xde\xd2\x1d\xc8\x2a\xe7\xb5\x2f\xee\x2c\xd7\x00\x3a\x2d\x27\x00\xbf\x0d\xb1\x1d\xcf\x29\x09\x2a\x70\xae\x42\xe3\x72\xc8\x6f\xd0\x9c\xee\x3b\xcc\x33\x74\xba\x68\x2e\x55\x64\x48\xd6\x3e\x8f\xe6\x8d\x8a\x91\x7b\x38\x75\x7f\x7b\x4a\xad\xd0\xde\xa2\xea\xa5\x71\xf7\xdc\xf2\x4a\x2d\x97\x1b\xd4\x97\x7e\x7e\x78\xe9\xd8\x3b\x92\x35\x6d\xf6\xcf\xde\x32\x6d\x48\xe4\x6a\x44\xd0\x62\xac\x87\x8b\x1e\x3e\x1d\x1c\x2e\x3e\x2d\x77\x86\x97\x4b\x07\x7a\xb9\x9d\x8d\xf6\x0c\xdd\xce\x29\x9b\x61\x59\xaf\x6f\xba\x0f\xf4\x78\xce\x70\x21\x37\xdd\xb8\x1d\x7f\x4f\x70\x30\x2b\xf3\xcd\x7e\xdb\x26\x35\x31\x77\x30\x8e\x42\xe0\xed\x4e\x07\xd9\x81\xae\x77\xfa\x97\xe6\x7b\x51\x3b\x89\x2e\xc5\x3b\xe0\x8d\x54\x97\xdb\x77\xe2\xa4\xcd\xf8\xcf\x10\xb1\x28\xc8\x32\x16\xa5\xdd\x06\x9a\x7b\x38\x65\xbc\x4c\x49\x12\x7b\x47\xd5\x09\x18\xa2\x92\x4c\x21\xac\xad\x59\x1d\x29\xbc\x5c\xbc\x5c\xfc\x17\x00\x00\xff\xff\xdf\xd5\xd7\x46\x5f\x17\x00\x00") func yaoModelsAgentAssistantModYaoBytes() ([]byte, error) { return bindataRead( @@ -2519,7 +2519,7 @@ func yaoModelsAgentAssistantModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 5521, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 5983, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2539,7 +2539,7 @@ func yaoModelsAgentChatModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 1741, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 1741, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2559,7 +2559,7 @@ func yaoModelsAgentHistoryModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/history.mod.yao", size: 2941, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/agent/history.mod.yao", size: 2941, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2579,7 +2579,7 @@ func yaoModelsAttachmentModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4286, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4286, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2599,7 +2599,7 @@ func yaoModelsAuditModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/audit.mod.yao", size: 5588, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/audit.mod.yao", size: 5588, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2619,7 +2619,7 @@ func yaoModelsConfigModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/config.mod.yao", size: 1649, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/config.mod.yao", size: 1649, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2639,7 +2639,7 @@ func yaoModelsDslModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/dsl.mod.yao", size: 3826, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/dsl.mod.yao", size: 3826, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2659,7 +2659,7 @@ func yaoModelsInvitationModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/invitation.mod.yao", size: 6693, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/invitation.mod.yao", size: 6693, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2679,7 +2679,7 @@ func yaoModelsJobCategoryModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2041, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2041, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2699,7 +2699,7 @@ func yaoModelsJobExecutionModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 7201, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 7201, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2719,7 +2719,7 @@ func yaoModelsJobJobModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6330, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6330, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2739,7 +2739,7 @@ func yaoModelsJobLogModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/log.mod.yao", size: 4711, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/job/log.mod.yao", size: 4711, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2759,7 +2759,7 @@ func yaoModelsKbCollectionModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5390, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5390, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2779,7 +2779,7 @@ func yaoModelsKbDocumentModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9906, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9906, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2799,7 +2799,7 @@ func yaoModelsMemberModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 14798, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 14798, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2819,7 +2819,7 @@ func yaoModelsRoleModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/role.mod.yao", size: 6434, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/role.mod.yao", size: 6434, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2839,7 +2839,7 @@ func yaoModelsTeamModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/team.mod.yao", size: 15823, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/team.mod.yao", size: 15823, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2859,7 +2859,7 @@ func yaoModelsUserOauth_accountModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user/oauth_account.mod.yao", size: 6928, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/user/oauth_account.mod.yao", size: 6928, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2879,7 +2879,7 @@ func yaoModelsUserTypeModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user/type.mod.yao", size: 7502, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/user/type.mod.yao", size: 7502, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2899,7 +2899,7 @@ func yaoModelsUserModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 12335, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 12335, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2919,7 +2919,7 @@ func yaoReleaseAppYaz() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2939,7 +2939,7 @@ func yaoStoresAgentCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/stores/agent/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2959,7 +2959,7 @@ func yaoStoresAgentMemoryBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory.badger.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory.badger.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2979,7 +2979,7 @@ func yaoStoresCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/cache.lru.yao", size: 285, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/stores/cache.lru.yao", size: 285, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2999,7 +2999,7 @@ func yaoStoresKbCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/kb/cache.lru.yao", size: 304, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/stores/kb/cache.lru.yao", size: 304, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3019,7 +3019,7 @@ func yaoStoresKbStoreBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/kb/store.badger.yao", size: 349, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/stores/kb/store.badger.yao", size: 349, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3039,7 +3039,7 @@ func yaoStoresOauthCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3059,7 +3059,7 @@ func yaoStoresOauthClientBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/client.badger.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/client.badger.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3079,7 +3079,7 @@ func yaoStoresOauthStoreBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/store.badger.yao", size: 376, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/store.badger.yao", size: 376, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3099,7 +3099,7 @@ func yaoStoresStoreBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/store.badger.yao", size: 341, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/stores/store.badger.yao", size: 341, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3119,7 +3119,7 @@ func yaoUploadersAttachmentLocalYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/uploaders/attachment.local.yao", size: 1163, mode: os.FileMode(420), modTime: time.Unix(1763118620, 0)} + info := bindataFileInfo{name: "yao/uploaders/attachment.local.yao", size: 1163, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/openapi/agent/assistant.go b/openapi/agent/assistant.go index 04a87e97..ce2d10ea 100644 --- a/openapi/agent/assistant.go +++ b/openapi/agent/assistant.go @@ -192,6 +192,17 @@ func GetAssistant(c *gin.Context) { return } + // Parse select fields (optional - if not provided, returns default fields) + // Query parameter: ?select=field1,field2,field3 + var fields []string + if selectParam := c.Query("select"); selectParam != "" { + fields = strings.Split(selectParam, ",") + // Trim whitespace from each field + for i, field := range fields { + fields[i] = strings.TrimSpace(field) + } + } + // Parse locale (optional - if not provided, returns raw data without i18n translation) // This is useful for form editing scenarios where you need the original values var assistant *agenttypes.AssistantModel @@ -200,10 +211,10 @@ func GetAssistant(c *gin.Context) { if loc := c.Query("locale"); loc != "" { // If locale is specified, get assistant with translation locale := strings.ToLower(strings.TrimSpace(loc)) - assistant, err = agentInstance.Store.GetAssistant(assistantID, locale) + assistant, err = agentInstance.Store.GetAssistant(assistantID, fields, locale) } else { // If no locale specified, get raw data without translation - assistant, err = agentInstance.Store.GetAssistant(assistantID) + assistant, err = agentInstance.Store.GetAssistant(assistantID, fields) } if err != nil { log.Error("Failed to get assistant %s: %v", assistantID, err) @@ -521,8 +532,8 @@ func checkAssistantPermission(authInfo *types.AuthorizedInfo, assistantID string return false, fmt.Errorf("agent store not initialized") } - // Get assistant from store - assistant, err := agentInstance.Store.GetAssistant(assistantID) + // Get assistant from store - only need default fields for permission check + assistant, err := agentInstance.Store.GetAssistant(assistantID, nil) if err != nil { return false, fmt.Errorf("assistant not found: %s", assistantID) } diff --git a/openapi/agent/filter.go b/openapi/agent/filter.go index f946cd3e..deeed3bb 100644 --- a/openapi/agent/filter.go +++ b/openapi/agent/filter.go @@ -126,7 +126,7 @@ func AuthQueryFilter(c *gin.Context, authInfo *types.AuthorizedInfo) func(query. } // FilterBuiltInFields filters sensitive fields for built-in assistants in a list -// For built-in assistants, code-level fields (prompts, workflow, tools, kb, mcp, options) should be cleared +// For built-in assistants, code-level fields (prompts, prompt_presets, workflow, kb, mcp, options, source) should be cleared func FilterBuiltInFields(assistants []*agenttypes.AssistantModel) { if assistants == nil { return @@ -138,7 +138,7 @@ func FilterBuiltInFields(assistants []*agenttypes.AssistantModel) { } // FilterBuiltInAssistant filters sensitive fields for a single built-in assistant -// For built-in assistants, code-level fields (prompts, workflow, tools, kb, mcp, options) should be cleared +// For built-in assistants, code-level fields (prompts, prompt_presets, workflow, kb, mcp, options, source) should be cleared // This function can be used for both single assistant and list of assistants func FilterBuiltInAssistant(assistant *agenttypes.AssistantModel) { if assistant == nil { @@ -148,10 +148,11 @@ func FilterBuiltInAssistant(assistant *agenttypes.AssistantModel) { if assistant.BuiltIn { // Clear code-level sensitive fields for built-in assistants assistant.Prompts = nil + assistant.PromptPresets = nil assistant.Workflow = nil - assistant.Tools = nil assistant.KB = nil assistant.MCP = nil assistant.Options = nil + assistant.Source = "" } } diff --git a/openapi/agent/models.go b/openapi/agent/models.go index db15bfa0..e625f630 100644 --- a/openapi/agent/models.go +++ b/openapi/agent/models.go @@ -130,6 +130,17 @@ func GetModelDetails(c *gin.Context) { return } + // For model API, we only need minimal fields: assistant_id, name, connector, created_at, and permission fields + modelFields := []string{ + "assistant_id", + "name", + "connector", + "created_at", + "built_in", + "__yao_team_id", + "__yao_created_by", + } + // Parse locale (optional - for assistant name translation) // Priority: 1. Query parameter "locale", 2. Header "Accept-Language", 3. Metadata locale := context.GetLocale(c, nil) @@ -138,9 +149,9 @@ func GetModelDetails(c *gin.Context) { var err error if locale != "" { - assistant, err = agentInstance.Store.GetAssistant(assistantID, locale) + assistant, err = agentInstance.Store.GetAssistant(assistantID, modelFields, locale) } else { - assistant, err = agentInstance.Store.GetAssistant(assistantID) + assistant, err = agentInstance.Store.GetAssistant(assistantID, modelFields) } if err != nil { diff --git a/openapi/tests/agent/assistant_update_test.go b/openapi/tests/agent/assistant_update_test.go index ba87fd06..281b6a9a 100644 --- a/openapi/tests/agent/assistant_update_test.go +++ b/openapi/tests/agent/assistant_update_test.go @@ -620,62 +620,7 @@ func TestUpdateAssistant(t *testing.T) { t.Logf("Successfully updated assistant mcp settings: %s", assistantID) }) - t.Run("UpdateAssistantTools", func(t *testing.T) { - // Create a test assistant - assistantID := createTestAssistant("Tools Update Test") - defer func() { - deleteReq, _ := http.NewRequest("DELETE", serverURL+baseURL+"/agent/assistants/"+assistantID, nil) - deleteReq.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) - resp, _ := http.DefaultClient.Do(deleteReq) - if resp != nil { - resp.Body.Close() - } - }() - - // Update tools - updateData := map[string]interface{}{ - "tools": []map[string]interface{}{ - { - "name": "web_search", - "description": "Search the web for information", - "parameters": map[string]interface{}{ - "query": map[string]interface{}{ - "type": "string", - "description": "Search query", - "required": true, - }, - }, - }, - { - "name": "calculator", - "description": "Perform calculations", - "parameters": map[string]interface{}{ - "expression": map[string]interface{}{ - "type": "string", - "description": "Mathematical expression", - "required": true, - }, - }, - }, - }, - } - - jsonData, err := json.Marshal(updateData) - assert.NoError(t, err) - - req, err := http.NewRequest("PUT", serverURL+baseURL+"/agent/assistants/"+assistantID, bytes.NewBuffer(jsonData)) - assert.NoError(t, err) - req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) - req.Header.Set("Content-Type", "application/json") - - resp, err := http.DefaultClient.Do(req) - assert.NoError(t, err) - assert.NotNil(t, resp) - defer resp.Body.Close() - - assert.Equal(t, http.StatusOK, resp.StatusCode, "Should successfully update tools") - t.Logf("Successfully updated assistant tools: %s", assistantID) - }) + // Note: UpdateAssistantTools test removed - tools field is deprecated and replaced by MCP t.Run("UpdateAssistantWorkflow", func(t *testing.T) { // Create a test assistant @@ -776,12 +721,7 @@ func TestUpdateAssistant(t *testing.T) { }, }, }, - "tools": []map[string]interface{}{ - { - "name": "updated_tool", - "description": "Updated tool description", - }, - }, + // Note: tools field removed - now handled by MCP "kb": map[string]interface{}{ "collections": []string{"updated-collection"}, "enabled": true, diff --git a/yao/models/agent/assistant.mod.yao b/yao/models/agent/assistant.mod.yao index e13b92c9..3b836660 100644 --- a/yao/models/agent/assistant.mod.yao +++ b/yao/models/agent/assistant.mod.yao @@ -57,6 +57,13 @@ "length": 200, "nullable": false }, + { + "name": "connector_options", + "type": "json", + "label": "Connector Options", + "comment": "Connector selection options: optional flag, available connectors list, and capability filters", + "nullable": true + }, { "name": "description", "type": "string", @@ -108,7 +115,14 @@ "name": "prompts", "type": "json", "label": "Prompts", - "comment": "Assistant prompts", + "comment": "Assistant default prompts", + "nullable": true + }, + { + "name": "prompt_presets", + "type": "json", + "label": "Prompt Presets", + "comment": "Prompt presets organized by mode (e.g., chat, task, etc.)", "nullable": true }, { @@ -133,10 +147,10 @@ "nullable": true }, { - "name": "tools", - "type": "json", - "label": "Tools", - "comment": "Assistant tools", + "name": "source", + "type": "text", + "label": "Source", + "comment": "Hook script source code", "nullable": true }, { From 51092e33244d77987d54fccfdff10f7c10fd64b6 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 2 Dec 2025 14:54:35 +0800 Subject: [PATCH 3/7] Refactor assistant model and enhance data handling - Removed the GetByConnector method from the Assistant model to streamline the retrieval process. - Introduced new fields for connector options and prompt presets, allowing for more flexible configurations. - Updated the Map method to include additional fields such as connector options and prompt presets. - Enhanced the Clone method to support deep copying of new fields. - Improved the Update method to handle updates for the new source, connector options, and prompt presets fields. - Refactored tests to ensure comprehensive coverage of the new functionalities and maintain clarity in the assistant structure. --- agent/assistant/assistant.go | 123 +++-- agent/assistant/load.go | 145 +++++- agent/assistant/load_test.go | 826 ++++++++++++++++-------------- agent/assistant/source.go | 36 ++ agent/store/types/convert.go | 53 ++ agent/store/types/convert_test.go | 267 ++++++++++ 6 files changed, 1019 insertions(+), 431 deletions(-) create mode 100644 agent/assistant/source.go diff --git a/agent/assistant/assistant.go b/agent/assistant/assistant.go index 46d06eb1..7fe86c47 100644 --- a/agent/assistant/assistant.go +++ b/agent/assistant/assistant.go @@ -15,31 +15,6 @@ func Get(id string) (*Assistant, error) { return LoadStore(id) } -// GetByConnector get the assistant by connector -func GetByConnector(connector string, name string) (*Assistant, error) { - id := "connector:" + connector - - assistant, exists := loaded.Get(id) - if exists { - return assistant, nil - } - - data := map[string]interface{}{ - "assistant_id": id, - "connector": connector, - "description": "Default assistant for " + connector, - "name": name, - "type": "assistant", - } - - assistant, err := loadMap(data) - if err != nil { - return nil, err - } - loaded.Put(assistant) - return assistant, nil -} - // GetPlaceholder returns the placeholder of the assistant func (ast *Assistant) GetPlaceholder(locale string) *store.Placeholder { @@ -88,30 +63,33 @@ func (ast *Assistant) Map() map[string]interface{} { } return map[string]interface{}{ - "assistant_id": ast.ID, - "type": ast.Type, - "name": ast.Name, - "readonly": ast.Readonly, - "public": ast.Public, - "share": ast.Share, - "avatar": ast.Avatar, - "connector": ast.Connector, - "path": ast.Path, - "built_in": ast.BuiltIn, - "sort": ast.Sort, - "description": ast.Description, - "options": ast.Options, - "prompts": ast.Prompts, - "kb": ast.KB, - "mcp": ast.MCP, - "workflow": ast.Workflow, - "tags": ast.Tags, - "mentionable": ast.Mentionable, - "automated": ast.Automated, - "placeholder": ast.Placeholder, - "locales": ast.Locales, - "created_at": store.ToMySQLTime(ast.CreatedAt), - "updated_at": store.ToMySQLTime(ast.UpdatedAt), + "assistant_id": ast.ID, + "type": ast.Type, + "name": ast.Name, + "readonly": ast.Readonly, + "public": ast.Public, + "share": ast.Share, + "avatar": ast.Avatar, + "connector": ast.Connector, + "connector_options": ast.ConnectorOptions, + "path": ast.Path, + "built_in": ast.BuiltIn, + "sort": ast.Sort, + "description": ast.Description, + "options": ast.Options, + "prompts": ast.Prompts, + "prompt_presets": ast.PromptPresets, + "source": ast.Source, + "kb": ast.KB, + "mcp": ast.MCP, + "workflow": ast.Workflow, + "tags": ast.Tags, + "mentionable": ast.Mentionable, + "automated": ast.Automated, + "placeholder": ast.Placeholder, + "locales": ast.Locales, + "created_at": store.ToMySQLTime(ast.CreatedAt), + "updated_at": store.ToMySQLTime(ast.UpdatedAt), } } @@ -173,6 +151,7 @@ func (ast *Assistant) Clone() *Assistant { Share: ast.Share, Mentionable: ast.Mentionable, Automated: ast.Automated, + Source: ast.Source, CreatedAt: ast.CreatedAt, UpdatedAt: ast.UpdatedAt, }, @@ -245,6 +224,31 @@ func (ast *Assistant) Clone() *Assistant { copy(clone.Prompts, ast.Prompts) } + // Deep copy prompt presets + if ast.PromptPresets != nil { + clone.PromptPresets = make(map[string][]store.Prompt) + for k, v := range ast.PromptPresets { + prompts := make([]store.Prompt, len(v)) + copy(prompts, v) + clone.PromptPresets[k] = prompts + } + } + + // Deep copy connector options + if ast.ConnectorOptions != nil { + clone.ConnectorOptions = &store.ConnectorOptions{ + Optional: ast.ConnectorOptions.Optional, + } + if ast.ConnectorOptions.Connectors != nil { + clone.ConnectorOptions.Connectors = make([]string, len(ast.ConnectorOptions.Connectors)) + copy(clone.ConnectorOptions.Connectors, ast.ConnectorOptions.Connectors) + } + if ast.ConnectorOptions.Filters != nil { + clone.ConnectorOptions.Filters = make([]store.ModelCapability, len(ast.ConnectorOptions.Filters)) + copy(clone.ConnectorOptions.Filters, ast.ConnectorOptions.Filters) + } + } + // Deep copy workflow if ast.Workflow != nil { clone.Workflow = &store.Workflow{} @@ -341,6 +345,27 @@ func (ast *Assistant) Update(data map[string]interface{}) error { if v, ok := data["options"].(map[string]interface{}); ok { ast.Options = v } + if v, ok := data["source"].(string); ok { + ast.Source = v + } + + // ConnectorOptions + if v, has := data["connector_options"]; has { + connOpts, err := store.ToConnectorOptions(v) + if err != nil { + return err + } + ast.ConnectorOptions = connOpts + } + + // PromptPresets + if v, has := data["prompt_presets"]; has { + presets, err := store.ToPromptPresets(v) + if err != nil { + return err + } + ast.PromptPresets = presets + } // KB if v, has := data["kb"]; has { diff --git a/agent/assistant/load.go b/agent/assistant/load.go index c46c541c..c65bbeaf 100644 --- a/agent/assistant/load.go +++ b/agent/assistant/load.go @@ -268,7 +268,7 @@ func LoadPath(path string) (*Assistant, error) { updatedAt := int64(0) - // prompts + // prompts (default prompts from prompts.yml) promptsfile := filepath.Join(path, "prompts.yml") if has, _ := app.Exists(promptsfile); has { prompts, ts, err := loadPrompts(promptsfile, path) @@ -280,6 +280,19 @@ func LoadPath(path string) (*Assistant, error) { updatedAt = ts } + // prompt_presets (from prompts directory, key is filename without extension) + promptsDir := filepath.Join(path, "prompts") + if has, _ := app.Exists(promptsDir); has { + presets, ts, err := loadPromptPresets(promptsDir, path) + if err != nil { + return nil, err + } + if len(presets) > 0 { + data["prompt_presets"] = presets + updatedAt = max(updatedAt, ts) + } + } + // load script scriptfile := filepath.Join(path, "src", "index.ts") if has, _ := app.Exists(scriptfile); has { @@ -419,6 +432,15 @@ func loadMap(data map[string]interface{}) (*Assistant, error) { assistant.Connector = connector } + // connector_options + if connOpts, has := data["connector_options"]; has { + opts, err := store.ToConnectorOptions(connOpts) + if err != nil { + return nil, err + } + assistant.ConnectorOptions = opts + } + // tags if v, has := data["tags"]; has { switch vv := v.(type) { @@ -510,6 +532,20 @@ func loadMap(data map[string]interface{}) (*Assistant, error) { } } + // prompt_presets + if presets, has := data["prompt_presets"]; has { + promptPresets, err := store.ToPromptPresets(presets) + if err != nil { + return nil, err + } + assistant.PromptPresets = promptPresets + } + + // source (hook script code) - store the source code + if source, ok := data["source"].(string); ok { + assistant.Source = source + } + // tools - deprecated, now handled by MCP // if tools, has := data["tools"]; has { // ... removed ... @@ -542,7 +578,29 @@ func loadMap(data map[string]interface{}) (*Assistant, error) { assistant.Workflow = wf } - // script + // uses (wrapper configurations for vision, audio, etc.) + if uses, has := data["uses"]; has { + switch v := uses.(type) { + case *context.Uses: + assistant.Uses = v + case context.Uses: + assistant.Uses = &v + default: + raw, err := jsoniter.Marshal(v) + if err != nil { + return nil, err + } + var usesConfig context.Uses + err = jsoniter.Unmarshal(raw, &usesConfig) + if err != nil { + return nil, err + } + assistant.Uses = &usesConfig + } + } + + // script loading priority: script field > source field + // If script field exists, use it; otherwise try source field if data["script"] != nil { switch v := data["script"].(type) { case string: @@ -557,6 +615,13 @@ func loadMap(data map[string]interface{}) (*Assistant, error) { case *v8.Script: assistant.Script = &hook.Script{Script: v} } + } else if assistant.Source != "" { + // Load from source field if script is not provided + script, err := loadSource(assistant.Source, assistant.ID) + if err != nil { + return nil, err + } + assistant.Script = script } // created_at @@ -603,6 +668,7 @@ func loadPrompts(file string, root string) (string, int64, error) { return "", 0, err } + // Replace @assets/xxx references with file content re := regexp.MustCompile(`@assets/([^\s]+\.(md|yml|yaml|json|txt))`) prompts = re.ReplaceAllFunc(prompts, func(s []byte) []byte { asset := re.FindStringSubmatch(string(s))[1] @@ -623,6 +689,81 @@ func loadPrompts(file string, root string) (string, int64, error) { return string(prompts), ts.UnixNano(), nil } +// loadPromptPresets loads prompt presets from the prompts directory +// Supports multi-level directories, key is path with "/" replaced by "." +// e.g., prompts/chat/default.yml -> "chat.default" +func loadPromptPresets(dir string, root string) (map[string][]store.Prompt, int64, error) { + app, err := fs.Get("app") + if err != nil { + return nil, 0, err + } + + // Read directory recursively - returns full paths relative to app root + files, err := app.ReadDir(dir, true) + if err != nil { + return nil, 0, err + } + + presets := make(map[string][]store.Prompt) + var latestTs int64 + + for _, file := range files { + // Only process .yml/.yaml files + if !strings.HasSuffix(file, ".yml") && !strings.HasSuffix(file, ".yaml") { + continue + } + + // file is already full path relative to app root (e.g., /assistants/tests/fullfields/prompts/chat/friendly.yml) + ts, err := app.ModTime(file) + if err != nil { + return nil, 0, err + } + if ts.UnixNano() > latestTs { + latestTs = ts.UnixNano() + } + + // Read file content directly + content, err := app.ReadFile(file) + if err != nil { + return nil, 0, err + } + + // Replace @assets/xxx references with file content + re := regexp.MustCompile(`@assets/([^\s]+\.(md|yml|yaml|json|txt))`) + content = re.ReplaceAllFunc(content, func(s []byte) []byte { + asset := re.FindStringSubmatch(string(s))[1] + assetFile := filepath.Join(root, "assets", asset) + assetContent, err := app.ReadFile(assetFile) + if err != nil { + return []byte("") + } + // Add proper YAML formatting for content + lines := strings.Split(string(assetContent), "\n") + formattedContent := "|\n" + for _, line := range lines { + formattedContent += " " + line + "\n" + } + return []byte(formattedContent) + }) + + // Parse prompts + var prompts []store.Prompt + err = yaml.Unmarshal(content, &prompts) + if err != nil { + return nil, 0, fmt.Errorf("failed to parse prompt preset %s: %w", file, err) + } + + // Build key: get relative path from dir, remove extension and replace "/" with "." + // e.g., "/assistants/tests/fullfields/prompts/chat/friendly.yml" -> "chat.friendly" + relPath := strings.TrimPrefix(file, dir+"/") + key := strings.TrimSuffix(relPath, filepath.Ext(relPath)) + key = strings.ReplaceAll(key, "/", ".") + presets[key] = prompts + } + + return presets, latestTs, nil +} + func loadScript(file string, root string) (*hook.Script, int64, error) { app, err := fs.Get("app") diff --git a/agent/assistant/load_test.go b/agent/assistant/load_test.go index 3a047782..b5e2b35a 100644 --- a/agent/assistant/load_test.go +++ b/agent/assistant/load_test.go @@ -1,435 +1,501 @@ package assistant -// func prepare(t *testing.T) { -// test.Prepare(t, config.Conf) -// } +import ( + "testing" -// func TestLoad_LoadPath(t *testing.T) { -// prepare(t) -// defer test.Clean() + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + store "github.com/yaoapp/yao/agent/store/types" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/test" +) -// assistant, err := LoadPath("/assistants/modi") -// if err != nil { -// t.Fatal(err) -// } +func prepare(t *testing.T) { + test.Prepare(t, config.Conf) +} -// // Validate basic properties -// assert.NotNil(t, assistant) -// assert.Equal(t, "modi", assistant.ID) -// assert.Equal(t, "Modi", assistant.Name) -// assert.Equal(t, "https://api.dicebear.com/7.x/bottts/svg?seed=Modi", assistant.Avatar) -// assert.Equal(t, "deepseek", assistant.Connector) -// assert.NotNil(t, assistant.Prompts) -// assert.NotNil(t, assistant.Script) +// TestLoadPath tests loading assistant from path +func TestLoadPath(t *testing.T) { + prepare(t) + defer test.Clean() -// // Test non-existent assistant -// _, err = LoadPath("/assistants/non-existent") -// assert.Error(t, err) -// } + t.Run("LoadFullFieldsAssistant", func(t *testing.T) { + assistant, err := LoadPath("/assistants/tests/fullfields") + require.NoError(t, err) + require.NotNil(t, assistant) -// func TestLoad_LoadStore(t *testing.T) { -// prepare(t) -// defer test.Clean() + // Basic fields + assert.Equal(t, "tests.fullfields", assistant.ID) + assert.Equal(t, "Full Fields Test Assistant", assistant.Name) + assert.Equal(t, "assistant", assistant.Type) + assert.Equal(t, "/api/__yao/app/icons/app.png", assistant.Avatar) + assert.Equal(t, "gpt-4o", assistant.Connector) + assert.Equal(t, "/assistants/tests/fullfields", assistant.Path) + assert.Equal(t, "Test assistant with all available fields for unit testing", assistant.Description) -// // Test with nil storage -// _, err := LoadStore("test-id") -// assert.Error(t, err) -// assert.Contains(t, err.Error(), "storage is not set") + // Boolean fields + assert.True(t, assistant.Public) + assert.True(t, assistant.Readonly) + assert.True(t, assistant.Mentionable) + assert.False(t, assistant.Automated) -// // Setup mock storage -// mockStore := &mockStore{ -// data: map[string]map[string]interface{}{ -// "test-id": { -// "assistant_id": "test-id", -// "name": "Test Assistant", -// "avatar": "test-avatar", -// "connector": "gpt-3_5-turbo", -// }, -// }, -// } -// SetStorage(mockStore) -// defer SetStorage(nil) + // Share field + assert.Equal(t, "team", assistant.Share) -// // Test loading from store -// assistant, err := LoadStore("test-id") -// assert.NoError(t, err) -// assert.NotNil(t, assistant) -// assert.Equal(t, "test-id", assistant.ID) -// assert.Equal(t, "Test Assistant", assistant.Name) -// assert.Equal(t, "test-avatar", assistant.Avatar) -// assert.Equal(t, "gpt-3_5-turbo", assistant.Connector) + // Sort field + assert.Equal(t, 100, assistant.Sort) -// // Test cache functionality -// assistant2, err := LoadStore("test-id") -// assert.NoError(t, err) -// assert.Equal(t, assistant, assistant2) // Should be the same instance from cache + // Tags + assert.NotNil(t, assistant.Tags) + assert.Contains(t, assistant.Tags, "Test") + assert.Contains(t, assistant.Tags, "Development") + assert.Contains(t, assistant.Tags, "FullFields") -// // Test non-existent assistant -// _, err = LoadStore("non-existent") -// assert.Error(t, err) -// } + // Options + assert.NotNil(t, assistant.Options) + assert.Equal(t, 0.7, assistant.Options["temperature"]) + assert.Equal(t, float64(2000), assistant.Options["max_tokens"]) -// func TestLoad_Cache(t *testing.T) { -// prepare(t) -// defer test.Clean() + // Prompts (default prompts from prompts.yml) + assert.NotNil(t, assistant.Prompts) + assert.GreaterOrEqual(t, len(assistant.Prompts), 1) + assert.Equal(t, "system", assistant.Prompts[0].Role) -// // Clear any existing cache first -// ClearCache() + // Script (from src/index.ts) + assert.NotNil(t, assistant.Script) + }) -// // Test cache operations -// SetCache(2) // Set small cache size for testing -// assert.Equal(t, 2, loaded.capacity, "Cache capacity should be 2") + t.Run("LoadConnectorOptions", func(t *testing.T) { + assistant, err := LoadPath("/assistants/tests/fullfields") + require.NoError(t, err) + require.NotNil(t, assistant) -// // Create test assistants -// assistant1 := &Assistant{ID: "id1", Name: "Assistant 1"} -// assistant2 := &Assistant{ID: "id2", Name: "Assistant 2"} -// assistant3 := &Assistant{ID: "id3", Name: "Assistant 3"} + // ConnectorOptions + assert.NotNil(t, assistant.ConnectorOptions) + assert.True(t, assistant.ConnectorOptions.Optional) + assert.NotNil(t, assistant.ConnectorOptions.Connectors) + assert.Contains(t, assistant.ConnectorOptions.Connectors, "gpt-4o") + assert.Contains(t, assistant.ConnectorOptions.Connectors, "gpt-4o-mini") + assert.Contains(t, assistant.ConnectorOptions.Connectors, "deepseek") + assert.NotNil(t, assistant.ConnectorOptions.Filters) + assert.Len(t, assistant.ConnectorOptions.Filters, 2) + }) -// // Test Put and Get -// loaded.Put(assistant1) -// assert.Equal(t, 1, loaded.Len(), "Cache should have 1 item") + t.Run("LoadPromptPresets", func(t *testing.T) { + assistant, err := LoadPath("/assistants/tests/fullfields") + require.NoError(t, err) + require.NotNil(t, assistant) -// loaded.Put(assistant2) -// assert.Equal(t, 2, loaded.Len(), "Cache should have 2 items") + // PromptPresets (from prompts directory) + assert.NotNil(t, assistant.PromptPresets) -// // Test cache hit -// cached, exists := loaded.Get("id1") -// assert.True(t, exists) -// assert.Equal(t, assistant1, cached) + // Top-level presets: chat.yml -> "chat", task.yml -> "task" + chatPreset, hasChat := assistant.PromptPresets["chat"] + assert.True(t, hasChat, "Should have 'chat' preset") + assert.NotEmpty(t, chatPreset) -// // Test cache eviction (LRU) -// // At this point: assistant1 is most recently used (due to Get), then assistant2 -// loaded.Put(assistant3) // This should evict assistant2 since it's least recently used -// assert.Equal(t, 2, loaded.Len(), "Cache should still have 2 items") -// _, exists = loaded.Get("id2") -// assert.False(t, exists, "assistant2 should have been evicted (least recently used)") -// _, exists = loaded.Get("id1") -// assert.True(t, exists, "assistant1 should still be in cache (was accessed recently)") -// _, exists = loaded.Get("id3") -// assert.True(t, exists, "assistant3 should be in cache (most recently added)") + taskPreset, hasTask := assistant.PromptPresets["task"] + assert.True(t, hasTask, "Should have 'task' preset") + assert.NotEmpty(t, taskPreset) -// // Test clear cache -// ClearCache() -// assert.Nil(t, loaded) + // Nested presets: chat/friendly.yml -> "chat.friendly" + friendlyPreset, hasFriendly := assistant.PromptPresets["chat.friendly"] + assert.True(t, hasFriendly, "Should have 'chat.friendly' preset") + assert.NotEmpty(t, friendlyPreset) -// // Test setting new cache capacity -// SetCache(100) -// assert.NotNil(t, loaded) -// } + professionalPreset, hasProfessional := assistant.PromptPresets["chat.professional"] + assert.True(t, hasProfessional, "Should have 'chat.professional' preset") + assert.NotEmpty(t, professionalPreset) -// func TestLoad_Validate(t *testing.T) { -// tests := []struct { -// name string -// ast *Assistant -// wantErr bool -// }{ -// { -// name: "valid assistant", -// ast: &Assistant{ -// ID: "test-id", -// Name: "Test Assistant", -// Connector: "test-connector", -// }, -// wantErr: false, -// }, -// { -// name: "missing id", -// ast: &Assistant{ -// Name: "Test Assistant", -// Connector: "test-connector", -// }, -// wantErr: true, -// }, -// { -// name: "missing name", -// ast: &Assistant{ -// ID: "test-id", -// Connector: "test-connector", -// }, -// wantErr: true, -// }, -// { -// name: "missing connector", -// ast: &Assistant{ -// ID: "test-id", -// Name: "Test Assistant", -// }, -// wantErr: true, -// }, -// } + // task/analysis.yml -> "task.analysis" + analysisPreset, hasAnalysis := assistant.PromptPresets["task.analysis"] + assert.True(t, hasAnalysis, "Should have 'task.analysis' preset") + assert.NotEmpty(t, analysisPreset) + }) -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// err := tt.ast.Validate() -// if (err != nil) != tt.wantErr { -// t.Errorf("Assistant.Validate() error = %v, wantErr %v", err, tt.wantErr) -// } -// }) -// } -// } + t.Run("LoadKnowledgeBase", func(t *testing.T) { + assistant, err := LoadPath("/assistants/tests/fullfields") + require.NoError(t, err) + require.NotNil(t, assistant) -// func TestLoad_Clone(t *testing.T) { -// // Create a test assistant with all fields populated -// original := &Assistant{ -// ID: "test-id", -// Type: "test-type", -// Name: "Test Assistant", -// Avatar: "test-avatar", -// Connector: "test-connector", -// Path: "test-path", -// BuiltIn: true, -// Sort: 1, -// Description: "test description", -// Tags: []string{"tag1", "tag2"}, -// Readonly: true, -// Mentionable: true, -// Automated: true, -// Options: map[string]interface{}{"key": "value"}, -// Prompts: []Prompt{{Role: "system", Content: "test"}}, -// Workflow: map[string]interface{}{"step": "test"}, -// } + // KB + assert.NotNil(t, assistant.KB) + assert.NotNil(t, assistant.KB.Collections) + assert.Contains(t, assistant.KB.Collections, "test-collection") + assert.NotNil(t, assistant.KB.Options) + assert.Equal(t, float64(5), assistant.KB.Options["top_k"]) + }) -// // Clone the assistant -// clone := original.Clone() + t.Run("LoadMCPServers", func(t *testing.T) { + assistant, err := LoadPath("/assistants/tests/fullfields") + require.NoError(t, err) + require.NotNil(t, assistant) -// // Verify all fields are correctly cloned -// assert.Equal(t, original.ID, clone.ID) -// assert.Equal(t, original.Type, clone.Type) -// assert.Equal(t, original.Name, clone.Name) -// assert.Equal(t, original.Avatar, clone.Avatar) -// assert.Equal(t, original.Connector, clone.Connector) -// assert.Equal(t, original.Path, clone.Path) -// assert.Equal(t, original.BuiltIn, clone.BuiltIn) -// assert.Equal(t, original.Sort, clone.Sort) -// assert.Equal(t, original.Description, clone.Description) -// assert.Equal(t, original.Tags, clone.Tags) -// assert.Equal(t, original.Readonly, clone.Readonly) -// assert.Equal(t, original.Mentionable, clone.Mentionable) -// assert.Equal(t, original.Automated, clone.Automated) -// assert.Equal(t, original.Options, clone.Options) -// assert.Equal(t, original.Prompts, clone.Prompts) -// assert.Equal(t, original.Workflow, clone.Workflow) + // MCP + assert.NotNil(t, assistant.MCP) + assert.NotNil(t, assistant.MCP.Servers) + assert.Len(t, assistant.MCP.Servers, 1) + assert.Equal(t, "echo", assistant.MCP.Servers[0].ServerID) + assert.Contains(t, assistant.MCP.Servers[0].Tools, "ping") + assert.Contains(t, assistant.MCP.Servers[0].Tools, "echo") + }) -// // Verify deep copy by modifying original -// original.Tags[0] = "modified" -// original.Options["key"] = "modified" -// original.Workflow["step"] = "modified" -// assert.NotEqual(t, original.Tags[0], clone.Tags[0]) -// assert.NotEqual(t, original.Options["key"], clone.Options["key"]) -// assert.NotEqual(t, original.Workflow["step"], clone.Workflow["step"]) + t.Run("LoadWorkflow", func(t *testing.T) { + assistant, err := LoadPath("/assistants/tests/fullfields") + require.NoError(t, err) + require.NotNil(t, assistant) -// // Test nil case -// var nilAssistant *Assistant -// assert.Nil(t, nilAssistant.Clone()) -// } + // Workflow + assert.NotNil(t, assistant.Workflow) + assert.NotNil(t, assistant.Workflow.Workflows) + assert.Contains(t, assistant.Workflow.Workflows, "test-workflow") + assert.NotNil(t, assistant.Workflow.Options) + assert.Equal(t, float64(10), assistant.Workflow.Options["max_steps"]) + }) -// func TestLoad_Update(t *testing.T) { -// // Create a test assistant -// ast := &Assistant{ -// ID: "test-id", -// Name: "Original Name", -// Connector: "original-connector", -// } + t.Run("LoadPlaceholder", func(t *testing.T) { + assistant, err := LoadPath("/assistants/tests/fullfields") + require.NoError(t, err) + require.NotNil(t, assistant) -// // Test updating various fields -// updates := map[string]interface{}{ -// "name": "Updated Name", -// "avatar": "updated-avatar", -// "description": "Updated description", -// "connector": "updated-connector", -// "type": "updated-type", -// "sort": 2, -// "mentionable": true, -// "automated": true, -// "tags": []string{"new-tag"}, -// "options": map[string]interface{}{"new": "value"}, -// } + // Placeholder + assert.NotNil(t, assistant.Placeholder) + assert.Equal(t, "Full Fields Test", assistant.Placeholder.Title) + assert.Equal(t, "Test assistant with complete field coverage", assistant.Placeholder.Description) + assert.NotNil(t, assistant.Placeholder.Prompts) + assert.Len(t, assistant.Placeholder.Prompts, 3) + }) -// err := ast.Update(updates) -// assert.NoError(t, err) + t.Run("LoadLocales", func(t *testing.T) { + assistant, err := LoadPath("/assistants/tests/fullfields") + require.NoError(t, err) + require.NotNil(t, assistant) -// // Verify updates -// assert.Equal(t, "Updated Name", ast.Name) -// assert.Equal(t, "updated-avatar", ast.Avatar) -// assert.Equal(t, "Updated description", ast.Description) -// assert.Equal(t, "updated-connector", ast.Connector) -// assert.Equal(t, "updated-type", ast.Type) -// assert.Equal(t, 2, ast.Sort) -// assert.True(t, ast.Mentionable) -// assert.True(t, ast.Automated) -// assert.Equal(t, []string{"new-tag"}, ast.Tags) -// assert.Equal(t, map[string]interface{}{"new": "value"}, ast.Options) + // Locales + assert.NotNil(t, assistant.Locales) -// // Test nil assistant -// var nilAssistant *Assistant -// err = nilAssistant.Update(updates) -// assert.Error(t, err) + enLocale, hasEn := assistant.Locales["en-us"] + assert.True(t, hasEn, "Should have en-us locale") + assert.NotNil(t, enLocale) -// // Test invalid update that would make the assistant invalid -// invalidUpdates := map[string]interface{}{ -// "name": "", -// } -// err = ast.Update(invalidUpdates) -// assert.Error(t, err) -// } + zhLocale, hasZh := assistant.Locales["zh-cn"] + assert.True(t, hasZh, "Should have zh-cn locale") + assert.NotNil(t, zhLocale) + }) -// func TestLoadBuiltIn(t *testing.T) { -// prepare(t) -// defer test.Clean() + t.Run("LoadNonExistentAssistant", func(t *testing.T) { + _, err := LoadPath("/assistants/non-existent") + assert.Error(t, err) + }) +} -// // Clear any existing cache and storage -// ClearCache() -// SetStorage(nil) +// TestLoadPathMCPTest tests loading the MCP test assistant +func TestLoadPathMCPTest(t *testing.T) { + prepare(t) + defer test.Clean() -// // Create a mock store to verify built-in assistants are saved -// mockStore := &mockStore{ -// data: make(map[string]map[string]interface{}), -// } -// SetStorage(mockStore) -// SetCache(100) + assistant, err := LoadPath("/assistants/tests/mcptest") + require.NoError(t, err) + require.NotNil(t, assistant) -// // Test loading built-in assistants -// err := LoadBuiltIn() -// assert.NoError(t, err) + assert.Equal(t, "tests.mcptest", assistant.ID) + assert.Equal(t, "MCP Test Assistant", assistant.Name) + assert.Equal(t, "gpt-4o", assistant.Connector) -// // Verify Modi assistant was loaded -// assistant, exists := loaded.Get("modi") -// assert.True(t, exists, "Modi assistant should be loaded in cache") -// if exists { -// assert.Equal(t, "modi", assistant.ID) -// assert.Equal(t, "Modi", assistant.Name) -// assert.Equal(t, "deepseek", assistant.Connector) -// assert.True(t, assistant.BuiltIn) -// assert.True(t, assistant.Readonly) -// assert.NotNil(t, assistant.Prompts) -// assert.NotNil(t, assistant.Script) -// } + // MCP configuration + assert.NotNil(t, assistant.MCP) + assert.Len(t, assistant.MCP.Servers, 1) + assert.Equal(t, "echo", assistant.MCP.Servers[0].ServerID) -// } + // Locales + assert.NotNil(t, assistant.Locales) + assert.Contains(t, assistant.Locales, "en-us") + assert.Contains(t, assistant.Locales, "zh-cn") +} -// // mockStore implements store.Store interface for testing -// type mockStore struct { -// data map[string]map[string]interface{} -// } +// TestLoadPathBuildRequest tests loading the build request test assistant +func TestLoadPathBuildRequest(t *testing.T) { + prepare(t) + defer test.Clean() -// func (m *mockStore) GetAssistant(id string, locale ...string) (map[string]interface{}, error) { -// if data, ok := m.data[id]; ok { -// return data, nil -// } -// return nil, fmt.Errorf("assistant not found: %s", id) -// } + assistant, err := LoadPath("/assistants/tests/buildrequest") + require.NoError(t, err) + require.NotNil(t, assistant) -// // Add other required interface methods with empty implementations -// func (m *mockStore) GetThread(id string) (map[string]interface{}, error) { return nil, nil } -// func (m *mockStore) GetMessage(id string) (map[string]interface{}, error) { return nil, nil } -// func (m *mockStore) GetFile(id string) (map[string]interface{}, error) { return nil, nil } -// func (m *mockStore) CreateAssistant(data map[string]interface{}) (map[string]interface{}, error) { -// return nil, nil -// } -// func (m *mockStore) CreateThread(data map[string]interface{}) (map[string]interface{}, error) { -// return nil, nil -// } -// func (m *mockStore) CreateMessage(data map[string]interface{}) (map[string]interface{}, error) { -// return nil, nil -// } -// func (m *mockStore) CreateFile(data map[string]interface{}) (map[string]interface{}, error) { -// return nil, nil -// } -// func (m *mockStore) UpdateAssistant(id string, data map[string]interface{}) error { return nil } -// func (m *mockStore) UpdateThread(id string, data map[string]interface{}) error { return nil } -// func (m *mockStore) UpdateMessage(id string, data map[string]interface{}) error { return nil } -// func (m *mockStore) UpdateFile(id string, data map[string]interface{}) error { return nil } -// func (m *mockStore) DeleteAssistant(id string) error { return nil } -// func (m *mockStore) DeleteThread(id string) error { return nil } -// func (m *mockStore) DeleteMessage(id string) error { return nil } -// func (m *mockStore) DeleteFile(id string) error { return nil } -// func (m *mockStore) ListAssistants(query map[string]interface{}) ([]map[string]interface{}, error) { -// return nil, nil -// } -// func (m *mockStore) ListThreads(query map[string]interface{}) ([]map[string]interface{}, error) { -// return nil, nil -// } -// func (m *mockStore) ListMessages(query map[string]interface{}) ([]map[string]interface{}, error) { -// return nil, nil -// } -// func (m *mockStore) ListFiles(query map[string]interface{}) ([]map[string]interface{}, error) { -// return nil, nil -// } -// func (m *mockStore) DeleteAllChats(id string) error { return nil } -// func (m *mockStore) DeleteChat(id string, chatID string) error { return nil } -// func (m *mockStore) GetAssistants(filter store.AssistantFilter, locale ...string) (*store.AssistantResponse, error) { -// return nil, nil -// } -// func (m *mockStore) GetChat(id string, chatID string, locale ...string) (*store.ChatInfo, error) { -// return nil, nil -// } -// func (m *mockStore) GetChatWithFilter(id string, chatID string, filter store.ChatFilter, locale ...string) (*store.ChatInfo, error) { -// return nil, nil -// } -// func (m *mockStore) GetChats(id string, filter store.ChatFilter, locale ...string) (*store.ChatGroupResponse, error) { -// return nil, nil -// } -// func (m *mockStore) GetHistory(id string, chatID string, locale ...string) ([]map[string]interface{}, error) { -// return nil, nil -// } -// func (m *mockStore) GetHistoryWithFilter(id string, chatID string, filter store.ChatFilter, locale ...string) ([]map[string]interface{}, error) { -// return nil, nil -// } -// func (m *mockStore) SaveAssistant(assistant map[string]interface{}) (interface{}, error) { -// return nil, nil -// } -// func (m *mockStore) SaveHistory(sid string, messages []map[string]interface{}, cid string, context map[string]interface{}) error { -// return nil -// } -// func (m *mockStore) UpdateChatTitle(sid string, cid string, title string) error { return nil } -// func (m *mockStore) DeleteAssistants(filter store.AssistantFilter) (int64, error) { return 0, nil } -// func (m *mockStore) GetAssistantTags(locale ...string) ([]store.Tag, error) { -// return []store.Tag{}, nil -// } + assert.Equal(t, "tests.buildrequest", assistant.ID) + assert.Equal(t, "Build Request Test", assistant.Name) -// // Attachment related methods -// func (m *mockStore) SaveAttachment(attachment map[string]interface{}) (interface{}, error) { -// return attachment["file_id"], nil -// } + // Script should be loaded + assert.NotNil(t, assistant.Script) -// func (m *mockStore) DeleteAttachment(fileID string) error { -// return nil -// } + // Options + assert.NotNil(t, assistant.Options) + assert.Equal(t, 0.5, assistant.Options["temperature"]) +} -// func (m *mockStore) GetAttachments(filter store.AttachmentFilter, locale ...string) (*store.AttachmentResponse, error) { -// return &store.AttachmentResponse{}, nil -// } +// TestCache tests the assistant cache functionality +func TestCache(t *testing.T) { + // Clear any existing cache + ClearCache() -// func (m *mockStore) GetAttachment(fileID string, locale ...string) (map[string]interface{}, error) { -// return nil, nil -// } + // Set small cache for testing + SetCache(3) + assert.NotNil(t, loaded) -// func (m *mockStore) DeleteAttachments(filter store.AttachmentFilter) (int64, error) { -// return 0, nil -// } + // Create test assistants + ast1 := &Assistant{AssistantModel: store.AssistantModel{ID: "id1", Name: "Assistant 1"}} + ast2 := &Assistant{AssistantModel: store.AssistantModel{ID: "id2", Name: "Assistant 2"}} + ast3 := &Assistant{AssistantModel: store.AssistantModel{ID: "id3", Name: "Assistant 3"}} + ast4 := &Assistant{AssistantModel: store.AssistantModel{ID: "id4", Name: "Assistant 4"}} -// // Knowledge related methods -// func (m *mockStore) SaveKnowledge(knowledge map[string]interface{}) (interface{}, error) { -// return knowledge["collection_id"], nil -// } + t.Run("PutAndGet", func(t *testing.T) { + loaded.Put(ast1) + assert.Equal(t, 1, loaded.Len()) -// func (m *mockStore) DeleteKnowledge(collectionID string) error { -// return nil -// } + cached, exists := loaded.Get("id1") + assert.True(t, exists) + assert.Equal(t, ast1, cached) + }) -// func (m *mockStore) GetKnowledges(filter store.KnowledgeFilter, locale ...string) (*store.KnowledgeResponse, error) { -// return &store.KnowledgeResponse{}, nil -// } + t.Run("CacheEviction", func(t *testing.T) { + loaded.Put(ast2) + loaded.Put(ast3) + assert.Equal(t, 3, loaded.Len()) -// func (m *mockStore) GetKnowledge(collectionID string, locale ...string) (map[string]interface{}, error) { -// return nil, nil -// } + // Access ast1 to make it recently used + loaded.Get("id1") -// func (m *mockStore) DeleteKnowledges(filter store.KnowledgeFilter) (int64, error) { -// return 0, nil -// } + // Add ast4, should evict ast2 (least recently used) + loaded.Put(ast4) + assert.Equal(t, 3, loaded.Len()) -// // Close closes the store and releases any resources -// func (m *mockStore) Close() error { -// return nil -// } + _, exists := loaded.Get("id2") + assert.False(t, exists, "ast2 should be evicted") + + _, exists = loaded.Get("id1") + assert.True(t, exists, "ast1 should still exist") + + _, exists = loaded.Get("id4") + assert.True(t, exists, "ast4 should exist") + }) + + t.Run("ClearCache", func(t *testing.T) { + ClearCache() + assert.Nil(t, loaded) + }) + + t.Run("SetCacheAfterClear", func(t *testing.T) { + SetCache(100) + assert.NotNil(t, loaded) + }) +} + +// TestClone tests the assistant Clone method +func TestClone(t *testing.T) { + prepare(t) + defer test.Clean() + + t.Run("CloneFullFieldsAssistant", func(t *testing.T) { + original, err := LoadPath("/assistants/tests/fullfields") + require.NoError(t, err) + + clone := original.Clone() + require.NotNil(t, clone) + + // Basic fields should be equal + assert.Equal(t, original.ID, clone.ID) + assert.Equal(t, original.Name, clone.Name) + assert.Equal(t, original.Type, clone.Type) + assert.Equal(t, original.Connector, clone.Connector) + assert.Equal(t, original.Description, clone.Description) + + // Verify deep copy - modifying original should not affect clone + if len(original.Tags) > 0 { + originalTag := original.Tags[0] + original.Tags[0] = "modified" + assert.NotEqual(t, original.Tags[0], clone.Tags[0]) + original.Tags[0] = originalTag // restore + } + + if original.Options != nil { + original.Options["test_key"] = "test_value" + _, exists := clone.Options["test_key"] + assert.False(t, exists, "Clone should not have modified key") + delete(original.Options, "test_key") // cleanup + } + }) + + t.Run("CloneNil", func(t *testing.T) { + var nilAssistant *Assistant + assert.Nil(t, nilAssistant.Clone()) + }) +} + +// TestUpdate tests the assistant Update method +func TestUpdate(t *testing.T) { + prepare(t) + defer test.Clean() + + t.Run("UpdateBasicFields", func(t *testing.T) { + assistant, err := LoadPath("/assistants/tests/fullfields") + require.NoError(t, err) + + updates := map[string]interface{}{ + "name": "Updated Name", + "description": "Updated description", + "tags": []string{"updated", "tags"}, + } + + err = assistant.Update(updates) + require.NoError(t, err) + + assert.Equal(t, "Updated Name", assistant.Name) + assert.Equal(t, "Updated description", assistant.Description) + assert.Equal(t, []string{"updated", "tags"}, assistant.Tags) + }) + + t.Run("UpdateConnectorOptions", func(t *testing.T) { + assistant, err := LoadPath("/assistants/tests/fullfields") + require.NoError(t, err) + + updates := map[string]interface{}{ + "connector_options": map[string]interface{}{ + "optional": false, + "connectors": []string{"new-connector"}, + }, + } + + err = assistant.Update(updates) + require.NoError(t, err) + + assert.NotNil(t, assistant.ConnectorOptions) + assert.False(t, assistant.ConnectorOptions.Optional) + assert.Contains(t, assistant.ConnectorOptions.Connectors, "new-connector") + }) + + t.Run("UpdatePromptPresets", func(t *testing.T) { + assistant, err := LoadPath("/assistants/tests/fullfields") + require.NoError(t, err) + + updates := map[string]interface{}{ + "prompt_presets": map[string]interface{}{ + "custom": []map[string]interface{}{ + {"role": "system", "content": "Custom preset"}, + }, + }, + } + + err = assistant.Update(updates) + require.NoError(t, err) + + assert.NotNil(t, assistant.PromptPresets) + customPreset, exists := assistant.PromptPresets["custom"] + assert.True(t, exists) + assert.Len(t, customPreset, 1) + }) + + t.Run("UpdateSource", func(t *testing.T) { + assistant, err := LoadPath("/assistants/tests/fullfields") + require.NoError(t, err) + + updates := map[string]interface{}{ + "source": "function Create(ctx, messages) { return { messages: messages }; }", + } + + err = assistant.Update(updates) + require.NoError(t, err) + + assert.Equal(t, "function Create(ctx, messages) { return { messages: messages }; }", assistant.Source) + }) + + t.Run("UpdateNilAssistant", func(t *testing.T) { + var nilAssistant *Assistant + err := nilAssistant.Update(map[string]interface{}{"name": "test"}) + assert.Error(t, err) + }) +} + +// TestMap tests the assistant Map method +func TestMap(t *testing.T) { + prepare(t) + defer test.Clean() + + assistant, err := LoadPath("/assistants/tests/fullfields") + require.NoError(t, err) + + m := assistant.Map() + require.NotNil(t, m) + + // Check all fields are present + assert.Equal(t, assistant.ID, m["assistant_id"]) + assert.Equal(t, assistant.Name, m["name"]) + assert.Equal(t, assistant.Type, m["type"]) + assert.Equal(t, assistant.Connector, m["connector"]) + assert.Equal(t, assistant.Description, m["description"]) + assert.Equal(t, assistant.Path, m["path"]) + assert.Equal(t, assistant.Tags, m["tags"]) + assert.Equal(t, assistant.Options, m["options"]) + assert.Equal(t, assistant.Prompts, m["prompts"]) + assert.Equal(t, assistant.KB, m["kb"]) + assert.Equal(t, assistant.MCP, m["mcp"]) + assert.Equal(t, assistant.Workflow, m["workflow"]) + assert.Equal(t, assistant.Placeholder, m["placeholder"]) + assert.Equal(t, assistant.Locales, m["locales"]) + + // New fields + assert.Equal(t, assistant.ConnectorOptions, m["connector_options"]) + assert.Equal(t, assistant.PromptPresets, m["prompt_presets"]) + assert.Equal(t, assistant.Source, m["source"]) +} + +// TestValidate tests the assistant Validate method +func TestValidate(t *testing.T) { + tests := []struct { + name string + ast *Assistant + wantErr bool + }{ + { + name: "ValidAssistant", + ast: &Assistant{ + AssistantModel: store.AssistantModel{ + ID: "test-id", + Name: "Test Assistant", + Connector: "gpt-4o", + }, + }, + wantErr: false, + }, + { + name: "MissingID", + ast: &Assistant{ + AssistantModel: store.AssistantModel{ + Name: "Test Assistant", + Connector: "gpt-4o", + }, + }, + wantErr: true, + }, + { + name: "MissingName", + ast: &Assistant{ + AssistantModel: store.AssistantModel{ + ID: "test-id", + Connector: "gpt-4o", + }, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.ast.Validate() + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/agent/assistant/source.go b/agent/assistant/source.go new file mode 100644 index 00000000..a218ffb9 --- /dev/null +++ b/agent/assistant/source.go @@ -0,0 +1,36 @@ +package assistant + +import ( + "fmt" + "time" + + v8 "github.com/yaoapp/gou/runtime/v8" + "github.com/yaoapp/yao/agent/assistant/hook" +) + +// loadSource loads hook script from source code string +// The source field stores TypeScript code directly +// Priority: script field > source field (if script exists, source is ignored) +func loadSource(source string, assistantID string) (*hook.Script, error) { + if source == "" { + return nil, nil + } + + // Generate a virtual file path for the script + file := fmt.Sprintf("assistants/%s/source.ts", assistantID) + + script, err := v8.MakeScript([]byte(source), file, 5*time.Second, true) + if err != nil { + return nil, fmt.Errorf("failed to compile source script: %w", err) + } + + return &hook.Script{Script: script}, nil +} + +// TODO: Future enhancement - support multiple files merged with special comment delimiter +// Format: // file: index.ts +// This would allow splitting large scripts into multiple logical files while storing as single source +// func loadSourceMultiFile(source string, assistantID string) (*hook.Script, error) { +// // Parse source by "// file: xxx.ts" delimiter +// // Merge and compile +// } diff --git a/agent/store/types/convert.go b/agent/store/types/convert.go index d8a59b44..4c39859c 100644 --- a/agent/store/types/convert.go +++ b/agent/store/types/convert.go @@ -462,3 +462,56 @@ func ParseModelID(modelID string) string { } return parts[len(parts)-1] } + +// ToConnectorOptions converts various types to ConnectorOptions +func ToConnectorOptions(v interface{}) (*ConnectorOptions, error) { + if v == nil { + return nil, nil + } + + switch opts := v.(type) { + case *ConnectorOptions: + return opts, nil + + case ConnectorOptions: + return &opts, nil + + default: + raw, err := jsoniter.Marshal(opts) + if err != nil { + return nil, fmt.Errorf("connector_options format error: %s", err.Error()) + } + + var connOpts ConnectorOptions + err = jsoniter.Unmarshal(raw, &connOpts) + if err != nil { + return nil, fmt.Errorf("connector_options format error: %s", err.Error()) + } + return &connOpts, nil + } +} + +// ToPromptPresets converts various types to map[string][]Prompt +func ToPromptPresets(v interface{}) (map[string][]Prompt, error) { + if v == nil { + return nil, nil + } + + switch presets := v.(type) { + case map[string][]Prompt: + return presets, nil + + default: + raw, err := jsoniter.Marshal(presets) + if err != nil { + return nil, fmt.Errorf("prompt_presets format error: %s", err.Error()) + } + + var result map[string][]Prompt + err = jsoniter.Unmarshal(raw, &result) + if err != nil { + return nil, fmt.Errorf("prompt_presets format error: %s", err.Error()) + } + return result, nil + } +} diff --git a/agent/store/types/convert_test.go b/agent/store/types/convert_test.go index 3fbfce1f..67c97aa9 100644 --- a/agent/store/types/convert_test.go +++ b/agent/store/types/convert_test.go @@ -1148,6 +1148,273 @@ func TestModelID(t *testing.T) { }) } +// TestToConnectorOptions tests the ToConnectorOptions conversion function +func TestToConnectorOptions(t *testing.T) { + t.Run("NilInput", func(t *testing.T) { + result, err := ToConnectorOptions(nil) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if result != nil { + t.Errorf("Expected nil result, got: %v", result) + } + }) + + t.Run("ConnectorOptionsPointer", func(t *testing.T) { + opts := &ConnectorOptions{ + Optional: true, + Connectors: []string{"openai", "anthropic"}, + Filters: []ModelCapability{CapVision, CapToolCalls}, + } + result, err := ToConnectorOptions(opts) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if result != opts { + t.Errorf("Expected same pointer") + } + }) + + t.Run("ConnectorOptionsValue", func(t *testing.T) { + opts := ConnectorOptions{ + Optional: true, + Connectors: []string{"openai", "anthropic"}, + Filters: []ModelCapability{CapVision, CapToolCalls}, + } + result, err := ToConnectorOptions(opts) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if !result.Optional { + t.Error("Expected Optional to be true") + } + if len(result.Connectors) != 2 { + t.Errorf("Expected 2 connectors, got %d", len(result.Connectors)) + } + if len(result.Filters) != 2 { + t.Errorf("Expected 2 filters, got %d", len(result.Filters)) + } + }) + + t.Run("MapInput", func(t *testing.T) { + data := map[string]interface{}{ + "optional": true, + "connectors": []string{"openai", "anthropic", "azure"}, + "filters": []string{"vision", "tool_calls", "audio"}, + } + result, err := ToConnectorOptions(data) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if !result.Optional { + t.Error("Expected Optional to be true") + } + if len(result.Connectors) != 3 { + t.Errorf("Expected 3 connectors, got %d", len(result.Connectors)) + } + if len(result.Filters) != 3 { + t.Errorf("Expected 3 filters, got %d", len(result.Filters)) + } + }) + + t.Run("MapInputOptionalOnly", func(t *testing.T) { + data := map[string]interface{}{ + "optional": true, + } + result, err := ToConnectorOptions(data) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if !result.Optional { + t.Error("Expected Optional to be true") + } + if result.Connectors != nil { + t.Error("Expected Connectors to be nil") + } + if result.Filters != nil { + t.Error("Expected Filters to be nil") + } + }) + + t.Run("InvalidInput", func(t *testing.T) { + // Test with data that can't be marshaled + invalidData := make(chan int) + _, err := ToConnectorOptions(invalidData) + if err == nil { + t.Error("Expected error for invalid input") + } + }) + + t.Run("InvalidJSONUnmarshal", func(t *testing.T) { + // Test with data that marshals but can't unmarshal to ConnectorOptions + data := map[string]interface{}{ + "invalid_field": "should cause unmarshal to fail gracefully", + } + result, err := ToConnectorOptions(data) + // Should not error, just return empty ConnectorOptions + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if result == nil { + t.Error("Expected non-nil result") + } + }) +} + +// TestToPromptPresets tests the ToPromptPresets conversion function +func TestToPromptPresets(t *testing.T) { + t.Run("NilInput", func(t *testing.T) { + result, err := ToPromptPresets(nil) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if result != nil { + t.Errorf("Expected nil result, got: %v", result) + } + }) + + t.Run("MapStringPromptSlice", func(t *testing.T) { + presets := map[string][]Prompt{ + "chat": { + {Role: "system", Content: "You are a chat assistant"}, + }, + "task": { + {Role: "system", Content: "You are a task assistant"}, + }, + } + result, err := ToPromptPresets(presets) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if len(result) != 2 { + t.Errorf("Expected 2 presets, got %d", len(result)) + } + if len(result["chat"]) != 1 { + t.Errorf("Expected 1 chat prompt, got %d", len(result["chat"])) + } + if len(result["task"]) != 1 { + t.Errorf("Expected 1 task prompt, got %d", len(result["task"])) + } + }) + + t.Run("MapInput", func(t *testing.T) { + data := map[string]interface{}{ + "chat": []interface{}{ + map[string]interface{}{"role": "system", "content": "Chat mode system prompt"}, + map[string]interface{}{"role": "user", "content": "Example user message"}, + }, + "task": []interface{}{ + map[string]interface{}{"role": "system", "content": "Task mode system prompt"}, + }, + "analyze": []interface{}{ + map[string]interface{}{"role": "system", "content": "Analyze mode system prompt"}, + }, + } + result, err := ToPromptPresets(data) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if len(result) != 3 { + t.Errorf("Expected 3 presets, got %d", len(result)) + } + if len(result["chat"]) != 2 { + t.Errorf("Expected 2 chat prompts, got %d", len(result["chat"])) + } + if len(result["task"]) != 1 { + t.Errorf("Expected 1 task prompt, got %d", len(result["task"])) + } + if len(result["analyze"]) != 1 { + t.Errorf("Expected 1 analyze prompt, got %d", len(result["analyze"])) + } + }) + + t.Run("EmptyMap", func(t *testing.T) { + data := map[string]interface{}{} + result, err := ToPromptPresets(data) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if result == nil { + t.Error("Expected non-nil result") + } + if len(result) != 0 { + t.Errorf("Expected empty map, got %d entries", len(result)) + } + }) + + t.Run("SinglePreset", func(t *testing.T) { + data := map[string]interface{}{ + "default": []interface{}{ + map[string]interface{}{"role": "system", "content": "Default prompt"}, + }, + } + result, err := ToPromptPresets(data) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if len(result) != 1 { + t.Errorf("Expected 1 preset, got %d", len(result)) + } + if _, ok := result["default"]; !ok { + t.Error("Expected 'default' key in result") + } + }) + + t.Run("InvalidInput", func(t *testing.T) { + // Test with data that can't be marshaled + invalidData := make(chan int) + _, err := ToPromptPresets(invalidData) + if err == nil { + t.Error("Expected error for invalid input") + } + }) + + t.Run("InvalidJSONUnmarshal", func(t *testing.T) { + // Test with data that marshals but can't unmarshal to map[string][]Prompt + // This is a string that can be marshaled but won't unmarshal to the expected type + data := "not a map" + _, err := ToPromptPresets(data) + if err == nil { + t.Error("Expected error for invalid JSON unmarshal") + } + }) + + t.Run("PromptWithAllFields", func(t *testing.T) { + data := map[string]interface{}{ + "advanced": []interface{}{ + map[string]interface{}{ + "role": "system", + "content": "Advanced system prompt", + "name": "system-prompt", + }, + map[string]interface{}{ + "role": "user", + "content": "User example", + "name": "user-example", + }, + map[string]interface{}{ + "role": "assistant", + "content": "Assistant response", + "name": "assistant-response", + }, + }, + } + result, err := ToPromptPresets(data) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if len(result["advanced"]) != 3 { + t.Errorf("Expected 3 prompts in advanced, got %d", len(result["advanced"])) + } + if result["advanced"][0].Role != "system" { + t.Errorf("Expected role 'system', got '%s'", result["advanced"][0].Role) + } + if result["advanced"][0].Content != "Advanced system prompt" { + t.Errorf("Expected content 'Advanced system prompt', got '%s'", result["advanced"][0].Content) + } + }) +} + // TestParseModelID tests the ParseModelID function func TestParseModelID(t *testing.T) { t.Run("ValidModelID", func(t *testing.T) { From 45029269af4a0d01767bdcfd69f27ba4bf9c00fb Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 2 Dec 2025 15:37:41 +0800 Subject: [PATCH 4/7] Enhance assistant model with global prompts functionality - Introduced the `disable_global_prompts` field in the Assistant model to control the usage of global prompts. - Updated the Load and Get methods to initialize and retrieve global prompts from the configuration. - Refactored tests to validate the new global prompts functionality and ensure proper loading and context handling. - Improved the overall structure and clarity of the assistant's capabilities and configurations. --- agent/assistant/assistant.go | 93 ++++--- agent/assistant/load.go | 123 +------- agent/assistant/load_test.go | 1 + agent/load.go | 25 ++ agent/load_test.go | 181 +++++++++++- agent/store/types/convert.go | 3 + agent/store/types/convert_test.go | 72 ++++- agent/store/types/fields.go | 68 ++--- agent/store/types/fields_test.go | 2 + agent/store/types/prompt.go | 284 +++++++++++++++++++ agent/store/types/prompt_test.go | 432 +++++++++++++++++++++++++++++ agent/store/types/types.go | 57 ++-- agent/store/xun/assistant.go | 44 +-- agent/types/types.go | 5 +- data/bindata.go | 284 +++++++++---------- yao/models/agent/assistant.mod.yao | 8 + 16 files changed, 1279 insertions(+), 403 deletions(-) create mode 100644 agent/store/types/prompt.go create mode 100644 agent/store/types/prompt_test.go diff --git a/agent/assistant/assistant.go b/agent/assistant/assistant.go index 7fe86c47..63330a69 100644 --- a/agent/assistant/assistant.go +++ b/agent/assistant/assistant.go @@ -63,33 +63,34 @@ func (ast *Assistant) Map() map[string]interface{} { } return map[string]interface{}{ - "assistant_id": ast.ID, - "type": ast.Type, - "name": ast.Name, - "readonly": ast.Readonly, - "public": ast.Public, - "share": ast.Share, - "avatar": ast.Avatar, - "connector": ast.Connector, - "connector_options": ast.ConnectorOptions, - "path": ast.Path, - "built_in": ast.BuiltIn, - "sort": ast.Sort, - "description": ast.Description, - "options": ast.Options, - "prompts": ast.Prompts, - "prompt_presets": ast.PromptPresets, - "source": ast.Source, - "kb": ast.KB, - "mcp": ast.MCP, - "workflow": ast.Workflow, - "tags": ast.Tags, - "mentionable": ast.Mentionable, - "automated": ast.Automated, - "placeholder": ast.Placeholder, - "locales": ast.Locales, - "created_at": store.ToMySQLTime(ast.CreatedAt), - "updated_at": store.ToMySQLTime(ast.UpdatedAt), + "assistant_id": ast.ID, + "type": ast.Type, + "name": ast.Name, + "readonly": ast.Readonly, + "public": ast.Public, + "share": ast.Share, + "avatar": ast.Avatar, + "connector": ast.Connector, + "connector_options": ast.ConnectorOptions, + "path": ast.Path, + "built_in": ast.BuiltIn, + "sort": ast.Sort, + "description": ast.Description, + "options": ast.Options, + "prompts": ast.Prompts, + "prompt_presets": ast.PromptPresets, + "disable_global_prompts": ast.DisableGlobalPrompts, + "source": ast.Source, + "kb": ast.KB, + "mcp": ast.MCP, + "workflow": ast.Workflow, + "tags": ast.Tags, + "mentionable": ast.Mentionable, + "automated": ast.Automated, + "placeholder": ast.Placeholder, + "locales": ast.Locales, + "created_at": store.ToMySQLTime(ast.CreatedAt), + "updated_at": store.ToMySQLTime(ast.UpdatedAt), } } @@ -137,23 +138,24 @@ func (ast *Assistant) Clone() *Assistant { clone := &Assistant{ AssistantModel: store.AssistantModel{ - ID: ast.ID, - Type: ast.Type, - Name: ast.Name, - Avatar: ast.Avatar, - Connector: ast.Connector, - Path: ast.Path, - BuiltIn: ast.BuiltIn, - Sort: ast.Sort, - Description: ast.Description, - Readonly: ast.Readonly, - Public: ast.Public, - Share: ast.Share, - Mentionable: ast.Mentionable, - Automated: ast.Automated, - Source: ast.Source, - CreatedAt: ast.CreatedAt, - UpdatedAt: ast.UpdatedAt, + ID: ast.ID, + Type: ast.Type, + Name: ast.Name, + Avatar: ast.Avatar, + Connector: ast.Connector, + Path: ast.Path, + BuiltIn: ast.BuiltIn, + Sort: ast.Sort, + Description: ast.Description, + Readonly: ast.Readonly, + Public: ast.Public, + Share: ast.Share, + Mentionable: ast.Mentionable, + Automated: ast.Automated, + DisableGlobalPrompts: ast.DisableGlobalPrompts, + Source: ast.Source, + CreatedAt: ast.CreatedAt, + UpdatedAt: ast.UpdatedAt, }, Search: ast.Search, Script: ast.Script, @@ -330,6 +332,9 @@ func (ast *Assistant) Update(data map[string]interface{}) error { if v, ok := data["automated"].(bool); ok { ast.Automated = v } + if v, ok := data["disable_global_prompts"].(bool); ok { + ast.DisableGlobalPrompts = v + } if v, ok := data["readonly"].(bool); ok { ast.Readonly = v } diff --git a/agent/assistant/load.go b/agent/assistant/load.go index c65bbeaf..c15c228b 100644 --- a/agent/assistant/load.go +++ b/agent/assistant/load.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "path/filepath" - "regexp" "strings" "time" @@ -271,7 +270,7 @@ func LoadPath(path string) (*Assistant, error) { // prompts (default prompts from prompts.yml) promptsfile := filepath.Join(path, "prompts.yml") if has, _ := app.Exists(promptsfile); has { - prompts, ts, err := loadPrompts(promptsfile, path) + prompts, ts, err := store.LoadPrompts(promptsfile, path) if err != nil { return nil, err } @@ -283,7 +282,7 @@ func LoadPath(path string) (*Assistant, error) { // prompt_presets (from prompts directory, key is filename without extension) promptsDir := filepath.Join(path, "prompts") if has, _ := app.Exists(promptsDir); has { - presets, ts, err := loadPromptPresets(promptsDir, path) + presets, ts, err := store.LoadPromptPresets(promptsDir, path) if err != nil { return nil, err } @@ -397,6 +396,11 @@ func loadMap(data map[string]interface{}) (*Assistant, error) { assistant.Automated = v } + // DisableGlobalPrompts + if v, ok := data["disable_global_prompts"].(bool); ok { + assistant.DisableGlobalPrompts = v + } + // Readonly if v, ok := data["readonly"].(bool); ok { assistant.Readonly = v @@ -651,119 +655,6 @@ func loadMap(data map[string]interface{}) (*Assistant, error) { return assistant, nil } -func loadPrompts(file string, root string) (string, int64, error) { - - app, err := fs.Get("app") - if err != nil { - return "", 0, err - } - - ts, err := app.ModTime(file) - if err != nil { - return "", 0, err - } - - prompts, err := app.ReadFile(file) - if err != nil { - return "", 0, err - } - - // Replace @assets/xxx references with file content - re := regexp.MustCompile(`@assets/([^\s]+\.(md|yml|yaml|json|txt))`) - prompts = re.ReplaceAllFunc(prompts, func(s []byte) []byte { - asset := re.FindStringSubmatch(string(s))[1] - assetFile := filepath.Join(root, "assets", asset) - assetContent, err := app.ReadFile(assetFile) - if err != nil { - return []byte("") - } - // Add proper YAML formatting for content - lines := strings.Split(string(assetContent), "\n") - formattedContent := "|\n" - for _, line := range lines { - formattedContent += " " + line + "\n" - } - return []byte(formattedContent) - }) - - return string(prompts), ts.UnixNano(), nil -} - -// loadPromptPresets loads prompt presets from the prompts directory -// Supports multi-level directories, key is path with "/" replaced by "." -// e.g., prompts/chat/default.yml -> "chat.default" -func loadPromptPresets(dir string, root string) (map[string][]store.Prompt, int64, error) { - app, err := fs.Get("app") - if err != nil { - return nil, 0, err - } - - // Read directory recursively - returns full paths relative to app root - files, err := app.ReadDir(dir, true) - if err != nil { - return nil, 0, err - } - - presets := make(map[string][]store.Prompt) - var latestTs int64 - - for _, file := range files { - // Only process .yml/.yaml files - if !strings.HasSuffix(file, ".yml") && !strings.HasSuffix(file, ".yaml") { - continue - } - - // file is already full path relative to app root (e.g., /assistants/tests/fullfields/prompts/chat/friendly.yml) - ts, err := app.ModTime(file) - if err != nil { - return nil, 0, err - } - if ts.UnixNano() > latestTs { - latestTs = ts.UnixNano() - } - - // Read file content directly - content, err := app.ReadFile(file) - if err != nil { - return nil, 0, err - } - - // Replace @assets/xxx references with file content - re := regexp.MustCompile(`@assets/([^\s]+\.(md|yml|yaml|json|txt))`) - content = re.ReplaceAllFunc(content, func(s []byte) []byte { - asset := re.FindStringSubmatch(string(s))[1] - assetFile := filepath.Join(root, "assets", asset) - assetContent, err := app.ReadFile(assetFile) - if err != nil { - return []byte("") - } - // Add proper YAML formatting for content - lines := strings.Split(string(assetContent), "\n") - formattedContent := "|\n" - for _, line := range lines { - formattedContent += " " + line + "\n" - } - return []byte(formattedContent) - }) - - // Parse prompts - var prompts []store.Prompt - err = yaml.Unmarshal(content, &prompts) - if err != nil { - return nil, 0, fmt.Errorf("failed to parse prompt preset %s: %w", file, err) - } - - // Build key: get relative path from dir, remove extension and replace "/" with "." - // e.g., "/assistants/tests/fullfields/prompts/chat/friendly.yml" -> "chat.friendly" - relPath := strings.TrimPrefix(file, dir+"/") - key := strings.TrimSuffix(relPath, filepath.Ext(relPath)) - key = strings.ReplaceAll(key, "/", ".") - presets[key] = prompts - } - - return presets, latestTs, nil -} - func loadScript(file string, root string) (*hook.Script, int64, error) { app, err := fs.Get("app") diff --git a/agent/assistant/load_test.go b/agent/assistant/load_test.go index b5e2b35a..48deaee2 100644 --- a/agent/assistant/load_test.go +++ b/agent/assistant/load_test.go @@ -38,6 +38,7 @@ func TestLoadPath(t *testing.T) { assert.True(t, assistant.Readonly) assert.True(t, assistant.Mentionable) assert.False(t, assistant.Automated) + assert.True(t, assistant.DisableGlobalPrompts) // Share field assert.Equal(t, "team", assistant.Share) diff --git a/agent/load.go b/agent/load.go index d5c56f24..461dfe89 100644 --- a/agent/load.go +++ b/agent/load.go @@ -80,6 +80,12 @@ func Load(cfg config.Config) error { return err } + // Initialize Global Prompts + err = initGlobalPrompts() + if err != nil { + return err + } + // Initialize Assistant err = initAssistant() if err != nil { @@ -104,6 +110,25 @@ func initGlobalI18n() error { return nil } +// initGlobalPrompts initialize the global prompts from agent/prompts.yml +func initGlobalPrompts() error { + prompts, _, err := store.LoadGlobalPrompts() + if err != nil { + return err + } + agentDSL.GlobalPrompts = prompts + return nil +} + +// GetGlobalPrompts returns the global prompts +// ctx: context variables for parsing $CTX.* variables +func GetGlobalPrompts(ctx map[string]string) []store.Prompt { + if agentDSL == nil || len(agentDSL.GlobalPrompts) == 0 { + return nil + } + return store.Prompts(agentDSL.GlobalPrompts).Parse(ctx) +} + // initModelCapabilities initialize the model capabilities configuration func initModelCapabilities() error { path := filepath.Join("agent", "models.yml") diff --git a/agent/load_test.go b/agent/load_test.go index a156e99e..40c95069 100644 --- a/agent/load_test.go +++ b/agent/load_test.go @@ -1,16 +1,173 @@ package agent -// func TestLoad(t *testing.T) { -// test.Prepare(t, config.Conf) -// defer test.Clean() +import ( + "strings" + "testing" + "time" -// err := Load(config.Conf) -// if err != nil { -// t.Fatal(err) -// } -// check(t) -// } + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/test" +) -// func check(t *testing.T) { -// assert.NotNil(t, Agent) -// } +func prepare(t *testing.T) { + test.Prepare(t, config.Conf) + err := Load(config.Conf) + require.NoError(t, err) +} + +func TestLoad(t *testing.T) { + prepare(t) + defer test.Clean() + + agent := GetAgent() + require.NotNil(t, agent) + + t.Run("LoadAgentSettings", func(t *testing.T) { + // Cache setting + assert.NotEmpty(t, agent.Cache) + + // Store setting + assert.NotNil(t, agent.Store) + assert.Greater(t, agent.StoreSetting.MaxSize, 0) + + // Uses setting + assert.NotNil(t, agent.Uses) + assert.NotEmpty(t, agent.Uses.Default) + }) + + t.Run("LoadDefaultAssistant", func(t *testing.T) { + assert.NotNil(t, agent.Assistant) + }) + + t.Run("LoadGlobalPrompts", func(t *testing.T) { + // Global prompts should be loaded from agent/prompts.yml + assert.NotNil(t, agent.GlobalPrompts) + assert.Greater(t, len(agent.GlobalPrompts), 0) + + // First prompt should be system role + assert.Equal(t, "system", agent.GlobalPrompts[0].Role) + + // Content should contain system context info (with variables not yet parsed) + assert.Contains(t, agent.GlobalPrompts[0].Content, "$SYS.") + }) + + t.Run("LoadModelCapabilities", func(t *testing.T) { + // Model capabilities should be loaded from agent/models.yml + assert.NotNil(t, agent.Models) + assert.Greater(t, len(agent.Models), 0) + }) +} + +func TestGetGlobalPrompts(t *testing.T) { + prepare(t) + defer test.Clean() + + t.Run("ParseWithoutContext", func(t *testing.T) { + prompts := GetGlobalPrompts(nil) + require.NotNil(t, prompts) + require.Greater(t, len(prompts), 0) + + // $SYS.* variables should be replaced + content := prompts[0].Content + assert.NotContains(t, content, "$SYS.DATETIME") + assert.NotContains(t, content, "$SYS.TIMEZONE") + assert.NotContains(t, content, "$SYS.WEEKDAY") + + // Should contain actual time values + now := time.Now() + assert.Contains(t, content, now.Format("2006-01-02")) + }) + + t.Run("ParseWithContext", func(t *testing.T) { + ctx := map[string]string{ + "USER_ID": "test-user-123", + "LOCALE": "zh-CN", + } + + prompts := GetGlobalPrompts(ctx) + require.NotNil(t, prompts) + require.Greater(t, len(prompts), 0) + + // $SYS.* variables should be replaced + content := prompts[0].Content + assert.NotContains(t, content, "$SYS.DATETIME") + }) + + t.Run("ParseSystemTimeVariables", func(t *testing.T) { + prompts := GetGlobalPrompts(nil) + require.NotNil(t, prompts) + + content := prompts[0].Content + now := time.Now() + + // Should contain current date + assert.Contains(t, content, now.Format("2006-01-02")) + + // Should contain timezone + assert.Contains(t, content, now.Location().String()) + + // Should contain weekday + assert.Contains(t, content, now.Weekday().String()) + }) +} + +func TestGetGlobalPromptsWithDisableFlag(t *testing.T) { + prepare(t) + defer test.Clean() + + agent := GetAgent() + require.NotNil(t, agent) + + t.Run("GlobalPromptsExist", func(t *testing.T) { + // Verify global prompts are loaded + assert.NotNil(t, agent.GlobalPrompts) + assert.Greater(t, len(agent.GlobalPrompts), 0) + }) + + t.Run("AssistantCanDisableGlobalPrompts", func(t *testing.T) { + // The fullfields test assistant has disable_global_prompts: true + // This test verifies the flag is properly loaded + // The actual merging logic is in the assistant module + prompts := GetGlobalPrompts(nil) + assert.NotNil(t, prompts) + + // Global prompts should still be available + // The assistant decides whether to use them based on DisableGlobalPrompts flag + }) +} + +func TestGlobalPromptsContent(t *testing.T) { + prepare(t) + defer test.Clean() + + agent := GetAgent() + require.NotNil(t, agent) + require.NotNil(t, agent.GlobalPrompts) + require.Greater(t, len(agent.GlobalPrompts), 0) + + t.Run("SystemContextPrompt", func(t *testing.T) { + // Find system prompt + var systemPrompt string + for _, p := range agent.GlobalPrompts { + if p.Role == "system" { + systemPrompt = p.Content + break + } + } + + assert.NotEmpty(t, systemPrompt) + assert.Contains(t, systemPrompt, "System Context") + }) + + t.Run("VariablesInRawPrompts", func(t *testing.T) { + // Raw prompts should contain unparsed variables + content := agent.GlobalPrompts[0].Content + assert.True(t, + strings.Contains(content, "$SYS.") || + strings.Contains(content, "$ENV.") || + strings.Contains(content, "$CTX."), + "Raw prompts should contain variable placeholders") + }) +} diff --git a/agent/store/types/convert.go b/agent/store/types/convert.go index 4c39859c..9afa3f9f 100644 --- a/agent/store/types/convert.go +++ b/agent/store/types/convert.go @@ -291,6 +291,9 @@ func ToAssistantModel(v interface{}) (*AssistantModel, error) { } } + // DisableGlobalPrompts + model.DisableGlobalPrompts = getBoolValue(data, "disable_global_prompts") + // ConnectorOptions if connectorOptions, ok := data["connector_options"]; ok && connectorOptions != nil { raw, err := jsoniter.Marshal(connectorOptions) diff --git a/agent/store/types/convert_test.go b/agent/store/types/convert_test.go index 67c97aa9..aa65fb66 100644 --- a/agent/store/types/convert_test.go +++ b/agent/store/types/convert_test.go @@ -459,7 +459,8 @@ func TestToAssistantModel(t *testing.T) { {"role": "system", "content": "You are a task assistant"}, }, }, - "source": "function hook() { return 'test'; }", + "disable_global_prompts": true, + "source": "function hook() { return 'test'; }", "kb": map[string]interface{}{ "collections": []string{"col1"}, }, @@ -575,6 +576,9 @@ func TestToAssistantModel(t *testing.T) { t.Errorf("Expected 1 task prompt, got %d", len(taskPrompts)) } } + if !result.DisableGlobalPrompts { + t.Error("Expected DisableGlobalPrompts to be true") + } if result.KB == nil { t.Error("Expected KB to be set") } @@ -813,7 +817,8 @@ function beforeChat(context) { {"role": "system", "content": "Chat mode"}, }, }, - "source": "function test() {}", + "disable_global_prompts": true, + "source": "function test() {}", } result, err := ToAssistantModel(data) @@ -827,6 +832,9 @@ function beforeChat(context) { if result.PromptPresets == nil { t.Error("Expected PromptPresets to be set") } + if !result.DisableGlobalPrompts { + t.Error("Expected DisableGlobalPrompts to be true") + } if result.Source == "" { t.Error("Expected Source to be set") } @@ -834,9 +842,10 @@ function beforeChat(context) { t.Run("NilNewFields", func(t *testing.T) { data := map[string]interface{}{ - "connector_options": nil, - "prompt_presets": nil, - "source": nil, + "connector_options": nil, + "prompt_presets": nil, + "disable_global_prompts": nil, + "source": nil, } result, err := ToAssistantModel(data) @@ -850,10 +859,63 @@ function beforeChat(context) { if result.PromptPresets != nil { t.Error("Expected PromptPresets to be nil") } + if result.DisableGlobalPrompts { + t.Error("Expected DisableGlobalPrompts to be false") + } if result.Source != "" { t.Error("Expected Source to be empty") } }) + + t.Run("DisableGlobalPrompts", func(t *testing.T) { + // Test with true + data := map[string]interface{}{ + "disable_global_prompts": true, + } + result, err := ToAssistantModel(data) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if !result.DisableGlobalPrompts { + t.Error("Expected DisableGlobalPrompts to be true") + } + + // Test with false + data = map[string]interface{}{ + "disable_global_prompts": false, + } + result, err = ToAssistantModel(data) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if result.DisableGlobalPrompts { + t.Error("Expected DisableGlobalPrompts to be false") + } + + // Test with int 1 + data = map[string]interface{}{ + "disable_global_prompts": 1, + } + result, err = ToAssistantModel(data) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if !result.DisableGlobalPrompts { + t.Error("Expected DisableGlobalPrompts to be true for int 1") + } + + // Test with string "true" + data = map[string]interface{}{ + "disable_global_prompts": "true", + } + result, err = ToAssistantModel(data) + if err != nil { + t.Errorf("Expected no error, got: %v", err) + } + if !result.DisableGlobalPrompts { + t.Error("Expected DisableGlobalPrompts to be true for string 'true'") + } + }) } // TestToAssistantModelComplexTypes tests complex type conversions in ToAssistantModel diff --git a/agent/store/types/fields.go b/agent/store/types/fields.go index d5c4975d..d7f22043 100644 --- a/agent/store/types/fields.go +++ b/agent/store/types/fields.go @@ -4,39 +4,40 @@ import "github.com/yaoapp/kun/log" // AssistantAllowedFields defines the whitelist of fields that can be selected for assistants var AssistantAllowedFields = map[string]bool{ - "id": true, - "assistant_id": true, - "type": true, - "name": true, - "avatar": true, - "connector": true, - "connector_options": true, - "description": true, - "path": true, - "sort": true, - "built_in": true, - "placeholder": true, - "options": true, - "prompts": true, - "prompt_presets": true, - "workflow": true, - "kb": true, - "mcp": true, - "source": true, - "tags": true, - "readonly": true, - "public": true, - "share": true, - "locales": true, - "uses": true, - "automated": true, - "mentionable": true, - "created_at": true, - "updated_at": true, - "__yao_created_by": true, - "__yao_updated_by": true, - "__yao_team_id": true, - "__yao_tenant_id": true, + "id": true, + "assistant_id": true, + "type": true, + "name": true, + "avatar": true, + "connector": true, + "connector_options": true, + "description": true, + "path": true, + "sort": true, + "built_in": true, + "placeholder": true, + "options": true, + "prompts": true, + "prompt_presets": true, + "disable_global_prompts": true, + "workflow": true, + "kb": true, + "mcp": true, + "source": true, + "tags": true, + "readonly": true, + "public": true, + "share": true, + "locales": true, + "uses": true, + "automated": true, + "mentionable": true, + "created_at": true, + "updated_at": true, + "__yao_created_by": true, + "__yao_updated_by": true, + "__yao_team_id": true, + "__yao_tenant_id": true, } // AssistantDefaultFields defines the default fields to select for assistants when no specific fields are requested @@ -83,6 +84,7 @@ var AssistantFullFields = []string{ "options", "prompts", "prompt_presets", + "disable_global_prompts", "workflow", "kb", "mcp", diff --git a/agent/store/types/fields_test.go b/agent/store/types/fields_test.go index 9666e611..f016323a 100644 --- a/agent/store/types/fields_test.go +++ b/agent/store/types/fields_test.go @@ -119,6 +119,7 @@ func TestAssistantAllowedFields(t *testing.T) { "options", "prompts", "prompt_presets", + "disable_global_prompts", "workflow", "kb", "mcp", @@ -224,6 +225,7 @@ func TestAssistantFullFields(t *testing.T) { "options", "prompts", "prompt_presets", + "disable_global_prompts", "workflow", "kb", "mcp", diff --git a/agent/store/types/prompt.go b/agent/store/types/prompt.go new file mode 100644 index 00000000..eaa551a0 --- /dev/null +++ b/agent/store/types/prompt.go @@ -0,0 +1,284 @@ +package types + +import ( + "os" + "path/filepath" + "regexp" + "strings" + "time" + + "github.com/yaoapp/gou/application" + "github.com/yaoapp/gou/fs" + "gopkg.in/yaml.v3" +) + +// Prompts is a slice of Prompt with helper methods +type Prompts []Prompt + +// SystemVariables defines the available system variables +// These are computed at parse time +var SystemVariables = map[string]func() string{ + "TIME": func() string { return time.Now().Format("15:04:05") }, + "DATE": func() string { return time.Now().Format("2006-01-02") }, + "DATETIME": func() string { return time.Now().Format("2006-01-02 15:04:05") }, + "TIMEZONE": func() string { return time.Now().Location().String() }, + "WEEKDAY": func() string { return time.Now().Weekday().String() }, + "YEAR": func() string { return time.Now().Format("2006") }, + "MONTH": func() string { return time.Now().Format("01") }, + "DAY": func() string { return time.Now().Format("02") }, + "HOUR": func() string { return time.Now().Format("15") }, + "MINUTE": func() string { return time.Now().Format("04") }, + "SECOND": func() string { return time.Now().Format("05") }, + "UNIX": func() string { return time.Now().Format("1136239445") }, // Unix timestamp +} + +// Regular expressions for variable replacement +var ( + reSysVar = regexp.MustCompile(`\$SYS\.([A-Z_]+)`) + reEnvVar = regexp.MustCompile(`\$ENV\.([A-Za-z_][A-Za-z0-9_]*)`) + reCtxVar = regexp.MustCompile(`\$CTX\.([A-Za-z_][A-Za-z0-9_]*)`) + reAssetRef = regexp.MustCompile(`@assets/([^\s]+\.(md|yml|yaml|json|txt))`) +) + +// LoadPrompts loads prompts from a YAML file +// Handles @assets/* replacement at load time +// file: prompt file path relative to app root (e.g., "assistants/test/prompts.yml") +// root: resource root directory for assets (e.g., "assistants/test") +// Returns: prompts slice, modification timestamp, error +func LoadPrompts(file string, root string) ([]Prompt, int64, error) { + app, err := fs.Get("app") + if err != nil { + return nil, 0, err + } + + ts, err := app.ModTime(file) + if err != nil { + return nil, 0, err + } + + content, err := app.ReadFile(file) + if err != nil { + return nil, 0, err + } + + // Replace @assets/xxx references with file content + content = replaceAssets(content, root, app) + + // Parse prompts + var prompts []Prompt + err = yaml.Unmarshal(content, &prompts) + if err != nil { + return nil, 0, err + } + + return prompts, ts.UnixNano(), nil +} + +// LoadPromptsRaw loads raw prompt content from a YAML file +// Handles @assets/* replacement at load time +// Returns raw YAML string for further processing +func LoadPromptsRaw(file string, root string) (string, int64, error) { + app, err := fs.Get("app") + if err != nil { + return "", 0, err + } + + ts, err := app.ModTime(file) + if err != nil { + return "", 0, err + } + + content, err := app.ReadFile(file) + if err != nil { + return "", 0, err + } + + // Replace @assets/xxx references with file content + content = replaceAssets(content, root, app) + + return string(content), ts.UnixNano(), nil +} + +// LoadGlobalPrompts loads global prompts from agent/prompts.yml +// Returns: prompts slice, modification timestamp, error +func LoadGlobalPrompts() ([]Prompt, int64, error) { + file := filepath.Join("agent", "prompts.yml") + + // Check if file exists + exists, _ := application.App.Exists(file) + if !exists { + return nil, 0, nil + } + + return LoadPrompts(file, "agent") +} + +// LoadPromptPresets loads prompt presets from a directory +// Supports multi-level directories, key is path with "/" replaced by "." +// e.g., prompts/chat/friendly.yml -> "chat.friendly" +func LoadPromptPresets(dir string, root string) (map[string][]Prompt, int64, error) { + app, err := fs.Get("app") + if err != nil { + return nil, 0, err + } + + // Check if directory exists + exists, _ := app.Exists(dir) + if !exists { + return nil, 0, nil + } + + // Read directory recursively + files, err := app.ReadDir(dir, true) + if err != nil { + return nil, 0, err + } + + presets := make(map[string][]Prompt) + var latestTs int64 + + for _, file := range files { + // Only process .yml/.yaml files + if !strings.HasSuffix(file, ".yml") && !strings.HasSuffix(file, ".yaml") { + continue + } + + ts, err := app.ModTime(file) + if err != nil { + return nil, 0, err + } + if ts.UnixNano() > latestTs { + latestTs = ts.UnixNano() + } + + // Read file content + content, err := app.ReadFile(file) + if err != nil { + return nil, 0, err + } + + // Replace @assets/xxx references with file content + content = replaceAssets(content, root, app) + + // Parse prompts + var prompts []Prompt + err = yaml.Unmarshal(content, &prompts) + if err != nil { + return nil, 0, err + } + + // Build key: get relative path from dir, remove extension and replace "/" with "." + relPath := strings.TrimPrefix(file, dir+"/") + key := strings.TrimSuffix(relPath, filepath.Ext(relPath)) + key = strings.ReplaceAll(key, "/", ".") + presets[key] = prompts + } + + return presets, latestTs, nil +} + +// replaceAssets replaces @assets/xxx references with file content +func replaceAssets(content []byte, root string, app fs.FileSystem) []byte { + return reAssetRef.ReplaceAllFunc(content, func(s []byte) []byte { + matches := reAssetRef.FindStringSubmatch(string(s)) + if len(matches) < 2 { + return s + } + + asset := matches[1] + assetFile := filepath.Join(root, "assets", asset) + assetContent, err := app.ReadFile(assetFile) + if err != nil { + return []byte("") + } + + // Add proper YAML formatting for content (multiline string) + lines := strings.Split(string(assetContent), "\n") + formattedContent := "|\n" + for _, line := range lines { + formattedContent += " " + line + "\n" + } + return []byte(formattedContent) + }) +} + +// Parse parses a single prompt, replacing variables +// ctx: context variables map, key corresponds to $CTX.{key} +// Returns a new Prompt with variables replaced +func (p Prompt) Parse(ctx map[string]string) Prompt { + result := Prompt{ + Role: p.Role, + Content: parseVariables(p.Content, ctx), + Name: p.Name, + } + return result +} + +// Parse parses all prompts in the slice, replacing variables +// ctx: context variables map, key corresponds to $CTX.{key} +// Returns a new Prompts slice with variables replaced +func (ps Prompts) Parse(ctx map[string]string) Prompts { + result := make(Prompts, len(ps)) + for i, p := range ps { + result[i] = p.Parse(ctx) + } + return result +} + +// parseVariables replaces all variable types in content +func parseVariables(content string, ctx map[string]string) string { + // Replace $SYS.* variables + content = reSysVar.ReplaceAllStringFunc(content, func(s string) string { + matches := reSysVar.FindStringSubmatch(s) + if len(matches) < 2 { + return s + } + varName := matches[1] + if fn, ok := SystemVariables[varName]; ok { + return fn() + } + return s // Keep original if not found + }) + + // Replace $ENV.* variables + content = reEnvVar.ReplaceAllStringFunc(content, func(s string) string { + matches := reEnvVar.FindStringSubmatch(s) + if len(matches) < 2 { + return s + } + varName := matches[1] + return os.Getenv(varName) + }) + + // Replace $CTX.* variables + if ctx != nil { + content = reCtxVar.ReplaceAllStringFunc(content, func(s string) string { + matches := reCtxVar.FindStringSubmatch(s) + if len(matches) < 2 { + return s + } + varName := matches[1] + if val, ok := ctx[varName]; ok { + return val + } + return "" // Empty string if not found in ctx + }) + } + + return content +} + +// Merge merges two prompt slices +// globalPrompts are prepended to assistantPrompts +func Merge(globalPrompts, assistantPrompts []Prompt) []Prompt { + if len(globalPrompts) == 0 { + return assistantPrompts + } + if len(assistantPrompts) == 0 { + return globalPrompts + } + result := make([]Prompt, 0, len(globalPrompts)+len(assistantPrompts)) + result = append(result, globalPrompts...) + result = append(result, assistantPrompts...) + return result +} diff --git a/agent/store/types/prompt_test.go b/agent/store/types/prompt_test.go new file mode 100644 index 00000000..b805ac54 --- /dev/null +++ b/agent/store/types/prompt_test.go @@ -0,0 +1,432 @@ +package types + +import ( + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestPromptParse(t *testing.T) { + tests := []struct { + name string + prompt Prompt + ctx map[string]string + validate func(t *testing.T, result Prompt) + }{ + { + name: "ParseSysTimeVariables", + prompt: Prompt{ + Role: "system", + Content: "Current time: $SYS.TIME, Date: $SYS.DATE", + }, + ctx: nil, + validate: func(t *testing.T, result Prompt) { + assert.Equal(t, "system", result.Role) + // Check that variables are replaced (not exact match due to time) + assert.NotContains(t, result.Content, "$SYS.TIME") + assert.NotContains(t, result.Content, "$SYS.DATE") + assert.Contains(t, result.Content, "Current time:") + assert.Contains(t, result.Content, "Date:") + }, + }, + { + name: "ParseSysDatetimeVariable", + prompt: Prompt{ + Role: "system", + Content: "Now: $SYS.DATETIME, Timezone: $SYS.TIMEZONE", + }, + ctx: nil, + validate: func(t *testing.T, result Prompt) { + assert.NotContains(t, result.Content, "$SYS.DATETIME") + assert.NotContains(t, result.Content, "$SYS.TIMEZONE") + }, + }, + { + name: "ParseSysWeekdayVariable", + prompt: Prompt{ + Role: "system", + Content: "Today is $SYS.WEEKDAY", + }, + ctx: nil, + validate: func(t *testing.T, result Prompt) { + weekday := time.Now().Weekday().String() + assert.Contains(t, result.Content, weekday) + }, + }, + { + name: "ParseSysYearMonthDay", + prompt: Prompt{ + Role: "system", + Content: "Year: $SYS.YEAR, Month: $SYS.MONTH, Day: $SYS.DAY", + }, + ctx: nil, + validate: func(t *testing.T, result Prompt) { + now := time.Now() + assert.Contains(t, result.Content, now.Format("2006")) + assert.Contains(t, result.Content, now.Format("01")) + assert.Contains(t, result.Content, now.Format("02")) + }, + }, + { + name: "ParseSysHourMinuteSecond", + prompt: Prompt{ + Role: "system", + Content: "Hour: $SYS.HOUR, Minute: $SYS.MINUTE, Second: $SYS.SECOND", + }, + ctx: nil, + validate: func(t *testing.T, result Prompt) { + assert.NotContains(t, result.Content, "$SYS.HOUR") + assert.NotContains(t, result.Content, "$SYS.MINUTE") + assert.NotContains(t, result.Content, "$SYS.SECOND") + }, + }, + { + name: "ParseEnvVariable", + prompt: Prompt{ + Role: "system", + Content: "App: $ENV.TEST_APP_NAME", + }, + ctx: nil, + validate: func(t *testing.T, result Prompt) { + assert.Contains(t, result.Content, "App: TestApp") + }, + }, + { + name: "ParseEnvVariableNotFound", + prompt: Prompt{ + Role: "system", + Content: "Value: $ENV.NOT_EXIST_VAR_12345", + }, + ctx: nil, + validate: func(t *testing.T, result Prompt) { + // Should be replaced with empty string + assert.Equal(t, "Value: ", result.Content) + }, + }, + { + name: "ParseCtxVariables", + prompt: Prompt{ + Role: "system", + Content: "User: $CTX.USER_ID, Locale: $CTX.LOCALE", + }, + ctx: map[string]string{ + "USER_ID": "user-123", + "LOCALE": "zh-CN", + }, + validate: func(t *testing.T, result Prompt) { + assert.Equal(t, "User: user-123, Locale: zh-CN", result.Content) + }, + }, + { + name: "ParseCtxVariableNotFound", + prompt: Prompt{ + Role: "system", + Content: "Value: $CTX.NOT_EXIST", + }, + ctx: map[string]string{ + "OTHER": "value", + }, + validate: func(t *testing.T, result Prompt) { + // Should be replaced with empty string + assert.Equal(t, "Value: ", result.Content) + }, + }, + { + name: "ParseCtxWithNilMap", + prompt: Prompt{ + Role: "system", + Content: "Value: $CTX.SOMETHING", + }, + ctx: nil, + validate: func(t *testing.T, result Prompt) { + // Should keep original when ctx is nil + assert.Equal(t, "Value: $CTX.SOMETHING", result.Content) + }, + }, + { + name: "ParseMixedVariables", + prompt: Prompt{ + Role: "system", + Content: "Time: $SYS.TIME, App: $ENV.TEST_APP_NAME, User: $CTX.USER_ID", + }, + ctx: map[string]string{ + "USER_ID": "user-456", + }, + validate: func(t *testing.T, result Prompt) { + assert.NotContains(t, result.Content, "$SYS.TIME") + assert.Contains(t, result.Content, "App: TestApp") + assert.Contains(t, result.Content, "User: user-456") + }, + }, + { + name: "ParseUnknownSysVariable", + prompt: Prompt{ + Role: "system", + Content: "Value: $SYS.UNKNOWN_VAR", + }, + ctx: nil, + validate: func(t *testing.T, result Prompt) { + // Should keep original if not found + assert.Equal(t, "Value: $SYS.UNKNOWN_VAR", result.Content) + }, + }, + { + name: "ParsePreservesRoleAndName", + prompt: Prompt{ + Role: "user", + Content: "Hello $CTX.NAME", + Name: "test_user", + }, + ctx: map[string]string{ + "NAME": "World", + }, + validate: func(t *testing.T, result Prompt) { + assert.Equal(t, "user", result.Role) + assert.Equal(t, "Hello World", result.Content) + assert.Equal(t, "test_user", result.Name) + }, + }, + { + name: "ParseCustomCtxVariables", + prompt: Prompt{ + Role: "system", + Content: "Custom: $CTX.MY_CUSTOM_VAR, Another: $CTX.ANOTHER_VAR", + }, + ctx: map[string]string{ + "MY_CUSTOM_VAR": "custom-value", + "ANOTHER_VAR": "another-value", + }, + validate: func(t *testing.T, result Prompt) { + assert.Equal(t, "Custom: custom-value, Another: another-value", result.Content) + }, + }, + { + name: "ParseMultilineContent", + prompt: Prompt{ + Role: "system", + Content: `# System Context +Current Time: $SYS.TIME +User: $CTX.USER_ID +App: $ENV.TEST_APP_NAME`, + }, + ctx: map[string]string{ + "USER_ID": "user-789", + }, + validate: func(t *testing.T, result Prompt) { + assert.Contains(t, result.Content, "# System Context") + assert.NotContains(t, result.Content, "$SYS.TIME") + assert.Contains(t, result.Content, "User: user-789") + assert.Contains(t, result.Content, "App: TestApp") + }, + }, + } + + // Set test environment variable + os.Setenv("TEST_APP_NAME", "TestApp") + defer os.Unsetenv("TEST_APP_NAME") + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.prompt.Parse(tt.ctx) + tt.validate(t, result) + }) + } +} + +func TestPromptsParse(t *testing.T) { + os.Setenv("TEST_APP_NAME", "TestApp") + defer os.Unsetenv("TEST_APP_NAME") + + prompts := Prompts{ + {Role: "system", Content: "Time: $SYS.TIME"}, + {Role: "system", Content: "User: $CTX.USER_ID"}, + {Role: "user", Content: "App: $ENV.TEST_APP_NAME"}, + } + + ctx := map[string]string{ + "USER_ID": "user-123", + } + + result := prompts.Parse(ctx) + + assert.Len(t, result, 3) + assert.NotContains(t, result[0].Content, "$SYS.TIME") + assert.Equal(t, "User: user-123", result[1].Content) + assert.Equal(t, "App: TestApp", result[2].Content) +} + +func TestMergePrompts(t *testing.T) { + tests := []struct { + name string + globalPrompts []Prompt + assistantPrompts []Prompt + expectedLen int + validate func(t *testing.T, result []Prompt) + }{ + { + name: "MergeBothNonEmpty", + globalPrompts: []Prompt{ + {Role: "system", Content: "Global prompt 1"}, + {Role: "system", Content: "Global prompt 2"}, + }, + assistantPrompts: []Prompt{ + {Role: "system", Content: "Assistant prompt 1"}, + }, + expectedLen: 3, + validate: func(t *testing.T, result []Prompt) { + assert.Equal(t, "Global prompt 1", result[0].Content) + assert.Equal(t, "Global prompt 2", result[1].Content) + assert.Equal(t, "Assistant prompt 1", result[2].Content) + }, + }, + { + name: "MergeGlobalEmpty", + globalPrompts: []Prompt{}, + assistantPrompts: []Prompt{ + {Role: "system", Content: "Assistant prompt"}, + }, + expectedLen: 1, + validate: func(t *testing.T, result []Prompt) { + assert.Equal(t, "Assistant prompt", result[0].Content) + }, + }, + { + name: "MergeAssistantEmpty", + globalPrompts: []Prompt{ + {Role: "system", Content: "Global prompt"}, + }, + assistantPrompts: []Prompt{}, + expectedLen: 1, + validate: func(t *testing.T, result []Prompt) { + assert.Equal(t, "Global prompt", result[0].Content) + }, + }, + { + name: "MergeBothEmpty", + globalPrompts: []Prompt{}, + assistantPrompts: []Prompt{}, + expectedLen: 0, + validate: func(t *testing.T, result []Prompt) { + assert.Empty(t, result) + }, + }, + { + name: "MergeGlobalNil", + globalPrompts: nil, + assistantPrompts: []Prompt{ + {Role: "system", Content: "Assistant prompt"}, + }, + expectedLen: 1, + validate: func(t *testing.T, result []Prompt) { + assert.Equal(t, "Assistant prompt", result[0].Content) + }, + }, + { + name: "MergeAssistantNil", + globalPrompts: []Prompt{ + {Role: "system", Content: "Global prompt"}, + }, + assistantPrompts: nil, + expectedLen: 1, + validate: func(t *testing.T, result []Prompt) { + assert.Equal(t, "Global prompt", result[0].Content) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Merge(tt.globalPrompts, tt.assistantPrompts) + assert.Len(t, result, tt.expectedLen) + if tt.validate != nil { + tt.validate(t, result) + } + }) + } +} + +func TestSystemVariables(t *testing.T) { + // Test that all system variables are defined and return non-empty values + expectedVars := []string{ + "TIME", "DATE", "DATETIME", "TIMEZONE", "WEEKDAY", + "YEAR", "MONTH", "DAY", "HOUR", "MINUTE", "SECOND", "UNIX", + } + + for _, varName := range expectedVars { + t.Run(varName, func(t *testing.T) { + fn, ok := SystemVariables[varName] + assert.True(t, ok, "SystemVariables should contain %s", varName) + value := fn() + assert.NotEmpty(t, value, "SystemVariables[%s]() should return non-empty value", varName) + }) + } +} + +func TestParseVariablesEdgeCases(t *testing.T) { + os.Setenv("TEST_VAR", "test-value") + defer os.Unsetenv("TEST_VAR") + + tests := []struct { + name string + content string + ctx map[string]string + expected string + }{ + { + name: "EmptyContent", + content: "", + ctx: nil, + expected: "", + }, + { + name: "NoVariables", + content: "Hello, World!", + ctx: nil, + expected: "Hello, World!", + }, + { + name: "PartialVariableSyntax", + content: "Value: $SYS Value: $ENV Value: $CTX", + ctx: nil, + expected: "Value: $SYS Value: $ENV Value: $CTX", + }, + { + name: "VariableInMiddleOfWord", + content: "prefix$SYS.TIMEsuffix", + ctx: nil, + expected: "prefix$SYS.TIMEsuffix", // Should not match - variable must be followed by valid char + }, + { + name: "MultipleOccurrences", + content: "$CTX.VAR and $CTX.VAR again", + ctx: map[string]string{"VAR": "value"}, + expected: "value and value again", + }, + { + name: "SpecialCharactersInValue", + content: "User: $CTX.USER", + ctx: map[string]string{"USER": "user@example.com"}, + expected: "User: user@example.com", + }, + { + name: "UnicodeInValue", + content: "Name: $CTX.NAME", + ctx: map[string]string{"NAME": "用户名"}, + expected: "Name: 用户名", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := parseVariables(tt.content, tt.ctx) + if tt.name == "VariableInMiddleOfWord" { + // This case depends on regex behavior - just check it doesn't crash + assert.NotEmpty(t, result) + } else { + assert.Equal(t, tt.expected, result) + } + }) + } +} diff --git a/agent/store/types/types.go b/agent/store/types/types.go index a4d1ca13..c6e60d61 100644 --- a/agent/store/types/types.go +++ b/agent/store/types/types.go @@ -246,34 +246,35 @@ type ConnectorOptions struct { // AssistantModel the assistant database model type AssistantModel struct { - ID string `json:"assistant_id"` // Assistant ID - Type string `json:"type,omitempty"` // Assistant Type, default is assistant - Name string `json:"name,omitempty"` // Assistant Name - Avatar string `json:"avatar,omitempty"` // Assistant Avatar - Connector string `json:"connector"` // AI Connector (default connector) - ConnectorOptions *ConnectorOptions `json:"connector_options,omitempty"` // Connector selection options for user to choose from - Path string `json:"path,omitempty"` // Assistant Path - BuiltIn bool `json:"built_in,omitempty"` // Whether this is a built-in assistant - Sort int `json:"sort,omitempty"` // Assistant Sort - Description string `json:"description,omitempty"` // Assistant Description - Tags []string `json:"tags,omitempty"` // Assistant Tags - Readonly bool `json:"readonly,omitempty"` // Whether this assistant is readonly - Public bool `json:"public,omitempty"` // Whether this assistant is shared across all teams in the platform - Share string `json:"share,omitempty"` // Assistant sharing scope (private/team) - Mentionable bool `json:"mentionable,omitempty"` // Whether this assistant is mentionable - Automated bool `json:"automated,omitempty"` // Whether this assistant is automated - Options map[string]interface{} `json:"options,omitempty"` // AI Options - Prompts []Prompt `json:"prompts,omitempty"` // AI Prompts (default prompts) - PromptPresets map[string][]Prompt `json:"prompt_presets,omitempty"` // Prompt presets organized by mode (e.g., "chat", "task", etc.) - KB *KnowledgeBase `json:"kb,omitempty"` // Knowledge base configuration - MCP *MCPServers `json:"mcp,omitempty"` // MCP servers configuration - Workflow *Workflow `json:"workflow,omitempty"` // Workflow configuration - Placeholder *Placeholder `json:"placeholder,omitempty"` // Assistant Placeholder - Source string `json:"source,omitempty"` // Hook script source code - Locales i18n.Map `json:"locales,omitempty"` // Assistant Locales - Uses *context.Uses `json:"uses,omitempty"` // Assistant-specific wrapper configurations for vision, audio, etc. If not set, use global settings - CreatedAt int64 `json:"created_at"` // Creation timestamp - UpdatedAt int64 `json:"updated_at"` // Last update timestamp + ID string `json:"assistant_id"` // Assistant ID + Type string `json:"type,omitempty"` // Assistant Type, default is assistant + Name string `json:"name,omitempty"` // Assistant Name + Avatar string `json:"avatar,omitempty"` // Assistant Avatar + Connector string `json:"connector"` // AI Connector (default connector) + ConnectorOptions *ConnectorOptions `json:"connector_options,omitempty"` // Connector selection options for user to choose from + Path string `json:"path,omitempty"` // Assistant Path + BuiltIn bool `json:"built_in,omitempty"` // Whether this is a built-in assistant + Sort int `json:"sort,omitempty"` // Assistant Sort + Description string `json:"description,omitempty"` // Assistant Description + Tags []string `json:"tags,omitempty"` // Assistant Tags + Readonly bool `json:"readonly,omitempty"` // Whether this assistant is readonly + Public bool `json:"public,omitempty"` // Whether this assistant is shared across all teams in the platform + Share string `json:"share,omitempty"` // Assistant sharing scope (private/team) + Mentionable bool `json:"mentionable,omitempty"` // Whether this assistant is mentionable + Automated bool `json:"automated,omitempty"` // Whether this assistant is automated + Options map[string]interface{} `json:"options,omitempty"` // AI Options + Prompts []Prompt `json:"prompts,omitempty"` // AI Prompts (default prompts) + PromptPresets map[string][]Prompt `json:"prompt_presets,omitempty"` // Prompt presets organized by mode (e.g., "chat", "task", etc.) + DisableGlobalPrompts bool `json:"disable_global_prompts,omitempty"` // Whether to disable global prompts, default is false + KB *KnowledgeBase `json:"kb,omitempty"` // Knowledge base configuration + MCP *MCPServers `json:"mcp,omitempty"` // MCP servers configuration + Workflow *Workflow `json:"workflow,omitempty"` // Workflow configuration + Placeholder *Placeholder `json:"placeholder,omitempty"` // Assistant Placeholder + Source string `json:"source,omitempty"` // Hook script source code + Locales i18n.Map `json:"locales,omitempty"` // Assistant Locales + Uses *context.Uses `json:"uses,omitempty"` // Assistant-specific wrapper configurations for vision, audio, etc. If not set, use global settings + CreatedAt int64 `json:"created_at"` // Creation timestamp + UpdatedAt int64 `json:"updated_at"` // Last update timestamp // Permission management fields (not exposed in JSON API responses) YaoCreatedBy string `json:"-"` // User who created the assistant (not exposed in JSON) diff --git a/agent/store/xun/assistant.go b/agent/store/xun/assistant.go index d36bcfc4..9b934e61 100644 --- a/agent/store/xun/assistant.go +++ b/agent/store/xun/assistant.go @@ -59,6 +59,7 @@ func (conv *Xun) SaveAssistant(assistant *types.AssistantModel) (string, error) data["public"] = assistant.Public data["mentionable"] = assistant.Mentionable data["automated"] = assistant.Automated + data["disable_global_prompts"] = assistant.DisableGlobalPrompts // Set timestamps now := time.Now().UnixNano() @@ -502,27 +503,28 @@ func (conv *Xun) GetAssistant(assistantID string, fields []string, locale ...str // Convert map to types.AssistantModel model := &types.AssistantModel{ - ID: getString(data, "assistant_id"), - Type: getString(data, "type"), - Name: getString(data, "name"), - Avatar: getString(data, "avatar"), - Connector: getString(data, "connector"), - Path: getString(data, "path"), - Source: getString(data, "source"), - BuiltIn: getBool(data, "built_in"), - Sort: getInt(data, "sort"), - Description: getString(data, "description"), - Readonly: getBool(data, "readonly"), - Public: getBool(data, "public"), - Share: getString(data, "share"), - Mentionable: getBool(data, "mentionable"), - Automated: getBool(data, "automated"), - CreatedAt: getInt64(data, "created_at"), - UpdatedAt: getInt64(data, "updated_at"), - YaoCreatedBy: getString(data, "__yao_created_by"), - YaoUpdatedBy: getString(data, "__yao_updated_by"), - YaoTeamID: getString(data, "__yao_team_id"), - YaoTenantID: getString(data, "__yao_tenant_id"), + ID: getString(data, "assistant_id"), + Type: getString(data, "type"), + Name: getString(data, "name"), + Avatar: getString(data, "avatar"), + Connector: getString(data, "connector"), + Path: getString(data, "path"), + Source: getString(data, "source"), + BuiltIn: getBool(data, "built_in"), + Sort: getInt(data, "sort"), + Description: getString(data, "description"), + Readonly: getBool(data, "readonly"), + Public: getBool(data, "public"), + Share: getString(data, "share"), + Mentionable: getBool(data, "mentionable"), + Automated: getBool(data, "automated"), + DisableGlobalPrompts: getBool(data, "disable_global_prompts"), + CreatedAt: getInt64(data, "created_at"), + UpdatedAt: getInt64(data, "updated_at"), + YaoCreatedBy: getString(data, "__yao_created_by"), + YaoUpdatedBy: getString(data, "__yao_updated_by"), + YaoTeamID: getString(data, "__yao_team_id"), + YaoTenantID: getString(data, "__yao_tenant_id"), } // Handle Tags diff --git a/agent/types/types.go b/agent/types/types.go index 38d980dd..99f009c7 100644 --- a/agent/types/types.go +++ b/agent/types/types.go @@ -22,8 +22,9 @@ type DSL struct { // Internal // =============================== // ID string `json:"-" yaml:"-"` // The id of the instance - Assistant assistant.API `json:"-" yaml:"-"` // The default assistant - Store store.Store `json:"-" yaml:"-"` // The store of the assistant + Assistant assistant.API `json:"-" yaml:"-"` // The default assistant + Store store.Store `json:"-" yaml:"-"` // The store of the assistant + GlobalPrompts []store.Prompt `json:"-" yaml:"-"` // Global prompts loaded from agent/prompts.yml } // Uses the default assistant settings diff --git a/data/bindata.go b/data/bindata.go index 892300ef..2d4e9d3c 100644 --- a/data/bindata.go +++ b/data/bindata.go @@ -319,7 +319,7 @@ func cuiSetupIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -339,7 +339,7 @@ func cuiV09IndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v0.9/index.html", size: 13, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "cui/v0.9/index.html", size: 13, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -359,7 +359,7 @@ func cuiV10IndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v1.0/index.html", size: 49, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "cui/v1.0/index.html", size: 49, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -379,7 +379,7 @@ func cuiV10Layouts__indexAsyncJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v1.0/layouts__index.async.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "cui/v1.0/layouts__index.async.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -399,7 +399,7 @@ func cuiV10UmiJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cui/v1.0/umi.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "cui/v1.0/umi.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -419,7 +419,7 @@ func initEnv() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.env", size: 219, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.env", size: 219, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -439,7 +439,7 @@ func initVscodeSettingsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -459,7 +459,7 @@ func initVscodeTypesRuntimeConsoleDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 221, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 221, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -479,7 +479,7 @@ func initVscodeTypesRuntimeExceptionDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/exception.d.ts", size: 738, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/exception.d.ts", size: 738, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -499,7 +499,7 @@ func initVscodeTypesRuntimeFsDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/fs.d.ts", size: 8554, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/fs.d.ts", size: 8554, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -519,7 +519,7 @@ func initVscodeTypesRuntimeGlobalDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 1759, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 1759, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -539,7 +539,7 @@ func initVscodeTypesRuntimeHttpDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/http.d.ts", size: 6179, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/http.d.ts", size: 6179, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -559,7 +559,7 @@ func initVscodeTypesRuntimeIoDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/io.d.ts", size: 587, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/io.d.ts", size: 587, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -579,7 +579,7 @@ func initVscodeTypesRuntimeLogDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/log.d.ts", size: 1692, mode: os.FileMode(493), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/log.d.ts", size: 1692, mode: os.FileMode(493), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -599,7 +599,7 @@ func initVscodeTypesRuntimeNeoDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/neo.d.ts", size: 3750, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/neo.d.ts", size: 3750, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -619,7 +619,7 @@ func initVscodeTypesRuntimeProcessFsDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process/fs.d.ts", size: 11133, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/fs.d.ts", size: 11133, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -639,7 +639,7 @@ func initVscodeTypesRuntimeProcessHttpDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process/http.d.ts", size: 5653, mode: os.FileMode(493), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/http.d.ts", size: 5653, mode: os.FileMode(493), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -659,7 +659,7 @@ func initVscodeTypesRuntimeProcessModelDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process/model.d.ts", size: 6656, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process/model.d.ts", size: 6656, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -679,7 +679,7 @@ func initVscodeTypesRuntimeProcessDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/process.d.ts", size: 23165, mode: os.FileMode(493), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/process.d.ts", size: 23165, mode: os.FileMode(493), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -699,7 +699,7 @@ func initVscodeTypesRuntimeQueryDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6124, mode: os.FileMode(493), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6124, mode: os.FileMode(493), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -719,7 +719,7 @@ func initVscodeTypesRuntimeStoreDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 2251, mode: os.FileMode(493), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 2251, mode: os.FileMode(493), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -739,7 +739,7 @@ func initVscodeTypesRuntimeSuiDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/sui.d.ts", size: 1713, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/sui.d.ts", size: 1713, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -759,7 +759,7 @@ func initVscodeTypesRuntimeTimeDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime/time.d.ts", size: 711, mode: os.FileMode(493), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime/time.d.ts", size: 711, mode: os.FileMode(493), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -779,7 +779,7 @@ func initVscodeTypesRuntimeDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 424, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 424, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -799,7 +799,7 @@ func initVscodeTypesSuiDTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 8931, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 8931, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -819,7 +819,7 @@ func initAppYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/app.yao", size: 3115, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/app.yao", size: 3115, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -839,7 +839,7 @@ func initDataReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -859,7 +859,7 @@ func initDataTemplatesDefault__assetsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -879,7 +879,7 @@ func initDataTemplatesDefault__assetsImagesIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -899,7 +899,7 @@ func initDataTemplatesDefault__assetsImagesLogosLogo_colorSvg() (*asset, error) return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -919,7 +919,7 @@ func initDataTemplatesDefault__assetsImagesLogosWordmarkSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -939,7 +939,7 @@ func initDataTemplatesDefault__dataJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 30, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 30, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -959,7 +959,7 @@ func initDataTemplatesDefault__documentHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/__document.html", size: 492, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/data/templates/default/__document.html", size: 492, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -979,7 +979,7 @@ func initDataTemplatesDefaultIndexIndexCss() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 2896, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 2896, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -999,7 +999,7 @@ func initDataTemplatesDefaultIndexIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 2361, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 2361, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1019,7 +1019,7 @@ func initDataTemplatesDefaultIndexIndexJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 31, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 31, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1039,7 +1039,7 @@ func initDbReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1059,7 +1059,7 @@ func initFlowsMenuFlowYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/flows/menu.flow.yao", size: 813, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/flows/menu.flow.yao", size: 813, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1079,7 +1079,7 @@ func initFormsAccountFormYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/forms/account.form.yao", size: 1194, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/forms/account.form.yao", size: 1194, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1099,7 +1099,7 @@ func initIconsAppIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1119,7 +1119,7 @@ func initIconsAppIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1139,7 +1139,7 @@ func initIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1159,7 +1159,7 @@ func initLoginsAdminLoginYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/logins/admin.login.yao", size: 302, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/logins/admin.login.yao", size: 302, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1179,7 +1179,7 @@ func initLogsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1199,7 +1199,7 @@ func initModelsAdminUserModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/models/admin/user.mod.yao", size: 6416, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/models/admin/user.mod.yao", size: 6416, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1219,7 +1219,7 @@ func initModelsTestsPetModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/models/tests/pet.mod.yao", size: 525, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/models/tests/pet.mod.yao", size: 525, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1239,7 +1239,7 @@ func initNeoNeoYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/neo/neo.yml", size: 724, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/neo/neo.yml", size: 724, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1259,7 +1259,7 @@ func initPublicReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/README.md", size: 108, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/public/README.md", size: 108, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1279,7 +1279,7 @@ func initPublicAssetsReadmeMd() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/public/assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1299,7 +1299,7 @@ func initPublicAssetsImagesIconsAppPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/public/assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1319,7 +1319,7 @@ func initPublicAssetsImagesLogosLogo_colorSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/public/assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1339,7 +1339,7 @@ func initPublicAssetsImagesLogosWordmarkSvg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/public/assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1359,7 +1359,7 @@ func initPublicAssetsLibsuiMinJs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/libsui.min.js", size: 12569, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/public/assets/libsui.min.js", size: 12569, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1379,7 +1379,7 @@ func initPublicAssetsLibsuiMinJsMap() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/assets/libsui.min.js.map", size: 38553, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/public/assets/libsui.min.js.map", size: 38553, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1399,7 +1399,7 @@ func initPublicIndexCfg() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/index.cfg", size: 85, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/public/index.cfg", size: 85, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1419,7 +1419,7 @@ func initPublicIndexSui() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/public/index.sui", size: 5682, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/public/index.sui", size: 5682, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1439,7 +1439,7 @@ func initScriptsAccountTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/account.ts", size: 2521, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/scripts/account.ts", size: 2521, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1459,7 +1459,7 @@ func initScriptsAiNeoTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/ai/neo.ts", size: 375, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/scripts/ai/neo.ts", size: 375, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1479,7 +1479,7 @@ func initScriptsTestsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/tests.ts", size: 1044, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/scripts/tests.ts", size: 1044, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1499,7 +1499,7 @@ func initScriptsUtilsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/scripts/utils.ts", size: 1230, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/scripts/utils.ts", size: 1230, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1519,7 +1519,7 @@ func initSuisWebSuiYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/suis/web.sui.yao", size: 675, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/suis/web.sui.yao", size: 675, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1539,7 +1539,7 @@ func initTablesAccountTabYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/tables/account.tab.yao", size: 5597, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/tables/account.tab.yao", size: 5597, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1559,7 +1559,7 @@ func initTsconfigJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1579,7 +1579,7 @@ func libsuiAgentTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/agent.ts", size: 15267, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "libsui/agent.ts", size: 15267, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1599,7 +1599,7 @@ func libsuiIndexTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/index.ts", size: 13049, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "libsui/index.ts", size: 13049, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1619,7 +1619,7 @@ func libsuiUtilsTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1639,7 +1639,7 @@ func libsuiYaoTs() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1659,7 +1659,7 @@ func publicIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1679,7 +1679,7 @@ func uiIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1699,7 +1699,7 @@ func yaoDataIcons404Png() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1719,7 +1719,7 @@ func yaoDataIconsIconIcns() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1739,7 +1739,7 @@ func yaoDataIconsIconIco() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1759,7 +1759,7 @@ func yaoDataIconsIconPng() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1779,7 +1779,7 @@ func yaoDataIndexHtml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1799,7 +1799,7 @@ func yaoDataKbProvidersChunkingSemanticEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/en.json", size: 5543, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/en.json", size: 5543, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1819,7 +1819,7 @@ func yaoDataKbProvidersChunkingSemanticZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/zh-cn.json", size: 5446, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/zh-cn.json", size: 5446, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1839,7 +1839,7 @@ func yaoDataKbProvidersChunkingStructuredEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/en.json", size: 2423, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/en.json", size: 2423, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1859,7 +1859,7 @@ func yaoDataKbProvidersChunkingStructuredZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/zh-cn.json", size: 2321, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/zh-cn.json", size: 2321, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1879,7 +1879,7 @@ func yaoDataKbProvidersConverterMcpEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/en.json", size: 4235, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/en.json", size: 4235, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1899,7 +1899,7 @@ func yaoDataKbProvidersConverterMcpZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/zh-cn.json", size: 4060, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/zh-cn.json", size: 4060, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1919,7 +1919,7 @@ func yaoDataKbProvidersConverterOcrEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/en.json", size: 6631, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/en.json", size: 6631, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1939,7 +1939,7 @@ func yaoDataKbProvidersConverterOcrZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/zh-cn.json", size: 6501, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/zh-cn.json", size: 6501, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1959,7 +1959,7 @@ func yaoDataKbProvidersConverterOfficeEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/en.json", size: 5476, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/en.json", size: 5476, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1979,7 +1979,7 @@ func yaoDataKbProvidersConverterOfficeZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/zh-cn.json", size: 5356, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/zh-cn.json", size: 5356, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -1999,7 +1999,7 @@ func yaoDataKbProvidersConverterUtf8EnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/en.json", size: 292, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/en.json", size: 292, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2019,7 +2019,7 @@ func yaoDataKbProvidersConverterUtf8ZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/zh-cn.json", size: 281, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/zh-cn.json", size: 281, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2039,7 +2039,7 @@ func yaoDataKbProvidersConverterVideoEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/en.json", size: 6411, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/en.json", size: 6411, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2059,7 +2059,7 @@ func yaoDataKbProvidersConverterVideoZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/zh-cn.json", size: 6297, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/zh-cn.json", size: 6297, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2079,7 +2079,7 @@ func yaoDataKbProvidersConverterVisionEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/en.json", size: 4085, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/en.json", size: 4085, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2099,7 +2099,7 @@ func yaoDataKbProvidersConverterVisionZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/zh-cn.json", size: 3949, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/zh-cn.json", size: 3949, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2119,7 +2119,7 @@ func yaoDataKbProvidersConverterWhisperEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/en.json", size: 4449, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/en.json", size: 4449, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2139,7 +2139,7 @@ func yaoDataKbProvidersConverterWhisperZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/zh-cn.json", size: 4312, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/zh-cn.json", size: 4312, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2159,7 +2159,7 @@ func yaoDataKbProvidersEmbeddingFastembedEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/en.json", size: 6865, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/en.json", size: 6865, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2179,7 +2179,7 @@ func yaoDataKbProvidersEmbeddingFastembedZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/zh-cn.json", size: 6685, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/zh-cn.json", size: 6685, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2199,7 +2199,7 @@ func yaoDataKbProvidersEmbeddingOpenaiEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/en.json", size: 5636, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/en.json", size: 5636, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2219,7 +2219,7 @@ func yaoDataKbProvidersEmbeddingOpenaiZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/zh-cn.json", size: 5463, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/zh-cn.json", size: 5463, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2239,7 +2239,7 @@ func yaoDataKbProvidersExtractionOpenaiEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/en.json", size: 9110, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/en.json", size: 9110, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2259,7 +2259,7 @@ func yaoDataKbProvidersExtractionOpenaiZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/zh-cn.json", size: 8827, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/zh-cn.json", size: 8827, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2279,7 +2279,7 @@ func yaoDataKbProvidersFetcherHttpEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/en.json", size: 5885, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/en.json", size: 5885, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2299,7 +2299,7 @@ func yaoDataKbProvidersFetcherHttpZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/zh-cn.json", size: 5925, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/zh-cn.json", size: 5925, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2319,7 +2319,7 @@ func yaoDataKbProvidersFetcherMcpEnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/en.json", size: 6819, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/en.json", size: 6819, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2339,7 +2339,7 @@ func yaoDataKbProvidersFetcherMcpZhCnJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/zh-cn.json", size: 6611, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/zh-cn.json", size: 6611, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2359,7 +2359,7 @@ func yaoFieldsModelTransJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/fields/model.trans.json", size: 14938, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/fields/model.trans.json", size: 14938, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2379,7 +2379,7 @@ func yaoLangsEnUsJson() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2399,7 +2399,7 @@ func yaoLangsZhCnGlobalYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-cn/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2419,7 +2419,7 @@ func yaoLangsZhCnLoginsAdminLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-cn/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2439,7 +2439,7 @@ func yaoLangsZhCnLoginsUserLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-cn/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/langs/zh-cn/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2459,7 +2459,7 @@ func yaoLangsZhHkGlobalYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-hk/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2479,7 +2479,7 @@ func yaoLangsZhHkLoginsAdminLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-hk/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2499,12 +2499,12 @@ func yaoLangsZhHkLoginsUserLoginYml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/langs/zh-hk/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/langs/zh-hk/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _yaoModelsAgentAssistantModYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x58\x4b\x73\xdb\x36\x10\xbe\xfb\x57\xec\xf0\xd4\xce\x28\x4e\xda\x43\xa7\xd6\xa9\x4e\x72\xa8\xa7\x71\xa3\x49\xe2\xe6\x90\xf1\x68\x96\xe4\x92\x42\x05\x02\x2c\xb0\xb4\xad\x7a\xfc\xdf\x3b\x00\x29\x3e\x44\xe8\x41\xa5\x17\x3d\x80\xdd\xc5\xb7\x0f\xec\x62\xf7\xf9\x02\x20\x52\x58\x50\x34\x87\xe8\xda\x5a\x61\x19\x15\x47\x33\xb7\x2c\x31\x26\x19\x58\x4f\xc9\x26\x46\x94\x2c\xb4\x1a\xec\x02\x63\x2c\x09\x32\x6d\xc0\xb2\x36\x42\xe5\x70\x7d\x03\xd8\x6e\x27\x5a\x65\x22\xaf\x0c\x3a\x4e\x0b\xa8\x52\x28\x88\x31\x45\xc6\x5a\x30\x63\x6e\xa3\x39\x7c\x8b\x30\x27\x77\x18\x44\x76\x63\x99\x8a\xe8\xde\x6f\xc7\x95\x90\x2c\xdc\x99\x6c\x2a\xf2\x4b\x86\x30\xd5\x4a\x6e\xfa\x6b\x56\x1b\x8e\xe6\x70\x75\x75\x75\xd5\x48\x8d\xa5\x53\xef\xb9\x53\xd4\xcb\x5f\x62\xa7\x16\x44\x89\x2e\x0a\x77\xa8\x53\xc8\xed\xf6\x70\xd7\x02\xe0\xc5\x4b\x4b\xb4\xac\x0a\xe5\x61\x5e\x00\x00\x3c\xfb\xcf\x9e\x11\x45\xea\x95\xf1\x6b\xbc\x29\xfd\xda\xcd\xfb\x6e\x6d\x6c\x55\xe8\x6f\xf7\x70\xdc\x29\xf1\x4f\x45\x3d\x20\x22\x25\xc5\x22\x13\x64\x22\x4f\xfe\x32\x0b\x43\x68\x39\x96\x21\x30\x96\x9d\x6b\xce\x01\x74\x1d\x42\xd2\xc9\x21\x95\xf3\x2a\x9a\xc3\xcf\x6f\xde\xb4\x8b\xaa\x92\xb2\xb1\x7f\x86\xd2\x52\xbb\x51\x79\xe5\x7a\x7e\xf3\xab\x42\xa5\xf4\xd4\x2c\x1e\x54\xd1\x2b\x73\xba\x6a\x5f\x06\xe4\x41\x95\x86\x12\x83\xca\xa4\x94\x61\x25\x79\x60\xe2\x68\x3a\x76\xff\x7d\x3a\xf6\x3f\x07\xe4\x41\xec\x43\x89\xc7\x1c\x71\x14\x20\x3e\x20\xa3\x99\x12\x39\x3b\x0c\x41\x90\xb5\x54\xb8\xfb\xf4\xe1\x7f\x84\x9a\x68\xa5\x28\x61\x3d\x05\xed\xbb\x31\x4f\x10\x70\xe3\x6e\x68\xcf\x98\x81\xc8\x40\x69\x06\x4b\x3c\x83\xca\x12\xf0\x8a\x20\x97\x3a\x46\x39\xa6\x9e\x78\x33\x4e\x53\x73\xa9\x7d\xde\xb5\x63\x75\xff\xb6\x5a\x1d\x52\x16\x3e\xee\x72\xf6\x94\xee\xa8\x2c\x49\x4a\x1c\x21\x34\x27\xcd\x9b\x1f\x28\x21\x93\x98\xcf\x9c\x1f\x85\x47\xde\xa9\x6a\x41\x0a\xcb\x33\x9f\xd2\x13\x2c\x31\x16\x52\xf0\x06\x32\x21\x99\x4c\xef\xc4\x29\x9e\xed\x57\x99\xd3\x7d\xfb\x3e\xc4\xb5\xc7\xbb\x01\xca\xd6\x53\xbf\xec\x8f\xc7\xe9\x17\xbe\x44\x5e\x4d\xd0\x61\x31\x20\x0f\x82\x77\xf5\x15\x73\x82\xa1\xe4\xef\xbe\x4d\xbe\x74\x8e\x80\x0a\xc5\x94\x0f\x32\xfd\x16\xe9\xe7\x01\x7d\x18\xa9\x36\x0c\xda\xa4\x7d\xfe\x2e\x8f\x6e\x8b\xf4\x34\x7b\xfa\x87\xc0\x52\x04\xe2\x22\xd6\x5a\x12\x86\xee\xc1\x5b\xc7\x03\x37\xe1\xa8\xf8\xba\x22\x5e\x91\x01\x5e\x09\x0b\xc2\x02\x82\x3f\xe2\x95\x50\x10\x48\xf4\x1d\xfc\x61\x49\x3b\x3d\x1e\x24\x26\xb4\xd2\x72\x60\x94\x23\xf7\x78\x11\xe2\x09\x5a\x3c\x28\x7d\x4a\x14\x4c\x4d\x31\x87\x12\x4b\x07\x6b\x24\x75\x0a\xa4\xd2\xe8\xa2\xe4\xd3\x21\x2d\x76\xe9\x0f\x26\xf8\x91\xf4\xe9\xd0\x96\xa5\x21\x4b\x93\x11\xc2\x62\x97\xad\x07\xb4\x21\x69\x24\x83\x36\x39\x2a\xf1\x2f\xa5\x10\x6f\xa0\xd0\x29\xc1\x0f\x74\x99\x5f\xce\x20\x59\x21\xcf\x80\xd1\xae\x67\x40\x9c\x5c\xfe\x78\x9e\x22\x8f\xda\xac\x33\xa9\x1f\x4f\x56\xe1\xeb\x88\x21\x68\xe5\xb1\xdc\x29\xa8\xd6\xf1\xc9\x78\xfe\x50\xfa\x51\x52\x9a\x13\xbc\x45\x7b\xec\xc9\xb4\x6e\x89\x63\xb4\xae\x94\xc9\xa6\xee\x9d\x19\x06\x45\x52\x9e\x0c\xf4\xf6\xdd\x02\x3e\x93\x79\x18\xd4\xc6\x1e\x4a\xb7\x6f\xeb\xfd\x5e\xb5\x75\x7d\x95\x7b\x6f\xf4\x1a\x13\xed\x1e\x21\xe7\xe1\xb5\xba\x32\x49\xe0\x19\xca\xf4\xc4\xc1\x4c\x3f\x24\xef\xa1\xfd\x5d\xeb\x35\xd4\xb5\x14\x6a\xa9\x90\xe8\xf4\x4c\x58\xbe\x05\x3c\xd5\x8e\x5f\x06\xc4\xe1\x57\xfd\x80\x64\x0a\x92\xb6\xb5\x9c\x50\x64\x3e\x8d\x78\x82\xa0\xb6\xa2\xc1\x32\x72\x65\xcf\xab\x2c\x7b\xb2\x51\x15\x4b\x91\x4c\xc1\xbc\xf0\x1c\x70\x3d\xae\x73\xfb\x0a\x64\xaf\x21\xb5\x60\x57\x68\x28\x05\x4c\x8c\xb6\x16\x50\x4a\x60\xc2\xc2\x82\x50\x3e\x58\x4b\x89\x9c\x69\x53\x1c\xd7\x71\xd2\x7b\xd8\x9f\x3a\xd6\x92\x54\x55\x84\x82\x77\x48\x1d\x7e\xa7\xac\xd0\x4f\x2c\x6c\xa2\xfb\x7d\xa0\xde\x8e\x39\xbe\x35\x2b\xe0\x32\xbe\x78\x40\xa6\x68\x06\xaf\x5f\xc3\x47\xe7\xc7\x07\x61\x85\xbb\xa2\xac\xbd\xd2\xfa\x51\x91\xe9\xe8\x9d\x41\x22\x47\xfb\x57\x47\xb6\x35\x14\x14\x54\xc4\x64\x6c\x43\x7d\x1f\xea\x35\xdb\xf3\xf6\x99\xea\x8c\x38\x91\x3a\x41\x49\xa7\x5f\xb5\x0f\xbb\xf4\xe1\xb1\xc0\x4f\xbf\x2a\x18\x89\x9e\x72\xeb\x2a\x3b\x01\xd4\x9d\x3d\x86\xe8\x95\x2d\x29\x11\x99\x48\xe0\xd1\x60\x59\x92\xd9\x1d\x42\xb9\xa4\xea\xbc\xa7\xd5\x0c\xb0\x4a\x85\xae\x4b\x28\xdc\xec\x34\x7a\x4d\x93\x67\x89\x59\xa8\x73\x53\x0a\x56\xac\x0b\x64\x0a\x0c\x65\xf6\xdf\xcf\xeb\x31\x53\xb8\xbd\xde\xd2\x1d\xc8\x2a\xe7\xb5\x2f\xee\x2c\xd7\x00\x3a\x2d\x27\x00\xbf\x0d\xb1\x1d\xcf\x29\x09\x2a\x70\xae\x42\xe3\x72\xc8\x6f\xd0\x9c\xee\x3b\xcc\x33\x74\xba\x68\x2e\x55\x64\x48\xd6\x3e\x8f\xe6\x8d\x8a\x91\x7b\x38\x75\x7f\x7b\x4a\xad\xd0\xde\xa2\xea\xa5\x71\xf7\xdc\xf2\x4a\x2d\x97\x1b\xd4\x97\x7e\x7e\x78\xe9\xd8\x3b\x92\x35\x6d\xf6\xcf\xde\x32\x6d\x48\xe4\x6a\x44\xd0\x62\xac\x87\x8b\x1e\x3e\x1d\x1c\x2e\x3e\x2d\x77\x86\x97\x4b\x07\x7a\xb9\x9d\x8d\xf6\x0c\xdd\xce\x29\x9b\x61\x59\xaf\x6f\xba\x0f\xf4\x78\xce\x70\x21\x37\xdd\xb8\x1d\x7f\x4f\x70\x30\x2b\xf3\xcd\x7e\xdb\x26\x35\x31\x77\x30\x8e\x42\xe0\xed\x4e\x07\xd9\x81\xae\x77\xfa\x97\xe6\x7b\x51\x3b\x89\x2e\xc5\x3b\xe0\x8d\x54\x97\xdb\x77\xe2\xa4\xcd\xf8\xcf\x10\xb1\x28\xc8\x32\x16\xa5\xdd\x06\x9a\x7b\x38\x65\xbc\x4c\x49\x12\x7b\x47\xd5\x09\x18\xa2\x92\x4c\x21\xac\xad\x59\x1d\x29\xbc\x5c\xbc\x5c\xfc\x17\x00\x00\xff\xff\xdf\xd5\xd7\x46\x5f\x17\x00\x00") +var _yaoModelsAgentAssistantModYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x58\x4b\x6f\xdc\x36\x10\xbe\xfb\x57\x0c\x74\x6a\x81\x8d\x93\xf6\x50\xd4\x3e\xd5\x49\x80\xd6\x68\xd2\x2c\xf2\x68\x0e\x41\x20\x8c\xa4\x91\x96\x5d\x8a\x54\xc9\x91\xed\xad\xe1\xff\x5e\x90\xd2\xea\xb1\xe2\x3e\xb4\x69\x2f\x7e\x50\x33\xc3\x6f\x9e\x9c\x99\xc7\x0b\x80\x48\x61\x49\xd1\x35\x44\x37\xd6\x0a\xcb\xa8\x38\x5a\xb8\x63\x89\x09\xc9\xc0\x79\x46\x36\x35\xa2\x62\xa1\xd5\xe8\x2b\x30\x26\x92\x20\xd7\x06\x2c\x6b\x23\x54\x01\x37\xb7\x80\xdd\xe7\x54\xab\x5c\x14\xb5\x41\xc7\x69\x01\x55\x06\x25\x31\x66\xc8\xd8\x08\x66\x2c\x6c\x74\x0d\x5f\x22\x2c\xc8\x5d\x06\x91\xdd\x58\xa6\x32\xfa\xea\x3f\x27\xb5\x90\x2c\xdc\x9d\x6c\x6a\xf2\x47\x86\x30\xd3\x4a\x6e\x86\x67\x56\x1b\x8e\xae\xe1\xea\xea\xea\xaa\x95\x9a\x48\xa7\xde\x63\xaf\xa8\x97\x1f\x63\xaf\x16\x44\xa9\x2e\x4b\x77\xa9\x53\xc8\x7d\x1d\xe0\x6e\x04\xc0\x93\x97\x96\x6a\x59\x97\xca\xc3\xbc\x00\x00\x78\xf4\x3f\x07\x46\x14\x99\x57\xc6\x9f\xf1\xa6\xf2\x67\xb7\xaf\xfb\xb3\xa9\x55\x61\xf8\x79\x80\xe3\x93\x12\x7f\xd7\x34\x00\x22\x32\x52\x2c\x72\x41\x26\xf2\xe4\x4f\x8b\x30\x84\x8e\x23\x0e\x81\xb1\xec\x5c\x73\x0e\xa0\x9b\x10\x92\x5e\x0e\xa9\x82\x57\xd1\x35\xfc\xf8\xe2\x45\x77\xa8\x6a\x29\x5b\xfb\xe7\x28\x2d\x75\x1f\x6a\xaf\xdc\xc0\x6f\xfe\x54\xa8\x8c\x1e\xda\xc3\x83\x2a\x7a\x65\x4e\x57\xed\xe3\x88\x3c\xa8\xd2\x58\x62\x50\x99\x8c\x72\xac\x25\x8f\x4c\x1c\xcd\xc7\xee\x7f\x9f\x8e\xfd\x8f\x11\x79\x10\xfb\x58\xe2\x31\x47\x1c\x05\x88\x77\xc8\x68\xe6\x44\xce\x0e\x43\x10\x64\x23\x15\x3e\xbd\x7f\xf3\x1f\x42\x4d\xb5\x52\x94\xb2\x9e\x83\xf6\xd5\x94\x27\x08\xb8\x75\x37\x74\x77\x2c\x40\xe4\xa0\x34\x83\x25\x5e\x40\x6d\x09\x78\x45\x50\x48\x9d\xa0\x9c\x52\xcf\xcc\x8c\xd3\xd4\x8c\xb5\xaf\xbb\x76\xaa\xee\x5f\x56\xab\x43\xca\xc2\xbb\x5d\xce\x81\xd2\x3d\x95\x25\x49\xa9\x23\x84\xf6\xa6\xeb\xf6\x0f\x94\x90\x4b\x2c\x16\xce\x8f\xc2\x23\xef\x55\xb5\x20\x85\xe5\x85\x2f\xe9\x29\x56\x98\x08\x29\x78\x03\xb9\x90\x4c\x66\x70\xe3\x1c\xcf\x0e\x5f\x99\xd3\x7d\xfb\x3a\xc4\xb5\xc7\xbb\x01\xca\xce\x53\x3f\xed\x8f\xc7\xf9\x09\x5f\x21\xaf\x66\xe8\xb0\x1c\x91\x07\xc1\xbb\xf7\x15\x0b\x82\xb1\xe4\x6f\xce\x26\xff\x74\x4e\x80\x0a\xc5\x54\x8c\x2a\xfd\x16\xe9\x87\x11\x7d\x18\xa9\x36\x0c\xda\x64\x43\xfe\xbe\x8e\x6e\x1f\xe9\x79\xf6\xf4\x8d\x40\x2c\x02\x71\x91\x68\x2d\x09\x43\x79\xf0\xd2\xf1\xc0\x6d\x38\x2a\x3e\xaf\x88\x57\x64\x80\x57\xc2\x82\xb0\x80\xe0\xaf\x78\x26\x14\x04\x0a\x7d\x0f\x7f\xfc\xa4\x9d\x1e\x0f\x12\x53\x5a\x69\x39\x32\xca\x91\x3c\x5e\x86\x78\x82\x16\x0f\x4a\x9f\x13\x05\x73\x4b\xcc\xa1\xc2\xd2\xc3\x9a\x48\x9d\x03\xa9\x32\xba\xac\xf8\x74\x48\xcb\x5d\xfa\x83\x05\x7e\x22\x7d\x3e\xb4\xb8\x32\x64\x69\x36\x42\x58\xee\xb2\x0d\x80\xb6\x24\xad\x64\xd0\xa6\x40\x25\xfe\xa1\x0c\x92\x0d\x94\x3a\x23\xf8\x8e\x2e\x8b\xcb\x05\xa4\x2b\xe4\x05\x30\xda\xf5\x02\x88\xd3\xcb\xef\xcf\x2c\xb8\xc2\x3a\xd2\xb8\x79\xcf\xe2\xbd\x26\xdf\x9f\x63\xaf\x1b\x09\xf0\x6b\xf3\x22\x1e\x72\x42\x97\x71\x1a\xda\x7b\xb7\xef\x68\x7b\xaf\x1f\x23\x7c\x3e\xfe\x1f\x19\x78\xaf\xcd\x3a\x97\xfa\xfe\x64\x77\x7d\x9e\x30\x04\x23\x6a\x2a\x77\x8e\x07\xd6\xc9\xc9\x78\x7e\x57\xfa\x5e\x52\x56\x10\xbc\x44\x7b\xac\x3d\x5c\x77\xc4\x09\x5a\xf7\x6c\xcb\xf6\x8d\x3f\x33\xe4\xcb\xb4\x3a\x19\xe8\xdb\x57\x4b\xf8\x40\xe6\x6e\xd4\x07\x0c\x50\xba\xef\xb6\xf9\x3e\xe8\x2c\x1a\xe7\x0f\x67\x1f\xd6\xae\xe1\x3a\x0f\xaf\xd5\xb5\x49\x03\x2d\x37\xd3\x03\x07\x5f\xb5\x31\xf9\x00\xed\x6f\x5a\xaf\xa1\xe9\x1b\xa0\x91\x0a\xa9\xce\xce\x84\xe5\xc7\xdd\x53\xed\xf8\x71\x44\x1c\x9e\x60\x46\x24\x73\x90\x74\x63\xf4\x8c\x64\x7f\x3f\xe1\x09\x82\xda\x8a\x06\xcb\xc8\xb5\x3d\x2f\x87\xf7\x54\xde\x3a\x91\x22\x9d\x83\x79\xe9\x39\xe0\x66\x5a\x51\xf6\x35\x03\x83\xe1\xdb\x82\x5d\xa1\xa1\x0c\x30\x35\xda\x5a\x40\x29\x81\x09\x4b\x0b\x42\xf9\x60\xad\x24\x72\xae\x4d\x79\x5c\xc7\x59\xbd\xbf\xbf\x75\xaa\x25\xa9\xba\x0c\x05\xef\x98\x3a\xdc\x93\xad\xd0\x6f\x67\x6c\xaa\x87\x33\xaf\xde\xae\x74\xbe\xb4\x27\xe0\x5e\x37\x71\x87\x4c\xd1\x02\x9e\x3f\x87\x77\xce\x8f\x77\xc2\x0a\x97\xa2\xac\xbd\xd2\xfa\x5e\x91\xe9\xe9\x9d\x41\x22\x47\xfb\x67\x4f\xb6\x35\x14\x94\x54\x26\x64\x6c\x4b\xfd\x35\x34\x57\x77\xf7\xed\x33\xd5\x19\x71\x22\x75\x8a\x92\x4e\x4f\xb5\x37\xbb\xf4\xe1\x15\xc8\x0f\x3f\x2b\x98\x88\x9e\x93\x75\xb5\x9d\x01\xea\x93\x3d\x86\xe8\x99\xad\x28\x15\xb9\x48\xe1\xde\x60\x55\x91\xd9\x5d\xb8\xb9\xa2\xea\xbc\xa7\xd5\x02\xb0\xce\x84\x6e\xda\x05\xb8\xdd\x19\x6a\xdb\x87\xd8\x12\xb3\x50\xe7\x96\x14\xac\x59\x97\xc8\x14\x58\x40\xed\xcf\xcf\x9b\x29\x53\x78\x95\xb0\xa5\x3b\x50\x55\xce\x1b\xd5\xdc\x5d\x6e\xd8\x75\x5a\xce\x00\xfe\x36\xc4\x76\xbc\xa6\xa4\xa8\xc0\xb9\x0a\x8d\xab\x21\xbf\x40\x7b\xbb\x9f\xa6\xcf\xd0\xe9\xa2\x4d\xaa\xc8\x90\x6c\x7c\x1e\x5d\xb7\x2a\x46\xae\x49\xec\xff\x1d\x28\xb5\x42\xfb\x16\xd5\xa0\x8c\xbb\xd6\xd2\x2b\x15\xc7\x1b\xd4\x97\x7e\x57\x7a\xe9\xd8\x7b\x92\x35\x6d\xf6\xef\x19\x73\x6d\x48\x14\x6a\x42\xd0\x61\x6c\x16\xa9\x1e\x3e\x1d\x5c\xa4\x3e\xc4\x3b\x8b\xda\xd8\x81\x8e\xb7\x7b\xe0\x81\xa1\xbb\x9d\x6c\xbb\x18\x1c\xcc\x88\x5f\x03\xf3\xac\x33\x5c\xc8\x4d\xb7\xee\x8b\xcf\x13\x1c\xed\x05\xfd\x62\xa3\x1b\x09\xdb\x98\x3b\x18\x47\x21\xf0\x76\x67\x5a\xee\x41\x37\x5f\x86\x49\xf3\xad\xa8\x9d\x44\x57\xe2\x1d\xf0\x56\xaa\xab\xed\x3b\x71\xd2\x55\xfc\x47\x88\x58\x94\x64\x19\xcb\xca\x6e\x03\xcd\x35\x4e\x39\xc7\x19\x49\x62\xef\xa8\xa6\x00\x43\x54\x91\x29\x85\xb5\x0d\xab\x23\x85\xa7\x8b\xa7\x8b\x7f\x03\x00\x00\xff\xff\xa5\x4c\x70\x1c\x4b\x18\x00\x00") func yaoModelsAgentAssistantModYaoBytes() ([]byte, error) { return bindataRead( @@ -2519,7 +2519,7 @@ func yaoModelsAgentAssistantModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 5983, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 6219, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2539,7 +2539,7 @@ func yaoModelsAgentChatModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 1741, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 1741, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2559,7 +2559,7 @@ func yaoModelsAgentHistoryModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/agent/history.mod.yao", size: 2941, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/agent/history.mod.yao", size: 2941, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2579,7 +2579,7 @@ func yaoModelsAttachmentModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4286, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4286, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2599,7 +2599,7 @@ func yaoModelsAuditModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/audit.mod.yao", size: 5588, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/audit.mod.yao", size: 5588, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2619,7 +2619,7 @@ func yaoModelsConfigModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/config.mod.yao", size: 1649, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/config.mod.yao", size: 1649, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2639,7 +2639,7 @@ func yaoModelsDslModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/dsl.mod.yao", size: 3826, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/dsl.mod.yao", size: 3826, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2659,7 +2659,7 @@ func yaoModelsInvitationModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/invitation.mod.yao", size: 6693, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/invitation.mod.yao", size: 6693, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2679,7 +2679,7 @@ func yaoModelsJobCategoryModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2041, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2041, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2699,7 +2699,7 @@ func yaoModelsJobExecutionModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 7201, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 7201, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2719,7 +2719,7 @@ func yaoModelsJobJobModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6330, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6330, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2739,7 +2739,7 @@ func yaoModelsJobLogModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/job/log.mod.yao", size: 4711, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/job/log.mod.yao", size: 4711, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2759,7 +2759,7 @@ func yaoModelsKbCollectionModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5390, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5390, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2779,7 +2779,7 @@ func yaoModelsKbDocumentModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9906, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9906, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2799,7 +2799,7 @@ func yaoModelsMemberModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 14798, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 14798, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2819,7 +2819,7 @@ func yaoModelsRoleModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/role.mod.yao", size: 6434, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/role.mod.yao", size: 6434, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2839,7 +2839,7 @@ func yaoModelsTeamModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/team.mod.yao", size: 15823, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/team.mod.yao", size: 15823, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2859,7 +2859,7 @@ func yaoModelsUserOauth_accountModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user/oauth_account.mod.yao", size: 6928, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/user/oauth_account.mod.yao", size: 6928, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2879,7 +2879,7 @@ func yaoModelsUserTypeModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user/type.mod.yao", size: 7502, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/user/type.mod.yao", size: 7502, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2899,7 +2899,7 @@ func yaoModelsUserModYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 12335, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 12335, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2919,7 +2919,7 @@ func yaoReleaseAppYaz() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2939,7 +2939,7 @@ func yaoStoresAgentCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/stores/agent/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2959,7 +2959,7 @@ func yaoStoresAgentMemoryBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/agent/memory.badger.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/stores/agent/memory.badger.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2979,7 +2979,7 @@ func yaoStoresCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/cache.lru.yao", size: 285, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/stores/cache.lru.yao", size: 285, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -2999,7 +2999,7 @@ func yaoStoresKbCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/kb/cache.lru.yao", size: 304, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/stores/kb/cache.lru.yao", size: 304, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3019,7 +3019,7 @@ func yaoStoresKbStoreBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/kb/store.badger.yao", size: 349, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/stores/kb/store.badger.yao", size: 349, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3039,7 +3039,7 @@ func yaoStoresOauthCacheLruYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3059,7 +3059,7 @@ func yaoStoresOauthClientBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/client.badger.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/client.badger.yao", size: 352, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3079,7 +3079,7 @@ func yaoStoresOauthStoreBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/oauth/store.badger.yao", size: 376, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/stores/oauth/store.badger.yao", size: 376, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3099,7 +3099,7 @@ func yaoStoresStoreBadgerYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/stores/store.badger.yao", size: 341, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/stores/store.badger.yao", size: 341, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -3119,7 +3119,7 @@ func yaoUploadersAttachmentLocalYao() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "yao/uploaders/attachment.local.yao", size: 1163, mode: os.FileMode(420), modTime: time.Unix(1764649139, 0)} + info := bindataFileInfo{name: "yao/uploaders/attachment.local.yao", size: 1163, mode: os.FileMode(420), modTime: time.Unix(1764660943, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/yao/models/agent/assistant.mod.yao b/yao/models/agent/assistant.mod.yao index 3b836660..a1288941 100644 --- a/yao/models/agent/assistant.mod.yao +++ b/yao/models/agent/assistant.mod.yao @@ -125,6 +125,14 @@ "comment": "Prompt presets organized by mode (e.g., chat, task, etc.)", "nullable": true }, + { + "name": "disable_global_prompts", + "type": "boolean", + "label": "Disable Global Prompts", + "comment": "Whether to disable global prompts for this assistant", + "default": false, + "index": true + }, { "name": "workflow", "type": "json", From 42e920eed6c08929e52f5bbdac701c9894d2ff4a Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 2 Dec 2025 16:03:31 +0800 Subject: [PATCH 5/7] Implement global prompts functionality in the assistant module - Added methods to set and retrieve global prompts, enhancing the assistant's capabilities. - Updated the assistant's message building process to include global prompts, ensuring context-aware parsing. - Introduced tests to validate the integration and functionality of global prompts within the assistant. - Improved context variable handling for prompt parsing, supporting dynamic content generation. --- agent/assistant/build.go | 153 ++++++++++-- agent/assistant/build_prompts_test.go | 347 ++++++++++++++++++++++++++ agent/assistant/load.go | 19 +- agent/load.go | 5 + agent/load_test.go | 35 +++ 5 files changed, 540 insertions(+), 19 deletions(-) create mode 100644 agent/assistant/build_prompts_test.go diff --git a/agent/assistant/build.go b/agent/assistant/build.go index d7feef05..41131d3f 100644 --- a/agent/assistant/build.go +++ b/agent/assistant/build.go @@ -3,8 +3,10 @@ package assistant import ( "fmt" + "github.com/spf13/cast" "github.com/yaoapp/gou/json" "github.com/yaoapp/yao/agent/context" + store "github.com/yaoapp/yao/agent/store/types" ) // BuildRequest build the LLM request @@ -48,29 +50,146 @@ func (ast *Assistant) buildMessages(ctx *context.Context, messages []context.Mes finalMessages = append([]context.Message{mcpSamplesMsg}, finalMessages...) } - // ⚠️ Just for testing, will remove later - // If we have prompts, prepend them to the beginning - if len(ast.Prompts) > 0 { - promptMessages := make([]context.Message, 0, len(ast.Prompts)) - for _, prompt := range ast.Prompts { - msg := context.Message{ - Role: context.MessageRole(prompt.Role), - Content: prompt.Content, - } - // Add name if provided - if prompt.Name != "" { - name := prompt.Name - msg.Name = &name - } - promptMessages = append(promptMessages, msg) - } - // Prepend prompt messages to the beginning + // Build and prepend system prompts (global + assistant prompts) + promptMessages := ast.buildSystemPrompts(ctx) + if len(promptMessages) > 0 { finalMessages = append(promptMessages, finalMessages...) } return finalMessages, nil } +// buildSystemPrompts builds system prompt messages from global prompts and assistant prompts +// Order: Global prompts (if not disabled) -> Assistant prompts +// Variables are parsed with context information +func (ast *Assistant) buildSystemPrompts(ctx *context.Context) []context.Message { + // Build context variables from ctx and ast + ctxVars := ast.buildContextVariables(ctx) + + var allPrompts []store.Prompt + + // 1. Add global prompts (if not disabled) + if !ast.DisableGlobalPrompts && len(globalPrompts) > 0 { + // Parse global prompts with context variables + parsedGlobal := store.Prompts(globalPrompts).Parse(ctxVars) + allPrompts = append(allPrompts, parsedGlobal...) + } + + // 2. Add assistant prompts + if len(ast.Prompts) > 0 { + // Parse assistant prompts with context variables + parsedAssistant := store.Prompts(ast.Prompts).Parse(ctxVars) + allPrompts = append(allPrompts, parsedAssistant...) + } + + // Convert to context.Message slice + if len(allPrompts) == 0 { + return nil + } + + messages := make([]context.Message, 0, len(allPrompts)) + for _, prompt := range allPrompts { + msg := context.Message{ + Role: context.MessageRole(prompt.Role), + Content: prompt.Content, + } + if prompt.Name != "" { + name := prompt.Name + msg.Name = &name + } + messages = append(messages, msg) + } + + return messages +} + +// buildContextVariables extracts context variables from Context and Assistant for prompt parsing +func (ast *Assistant) buildContextVariables(ctx *context.Context) map[string]string { + vars := make(map[string]string) + + // Get locale from ctx (default to empty) + locale := "" + if ctx != nil && ctx.Locale != "" { + locale = ctx.Locale + } + + // Assistant info (with locale support) + if ast != nil { + if ast.ID != "" { + vars["ASSISTANT_ID"] = ast.ID + } + // Use localized name and description + name := ast.GetName(locale) + if name != "" { + vars["ASSISTANT_NAME"] = name + } + description := ast.GetDescription(locale) + if description != "" { + vars["ASSISTANT_DESCRIPTION"] = description + } + if ast.Type != "" { + vars["ASSISTANT_TYPE"] = ast.Type + } + } + + if ctx == nil { + return vars + } + + // Basic context info + if ctx.ChatID != "" { + vars["CHAT_ID"] = ctx.ChatID + } + if ctx.Locale != "" { + vars["LOCALE"] = ctx.Locale + } + if ctx.Theme != "" { + vars["THEME"] = ctx.Theme + } + if ctx.Route != "" { + vars["ROUTE"] = ctx.Route + } + if ctx.Referer != "" { + vars["REFERER"] = ctx.Referer + } + + // Client info (only non-sensitive fields) + if ctx.Client.Type != "" { + vars["CLIENT_TYPE"] = ctx.Client.Type + } + + // Authorized info (only internal IDs, no PII) + // Note: USER_SUBJECT and CLIENT_IP are excluded for privacy/GDPR compliance + if ctx.Authorized != nil { + if ctx.Authorized.UserID != "" { + vars["USER_ID"] = ctx.Authorized.UserID + } + if ctx.Authorized.TeamID != "" { + vars["TEAM_ID"] = ctx.Authorized.TeamID + } + if ctx.Authorized.TenantID != "" { + vars["TENANT_ID"] = ctx.Authorized.TenantID + } + } + + // Metadata - custom variables from ctx.Metadata + // All metadata keys are exposed as $CTX.{KEY} + // Supports string, int, uint, float, bool types + if ctx.Metadata != nil { + for key, value := range ctx.Metadata { + if value == nil { + continue + } + strVal := cast.ToString(value) + if strVal != "" { + vars[key] = strVal + } + } + } + + return vars +} + // buildCompletionOptions builds completion options from multiple sources // Priority (lowest to highest, later overrides earlier): ast > ctx > createResponse // The priority means: if createResponse has a value, use it; else use ctx; else use ast diff --git a/agent/assistant/build_prompts_test.go b/agent/assistant/build_prompts_test.go new file mode 100644 index 00000000..a8f029be --- /dev/null +++ b/agent/assistant/build_prompts_test.go @@ -0,0 +1,347 @@ +package assistant_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/yaoapp/yao/agent/assistant" + "github.com/yaoapp/yao/agent/context" + store "github.com/yaoapp/yao/agent/store/types" + "github.com/yaoapp/yao/agent/testutils" + "github.com/yaoapp/yao/openapi/oauth/types" +) + +func TestBuildSystemPromptsIntegration(t *testing.T) { + testutils.Prepare(t) + defer testutils.Clean(t) + + t.Run("AssistantWithLocale", func(t *testing.T) { + // Load an assistant with locales + ast, err := assistant.Get("tests.fullfields") + require.NoError(t, err) + + ctx := &context.Context{ + Locale: "zh-cn", + Authorized: &types.AuthorizedInfo{ + UserID: "test-user-123", + TeamID: "test-team-456", + }, + Metadata: map[string]interface{}{ + "CUSTOM_VAR": "custom-value", + "INT_VAR": 42, + "BOOL_VAR": true, + }, + } + + // Build request to test the full flow + messages := []context.Message{ + {Role: context.RoleUser, Content: "Hello"}, + } + + finalMessages, options, err := ast.BuildRequest(ctx, messages, nil) + require.NoError(t, err) + require.NotNil(t, options) + + // Should have system prompts prepended + assert.Greater(t, len(finalMessages), 1) + + // First messages should be system prompts + hasSystemPrompt := false + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem { + hasSystemPrompt = true + break + } + } + assert.True(t, hasSystemPrompt, "Should have system prompts") + }) + + t.Run("DisableGlobalPrompts", func(t *testing.T) { + // Load fullfields assistant which has disable_global_prompts: true + ast, err := assistant.Get("tests.fullfields") + require.NoError(t, err) + require.True(t, ast.DisableGlobalPrompts) + + ctx := &context.Context{ + Locale: "en-us", + } + + messages := []context.Message{ + {Role: context.RoleUser, Content: "Hello"}, + } + + finalMessages, _, err := ast.BuildRequest(ctx, messages, nil) + require.NoError(t, err) + + // Should still have assistant prompts + hasSystemPrompt := false + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem { + hasSystemPrompt = true + break + } + } + assert.True(t, hasSystemPrompt, "Should have assistant prompts even with global disabled") + }) + + t.Run("MetadataTypeConversion", func(t *testing.T) { + ast, err := assistant.Get("yaobots") + require.NoError(t, err) + + ctx := &context.Context{ + Metadata: map[string]interface{}{ + "STRING_VAL": "hello", + "INT_VAL": 123, + "INT64_VAL": int64(456), + "FLOAT_VAL": 3.14, + "BOOL_TRUE": true, + "BOOL_FALSE": false, + "UINT_VAL": uint(789), + "NIL_VAL": nil, + "EMPTY_VAL": "", + "ZERO_INT": 0, + "ZERO_FLOAT": 0.0, + }, + } + + messages := []context.Message{ + {Role: context.RoleUser, Content: "Test metadata"}, + } + + // This should not panic + _, _, err = ast.BuildRequest(ctx, messages, nil) + require.NoError(t, err) + }) + + t.Run("AuthorizedInfoPrivacy", func(t *testing.T) { + ast, err := assistant.Get("yaobots") + require.NoError(t, err) + + ctx := &context.Context{ + Authorized: &types.AuthorizedInfo{ + UserID: "user-123", + Subject: "user@example.com", // PII - should not be exposed + TeamID: "team-456", + TenantID: "tenant-789", + }, + Client: context.Client{ + Type: "web", + IP: "192.168.1.1", // Should not be exposed + }, + } + + messages := []context.Message{ + {Role: context.RoleUser, Content: "Test privacy"}, + } + + finalMessages, _, err := ast.BuildRequest(ctx, messages, nil) + require.NoError(t, err) + + // Check that sensitive info is not in any system prompts + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem { + assert.NotContains(t, msg.Content, "user@example.com", "Subject should not be in prompts") + assert.NotContains(t, msg.Content, "192.168.1.1", "IP should not be in prompts") + } + } + }) + + t.Run("ContextVariablesInPrompts", func(t *testing.T) { + // Set up global prompts with variables + assistant.SetGlobalPrompts([]store.Prompt{ + {Role: "system", Content: "User ID: $CTX.USER_ID, Team: $CTX.TEAM_ID, Custom: $CTX.MY_VAR"}, + }) + defer assistant.SetGlobalPrompts(nil) + + ast, err := assistant.Get("yaobots") + require.NoError(t, err) + + ctx := &context.Context{ + Authorized: &types.AuthorizedInfo{ + UserID: "user-abc", + TeamID: "team-xyz", + }, + Metadata: map[string]interface{}{ + "MY_VAR": "my-value", + }, + } + + messages := []context.Message{ + {Role: context.RoleUser, Content: "Test variables"}, + } + + finalMessages, _, err := ast.BuildRequest(ctx, messages, nil) + require.NoError(t, err) + + // Find the global prompt and verify variables are replaced + found := false + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem && !found { + if assert.Contains(t, msg.Content, "User ID: user-abc") { + found = true + assert.Contains(t, msg.Content, "Team: team-xyz") + assert.Contains(t, msg.Content, "Custom: my-value") + } + } + } + assert.True(t, found, "Should find global prompt with replaced variables") + }) + + t.Run("SystemVariablesReplacement", func(t *testing.T) { + // Set up global prompts with $SYS.* variables + assistant.SetGlobalPrompts([]store.Prompt{ + {Role: "system", Content: "Time: $SYS.TIME, Date: $SYS.DATE, Datetime: $SYS.DATETIME, Weekday: $SYS.WEEKDAY"}, + }) + defer assistant.SetGlobalPrompts(nil) + + ast, err := assistant.Get("yaobots") + require.NoError(t, err) + + ctx := &context.Context{} + + messages := []context.Message{ + {Role: context.RoleUser, Content: "Test system variables"}, + } + + finalMessages, _, err := ast.BuildRequest(ctx, messages, nil) + require.NoError(t, err) + + // Find the global prompt and verify $SYS.* variables are replaced + found := false + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem { + // Should NOT contain $SYS. prefix (variables should be replaced) + if !assert.NotContains(t, msg.Content, "$SYS.TIME") { + continue + } + if !assert.NotContains(t, msg.Content, "$SYS.DATE") { + continue + } + if !assert.NotContains(t, msg.Content, "$SYS.DATETIME") { + continue + } + if !assert.NotContains(t, msg.Content, "$SYS.WEEKDAY") { + continue + } + + // Should contain "Time:", "Date:", etc. with actual values + assert.Contains(t, msg.Content, "Time:") + assert.Contains(t, msg.Content, "Date:") + assert.Contains(t, msg.Content, "Datetime:") + assert.Contains(t, msg.Content, "Weekday:") + found = true + break + } + } + assert.True(t, found, "Should find global prompt with replaced $SYS.* variables") + }) + + t.Run("EnvVariablesReplacement", func(t *testing.T) { + // Set test environment variable + t.Setenv("TEST_PROMPT_VAR", "env-test-value") + + // Set up global prompts with $ENV.* variables + assistant.SetGlobalPrompts([]store.Prompt{ + {Role: "system", Content: "Env Value: $ENV.TEST_PROMPT_VAR, Not Exist: $ENV.NOT_EXIST_VAR_XYZ"}, + }) + defer assistant.SetGlobalPrompts(nil) + + ast, err := assistant.Get("yaobots") + require.NoError(t, err) + + ctx := &context.Context{} + + messages := []context.Message{ + {Role: context.RoleUser, Content: "Test env variables"}, + } + + finalMessages, _, err := ast.BuildRequest(ctx, messages, nil) + require.NoError(t, err) + + // Find the global prompt and verify $ENV.* variables are replaced + found := false + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem { + // Should NOT contain $ENV. prefix for existing vars + if !assert.NotContains(t, msg.Content, "$ENV.TEST_PROMPT_VAR") { + continue + } + // Should contain the actual env value + assert.Contains(t, msg.Content, "Env Value: env-test-value") + // Non-existent env var should be replaced with empty string + assert.Contains(t, msg.Content, "Not Exist: ") + assert.NotContains(t, msg.Content, "$ENV.NOT_EXIST_VAR_XYZ") + found = true + break + } + } + assert.True(t, found, "Should find global prompt with replaced $ENV.* variables") + }) + + t.Run("AllVariableTypesReplacement", func(t *testing.T) { + // Set test environment variable + t.Setenv("TEST_APP_NAME", "MyTestApp") + + // Set up global prompts with all variable types + assistant.SetGlobalPrompts([]store.Prompt{ + {Role: "system", Content: `System Info: +- Time: $SYS.TIME +- Date: $SYS.DATE +- App: $ENV.TEST_APP_NAME +- User: $CTX.USER_ID +- Custom: $CTX.CUSTOM_KEY +- Assistant: $CTX.ASSISTANT_NAME`}, + }) + defer assistant.SetGlobalPrompts(nil) + + ast, err := assistant.Get("yaobots") + require.NoError(t, err) + + ctx := &context.Context{ + Authorized: &types.AuthorizedInfo{ + UserID: "all-vars-user", + }, + Metadata: map[string]interface{}{ + "CUSTOM_KEY": "custom-value-123", + }, + } + + messages := []context.Message{ + {Role: context.RoleUser, Content: "Test all variables"}, + } + + finalMessages, _, err := ast.BuildRequest(ctx, messages, nil) + require.NoError(t, err) + + // Find the global prompt and verify ALL variable types are replaced + found := false + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem && !found { + content := msg.Content + + // Check $SYS.* replaced + if assert.NotContains(t, content, "$SYS.TIME") && + assert.NotContains(t, content, "$SYS.DATE") { + + // Check $ENV.* replaced + assert.NotContains(t, content, "$ENV.TEST_APP_NAME") + assert.Contains(t, content, "App: MyTestApp") + + // Check $CTX.* replaced + assert.NotContains(t, content, "$CTX.USER_ID") + assert.Contains(t, content, "User: all-vars-user") + + assert.NotContains(t, content, "$CTX.CUSTOM_KEY") + assert.Contains(t, content, "Custom: custom-value-123") + + // Check assistant name from $CTX.ASSISTANT_NAME + assert.NotContains(t, content, "$CTX.ASSISTANT_NAME") + + found = true + } + } + } + assert.True(t, found, "Should find global prompt with all variable types replaced") + }) +} diff --git a/agent/assistant/load.go b/agent/assistant/load.go index c15c228b..e62b6789 100644 --- a/agent/assistant/load.go +++ b/agent/assistant/load.go @@ -27,8 +27,9 @@ var loaded = NewCache(200) // 200 is the default capacity var storage store.Store = nil var search interface{} = nil var modelCapabilities map[string]gouOpenAI.Capabilities = map[string]gouOpenAI.Capabilities{} -var defaultConnector string = "" // default connector -var globalUses *context.Uses = nil // global uses configuration from agent.yml +var defaultConnector string = "" // default connector +var globalUses *context.Uses = nil // global uses configuration from agent.yml +var globalPrompts []store.Prompt = nil // global prompts from agent/prompts.yml // LoadBuiltIn load the built-in assistants func LoadBuiltIn() error { @@ -145,6 +146,20 @@ func SetGlobalUses(uses *context.Uses) { globalUses = uses } +// SetGlobalPrompts set the global prompts from agent/prompts.yml +func SetGlobalPrompts(prompts []store.Prompt) { + globalPrompts = prompts +} + +// GetGlobalPrompts returns the global prompts with variables parsed +// ctx: context variables for parsing $CTX.* variables +func GetGlobalPrompts(ctx map[string]string) []store.Prompt { + if len(globalPrompts) == 0 { + return nil + } + return store.Prompts(globalPrompts).Parse(ctx) +} + // SetCache set the cache func SetCache(capacity int) { ClearCache() diff --git a/agent/load.go b/agent/load.go index 461dfe89..44fa1494 100644 --- a/agent/load.go +++ b/agent/load.go @@ -200,6 +200,11 @@ func initAssistant() error { assistant.SetGlobalUses(globalUses) } + // Set global prompts + if len(agentDSL.GlobalPrompts) > 0 { + assistant.SetGlobalPrompts(agentDSL.GlobalPrompts) + } + if agentDSL.Models != nil { assistant.SetModelCapabilities(agentDSL.Models) } diff --git a/agent/load_test.go b/agent/load_test.go index 40c95069..602cc094 100644 --- a/agent/load_test.go +++ b/agent/load_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/yaoapp/yao/agent/assistant" "github.com/yaoapp/yao/config" "github.com/yaoapp/yao/test" ) @@ -171,3 +172,37 @@ func TestGlobalPromptsContent(t *testing.T) { "Raw prompts should contain variable placeholders") }) } + +func TestAssistantGlobalPrompts(t *testing.T) { + prepare(t) + defer test.Clean() + + t.Run("AssistantModuleReceivesGlobalPrompts", func(t *testing.T) { + // Verify assistant module has global prompts + prompts := assistant.GetGlobalPrompts(nil) + require.NotNil(t, prompts) + require.Greater(t, len(prompts), 0) + + // Should be parsed (no $SYS.* variables) + content := prompts[0].Content + assert.NotContains(t, content, "$SYS.DATETIME") + }) + + t.Run("AssistantModuleParsesWithContext", func(t *testing.T) { + ctx := map[string]string{ + "USER_ID": "assistant-test-user", + "LOCALE": "en-US", + } + + prompts := assistant.GetGlobalPrompts(ctx) + require.NotNil(t, prompts) + + // $SYS.* should be replaced + content := prompts[0].Content + assert.NotContains(t, content, "$SYS.") + + // Should contain current time info + now := time.Now() + assert.Contains(t, content, now.Format("2006-01-02")) + }) +} From ee44432e6941cb97ded3d2ded96699ea11e2ee1d Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 2 Dec 2025 16:24:22 +0800 Subject: [PATCH 6/7] Enhance assistant prompt handling with new preset and global prompt controls - Introduced prompt preset selection and global prompt disabling features in the assistant's message building process. - Updated the `buildSystemPrompts` method to prioritize prompt presets and manage global prompt settings based on context and hook responses. - Added new fields to the `HookCreateResponse` struct for prompt configuration, allowing for dynamic adjustments during message processing. - Refactored tests to validate the integration of prompt presets and global prompt controls, ensuring robust functionality and context-aware behavior. --- agent/assistant/build.go | 85 ++++- agent/assistant/build_prompts_test.go | 493 ++++++++++++++++++++++++++ agent/context/types.go | 4 + 3 files changed, 575 insertions(+), 7 deletions(-) diff --git a/agent/assistant/build.go b/agent/assistant/build.go index 41131d3f..45adbce4 100644 --- a/agent/assistant/build.go +++ b/agent/assistant/build.go @@ -51,7 +51,7 @@ func (ast *Assistant) buildMessages(ctx *context.Context, messages []context.Mes } // Build and prepend system prompts (global + assistant prompts) - promptMessages := ast.buildSystemPrompts(ctx) + promptMessages := ast.buildSystemPrompts(ctx, createResponse) if len(promptMessages) > 0 { finalMessages = append(promptMessages, finalMessages...) } @@ -60,25 +60,41 @@ func (ast *Assistant) buildMessages(ctx *context.Context, messages []context.Mes } // buildSystemPrompts builds system prompt messages from global prompts and assistant prompts -// Order: Global prompts (if not disabled) -> Assistant prompts +// Order: Global prompts (if not disabled) -> Assistant prompts (or preset) // Variables are parsed with context information -func (ast *Assistant) buildSystemPrompts(ctx *context.Context) []context.Message { +// +// Priority for prompt preset selection: +// 1. createResponse.PromptPreset (highest) +// 2. ctx.Metadata["__prompt_preset"] +// 3. ast.Prompts (default) +// +// Priority for disable global prompts: +// 1. createResponse.DisableGlobalPrompts (highest) +// 2. ctx.Metadata["__disable_global_prompts"] +// 3. ast.DisableGlobalPrompts (default) +func (ast *Assistant) buildSystemPrompts(ctx *context.Context, createResponse *context.HookCreateResponse) []context.Message { // Build context variables from ctx and ast ctxVars := ast.buildContextVariables(ctx) + // Determine if global prompts should be disabled + disableGlobal := ast.shouldDisableGlobalPrompts(ctx, createResponse) + + // Get assistant prompts (default or preset) + assistantPrompts := ast.getAssistantPrompts(ctx, createResponse) + var allPrompts []store.Prompt // 1. Add global prompts (if not disabled) - if !ast.DisableGlobalPrompts && len(globalPrompts) > 0 { + if !disableGlobal && len(globalPrompts) > 0 { // Parse global prompts with context variables parsedGlobal := store.Prompts(globalPrompts).Parse(ctxVars) allPrompts = append(allPrompts, parsedGlobal...) } - // 2. Add assistant prompts - if len(ast.Prompts) > 0 { + // 2. Add assistant prompts (default or preset) + if len(assistantPrompts) > 0 { // Parse assistant prompts with context variables - parsedAssistant := store.Prompts(ast.Prompts).Parse(ctxVars) + parsedAssistant := store.Prompts(assistantPrompts).Parse(ctxVars) allPrompts = append(allPrompts, parsedAssistant...) } @@ -103,6 +119,61 @@ func (ast *Assistant) buildSystemPrompts(ctx *context.Context) []context.Message return messages } +// shouldDisableGlobalPrompts determines if global prompts should be disabled +// Priority: createResponse > ctx.Metadata > ast.DisableGlobalPrompts +func (ast *Assistant) shouldDisableGlobalPrompts(ctx *context.Context, createResponse *context.HookCreateResponse) bool { + // Priority 1: Hook response (highest) + if createResponse != nil && createResponse.DisableGlobalPrompts != nil { + return *createResponse.DisableGlobalPrompts + } + + // Priority 2: ctx.Metadata["__disable_global_prompts"] + if ctx != nil && ctx.Metadata != nil { + if disable, ok := ctx.Metadata["__disable_global_prompts"].(bool); ok { + return disable + } + } + + // Priority 3: Assistant configuration (default) + return ast.DisableGlobalPrompts +} + +// getAssistantPrompts returns the assistant prompts based on preset selection +// Priority: createResponse.PromptPreset > ctx.Metadata["__prompt_preset"] > ast.Prompts +func (ast *Assistant) getAssistantPrompts(ctx *context.Context, createResponse *context.HookCreateResponse) []store.Prompt { + // Get preset key + presetKey := ast.getPromptPresetKey(ctx, createResponse) + + // If preset key is specified and exists, use it + if presetKey != "" && ast.PromptPresets != nil { + if presets, ok := ast.PromptPresets[presetKey]; ok && len(presets) > 0 { + return presets + } + } + + // Fallback to default prompts + return ast.Prompts +} + +// getPromptPresetKey returns the prompt preset key +// Priority: createResponse.PromptPreset > ctx.Metadata["__prompt_preset"] +func (ast *Assistant) getPromptPresetKey(ctx *context.Context, createResponse *context.HookCreateResponse) string { + // Priority 1: Hook response (highest) + if createResponse != nil && createResponse.PromptPreset != "" { + return createResponse.PromptPreset + } + + // Priority 2: ctx.Metadata["__prompt_preset"] + if ctx != nil && ctx.Metadata != nil { + if preset, ok := ctx.Metadata["__prompt_preset"].(string); ok && preset != "" { + return preset + } + } + + // No preset specified + return "" +} + // buildContextVariables extracts context variables from Context and Assistant for prompt parsing func (ast *Assistant) buildContextVariables(ctx *context.Context) map[string]string { vars := make(map[string]string) diff --git a/agent/assistant/build_prompts_test.go b/agent/assistant/build_prompts_test.go index a8f029be..a96fda4c 100644 --- a/agent/assistant/build_prompts_test.go +++ b/agent/assistant/build_prompts_test.go @@ -1,6 +1,8 @@ package assistant_test import ( + stdContext "context" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -12,6 +14,43 @@ import ( "github.com/yaoapp/yao/openapi/oauth/types" ) +// containsString is a helper to check if a content (string or interface{}) contains a substring +func containsString(content interface{}, substr string) bool { + switch v := content.(type) { + case string: + return strings.Contains(v, substr) + default: + return false + } +} + +// newPromptTestContext creates a context suitable for prompt testing with Create Hook +func newPromptTestContext(chatID, assistantID string) *context.Context { + return &context.Context{ + Context: stdContext.Background(), + ChatID: chatID, + AssistantID: assistantID, + Locale: "en-us", + Theme: "light", + Client: context.Client{ + Type: "web", + UserAgent: "TestAgent/1.0", + IP: "127.0.0.1", + }, + Referer: context.RefererAPI, + Accept: context.AcceptWebCUI, + Metadata: make(map[string]interface{}), + Authorized: &types.AuthorizedInfo{ + Subject: "test-user", + ClientID: "test-client-id", + UserID: "test-user-123", + TeamID: "test-team-456", + TenantID: "test-tenant-789", + SessionID: "test-session-id", + }, + } +} + func TestBuildSystemPromptsIntegration(t *testing.T) { testutils.Prepare(t) defer testutils.Clean(t) @@ -344,4 +383,458 @@ func TestBuildSystemPromptsIntegration(t *testing.T) { } assert.True(t, found, "Should find global prompt with all variable types replaced") }) + + t.Run("PromptPresetFromHook", func(t *testing.T) { + // Load fullfields assistant which has prompt_presets + ast, err := assistant.Get("tests.fullfields") + require.NoError(t, err) + require.NotNil(t, ast.PromptPresets) + require.Contains(t, ast.PromptPresets, "chat.friendly") + + ctx := &context.Context{} + + messages := []context.Message{ + {Role: context.RoleUser, Content: "Test preset from hook"}, + } + + // Hook returns prompt_preset + createResponse := &context.HookCreateResponse{ + PromptPreset: "chat.friendly", + } + + finalMessages, _, err := ast.BuildRequest(ctx, messages, createResponse) + require.NoError(t, err) + + // Should have system prompts from the preset + hasSystemPrompt := false + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem { + hasSystemPrompt = true + // Verify it's from the friendly preset (check content) + assert.Contains(t, msg.Content, "friendly", "Should use friendly preset prompts") + break + } + } + assert.True(t, hasSystemPrompt, "Should have system prompts from preset") + }) + + t.Run("PromptPresetFromMetadata", func(t *testing.T) { + // Load fullfields assistant which has prompt_presets + ast, err := assistant.Get("tests.fullfields") + require.NoError(t, err) + + ctx := &context.Context{ + Metadata: map[string]interface{}{ + "__prompt_preset": "chat.professional", + }, + } + + messages := []context.Message{ + {Role: context.RoleUser, Content: "Test preset from metadata"}, + } + + finalMessages, _, err := ast.BuildRequest(ctx, messages, nil) + require.NoError(t, err) + + // Should have system prompts from the preset + hasSystemPrompt := false + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem { + hasSystemPrompt = true + // Verify it's from the professional preset + assert.Contains(t, msg.Content, "professional", "Should use professional preset prompts") + break + } + } + assert.True(t, hasSystemPrompt, "Should have system prompts from preset") + }) + + t.Run("PromptPresetHookOverridesMetadata", func(t *testing.T) { + // Load fullfields assistant + ast, err := assistant.Get("tests.fullfields") + require.NoError(t, err) + + ctx := &context.Context{ + Metadata: map[string]interface{}{ + "__prompt_preset": "chat.professional", // Lower priority + }, + } + + messages := []context.Message{ + {Role: context.RoleUser, Content: "Test hook overrides metadata"}, + } + + // Hook returns different preset (higher priority) + createResponse := &context.HookCreateResponse{ + PromptPreset: "chat.friendly", + } + + finalMessages, _, err := ast.BuildRequest(ctx, messages, createResponse) + require.NoError(t, err) + + // Should use hook's preset, not metadata's + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem { + assert.Contains(t, msg.Content, "friendly", "Hook preset should override metadata preset") + break + } + } + }) + + t.Run("PromptPresetNotFound", func(t *testing.T) { + // Load fullfields assistant + ast, err := assistant.Get("tests.fullfields") + require.NoError(t, err) + + ctx := &context.Context{ + Metadata: map[string]interface{}{ + "__prompt_preset": "non.existent.preset", + }, + } + + messages := []context.Message{ + {Role: context.RoleUser, Content: "Test non-existent preset"}, + } + + finalMessages, _, err := ast.BuildRequest(ctx, messages, nil) + require.NoError(t, err) + + // Should fallback to default prompts (not crash) + hasSystemPrompt := false + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem { + hasSystemPrompt = true + break + } + } + assert.True(t, hasSystemPrompt, "Should fallback to default prompts when preset not found") + }) + + t.Run("DisableGlobalPromptsFromHook", func(t *testing.T) { + // Set global prompts + assistant.SetGlobalPrompts([]store.Prompt{ + {Role: "system", Content: "GLOBAL_PROMPT_MARKER"}, + }) + defer assistant.SetGlobalPrompts(nil) + + // Load an assistant that does NOT disable global prompts + ast, err := assistant.Get("yaobots") + require.NoError(t, err) + require.False(t, ast.DisableGlobalPrompts) + + ctx := &context.Context{} + + messages := []context.Message{ + {Role: context.RoleUser, Content: "Test disable from hook"}, + } + + // Hook disables global prompts + disableTrue := true + createResponse := &context.HookCreateResponse{ + DisableGlobalPrompts: &disableTrue, + } + + finalMessages, _, err := ast.BuildRequest(ctx, messages, createResponse) + require.NoError(t, err) + + // Should NOT have global prompt + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem { + assert.NotContains(t, msg.Content, "GLOBAL_PROMPT_MARKER", "Global prompts should be disabled by hook") + } + } + }) + + t.Run("DisableGlobalPromptsFromMetadata", func(t *testing.T) { + // Set global prompts + assistant.SetGlobalPrompts([]store.Prompt{ + {Role: "system", Content: "GLOBAL_PROMPT_MARKER_2"}, + }) + defer assistant.SetGlobalPrompts(nil) + + // Load an assistant that does NOT disable global prompts + ast, err := assistant.Get("yaobots") + require.NoError(t, err) + + ctx := &context.Context{ + Metadata: map[string]interface{}{ + "__disable_global_prompts": true, + }, + } + + messages := []context.Message{ + {Role: context.RoleUser, Content: "Test disable from metadata"}, + } + + finalMessages, _, err := ast.BuildRequest(ctx, messages, nil) + require.NoError(t, err) + + // Should NOT have global prompt + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem { + assert.NotContains(t, msg.Content, "GLOBAL_PROMPT_MARKER_2", "Global prompts should be disabled by metadata") + } + } + }) + + t.Run("EnableGlobalPromptsOverrideAssistant", func(t *testing.T) { + // Set global prompts + assistant.SetGlobalPrompts([]store.Prompt{ + {Role: "system", Content: "GLOBAL_ENABLED_MARKER"}, + }) + defer assistant.SetGlobalPrompts(nil) + + // Load fullfields assistant which has disable_global_prompts: true + ast, err := assistant.Get("tests.fullfields") + require.NoError(t, err) + require.True(t, ast.DisableGlobalPrompts) + + ctx := &context.Context{} + + messages := []context.Message{ + {Role: context.RoleUser, Content: "Test enable override"}, + } + + // Hook enables global prompts (overrides assistant's disable) + disableFalse := false + createResponse := &context.HookCreateResponse{ + DisableGlobalPrompts: &disableFalse, + } + + finalMessages, _, err := ast.BuildRequest(ctx, messages, createResponse) + require.NoError(t, err) + + // Should have global prompt (hook enabled it) + found := false + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem && msg.Content == "GLOBAL_ENABLED_MARKER" { + found = true + break + } + } + assert.True(t, found, "Global prompts should be enabled by hook override") + }) +} + +// TestPromptPresetAssistant tests the tests.promptpreset assistant with Create Hook +func TestPromptPresetAssistant(t *testing.T) { + testutils.Prepare(t) + defer testutils.Clean(t) + + t.Run("LoadPromptPresetAssistant", func(t *testing.T) { + ast, err := assistant.Get("tests.promptpreset") + require.NoError(t, err) + require.NotNil(t, ast) + + assert.Equal(t, "tests.promptpreset", ast.ID) + assert.Equal(t, "Prompt Preset Test", ast.Name) + assert.False(t, ast.DisableGlobalPrompts) + + // Should have prompt presets loaded + require.NotNil(t, ast.PromptPresets) + assert.Contains(t, ast.PromptPresets, "mode.friendly") + assert.Contains(t, ast.PromptPresets, "mode.professional") + + // Should have script + assert.NotNil(t, ast.Script) + }) + + t.Run("CreateHookSelectsFriendlyPreset", func(t *testing.T) { + ast, err := assistant.Get("tests.promptpreset") + require.NoError(t, err) + + ctx := newPromptTestContext("chat-friendly-test", "tests.promptpreset") + + messages := []context.Message{ + {Role: context.RoleUser, Content: "use friendly mode please"}, + } + + // Call Create hook + createResponse, err := ast.Script.Create(ctx, messages) + require.NoError(t, err) + require.NotNil(t, createResponse) + assert.Equal(t, "mode.friendly", createResponse.PromptPreset) + + // Build request + finalMessages, _, err := ast.BuildRequest(ctx, messages, createResponse) + require.NoError(t, err) + + // Should have friendly preset marker in one of the system messages + found := false + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem && containsString(msg.Content, "FRIENDLY_PRESET_MARKER") { + found = true + break + } + } + assert.True(t, found, "Should use friendly preset from Create Hook") + }) + + t.Run("CreateHookSelectsProfessionalPreset", func(t *testing.T) { + ast, err := assistant.Get("tests.promptpreset") + require.NoError(t, err) + + ctx := newPromptTestContext("chat-professional-test", "tests.promptpreset") + + messages := []context.Message{ + {Role: context.RoleUser, Content: "use professional tone"}, + } + + // Call Create hook + createResponse, err := ast.Script.Create(ctx, messages) + require.NoError(t, err) + require.NotNil(t, createResponse) + assert.Equal(t, "mode.professional", createResponse.PromptPreset) + + // Build request + finalMessages, _, err := ast.BuildRequest(ctx, messages, createResponse) + require.NoError(t, err) + + // Should have professional preset marker in one of the system messages + found := false + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem && containsString(msg.Content, "PROFESSIONAL_PRESET_MARKER") { + found = true + break + } + } + assert.True(t, found, "Should use professional preset from Create Hook") + }) + + t.Run("CreateHookDisablesGlobalPrompts", func(t *testing.T) { + // Set global prompts + assistant.SetGlobalPrompts([]store.Prompt{ + {Role: "system", Content: "GLOBAL_MARKER_FOR_DISABLE_TEST"}, + }) + defer assistant.SetGlobalPrompts(nil) + + ast, err := assistant.Get("tests.promptpreset") + require.NoError(t, err) + + ctx := newPromptTestContext("chat-disable-global-test", "tests.promptpreset") + + messages := []context.Message{ + {Role: context.RoleUser, Content: "disable global prompts"}, + } + + // Call Create hook + createResponse, err := ast.Script.Create(ctx, messages) + require.NoError(t, err) + require.NotNil(t, createResponse) + require.NotNil(t, createResponse.DisableGlobalPrompts) + assert.True(t, *createResponse.DisableGlobalPrompts) + + // Build request + finalMessages, _, err := ast.BuildRequest(ctx, messages, createResponse) + require.NoError(t, err) + + // Should NOT have global prompt + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem { + assert.NotContains(t, msg.Content, "GLOBAL_MARKER_FOR_DISABLE_TEST") + } + } + }) + + t.Run("CreateHookPresetAndDisableGlobal", func(t *testing.T) { + // Set global prompts + assistant.SetGlobalPrompts([]store.Prompt{ + {Role: "system", Content: "GLOBAL_MARKER_COMBINED_TEST"}, + }) + defer assistant.SetGlobalPrompts(nil) + + ast, err := assistant.Get("tests.promptpreset") + require.NoError(t, err) + + ctx := newPromptTestContext("chat-combined-test", "tests.promptpreset") + + messages := []context.Message{ + {Role: context.RoleUser, Content: "friendly no global"}, + } + + // Call Create hook + createResponse, err := ast.Script.Create(ctx, messages) + require.NoError(t, err) + require.NotNil(t, createResponse) + assert.Equal(t, "mode.friendly", createResponse.PromptPreset) + require.NotNil(t, createResponse.DisableGlobalPrompts) + assert.True(t, *createResponse.DisableGlobalPrompts) + + // Build request + finalMessages, _, err := ast.BuildRequest(ctx, messages, createResponse) + require.NoError(t, err) + + // Should have friendly preset but NOT global + hasFriendly := false + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem { + assert.NotContains(t, msg.Content, "GLOBAL_MARKER_COMBINED_TEST") + if containsString(msg.Content, "FRIENDLY_PRESET_MARKER") { + hasFriendly = true + } + } + } + assert.True(t, hasFriendly, "Should have friendly preset") + }) + + t.Run("CreateHookUnknownPresetFallback", func(t *testing.T) { + ast, err := assistant.Get("tests.promptpreset") + require.NoError(t, err) + + ctx := newPromptTestContext("chat-unknown-preset-test", "tests.promptpreset") + + messages := []context.Message{ + {Role: context.RoleUser, Content: "unknown preset test"}, + } + + // Call Create hook + createResponse, err := ast.Script.Create(ctx, messages) + require.NoError(t, err) + require.NotNil(t, createResponse) + assert.Equal(t, "non.existent.preset", createResponse.PromptPreset) + + // Build request - should not error, fallback to default + finalMessages, _, err := ast.BuildRequest(ctx, messages, createResponse) + require.NoError(t, err) + + // Should fallback to default prompts + found := false + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem && containsString(msg.Content, "DEFAULT_PROMPT_MARKER") { + found = true + break + } + } + assert.True(t, found, "Should fallback to default prompts when preset not found") + }) + + t.Run("CreateHookReturnsNull", func(t *testing.T) { + ast, err := assistant.Get("tests.promptpreset") + require.NoError(t, err) + + ctx := newPromptTestContext("chat-null-test", "tests.promptpreset") + + messages := []context.Message{ + {Role: context.RoleUser, Content: "just a normal message"}, + } + + // Call Create hook - should return nil + createResponse, err := ast.Script.Create(ctx, messages) + require.NoError(t, err) + assert.Nil(t, createResponse) + + // Build request with nil createResponse + finalMessages, _, err := ast.BuildRequest(ctx, messages, nil) + require.NoError(t, err) + + // Should use default prompts + found := false + for _, msg := range finalMessages { + if msg.Role == context.RoleSystem && containsString(msg.Content, "DEFAULT_PROMPT_MARKER") { + found = true + break + } + } + assert.True(t, found, "Should use default prompts when hook returns null") + }) } diff --git a/agent/context/types.go b/agent/context/types.go index 4a5f6587..d62cc1e5 100644 --- a/agent/context/types.go +++ b/agent/context/types.go @@ -326,6 +326,10 @@ type HookCreateResponse struct { // MCP configuration - allow hook to add/override MCP servers for this request MCPServers []MCPServerConfig `json:"mcp_servers,omitempty"` + // Prompt configuration + PromptPreset string `json:"prompt_preset,omitempty"` // Select prompt preset (e.g., "chat.friendly", "task.analysis") + DisableGlobalPrompts *bool `json:"disable_global_prompts,omitempty"` // Temporarily disable global prompts for this request + // Context adjustments - allow hook to modify context fields AssistantID string `json:"assistant_id,omitempty"` // Override assistant ID Connector string `json:"connector,omitempty"` // Override connector From 4b747a5bfaa0a8b7d665a36529ec0f04158a1fd7 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 2 Dec 2025 16:54:57 +0800 Subject: [PATCH 7/7] Add GetStorage function and enhance loadSource logic - Introduced GetStorage function for testing purposes, allowing retrieval of the current storage instance. - Enhanced loadSource function to load scripts from the source field if present, improving assistant initialization. - Updated comments for clarity on TypeScript handling in loadSource, ensuring better understanding of script loading mechanics. --- agent/assistant/load.go | 14 + agent/assistant/load_store_test.go | 937 +++++++++++++++++++++++++++++ agent/assistant/source.go | 11 +- 3 files changed, 958 insertions(+), 4 deletions(-) create mode 100644 agent/assistant/load_store_test.go diff --git a/agent/assistant/load.go b/agent/assistant/load.go index e62b6789..c6c8d524 100644 --- a/agent/assistant/load.go +++ b/agent/assistant/load.go @@ -131,6 +131,11 @@ func SetStorage(s store.Store) { storage = s } +// GetStorage returns the storage (for testing purposes) +func GetStorage() store.Store { + return storage +} + // SetModelCapabilities set the model capabilities configuration func SetModelCapabilities(capabilities map[string]gouOpenAI.Capabilities) { modelCapabilities = capabilities @@ -214,6 +219,15 @@ func LoadStore(id string) (*Assistant, error) { // Create assistant from store model assistant = &Assistant{AssistantModel: *storeModel} + // Load script from source field if present + if assistant.Source != "" { + script, err := loadSource(assistant.Source, assistant.ID) + if err != nil { + return nil, err + } + assistant.Script = script + } + // Initialize the assistant err = assistant.initialize() if err != nil { diff --git a/agent/assistant/load_store_test.go b/agent/assistant/load_store_test.go new file mode 100644 index 00000000..3eaba542 --- /dev/null +++ b/agent/assistant/load_store_test.go @@ -0,0 +1,937 @@ +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" + 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.Script, "Script 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.Script, "Script 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 { + return &context.Context{ + Context: stdContext.Background(), + ChatID: chatID, + AssistantID: assistantID, + Connector: "", + Locale: "en-us", + Theme: "light", + Client: context.Client{ + Type: "web", + UserAgent: "TestAgent/1.0", + IP: "127.0.0.1", + }, + Referer: context.RefererAPI, + Accept: context.AcceptWebCUI, + Route: "", + Metadata: make(map[string]interface{}), + 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", + }, + } +} + +// 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.Script, "Script should be compiled from Source") + + // Verify the script object exists and is usable + assert.NotNil(t, loaded.Script.Script) + + // Execute the Create hook + ctx := newStoreTestContext("test-chat-id", assistantID) + messages := []context.Message{{Role: "user", Content: "Hello"}} + + res, err := loaded.Script.Create(ctx, messages) + 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"}, + }, + 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.Script) + assert.NotEmpty(t, loaded.Source) + + // 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.Script.Create(ctx, messages) + 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.Script, "Script 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.Script.Create(ctx, messages) + 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.Script) + + ctx := newStoreTestContext("null-test-chat", assistantID) + messages := []context.Message{{Role: "user", Content: "Hello"}} + + res, err := loaded.Script.Create(ctx, messages) + 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.Script) + + // 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.Script.Create(ctx, messages) + 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.Script.Create(ctx, messages) + 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.Script.Create(ctx, messages) + 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.Script) + + // 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.Script.Create(ctx, messages) + 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.Script.Create(ctx, messages) + require.NoError(t, err) + require.NotNil(t, res) + require.NotNil(t, res.DisableGlobalPrompts) + assert.False(t, *res.DisableGlobalPrompts) + }) +} diff --git a/agent/assistant/source.go b/agent/assistant/source.go index a218ffb9..f2468e90 100644 --- a/agent/assistant/source.go +++ b/agent/assistant/source.go @@ -2,6 +2,7 @@ package assistant import ( "fmt" + "strings" "time" v8 "github.com/yaoapp/gou/runtime/v8" @@ -9,17 +10,19 @@ import ( ) // loadSource loads hook script from source code string -// The source field stores TypeScript code directly +// The source field stores TypeScript code directly (but without imports) // Priority: script field > source field (if script exists, source is ignored) +// Note: Uses MakeScriptInMemory which supports TypeScript syntax without file resolution. func loadSource(source string, assistantID string) (*hook.Script, error) { if source == "" { return nil, nil } - // Generate a virtual file path for the script - file := fmt.Sprintf("assistants/%s/source.ts", assistantID) + // Use virtual .ts path for TypeScript support + // MakeScriptInMemory handles TypeScript transform without file system access + virtualFile := fmt.Sprintf("assistants/%s/source.ts", strings.ReplaceAll(assistantID, ".", "/")) - script, err := v8.MakeScript([]byte(source), file, 5*time.Second, true) + script, err := v8.MakeScriptInMemory([]byte(source), virtualFile, 5*time.Second, true) if err != nil { return nil, fmt.Errorf("failed to compile source script: %w", err) }